Local snapshot for Docker build (includes mod-ale)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AsioHacksFwd_h__
|
||||
#define AsioHacksFwd_h__
|
||||
|
||||
/**
|
||||
Collection of forward declarations to improve compile time
|
||||
*/
|
||||
namespace boost::posix_time
|
||||
{
|
||||
class ptime;
|
||||
}
|
||||
|
||||
namespace boost::asio
|
||||
{
|
||||
template <typename Time>
|
||||
struct time_traits;
|
||||
}
|
||||
|
||||
namespace boost::asio::ip
|
||||
{
|
||||
class address;
|
||||
class tcp;
|
||||
|
||||
template <typename InternetProtocol>
|
||||
class basic_endpoint;
|
||||
|
||||
typedef basic_endpoint<tcp> tcp_endpoint;
|
||||
}
|
||||
|
||||
namespace Acore::Asio
|
||||
{
|
||||
class DeadlineTimer;
|
||||
class IoContext;
|
||||
class Resolver;
|
||||
class Strand;
|
||||
}
|
||||
|
||||
#endif // AsioHacksFwd_h__
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef IoContext_h__
|
||||
#define IoContext_h__
|
||||
|
||||
#include <boost/version.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#define IoContextBaseNamespace boost::asio
|
||||
#define IoContextBase io_context
|
||||
|
||||
namespace Acore::Asio
|
||||
{
|
||||
class IoContext
|
||||
{
|
||||
public:
|
||||
IoContext() : _impl() { }
|
||||
explicit IoContext(int concurrency_hint) : _impl(concurrency_hint) { }
|
||||
|
||||
operator IoContextBaseNamespace::IoContextBase&() { return _impl; }
|
||||
operator IoContextBaseNamespace::IoContextBase const&() const { return _impl; }
|
||||
|
||||
std::size_t run() { return _impl.run(); }
|
||||
void stop() { _impl.stop(); }
|
||||
|
||||
boost::asio::io_context::executor_type get_executor() noexcept { return _impl.get_executor(); }
|
||||
|
||||
private:
|
||||
IoContextBaseNamespace::IoContextBase _impl;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline decltype(auto) post(IoContextBaseNamespace::IoContextBase& ioContext, T&& t)
|
||||
{
|
||||
return boost::asio::post(ioContext, std::forward<T>(t));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline boost::asio::io_context& get_io_context(T&& ioObject)
|
||||
{
|
||||
return static_cast<boost::asio::io_context&>(ioObject.get_executor().context());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // IoContext_h__
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 IpAddress_h__
|
||||
#define IpAddress_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
namespace Acore::Net
|
||||
{
|
||||
using boost::asio::ip::make_address;
|
||||
using boost::asio::ip::make_address_v4;
|
||||
inline uint32 address_to_uint(boost::asio::ip::address_v4 const& address) { return address.to_uint(); }
|
||||
}
|
||||
|
||||
#endif // IpAddress_h__
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef IpNetwork_h__
|
||||
#define IpNetwork_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include "IpAddress.h"
|
||||
#include <boost/asio/ip/network_v4.hpp>
|
||||
#include <boost/asio/ip/network_v6.hpp>
|
||||
|
||||
namespace Acore::Net
|
||||
{
|
||||
inline bool IsInNetwork(boost::asio::ip::address_v4 const& networkAddress, boost::asio::ip::address_v4 const& mask, boost::asio::ip::address_v4 const& clientAddress)
|
||||
{
|
||||
boost::asio::ip::network_v4 network = boost::asio::ip::make_network_v4(networkAddress, mask);
|
||||
boost::asio::ip::address_v4_range hosts = network.hosts();
|
||||
return hosts.find(clientAddress) != hosts.end();
|
||||
}
|
||||
|
||||
inline boost::asio::ip::address_v4 GetDefaultNetmaskV4(boost::asio::ip::address_v4 const& networkAddress)
|
||||
{
|
||||
if ((address_to_uint(networkAddress) & 0x80000000) == 0)
|
||||
{
|
||||
return boost::asio::ip::address_v4(0xFF000000);
|
||||
}
|
||||
if ((address_to_uint(networkAddress) & 0xC0000000) == 0x80000000)
|
||||
{
|
||||
return boost::asio::ip::address_v4(0xFFFF0000);
|
||||
}
|
||||
if ((address_to_uint(networkAddress) & 0xE0000000) == 0xC0000000)
|
||||
{
|
||||
return boost::asio::ip::address_v4(0xFFFFFF00);
|
||||
}
|
||||
return boost::asio::ip::address_v4(0xFFFFFFFF);
|
||||
}
|
||||
|
||||
inline bool IsInNetwork(boost::asio::ip::address_v6 const& networkAddress, uint16 prefixLength, boost::asio::ip::address_v6 const& clientAddress)
|
||||
{
|
||||
boost::asio::ip::network_v6 network = boost::asio::ip::make_network_v6(networkAddress, prefixLength);
|
||||
boost::asio::ip::address_v6_range hosts = network.hosts();
|
||||
return hosts.find(clientAddress) != hosts.end();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // IpNetwork_h__
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef Resolver_h__
|
||||
#define Resolver_h__
|
||||
|
||||
#include "Optional.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace Acore::Asio
|
||||
{
|
||||
/**
|
||||
Hack to make it possible to forward declare resolver (one of its template arguments is a typedef to something super long and using nested classes)
|
||||
*/
|
||||
class Resolver
|
||||
{
|
||||
public:
|
||||
explicit Resolver(IoContext& ioContext) : _impl(ioContext) { }
|
||||
|
||||
Optional<boost::asio::ip::tcp::endpoint> Resolve(boost::asio::ip::tcp const& protocol, std::string const& host, std::string const& service)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
boost::asio::ip::resolver_base::flags flagsResolver = boost::asio::ip::resolver_base::all_matching;
|
||||
boost::asio::ip::tcp::resolver::results_type results = _impl.resolve(protocol, host, service, flagsResolver, ec);
|
||||
if (results.begin() == results.end() || ec)
|
||||
return {};
|
||||
|
||||
return results.begin()->endpoint();
|
||||
}
|
||||
|
||||
private:
|
||||
boost::asio::ip::tcp::resolver _impl;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // Resolver_h__
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 _STEADYTIMER_H
|
||||
#define _STEADYTIMER_H
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Acore::Asio::SteadyTimer
|
||||
{
|
||||
inline auto GetExpirationTime(int32 seconds)
|
||||
{
|
||||
return std::chrono::steady_clock::now() + std::chrono::seconds(seconds);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _STEADYTIMER_H
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef Strand_h__
|
||||
#define Strand_h__
|
||||
|
||||
#include "IoContext.h"
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
|
||||
namespace Acore::Asio
|
||||
{
|
||||
/**
|
||||
Hack to make it possible to forward declare strand (which is a inner class)
|
||||
*/
|
||||
class Strand : public IoContextBaseNamespace::IoContextBase::strand
|
||||
{
|
||||
public:
|
||||
Strand(IoContext& ioContext) : IoContextBaseNamespace::IoContextBase::strand(ioContext) { }
|
||||
};
|
||||
|
||||
using boost::asio::bind_executor;
|
||||
}
|
||||
|
||||
#endif // Strand_h__
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Banner.h"
|
||||
#include "GitRevision.h"
|
||||
#include "StringFormat.h"
|
||||
|
||||
void Acore::Banner::Show(std::string_view applicationName, void(*log)(std::string_view text), void(*logExtraInfo)())
|
||||
{
|
||||
log(Acore::StringFormat("{} ({})", GitRevision::GetFullVersion(), applicationName));
|
||||
log("<Ctrl-C> to stop.\n");
|
||||
log(" █████╗ ███████╗███████╗██████╗ ██████╗ ████████╗██╗ ██╗");
|
||||
log(" ██╔══██╗╚══███╔╝██╔════╝██╔══██╗██╔═══██╗╚══██╔══╝██║ ██║");
|
||||
log(" ███████║ ███╔╝ █████╗ ██████╔╝██║ ██║ ██║ ███████║");
|
||||
log(" ██╔══██║ ███╔╝ ██╔══╝ ██╔══██╗██║ ██║ ██║ ██╔══██║");
|
||||
log(" ██║ ██║███████╗███████╗██║ ██║╚██████╔╝ ██║ ██║ ██║");
|
||||
log(" ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝");
|
||||
log(" ██████╗ ██████╗ ██████╗ ███████╗");
|
||||
log(" ██╔════╝██╔═══██╗██╔══██╗██╔════╝");
|
||||
log(" ██║ ██║ ██║██████╔╝█████╗");
|
||||
log(" ██║ ██║ ██║██╔══██╗██╔══╝");
|
||||
log(" ╚██████╗╚██████╔╝██║ ██║███████╗");
|
||||
log(" ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝\n");
|
||||
log(" AzerothCore 3.3.5a - www.azerothcore.org\n");
|
||||
|
||||
if (logExtraInfo)
|
||||
{
|
||||
logExtraInfo();
|
||||
}
|
||||
|
||||
log(" ");
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_BANNER_H
|
||||
#define AZEROTHCORE_BANNER_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <string_view>
|
||||
|
||||
namespace Acore::Banner
|
||||
{
|
||||
AC_COMMON_API void Show(std::string_view applicationName, void(*log)(std::string_view text), void(*logExtraInfo)());
|
||||
}
|
||||
|
||||
#endif // AZEROTHCORE_BANNER_H
|
||||
@@ -0,0 +1,94 @@
|
||||
#
|
||||
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
#
|
||||
# This file is free software; as a special exception the author gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
|
||||
CollectSourceFiles(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PRIVATE_SOURCES
|
||||
# Exclude
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Debugging
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Platform
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Collision
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Navigation
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)
|
||||
|
||||
if (BUILD_APPLICATION_WORLDSERVER OR BUILD_TOOLS_MAPS)
|
||||
unset(PRIVATE_SOURCES)
|
||||
CollectSourceFiles(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PRIVATE_SOURCES
|
||||
# Exclude
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Debugging
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Platform
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)
|
||||
endif()
|
||||
|
||||
# Manually set sources for Debugging directory as we don't want to include WheatyExceptionReport in common project
|
||||
# It needs to be included both in authserver and worldserver for the static global variable to be properly initialized
|
||||
# and to handle crash logs on windows
|
||||
list(APPEND PRIVATE_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Debugging/Errors.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Debugging/Errors.h)
|
||||
|
||||
if (USE_COREPCH)
|
||||
set(PRIVATE_PCH_HEADER PrecompiledHeaders/commonPCH.h)
|
||||
endif()
|
||||
|
||||
# Group sources
|
||||
GroupSources(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
add_library(common
|
||||
${PRIVATE_SOURCES})
|
||||
|
||||
CollectIncludeDirectories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PUBLIC_INCLUDES
|
||||
# Exclude
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders)
|
||||
|
||||
target_include_directories(common
|
||||
PUBLIC
|
||||
# Provide the binary dir for all child targets
|
||||
${CMAKE_BINARY_DIR}
|
||||
${PUBLIC_INCLUDES}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_link_libraries(common
|
||||
PRIVATE
|
||||
acore-core-interface
|
||||
PUBLIC
|
||||
boost
|
||||
argon2
|
||||
sfmt
|
||||
utf8cpp
|
||||
openssl
|
||||
threads
|
||||
jemalloc
|
||||
stdfs
|
||||
fmt)
|
||||
|
||||
if (BUILD_APPLICATION_WORLDSERVER OR BUILD_TOOLS_MAPS)
|
||||
target_link_libraries(common
|
||||
PUBLIC
|
||||
g3dlib
|
||||
Detour)
|
||||
endif()
|
||||
|
||||
set_target_properties(common
|
||||
PROPERTIES
|
||||
FOLDER
|
||||
"server")
|
||||
|
||||
# Generate precompiled header
|
||||
if (USE_COREPCH)
|
||||
add_cxx_pch(common ${PRIVATE_PCH_HEADER})
|
||||
endif ()
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "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
|
||||
// cppcheck-suppress integerOverflow
|
||||
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
|
||||
right = rightOrig;
|
||||
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 (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));
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _BIH_H
|
||||
#define _BIH_H
|
||||
|
||||
#include "G3D/AABox.h"
|
||||
#include "G3D/Ray.h"
|
||||
#include "G3D/Vector3.h"
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#define MAX_STACK_SIZE 64
|
||||
|
||||
// https://stackoverflow.com/a/4328396
|
||||
|
||||
static inline uint32 floatToRawIntBits(float f)
|
||||
{
|
||||
static_assert(sizeof(float) == sizeof(uint32), "Size of uint32 and float must be equal for this to work");
|
||||
uint32 ret;
|
||||
memcpy(&ret, &f, sizeof(float));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline float intBitsToFloat(uint32 i)
|
||||
{
|
||||
static_assert(sizeof(float) == sizeof(uint32), "Size of uint32 and float must be equal for this to work");
|
||||
float ret;
|
||||
memcpy(&ret, &i, sizeof(uint32));
|
||||
return ret;
|
||||
}
|
||||
|
||||
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();
|
||||
bounds = G3D::AABox::empty();
|
||||
// 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;
|
||||
}
|
||||
[[nodiscard]] uint32 primCount() const { return objects.size(); }
|
||||
G3D::AABox const& bound() const { return bounds; }
|
||||
|
||||
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; // cppcheck-suppress integerOverflow
|
||||
bool BVH2 = tn & (1 << 29); // cppcheck-suppress integerOverflow
|
||||
int offset = tn & ~(7 << 29); // cppcheck-suppress integerOverflow
|
||||
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; // cppcheck-suppress integerOverflow
|
||||
bool BVH2 = tn & (1 << 29); // cppcheck-suppress integerOverflow
|
||||
int offset = tn & ~(7 << 29); // cppcheck-suppress integerOverflow
|
||||
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{0};
|
||||
int numLeaves{0};
|
||||
int sumObjects{0};
|
||||
int minObjects{0x0FFFFFFF};
|
||||
int maxObjects{-1}; // 0xFFFFFFFF
|
||||
int sumDepth{0};
|
||||
int minDepth{0x0FFFFFFF};
|
||||
int maxDepth{-1}; // 0xFFFFFFFF
|
||||
int numLeavesN[6];
|
||||
int numBVH2{0};
|
||||
|
||||
public:
|
||||
BuildStats()
|
||||
{
|
||||
for (int& 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; // cppcheck-suppress integerOverflow
|
||||
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
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _BIH_WRAP
|
||||
#define _BIH_WRAP
|
||||
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
#include "G3D/Array.h"
|
||||
#include "G3D/Set.h"
|
||||
#include "G3D/Table.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] = nullptr;
|
||||
}
|
||||
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
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DynamicTree.h"
|
||||
#include "BoundingIntervalHierarchyWrapper.h"
|
||||
#include "GameObjectModel.h"
|
||||
#include "MapTree.h"
|
||||
#include "ModelIgnoreFlags.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "RegularGrid.h"
|
||||
#include "Timer.h"
|
||||
#include "VMapFactory.h"
|
||||
#include "VMapMgr2.h"
|
||||
#include "WorldModel.h"
|
||||
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Ray.h>
|
||||
#include <G3D/Vector3.h>
|
||||
|
||||
using VMAP::ModelInstance;
|
||||
|
||||
namespace
|
||||
{
|
||||
int CHECK_TREE_PERIOD = 200;
|
||||
}
|
||||
|
||||
template<> struct HashTrait< GameObjectModel>
|
||||
{
|
||||
static std::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();}
|
||||
};
|
||||
|
||||
typedef RegularGrid2D<GameObjectModel, BIHWrap<GameObjectModel>> ParentTree;
|
||||
|
||||
struct DynTreeImpl : public ParentTree
|
||||
{
|
||||
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
|
||||
{
|
||||
DynamicTreeIntersectionCallback(uint32 phasemask, VMAP::ModelIgnoreFlags ignoreFlags) :
|
||||
_didHit(false), _phaseMask(phasemask), _ignoreFlags(ignoreFlags) { }
|
||||
|
||||
bool operator()(const G3D::Ray& r, const GameObjectModel& obj, float& distance, bool stopAtFirstHit)
|
||||
{
|
||||
bool result = obj.intersectRay(r, distance, stopAtFirstHit, _phaseMask, _ignoreFlags);
|
||||
if (result)
|
||||
{
|
||||
_didHit = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool didHit() const
|
||||
{
|
||||
return _didHit;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _didHit;
|
||||
uint32 _phaseMask;
|
||||
VMAP::ModelIgnoreFlags _ignoreFlags;
|
||||
};
|
||||
|
||||
struct DynamicTreeLocationInfoCallback
|
||||
{
|
||||
DynamicTreeLocationInfoCallback(uint32 phaseMask)
|
||||
: _phaseMask(phaseMask), _hitModel(nullptr) {}
|
||||
|
||||
void operator()(G3D::Vector3 const& p, GameObjectModel const& obj)
|
||||
{
|
||||
if (obj.GetLocationInfo(p, _locationInfo, _phaseMask))
|
||||
_hitModel = &obj;
|
||||
}
|
||||
|
||||
VMAP::LocationInfo& GetLocationInfo()
|
||||
{
|
||||
return _locationInfo;
|
||||
}
|
||||
GameObjectModel const* GetHitModel() const
|
||||
{
|
||||
return _hitModel;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 _phaseMask;
|
||||
VMAP::LocationInfo _locationInfo;
|
||||
GameObjectModel const* _hitModel;
|
||||
};
|
||||
|
||||
bool DynamicMapTree::GetIntersectionTime(const uint32 phasemask, const G3D::Ray& ray, const G3D::Vector3& endPos, float& maxDist) const
|
||||
{
|
||||
float distance = maxDist;
|
||||
DynamicTreeIntersectionCallback callback(phasemask, VMAP::ModelIgnoreFlags::Nothing);
|
||||
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, VMAP::ModelIgnoreFlags ignoreFlags) 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, ignoreFlags);
|
||||
impl->intersectRay(r, callback, maxDist, v2, true);
|
||||
|
||||
return !callback.didHit();
|
||||
}
|
||||
|
||||
float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const
|
||||
{
|
||||
G3D::Vector3 v(x, y, z);
|
||||
G3D::Ray r(v, G3D::Vector3(0, 0, -1));
|
||||
DynamicTreeIntersectionCallback callback(phasemask, VMAP::ModelIgnoreFlags::Nothing);
|
||||
impl->intersectZAllignedRay(r, callback, maxSearchDist);
|
||||
|
||||
if (callback.didHit())
|
||||
{
|
||||
return v.z - maxSearchDist;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -G3D::finf();
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicMapTree::GetAreaAndLiquidData(float x, float y, float z, uint32 phasemask, Optional<uint8> reqLiquidType, VMAP::AreaAndLiquidData& data) const
|
||||
{
|
||||
G3D::Vector3 v(x, y, z + 0.5f);
|
||||
DynamicTreeLocationInfoCallback intersectionCallBack(phasemask);
|
||||
impl->intersectPoint(v, intersectionCallBack);
|
||||
if (intersectionCallBack.GetLocationInfo().hitModel)
|
||||
{
|
||||
data.floorZ = intersectionCallBack.GetLocationInfo().ground_Z;
|
||||
uint32 liquidType = intersectionCallBack.GetLocationInfo().hitModel->GetLiquidType();
|
||||
float liquidLevel;
|
||||
if (!reqLiquidType || (dynamic_cast<VMAP::VMapMgr2*>(VMAP::VMapFactory::createOrGetVMapMgr())->GetLiquidFlagsPtr(liquidType) & *reqLiquidType))
|
||||
if (intersectionCallBack.GetHitModel()->GetLiquidLevel(v, intersectionCallBack.GetLocationInfo(), liquidLevel))
|
||||
data.liquidInfo.emplace(liquidType, liquidLevel);
|
||||
|
||||
data.areaInfo.emplace(intersectionCallBack.GetLocationInfo().hitModel->GetWmoID(),
|
||||
0,
|
||||
intersectionCallBack.GetLocationInfo().rootId,
|
||||
intersectionCallBack.GetLocationInfo().hitModel->GetMogpFlags(),
|
||||
0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _DYNTREE_H
|
||||
#define _DYNTREE_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Optional.h"
|
||||
|
||||
namespace G3D
|
||||
{
|
||||
class Ray;
|
||||
class Vector3;
|
||||
}
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
struct AreaAndLiquidData;
|
||||
enum class ModelIgnoreFlags : uint32;
|
||||
}
|
||||
|
||||
class GameObjectModel;
|
||||
struct DynTreeImpl;
|
||||
|
||||
class DynamicMapTree
|
||||
{
|
||||
DynTreeImpl* impl;
|
||||
|
||||
public:
|
||||
DynamicMapTree();
|
||||
~DynamicMapTree();
|
||||
|
||||
[[nodiscard]] bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, VMAP::ModelIgnoreFlags ignoreFlags) const;
|
||||
|
||||
bool GetIntersectionTime(uint32 phasemask, const G3D::Ray& ray, const G3D::Vector3& endPos, float& maxDist) const;
|
||||
|
||||
bool GetAreaAndLiquidData(float x, float y, float z, uint32 phasemask, Optional<uint8> reqLiquidType, VMAP::AreaAndLiquidData& data) const;
|
||||
|
||||
bool GetObjectHitPos(uint32 phasemask, const G3D::Vector3& pPos1,
|
||||
const G3D::Vector3& pPos2, G3D::Vector3& pResultHitPos,
|
||||
float pModifyDist) const;
|
||||
|
||||
[[nodiscard]] float getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const;
|
||||
|
||||
void insert(const GameObjectModel&);
|
||||
void remove(const GameObjectModel&);
|
||||
[[nodiscard]] bool contains(const GameObjectModel&) const;
|
||||
[[nodiscard]] int size() const;
|
||||
|
||||
void balance();
|
||||
void update(uint32 diff);
|
||||
};
|
||||
|
||||
#endif // _DYNTREE_H
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _IVMAPMANAGER_H
|
||||
#define _IVMAPMANAGER_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "ModelIgnoreFlags.h"
|
||||
#include "Optional.h"
|
||||
#include <string>
|
||||
|
||||
//===========================================================
|
||||
|
||||
/**
|
||||
This is the minimum interface to the VMapMamager.
|
||||
*/
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class StaticMapTree;
|
||||
|
||||
enum VMAP_LOAD_RESULT
|
||||
{
|
||||
VMAP_LOAD_RESULT_ERROR,
|
||||
VMAP_LOAD_RESULT_OK,
|
||||
VMAP_LOAD_RESULT_IGNORED
|
||||
};
|
||||
|
||||
enum class LoadResult : uint8
|
||||
{
|
||||
Success,
|
||||
FileNotFound,
|
||||
VersionMismatch
|
||||
};
|
||||
|
||||
#define VMAP_INVALID_HEIGHT -100000.0f // for check
|
||||
#define VMAP_INVALID_HEIGHT_VALUE -200000.0f // real assigned value in unknown height case
|
||||
|
||||
struct AreaAndLiquidData
|
||||
{
|
||||
struct AreaInfo
|
||||
{
|
||||
AreaInfo() = default;
|
||||
AreaInfo(int32 _groupId, int32 _adtId, int32 _rootId, uint32 _mogpFlags, uint32 _uniqueId)
|
||||
: groupId(_groupId), adtId(_adtId), rootId(_rootId), mogpFlags(_mogpFlags), uniqueId(_uniqueId) { }
|
||||
int32 groupId = 0;
|
||||
int32 adtId = 0;
|
||||
int32 rootId = 0;
|
||||
uint32 mogpFlags = 0;
|
||||
uint32 uniqueId = 0;
|
||||
};
|
||||
|
||||
struct LiquidInfo
|
||||
{
|
||||
LiquidInfo() = default;
|
||||
LiquidInfo(uint32 _type, float _level)
|
||||
: type(_type), level(_level) {}
|
||||
uint32 type = 0;
|
||||
float level = 0.0f;
|
||||
};
|
||||
|
||||
float floorZ = VMAP_INVALID_HEIGHT;
|
||||
Optional<AreaInfo> areaInfo;
|
||||
Optional<LiquidInfo> liquidInfo;
|
||||
};
|
||||
|
||||
//===========================================================
|
||||
class IVMapMgr
|
||||
{
|
||||
private:
|
||||
bool iEnableLineOfSightCalc{true};
|
||||
bool iEnableHeightCalc{true};
|
||||
|
||||
public:
|
||||
IVMapMgr() { }
|
||||
|
||||
virtual ~IVMapMgr() = default;
|
||||
|
||||
virtual LoadResult existsMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 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; }
|
||||
|
||||
[[nodiscard]] bool isLineOfSightCalcEnabled() const { return (iEnableLineOfSightCalc); }
|
||||
[[nodiscard]] bool isHeightCalcEnabled() const { return (iEnableHeightCalc); }
|
||||
[[nodiscard]] bool isMapLoadingEnabled() const { return (iEnableLineOfSightCalc || iEnableHeightCalc ); }
|
||||
|
||||
[[nodiscard]] virtual std::string getDirFileName(unsigned int pMapId, int x, int y) const = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "MMapMgr.h"
|
||||
#include "Config.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include "MapDefines.h"
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
// ######################## MMapMgr ########################
|
||||
std::shared_ptr<dtNavMesh> MMapMgr::LoadNavMesh(uint32 mapId)
|
||||
{
|
||||
// load and init dtNavMesh - read parameters from file
|
||||
std::string fileName = Acore::StringFormat(MAP_FILE_NAME_FORMAT, sConfigMgr->GetOption<std::string>("DataDir", "."), mapId);
|
||||
|
||||
FILE* file = fopen(fileName.c_str(), "rb");
|
||||
if (!file)
|
||||
{
|
||||
LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not open mmap file '{}'", fileName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
dtNavMeshParams params;
|
||||
uint32 count = uint32(fread(¶ms, sizeof(dtNavMeshParams), 1, file));
|
||||
fclose(file);
|
||||
if (count != 1)
|
||||
{
|
||||
LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not read params from file '{}'", fileName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
dtNavMesh* mesh = dtAllocNavMesh();
|
||||
ASSERT(mesh);
|
||||
if (DT_SUCCESS != mesh->init(¶ms))
|
||||
{
|
||||
dtFreeNavMesh(mesh);
|
||||
LOG_ERROR("maps", "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {:03} from file {}", mapId, fileName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LOG_DEBUG("maps", "MMAP:loadMapData: Loaded {:03}.mmap", mapId);
|
||||
|
||||
std::shared_ptr<dtNavMesh> navMesh = std::shared_ptr<dtNavMesh>(mesh, NavMeshDeleter());
|
||||
return navMesh;
|
||||
}
|
||||
|
||||
uint32 MMapMgr::packTileID(int32 x, int32 y)
|
||||
{
|
||||
return uint32(x << 16 | y);
|
||||
}
|
||||
|
||||
bool MMapMgr::LoadTile(dtNavMesh* navMesh, uint32 mapId, int32 x, int32 y)
|
||||
{
|
||||
// load this tile :: mmaps/MMMXXYY.mmtile
|
||||
std::string fileName = Acore::StringFormat(TILE_FILE_NAME_FORMAT, sConfigMgr->GetOption<std::string>("DataDir", "."), mapId, x, y);
|
||||
FILE* file = fopen(fileName.c_str(), "rb");
|
||||
if (!file)
|
||||
{
|
||||
LOG_DEBUG("maps", "MMAP:loadMap: Could not open mmtile file '{}'", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// read header
|
||||
MmapTileHeader fileHeader;
|
||||
if (fread(&fileHeader, sizeof(MmapTileHeader), 1, file) != 1 || fileHeader.mmapMagic != MMAP_MAGIC)
|
||||
{
|
||||
LOG_ERROR("maps", "MMAP:loadMap: Bad header in mmap {:03}{:02}{:02}.mmtile", mapId, x, y);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fileHeader.mmapVersion != MMAP_VERSION)
|
||||
{
|
||||
LOG_ERROR("maps", "MMAP:loadMap: {:03}{:02}{:02}.mmtile was built with generator v{}, expected v{}",
|
||||
mapId, x, y, fileHeader.mmapVersion, MMAP_VERSION);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char* data = (unsigned char*)dtAlloc(fileHeader.size, DT_ALLOC_PERM);
|
||||
ASSERT(data);
|
||||
|
||||
std::size_t result = fread(data, fileHeader.size, 1, file);
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("maps", "MMAP:loadMap: Bad header or data in mmap {:03}{:02}{:02}.mmtile", mapId, x, y);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
dtTileRef tileRef = 0;
|
||||
|
||||
// memory allocated for data is now managed by detour, and will be deallocated when the tile is removed
|
||||
if (dtStatusSucceed(navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef)))
|
||||
{
|
||||
dtMeshHeader* header = (dtMeshHeader*)data;
|
||||
LOG_DEBUG("maps", "MMAP:loadMap: Loaded mmtile {:03}[{:02},{:02}] into {:03}[{:02},{:02}]", mapId, x, y, mapId, header->x, header->y);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_ERROR("maps", "MMAP:loadMap: Could not load {:03}{:02}{:02}.mmtile into navmesh", mapId, x, y);
|
||||
dtFree(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
ManagedNavMeshQuery MMapMgr::CreateNavMeshQuery(dtNavMesh* navMesh)
|
||||
{
|
||||
// allocate mesh query
|
||||
dtNavMeshQuery* query = dtAllocNavMeshQuery();
|
||||
ASSERT(query);
|
||||
|
||||
if (dtStatusFailed(query->init(navMesh, 1024)))
|
||||
{
|
||||
dtFreeNavMeshQuery(query);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ManagedNavMeshQuery navMeshQuery = ManagedNavMeshQuery(query);
|
||||
return navMeshQuery;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _MMAP_MANAGER_H
|
||||
#define _MMAP_MANAGER_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourExtended.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include <memory>
|
||||
|
||||
// memory management
|
||||
inline void* dtCustomAlloc(std::size_t size, dtAllocHint /*hint*/)
|
||||
{
|
||||
return (void*)new unsigned char[size];
|
||||
}
|
||||
|
||||
inline void dtCustomFree(void* ptr)
|
||||
{
|
||||
delete [] (unsigned char*)ptr;
|
||||
}
|
||||
|
||||
// move map related classes
|
||||
namespace MMAP
|
||||
{
|
||||
enum MMAP_LOAD_RESULT
|
||||
{
|
||||
MMAP_LOAD_RESULT_ERROR,
|
||||
MMAP_LOAD_RESULT_OK,
|
||||
MMAP_LOAD_RESULT_IGNORED,
|
||||
};
|
||||
|
||||
static char const* const MAP_FILE_NAME_FORMAT = "{}/mmaps/{:03}.mmap";
|
||||
static char const* const TILE_FILE_NAME_FORMAT = "{}/mmaps/{:03}{:02}{:02}.mmtile";
|
||||
|
||||
struct NavMeshDeleter
|
||||
{
|
||||
void operator()(dtNavMesh* navMesh) noexcept { dtFreeNavMesh(navMesh); }
|
||||
};
|
||||
|
||||
struct NavMeshQueryDeleter
|
||||
{
|
||||
void operator()(dtNavMeshQuery* query) noexcept { dtFreeNavMeshQuery(query); }
|
||||
};
|
||||
|
||||
using ManagedNavMeshQuery = std::unique_ptr<dtNavMeshQuery, NavMeshQueryDeleter>;
|
||||
|
||||
class MMapMgr
|
||||
{
|
||||
public:
|
||||
MMapMgr() = default;
|
||||
~MMapMgr() = default;
|
||||
|
||||
static std::shared_ptr<dtNavMesh> LoadNavMesh(uint32 mapId);
|
||||
static bool LoadTile(dtNavMesh* navMesh, uint32 mapId, int32 x, int32 y);
|
||||
static ManagedNavMeshQuery CreateNavMeshQuery(dtNavMesh* navMesh);
|
||||
|
||||
private:
|
||||
static uint32 packTileID(int32 x, int32 y);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "VMapFactory.h"
|
||||
#include "VMapMgr2.h"
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
VMapMgr2* gVMapMgr = nullptr;
|
||||
|
||||
//===============================================
|
||||
// just return the instance
|
||||
VMapMgr2* VMapFactory::createOrGetVMapMgr()
|
||||
{
|
||||
if (!gVMapMgr)
|
||||
{
|
||||
gVMapMgr = new VMapMgr2();
|
||||
}
|
||||
|
||||
return gVMapMgr;
|
||||
}
|
||||
|
||||
//===============================================
|
||||
// delete all internal data structures
|
||||
void VMapFactory::clear()
|
||||
{
|
||||
delete gVMapMgr;
|
||||
gVMapMgr = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _VMAPFACTORY_H
|
||||
#define _VMAPFACTORY_H
|
||||
|
||||
// This is the access point to the VMapMgr.
|
||||
namespace VMAP
|
||||
{
|
||||
class VMapMgr2;
|
||||
|
||||
class VMapFactory
|
||||
{
|
||||
public:
|
||||
static VMapMgr2* createOrGetVMapMgr();
|
||||
static void clear();
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>
|
||||
*
|
||||
* 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 "VMapMgr2.h"
|
||||
#include "Errors.h"
|
||||
#include "MapDefines.h"
|
||||
#include "MapTree.h"
|
||||
#include <G3D/Vector3.h>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using G3D::Vector3;
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
VMapMgr2::VMapMgr2()
|
||||
{
|
||||
GetLiquidFlagsPtr = &GetLiquidFlagsDummy;
|
||||
IsVMAPDisabledForPtr = &IsVMAPDisabledForDummy;
|
||||
}
|
||||
|
||||
VMapMgr2::~VMapMgr2()
|
||||
{
|
||||
}
|
||||
|
||||
Vector3 VMapMgr2::convertPositionToInternalRep(float x, float y, float z)
|
||||
{
|
||||
Vector3 pos;
|
||||
const float mid = 0.5f * MAX_NUMBER_OF_GRIDS * SIZE_OF_GRIDS;
|
||||
pos.x = mid - x;
|
||||
pos.y = mid - y;
|
||||
pos.z = z;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
// move to MapTree too?
|
||||
std::string VMapMgr2::getMapFileName(unsigned int mapId)
|
||||
{
|
||||
std::stringstream fname;
|
||||
fname.width(3);
|
||||
fname << std::setfill('0') << mapId << std::string(MAP_FILENAME_EXTENSION2);
|
||||
|
||||
return fname.str();
|
||||
}
|
||||
|
||||
LoadResult VMapMgr2::existsMap(const char* basePath, unsigned int mapId, int x, int y)
|
||||
{
|
||||
return StaticMapTree::CanLoadMap(std::string(basePath), mapId, x, y);
|
||||
}
|
||||
|
||||
} // namespace VMAP
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _VMAPMANAGER2_H
|
||||
#define _VMAPMANAGER2_H
|
||||
|
||||
#include "IVMapMgr.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
|
||||
{
|
||||
enum DisableTypes
|
||||
{
|
||||
VMAP_DISABLE_AREAFLAG = 0x1,
|
||||
VMAP_DISABLE_HEIGHT = 0x2,
|
||||
VMAP_DISABLE_LOS = 0x4,
|
||||
VMAP_DISABLE_LIQUIDSTATUS = 0x8
|
||||
};
|
||||
|
||||
class VMapMgr2 : public IVMapMgr
|
||||
{
|
||||
protected:
|
||||
static uint32 GetLiquidFlagsDummy(uint32) { return 0; }
|
||||
static bool IsVMAPDisabledForDummy(uint32 /*entry*/, uint8 /*flags*/) { return false; }
|
||||
|
||||
public:
|
||||
// public for debug
|
||||
static G3D::Vector3 convertPositionToInternalRep(float x, float y, float z);
|
||||
static std::string getMapFileName(unsigned int mapId);
|
||||
|
||||
VMapMgr2();
|
||||
~VMapMgr2() override;
|
||||
|
||||
bool processCommand(char* /*command*/) override { return false; } // for debug and extensions
|
||||
|
||||
// what's the use of this? o.O
|
||||
[[nodiscard]] std::string getDirFileName(unsigned int mapId, int /*x*/, int /*y*/) const override
|
||||
{
|
||||
return getMapFileName(mapId);
|
||||
}
|
||||
LoadResult existsMap(const char* basePath, unsigned int mapId, int x, int y) override;
|
||||
|
||||
typedef uint32(*GetLiquidFlagsFn)(uint32 liquidType);
|
||||
GetLiquidFlagsFn GetLiquidFlagsPtr;
|
||||
|
||||
typedef bool(*IsVMAPDisabledForFn)(uint32 entry, uint8 flags);
|
||||
IsVMAPDisabledForFn IsVMAPDisabledForPtr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Log.h"
|
||||
#include "WorldModelStore.h"
|
||||
|
||||
std::shared_ptr<VMAP::WorldModel> WorldModelStore::AcquireModelInstance(std::string const& basepath, std::string const& filename, uint32 flags/* Only used when creating the model */)
|
||||
{
|
||||
//! Critical section, thread safe access
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
|
||||
ModelFileMap::iterator model = _loadedModels.find(filename);
|
||||
if (model == _loadedModels.end())
|
||||
{
|
||||
std::shared_ptr<VMAP::WorldModel> worldmodel = std::make_shared<VMAP::WorldModel>();
|
||||
LOG_DEBUG("maps", "WorldModelStore: loading file '{}{}'", basepath, filename);
|
||||
if (!worldmodel->readFile(basepath + filename + ".vmo"))
|
||||
{
|
||||
LOG_ERROR("maps", "WorldModelStore: could not load '{}{}.vmo'", basepath, filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
worldmodel->Flags = flags;
|
||||
|
||||
model = _loadedModels.insert(std::pair<std::string, std::shared_ptr<VMAP::WorldModel>>(filename, worldmodel)).first;
|
||||
}
|
||||
|
||||
return model->second;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _WORLDMODELSTORE_H
|
||||
#define _WORLDMODELSTORE_H
|
||||
|
||||
#include "WorldModel.h"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
class WorldModelStore
|
||||
{
|
||||
public:
|
||||
static WorldModelStore* instance()
|
||||
{
|
||||
static WorldModelStore instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
std::shared_ptr<VMAP::WorldModel> AcquireModelInstance(std::string const& basepath, std::string const& filename, uint32 flags);
|
||||
|
||||
private:
|
||||
typedef std::unordered_map<std::string, std::shared_ptr<VMAP::WorldModel>> ModelFileMap;
|
||||
ModelFileMap _loadedModels;
|
||||
|
||||
std::mutex _lock;
|
||||
};
|
||||
|
||||
#define sWorldModelStore WorldModelStore::instance()
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _MAPDEFINES_H
|
||||
#define _MAPDEFINES_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "DetourNavMesh.h"
|
||||
|
||||
#define MAX_NUMBER_OF_GRIDS 64
|
||||
#define MAX_NUMBER_OF_CELLS 8
|
||||
#define SIZE_OF_GRIDS 533.3333f
|
||||
|
||||
#define MMAP_MAGIC 0x4d4d4150 // 'MMAP'
|
||||
#define MMAP_VERSION 19
|
||||
|
||||
struct MmapTileRecastConfig
|
||||
{
|
||||
float walkableSlopeAngle;
|
||||
|
||||
uint8 walkableRadius; // 1
|
||||
uint8 walkableHeight; // 1
|
||||
uint8 walkableClimb; // 1
|
||||
uint8 padding0{0}; // 1 → align next to 4
|
||||
|
||||
uint32 vertexPerMapEdge;
|
||||
uint32 vertexPerTileEdge;
|
||||
uint32 tilesPerMapEdge;
|
||||
float baseUnitDim;
|
||||
float cellSizeHorizontal;
|
||||
float cellSizeVertical;
|
||||
float maxSimplificationError;
|
||||
|
||||
bool operator==(const MmapTileRecastConfig& b) const {
|
||||
return walkableSlopeAngle == b.walkableSlopeAngle &&
|
||||
walkableRadius == b.walkableRadius &&
|
||||
walkableHeight == b.walkableHeight &&
|
||||
walkableClimb == b.walkableClimb &&
|
||||
vertexPerMapEdge == b.vertexPerMapEdge &&
|
||||
vertexPerTileEdge == b.vertexPerTileEdge &&
|
||||
tilesPerMapEdge == b.tilesPerMapEdge &&
|
||||
baseUnitDim == b.baseUnitDim &&
|
||||
cellSizeHorizontal == b.cellSizeHorizontal &&
|
||||
cellSizeVertical == b.cellSizeVertical &&
|
||||
maxSimplificationError == b.maxSimplificationError;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(MmapTileRecastConfig) == 36, "Unexpected size of MmapTileRecastConfig");
|
||||
|
||||
struct MmapTileHeader
|
||||
{
|
||||
uint32 mmapMagic{MMAP_MAGIC};
|
||||
uint32 dtVersion;
|
||||
uint32 mmapVersion{MMAP_VERSION};
|
||||
uint32 size{0};
|
||||
char usesLiquids{true};
|
||||
char padding[3] {};
|
||||
|
||||
MmapTileRecastConfig recastConfig;
|
||||
|
||||
MmapTileHeader() : dtVersion(DT_NAVMESH_VERSION) { }
|
||||
};
|
||||
|
||||
// All padding fields must be handled and initialized to ensure mmaps_generator will produce binary-identical *.mmtile files
|
||||
static_assert(sizeof(MmapTileHeader) == 56, "MmapTileHeader size is not correct, adjust the padding field size");
|
||||
static_assert(sizeof(MmapTileHeader) == (sizeof(MmapTileHeader::mmapMagic) +
|
||||
sizeof(MmapTileHeader::dtVersion) +
|
||||
sizeof(MmapTileHeader::mmapVersion) +
|
||||
sizeof(MmapTileHeader::size) +
|
||||
sizeof(MmapTileHeader::usesLiquids) +
|
||||
sizeof(MmapTileHeader::padding)+
|
||||
sizeof(MmapTileRecastConfig)), "MmapTileHeader has uninitialized padding fields");
|
||||
|
||||
enum NavTerrain
|
||||
{
|
||||
NAV_EMPTY = 0x00,
|
||||
NAV_GROUND = 0x01,
|
||||
NAV_MAGMA = 0x02,
|
||||
NAV_SLIME = 0x04,
|
||||
NAV_WATER = 0x08,
|
||||
NAV_UNUSED1 = 0x10,
|
||||
NAV_UNUSED2 = 0x20,
|
||||
NAV_UNUSED3 = 0x40,
|
||||
NAV_UNUSED4 = 0x80
|
||||
// we only have 8 bits
|
||||
};
|
||||
|
||||
#endif /* _MAPDEFINES_H */
|
||||
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "MapTree.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include "Metric.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include "VMapMgr2.h"
|
||||
#include "WorldModelStore.h"
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using G3D::Vector3;
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class MapRayCallback
|
||||
{
|
||||
public:
|
||||
MapRayCallback(ModelInstance* val, ModelIgnoreFlags ignoreFlags): prims(val), flags(ignoreFlags), hit(false) { }
|
||||
bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool StopAtFirstHit)
|
||||
{
|
||||
bool result = prims[entry].intersectRay(ray, distance, StopAtFirstHit, flags);
|
||||
if (result)
|
||||
{
|
||||
hit = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool didHit() { return hit; }
|
||||
protected:
|
||||
ModelInstance* prims;
|
||||
ModelIgnoreFlags flags;
|
||||
bool hit;
|
||||
};
|
||||
|
||||
class LocationInfoCallback
|
||||
{
|
||||
public:
|
||||
LocationInfoCallback(ModelInstance* val, LocationInfo& info): prims(val), locInfo(info), result(false) {}
|
||||
void operator()(const Vector3& point, uint32 entry)
|
||||
{
|
||||
#if defined(VMAP_DEBUG)
|
||||
LOG_DEBUG("maps", "LocationInfoCallback: trying to intersect '{}'", prims[entry].name);
|
||||
#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::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, ModelIgnoreFlags ignoreFlags) const
|
||||
{
|
||||
float distance = pMaxDist;
|
||||
MapRayCallback intersectionCallBack(iTreeValues, ignoreFlags);
|
||||
iTree.intersectRay(pRay, intersectionCallBack, distance, StopAtFirstHit);
|
||||
if (intersectionCallBack.didHit())
|
||||
{
|
||||
pMaxDist = distance;
|
||||
}
|
||||
return intersectionCallBack.didHit();
|
||||
}
|
||||
//=========================================================
|
||||
|
||||
bool StaticMapTree::isInLineOfSight(const Vector3& pos1, const Vector3& pos2, ModelIgnoreFlags ignoreFlags) 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() || !std::isfinite(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);
|
||||
|
||||
return !GetIntersectionTime(ray, maxDist, true, ignoreFlags);
|
||||
}
|
||||
//=========================================================
|
||||
/**
|
||||
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, ModelIgnoreFlags::Nothing))
|
||||
{
|
||||
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::finf();
|
||||
Vector3 dir = Vector3(0, 0, -1);
|
||||
G3D::Ray ray(pPos, dir); // direction with length of 1
|
||||
float maxDist = maxSearchDist;
|
||||
if (GetIntersectionTime(ray, maxDist, false, ModelIgnoreFlags::Nothing))
|
||||
{
|
||||
height = pPos.z - maxDist;
|
||||
}
|
||||
return (height);
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
LoadResult 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 + VMapMgr2::getMapFileName(mapID);
|
||||
|
||||
LoadResult result = LoadResult::Success;
|
||||
|
||||
FILE* rf = fopen(fullname.c_str(), "rb");
|
||||
if (!rf)
|
||||
{
|
||||
return LoadResult::FileNotFound;
|
||||
}
|
||||
|
||||
char tiled;
|
||||
char chunk[8];
|
||||
if (!readChunk(rf, chunk, VMAP_MAGIC, 8) || fread(&tiled, sizeof(char), 1, rf) != 1)
|
||||
{
|
||||
fclose(rf);
|
||||
return LoadResult::VersionMismatch;
|
||||
}
|
||||
if (tiled)
|
||||
{
|
||||
std::string tilefile = basePath + getTileFileName(mapID, tileX, tileY);
|
||||
FILE* tf = fopen(tilefile.c_str(), "rb");
|
||||
if (!tf)
|
||||
{
|
||||
result = LoadResult::FileNotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
|
||||
{
|
||||
result = LoadResult::VersionMismatch;
|
||||
}
|
||||
fclose(tf);
|
||||
}
|
||||
}
|
||||
fclose(rf);
|
||||
return result;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
bool StaticMapTree::InitMap(const std::string& fname)
|
||||
{
|
||||
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : initializing StaticMapTree '{}'", fname);
|
||||
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
|
||||
//LOG_DEBUG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : map isTiled: {}", static_cast<uint32>(iIsTiled));
|
||||
#endif
|
||||
if (!iIsTiled && ModelSpawn::readFromFile(rf, spawn))
|
||||
{
|
||||
std::shared_ptr<WorldModel> model = sWorldModelStore->AcquireModelInstance(iBasePath, spawn.name, spawn.flags);
|
||||
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : loading {}", spawn.name);
|
||||
if (model)
|
||||
{
|
||||
// assume that global model always is the first and only tree value (could be improved...)
|
||||
iTreeValues[0] = ModelInstance(spawn, model);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = false;
|
||||
//VMAP_ERROR_LOG(LOG_FILTER_GENERAL, "StaticMapTree::InitMap() : could not acquire WorldModel pointer for '{}'", spawn.name);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(rf);
|
||||
return success;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
void StaticMapTree::UnloadMap()
|
||||
{
|
||||
iLoadedTiles.clear();
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY)
|
||||
{
|
||||
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)
|
||||
{
|
||||
LOG_ERROR("maps", "StaticMapTree::LoadMapTile() : tree has not been initialized [{}, {}]", 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
|
||||
std::shared_ptr<WorldModel> model = sWorldModelStore->AcquireModelInstance(iBasePath, spawn.name, spawn.flags);
|
||||
if (!model)
|
||||
{
|
||||
LOG_ERROR("maps", "StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [{}, {}]", tileX, tileY);
|
||||
// why do we continue to try to load if the model was unsuccessful here?
|
||||
}
|
||||
|
||||
// update tree
|
||||
uint32 referencedVal;
|
||||
|
||||
if (fread(&referencedVal, sizeof(uint32), 1, tf) == 1)
|
||||
{
|
||||
if (referencedVal >= iNTreeValues)
|
||||
{
|
||||
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : invalid tree element ({}/{})", referencedVal, iNTreeValues);
|
||||
continue;
|
||||
}
|
||||
|
||||
// This looks odd and is confusing, took some research to figure it out:
|
||||
// the first WorldModel will create a "groupmodel" of all other same-models in the tile
|
||||
// we don't actually care about anything else
|
||||
if (!iTreeValues[referencedVal].getWorldModel())
|
||||
{
|
||||
iTreeValues[referencedVal] = ModelInstance(spawn, model);
|
||||
}
|
||||
#if defined(VMAP_DEBUG)
|
||||
else
|
||||
{
|
||||
if (iTreeValues[referencedVal].ID != spawn.ID)
|
||||
{
|
||||
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");
|
||||
}
|
||||
else if (iTreeValues[referencedVal].name != spawn.name)
|
||||
{
|
||||
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : name collision on GUID={}", spawn.ID);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
iLoadedTiles[packTileID(tileX, tileY)] = true;
|
||||
fclose(tf);
|
||||
}
|
||||
else
|
||||
{
|
||||
iLoadedTiles[packTileID(tileX, tileY)] = false;
|
||||
}
|
||||
|
||||
METRIC_EVENT("map_events", "LoadMapTile",
|
||||
"Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY)
|
||||
{
|
||||
uint32 tileID = packTileID(tileX, tileY);
|
||||
loadedTileMap::iterator tile = iLoadedTiles.find(tileID);
|
||||
if (tile == iLoadedTiles.end())
|
||||
{
|
||||
LOG_ERROR("maps", "StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:{} X:{} Y:{}", iMapID, tileX, tileY);
|
||||
return;
|
||||
}
|
||||
iLoadedTiles.erase(tile);
|
||||
|
||||
METRIC_EVENT("map_events", "UnloadMapTile",
|
||||
"Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
|
||||
}
|
||||
|
||||
void StaticMapTree::GetModelInstances(ModelInstance*& models, uint32& count)
|
||||
{
|
||||
models = iTreeValues;
|
||||
count = iNTreeValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _MAPTREE_H
|
||||
#define _MAPTREE_H
|
||||
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
#include "Define.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class ModelInstance;
|
||||
class GroupModel;
|
||||
class VMapMgr2;
|
||||
enum class ModelIgnoreFlags : uint32;
|
||||
enum class LoadResult : uint8;
|
||||
|
||||
struct GroupLocationInfo
|
||||
{
|
||||
const GroupModel* hitModel = nullptr;
|
||||
int32 rootId = -1;
|
||||
};
|
||||
|
||||
struct LocationInfo
|
||||
{
|
||||
LocationInfo(): ground_Z(-G3D::inf()) { }
|
||||
const ModelInstance* hitInstance{nullptr};
|
||||
const GroupModel* hitModel{nullptr};
|
||||
float ground_Z;
|
||||
int32 rootId = -1;
|
||||
};
|
||||
|
||||
class StaticMapTree
|
||||
{
|
||||
typedef std::unordered_map<uint32, bool> loadedTileMap;
|
||||
typedef std::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;
|
||||
std::string iBasePath;
|
||||
|
||||
private:
|
||||
bool GetIntersectionTime(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) 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 LoadResult CanLoadMap(const std::string& basePath, uint32 mapID, uint32 tileX, uint32 tileY);
|
||||
|
||||
StaticMapTree(uint32 mapID, const std::string& basePath);
|
||||
~StaticMapTree();
|
||||
|
||||
[[nodiscard]] bool isInLineOfSight(const G3D::Vector3& pos1, const G3D::Vector3& pos2, ModelIgnoreFlags ignoreFlags) const;
|
||||
bool GetObjectHitPos(const G3D::Vector3& pos1, const G3D::Vector3& pos2, G3D::Vector3& pResultHitPos, float pModifyDist) const;
|
||||
[[nodiscard]] float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const;
|
||||
bool GetLocationInfo(const G3D::Vector3& pos, LocationInfo& info) const;
|
||||
|
||||
bool InitMap(const std::string& fname);
|
||||
void UnloadMap();
|
||||
bool LoadMapTile(uint32 tileX, uint32 tileY);
|
||||
void UnloadMapTile(uint32 tileX, uint32 tileY);
|
||||
[[nodiscard]] bool isTiled() const { return iIsTiled; }
|
||||
[[nodiscard]] uint32 numLoadedTiles() const { return iLoadedTiles.size(); }
|
||||
void GetModelInstances(ModelInstance*& models, uint32& count);
|
||||
};
|
||||
|
||||
struct AreaInfo
|
||||
{
|
||||
AreaInfo(): ground_Z(-G3D::inf()) { }
|
||||
bool result{false};
|
||||
float ground_Z;
|
||||
uint32 flags{0};
|
||||
int32 adtId{0};
|
||||
int32 rootId{0};
|
||||
int32 groupId{0};
|
||||
};
|
||||
} // VMAP
|
||||
|
||||
#endif // _MAPTREE_H
|
||||
@@ -0,0 +1,610 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TileAssembler.h"
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
#include "MapDefines.h"
|
||||
#include "MapTree.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <iomanip>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
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)
|
||||
{
|
||||
boost::filesystem::create_directory(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))
|
||||
{
|
||||
// read mapID, tileX, tileY, Flags, NameSet, UniqueId, 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.emplace(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;
|
||||
}
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct WMOLiquidHeader
|
||||
{
|
||||
int xverts, yverts, xtiles, ytiles;
|
||||
float pos_x;
|
||||
float pos_y;
|
||||
float pos_z;
|
||||
short material;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
//=================================================================
|
||||
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;
|
||||
}
|
||||
|
||||
char ident[8];
|
||||
if (fread(ident, 1, 8, model_list) != 8 || memcmp(ident, VMAP::RAW_VMAP_MAGIC, 8) != 0)
|
||||
{
|
||||
fclose(model_list);
|
||||
return;
|
||||
}
|
||||
|
||||
FILE* model_list_copy = fopen((iDestDir + "/" + GAMEOBJECT_MODELS).c_str(), "wb");
|
||||
if (!model_list_copy)
|
||||
{
|
||||
fclose(model_list);
|
||||
return;
|
||||
}
|
||||
|
||||
fwrite(VMAP::VMAP_MAGIC, 1, 8, model_list_copy);
|
||||
|
||||
uint32 name_length, displayId;
|
||||
uint8 isWmo;
|
||||
char buff[500];
|
||||
while (!feof(model_list))
|
||||
{
|
||||
if (fread(&displayId, sizeof(uint32), 1, model_list) != 1)
|
||||
if (feof(model_list)) // EOF flag is only set after failed reading attempt
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (fread(&isWmo, sizeof(uint8), 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(&isWmo, sizeof(uint8), 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 = nullptr;
|
||||
if (liquidflags & 3)
|
||||
{
|
||||
READ_OR_RETURN(&blockId, 4);
|
||||
CMP_OR_RETURN(blockId, "LIQU");
|
||||
READ_OR_RETURN(&blocksize, sizeof(int));
|
||||
uint32 liquidType;
|
||||
READ_OR_RETURN(&liquidType, sizeof(uint32));
|
||||
if (liquidflags & 1)
|
||||
{
|
||||
WMOLiquidHeader hlq;
|
||||
READ_OR_RETURN(&hlq, sizeof(WMOLiquidHeader));
|
||||
liquid = new WmoLiquid(hlq.xtiles, hlq.ytiles, Vector3(hlq.pos_x, hlq.pos_y, hlq.pos_z), liquidType);
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
liquid = new WmoLiquid(0, 0, Vector3::zero(), liquidType);
|
||||
liquid->GetHeightStorage()[0] = bounds.high().z;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _TILEASSEMBLER_H_
|
||||
#define _TILEASSEMBLER_H_
|
||||
|
||||
#include <G3D/Matrix3.h>
|
||||
#include <G3D/Vector3.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() { }
|
||||
G3D::Vector3 iPos;
|
||||
G3D::Vector3 iDir;
|
||||
float iScale{0.0f};
|
||||
void init()
|
||||
{
|
||||
iRotation = G3D::Matrix3::fromEulerAnglesZYX(G3D::pif() * iDir.y / 180.f, G3D::pif() * iDir.x / 180.f, G3D::pif() * iDir.z / 180.f);
|
||||
}
|
||||
[[nodiscard]] 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{0};
|
||||
uint32 GroupWMOID{0};
|
||||
|
||||
G3D::AABox bounds;
|
||||
uint32 liquidflags{0};
|
||||
std::vector<MeshTriangle> triangles;
|
||||
std::vector<G3D::Vector3> vertexArray;
|
||||
class WmoLiquid* liquid;
|
||||
|
||||
GroupModel_Raw() : liquid(nullptr) { }
|
||||
|
||||
~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;
|
||||
G3D::Table<std::string, unsigned int > iUniqueNameIds;
|
||||
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);
|
||||
};
|
||||
|
||||
} // VMAP
|
||||
#endif /*_TILEASSEMBLER_H_*/
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "GameObjectModel.h"
|
||||
#include "Log.h"
|
||||
#include "MapTree.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "Timer.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include "VMapFactory.h"
|
||||
#include "VMapMgr2.h"
|
||||
#include "WorldModel.h"
|
||||
#include "WorldModelStore.h"
|
||||
|
||||
using G3D::Vector3;
|
||||
using G3D::Ray;
|
||||
using G3D::AABox;
|
||||
|
||||
struct GameobjectModelData
|
||||
{
|
||||
GameobjectModelData(char const* name_, uint32 nameLength, Vector3 const& lowBound, Vector3 const& highBound, bool isWmo_) :
|
||||
bound(lowBound, highBound), name(name_, nameLength), isWmo(isWmo_) { }
|
||||
|
||||
AABox bound;
|
||||
std::string name;
|
||||
bool isWmo;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<uint32, GameobjectModelData> ModelList;
|
||||
ModelList model_list;
|
||||
|
||||
void LoadGameObjectModelList(std::string const& dataPath)
|
||||
{
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
FILE* model_list_file = fopen((dataPath + "vmaps/" + VMAP::GAMEOBJECT_MODELS).c_str(), "rb");
|
||||
if (!model_list_file)
|
||||
{
|
||||
LOG_ERROR("maps", "Unable to open '{}' file.", VMAP::GAMEOBJECT_MODELS);
|
||||
return;
|
||||
}
|
||||
|
||||
char magic[8];
|
||||
if (fread(magic, 1, 8, model_list_file) != 8 || memcmp(magic, VMAP::VMAP_MAGIC, 8) != 0)
|
||||
{
|
||||
LOG_ERROR("maps", "File '{}' has wrong header, expected {}.", VMAP::GAMEOBJECT_MODELS, VMAP::VMAP_MAGIC);
|
||||
fclose(model_list_file);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 name_length, displayId;
|
||||
uint8 isWmo;
|
||||
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(&isWmo, sizeof(uint8), 1, model_list_file) != 1
|
||||
|| 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)
|
||||
{
|
||||
LOG_ERROR("maps", "File '{}' seems to be corrupted!", VMAP::GAMEOBJECT_MODELS);
|
||||
fclose(model_list_file);
|
||||
break;
|
||||
}
|
||||
|
||||
if (v1.isNaN() || v2.isNaN())
|
||||
{
|
||||
LOG_ERROR("maps", "File '{}' Model '{}' has invalid v1{} v2{} values!",
|
||||
VMAP::GAMEOBJECT_MODELS, std::string(buff, name_length), v1.toString(), v2.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
model_list.emplace(std::piecewise_construct, std::forward_as_tuple(displayId), std::forward_as_tuple(&buff[0], name_length, v1, v2, isWmo != 0));
|
||||
}
|
||||
|
||||
fclose(model_list_file);
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded {} GameObject Models in {} ms", uint32(model_list.size()), GetMSTimeDiffToNow(oldMSTime));
|
||||
LOG_INFO("server.loading", " ");
|
||||
}
|
||||
|
||||
bool GameObjectModel::initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath)
|
||||
{
|
||||
ModelList::const_iterator it = model_list.find(modelOwner->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())
|
||||
{
|
||||
LOG_ERROR("maps", "GameObject model {} has zero bounds, loading skipped", it->second.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
iModel = sWorldModelStore->AcquireModelInstance(dataPath + "vmaps/", it->second.name,
|
||||
it->second.isWmo ? VMAP::ModelFlags::MOD_WORLDSPAWN : VMAP::ModelFlags::MOD_M2);
|
||||
|
||||
if (!iModel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
name = it->second.name;
|
||||
iPos = modelOwner->GetPosition();
|
||||
phasemask = modelOwner->GetPhaseMask();
|
||||
iScale = modelOwner->GetScale();
|
||||
iInvScale = 1.f / iScale;
|
||||
|
||||
G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(modelOwner->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));
|
||||
modelOwner->DebugVisualizeCorner(pos);
|
||||
}
|
||||
#endif
|
||||
|
||||
owner = std::move(modelOwner);
|
||||
isWmo = it->second.isWmo;
|
||||
return true;
|
||||
}
|
||||
|
||||
GameObjectModel* GameObjectModel::Create(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath)
|
||||
{
|
||||
GameObjectModel* mdl = new GameObjectModel();
|
||||
if (!mdl->initialize(std::move(modelOwner), dataPath))
|
||||
{
|
||||
delete mdl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mdl;
|
||||
}
|
||||
|
||||
bool GameObjectModel::intersectRay(const G3D::Ray& ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask, VMAP::ModelIgnoreFlags ignoreFlags) 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, ignoreFlags);
|
||||
if (hit)
|
||||
{
|
||||
distance *= iScale;
|
||||
MaxDist = distance;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool GameObjectModel::GetLocationInfo(G3D::Vector3 const& point, VMAP::LocationInfo& info, uint32 ph_mask) const
|
||||
{
|
||||
if (!(phasemask & ph_mask) || !owner->IsSpawned() || !IsMapObject())
|
||||
return false;
|
||||
|
||||
if (!iBound.contains(point))
|
||||
return false;
|
||||
|
||||
// child bounds are defined in object space:
|
||||
Vector3 pModel = iInvRot * (point - iPos) * iInvScale;
|
||||
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
|
||||
float zDist;
|
||||
|
||||
VMAP::GroupLocationInfo groupInfo;
|
||||
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, groupInfo))
|
||||
{
|
||||
Vector3 modelGround = pModel + zDist * zDirModel;
|
||||
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
|
||||
if (info.ground_Z < world_Z)
|
||||
{
|
||||
info.ground_Z = world_Z;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameObjectModel::GetLiquidLevel(G3D::Vector3 const& point, VMAP::LocationInfo& info, float& liqHeight) const
|
||||
{
|
||||
// child bounds are defined in object space:
|
||||
Vector3 pModel = iInvRot * (point - 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 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 = owner->GetPosition();
|
||||
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->DebugVisualizeCorner(pos);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _GAMEOBJECT_MODEL_H
|
||||
#define _GAMEOBJECT_MODEL_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Matrix3.h>
|
||||
#include <G3D/Ray.h>
|
||||
#include <G3D/Vector3.h>
|
||||
#include <memory>
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class WorldModel;
|
||||
struct AreaInfo;
|
||||
struct LocationInfo;
|
||||
enum class ModelIgnoreFlags : uint32;
|
||||
}
|
||||
|
||||
class GameObject;
|
||||
struct GameObjectDisplayInfoEntry;
|
||||
|
||||
class GameObjectModelOwnerBase
|
||||
{
|
||||
public:
|
||||
virtual ~GameObjectModelOwnerBase() = default;
|
||||
|
||||
[[nodiscard]] virtual bool IsSpawned() const = 0;
|
||||
[[nodiscard]] virtual uint32 GetDisplayId() const = 0;
|
||||
[[nodiscard]] virtual uint32 GetPhaseMask() const = 0;
|
||||
[[nodiscard]] virtual G3D::Vector3 GetPosition() const = 0;
|
||||
[[nodiscard]] virtual float GetOrientation() const = 0;
|
||||
[[nodiscard]] virtual float GetScale() const = 0;
|
||||
virtual void DebugVisualizeCorner(G3D::Vector3 const& /*corner*/) const = 0;
|
||||
};
|
||||
|
||||
class GameObjectModel
|
||||
{
|
||||
GameObjectModel() = default;
|
||||
|
||||
public:
|
||||
std::string name;
|
||||
|
||||
[[nodiscard]] const G3D::AABox& GetBounds() const { return iBound; }
|
||||
|
||||
~GameObjectModel() = default;
|
||||
|
||||
[[nodiscard]] const G3D::Vector3& GetPosition() const { return iPos; }
|
||||
|
||||
/** Enables\disables collision. */
|
||||
void disable() { phasemask = 0; }
|
||||
void enable(uint32 ph_mask) { phasemask = ph_mask; }
|
||||
|
||||
[[nodiscard]] bool isEnabled() const { return phasemask != 0; }
|
||||
[[nodiscard]] bool IsMapObject() const { return isWmo; }
|
||||
|
||||
bool intersectRay(const G3D::Ray& Ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask, VMAP::ModelIgnoreFlags ignoreFlags) const;
|
||||
bool GetLocationInfo(G3D::Vector3 const& point, VMAP::LocationInfo& info, uint32 ph_mask) const;
|
||||
bool GetLiquidLevel(G3D::Vector3 const& point, VMAP::LocationInfo& info, float& liqHeight) const;
|
||||
|
||||
static GameObjectModel* Create(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath);
|
||||
|
||||
bool UpdatePosition();
|
||||
|
||||
private:
|
||||
bool initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath);
|
||||
|
||||
uint32 phasemask{0};
|
||||
G3D::AABox iBound;
|
||||
G3D::Matrix3 iInvRot;
|
||||
G3D::Vector3 iPos;
|
||||
float iInvScale{0};
|
||||
float iScale{0};
|
||||
std::shared_ptr<VMAP::WorldModel> iModel;
|
||||
std::unique_ptr<GameObjectModelOwnerBase> owner;
|
||||
bool isWmo{false};
|
||||
};
|
||||
|
||||
void LoadGameObjectModelList(std::string const& dataPath);
|
||||
|
||||
#endif // _GAMEOBJECT_MODEL_H
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ModelIgnoreFlags_h__
|
||||
#define ModelIgnoreFlags_h__
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
enum class ModelIgnoreFlags : uint32
|
||||
{
|
||||
Nothing = 0x00,
|
||||
M2 = 0x01
|
||||
};
|
||||
|
||||
inline ModelIgnoreFlags operator&(ModelIgnoreFlags left, ModelIgnoreFlags right)
|
||||
{
|
||||
return ModelIgnoreFlags(uint32(left) & uint32(right));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ModelIgnoreFlags_h__
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ModelInstance.h"
|
||||
#include "MapTree.h"
|
||||
#include "WorldModel.h"
|
||||
|
||||
using G3D::Vector3;
|
||||
using G3D::Ray;
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
ModelInstance::ModelInstance(const ModelSpawn& spawn, std::shared_ptr<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, ModelIgnoreFlags ignoreFlags) 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, ignoreFlags);
|
||||
if (hit)
|
||||
{
|
||||
distance *= iScale;
|
||||
pMaxDist = distance;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
GroupLocationInfo groupInfo;
|
||||
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, groupInfo))
|
||||
{
|
||||
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.rootId = groupInfo.rootId;
|
||||
info.hitModel = groupInfo.hitModel;
|
||||
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):
|
||||
liqHeight = (Vector3(pModel.x, pModel.y, zDist) * iInvRot * 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _MODELINSTANCE_H_
|
||||
#define _MODELINSTANCE_H_
|
||||
|
||||
#include "Define.h"
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Matrix3.h>
|
||||
#include <G3D/Ray.h>
|
||||
#include <G3D/Vector3.h>
|
||||
#include <memory>
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class WorldModel;
|
||||
struct AreaInfo;
|
||||
struct LocationInfo;
|
||||
enum class ModelIgnoreFlags : uint32;
|
||||
|
||||
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?
|
||||
[[nodiscard]] 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() { }
|
||||
ModelInstance(const ModelSpawn& spawn, std::shared_ptr<WorldModel> model);
|
||||
bool intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) const;
|
||||
bool GetLocationInfo(const G3D::Vector3& p, LocationInfo& info) const;
|
||||
bool GetLiquidLevel(const G3D::Vector3& p, LocationInfo& info, float& liqHeight) const;
|
||||
WorldModel* getWorldModel() { return iModel.get(); }
|
||||
protected:
|
||||
G3D::Matrix3 iInvRot;
|
||||
float iInvScale{0.0f};
|
||||
std::shared_ptr<WorldModel> iModel;
|
||||
};
|
||||
} // namespace VMAP
|
||||
|
||||
#endif // _MODELINSTANCE_H_
|
||||
@@ -0,0 +1,707 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "WorldModel.h"
|
||||
#include "MapTree.h"
|
||||
#include "ModelIgnoreFlags.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include <array>
|
||||
|
||||
using G3D::Vector3;
|
||||
|
||||
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 (std::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)
|
||||
{
|
||||
if (width && height)
|
||||
{
|
||||
iHeight = new float[(width + 1) * (height + 1)];
|
||||
iFlags = new uint8[width * height];
|
||||
}
|
||||
else
|
||||
{
|
||||
iHeight = new float[1];
|
||||
iFlags = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// simple case
|
||||
if (!iFlags)
|
||||
{
|
||||
liqHeight = iHeight[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
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 && (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
|
||||
*/
|
||||
|
||||
if (!iHeight)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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) +
|
||||
sizeof(uint32) +
|
||||
(iFlags ? ((iTilesX + 1) * (iTilesY + 1) * sizeof(float) + iTilesX * iTilesY) : sizeof(float));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (iTilesX && iTilesY)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
result = fwrite(iHeight, sizeof(float), 1, wf) == 1;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (liquid->iTilesX && liquid->iTilesY)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
liquid->iHeight = new float[1];
|
||||
result = fread(liquid->iHeight, sizeof(float), 1, rf) == 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
delete liquid;
|
||||
}
|
||||
else
|
||||
{
|
||||
out = liquid;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void WmoLiquid::GetPosInfo(uint32& tilesX, uint32& tilesY, G3D::Vector3& corner) const
|
||||
{
|
||||
tilesX = iTilesX;
|
||||
tilesY = iTilesY;
|
||||
corner = iCorner;
|
||||
}
|
||||
|
||||
// ===================== 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 (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 = nullptr;
|
||||
|
||||
if (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;
|
||||
}
|
||||
|
||||
inline bool IsInsideOrAboveBound(G3D::AABox const& bounds, const G3D::Point3& point)
|
||||
{
|
||||
return point.x >= bounds.low().x
|
||||
&& point.y >= bounds.low().y
|
||||
&& point.z >= bounds.low().z
|
||||
&& point.x <= bounds.high().x
|
||||
&& point.y <= bounds.high().y;
|
||||
}
|
||||
|
||||
GroupModel::InsideResult GroupModel::IsInsideObject(G3D::Ray const& ray, float& z_dist) const
|
||||
{
|
||||
if (triangles.empty() || !IsInsideOrAboveBound(iBound, ray.origin()))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
if (meshTree.bound().high().z >= ray.origin().z)
|
||||
{
|
||||
float dist = G3D::finf();
|
||||
if (IntersectRay(ray, dist, false))
|
||||
{
|
||||
z_dist = dist - 0.1f;
|
||||
return INSIDE;
|
||||
}
|
||||
if (meshTree.bound().contains(ray.origin()))
|
||||
return MAYBE_INSIDE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// some group models don't have any floor to intersect with
|
||||
// so we should attempt to intersect with a model part below this group
|
||||
// then find back where we originated from (in WorldModel::GetLocationInfo)
|
||||
float dist = G3D::finf();
|
||||
float delta = ray.origin().z - meshTree.bound().high().z;
|
||||
if (IntersectRay(ray.bumpedRay(delta), dist, false))
|
||||
{
|
||||
z_dist = dist - 0.1f + delta;
|
||||
return ABOVE;
|
||||
}
|
||||
}
|
||||
|
||||
return OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void GroupModel::GetMeshData(std::vector<G3D::Vector3>& outVertices, std::vector<MeshTriangle>& outTriangles, WmoLiquid*& liquid)
|
||||
{
|
||||
outVertices = vertices;
|
||||
outTriangles = triangles;
|
||||
liquid = iLiquid;
|
||||
}
|
||||
|
||||
// ===================== 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, ModelIgnoreFlags ignoreFlags) const
|
||||
{
|
||||
// If the caller asked us to ignore certain objects we should check flags
|
||||
if ((ignoreFlags & ModelIgnoreFlags::M2) != ModelIgnoreFlags::Nothing)
|
||||
{
|
||||
// M2 models are not taken into account for LoS calculation if caller requested their ignoring.
|
||||
if (Flags & MOD_M2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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(std::vector<GroupModel> const& vals) :
|
||||
prims(vals), hit() { }
|
||||
std::vector<GroupModel> const& prims;
|
||||
std::array<GroupModel const*, 3> hit;
|
||||
bool operator()(G3D::Ray const& ray, uint32 entry, float& distance, bool /*stopAtFirstHit*/)
|
||||
{
|
||||
float group_Z;
|
||||
if (GroupModel::InsideResult result = prims[entry].IsInsideObject(ray, group_Z); result != GroupModel::OUT_OF_BOUNDS)
|
||||
{
|
||||
if (result != GroupModel::MAYBE_INSIDE)
|
||||
{
|
||||
if (group_Z < distance)
|
||||
{
|
||||
distance = group_Z;
|
||||
hit[result] = &prims[entry];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
hit[result] = &prims[entry];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool WorldModel::GetLocationInfo(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, GroupLocationInfo& info) const
|
||||
{
|
||||
if (groupModels.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WModelAreaCallback callback(groupModels);
|
||||
G3D::Ray r(p - down * 0.1f, down);
|
||||
float zDist = groupTree.bound().extent().length();
|
||||
groupTree.intersectRay(r, callback, zDist, false);
|
||||
if (callback.hit[GroupModel::INSIDE])
|
||||
{
|
||||
info.rootId = RootWMOID;
|
||||
info.hitModel = callback.hit[GroupModel::INSIDE];
|
||||
dist = zDist;
|
||||
return true;
|
||||
}
|
||||
|
||||
// some group models don't have any floor to intersect with
|
||||
// so we should attempt to intersect with a model part below the group `p` is in (stored in GroupModel::ABOVE)
|
||||
// then find back where we originated from (GroupModel::MAYBE_INSIDE)
|
||||
if (callback.hit[GroupModel::MAYBE_INSIDE] && callback.hit[GroupModel::ABOVE])
|
||||
{
|
||||
info.rootId = RootWMOID;
|
||||
info.hitModel = callback.hit[GroupModel::MAYBE_INSIDE];
|
||||
dist = 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 (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;
|
||||
}
|
||||
|
||||
void WorldModel::GetGroupModels(std::vector<GroupModel>& outGroupModels)
|
||||
{
|
||||
outGroupModels = groupModels;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _WORLDMODEL_H
|
||||
#define _WORLDMODEL_H
|
||||
|
||||
#include "BoundingIntervalHierarchy.h"
|
||||
#include "Define.h"
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Ray.h>
|
||||
#include <G3D/Vector3.h>
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class TreeNode;
|
||||
struct AreaInfo;
|
||||
struct LocationInfo;
|
||||
struct GroupLocationInfo;
|
||||
enum class ModelIgnoreFlags : uint32;
|
||||
|
||||
class MeshTriangle
|
||||
{
|
||||
public:
|
||||
MeshTriangle() { }
|
||||
MeshTriangle(uint32 na, uint32 nb, uint32 nc): idx0(na), idx1(nb), idx2(nc) { }
|
||||
|
||||
uint32 idx0{0};
|
||||
uint32 idx1{0};
|
||||
uint32 idx2{0};
|
||||
};
|
||||
|
||||
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;
|
||||
[[nodiscard]] 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);
|
||||
void GetPosInfo(uint32& tilesX, uint32& tilesY, G3D::Vector3& corner) const;
|
||||
private:
|
||||
WmoLiquid() { }
|
||||
uint32 iTilesX{0}; //!< number of tiles in x direction, each
|
||||
uint32 iTilesY{0};
|
||||
G3D::Vector3 iCorner; //!< the lower corner
|
||||
uint32 iType{0}; //!< liquid type
|
||||
float* iHeight{nullptr}; //!< (tilesX + 1)*(tilesY + 1) height values
|
||||
uint8* iFlags{nullptr}; //!< info if liquid tile is used
|
||||
};
|
||||
|
||||
/*! holding additional info for WMO group files */
|
||||
class GroupModel
|
||||
{
|
||||
public:
|
||||
GroupModel() { }
|
||||
GroupModel(const GroupModel& other);
|
||||
GroupModel(uint32 mogpFlags, uint32 groupWMOID, const G3D::AABox& bound):
|
||||
iBound(bound), iMogpFlags(mogpFlags), iGroupWMOID(groupWMOID), iLiquid(nullptr) { }
|
||||
~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 = nullptr; }
|
||||
bool IntersectRay(const G3D::Ray& ray, float& distance, bool stopAtFirstHit) const;
|
||||
enum InsideResult { INSIDE = 0, MAYBE_INSIDE = 1, ABOVE = 2, OUT_OF_BOUNDS = -1 };
|
||||
InsideResult IsInsideObject(G3D::Ray const& ray, float& z_dist) const;
|
||||
bool GetLiquidLevel(const G3D::Vector3& pos, float& liqHeight) const;
|
||||
[[nodiscard]] uint32 GetLiquidType() const;
|
||||
bool writeToFile(FILE* wf);
|
||||
bool readFromFile(FILE* rf);
|
||||
[[nodiscard]] G3D::AABox const& GetBound() const { return iBound; }
|
||||
[[nodiscard]] G3D::AABox const& GetMeshTreeBound() const { return meshTree.bound(); }
|
||||
[[nodiscard]] uint32 GetMogpFlags() const { return iMogpFlags; }
|
||||
[[nodiscard]] uint32 GetWmoID() const { return iGroupWMOID; }
|
||||
void GetMeshData(std::vector<G3D::Vector3>& outVertices, std::vector<MeshTriangle>& outTriangles, WmoLiquid*& liquid);
|
||||
protected:
|
||||
G3D::AABox iBound;
|
||||
uint32 iMogpFlags{0};// 0x8 outdor; 0x2000 indoor
|
||||
uint32 iGroupWMOID{0};
|
||||
std::vector<G3D::Vector3> vertices;
|
||||
std::vector<MeshTriangle> triangles;
|
||||
BIH meshTree;
|
||||
WmoLiquid* iLiquid{nullptr};
|
||||
};
|
||||
/*! Holds a model (converted M2 or WMO) in its original coordinate space */
|
||||
class WorldModel
|
||||
{
|
||||
public:
|
||||
WorldModel() { }
|
||||
|
||||
//! 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, ModelIgnoreFlags ignoreFlags) const;
|
||||
bool GetLocationInfo(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, GroupLocationInfo& info) const;
|
||||
bool writeFile(const std::string& filename);
|
||||
bool readFile(const std::string& filename);
|
||||
void GetGroupModels(std::vector<GroupModel>& outGroupModels);
|
||||
uint32 Flags;
|
||||
protected:
|
||||
uint32 RootWMOID{0};
|
||||
std::vector<GroupModel> groupModels;
|
||||
BIH groupTree;
|
||||
};
|
||||
} // namespace VMAP
|
||||
|
||||
#endif // _WORLDMODEL_H
|
||||
@@ -0,0 +1,296 @@
|
||||
#ifndef _REGULAR_GRID_H
|
||||
#define _REGULAR_GRID_H
|
||||
|
||||
#include <G3D/PositionTrait.h>
|
||||
#include <G3D/Ray.h>
|
||||
#include <G3D/Table.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 * std::fabs(kx_inv);
|
||||
float tDeltaY = voxel * std::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
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _VMAPDEFINITIONS_H
|
||||
#define _VMAPDEFINITIONS_H
|
||||
|
||||
#define LIQUID_TILE_SIZE (533.333f / 128.f)
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
const char VMAP_MAGIC[] = "VMAP_4.8";
|
||||
const char RAW_VMAP_MAGIC[] = "VMAP048"; // 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
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _VMAPTOOLS_H
|
||||
#define _VMAPTOOLS_H
|
||||
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/CollisionDetection.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
|
||||
{
|
||||
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
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
char const* localeNames[TOTAL_LOCALES] =
|
||||
{
|
||||
"enUS",
|
||||
"koKR",
|
||||
"frFR",
|
||||
"deDE",
|
||||
"zhCN",
|
||||
"zhTW",
|
||||
"esES",
|
||||
"esMX",
|
||||
"ruRU"
|
||||
};
|
||||
|
||||
bool IsLocaleValid(std::string const& locale)
|
||||
{
|
||||
for (int i = 0; i < TOTAL_LOCALES; ++i)
|
||||
if (locale == localeNames[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
LocaleConstant GetLocaleByName(const std::string& name)
|
||||
{
|
||||
for (uint32 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
if (name == localeNames[i])
|
||||
return LocaleConstant(i);
|
||||
|
||||
return LOCALE_enUS; // including enGB case
|
||||
}
|
||||
|
||||
const std::string GetNameByLocaleConstant(LocaleConstant localeConstant)
|
||||
{
|
||||
if (localeConstant < TOTAL_LOCALES)
|
||||
{
|
||||
return localeNames[localeConstant];
|
||||
}
|
||||
|
||||
return "enUS"; // Default value for unsupported or invalid LocaleConstant
|
||||
}
|
||||
|
||||
void CleanStringForMysqlQuery(std::string& str)
|
||||
{
|
||||
std::string::size_type n = 0;
|
||||
while ((n = str.find('\\')) != str.npos) { str.erase(n, 1); }
|
||||
while ((n = str.find('"')) != str.npos) { str.erase(n, 1); }
|
||||
while ((n = str.find('\'')) != str.npos) { str.erase(n, 1); }
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_COMMON_H
|
||||
#define AZEROTHCORE_COMMON_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <string>
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
#include <ws2tcpip.h>
|
||||
#if AC_COMPILER == AC_COMPILER_INTEL
|
||||
# if !defined(BOOST_ASIO_HAS_MOVE)
|
||||
# define BOOST_ASIO_HAS_MOVE
|
||||
# endif // !defined(BOOST_ASIO_HAS_MOVE)
|
||||
# endif // if AC_COMPILER == AC_COMPILER_INTEL
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define STRINGIZE(a) #a
|
||||
|
||||
#define MAX_NETCLIENT_PACKET_SIZE (32767 - 1) // Client hardcap: int16 with trailing zero space otherwise crash on memory free
|
||||
|
||||
// TimeConstants
|
||||
constexpr auto SECOND = 1;
|
||||
constexpr auto MINUTE = SECOND * 60;
|
||||
constexpr auto HOUR = MINUTE * 60;
|
||||
constexpr auto DAY = HOUR * 24;
|
||||
constexpr auto WEEK = DAY * 7;
|
||||
constexpr auto MONTH = DAY * 30;
|
||||
constexpr auto YEAR = DAY * 365;
|
||||
constexpr auto IN_MILLISECONDS = 1000;
|
||||
|
||||
enum AccountTypes
|
||||
{
|
||||
SEC_PLAYER = 0,
|
||||
SEC_MODERATOR = 1,
|
||||
SEC_GAMEMASTER = 2,
|
||||
SEC_ADMINISTRATOR = 3,
|
||||
SEC_CONSOLE = 4 // must be always last in list, accounts must have less security level always also
|
||||
};
|
||||
|
||||
#define MAX_ACCOUNT_FLAG 32
|
||||
enum AccountFlag
|
||||
{
|
||||
ACCOUNT_FLAG_GM = 0x1, // Account is GM
|
||||
ACCOUNT_FLAG_NOKICK = 0x2, // NYI UNK
|
||||
ACCOUNT_FLAG_COLLECTOR = 0x4, // NYI Collector's Edition
|
||||
ACCOUNT_FLAG_TRIAL = 0x8, // NYI Trial account
|
||||
ACCOUNT_FLAG_CANCELLED = 0x10, // NYI UNK
|
||||
ACCOUNT_FLAG_IGR = 0x20, // NYI Internet Game Room (Internet cafe?)
|
||||
ACCOUNT_FLAG_WHOLESALER = 0x40, // NYI UNK
|
||||
ACCOUNT_FLAG_PRIVILEGED = 0x80, // NYI UNK
|
||||
ACCOUNT_FLAG_EU_FORBID_ELV = 0x100, // NYI UNK
|
||||
ACCOUNT_FLAG_EU_FORBID_BILLING = 0x200, // NYI UNK
|
||||
ACCOUNT_FLAG_RESTRICTED = 0x400, // NYI UNK
|
||||
ACCOUNT_FLAG_REFERRAL = 0x800, // NYI Recruit-A-Friend, either referer or referee
|
||||
ACCOUNT_FLAG_BLIZZARD = 0x1000, // NYI UNK
|
||||
ACCOUNT_FLAG_RECURRING_BILLING = 0x2000, // NYI UNK
|
||||
ACCOUNT_FLAG_NOELECTUP = 0x4000, // NYI UNK
|
||||
ACCOUNT_FLAG_KR_CERTIFICATE = 0x8000, // NYI Korean certificate?
|
||||
ACCOUNT_FLAG_EXPANSION_COLLECTOR = 0x10000, // NYI TBC Collector's Edition
|
||||
ACCOUNT_FLAG_DISABLE_VOICE = 0x20000, // NYI Can't join voice chat
|
||||
ACCOUNT_FLAG_DISABLE_VOICE_SPEAK = 0x40000, // NYI Can't speak in voice chat
|
||||
ACCOUNT_FLAG_REFERRAL_RESURRECT = 0x80000, // NYI Scroll of Resurrection
|
||||
ACCOUNT_FLAG_EU_FORBID_CC = 0x100000, // NYI UNK
|
||||
ACCOUNT_FLAG_OPENBETA_DELL = 0x200000, // NYI https://wowpedia.fandom.com/wiki/Dell_XPS_M1730_World_of_Warcraft_Edition
|
||||
ACCOUNT_FLAG_PROPASS = 0x400000, // NYI UNK
|
||||
ACCOUNT_FLAG_PROPASS_LOCK = 0x800000, // NYI Pro pass (arena tournament)
|
||||
ACCOUNT_FLAG_PENDING_UPGRADE = 0x1000000, // NYI UNK
|
||||
ACCOUNT_FLAG_RETAIL_FROM_TRIAL = 0x2000000, // NYI UNK
|
||||
ACCOUNT_FLAG_EXPANSION2_COLLECTOR = 0x4000000, // NYI WotLK Collector's Edition
|
||||
ACCOUNT_FLAG_OVERMIND_LINKED = 0x8000000, // NYI Linked with Battle.net account
|
||||
ACCOUNT_FLAG_DEMOS = 0x10000000, // NYI UNK
|
||||
ACCOUNT_FLAG_DEATH_KNIGHT_OK = 0x20000000, // NYI Has level 55 on account?
|
||||
// Below might be StarCraft II related
|
||||
ACCOUNT_FLAG_S2_REQUIRE_IGR = 0x40000000, // NYI UNK
|
||||
ACCOUNT_FLAG_S2_TRIAL = 0x80000000, // NYI UNK
|
||||
// ACCOUNT_FLAG_S2_RESTRICTED = 0xFFFFFFFF, // NYI UNK
|
||||
};
|
||||
|
||||
constexpr uint32 ACCOUNT_FLAGS_ALL =
|
||||
ACCOUNT_FLAG_GM | ACCOUNT_FLAG_NOKICK | ACCOUNT_FLAG_COLLECTOR |
|
||||
ACCOUNT_FLAG_TRIAL | ACCOUNT_FLAG_CANCELLED | ACCOUNT_FLAG_IGR |
|
||||
ACCOUNT_FLAG_WHOLESALER | ACCOUNT_FLAG_PRIVILEGED | ACCOUNT_FLAG_EU_FORBID_ELV |
|
||||
ACCOUNT_FLAG_EU_FORBID_BILLING | ACCOUNT_FLAG_RESTRICTED | ACCOUNT_FLAG_REFERRAL |
|
||||
ACCOUNT_FLAG_BLIZZARD | ACCOUNT_FLAG_RECURRING_BILLING | ACCOUNT_FLAG_NOELECTUP |
|
||||
ACCOUNT_FLAG_KR_CERTIFICATE | ACCOUNT_FLAG_EXPANSION_COLLECTOR | ACCOUNT_FLAG_DISABLE_VOICE |
|
||||
ACCOUNT_FLAG_DISABLE_VOICE_SPEAK | ACCOUNT_FLAG_REFERRAL_RESURRECT | ACCOUNT_FLAG_EU_FORBID_CC |
|
||||
ACCOUNT_FLAG_OPENBETA_DELL | ACCOUNT_FLAG_PROPASS | ACCOUNT_FLAG_PROPASS_LOCK |
|
||||
ACCOUNT_FLAG_PENDING_UPGRADE | ACCOUNT_FLAG_RETAIL_FROM_TRIAL | ACCOUNT_FLAG_EXPANSION2_COLLECTOR |
|
||||
ACCOUNT_FLAG_OVERMIND_LINKED | ACCOUNT_FLAG_DEMOS | ACCOUNT_FLAG_DEATH_KNIGHT_OK |
|
||||
ACCOUNT_FLAG_S2_REQUIRE_IGR | ACCOUNT_FLAG_S2_TRIAL;
|
||||
|
||||
enum LocaleConstant
|
||||
{
|
||||
LOCALE_enUS = 0,
|
||||
LOCALE_koKR = 1,
|
||||
LOCALE_frFR = 2,
|
||||
LOCALE_deDE = 3,
|
||||
LOCALE_zhCN = 4,
|
||||
LOCALE_zhTW = 5,
|
||||
LOCALE_esES = 6,
|
||||
LOCALE_esMX = 7,
|
||||
LOCALE_ruRU = 8,
|
||||
|
||||
TOTAL_LOCALES
|
||||
};
|
||||
|
||||
#define DEFAULT_LOCALE LOCALE_enUS
|
||||
|
||||
#define MAX_LOCALES 8
|
||||
#define MAX_ACCOUNT_TUTORIAL_VALUES 8
|
||||
|
||||
AC_COMMON_API extern char const* localeNames[TOTAL_LOCALES];
|
||||
|
||||
AC_COMMON_API bool IsLocaleValid(std::string const& locale);
|
||||
AC_COMMON_API LocaleConstant GetLocaleByName(const std::string& name);
|
||||
AC_COMMON_API const std::string GetNameByLocaleConstant(LocaleConstant localeConstant);
|
||||
AC_COMMON_API void CleanStringForMysqlQuery(std::string& str);
|
||||
|
||||
#define MAX_QUERY_LEN 32*1024
|
||||
|
||||
namespace Acore
|
||||
{
|
||||
template<class ArgumentType, class ResultType>
|
||||
struct unary_function
|
||||
{
|
||||
typedef ArgumentType argument_type;
|
||||
typedef ResultType result_type;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_COMPILERDEFS_H
|
||||
#define ACORE_COMPILERDEFS_H
|
||||
|
||||
#define AC_PLATFORM_WINDOWS 0
|
||||
#define AC_PLATFORM_UNIX 1
|
||||
#define AC_PLATFORM_APPLE 2
|
||||
#define AC_PLATFORM_INTEL 3
|
||||
|
||||
// must be first (win 64 also define _WIN32)
|
||||
#if defined( _WIN64 )
|
||||
#define AC_PLATFORM AC_PLATFORM_WINDOWS
|
||||
#elif defined( __WIN32__ ) || defined( WIN32 ) || defined( _WIN32 )
|
||||
#define AC_PLATFORM AC_PLATFORM_WINDOWS
|
||||
#elif defined( __APPLE_CC__ )
|
||||
#define AC_PLATFORM AC_PLATFORM_APPLE
|
||||
#elif defined( __INTEL_COMPILER )
|
||||
#define AC_PLATFORM AC_PLATFORM_INTEL
|
||||
#else
|
||||
#define AC_PLATFORM AC_PLATFORM_UNIX
|
||||
#endif
|
||||
|
||||
#define AC_COMPILER_MICROSOFT 0
|
||||
#define AC_COMPILER_GNU 1
|
||||
#define AC_COMPILER_BORLAND 2
|
||||
#define AC_COMPILER_INTEL 3
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define AC_COMPILER AC_COMPILER_MICROSOFT
|
||||
#elif defined( __BORLANDC__ )
|
||||
#define AC_COMPILER AC_COMPILER_BORLAND
|
||||
#elif defined( __INTEL_COMPILER )
|
||||
#define AC_COMPILER AC_COMPILER_INTEL
|
||||
#elif defined( __GNUC__ )
|
||||
#define AC_COMPILER AC_COMPILER_GNU
|
||||
#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
|
||||
#else
|
||||
# error "FATAL ERROR: Unknown compiler."
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BuiltInConfig.h"
|
||||
#include "Config.h"
|
||||
#include "GitRevision.h"
|
||||
|
||||
template<typename Fn>
|
||||
static std::string GetStringWithDefaultValueFromFunction(
|
||||
std::string const& key, Fn getter)
|
||||
{
|
||||
std::string const value = sConfigMgr->GetOption<std::string>(key, "");
|
||||
return value.empty() ? getter() : value;
|
||||
}
|
||||
|
||||
std::string BuiltInConfig::GetCMakeCommand()
|
||||
{
|
||||
return GetStringWithDefaultValueFromFunction(
|
||||
"CMakeCommand", GitRevision::GetCMakeCommand);
|
||||
}
|
||||
|
||||
std::string BuiltInConfig::GetBuildDirectory()
|
||||
{
|
||||
return GetStringWithDefaultValueFromFunction(
|
||||
"BuildDirectory", GitRevision::GetBuildDirectory);
|
||||
}
|
||||
|
||||
std::string BuiltInConfig::GetSourceDirectory()
|
||||
{
|
||||
return GetStringWithDefaultValueFromFunction(
|
||||
"SourceDirectory", GitRevision::GetSourceDirectory);
|
||||
}
|
||||
|
||||
std::string BuiltInConfig::GetMySQLExecutable()
|
||||
{
|
||||
return GetStringWithDefaultValueFromFunction(
|
||||
"MySQLExecutable", GitRevision::GetMySQLExecutable);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BUILT_IN_CONFIG_H
|
||||
#define BUILT_IN_CONFIG_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <string>
|
||||
|
||||
/// Provides helper functions to access built-in values
|
||||
/// which can be overwritten in config
|
||||
namespace BuiltInConfig
|
||||
{
|
||||
/// Returns the CMake command when any is specified in the config,
|
||||
/// returns the built-in path otherwise
|
||||
AC_COMMON_API std::string GetCMakeCommand();
|
||||
|
||||
/// Returns the build directory path when any is specified in the config,
|
||||
/// returns the built-in one otherwise
|
||||
AC_COMMON_API std::string GetBuildDirectory();
|
||||
|
||||
/// Returns the source directory path when any is specified in the config,
|
||||
/// returns the built-in one otherwise
|
||||
AC_COMMON_API std::string GetSourceDirectory();
|
||||
|
||||
/// Returns the path to the mysql executable (`mysql`) when any is specified
|
||||
/// in the config, returns the built-in one otherwise
|
||||
AC_COMMON_API std::string GetMySQLExecutable();
|
||||
|
||||
} // namespace BuiltInConfig
|
||||
|
||||
#endif // BUILT_IN_CONFIG_H
|
||||
@@ -0,0 +1,817 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Config.h"
|
||||
#include "Log.h"
|
||||
#include "StringConvert.h"
|
||||
#include "StringFormat.h"
|
||||
#include "Tokenize.h"
|
||||
#include "Util.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string _filename;
|
||||
std::vector<std::string> _additonalFiles;
|
||||
std::vector<std::string> _args;
|
||||
std::unordered_map<std::string /*name*/, std::string /*value*/> _configOptions;
|
||||
std::unordered_map<std::string /*name*/, std::string /*value*/> _envVarCache;
|
||||
std::mutex _configLock;
|
||||
ConfigPolicy _policy;
|
||||
|
||||
std::unordered_set<std::string> _criticalConfigOptions =
|
||||
{
|
||||
"RealmID",
|
||||
"LoginDatabaseInfo",
|
||||
"WorldDatabaseInfo",
|
||||
"CharacterDatabaseInfo",
|
||||
};
|
||||
|
||||
// Check system configs like *server.conf*
|
||||
bool IsAppConfig(std::string_view fileName)
|
||||
{
|
||||
std::size_t foundAuth = fileName.find("authserver.conf");
|
||||
std::size_t foundWorld = fileName.find("worldserver.conf");
|
||||
std::size_t foundImport = fileName.find("dbimport.conf");
|
||||
|
||||
return foundAuth != std::string_view::npos || foundWorld != std::string_view::npos || foundImport != std::string_view::npos;
|
||||
}
|
||||
|
||||
// Check logging system configs like Appender.* and Logger.*
|
||||
bool IsLoggingSystemOptions(std::string_view optionName)
|
||||
{
|
||||
std::size_t foundAppender = optionName.find("Appender.");
|
||||
std::size_t foundLogger = optionName.find("Logger.");
|
||||
|
||||
return foundAppender != std::string_view::npos || foundLogger != std::string_view::npos;
|
||||
}
|
||||
|
||||
Optional<ConfigSeverity> ParseSeverity(std::string_view value)
|
||||
{
|
||||
if (value.empty())
|
||||
return std::nullopt;
|
||||
|
||||
std::string lowered(value);
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
if (lowered == "skip")
|
||||
return ConfigSeverity::Skip;
|
||||
|
||||
if (lowered == "warn" || lowered == "warning")
|
||||
return ConfigSeverity::Warn;
|
||||
|
||||
if (lowered == "error")
|
||||
return ConfigSeverity::Error;
|
||||
|
||||
if (lowered == "fatal" || lowered == "abort" || lowered == "panic")
|
||||
return ConfigSeverity::Fatal;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename Format, typename... Args>
|
||||
inline void PrintError(std::string_view filename, Format&& fmt, Args&& ... args)
|
||||
{
|
||||
std::string message = Acore::StringFormat(std::forward<Format>(fmt), std::forward<Args>(args)...);
|
||||
|
||||
if (IsAppConfig(filename))
|
||||
{
|
||||
fmt::print("{}\n", message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("server.loading", message);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Format, typename... Args>
|
||||
inline void LogWithSeverity(ConfigSeverity severity, std::string_view filename, Format&& fmt, Args&&... args)
|
||||
{
|
||||
std::string message = Acore::StringFormat(std::forward<Format>(fmt), std::forward<Args>(args)...);
|
||||
|
||||
switch (severity)
|
||||
{
|
||||
case ConfigSeverity::Skip:
|
||||
return;
|
||||
case ConfigSeverity::Warn:
|
||||
{
|
||||
if (IsAppConfig(filename))
|
||||
fmt::print("{}\n", message);
|
||||
|
||||
LOG_WARN("server.loading", message);
|
||||
return;
|
||||
}
|
||||
case ConfigSeverity::Error:
|
||||
{
|
||||
if (IsAppConfig(filename))
|
||||
fmt::print("{}\n", message);
|
||||
|
||||
LOG_ERROR("server.loading", message);
|
||||
return;
|
||||
}
|
||||
case ConfigSeverity::Fatal:
|
||||
{
|
||||
if (IsAppConfig(filename))
|
||||
fmt::print("{}\n", message);
|
||||
|
||||
LOG_FATAL("server.loading", message);
|
||||
ABORT(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigPolicy ApplyPolicyString(ConfigPolicy policy, std::string_view input)
|
||||
{
|
||||
if (input.empty())
|
||||
return policy;
|
||||
|
||||
std::vector<std::pair<std::string, ConfigSeverity>> overrides;
|
||||
Optional<ConfigSeverity> defaultOverride;
|
||||
|
||||
std::string tokenBuffer(input);
|
||||
for (std::string_view rawToken : Acore::Tokenize(tokenBuffer, ',', false))
|
||||
{
|
||||
std::string token = Acore::String::Trim(std::string(rawToken), std::locale());
|
||||
if (token.empty())
|
||||
continue;
|
||||
|
||||
auto separator = token.find('=');
|
||||
if (separator == std::string::npos)
|
||||
continue;
|
||||
|
||||
std::string key = Acore::String::Trim(token.substr(0, separator), std::locale());
|
||||
std::string value = Acore::String::Trim(token.substr(separator + 1), std::locale());
|
||||
|
||||
if (key.empty() || value.empty())
|
||||
continue;
|
||||
|
||||
auto severity = ParseSeverity(value);
|
||||
if (!severity)
|
||||
continue;
|
||||
|
||||
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
if (key == "default")
|
||||
{
|
||||
defaultOverride = severity;
|
||||
continue;
|
||||
}
|
||||
|
||||
overrides.emplace_back(std::move(key), *severity);
|
||||
}
|
||||
|
||||
if (defaultOverride)
|
||||
{
|
||||
policy.defaultSeverity = *defaultOverride;
|
||||
policy.missingFileSeverity = *defaultOverride;
|
||||
policy.missingOptionSeverity = *defaultOverride;
|
||||
policy.criticalOptionSeverity = *defaultOverride;
|
||||
policy.unknownOptionSeverity = *defaultOverride;
|
||||
policy.valueErrorSeverity = *defaultOverride;
|
||||
}
|
||||
|
||||
for (auto const& [key, severity] : overrides)
|
||||
{
|
||||
if (key == "missing_file" || key == "file")
|
||||
policy.missingFileSeverity = severity;
|
||||
else if (key == "missing_option" || key == "option")
|
||||
policy.missingOptionSeverity = severity;
|
||||
else if (key == "critical_option" || key == "critical")
|
||||
policy.criticalOptionSeverity = severity;
|
||||
else if (key == "unknown_option" || key == "unknown")
|
||||
policy.unknownOptionSeverity = severity;
|
||||
else if (key == "value_error" || key == "value")
|
||||
policy.valueErrorSeverity = severity;
|
||||
}
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
ConfigPolicy ApplyPolicyFromArgs(ConfigPolicy policy, std::vector<std::string> const& args)
|
||||
{
|
||||
for (std::size_t i = 0; i < args.size(); ++i)
|
||||
{
|
||||
std::string const& arg = args[i];
|
||||
std::string_view value;
|
||||
|
||||
constexpr std::string_view shortOpt = "--config-policy";
|
||||
|
||||
if (arg.rfind(shortOpt, 0) == 0)
|
||||
{
|
||||
if (arg.size() == shortOpt.size() && (i + 1) < args.size())
|
||||
{
|
||||
value = args[i + 1];
|
||||
++i;
|
||||
}
|
||||
else if (arg.size() > shortOpt.size() && arg[shortOpt.size()] == '=')
|
||||
{
|
||||
value = std::string_view(arg).substr(shortOpt.size() + 1);
|
||||
}
|
||||
|
||||
if (!value.empty())
|
||||
policy = ApplyPolicyString(policy, value);
|
||||
}
|
||||
}
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
void AddKey(std::string const& optionName, std::string const& optionKey, std::string_view fileName, bool isOptional, [[maybe_unused]] bool isReload)
|
||||
{
|
||||
auto const& itr = _configOptions.find(optionName);
|
||||
|
||||
// Check old option
|
||||
if (isOptional && itr == _configOptions.end())
|
||||
{
|
||||
if (!IsLoggingSystemOptions(optionName) && !isReload)
|
||||
{
|
||||
LogWithSeverity(_policy.unknownOptionSeverity, fileName, "> Config::LoadFile: Found incorrect option '{}' in config file '{}'. Skip", optionName, fileName);
|
||||
|
||||
#ifdef CONFIG_ABORT_INCORRECT_OPTIONS
|
||||
ABORT("> Core can't start if found incorrect options");
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check exit option
|
||||
if (itr != _configOptions.end())
|
||||
{
|
||||
_configOptions.erase(optionName);
|
||||
}
|
||||
|
||||
_configOptions.emplace(optionName, optionKey);
|
||||
}
|
||||
|
||||
bool ParseFile(std::string const& file, bool isOptional, bool isReload)
|
||||
{
|
||||
std::ifstream in(file);
|
||||
|
||||
if (in.fail())
|
||||
{
|
||||
ConfigSeverity severity = isOptional ? ConfigSeverity::Skip : _policy.missingFileSeverity;
|
||||
LogWithSeverity(severity, file, "> Config::LoadFile: Failed open {}file '{}'", isOptional ? "optional " : "", file);
|
||||
// Treat SKIP as a successful no-op so the app can proceed
|
||||
return severity == ConfigSeverity::Skip;
|
||||
}
|
||||
|
||||
uint32 count = 0;
|
||||
uint32 lineNumber = 0;
|
||||
std::unordered_map<std::string /*name*/, std::string /*value*/> fileConfigs;
|
||||
|
||||
auto IsDuplicateOption = [&](std::string const& confOption)
|
||||
{
|
||||
auto const& itr = fileConfigs.find(confOption);
|
||||
if (itr != fileConfigs.end())
|
||||
{
|
||||
PrintError(file, "> Config::LoadFile: Duplicate key name '{}' in config file '{}'", confOption, file);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
while (in.good())
|
||||
{
|
||||
lineNumber++;
|
||||
std::string line;
|
||||
std::getline(in, line);
|
||||
|
||||
// read line error
|
||||
if (!in.good() && !in.eof())
|
||||
throw ConfigException(Acore::StringFormat("> Config::LoadFile: Failure to read line number {} in file '{}'", lineNumber, file));
|
||||
|
||||
// remove whitespace in line
|
||||
line = Acore::String::Trim(line, in.getloc());
|
||||
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
// comments and headers
|
||||
if (line[0] == '#' || line[0] == '[')
|
||||
continue;
|
||||
|
||||
auto const equal_pos = line.find('=');
|
||||
|
||||
if (equal_pos == std::string::npos || equal_pos == line.length())
|
||||
{
|
||||
PrintError(file, "> Config::LoadFile: Failure to read line number {} in file '{}'. Skip this line", lineNumber, file);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entry = Acore::String::Trim(line.substr(0, equal_pos), in.getloc());
|
||||
auto value = Acore::String::Trim(line.substr(equal_pos + 1, std::string::npos), in.getloc());
|
||||
|
||||
value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
|
||||
|
||||
// Skip if 2+ same options in one config file
|
||||
if (IsDuplicateOption(entry))
|
||||
continue;
|
||||
|
||||
// Add to temp container
|
||||
fileConfigs.emplace(entry, value);
|
||||
count++;
|
||||
}
|
||||
|
||||
// No lines read
|
||||
if (!count)
|
||||
{
|
||||
ConfigSeverity severity = isOptional ? ConfigSeverity::Skip : _policy.missingFileSeverity;
|
||||
LogWithSeverity(severity, file, "> Config::LoadFile: Empty file '{}'", file);
|
||||
// Treat SKIP as a successful no-op
|
||||
return severity == ConfigSeverity::Skip;
|
||||
}
|
||||
|
||||
// Add correct keys if file load without errors
|
||||
for (auto const& [entry, key] : fileConfigs)
|
||||
{
|
||||
AddKey(entry, key, file, isOptional, isReload);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadFile(std::string const& file, bool isOptional, bool isReload)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ParseFile(file, isOptional, isReload);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
PrintError(file, "> {}", e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts ini keys to the environment variable key (upper snake case).
|
||||
// Example of conversions:
|
||||
// SomeConfig => SOME_CONFIG
|
||||
// myNestedConfig.opt1 => MY_NESTED_CONFIG_OPT_1
|
||||
// LogDB.Opt.ClearTime => LOG_DB_OPT_CLEAR_TIME
|
||||
std::string IniKeyToEnvVarKey(std::string const& key)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
const char* str = key.c_str();
|
||||
std::size_t n = key.length();
|
||||
|
||||
char curr;
|
||||
bool isEnd;
|
||||
bool nextIsUpper;
|
||||
bool currIsNumeric;
|
||||
bool nextIsNumeric;
|
||||
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
curr = str[i];
|
||||
if (curr == ' ' || curr == '.' || curr == '-')
|
||||
{
|
||||
result += '_';
|
||||
continue;
|
||||
}
|
||||
|
||||
isEnd = i == n - 1;
|
||||
if (!isEnd)
|
||||
{
|
||||
nextIsUpper = isupper(str[i + 1]);
|
||||
|
||||
// handle "aB" to "A_B"
|
||||
if (!isupper(curr) && nextIsUpper)
|
||||
{
|
||||
result += static_cast<char>(std::toupper(curr));
|
||||
result += '_';
|
||||
continue;
|
||||
}
|
||||
|
||||
currIsNumeric = isNumeric(curr);
|
||||
nextIsNumeric = isNumeric(str[i + 1]);
|
||||
|
||||
// handle "a1" to "a_1"
|
||||
if (!currIsNumeric && nextIsNumeric)
|
||||
{
|
||||
result += static_cast<char>(std::toupper(curr));
|
||||
result += '_';
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle "1a" to "1_a"
|
||||
if (currIsNumeric && !nextIsNumeric)
|
||||
{
|
||||
result += static_cast<char>(std::toupper(curr));
|
||||
result += '_';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result += static_cast<char>(std::toupper(curr));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string GetEnvVarName(std::string const& configName)
|
||||
{
|
||||
return "AC_" + IniKeyToEnvVarKey(configName);
|
||||
}
|
||||
|
||||
Optional<std::string> EnvVarForIniKey(std::string const& key)
|
||||
{
|
||||
std::string envKey = GetEnvVarName(key);
|
||||
char* val = std::getenv(envKey.c_str());
|
||||
if (!val)
|
||||
return std::nullopt;
|
||||
|
||||
return std::string(val);
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigMgr::LoadInitial(std::string const& file, bool isReload /*= false*/)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configLock);
|
||||
_configOptions.clear();
|
||||
return LoadFile(file, false, isReload);
|
||||
}
|
||||
|
||||
bool ConfigMgr::LoadAdditionalFile(std::string file, bool isOptional /*= false*/, bool isReload /*= false*/)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configLock);
|
||||
return LoadFile(file, isOptional, isReload);
|
||||
}
|
||||
|
||||
ConfigMgr* ConfigMgr::instance()
|
||||
{
|
||||
static ConfigMgr instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
bool ConfigMgr::Reload()
|
||||
{
|
||||
if (!LoadAppConfigs(true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadModulesConfigs(true, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OverrideWithEnvVariablesIfAny();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check the _envVarCache if the env var is there
|
||||
// if not, check the env for the value
|
||||
Optional<std::string> GetEnvFromCache(std::string const& configName, std::string const& envVarName)
|
||||
{
|
||||
auto foundInCache = _envVarCache.find(envVarName);
|
||||
Optional<std::string> foundInEnv;
|
||||
// If it's not in the cache
|
||||
if (foundInCache == _envVarCache.end())
|
||||
{
|
||||
// Check the env itself
|
||||
foundInEnv = EnvVarForIniKey(configName);
|
||||
if (foundInEnv)
|
||||
{
|
||||
// If it's found in the env, put it in the cache
|
||||
_envVarCache.emplace(envVarName, *foundInEnv);
|
||||
}
|
||||
// Return the result of checking env
|
||||
return foundInEnv;
|
||||
}
|
||||
|
||||
return foundInCache->second;
|
||||
}
|
||||
|
||||
std::vector<std::string> ConfigMgr::OverrideWithEnvVariablesIfAny()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configLock);
|
||||
|
||||
std::vector<std::string> overriddenKeys;
|
||||
|
||||
for (auto& itr : _configOptions)
|
||||
{
|
||||
if (itr.first.empty())
|
||||
continue;
|
||||
|
||||
Optional<std::string> envVar = EnvVarForIniKey(itr.first);
|
||||
if (!envVar)
|
||||
continue;
|
||||
|
||||
itr.second = *envVar;
|
||||
|
||||
overriddenKeys.push_back(itr.first);
|
||||
}
|
||||
|
||||
return overriddenKeys;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T ConfigMgr::GetValueDefault(std::string const& name, T const& def, bool showLogs /*= true*/) const
|
||||
{
|
||||
std::string strValue;
|
||||
|
||||
auto const& itr = _configOptions.find(name);
|
||||
bool notFound = itr == _configOptions.end();
|
||||
auto envVarName = GetEnvVarName(name);
|
||||
Optional<std::string> envVar = GetEnvFromCache(name, envVarName);
|
||||
if (envVar)
|
||||
{
|
||||
// If showLogs and this key/value pair wasn't found in the currently saved config
|
||||
if (showLogs && (notFound || itr->second != envVar->c_str()))
|
||||
{
|
||||
LOG_INFO("server.loading", "> Config: Found config value '{}' from environment variable '{}'.", name, envVarName );
|
||||
AddKey(name, envVar->c_str(), "ENVIRONMENT", false, false);
|
||||
}
|
||||
|
||||
strValue = *envVar;
|
||||
}
|
||||
else if (notFound)
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
bool isCritical = _criticalConfigOptions.find(name) != _criticalConfigOptions.end();
|
||||
ConfigSeverity severity = isCritical ? _policy.criticalOptionSeverity : _policy.missingOptionSeverity;
|
||||
|
||||
if (isCritical)
|
||||
{
|
||||
LogWithSeverity(severity, _filename,
|
||||
"> Config:\n\nFATAL ERROR: Missing property {} in config file {}, add \"{} = {}\" to this file or define '{}' as an environment variable\n\nYour server cannot start without this option!",
|
||||
name, _filename, name, Acore::ToString(def), envVarName);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string configs = _filename;
|
||||
if (!_moduleConfigFiles.empty())
|
||||
configs += " or module config";
|
||||
|
||||
LogWithSeverity(severity, _filename,
|
||||
"> Config: Missing property {} in config file {}, add \"{} = {}\" to this file or define '{}' as an environment variable.",
|
||||
name, configs, name, def, envVarName);
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
else
|
||||
{
|
||||
strValue = itr->second;
|
||||
}
|
||||
|
||||
auto value = Acore::StringTo<T>(strValue);
|
||||
if (!value)
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
LogWithSeverity(_policy.valueErrorSeverity, _filename,
|
||||
"> Config: Bad value defined for name '{}', going to use '{}' instead",
|
||||
name, Acore::ToString(def));
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
return *value;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string ConfigMgr::GetValueDefault<std::string>(std::string const& name, std::string const& def, bool showLogs /*= true*/) const
|
||||
{
|
||||
auto const& itr = _configOptions.find(name);
|
||||
bool notFound = itr == _configOptions.end();
|
||||
auto envVarName = GetEnvVarName(name);
|
||||
Optional<std::string> envVar = GetEnvFromCache(name, envVarName);
|
||||
if (envVar)
|
||||
{
|
||||
// If showLogs and this key/value pair wasn't found in the currently saved config
|
||||
if (showLogs && (notFound || itr->second != envVar->c_str()))
|
||||
{
|
||||
LOG_INFO("server.loading", "> Config: Found config value '{}' from environment variable '{}'.", name, envVarName);
|
||||
AddKey(name, *envVar, "ENVIRONMENT", false, false);
|
||||
}
|
||||
|
||||
return *envVar;
|
||||
}
|
||||
else if (notFound)
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
bool isCritical = _criticalConfigOptions.find(name) != _criticalConfigOptions.end();
|
||||
ConfigSeverity severity = isCritical ? _policy.criticalOptionSeverity : _policy.missingOptionSeverity;
|
||||
|
||||
if (isCritical)
|
||||
{
|
||||
LogWithSeverity(severity, _filename,
|
||||
"> Config:\n\nFATAL ERROR: Missing property {} in config file {}, add \"{} = {}\" to this file or define '{}' as an environment variable.\n\nYour server cannot start without this option!",
|
||||
name, _filename, name, def, envVarName);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string configs = _filename;
|
||||
if (!_moduleConfigFiles.empty())
|
||||
configs += " or module config";
|
||||
|
||||
LogWithSeverity(severity, _filename,
|
||||
"> Config: Missing property {} in config file {}, add \"{} = {}\" to this file or define '{}' as an environment variable.",
|
||||
name, configs, name, def, envVarName);
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T ConfigMgr::GetOption(std::string const& name, T const& def, bool showLogs /*= true*/) const
|
||||
{
|
||||
return GetValueDefault<T>(name, def, showLogs);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool ConfigMgr::GetOption<bool>(std::string const& name, bool const& def, bool showLogs /*= true*/) const
|
||||
{
|
||||
std::string val = GetValueDefault(name, std::string(def ? "1" : "0"), showLogs);
|
||||
|
||||
auto boolVal = Acore::StringTo<bool>(val);
|
||||
if (!boolVal)
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
LogWithSeverity(_policy.valueErrorSeverity, _filename,
|
||||
"> Config: Bad value defined for name '{}', going to use '{}' instead",
|
||||
name, def ? "true" : "false");
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
return *boolVal;
|
||||
}
|
||||
|
||||
std::vector<std::string> ConfigMgr::GetKeysByString(std::string const& name)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configLock);
|
||||
|
||||
std::vector<std::string> keys;
|
||||
|
||||
for (auto const& [optionName, key] : _configOptions)
|
||||
{
|
||||
if (!optionName.compare(0, name.length(), name))
|
||||
{
|
||||
keys.emplace_back(optionName);
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
std::string const ConfigMgr::GetFilename()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configLock);
|
||||
return _filename;
|
||||
}
|
||||
|
||||
std::vector<std::string> const& ConfigMgr::GetArguments() const
|
||||
{
|
||||
return _args;
|
||||
}
|
||||
|
||||
std::string const ConfigMgr::GetConfigPath()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configLock);
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
return "configs/";
|
||||
#else
|
||||
return std::string(_CONF_DIR) + "/";
|
||||
#endif
|
||||
}
|
||||
|
||||
void ConfigMgr::Configure(std::string const& initFileName, std::vector<std::string> args, std::string_view modulesConfigList /*= {}*/, ConfigPolicy policy /*= {}*/)
|
||||
{
|
||||
_filename = initFileName;
|
||||
_args = std::move(args);
|
||||
_policy = policy;
|
||||
|
||||
if (char const* env = std::getenv("AC_CONFIG_POLICY"))
|
||||
_policy = ApplyPolicyString(_policy, env);
|
||||
|
||||
_policy = ApplyPolicyFromArgs(_policy, _args);
|
||||
|
||||
_additonalFiles.clear();
|
||||
_moduleConfigFiles.clear();
|
||||
|
||||
// Add modules config if exist
|
||||
if (!modulesConfigList.empty())
|
||||
{
|
||||
for (auto const& itr : Acore::Tokenize(modulesConfigList, ',', false))
|
||||
{
|
||||
if (!itr.empty())
|
||||
_additonalFiles.emplace_back(itr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigMgr::LoadAppConfigs(bool isReload /*= false*/)
|
||||
{
|
||||
// #1 - Load init config file .conf
|
||||
if (!LoadInitial(_filename, isReload))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigMgr::LoadModulesConfigs(bool isReload /*= false*/, bool isNeedPrintInfo /*= true*/)
|
||||
{
|
||||
if (_additonalFiles.empty())
|
||||
{
|
||||
// Send successful load if no found files
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isNeedPrintInfo)
|
||||
{
|
||||
LOG_INFO("server.loading", " ");
|
||||
LOG_INFO("server.loading", "Loading Modules Configuration...");
|
||||
}
|
||||
|
||||
// Start loading module configs
|
||||
std::string const& moduleConfigPath = GetConfigPath() + "modules/";
|
||||
|
||||
for (auto const& fileName : _additonalFiles)
|
||||
{
|
||||
bool isExistConfig = LoadAdditionalFile(moduleConfigPath + fileName, false, isReload);
|
||||
|
||||
if (isExistConfig)
|
||||
_moduleConfigFiles.emplace_back(fileName);
|
||||
}
|
||||
|
||||
if (isNeedPrintInfo)
|
||||
{
|
||||
if (!_moduleConfigFiles.empty())
|
||||
{
|
||||
// Print modules configurations
|
||||
LOG_INFO("server.loading", " ");
|
||||
LOG_INFO("server.loading", "Using modules configuration:");
|
||||
|
||||
for (auto const& itr : _moduleConfigFiles)
|
||||
{
|
||||
LOG_INFO("server.loading", "> {}", itr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_INFO("server.loading", "> Not found modules config files");
|
||||
}
|
||||
}
|
||||
|
||||
if (isNeedPrintInfo)
|
||||
{
|
||||
LOG_INFO("server.loading", " ");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define TEMPLATE_CONFIG_OPTION(__typename) \
|
||||
template __typename ConfigMgr::GetOption<__typename>(std::string const& name, __typename const& def, bool showLogs /*= true*/) const;
|
||||
|
||||
TEMPLATE_CONFIG_OPTION(std::string)
|
||||
TEMPLATE_CONFIG_OPTION(uint8)
|
||||
TEMPLATE_CONFIG_OPTION(int8)
|
||||
TEMPLATE_CONFIG_OPTION(uint16)
|
||||
TEMPLATE_CONFIG_OPTION(int16)
|
||||
TEMPLATE_CONFIG_OPTION(uint32)
|
||||
TEMPLATE_CONFIG_OPTION(int32)
|
||||
TEMPLATE_CONFIG_OPTION(uint64)
|
||||
TEMPLATE_CONFIG_OPTION(int64)
|
||||
TEMPLATE_CONFIG_OPTION(float)
|
||||
|
||||
#undef TEMPLATE_CONFIG_OPTION
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
enum class ConfigSeverity : uint8_t
|
||||
{
|
||||
Skip,
|
||||
Warn,
|
||||
Error,
|
||||
Fatal
|
||||
};
|
||||
|
||||
struct ConfigPolicy
|
||||
{
|
||||
ConfigSeverity defaultSeverity = ConfigSeverity::Warn;
|
||||
ConfigSeverity missingFileSeverity = ConfigSeverity::Error;
|
||||
ConfigSeverity missingOptionSeverity = ConfigSeverity::Warn;
|
||||
ConfigSeverity criticalOptionSeverity = ConfigSeverity::Fatal;
|
||||
ConfigSeverity unknownOptionSeverity = ConfigSeverity::Error;
|
||||
ConfigSeverity valueErrorSeverity = ConfigSeverity::Error;
|
||||
};
|
||||
|
||||
class ConfigMgr
|
||||
{
|
||||
ConfigMgr() = default;
|
||||
ConfigMgr(ConfigMgr const&) = delete;
|
||||
ConfigMgr& operator=(ConfigMgr const&) = delete;
|
||||
~ConfigMgr() = default;
|
||||
|
||||
public:
|
||||
bool LoadAppConfigs(bool isReload = false);
|
||||
bool LoadModulesConfigs(bool isReload = false, bool isNeedPrintInfo = true);
|
||||
void Configure(std::string const& initFileName, std::vector<std::string> args, std::string_view modulesConfigList = {}, ConfigPolicy policy = {});
|
||||
|
||||
static ConfigMgr* instance();
|
||||
|
||||
bool Reload();
|
||||
|
||||
/// Overrides configuration with environment variables and returns overridden keys
|
||||
std::vector<std::string> OverrideWithEnvVariablesIfAny();
|
||||
|
||||
std::string const GetFilename();
|
||||
std::string const GetConfigPath();
|
||||
[[nodiscard]] std::vector<std::string> const& GetArguments() const;
|
||||
std::vector<std::string> GetKeysByString(std::string const& name);
|
||||
|
||||
template<class T>
|
||||
T GetOption(std::string const& name, T const& def, bool showLogs = true) const;
|
||||
|
||||
bool isDryRun() { return dryRun; }
|
||||
void setDryRun(bool mode) { dryRun = mode; }
|
||||
|
||||
private:
|
||||
/// Method used only for loading main configuration files (authserver.conf and worldserver.conf)
|
||||
bool LoadInitial(std::string const& file, bool isReload = false);
|
||||
bool LoadAdditionalFile(std::string file, bool isOptional = false, bool isReload = false);
|
||||
|
||||
template<class T>
|
||||
T GetValueDefault(std::string const& name, T const& def, bool showLogs = true) const;
|
||||
|
||||
bool dryRun = false;
|
||||
|
||||
std::vector<std::string /*config variant*/> _moduleConfigFiles;
|
||||
};
|
||||
|
||||
class ConfigException : public std::length_error
|
||||
{
|
||||
public:
|
||||
explicit ConfigException(std::string const& message) : std::length_error(message) { }
|
||||
};
|
||||
|
||||
#define sConfigMgr ConfigMgr::instance()
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CONFIGVALUECACHE_H
|
||||
#define CONFIGVALUECACHE_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "Config.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include <variant>
|
||||
|
||||
template<typename ConfigEnum>
|
||||
class ConfigValueCache
|
||||
{
|
||||
static_assert(std::is_enum_v<ConfigEnum>);
|
||||
|
||||
public:
|
||||
enum class Reloadable : bool
|
||||
{
|
||||
No = false,
|
||||
Yes = true
|
||||
};
|
||||
|
||||
ConfigValueCache(ConfigEnum const configCount)
|
||||
{
|
||||
_configs.resize(static_cast<uint32>(configCount));
|
||||
_reloading = false;
|
||||
}
|
||||
|
||||
void Initialize(bool reload)
|
||||
{
|
||||
_reloading = reload;
|
||||
BuildConfigCache();
|
||||
_reloading = false;
|
||||
VerifyAllConfigsLoaded();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SetConfigValue(ConfigEnum const config, std::string const& configName, T const& defaultValue, Reloadable reloadable = Reloadable::Yes, std::function<bool(T const& value)>&& checker = {}, std::string const& validationErrorText = "")
|
||||
{
|
||||
uint32 const configIndex = static_cast<uint32>(config);
|
||||
ASSERT(configIndex < _configs.size(), "Config index out of bounds");
|
||||
T const& configValue = sConfigMgr->GetOption<T>(configName, defaultValue);
|
||||
|
||||
bool configValueChanged = false;
|
||||
if (_reloading)
|
||||
{
|
||||
if (std::get<T>(_configs[configIndex]) != configValue)
|
||||
configValueChanged = true;
|
||||
|
||||
if (reloadable == Reloadable::No)
|
||||
{
|
||||
if (configValueChanged)
|
||||
LOG_ERROR("server.loading", "Server Config (Name: {}) cannot be changed by reload. A server restart is required to update this config value.", configName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
ASSERT(_configs[configIndex].index() == 0, "Config overwriting an existing value");
|
||||
|
||||
if (checker && !checker(configValue))
|
||||
{
|
||||
LOG_ERROR("server.loading", "Server Config (Name: {}) failed validation check '{}'. Default value '{}' will be used instead.", configName, validationErrorText, defaultValue);
|
||||
_configs[configIndex] = defaultValue;
|
||||
}
|
||||
else
|
||||
_configs[configIndex] = configValue;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void OverwriteConfigValue(ConfigEnum const config, T const& value)
|
||||
{
|
||||
uint32 const configIndex = static_cast<uint32>(config);
|
||||
ASSERT(configIndex < _configs.size(), "Config index out of bounds");
|
||||
size_t const oldValueTypeIndex = _configs[configIndex].index();
|
||||
ASSERT(oldValueTypeIndex != 0, "Config value must already be set");
|
||||
_configs[configIndex] = value;
|
||||
ASSERT(oldValueTypeIndex == _configs[configIndex].index(), "Config value type changed");
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T GetConfigValue(ConfigEnum const config) const
|
||||
{
|
||||
uint32 const configIndex = static_cast<uint32>(config);
|
||||
ASSERT(configIndex < _configs.size(), "Config index out of bounds");
|
||||
ASSERT(_configs[configIndex].index() != 0, "Config value must already be set");
|
||||
|
||||
T const* value = std::get_if<T>(&_configs[configIndex]);
|
||||
ASSERT(value, "Wrong config variant type");
|
||||
|
||||
return *value;
|
||||
}
|
||||
|
||||
// Custom handling for string configs to convert from std::string to std::string_view
|
||||
std::string_view GetConfigValue(ConfigEnum const config) const
|
||||
{
|
||||
uint32 const configIndex = static_cast<uint32>(config);
|
||||
ASSERT(configIndex < _configs.size(), "Config index out of bounds");
|
||||
ASSERT(_configs[configIndex].index() != 0, "Config value must already be set");
|
||||
|
||||
std::string const* stringValue = std::get_if<std::string>(&_configs[configIndex]);
|
||||
ASSERT(stringValue, "Wrong config variant type");
|
||||
|
||||
return std::string_view(*stringValue);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void BuildConfigCache() = 0;
|
||||
|
||||
private:
|
||||
void VerifyAllConfigsLoaded()
|
||||
{
|
||||
uint32 configIndex = 0;
|
||||
for (auto const& variant : _configs)
|
||||
{
|
||||
if (variant.index() == 0)
|
||||
{
|
||||
LOG_ERROR("server.loading", "Server Config (Index: {}) is defined but not loaded, unable to continue.", configIndex);
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
++configIndex;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::variant<std::monostate, float, bool, uint32, std::string>> _configs;
|
||||
bool _reloading;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AES.h"
|
||||
#include "Errors.h"
|
||||
#include <limits>
|
||||
|
||||
Acore::Crypto::AES::AES(bool encrypting) : _ctx(EVP_CIPHER_CTX_new()), _encrypting(encrypting)
|
||||
{
|
||||
EVP_CIPHER_CTX_init(_ctx);
|
||||
int status = EVP_CipherInit_ex(_ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr, _encrypting ? 1 : 0);
|
||||
ASSERT(status);
|
||||
}
|
||||
|
||||
Acore::Crypto::AES::~AES()
|
||||
{
|
||||
EVP_CIPHER_CTX_free(_ctx);
|
||||
}
|
||||
|
||||
void Acore::Crypto::AES::Init(Key const& key)
|
||||
{
|
||||
int status = EVP_CipherInit_ex(_ctx, nullptr, nullptr, key.data(), nullptr, -1);
|
||||
ASSERT(status);
|
||||
}
|
||||
|
||||
bool Acore::Crypto::AES::Process(IV const& iv, uint8* data, std::size_t length, Tag& tag)
|
||||
{
|
||||
ASSERT(length <= static_cast<size_t>(std::numeric_limits<int>::max()));
|
||||
int len = static_cast<int>(length);
|
||||
if (!EVP_CipherInit_ex(_ctx, nullptr, nullptr, nullptr, iv.data(), -1))
|
||||
return false;
|
||||
|
||||
int outLen;
|
||||
if (!EVP_CipherUpdate(_ctx, data, &outLen, data, len))
|
||||
return false;
|
||||
|
||||
len -= outLen;
|
||||
|
||||
if (!_encrypting && !EVP_CIPHER_CTX_ctrl(_ctx, EVP_CTRL_GCM_SET_TAG, sizeof(tag), tag))
|
||||
return false;
|
||||
|
||||
if (!EVP_CipherFinal_ex(_ctx, data + outLen, &outLen))
|
||||
return false;
|
||||
|
||||
ASSERT(len == outLen);
|
||||
|
||||
if (_encrypting && !EVP_CIPHER_CTX_ctrl(_ctx, EVP_CTRL_GCM_GET_TAG, sizeof(tag), tag))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef Azeroth_AES_h__
|
||||
#define Azeroth_AES_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include <array>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
class AC_COMMON_API AES
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t IV_SIZE_BYTES = 12;
|
||||
static constexpr std::size_t KEY_SIZE_BYTES = 16;
|
||||
static constexpr std::size_t TAG_SIZE_BYTES = 12;
|
||||
|
||||
using IV = std::array<uint8, IV_SIZE_BYTES>;
|
||||
using Key = std::array<uint8, KEY_SIZE_BYTES>;
|
||||
using Tag = uint8[TAG_SIZE_BYTES];
|
||||
|
||||
explicit AES(bool encrypting);
|
||||
~AES();
|
||||
|
||||
void Init(Key const& key);
|
||||
|
||||
bool Process(IV const& iv, uint8* data, std::size_t length, Tag& tag);
|
||||
|
||||
private:
|
||||
EVP_CIPHER_CTX* _ctx;
|
||||
bool _encrypting;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // Azeroth_AES_h__
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ARC4.h"
|
||||
#include "Errors.h"
|
||||
|
||||
Acore::Crypto::ARC4::ARC4() : _ctx(EVP_CIPHER_CTX_new())
|
||||
{
|
||||
_cipher = EVP_CIPHER_fetch(nullptr, "RC4", nullptr);
|
||||
|
||||
EVP_CIPHER_CTX_init(_ctx);
|
||||
int result = EVP_EncryptInit_ex(_ctx, _cipher, nullptr, nullptr, nullptr);
|
||||
ASSERT(result == 1);
|
||||
}
|
||||
|
||||
Acore::Crypto::ARC4::~ARC4()
|
||||
{
|
||||
EVP_CIPHER_CTX_free(_ctx);
|
||||
EVP_CIPHER_free(_cipher);
|
||||
}
|
||||
|
||||
void Acore::Crypto::ARC4::Init(uint8 const* seed, std::size_t len)
|
||||
{
|
||||
int result1 = EVP_CIPHER_CTX_set_key_length(_ctx, len);
|
||||
ASSERT(result1 == 1);
|
||||
int result2 = EVP_EncryptInit_ex(_ctx, nullptr, nullptr, seed, nullptr);
|
||||
ASSERT(result2 == 1);
|
||||
}
|
||||
|
||||
void Acore::Crypto::ARC4::UpdateData(uint8* data, std::size_t len)
|
||||
{
|
||||
int outlen = 0;
|
||||
int result1 = EVP_EncryptUpdate(_ctx, data, &outlen, data, len);
|
||||
ASSERT(result1 == 1);
|
||||
int result2 = EVP_EncryptFinal_ex(_ctx, data, &outlen);
|
||||
ASSERT(result2 == 1);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _AUTH_SARC4_H
|
||||
#define _AUTH_SARC4_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <array>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
class AC_COMMON_API ARC4
|
||||
{
|
||||
public:
|
||||
ARC4();
|
||||
~ARC4();
|
||||
|
||||
void Init(uint8 const* seed, std::size_t len);
|
||||
|
||||
template <typename Container>
|
||||
void Init(Container const& c) { Init(std::data(c), std::size(c)); }
|
||||
|
||||
void UpdateData(uint8* data, std::size_t len);
|
||||
|
||||
template <typename Container>
|
||||
void UpdateData(Container& c) { UpdateData(std::data(c), std::size(c)); }
|
||||
private:
|
||||
EVP_CIPHER* _cipher;
|
||||
EVP_CIPHER_CTX* _ctx;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Argon2.h"
|
||||
#include <argon2/argon2.h>
|
||||
|
||||
/*static*/ Optional<std::string> Acore::Crypto::Argon2::Hash(std::string const& password, BigNumber const& salt, uint32 nIterations, uint32 kibMemoryCost)
|
||||
{
|
||||
char buf[ENCODED_HASH_LEN];
|
||||
std::vector<uint8> saltBytes = salt.ToByteVector();
|
||||
int status = argon2id_hash_encoded(
|
||||
nIterations,
|
||||
kibMemoryCost,
|
||||
PARALLELISM,
|
||||
password.c_str(), password.length(),
|
||||
saltBytes.data(), saltBytes.size(),
|
||||
HASH_LEN, buf, ENCODED_HASH_LEN
|
||||
);
|
||||
|
||||
if (status == ARGON2_OK)
|
||||
return std::string(buf);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/*static*/ bool Acore::Crypto::Argon2::Verify(std::string const& password, std::string const& hash)
|
||||
{
|
||||
int status = argon2id_verify(hash.c_str(), password.c_str(), password.length());
|
||||
return (status == ARGON2_OK);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AC_ARGON2_H
|
||||
#define AC_ARGON2_H
|
||||
|
||||
#include "BigNumber.h"
|
||||
#include "Optional.h"
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
struct AC_COMMON_API Argon2
|
||||
{
|
||||
static constexpr uint32 HASH_LEN = 16; // 128 bits, in bytes
|
||||
static constexpr uint32 ENCODED_HASH_LEN = 100; // in chars
|
||||
static constexpr uint32 DEFAULT_ITERATIONS = 10; // determined by dice roll, guaranteed to be secure (not really)
|
||||
static constexpr uint32 DEFAULT_MEMORY_COST = (1u << 17); // 2^17 kibibytes is 2^7 mebibytes is ~100MB
|
||||
static constexpr uint32 PARALLELISM = 1; // we don't support threaded hashing
|
||||
|
||||
static Optional<std::string> Hash(std::string const& password, BigNumber const& salt, uint32 nIterations = DEFAULT_ITERATIONS, uint32 kibMemoryCost = DEFAULT_MEMORY_COST);
|
||||
static bool Verify(std::string const& password, std::string const& hash);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AuthCrypt.h"
|
||||
#include "Errors.h"
|
||||
#include "HMAC.h"
|
||||
|
||||
void AuthCrypt::Init(SessionKey const& K)
|
||||
{
|
||||
uint8 ServerEncryptionKey[] = { 0xCC, 0x98, 0xAE, 0x04, 0xE8, 0x97, 0xEA, 0xCA, 0x12, 0xDD, 0xC0, 0x93, 0x42, 0x91, 0x53, 0x57 };
|
||||
_serverEncrypt.Init(Acore::Crypto::HMAC_SHA1::GetDigestOf(ServerEncryptionKey, K));
|
||||
|
||||
uint8 ServerDecryptionKey[] = { 0xC2, 0xB3, 0x72, 0x3C, 0xC6, 0xAE, 0xD9, 0xB5, 0x34, 0x3C, 0x53, 0xEE, 0x2F, 0x43, 0x67, 0xCE };
|
||||
_clientDecrypt.Init(Acore::Crypto::HMAC_SHA1::GetDigestOf(ServerDecryptionKey, K));
|
||||
|
||||
// Drop first 1024 bytes, as WoW uses ARC4-drop1024.
|
||||
std::array<uint8, 1024> syncBuf{};
|
||||
_serverEncrypt.UpdateData(syncBuf);
|
||||
_clientDecrypt.UpdateData(syncBuf);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void AuthCrypt::DecryptRecv(uint8* data, std::size_t len)
|
||||
{
|
||||
ASSERT(_initialized);
|
||||
_clientDecrypt.UpdateData(data, len);
|
||||
}
|
||||
|
||||
void AuthCrypt::EncryptSend(uint8* data, std::size_t len)
|
||||
{
|
||||
ASSERT(_initialized);
|
||||
_serverEncrypt.UpdateData(data, len);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _AUTHCRYPT_H
|
||||
#define _AUTHCRYPT_H
|
||||
|
||||
#include "ARC4.h"
|
||||
#include "AuthDefines.h"
|
||||
|
||||
class AC_COMMON_API AuthCrypt
|
||||
{
|
||||
public:
|
||||
AuthCrypt() = default;
|
||||
|
||||
void Init(SessionKey const& K);
|
||||
void DecryptRecv(uint8* data, std::size_t len);
|
||||
void EncryptSend(uint8* data, std::size_t len);
|
||||
|
||||
bool IsInitialized() const { return _initialized; }
|
||||
|
||||
private:
|
||||
Acore::Crypto::ARC4 _clientDecrypt;
|
||||
Acore::Crypto::ARC4 _serverEncrypt;
|
||||
bool _initialized{ false };
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_AUTHDEFINES_H
|
||||
#define AZEROTHCORE_AUTHDEFINES_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <array>
|
||||
|
||||
constexpr std::size_t SESSION_KEY_LENGTH = 40;
|
||||
using SessionKey = std::array<uint8, SESSION_KEY_LENGTH>;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SRP6.h"
|
||||
#include "CryptoRandom.h"
|
||||
#include "Util.h"
|
||||
#include <functional>
|
||||
|
||||
using SHA1 = Acore::Crypto::SHA1;
|
||||
using SRP6 = Acore::Crypto::SRP6;
|
||||
|
||||
/*static*/ std::array<uint8, 1> const SRP6::g = { 7 };
|
||||
/*static*/ std::array<uint8, 32> const SRP6::N = HexStrToByteArray<32>("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", true);
|
||||
/*static*/ BigNumber const SRP6::_g(SRP6::g);
|
||||
/*static*/ BigNumber const SRP6::_N(N);
|
||||
|
||||
/*static*/ std::pair<SRP6::Salt, SRP6::Verifier> SRP6::MakeRegistrationData(std::string const& username, std::string const& password)
|
||||
{
|
||||
std::pair<SRP6::Salt, SRP6::Verifier> res;
|
||||
Crypto::GetRandomBytes(res.first); // random salt
|
||||
res.second = CalculateVerifier(username, password, res.first);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*static*/ SRP6::Verifier SRP6::CalculateVerifier(std::string const& username, std::string const& password, SRP6::Salt const& salt)
|
||||
{
|
||||
// v = g ^ H(s || H(u || ':' || p)) mod N
|
||||
return _g.ModExp(
|
||||
SHA1::GetDigestOf(
|
||||
salt,
|
||||
SHA1::GetDigestOf(username, ":", password)
|
||||
)
|
||||
,_N).ToByteArray<32>();
|
||||
}
|
||||
|
||||
/*static*/ SessionKey SRP6::SHA1Interleave(SRP6::EphemeralKey const& S)
|
||||
{
|
||||
// split S into two buffers
|
||||
std::array<uint8, EPHEMERAL_KEY_LENGTH / 2> buf0{}, buf1{};
|
||||
for (std::size_t i = 0; i < EPHEMERAL_KEY_LENGTH / 2; ++i)
|
||||
{
|
||||
buf0[i] = S[2 * i + 0];
|
||||
buf1[i] = S[2 * i + 1];
|
||||
}
|
||||
|
||||
// find position of first nonzero byte
|
||||
std::size_t p = 0;
|
||||
while (p < EPHEMERAL_KEY_LENGTH && !S[p])
|
||||
++p;
|
||||
|
||||
if (p & 1)
|
||||
++p; // skip one extra byte if p is odd
|
||||
|
||||
p /= 2; // offset into buffers
|
||||
|
||||
// hash each of the halves, starting at the first nonzero byte
|
||||
SHA1::Digest const hash0 = SHA1::GetDigestOf(buf0.data() + p, EPHEMERAL_KEY_LENGTH / 2 - p);
|
||||
SHA1::Digest const hash1 = SHA1::GetDigestOf(buf1.data() + p, EPHEMERAL_KEY_LENGTH / 2 - p);
|
||||
|
||||
// stick the two hashes back together
|
||||
SessionKey K;
|
||||
for (std::size_t i = 0; i < SHA1::DIGEST_LENGTH; ++i)
|
||||
{
|
||||
K[2 * i + 0] = hash0[i];
|
||||
K[2 * i + 1] = hash1[i];
|
||||
}
|
||||
return K;
|
||||
}
|
||||
|
||||
SRP6::SRP6(std::string const& username, Salt const& salt, Verifier const& verifier)
|
||||
: _I(SHA1::GetDigestOf(username)), _b(Crypto::GetRandomBytes<32>()), _v(verifier), s(salt), B(_B(_b, _v)) {}
|
||||
|
||||
std::optional<SessionKey> SRP6::VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM)
|
||||
{
|
||||
ASSERT(!_used, "A single SRP6 object must only ever be used to verify ONCE!");
|
||||
_used = true;
|
||||
|
||||
BigNumber const _A(A);
|
||||
if ((_A % _N).IsZero())
|
||||
return std::nullopt;
|
||||
|
||||
BigNumber const u(SHA1::GetDigestOf(A, B));
|
||||
EphemeralKey const S = (_A * (_v.ModExp(u, _N))).ModExp(_b, N).ToByteArray<32>();
|
||||
|
||||
SessionKey K = SHA1Interleave(S);
|
||||
|
||||
// NgHash = H(N) xor H(g)
|
||||
SHA1::Digest const NHash = SHA1::GetDigestOf(N);
|
||||
SHA1::Digest const gHash = SHA1::GetDigestOf(g);
|
||||
SHA1::Digest NgHash;
|
||||
std::transform(NHash.begin(), NHash.end(), gHash.begin(), NgHash.begin(), std::bit_xor<>());
|
||||
|
||||
SHA1::Digest const ourM = SHA1::GetDigestOf(NgHash, _I, s, A, B, K);
|
||||
if (ourM == clientM)
|
||||
return K;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_SRP6_H
|
||||
#define AZEROTHCORE_SRP6_H
|
||||
|
||||
#include "AuthDefines.h"
|
||||
#include "BigNumber.h"
|
||||
#include "CryptoHash.h"
|
||||
#include <optional>
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
class AC_COMMON_API SRP6
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t SALT_LENGTH = 32;
|
||||
using Salt = std::array<uint8, SALT_LENGTH>;
|
||||
|
||||
static constexpr std::size_t VERIFIER_LENGTH = 32;
|
||||
using Verifier = std::array<uint8, VERIFIER_LENGTH>;
|
||||
|
||||
static constexpr std::size_t EPHEMERAL_KEY_LENGTH = 32;
|
||||
using EphemeralKey = std::array<uint8, EPHEMERAL_KEY_LENGTH>;
|
||||
|
||||
static std::array<uint8, 1> const g;
|
||||
static std::array<uint8, 32> const N;
|
||||
|
||||
// username + password must be passed through Utf8ToUpperOnlyLatin FIRST!
|
||||
static std::pair<Salt, Verifier> MakeRegistrationData(std::string const& username, std::string const& password);
|
||||
|
||||
// username + password must be passed through Utf8ToUpperOnlyLatin FIRST!
|
||||
static bool CheckLogin(std::string const& username, std::string const& password, Salt const& salt, Verifier const& verifier)
|
||||
{
|
||||
return (verifier == CalculateVerifier(username, password, salt));
|
||||
}
|
||||
|
||||
static SHA1::Digest GetSessionVerifier(EphemeralKey const& A, SHA1::Digest const& clientM, SessionKey const& K)
|
||||
{
|
||||
return SHA1::GetDigestOf(A, clientM, K);
|
||||
}
|
||||
|
||||
SRP6(std::string const& username, Salt const& salt, Verifier const& verifier);
|
||||
std::optional<SessionKey> VerifyChallengeResponse(EphemeralKey const& A, SHA1::Digest const& clientM);
|
||||
|
||||
private:
|
||||
bool _used = false; // a single instance can only be used to verify once
|
||||
|
||||
static Verifier CalculateVerifier(std::string const& username, std::string const& password, Salt const& salt);
|
||||
static SessionKey SHA1Interleave(EphemeralKey const& S);
|
||||
|
||||
/* global algorithm parameters */
|
||||
static BigNumber const _g; // a [g]enerator for the ring of integers mod N, algorithm parameter
|
||||
static BigNumber const _N; // the modulus, an algorithm parameter; all operations are mod this
|
||||
|
||||
static EphemeralKey _B(BigNumber const& b, BigNumber const& v) { return ((_g.ModExp(b, _N) + (v * 3)) % N).ToByteArray<EPHEMERAL_KEY_LENGTH>(); }
|
||||
|
||||
/* per-instantiation parameters, set on construction */
|
||||
SHA1::Digest const _I; // H(I) - the username, all uppercase
|
||||
BigNumber const _b; // b - randomly chosen by the server, 19 bytes, never given out
|
||||
BigNumber const _v; // v - the user's password verifier, derived from s + H(USERNAME || ":" || PASSWORD)
|
||||
|
||||
public:
|
||||
Salt const s; // s - the user's password salt, random, used to calculate v on registration
|
||||
EphemeralKey const B; // B = 3v + g^b
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BigNumber.h"
|
||||
#include "Errors.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <openssl/bn.h>
|
||||
|
||||
BigNumber::BigNumber()
|
||||
: _bn(BN_new())
|
||||
{ }
|
||||
|
||||
BigNumber::BigNumber(BigNumber const& bn)
|
||||
: _bn(BN_dup(bn.BN()))
|
||||
{ }
|
||||
|
||||
BigNumber::~BigNumber()
|
||||
{
|
||||
BN_free(_bn);
|
||||
}
|
||||
|
||||
void BigNumber::SetDword(int32 val)
|
||||
{
|
||||
SetDword(uint32(std::abs(val)));
|
||||
if (val < 0)
|
||||
BN_set_negative(_bn, 1);
|
||||
}
|
||||
|
||||
void BigNumber::SetDword(uint32 val)
|
||||
{
|
||||
BN_set_word(_bn, val);
|
||||
}
|
||||
|
||||
void BigNumber::SetQword(uint64 val)
|
||||
{
|
||||
BN_set_word(_bn, (uint32)(val >> 32));
|
||||
BN_lshift(_bn, _bn, 32);
|
||||
BN_add_word(_bn, (uint32)(val & 0xFFFFFFFF));
|
||||
}
|
||||
|
||||
void BigNumber::SetBinary(uint8 const* bytes, int32 len, bool littleEndian)
|
||||
{
|
||||
if (littleEndian)
|
||||
BN_lebin2bn(bytes, len, _bn);
|
||||
else
|
||||
BN_bin2bn(bytes, len, _bn);
|
||||
}
|
||||
|
||||
bool BigNumber::SetHexStr(char const* str)
|
||||
{
|
||||
int n = BN_hex2bn(&_bn, str);
|
||||
return (n > 0);
|
||||
}
|
||||
|
||||
void BigNumber::SetRand(int32 numbits)
|
||||
{
|
||||
BN_rand(_bn, numbits, 0, 1);
|
||||
}
|
||||
|
||||
BigNumber& BigNumber::operator=(BigNumber const& bn)
|
||||
{
|
||||
if (this == &bn)
|
||||
return *this;
|
||||
|
||||
BN_copy(_bn, bn._bn);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigNumber& BigNumber::operator+=(BigNumber const& bn)
|
||||
{
|
||||
BN_add(_bn, _bn, bn._bn);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigNumber& BigNumber::operator-=(BigNumber const& bn)
|
||||
{
|
||||
BN_sub(_bn, _bn, bn._bn);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigNumber& BigNumber::operator*=(BigNumber const& bn)
|
||||
{
|
||||
BN_CTX *bnctx;
|
||||
|
||||
bnctx = BN_CTX_new();
|
||||
BN_mul(_bn, _bn, bn._bn, bnctx);
|
||||
BN_CTX_free(bnctx);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigNumber& BigNumber::operator/=(BigNumber const& bn)
|
||||
{
|
||||
BN_CTX *bnctx;
|
||||
|
||||
bnctx = BN_CTX_new();
|
||||
BN_div(_bn, nullptr, _bn, bn._bn, bnctx);
|
||||
BN_CTX_free(bnctx);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigNumber& BigNumber::operator%=(BigNumber const& bn)
|
||||
{
|
||||
BN_CTX *bnctx;
|
||||
|
||||
bnctx = BN_CTX_new();
|
||||
BN_mod(_bn, _bn, bn._bn, bnctx);
|
||||
BN_CTX_free(bnctx);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigNumber& BigNumber::operator<<=(int n)
|
||||
{
|
||||
BN_lshift(_bn, _bn, n);
|
||||
return *this;
|
||||
}
|
||||
|
||||
int BigNumber::CompareTo(BigNumber const& bn) const
|
||||
{
|
||||
return BN_cmp(_bn, bn._bn);
|
||||
}
|
||||
|
||||
BigNumber BigNumber::Exp(BigNumber const& bn) const
|
||||
{
|
||||
BigNumber ret;
|
||||
BN_CTX *bnctx;
|
||||
|
||||
bnctx = BN_CTX_new();
|
||||
BN_exp(ret._bn, _bn, bn._bn, bnctx);
|
||||
BN_CTX_free(bnctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigNumber BigNumber::ModExp(BigNumber const& bn1, BigNumber const& bn2) const
|
||||
{
|
||||
BigNumber ret;
|
||||
BN_CTX *bnctx;
|
||||
|
||||
bnctx = BN_CTX_new();
|
||||
BN_mod_exp(ret._bn, _bn, bn1._bn, bn2._bn, bnctx);
|
||||
BN_CTX_free(bnctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int32 BigNumber::GetNumBytes() const
|
||||
{
|
||||
return BN_num_bytes(_bn);
|
||||
}
|
||||
|
||||
uint32 BigNumber::AsDword() const
|
||||
{
|
||||
return (uint32)BN_get_word(_bn);
|
||||
}
|
||||
|
||||
bool BigNumber::IsZero() const
|
||||
{
|
||||
return BN_is_zero(_bn);
|
||||
}
|
||||
|
||||
bool BigNumber::IsNegative() const
|
||||
{
|
||||
return BN_is_negative(_bn);
|
||||
}
|
||||
|
||||
void BigNumber::GetBytes(uint8* buf, std::size_t bufsize, bool littleEndian) const
|
||||
{
|
||||
int res = littleEndian ? BN_bn2lebinpad(_bn, buf, bufsize) : BN_bn2binpad(_bn, buf, bufsize);
|
||||
ASSERT(res > 0, "Buffer of size {} is too small to hold bignum with {} bytes.\n", bufsize, BN_num_bytes(_bn));
|
||||
}
|
||||
|
||||
std::vector<uint8> BigNumber::ToByteVector(int32 minSize, bool littleEndian) const
|
||||
{
|
||||
std::size_t length = std::max(GetNumBytes(), minSize);
|
||||
std::vector<uint8> v;
|
||||
v.resize(length);
|
||||
GetBytes(v.data(), length, littleEndian);
|
||||
return v;
|
||||
}
|
||||
|
||||
std::string BigNumber::AsHexStr() const
|
||||
{
|
||||
char* ch = BN_bn2hex(_bn);
|
||||
std::string ret = ch;
|
||||
OPENSSL_free(ch);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string BigNumber::AsDecStr() const
|
||||
{
|
||||
char* ch = BN_bn2dec(_bn);
|
||||
std::string ret = ch;
|
||||
OPENSSL_free(ch);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _AUTH_BIGNUMBER_H
|
||||
#define _AUTH_BIGNUMBER_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct bignum_st;
|
||||
|
||||
class AC_COMMON_API BigNumber
|
||||
{
|
||||
public:
|
||||
BigNumber();
|
||||
BigNumber(BigNumber const& bn);
|
||||
BigNumber(uint32 v) : BigNumber() { SetDword(v); }
|
||||
BigNumber(int32 v) : BigNumber() { SetDword(v); }
|
||||
BigNumber(std::string const& v) : BigNumber() { SetHexStr(v); }
|
||||
|
||||
template <std::size_t Size>
|
||||
BigNumber(std::array<uint8, Size> const& v, bool littleEndian = true) : BigNumber() { SetBinary(v.data(), Size, littleEndian); }
|
||||
|
||||
~BigNumber();
|
||||
|
||||
void SetDword(int32);
|
||||
void SetDword(uint32);
|
||||
void SetQword(uint64);
|
||||
void SetBinary(uint8 const* bytes, int32 len, bool littleEndian = true);
|
||||
|
||||
template <typename Container>
|
||||
auto SetBinary(Container const& c, bool littleEndian = true) -> std::enable_if_t<!std::is_pointer_v<std::decay_t<Container>>> { SetBinary(std::data(c), std::size(c), littleEndian); }
|
||||
|
||||
bool SetHexStr(char const* str);
|
||||
bool SetHexStr(std::string const& str) { return SetHexStr(str.c_str()); }
|
||||
|
||||
void SetRand(int32 numbits);
|
||||
|
||||
BigNumber& operator=(BigNumber const& bn);
|
||||
|
||||
BigNumber& operator+=(BigNumber const& bn);
|
||||
BigNumber operator+(BigNumber const& bn) const
|
||||
{
|
||||
BigNumber t(*this);
|
||||
return t += bn;
|
||||
}
|
||||
|
||||
BigNumber& operator-=(BigNumber const& bn);
|
||||
BigNumber operator-(BigNumber const& bn) const
|
||||
{
|
||||
BigNumber t(*this);
|
||||
return t -= bn;
|
||||
}
|
||||
|
||||
BigNumber& operator*=(BigNumber const& bn);
|
||||
BigNumber operator*(BigNumber const& bn) const
|
||||
{
|
||||
BigNumber t(*this);
|
||||
return t *= bn;
|
||||
}
|
||||
|
||||
BigNumber& operator/=(BigNumber const& bn);
|
||||
BigNumber operator/(BigNumber const& bn) const
|
||||
{
|
||||
BigNumber t(*this);
|
||||
return t /= bn;
|
||||
}
|
||||
|
||||
BigNumber& operator%=(BigNumber const& bn);
|
||||
BigNumber operator%(BigNumber const& bn) const
|
||||
{
|
||||
BigNumber t(*this);
|
||||
return t %= bn;
|
||||
}
|
||||
|
||||
BigNumber& operator<<=(int n);
|
||||
BigNumber operator<<(int n) const
|
||||
{
|
||||
BigNumber t(*this);
|
||||
return t <<= n;
|
||||
}
|
||||
|
||||
[[nodiscard]] int CompareTo(BigNumber const& bn) const;
|
||||
bool operator<=(BigNumber const& bn) const { return (CompareTo(bn) <= 0); }
|
||||
bool operator==(BigNumber const& bn) const { return (CompareTo(bn) == 0); }
|
||||
bool operator>=(BigNumber const& bn) const { return (CompareTo(bn) >= 0); }
|
||||
bool operator<(BigNumber const& bn) const { return (CompareTo(bn) < 0); }
|
||||
bool operator>(BigNumber const& bn) const { return (CompareTo(bn) > 0); }
|
||||
|
||||
[[nodiscard]] bool IsZero() const;
|
||||
[[nodiscard]] bool IsNegative() const;
|
||||
|
||||
[[nodiscard]] BigNumber ModExp(BigNumber const& bn1, BigNumber const& bn2) const;
|
||||
[[nodiscard]] BigNumber Exp(BigNumber const&) const;
|
||||
|
||||
[[nodiscard]] int32 GetNumBytes() const;
|
||||
|
||||
struct bignum_st* BN() { return _bn; }
|
||||
[[nodiscard]] struct bignum_st const* BN() const { return _bn; }
|
||||
|
||||
[[nodiscard]] uint32 AsDword() const;
|
||||
|
||||
void GetBytes(uint8* buf, std::size_t bufsize, bool littleEndian = true) const;
|
||||
[[nodiscard]] std::vector<uint8> ToByteVector(int32 minSize = 0, bool littleEndian = true) const;
|
||||
|
||||
template <std::size_t Size>
|
||||
std::array<uint8, Size> ToByteArray(bool littleEndian = true) const
|
||||
{
|
||||
std::array<uint8, Size> buf;
|
||||
GetBytes(buf.data(), Size, littleEndian);
|
||||
return buf;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string AsHexStr() const;
|
||||
[[nodiscard]] std::string AsDecStr() const;
|
||||
|
||||
private:
|
||||
struct bignum_st* _bn;
|
||||
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 AZEROTHCORE_CRYPTO_CONSTANTS_H
|
||||
#define AZEROTHCORE_CRYPTO_CONSTANTS_H
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
struct Constants
|
||||
{
|
||||
static constexpr std::size_t MD5_DIGEST_LENGTH_BYTES = 16;
|
||||
static constexpr std::size_t SHA1_DIGEST_LENGTH_BYTES = 20;
|
||||
static constexpr std::size_t SHA256_DIGEST_LENGTH_BYTES = 32;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_CRYPTO_GENERICS_HPP
|
||||
#define AZEROTHCORE_CRYPTO_GENERICS_HPP
|
||||
|
||||
#include "BigNumber.h"
|
||||
#include "CryptoRandom.h"
|
||||
#include "Define.h"
|
||||
#include "Errors.h"
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
namespace Acore::Impl
|
||||
{
|
||||
struct CryptoGenericsImpl
|
||||
{
|
||||
template <typename Cipher>
|
||||
static typename Cipher::IV GenerateRandomIV()
|
||||
{
|
||||
typename Cipher::IV iv;
|
||||
Acore::Crypto::GetRandomBytes(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
static void AppendToBack(std::vector<uint8>& data, Container const& tail)
|
||||
{
|
||||
data.insert(data.end(), std::begin(tail), std::end(tail));
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
static void SplitFromBack(std::vector<uint8>& data, Container& tail)
|
||||
{
|
||||
ASSERT(data.size() >= std::size(tail));
|
||||
for (std::size_t i = 1, N = std::size(tail); i <= N; ++i)
|
||||
{
|
||||
tail[N - i] = data.back();
|
||||
data.pop_back();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
template <typename Cipher>
|
||||
void AEEncryptWithRandomIV(std::vector<uint8>& data, typename Cipher::Key const& key)
|
||||
{
|
||||
using IV = typename Cipher::IV;
|
||||
using Tag = typename Cipher::Tag;
|
||||
// select random IV
|
||||
IV iv = Acore::Impl::CryptoGenericsImpl::GenerateRandomIV<Cipher>();
|
||||
Tag tag;
|
||||
|
||||
// encrypt data
|
||||
Cipher cipher(true);
|
||||
cipher.Init(key);
|
||||
bool success = cipher.Process(iv, data.data(), data.size(), tag);
|
||||
ASSERT(success);
|
||||
|
||||
// append trailing IV and tag
|
||||
Acore::Impl::CryptoGenericsImpl::AppendToBack(data, iv);
|
||||
Acore::Impl::CryptoGenericsImpl::AppendToBack(data, tag);
|
||||
}
|
||||
|
||||
template <typename Cipher>
|
||||
void AEEncryptWithRandomIV(std::vector<uint8>& data, BigNumber const& key)
|
||||
{
|
||||
AEEncryptWithRandomIV<Cipher>(data, key.ToByteArray<Cipher::KEY_SIZE_BYTES>());
|
||||
}
|
||||
|
||||
template <typename Cipher>
|
||||
bool AEDecrypt(std::vector<uint8>& data, typename Cipher::Key const& key)
|
||||
{
|
||||
using IV = typename Cipher::IV;
|
||||
using Tag = typename Cipher::Tag;
|
||||
|
||||
// extract trailing IV and tag
|
||||
IV iv;
|
||||
Tag tag;
|
||||
Acore::Impl::CryptoGenericsImpl::SplitFromBack(data, tag);
|
||||
Acore::Impl::CryptoGenericsImpl::SplitFromBack(data, iv);
|
||||
|
||||
// decrypt data
|
||||
Cipher cipher(false);
|
||||
cipher.Init(key);
|
||||
return cipher.Process(iv, data.data(), data.size(), tag);
|
||||
}
|
||||
|
||||
template <typename Cipher>
|
||||
bool AEDecrypt(std::vector<uint8>& data, BigNumber const& key)
|
||||
{
|
||||
return AEDecrypt<Cipher>(data, key.ToByteArray<Cipher::KEY_SIZE_BYTES>());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_CRYPTOHASH_H
|
||||
#define AZEROTHCORE_CRYPTOHASH_H
|
||||
|
||||
#include "CryptoConstants.h"
|
||||
#include "Errors.h"
|
||||
#include <array>
|
||||
#include <openssl/evp.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
class BigNumber;
|
||||
|
||||
namespace Acore::Impl
|
||||
{
|
||||
struct GenericHashImpl
|
||||
{
|
||||
typedef EVP_MD const* (*HashCreator)();
|
||||
|
||||
static EVP_MD_CTX* MakeCTX() noexcept { return EVP_MD_CTX_new(); }
|
||||
static void DestroyCTX(EVP_MD_CTX* ctx) { EVP_MD_CTX_free(ctx); }
|
||||
};
|
||||
|
||||
template <GenericHashImpl::HashCreator HashCreator, std::size_t DigestLength>
|
||||
class GenericHash
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t DIGEST_LENGTH = DigestLength;
|
||||
using Digest = std::array<uint8, DIGEST_LENGTH>;
|
||||
|
||||
static Digest GetDigestOf(uint8 const* data, std::size_t len)
|
||||
{
|
||||
GenericHash hash;
|
||||
hash.UpdateData(data, len);
|
||||
hash.Finalize();
|
||||
return hash.GetDigest();
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
static auto GetDigestOf(Ts&&... pack) -> std::enable_if_t<!(std::is_integral_v<std::decay_t<Ts>> || ...), Digest>
|
||||
{
|
||||
GenericHash hash;
|
||||
(hash.UpdateData(std::forward<Ts>(pack)), ...);
|
||||
hash.Finalize();
|
||||
return hash.GetDigest();
|
||||
}
|
||||
|
||||
GenericHash() : _ctx(GenericHashImpl::MakeCTX())
|
||||
{
|
||||
int result = EVP_DigestInit_ex(_ctx, HashCreator(), nullptr);
|
||||
ASSERT(result == 1);
|
||||
}
|
||||
|
||||
GenericHash(GenericHash const& right) : _ctx(GenericHashImpl::MakeCTX())
|
||||
{
|
||||
*this = right;
|
||||
}
|
||||
|
||||
GenericHash(GenericHash&& right) noexcept
|
||||
{
|
||||
*this = std::move(right);
|
||||
}
|
||||
|
||||
~GenericHash()
|
||||
{
|
||||
if (!_ctx)
|
||||
return;
|
||||
GenericHashImpl::DestroyCTX(_ctx);
|
||||
_ctx = nullptr;
|
||||
}
|
||||
|
||||
GenericHash& operator=(GenericHash const& right)
|
||||
{
|
||||
if (this == &right)
|
||||
return *this;
|
||||
|
||||
int result = EVP_MD_CTX_copy_ex(_ctx, right._ctx);
|
||||
ASSERT(result == 1);
|
||||
_digest = right._digest;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GenericHash& operator=(GenericHash&& right) noexcept
|
||||
{
|
||||
if (this == &right)
|
||||
return *this;
|
||||
|
||||
_ctx = std::exchange(right._ctx, GenericHashImpl::MakeCTX());
|
||||
_digest = std::exchange(right._digest, Digest{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
void UpdateData(uint8 const* data, std::size_t len)
|
||||
{
|
||||
int result = EVP_DigestUpdate(_ctx, data, len);
|
||||
ASSERT(result == 1);
|
||||
}
|
||||
|
||||
void UpdateData(std::string_view str) { UpdateData(reinterpret_cast<uint8 const*>(str.data()), str.size()); }
|
||||
void UpdateData(std::string const& str) { UpdateData(std::string_view(str)); } /* explicit overload to avoid using the container template */
|
||||
void UpdateData(char const* str) { UpdateData(std::string_view(str)); } /* explicit overload to avoid using the container template */
|
||||
|
||||
template <typename Container>
|
||||
void UpdateData(Container const& c) { UpdateData(std::data(c), std::size(c)); }
|
||||
|
||||
void Finalize()
|
||||
{
|
||||
uint32 length;
|
||||
int result = EVP_DigestFinal_ex(_ctx, _digest.data(), &length);
|
||||
ASSERT(result == 1);
|
||||
ASSERT(length == DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
Digest const& GetDigest() const { return _digest; }
|
||||
|
||||
private:
|
||||
EVP_MD_CTX* _ctx{};
|
||||
Digest _digest{};
|
||||
};
|
||||
}
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
using MD5 = Acore::Impl::GenericHash<EVP_md5, Constants::MD5_DIGEST_LENGTH_BYTES>;
|
||||
using SHA1 = Acore::Impl::GenericHash<EVP_sha1, Constants::SHA1_DIGEST_LENGTH_BYTES>;
|
||||
using SHA256 = Acore::Impl::GenericHash<EVP_sha256, Constants::SHA256_DIGEST_LENGTH_BYTES>;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CryptoRandom.h"
|
||||
#include "Errors.h"
|
||||
#include <openssl/rand.h>
|
||||
|
||||
void Acore::Crypto::GetRandomBytes(uint8* buf, std::size_t len)
|
||||
{
|
||||
int result = RAND_bytes(buf, len);
|
||||
ASSERT(result == 1, "Not enough randomness in OpenSSL's entropy pool. What in the world are you running on?");
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_CRYPTORANDOM_H
|
||||
#define AZEROTHCORE_CRYPTORANDOM_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <array>
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
AC_COMMON_API void GetRandomBytes(uint8* buf, std::size_t len);
|
||||
|
||||
template <typename Container>
|
||||
void GetRandomBytes(Container& c)
|
||||
{
|
||||
GetRandomBytes(std::data(c), std::size(c));
|
||||
}
|
||||
|
||||
template <std::size_t S>
|
||||
std::array<uint8, S> GetRandomBytes()
|
||||
{
|
||||
std::array<uint8, S> arr;
|
||||
GetRandomBytes(arr);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_HMAC_H
|
||||
#define AZEROTHCORE_HMAC_H
|
||||
|
||||
#include "CryptoConstants.h"
|
||||
#include "CryptoHash.h"
|
||||
#include "Errors.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
class BigNumber;
|
||||
|
||||
namespace Acore::Impl
|
||||
{
|
||||
template <GenericHashImpl::HashCreator HashCreator, std::size_t DigestLength>
|
||||
class GenericHMAC
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t DIGEST_LENGTH = DigestLength;
|
||||
using Digest = std::array<uint8, DIGEST_LENGTH>;
|
||||
|
||||
template <typename Container>
|
||||
static Digest GetDigestOf(Container const& seed, uint8 const* data, std::size_t len)
|
||||
{
|
||||
GenericHMAC hash(seed);
|
||||
hash.UpdateData(data, len);
|
||||
hash.Finalize();
|
||||
return hash.GetDigest();
|
||||
}
|
||||
|
||||
template <typename Container, typename... Ts>
|
||||
static auto GetDigestOf(Container const& seed, Ts&&... pack) -> std::enable_if_t<!(std::is_integral_v<std::decay_t<Ts>> || ...), Digest>
|
||||
{
|
||||
GenericHMAC hash(seed);
|
||||
(hash.UpdateData(std::forward<Ts>(pack)), ...);
|
||||
hash.Finalize();
|
||||
return hash.GetDigest();
|
||||
}
|
||||
|
||||
GenericHMAC(uint8 const* seed, std::size_t len) : _ctx(GenericHashImpl::MakeCTX()), _key(EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, nullptr, seed, len))
|
||||
{
|
||||
int result = EVP_DigestSignInit(_ctx, nullptr, HashCreator(), nullptr, _key);
|
||||
ASSERT(result == 1);
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
GenericHMAC(Container const& container) : GenericHMAC(std::data(container), std::size(container)) {}
|
||||
|
||||
GenericHMAC(GenericHMAC const& right) : _ctx(GenericHashImpl::MakeCTX())
|
||||
{
|
||||
*this = right;
|
||||
}
|
||||
|
||||
GenericHMAC(GenericHMAC&& right) noexcept
|
||||
{
|
||||
*this = std::move(right);
|
||||
}
|
||||
|
||||
~GenericHMAC()
|
||||
{
|
||||
GenericHashImpl::DestroyCTX(_ctx);
|
||||
_ctx = nullptr;
|
||||
EVP_PKEY_free(_key);
|
||||
_key = nullptr;
|
||||
}
|
||||
|
||||
GenericHMAC& operator=(GenericHMAC const& right)
|
||||
{
|
||||
if (this == &right)
|
||||
return *this;
|
||||
|
||||
int result = EVP_MD_CTX_copy_ex(_ctx, right._ctx);
|
||||
ASSERT(result == 1);
|
||||
_key = right._key; // EVP_PKEY uses reference counting internally, just copy the pointer
|
||||
EVP_PKEY_up_ref(_key); // Bump reference count for PKEY, as every instance of this class holds two references to PKEY and destructor decrements it twice
|
||||
_digest = right._digest;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GenericHMAC& operator=(GenericHMAC&& right) noexcept
|
||||
{
|
||||
if (this == &right)
|
||||
return *this;
|
||||
|
||||
_ctx = std::exchange(right._ctx, GenericHashImpl::MakeCTX());
|
||||
_key = std::exchange(right._key, EVP_PKEY_new());
|
||||
_digest = std::exchange(right._digest, Digest{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
void UpdateData(uint8 const* data, std::size_t len)
|
||||
{
|
||||
int result = EVP_DigestSignUpdate(_ctx, data, len);
|
||||
ASSERT(result == 1);
|
||||
}
|
||||
|
||||
void UpdateData(std::string_view str) { UpdateData(reinterpret_cast<uint8 const*>(str.data()), str.size()); }
|
||||
void UpdateData(std::string const& str) { UpdateData(std::string_view(str)); } /* explicit overload to avoid using the container template */
|
||||
void UpdateData(char const* str) { UpdateData(std::string_view(str)); } /* explicit overload to avoid using the container template */
|
||||
|
||||
template <typename Container>
|
||||
void UpdateData(Container const& c) { UpdateData(std::data(c), std::size(c)); }
|
||||
|
||||
void Finalize()
|
||||
{
|
||||
std::size_t length = DIGEST_LENGTH;
|
||||
int result = EVP_DigestSignFinal(_ctx, _digest.data(), &length);
|
||||
ASSERT(result == 1);
|
||||
ASSERT(length == DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
Digest const& GetDigest() const { return _digest; }
|
||||
private:
|
||||
EVP_MD_CTX* _ctx{};
|
||||
EVP_PKEY* _key{};
|
||||
Digest _digest{};
|
||||
};
|
||||
}
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
using HMAC_SHA1 = Acore::Impl::GenericHMAC<EVP_sha1, Constants::SHA1_DIGEST_LENGTH_BYTES>;
|
||||
using HMAC_SHA256 = Acore::Impl::GenericHMAC<EVP_sha256, Constants::SHA256_DIGEST_LENGTH_BYTES>;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "OpenSSLCrypto.h"
|
||||
#include <openssl/crypto.h> // NOTE: this import is NEEDED (even though some IDEs report it as unused)
|
||||
#include <openssl/provider.h>
|
||||
|
||||
OSSL_PROVIDER* LegacyProvider;
|
||||
OSSL_PROVIDER* DefaultProvider;
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
#include <boost/dll/runtime_symbol_info.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
void SetupLibrariesForWindows()
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::path programLocation{ boost::dll::program_location().remove_filename().string() };
|
||||
fs::path libLegacy{ boost::dll::program_location().remove_filename().string() + "/legacy.dll" };
|
||||
|
||||
ASSERT(fs::exists(libLegacy), "Not found 'legacy.dll'. Please copy library 'legacy.dll' from OpenSSL default dir to '{}'", programLocation.generic_string());
|
||||
OSSL_PROVIDER_set_default_search_path(nullptr, programLocation.generic_string().c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
void OpenSSLCrypto::threadsSetup()
|
||||
{
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
SetupLibrariesForWindows();
|
||||
#endif
|
||||
LegacyProvider = OSSL_PROVIDER_load(nullptr, "legacy");
|
||||
DefaultProvider = OSSL_PROVIDER_load(nullptr, "default");
|
||||
}
|
||||
|
||||
void OpenSSLCrypto::threadsCleanup()
|
||||
{
|
||||
OSSL_PROVIDER_unload(LegacyProvider);
|
||||
OSSL_PROVIDER_unload(DefaultProvider);
|
||||
OSSL_PROVIDER_set_default_search_path(nullptr, nullptr);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _AC_OPENSSL_CRYPTO_H
|
||||
#define _AC_OPENSSL_CRYPTO_H
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
/**
|
||||
* A group of functions which setup openssl crypto module to work properly in multithreaded enviroment
|
||||
* If not setup properly - it will crash
|
||||
*/
|
||||
namespace OpenSSLCrypto
|
||||
{
|
||||
/// Needs to be called before threads using openssl are spawned
|
||||
AC_COMMON_API void threadsSetup();
|
||||
|
||||
/// Needs to be called after threads using openssl are despawned
|
||||
AC_COMMON_API void threadsCleanup();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AZEROTHCORE_SESSIONKEYGENERATOR_HPP
|
||||
#define AZEROTHCORE_SESSIONKEYGENERATOR_HPP
|
||||
|
||||
template <typename Hash>
|
||||
class SessionKeyGenerator
|
||||
{
|
||||
public:
|
||||
template <typename C>
|
||||
SessionKeyGenerator(C const& buf) :
|
||||
o0it(o0.begin())
|
||||
{
|
||||
uint8 const* data = std::data(buf);
|
||||
std::size_t const len = std::size(buf);
|
||||
std::size_t const halflen = (len / 2);
|
||||
|
||||
o1 = Hash::GetDigestOf(data, halflen);
|
||||
o2 = Hash::GetDigestOf(data + halflen, len - halflen);
|
||||
o0 = Hash::GetDigestOf(o1, o0, o2);
|
||||
}
|
||||
|
||||
void Generate(uint8* buf, uint32 sz)
|
||||
{
|
||||
for (uint32 i = 0; i < sz; ++i)
|
||||
{
|
||||
if (o0it == o0.end())
|
||||
{
|
||||
o0 = Hash::GetDigestOf(o1, o0, o2);
|
||||
o0it = o0.begin();
|
||||
}
|
||||
|
||||
buf[i] = *(o0it++);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
typename Hash::Digest o0{};
|
||||
typename Hash::Digest o1{};
|
||||
typename Hash::Digest o2{};
|
||||
typename Hash::Digest::const_iterator o0it;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TOTP.h"
|
||||
#include "Timer.h"
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
constexpr std::size_t Acore::Crypto::TOTP::RECOMMENDED_SECRET_LENGTH;
|
||||
static constexpr uint32 TOTP_INTERVAL = 30;
|
||||
static constexpr uint32 HMAC_RESULT_SIZE = 20;
|
||||
|
||||
/*static*/ uint32 Acore::Crypto::TOTP::GenerateToken(Secret const& secret, time_t timestamp)
|
||||
{
|
||||
timestamp /= TOTP_INTERVAL;
|
||||
unsigned char challenge[8];
|
||||
for (int i = 8; i--; timestamp >>= 8)
|
||||
challenge[i] = timestamp;
|
||||
|
||||
unsigned char digest[HMAC_RESULT_SIZE];
|
||||
uint32 digestSize = HMAC_RESULT_SIZE;
|
||||
HMAC(EVP_sha1(), secret.data(), secret.size(), challenge, 8, digest, &digestSize);
|
||||
|
||||
uint32 offset = digest[19] & 0xF;
|
||||
uint32 truncated = (digest[offset] << 24) | (digest[offset + 1] << 16) | (digest[offset + 2] << 8) | (digest[offset + 3]);
|
||||
truncated &= 0x7FFFFFFF;
|
||||
return (truncated % 1000000);
|
||||
}
|
||||
|
||||
/*static*/ bool Acore::Crypto::TOTP::ValidateToken(Secret const& secret, uint32 token)
|
||||
{
|
||||
time_t now = GetEpochTime().count();
|
||||
return (
|
||||
(token == GenerateToken(secret, now - TOTP_INTERVAL)) ||
|
||||
(token == GenerateToken(secret, now)) ||
|
||||
(token == GenerateToken(secret, now + TOTP_INTERVAL))
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_TOTP_H
|
||||
#define ACORE_TOTP_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <ctime>
|
||||
#include <vector>
|
||||
|
||||
namespace Acore::Crypto
|
||||
{
|
||||
struct AC_COMMON_API TOTP
|
||||
{
|
||||
static constexpr std::size_t RECOMMENDED_SECRET_LENGTH = 20;
|
||||
using Secret = std::vector<uint8>;
|
||||
|
||||
static uint32 GenerateToken(Secret const& key, time_t timestamp);
|
||||
static bool ValidateToken(Secret const& key, uint32 token);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DBCFileLoader.h"
|
||||
#include "Errors.h"
|
||||
#include <string.h>
|
||||
|
||||
DBCFileLoader::DBCFileLoader() : recordSize(0), recordCount(0), fieldCount(0), stringSize(0), fieldsOffset(nullptr), data(nullptr), stringTable(nullptr) { }
|
||||
|
||||
bool DBCFileLoader::Load(char const* filename, char const* fmt)
|
||||
{
|
||||
uint32 header;
|
||||
if (data)
|
||||
{
|
||||
delete [] data;
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (!f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fread(&header, 4, 1, f) != 1) // Number of records
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
EndianConvert(header);
|
||||
|
||||
if (header != 0x43424457) //'WDBC'
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fread(&recordCount, 4, 1, f) != 1) // Number of records
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
EndianConvert(recordCount);
|
||||
|
||||
if (fread(&fieldCount, 4, 1, f) != 1) // Number of fields
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
EndianConvert(fieldCount);
|
||||
|
||||
if (fread(&recordSize, 4, 1, f) != 1) // Size of a record
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
EndianConvert(recordSize);
|
||||
|
||||
if (fread(&stringSize, 4, 1, f) != 1) // String size
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
EndianConvert(stringSize);
|
||||
|
||||
fieldsOffset = new uint32[fieldCount];
|
||||
fieldsOffset[0] = 0;
|
||||
|
||||
for (uint32 i = 1; i < fieldCount; ++i)
|
||||
{
|
||||
fieldsOffset[i] = fieldsOffset[i - 1];
|
||||
if (fmt[i - 1] == 'b' || fmt[i - 1] == 'X') // byte fields
|
||||
{
|
||||
fieldsOffset[i] += sizeof(uint8);
|
||||
}
|
||||
else // 4 byte fields (int32/float/strings)
|
||||
{
|
||||
fieldsOffset[i] += sizeof(uint32);
|
||||
}
|
||||
}
|
||||
|
||||
data = new unsigned char[recordSize * recordCount + stringSize];
|
||||
stringTable = data + recordSize * recordCount;
|
||||
|
||||
if (fread(data, recordSize * recordCount + stringSize, 1, f) != 1)
|
||||
{
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DBCFileLoader::~DBCFileLoader()
|
||||
{
|
||||
delete[] data;
|
||||
|
||||
delete[] fieldsOffset;
|
||||
}
|
||||
|
||||
DBCFileLoader::Record DBCFileLoader::getRecord(std::size_t id)
|
||||
{
|
||||
ASSERT(data);
|
||||
return Record(*this, data + id * recordSize);
|
||||
}
|
||||
|
||||
uint32 DBCFileLoader::GetFormatRecordSize(char const* format, int32* index_pos)
|
||||
{
|
||||
uint32 recordsize = 0;
|
||||
int32 i = -1;
|
||||
|
||||
for (uint32 x = 0; format[x]; ++x)
|
||||
{
|
||||
switch (format[x])
|
||||
{
|
||||
case FT_FLOAT:
|
||||
recordsize += sizeof(float);
|
||||
break;
|
||||
case FT_INT:
|
||||
recordsize += sizeof(uint32);
|
||||
break;
|
||||
case FT_STRING:
|
||||
recordsize += sizeof(char*);
|
||||
break;
|
||||
case FT_SORT:
|
||||
i = x;
|
||||
break;
|
||||
case FT_IND:
|
||||
i = x;
|
||||
recordsize += sizeof(uint32);
|
||||
break;
|
||||
case FT_BYTE:
|
||||
recordsize += sizeof(uint8);
|
||||
break;
|
||||
case FT_NA:
|
||||
case FT_NA_BYTE:
|
||||
break;
|
||||
case FT_LOGIC:
|
||||
ASSERT(false && "Attempted to load DBC files that do not have field types that match what is in the core. Check DBCfmt.h or your DBC files.");
|
||||
break;
|
||||
default:
|
||||
ASSERT(false && "Unknown field format character in DBCfmt.h");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index_pos)
|
||||
{
|
||||
*index_pos = i;
|
||||
}
|
||||
|
||||
return recordsize;
|
||||
}
|
||||
|
||||
char* DBCFileLoader::AutoProduceData(char const* format, uint32& records, char**& indexTable)
|
||||
{
|
||||
/*
|
||||
format STRING, NA, FLOAT, NA, INT <=>
|
||||
struct{
|
||||
char* field0,
|
||||
float field1,
|
||||
int field2
|
||||
}entry;
|
||||
|
||||
this func will generate entry[rows] data;
|
||||
*/
|
||||
|
||||
typedef char* ptr;
|
||||
if (strlen(format) != fieldCount)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//get struct size and index pos
|
||||
int32 i;
|
||||
uint32 recordsize = GetFormatRecordSize(format, &i);
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
uint32 maxi = 0;
|
||||
//find max index
|
||||
for (uint32 y = 0; y < recordCount; ++y)
|
||||
{
|
||||
uint32 ind = getRecord(y).getUInt(i);
|
||||
if (ind > maxi)
|
||||
{
|
||||
maxi = ind;
|
||||
}
|
||||
}
|
||||
|
||||
++maxi;
|
||||
records = maxi;
|
||||
indexTable = new ptr[maxi];
|
||||
memset(indexTable, 0, maxi * sizeof(ptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
records = recordCount;
|
||||
indexTable = new ptr[recordCount];
|
||||
}
|
||||
|
||||
char* dataTable = new char[recordCount * recordsize];
|
||||
|
||||
uint32 offset = 0;
|
||||
|
||||
for (uint32 y = 0; y < recordCount; ++y)
|
||||
{
|
||||
if (i >= 0)
|
||||
{
|
||||
indexTable[getRecord(y).getUInt(i)] = &dataTable[offset];
|
||||
}
|
||||
else
|
||||
{
|
||||
indexTable[y] = &dataTable[offset];
|
||||
}
|
||||
|
||||
for (uint32 x = 0; x < fieldCount; ++x)
|
||||
{
|
||||
switch (format[x])
|
||||
{
|
||||
case FT_FLOAT:
|
||||
*((float*)(&dataTable[offset])) = getRecord(y).getFloat(x);
|
||||
offset += sizeof(float);
|
||||
break;
|
||||
case FT_IND:
|
||||
case FT_INT:
|
||||
*((uint32*)(&dataTable[offset])) = getRecord(y).getUInt(x);
|
||||
offset += sizeof(uint32);
|
||||
break;
|
||||
case FT_BYTE:
|
||||
*((uint8*)(&dataTable[offset])) = getRecord(y).getUInt8(x);
|
||||
offset += sizeof(uint8);
|
||||
break;
|
||||
case FT_STRING:
|
||||
*((char**)(&dataTable[offset])) = nullptr; // will replace non-empty or "" strings in AutoProduceStrings
|
||||
offset += sizeof(char*);
|
||||
break;
|
||||
case FT_LOGIC:
|
||||
ASSERT(false && "Attempted to load DBC files that do not have field types that match what is in the core. Check DBCfmt.h or your DBC files.");
|
||||
break;
|
||||
case FT_NA:
|
||||
case FT_NA_BYTE:
|
||||
case FT_SORT:
|
||||
break;
|
||||
default:
|
||||
ASSERT(false && "Unknown field format character in DBCfmt.h");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
char* DBCFileLoader::AutoProduceStrings(char const* format, char* dataTable)
|
||||
{
|
||||
if (strlen(format) != fieldCount)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char* stringPool = new char[stringSize];
|
||||
memcpy(stringPool, stringTable, stringSize);
|
||||
|
||||
uint32 offset = 0;
|
||||
|
||||
for (uint32 y = 0; y < recordCount; ++y)
|
||||
{
|
||||
for (uint32 x = 0; x < fieldCount; ++x)
|
||||
{
|
||||
switch (format[x])
|
||||
{
|
||||
case FT_FLOAT:
|
||||
offset += sizeof(float);
|
||||
break;
|
||||
case FT_IND:
|
||||
case FT_INT:
|
||||
offset += sizeof(uint32);
|
||||
break;
|
||||
case FT_BYTE:
|
||||
offset += sizeof(uint8);
|
||||
break;
|
||||
case FT_STRING:
|
||||
{
|
||||
// fill only not filled entries
|
||||
char** slot = (char**)(&dataTable[offset]);
|
||||
if (!*slot || !** slot)
|
||||
{
|
||||
const char* st = getRecord(y).getString(x);
|
||||
*slot = stringPool + (st - (char const*)stringTable);
|
||||
}
|
||||
offset += sizeof(char*);
|
||||
break;
|
||||
}
|
||||
case FT_LOGIC:
|
||||
ASSERT(false && "Attempted to load DBC files that does not have field types that match what is in the core. Check DBCfmt.h or your DBC files.");
|
||||
break;
|
||||
case FT_NA:
|
||||
case FT_NA_BYTE:
|
||||
case FT_SORT:
|
||||
break;
|
||||
default:
|
||||
ASSERT(false && "Unknown field format character in DBCfmt.h");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stringPool;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DBC_FILE_LOADER_H
|
||||
#define DBC_FILE_LOADER_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Errors.h"
|
||||
#include "Utilities/ByteConverter.h"
|
||||
|
||||
enum DbcFieldFormat
|
||||
{
|
||||
FT_NA = 'x', //not used or unknown, 4 byte size
|
||||
FT_NA_BYTE = 'X', //not used or unknown, byte
|
||||
FT_STRING = 's', //char*
|
||||
FT_FLOAT = 'f', //float
|
||||
FT_INT = 'i', //uint32
|
||||
FT_BYTE = 'b', //uint8
|
||||
FT_SORT = 'd', //sorted by this field, field is not included
|
||||
FT_IND = 'n', //the same, but parsed to data
|
||||
FT_LOGIC = 'l' //Logical (boolean)
|
||||
};
|
||||
|
||||
class DBCFileLoader
|
||||
{
|
||||
public:
|
||||
DBCFileLoader();
|
||||
~DBCFileLoader();
|
||||
|
||||
bool Load(const char* filename, const char* fmt);
|
||||
|
||||
class Record
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] float getFloat(std::size_t field) const
|
||||
{
|
||||
ASSERT(field < file.fieldCount);
|
||||
float val = *reinterpret_cast<float*>(offset + file.GetOffset(field));
|
||||
EndianConvert(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32 getUInt(std::size_t field) const
|
||||
{
|
||||
ASSERT(field < file.fieldCount);
|
||||
uint32 val = *reinterpret_cast<uint32*>(offset + file.GetOffset(field));
|
||||
EndianConvert(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint8 getUInt8(std::size_t field) const
|
||||
{
|
||||
ASSERT(field < file.fieldCount);
|
||||
return *reinterpret_cast<uint8*>(offset + file.GetOffset(field));
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* getString(std::size_t field) const
|
||||
{
|
||||
ASSERT(field < file.fieldCount);
|
||||
std::size_t stringOffset = getUInt(field);
|
||||
ASSERT(stringOffset < file.stringSize);
|
||||
return reinterpret_cast<char*>(file.stringTable + stringOffset);
|
||||
}
|
||||
|
||||
private:
|
||||
Record(DBCFileLoader& file_, unsigned char* offset_): offset(offset_), file(file_) { }
|
||||
unsigned char* offset;
|
||||
DBCFileLoader& file;
|
||||
|
||||
friend class DBCFileLoader;
|
||||
};
|
||||
|
||||
// Get record by id
|
||||
Record getRecord(std::size_t id);
|
||||
|
||||
[[nodiscard]] uint32 GetNumRows() const { return recordCount; }
|
||||
[[nodiscard]] uint32 GetRowSize() const { return recordSize; }
|
||||
[[nodiscard]] uint32 GetCols() const { return fieldCount; }
|
||||
[[nodiscard]] uint32 GetOffset(std::size_t id) const { return (fieldsOffset != nullptr && id < fieldCount) ? fieldsOffset[id] : 0; }
|
||||
[[nodiscard]] bool IsLoaded() const { return data != nullptr; }
|
||||
char* AutoProduceData(char const* fmt, uint32& count, char**& indexTable);
|
||||
char* AutoProduceStrings(char const* fmt, char* dataTable);
|
||||
static uint32 GetFormatRecordSize(const char* format, int32* index_pos = nullptr);
|
||||
|
||||
private:
|
||||
uint32 recordSize;
|
||||
uint32 recordCount;
|
||||
uint32 fieldCount;
|
||||
uint32 stringSize;
|
||||
uint32* fieldsOffset;
|
||||
unsigned char* data;
|
||||
unsigned char* stringTable;
|
||||
|
||||
DBCFileLoader(DBCFileLoader const& right) = delete;
|
||||
DBCFileLoader& operator=(DBCFileLoader const& right) = delete;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Errors.h"
|
||||
#include "Duration.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <thread>
|
||||
|
||||
/**
|
||||
@file Errors.cpp
|
||||
|
||||
@brief This file contains definitions of functions used for reporting critical application errors
|
||||
|
||||
It is very important that (std::)abort is NEVER called in place of *((volatile int*)nullptr) = 0;
|
||||
Calling abort() on Windows does not invoke unhandled exception filters - a mechanism used by WheatyExceptionReport
|
||||
to log crashes. exit(1) calls here are for static analysis tools to indicate that calling functions defined in this file
|
||||
terminates the application.
|
||||
*/
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
#include <Windows.h>
|
||||
#define Crash(message) \
|
||||
ULONG_PTR execeptionArgs[] = { reinterpret_cast<ULONG_PTR>(strdup(message)), reinterpret_cast<ULONG_PTR>(_ReturnAddress()) }; \
|
||||
RaiseException(EXCEPTION_ASSERTION_FAILURE, 0, 2, execeptionArgs);
|
||||
#else
|
||||
// should be easily accessible in gdb
|
||||
extern "C" { char const* AcoreAssertionFailedMessage = nullptr; }
|
||||
#define Crash(message) \
|
||||
AcoreAssertionFailedMessage = strdup(message); \
|
||||
*((volatile int*)nullptr) = 0; \
|
||||
exit(1);
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* @name MakeMessage
|
||||
* @brief Make message for display erros
|
||||
* @param messageType Message type (ASSERTION FAILED, FATAL ERROR, ERROR) end etc
|
||||
* @param file Path to file
|
||||
* @param line Line number in file
|
||||
* @param function Functionn name
|
||||
* @param message Condition to string format
|
||||
* @param fmtMessage [optional] Display format message after condition
|
||||
* @param debugInfo [optional] Display debug info
|
||||
*/
|
||||
inline std::string MakeMessage(std::string_view messageType, std::string_view file, uint32 line, std::string_view function,
|
||||
std::string_view message, std::string_view fmtMessage = {}, std::string_view debugInfo = {})
|
||||
{
|
||||
std::string msg = Acore::StringFormat("\n>> {}\n\n# Location: {}:{}\n# Function: {}\n# Condition: {}\n", messageType, file, line, function, message);
|
||||
|
||||
if (!fmtMessage.empty())
|
||||
{
|
||||
msg.append(Acore::StringFormat("# Message: {}\n", fmtMessage));
|
||||
}
|
||||
|
||||
if (!debugInfo.empty())
|
||||
{
|
||||
msg.append(Acore::StringFormat("\n# Debug info: {}\n", debugInfo));
|
||||
}
|
||||
|
||||
return Acore::StringFormat(
|
||||
"#{0:-^{2}}#\n"
|
||||
" {1: ^{2}} \n"
|
||||
"#{0:-^{2}}#\n", "", msg, 70);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name MakeAbortMessage
|
||||
* @brief Make message for display erros
|
||||
* @param file Path to file
|
||||
* @param line Line number in file
|
||||
* @param function Functionn name
|
||||
* @param fmtMessage [optional] Display format message after condition
|
||||
*/
|
||||
inline std::string MakeAbortMessage(std::string_view file, uint32 line, std::string_view function, std::string_view fmtMessage = {})
|
||||
{
|
||||
std::string msg = Acore::StringFormat("\n>> ABORTED\n\n# Location '{}:{}'\n# Function '{}'\n", file, line, function);
|
||||
|
||||
if (!fmtMessage.empty())
|
||||
{
|
||||
msg.append(Acore::StringFormat("# Message '{}'\n", fmtMessage));
|
||||
}
|
||||
|
||||
return Acore::StringFormat(
|
||||
"\n#{0:-^{2}}#\n"
|
||||
" {1: ^{2}} \n"
|
||||
"#{0:-^{2}}#\n", "", msg, 70);
|
||||
}
|
||||
}
|
||||
|
||||
void Acore::Assert(std::string_view file, uint32 line, std::string_view function, std::string_view debugInfo, std::string_view message, std::string_view fmtMessage /*= {}*/)
|
||||
{
|
||||
std::string formattedMessage = MakeMessage("ASSERTION FAILED", file, line, function, message, fmtMessage, debugInfo);
|
||||
fmt::print(stderr, "{}", formattedMessage);
|
||||
fflush(stderr);
|
||||
Crash(formattedMessage.c_str());
|
||||
}
|
||||
|
||||
void Acore::Fatal(std::string_view file, uint32 line, std::string_view function, std::string_view message, std::string_view fmtMessage /*= {}*/)
|
||||
{
|
||||
std::string formattedMessage = MakeMessage("FATAL ERROR", file, line, function, message, fmtMessage);
|
||||
fmt::print(stderr, "{}", formattedMessage);
|
||||
fflush(stderr);
|
||||
std::this_thread::sleep_for(10s);
|
||||
Crash(formattedMessage.c_str());
|
||||
}
|
||||
|
||||
void Acore::Error(std::string_view file, uint32 line, std::string_view function, std::string_view message)
|
||||
{
|
||||
std::string formattedMessage = MakeMessage("ERROR", file, line, function, message);
|
||||
fmt::print(stderr, "{}", formattedMessage);
|
||||
fflush(stderr);
|
||||
std::this_thread::sleep_for(10s);
|
||||
Crash(formattedMessage.c_str());
|
||||
}
|
||||
|
||||
void Acore::Warning(std::string_view file, uint32 line, std::string_view function, std::string_view message)
|
||||
{
|
||||
std::string formattedMessage = MakeMessage("WARNING", file, line, function, message);
|
||||
fmt::print(stderr, "{}", formattedMessage);
|
||||
}
|
||||
|
||||
void Acore::Abort(std::string_view file, uint32 line, std::string_view function, std::string_view fmtMessage /*= {}*/)
|
||||
{
|
||||
std::string formattedMessage = MakeAbortMessage(file, line, function, fmtMessage);
|
||||
fmt::print(stderr, "{}", formattedMessage);
|
||||
fflush(stderr);
|
||||
std::this_thread::sleep_for(10s);
|
||||
Crash(formattedMessage.c_str());
|
||||
}
|
||||
|
||||
void Acore::AbortHandler(int sigval)
|
||||
{
|
||||
// nothing useful to log here, no way to pass args
|
||||
std::string formattedMessage = StringFormat("Caught signal {}\n", sigval);
|
||||
fmt::print(stderr, "{}", formattedMessage);
|
||||
fflush(stderr);
|
||||
Crash(formattedMessage.c_str());
|
||||
}
|
||||
|
||||
std::string GetDebugInfo()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _ACORE_ERRORS_H_
|
||||
#define _ACORE_ERRORS_H_
|
||||
|
||||
#include "StringFormat.h"
|
||||
|
||||
namespace Acore
|
||||
{
|
||||
// Default function
|
||||
[[noreturn]] AC_COMMON_API void Assert(std::string_view file, uint32 line, std::string_view function, std::string_view debugInfo, std::string_view message, std::string_view fmtMessage = {});
|
||||
[[noreturn]] AC_COMMON_API void Fatal(std::string_view file, uint32 line, std::string_view function, std::string_view message, std::string_view fmtMessage = {});
|
||||
[[noreturn]] AC_COMMON_API void Error(std::string_view file, uint32 line, std::string_view function, std::string_view message);
|
||||
[[noreturn]] AC_COMMON_API void Abort(std::string_view file, uint32 line, std::string_view function, std::string_view fmtMessage = {});
|
||||
|
||||
template<typename... Args>
|
||||
AC_COMMON_API inline void Assert(std::string_view file, uint32 line, std::string_view function, std::string_view debugInfo, std::string_view message, std::string_view fmt, Args&&... args)
|
||||
{
|
||||
Assert(file, line, function, debugInfo, message, StringFormat(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
AC_COMMON_API inline void Fatal(std::string_view file, uint32 line, std::string_view function, std::string_view message, std::string_view fmt, Args&&... args)
|
||||
{
|
||||
Fatal(file, line, function, message, StringFormat(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
AC_COMMON_API inline void Abort(std::string_view file, uint32 line, std::string_view function, std::string_view fmt, Args&&... args)
|
||||
{
|
||||
Abort(file, line, function, StringFormat(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
AC_COMMON_API void Warning(std::string_view file, uint32 line, std::string_view function, std::string_view message);
|
||||
|
||||
[[noreturn]] AC_COMMON_API void AbortHandler(int sigval);
|
||||
|
||||
} // namespace Acore
|
||||
|
||||
AC_COMMON_API std::string GetDebugInfo();
|
||||
|
||||
#define WPAssert(cond, ...) do { if (!(cond)) Acore::Assert(__FILE__, __LINE__, __FUNCTION__, GetDebugInfo(), #cond, ##__VA_ARGS__); } while(0)
|
||||
#define WPAssert_NODEBUGINFO(cond) do { if (!(cond)) Acore::Assert(__FILE__, __LINE__, __FUNCTION__, "", #cond); } while(0)
|
||||
#define WPFatal(cond, ...) do { if (!(cond)) Acore::Fatal(__FILE__, __LINE__, __FUNCTION__, #cond, ##__VA_ARGS__); } while(0)
|
||||
#define WPError(cond, msg) do { if (!(cond)) Acore::Error(__FILE__, __LINE__, __FUNCTION__, (msg)); } while(0)
|
||||
#define WPWarning(cond, msg) do { if (!(cond)) Acore::Warning(__FILE__, __LINE__, __FUNCTION__, (msg)); } while(0)
|
||||
#define WPAbort(...) do { Acore::Abort(__FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); } while(0)
|
||||
|
||||
#ifdef PERFORMANCE_PROFILING
|
||||
#define ASSERT(cond, ...) ((void)0)
|
||||
#define ASSERT_NODEBUGINFO(cond, ...) ((void)0)
|
||||
#else
|
||||
#define ASSERT WPAssert
|
||||
#define ASSERT_NODEBUGINFO WPAssert_NODEBUGINFO
|
||||
#endif
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
#define EXCEPTION_ASSERTION_FAILURE 0xC0000420L
|
||||
#endif
|
||||
|
||||
#define ABORT WPAbort
|
||||
|
||||
template <typename T>
|
||||
inline T* ASSERT_NOTNULL_IMPL(T* pointer, std::string_view expr)
|
||||
{
|
||||
ASSERT(pointer, "{}", expr);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
#define ASSERT_NOTNULL(pointer) ASSERT_NOTNULL_IMPL(pointer, #pointer)
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,202 @@
|
||||
#ifndef _WHEATYEXCEPTIONREPORT_
|
||||
#define _WHEATYEXCEPTIONREPORT_
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS && !defined(__MINGW32__)
|
||||
|
||||
#include <dbghelp.h>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <stdlib.h>
|
||||
#include <winnt.h>
|
||||
#include <winternl.h>
|
||||
#define countof _countof
|
||||
|
||||
#define WER_MAX_ARRAY_ELEMENTS_COUNT 10
|
||||
#define WER_MAX_NESTING_LEVEL 4
|
||||
#define WER_SMALL_BUFFER_SIZE 1024
|
||||
#define WER_LARGE_BUFFER_SIZE WER_SMALL_BUFFER_SIZE * 16
|
||||
|
||||
enum BasicType // Stolen from CVCONST.H in the DIA 2.0 SDK
|
||||
{
|
||||
btNoType = 0,
|
||||
btVoid = 1,
|
||||
btChar = 2,
|
||||
btWChar = 3,
|
||||
btInt = 6,
|
||||
btUInt = 7,
|
||||
btFloat = 8,
|
||||
btBCD = 9,
|
||||
btBool = 10,
|
||||
btLong = 13,
|
||||
btULong = 14,
|
||||
btCurrency = 25,
|
||||
btDate = 26,
|
||||
btVariant = 27,
|
||||
btComplex = 28,
|
||||
btBit = 29,
|
||||
btBSTR = 30,
|
||||
btHresult = 31,
|
||||
|
||||
// Custom types
|
||||
btStdString = 101
|
||||
};
|
||||
|
||||
enum DataKind // Stolen from CVCONST.H in the DIA 2.0 SDK
|
||||
{
|
||||
DataIsUnknown,
|
||||
DataIsLocal,
|
||||
DataIsStaticLocal,
|
||||
DataIsParam,
|
||||
DataIsObjectPtr,
|
||||
DataIsFileStatic,
|
||||
DataIsGlobal,
|
||||
DataIsMember,
|
||||
DataIsStaticMember,
|
||||
DataIsConstant
|
||||
};
|
||||
|
||||
char const* const rgBaseType[] =
|
||||
{
|
||||
"<user defined>", // btNoType = 0,
|
||||
"void", // btVoid = 1,
|
||||
"char",//char* // btChar = 2,
|
||||
"wchar_t*", // btWChar = 3,
|
||||
"signed char",
|
||||
"unsigned char",
|
||||
"int", // btInt = 6,
|
||||
"unsigned int", // btUInt = 7,
|
||||
"float", // btFloat = 8,
|
||||
"<BCD>", // btBCD = 9,
|
||||
"bool", // btBool = 10,
|
||||
"short",
|
||||
"unsigned short",
|
||||
"long", // btLong = 13,
|
||||
"unsigned long", // btULong = 14,
|
||||
"int8",
|
||||
"int16",
|
||||
"int32",
|
||||
"int64",
|
||||
"int128",
|
||||
"uint8",
|
||||
"uint16",
|
||||
"uint32",
|
||||
"uint64",
|
||||
"uint128",
|
||||
"<currency>", // btCurrency = 25,
|
||||
"<date>", // btDate = 26,
|
||||
"VARIANT", // btVariant = 27,
|
||||
"<complex>", // btComplex = 28,
|
||||
"<bit>", // btBit = 29,
|
||||
"BSTR", // btBSTR = 30,
|
||||
"HRESULT" // btHresult = 31
|
||||
};
|
||||
|
||||
struct SymbolPair
|
||||
{
|
||||
SymbolPair(DWORD type, DWORD_PTR offset)
|
||||
{
|
||||
_type = type;
|
||||
_offset = offset;
|
||||
}
|
||||
|
||||
bool operator<(SymbolPair const& other) const
|
||||
{
|
||||
return _offset < other._offset ||
|
||||
(_offset == other._offset && _type < other._type);
|
||||
}
|
||||
|
||||
DWORD _type;
|
||||
DWORD_PTR _offset;
|
||||
};
|
||||
typedef std::set<SymbolPair> SymbolPairs;
|
||||
|
||||
struct SymbolDetail
|
||||
{
|
||||
SymbolDetail() : Prefix(), Type(), Suffix(), Name(), Value(), Logged(false), HasChildren(false) {}
|
||||
|
||||
std::string ToString();
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return Value.empty() && !HasChildren;
|
||||
}
|
||||
|
||||
std::string Prefix;
|
||||
std::string Type;
|
||||
std::string Suffix;
|
||||
std::string Name;
|
||||
std::string Value;
|
||||
bool Logged;
|
||||
bool HasChildren;
|
||||
};
|
||||
|
||||
class WheatyExceptionReport
|
||||
{
|
||||
public:
|
||||
WheatyExceptionReport();
|
||||
~WheatyExceptionReport();
|
||||
|
||||
// entry point where control comes on an unhandled exception
|
||||
static LONG WINAPI WheatyUnhandledExceptionFilter(
|
||||
PEXCEPTION_POINTERS pExceptionInfo);
|
||||
|
||||
static void __cdecl WheatyCrtHandler(wchar_t const* expression, wchar_t const* function, wchar_t const* file, unsigned int line, uintptr_t pReserved);
|
||||
|
||||
static void printTracesForAllThreads(bool);
|
||||
private:
|
||||
// where report info is extracted and generated
|
||||
static void GenerateExceptionReport(PEXCEPTION_POINTERS pExceptionInfo);
|
||||
static void PrintSystemInfo();
|
||||
static BOOL _GetWindowsVersion(TCHAR* szVersion, DWORD cntMax);
|
||||
static BOOL _GetProcessorName(TCHAR* sProcessorName, DWORD maxcount);
|
||||
|
||||
// Helper functions
|
||||
static LPTSTR GetExceptionString(DWORD dwCode);
|
||||
static BOOL GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len,
|
||||
DWORD& section, DWORD_PTR& offset);
|
||||
|
||||
static void WriteStackDetails(PCONTEXT pContext, bool bWriteVariables, HANDLE pThreadHandle);
|
||||
|
||||
static BOOL CALLBACK EnumerateSymbolsCallback(PSYMBOL_INFO, ULONG, PVOID);
|
||||
|
||||
static bool FormatSymbolValue(PSYMBOL_INFO, STACKFRAME64*);
|
||||
|
||||
static void DumpTypeIndex(DWORD64, DWORD, DWORD_PTR, bool&, char const*, char*, bool, bool);
|
||||
|
||||
static void FormatOutputValue(char* pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress, std::size_t bufferSize, std::size_t countOverride = 0);
|
||||
|
||||
static BasicType GetBasicType(DWORD typeIndex, DWORD64 modBase);
|
||||
static DWORD_PTR DereferenceUnsafePointer(DWORD_PTR address);
|
||||
|
||||
static int __cdecl Log(const TCHAR* format, ...);
|
||||
static int __cdecl StackLog(const TCHAR* format, va_list argptr);
|
||||
static int __cdecl HeapLog(const TCHAR* format, va_list argptr);
|
||||
|
||||
static bool StoreSymbol(DWORD type, DWORD_PTR offset);
|
||||
static void ClearSymbols();
|
||||
|
||||
// Variables used by the class
|
||||
static TCHAR m_szLogFileName[MAX_PATH];
|
||||
static TCHAR m_szDumpFileName[MAX_PATH];
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter;
|
||||
static _invalid_parameter_handler m_previousCrtHandler;
|
||||
static HANDLE m_hReportFile;
|
||||
static HANDLE m_hDumpFile;
|
||||
static HANDLE m_hProcess;
|
||||
static SymbolPairs symbols;
|
||||
static std::stack<SymbolDetail> symbolDetails;
|
||||
static bool stackOverflowException;
|
||||
static bool alreadyCrashed;
|
||||
static std::mutex alreadyCrashedLock;
|
||||
typedef NTSTATUS(NTAPI* pRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation);
|
||||
static pRtlGetVersion RtlGetVersion;
|
||||
|
||||
static void PushSymbolDetail();
|
||||
static void PopSymbolDetail();
|
||||
static void PrintSymbolDetail();
|
||||
};
|
||||
|
||||
extern WheatyExceptionReport g_WheatyExceptionReport; // global instance of class
|
||||
#endif // _WIN32
|
||||
#endif // _WHEATYEXCEPTIONREPORT_
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_DEFINE_H
|
||||
#define ACORE_DEFINE_H
|
||||
|
||||
#include "CompilerDefs.h"
|
||||
#include <cinttypes>
|
||||
#include <climits>
|
||||
|
||||
#define ACORE_LITTLEENDIAN 0
|
||||
#define ACORE_BIGENDIAN 1
|
||||
|
||||
#if !defined(ACORE_ENDIAN)
|
||||
# if defined (BOOST_BIG_ENDIAN)
|
||||
# define ACORE_ENDIAN ACORE_BIGENDIAN
|
||||
# else
|
||||
# define ACORE_ENDIAN ACORE_LITTLEENDIAN
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
# define ACORE_PATH_MAX MAX_PATH
|
||||
# define _USE_MATH_DEFINES
|
||||
#else //AC_PLATFORM != AC_PLATFORM_WINDOWS
|
||||
# define ACORE_PATH_MAX PATH_MAX
|
||||
#endif //AC_PLATFORM
|
||||
|
||||
#if !defined(COREDEBUG)
|
||||
# define ACORE_INLINE inline
|
||||
#else //COREDEBUG
|
||||
# if !defined(ACORE_DEBUG)
|
||||
# define ACORE_DEBUG
|
||||
# endif //ACORE_DEBUG
|
||||
# define ACORE_INLINE
|
||||
#endif //!COREDEBUG
|
||||
|
||||
#if AC_COMPILER == AC_COMPILER_GNU
|
||||
# define ATTR_PRINTF(F, V) __attribute__ ((format (printf, F, V)))
|
||||
#else //AC_COMPILER != AC_COMPILER_GNU
|
||||
# define ATTR_PRINTF(F, V)
|
||||
#endif //AC_COMPILER == AC_COMPILER_GNU
|
||||
|
||||
#ifdef ACORE_API_USE_DYNAMIC_LINKING
|
||||
# if AC_COMPILER == AC_COMPILER_MICROSOFT
|
||||
# define AC_API_EXPORT __declspec(dllexport)
|
||||
# define AC_API_IMPORT __declspec(dllimport)
|
||||
# elif AC_COMPILER == AC_COMPILER_GNU
|
||||
# define AC_API_EXPORT __attribute__((visibility("default")))
|
||||
# define AC_API_IMPORT
|
||||
# else
|
||||
# error compiler not supported!
|
||||
# endif
|
||||
#else
|
||||
# define AC_API_EXPORT
|
||||
# define AC_API_IMPORT
|
||||
#endif
|
||||
|
||||
#ifdef ACORE_API_EXPORT_COMMON
|
||||
# define AC_COMMON_API AC_API_EXPORT
|
||||
#else
|
||||
# define AC_COMMON_API AC_API_IMPORT
|
||||
#endif
|
||||
|
||||
#ifdef ACORE_API_EXPORT_DATABASE
|
||||
# define AC_DATABASE_API AC_API_EXPORT
|
||||
#else
|
||||
# define AC_DATABASE_API AC_API_IMPORT
|
||||
#endif
|
||||
|
||||
#ifdef ACORE_API_EXPORT_SHARED
|
||||
# define AC_SHARED_API AC_API_EXPORT
|
||||
#else
|
||||
# define AC_SHARED_API AC_API_IMPORT
|
||||
#endif
|
||||
|
||||
#ifdef ACORE_API_EXPORT_GAME
|
||||
# define AC_GAME_API AC_API_EXPORT
|
||||
#else
|
||||
# define AC_GAME_API AC_API_IMPORT
|
||||
#endif
|
||||
|
||||
#define UI64LIT(N) UINT64_C(N)
|
||||
#define SI64LIT(N) INT64_C(N)
|
||||
|
||||
#define STRING_VIEW_FMT_ARG(str) static_cast<int>((str).length()), (str).data()
|
||||
|
||||
typedef std::int64_t int64;
|
||||
typedef std::int32_t int32;
|
||||
typedef std::int16_t int16;
|
||||
typedef std::int8_t int8;
|
||||
typedef std::uint64_t uint64;
|
||||
typedef std::uint32_t uint32;
|
||||
typedef std::uint16_t uint16;
|
||||
typedef std::uint8_t uint8;
|
||||
|
||||
#endif //ACORE_DEFINE_H
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_FACTORY_HOLDER
|
||||
#define ACORE_FACTORY_HOLDER
|
||||
|
||||
#include "Define.h"
|
||||
#include "ObjectRegistry.h"
|
||||
|
||||
/** FactoryHolder holds a factory object of a specific type
|
||||
*/
|
||||
template<class T, class O, class Key = std::string>
|
||||
class FactoryHolder
|
||||
{
|
||||
public:
|
||||
typedef ObjectRegistry<FactoryHolder<T, O, Key>, Key> FactoryHolderRegistry;
|
||||
|
||||
explicit FactoryHolder(Key const& k) : _key(k) { }
|
||||
virtual ~FactoryHolder() { }
|
||||
|
||||
void RegisterSelf() { FactoryHolderRegistry::instance()->InsertItem(this, _key); }
|
||||
|
||||
/// Abstract Factory create method
|
||||
virtual T* Create(O* object = nullptr) const = 0;
|
||||
private:
|
||||
Key const _key;
|
||||
};
|
||||
|
||||
/** Permissible is a classic way of letting the object decide
|
||||
* whether how good they handle things. This is not retricted
|
||||
* to factory selectors.
|
||||
*/
|
||||
template<class T>
|
||||
class Permissible
|
||||
{
|
||||
public:
|
||||
virtual ~Permissible() { }
|
||||
virtual int32 Permit(T const*) const = 0;
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _LINKEDLIST
|
||||
#define _LINKEDLIST
|
||||
|
||||
#include "Define.h"
|
||||
#include <iterator>
|
||||
|
||||
//============================================
|
||||
class LinkedListHead;
|
||||
|
||||
class LinkedListElement
|
||||
{
|
||||
private:
|
||||
friend class LinkedListHead;
|
||||
|
||||
LinkedListElement* iNext{nullptr};
|
||||
LinkedListElement* iPrev{nullptr};
|
||||
public:
|
||||
LinkedListElement() = default;
|
||||
~LinkedListElement() { delink(); }
|
||||
|
||||
[[nodiscard]] bool hasNext() const { return (iNext && iNext->iNext != nullptr); }
|
||||
[[nodiscard]] bool hasPrev() const { return (iPrev && iPrev->iPrev != nullptr); }
|
||||
[[nodiscard]] bool isInList() const { return (iNext != nullptr && iPrev != nullptr); }
|
||||
|
||||
LinkedListElement* next() { return hasNext() ? iNext : nullptr; }
|
||||
[[nodiscard]] LinkedListElement const* next() const { return hasNext() ? iNext : nullptr; }
|
||||
LinkedListElement* prev() { return hasPrev() ? iPrev : nullptr; }
|
||||
[[nodiscard]] LinkedListElement const* prev() const { return hasPrev() ? iPrev : nullptr; }
|
||||
|
||||
LinkedListElement* nocheck_next() { return iNext; }
|
||||
[[nodiscard]] LinkedListElement const* nocheck_next() const { return iNext; }
|
||||
LinkedListElement* nocheck_prev() { return iPrev; }
|
||||
[[nodiscard]] LinkedListElement const* nocheck_prev() const { return iPrev; }
|
||||
|
||||
void delink()
|
||||
{
|
||||
if (isInList())
|
||||
{
|
||||
iNext->iPrev = iPrev;
|
||||
iPrev->iNext = iNext;
|
||||
iNext = nullptr;
|
||||
iPrev = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void insertBefore(LinkedListElement* pElem)
|
||||
{
|
||||
pElem->iNext = this;
|
||||
pElem->iPrev = iPrev;
|
||||
iPrev->iNext = pElem;
|
||||
iPrev = pElem;
|
||||
}
|
||||
|
||||
void insertAfter(LinkedListElement* pElem)
|
||||
{
|
||||
pElem->iPrev = this;
|
||||
pElem->iNext = iNext;
|
||||
iNext->iPrev = pElem;
|
||||
iNext = pElem;
|
||||
}
|
||||
};
|
||||
|
||||
//============================================
|
||||
|
||||
class LinkedListHead
|
||||
{
|
||||
private:
|
||||
LinkedListElement iFirst;
|
||||
LinkedListElement iLast;
|
||||
uint32 iSize{0};
|
||||
public:
|
||||
LinkedListHead()
|
||||
{
|
||||
// create empty list
|
||||
|
||||
iFirst.iNext = &iLast;
|
||||
iLast.iPrev = &iFirst;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsEmpty() const { return (!iFirst.iNext->isInList()); }
|
||||
|
||||
LinkedListElement* getFirst() { return (IsEmpty() ? nullptr : iFirst.iNext); }
|
||||
[[nodiscard]] LinkedListElement const* getFirst() const { return (IsEmpty() ? nullptr : iFirst.iNext); }
|
||||
|
||||
LinkedListElement* getLast() { return (IsEmpty() ? nullptr : iLast.iPrev); }
|
||||
[[nodiscard]] LinkedListElement const* getLast() const { return (IsEmpty() ? nullptr : iLast.iPrev); }
|
||||
|
||||
void insertFirst(LinkedListElement* pElem)
|
||||
{
|
||||
iFirst.insertAfter(pElem);
|
||||
}
|
||||
|
||||
void insertLast(LinkedListElement* pElem)
|
||||
{
|
||||
iLast.insertBefore(pElem);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32 getSize() const
|
||||
{
|
||||
if (!iSize)
|
||||
{
|
||||
uint32 result = 0;
|
||||
LinkedListElement const* e = getFirst();
|
||||
while (e)
|
||||
{
|
||||
++result;
|
||||
e = e->next();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return iSize;
|
||||
}
|
||||
}
|
||||
|
||||
void incSize() { ++iSize; }
|
||||
void decSize() { --iSize; }
|
||||
|
||||
template<class _Ty>
|
||||
class Iterator
|
||||
{
|
||||
public:
|
||||
typedef std::bidirectional_iterator_tag iterator_category;
|
||||
typedef _Ty value_type;
|
||||
typedef ptrdiff_t difference_type;
|
||||
typedef ptrdiff_t distance_type;
|
||||
typedef _Ty* pointer;
|
||||
typedef _Ty const* const_pointer;
|
||||
typedef _Ty& reference;
|
||||
typedef _Ty const& const_reference;
|
||||
|
||||
Iterator() : _Ptr(0)
|
||||
{
|
||||
// construct with null node pointer
|
||||
}
|
||||
|
||||
Iterator(pointer _Pnode) : _Ptr(_Pnode)
|
||||
{
|
||||
// construct with node pointer _Pnode
|
||||
}
|
||||
|
||||
Iterator& operator=(Iterator const& _Right)
|
||||
{
|
||||
_Ptr = _Right._Ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator& operator=(const_pointer const& _Right)
|
||||
{
|
||||
_Ptr = pointer(_Right);
|
||||
return *this;
|
||||
}
|
||||
|
||||
reference operator*()
|
||||
{
|
||||
// return designated value
|
||||
return *_Ptr;
|
||||
}
|
||||
|
||||
pointer operator->()
|
||||
{
|
||||
// return pointer to class object
|
||||
return _Ptr;
|
||||
}
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
// preincrement
|
||||
_Ptr = _Ptr->next();
|
||||
return (*this);
|
||||
}
|
||||
|
||||
Iterator operator++(int)
|
||||
{
|
||||
// postincrement
|
||||
iterator _Tmp = *this;
|
||||
++*this;
|
||||
return (_Tmp);
|
||||
}
|
||||
|
||||
Iterator& operator--()
|
||||
{
|
||||
// predecrement
|
||||
_Ptr = _Ptr->prev();
|
||||
return (*this);
|
||||
}
|
||||
|
||||
Iterator operator--(int)
|
||||
{
|
||||
// postdecrement
|
||||
iterator _Tmp = *this;
|
||||
--*this;
|
||||
return (_Tmp);
|
||||
}
|
||||
|
||||
bool operator==(Iterator const& _Right) const
|
||||
{
|
||||
// test for iterator equality
|
||||
return (_Ptr == _Right._Ptr);
|
||||
}
|
||||
|
||||
bool operator!=(Iterator const& _Right) const
|
||||
{
|
||||
// test for iterator inequality
|
||||
return (!(*this == _Right));
|
||||
}
|
||||
|
||||
bool operator==(pointer const& _Right) const
|
||||
{
|
||||
// test for pointer equality
|
||||
return (_Ptr != _Right);
|
||||
}
|
||||
|
||||
bool operator!=(pointer const& _Right) const
|
||||
{
|
||||
// test for pointer equality
|
||||
return (!(*this == _Right));
|
||||
}
|
||||
|
||||
bool operator==(const_reference _Right) const
|
||||
{
|
||||
// test for reference equality
|
||||
return (_Ptr == &_Right);
|
||||
}
|
||||
|
||||
bool operator!=(const_reference _Right) const
|
||||
{
|
||||
// test for reference equality
|
||||
return (_Ptr != &_Right);
|
||||
}
|
||||
|
||||
pointer _Mynode()
|
||||
{
|
||||
// return node pointer
|
||||
return (_Ptr);
|
||||
}
|
||||
|
||||
protected:
|
||||
pointer _Ptr; // pointer to node
|
||||
};
|
||||
|
||||
typedef Iterator<LinkedListElement> iterator;
|
||||
};
|
||||
|
||||
//============================================
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _REFMANAGER_H
|
||||
#define _REFMANAGER_H
|
||||
//=====================================================
|
||||
|
||||
#include "Dynamic/LinkedList.h"
|
||||
#include "Dynamic/LinkedReference/Reference.h"
|
||||
|
||||
template <class TO, class FROM> class RefMgr : public LinkedListHead
|
||||
{
|
||||
public:
|
||||
typedef LinkedListHead::Iterator< Reference<TO, FROM>> iterator;
|
||||
RefMgr() = default;
|
||||
virtual ~RefMgr() { clearReferences(); }
|
||||
|
||||
Reference<TO, FROM>* getFirst() { return ((Reference<TO, FROM>*) LinkedListHead::getFirst()); }
|
||||
[[nodiscard]] Reference<TO, FROM> const* getFirst() const { return ((Reference<TO, FROM> const*) LinkedListHead::getFirst()); }
|
||||
Reference<TO, FROM>* getLast() { return ((Reference<TO, FROM>*) LinkedListHead::getLast()); }
|
||||
[[nodiscard]] Reference<TO, FROM> const* getLast() const { return ((Reference<TO, FROM> const*) LinkedListHead::getLast()); }
|
||||
|
||||
iterator begin() { return iterator(getFirst()); }
|
||||
iterator end() { return iterator(nullptr); }
|
||||
iterator rbegin() { return iterator(getLast()); }
|
||||
iterator rend() { return iterator(nullptr); }
|
||||
|
||||
void clearReferences()
|
||||
{
|
||||
LinkedListElement* ref;
|
||||
while ((ref = getFirst()) != nullptr)
|
||||
{
|
||||
((Reference<TO, FROM>*) ref)->invalidate();
|
||||
ref->delink(); // the delink might be already done by invalidate(), but doing it here again does not hurt and insures an empty list
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//=====================================================
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _REFERENCE_H
|
||||
#define _REFERENCE_H
|
||||
|
||||
#include "Dynamic/LinkedList.h"
|
||||
#include "Errors.h" // for ASSERT
|
||||
|
||||
//=====================================================
|
||||
|
||||
template <class TO, class FROM> class Reference : public LinkedListElement
|
||||
{
|
||||
private:
|
||||
TO* iRefTo;
|
||||
FROM* iRefFrom;
|
||||
protected:
|
||||
// Tell our refTo (target) object that we have a link
|
||||
virtual void targetObjectBuildLink() = 0;
|
||||
|
||||
// Tell our refTo (taget) object, that the link is cut
|
||||
virtual void targetObjectDestroyLink() = 0;
|
||||
|
||||
// Tell our refFrom (source) object, that the link is cut (Target destroyed)
|
||||
virtual void sourceObjectDestroyLink() = 0;
|
||||
public:
|
||||
Reference() { iRefTo = nullptr; iRefFrom = nullptr; }
|
||||
virtual ~Reference() = default;
|
||||
|
||||
// Create new link
|
||||
void link(TO* toObj, FROM* fromObj)
|
||||
{
|
||||
ASSERT(fromObj); // fromObj MUST not be nullptr
|
||||
if (isValid())
|
||||
{
|
||||
unlink();
|
||||
}
|
||||
if (toObj != nullptr)
|
||||
{
|
||||
iRefTo = toObj;
|
||||
iRefFrom = fromObj;
|
||||
targetObjectBuildLink();
|
||||
}
|
||||
}
|
||||
|
||||
// We don't need the reference anymore. Call comes from the refFrom object
|
||||
// Tell our refTo object, that the link is cut
|
||||
void unlink()
|
||||
{
|
||||
targetObjectDestroyLink();
|
||||
delink();
|
||||
iRefTo = nullptr;
|
||||
iRefFrom = nullptr;
|
||||
}
|
||||
|
||||
// Link is invalid due to destruction of referenced target object. Call comes from the refTo object
|
||||
// Tell our refFrom object, that the link is cut
|
||||
void invalidate() // the iRefFrom MUST remain!!
|
||||
{
|
||||
sourceObjectDestroyLink();
|
||||
delink();
|
||||
iRefTo = nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isValid() const // Only check the iRefTo
|
||||
{
|
||||
return iRefTo != nullptr;
|
||||
}
|
||||
|
||||
Reference<TO, FROM>* next() { return ((Reference<TO, FROM>*) LinkedListElement::next()); }
|
||||
[[nodiscard]] Reference<TO, FROM> const* next() const { return ((Reference<TO, FROM> const*) LinkedListElement::next()); }
|
||||
Reference<TO, FROM>* prev() { return ((Reference<TO, FROM>*) LinkedListElement::prev()); }
|
||||
[[nodiscard]] Reference<TO, FROM> const* prev() const { return ((Reference<TO, FROM> const*) LinkedListElement::prev()); }
|
||||
|
||||
Reference<TO, FROM>* nocheck_next() { return ((Reference<TO, FROM>*) LinkedListElement::nocheck_next()); }
|
||||
[[nodiscard]] Reference<TO, FROM> const* nocheck_next() const { return ((Reference<TO, FROM> const*) LinkedListElement::nocheck_next()); }
|
||||
Reference<TO, FROM>* nocheck_prev() { return ((Reference<TO, FROM>*) LinkedListElement::nocheck_prev()); }
|
||||
[[nodiscard]] Reference<TO, FROM> const* nocheck_prev() const { return ((Reference<TO, FROM> const*) LinkedListElement::nocheck_prev()); }
|
||||
|
||||
TO* operator ->() const { return iRefTo; }
|
||||
[[nodiscard]] TO* getTarget() const { return iRefTo; }
|
||||
|
||||
[[nodiscard]] FROM* GetSource() const { return iRefFrom; }
|
||||
};
|
||||
|
||||
//=====================================================
|
||||
#endif
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_OBJECTREGISTRY_H
|
||||
#define ACORE_OBJECTREGISTRY_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
/** ObjectRegistry holds all registry item of the same type
|
||||
*/
|
||||
template<class T, class Key = std::string>
|
||||
class ObjectRegistry final
|
||||
{
|
||||
public:
|
||||
typedef std::map<Key, std::unique_ptr<T>> RegistryMapType;
|
||||
|
||||
/// Returns a registry item
|
||||
T const* GetRegistryItem(Key const& key) const
|
||||
{
|
||||
auto itr = _registeredObjects.find(key);
|
||||
if (itr == _registeredObjects.end())
|
||||
return nullptr;
|
||||
return itr->second.get();
|
||||
}
|
||||
|
||||
static ObjectRegistry<T, Key>* instance()
|
||||
{
|
||||
static ObjectRegistry<T, Key>* instance = new ObjectRegistry<T, Key>();
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// Inserts a registry item
|
||||
bool InsertItem(T* obj, Key const& key, bool force = false)
|
||||
{
|
||||
auto itr = _registeredObjects.find(key);
|
||||
if (itr != _registeredObjects.end())
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_registeredObjects.erase(itr);
|
||||
}
|
||||
|
||||
_registeredObjects.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(obj));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns true if registry contains an item
|
||||
bool HasItem(Key const& key) const
|
||||
{
|
||||
return (_registeredObjects.count(key) > 0);
|
||||
}
|
||||
|
||||
/// Return the map of registered items
|
||||
RegistryMapType const& GetRegisteredItems() const
|
||||
{
|
||||
return _registeredObjects;
|
||||
}
|
||||
|
||||
private:
|
||||
RegistryMapType _registeredObjects;
|
||||
|
||||
// non instanceable, only static
|
||||
ObjectRegistry() { }
|
||||
~ObjectRegistry() { }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_TYPECONTAINER_H
|
||||
#define ACORE_TYPECONTAINER_H
|
||||
|
||||
/*
|
||||
* Here, you'll find a series of containers that allow you to hold multiple
|
||||
* types of object at the same time.
|
||||
*/
|
||||
|
||||
#include "Dynamic/TypeList.h"
|
||||
#include "GridRefMgr.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* @class ContainerMapList is a mulit-type container for map elements
|
||||
* By itself its meaningless but collaborate along with TypeContainers,
|
||||
* it become the most powerfully container in the whole system.
|
||||
*/
|
||||
template<class OBJECT>
|
||||
struct ContainerMapList
|
||||
{
|
||||
GridRefMgr<OBJECT> _element;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ContainerMapList<TypeNull> /* nothing is in type null */
|
||||
{
|
||||
};
|
||||
|
||||
template<class H, class T>
|
||||
struct ContainerMapList<TypeList<H, T>>
|
||||
{
|
||||
ContainerMapList<H> _elements;
|
||||
ContainerMapList<T> _TailElements;
|
||||
};
|
||||
|
||||
template<class OBJECT>
|
||||
struct ContainerVector
|
||||
{
|
||||
std::vector<OBJECT*> _element;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ContainerVector<TypeNull>
|
||||
{
|
||||
};
|
||||
|
||||
template<class H, class T>
|
||||
struct ContainerVector<TypeList<H, T>>
|
||||
{
|
||||
ContainerVector<H> _elements;
|
||||
ContainerVector<T> _TailElements;
|
||||
};
|
||||
|
||||
template<class OBJECT, class KEY_TYPE>
|
||||
struct ContainerUnorderedMap
|
||||
{
|
||||
std::unordered_map<KEY_TYPE, OBJECT*> _element;
|
||||
};
|
||||
|
||||
template<class KEY_TYPE>
|
||||
struct ContainerUnorderedMap<TypeNull, KEY_TYPE>
|
||||
{
|
||||
};
|
||||
|
||||
template<class H, class T, class KEY_TYPE>
|
||||
struct ContainerUnorderedMap<TypeList<H, T>, KEY_TYPE>
|
||||
{
|
||||
ContainerUnorderedMap<H, KEY_TYPE> _elements;
|
||||
ContainerUnorderedMap<T, KEY_TYPE> _TailElements;
|
||||
};
|
||||
|
||||
/*
|
||||
* @class ContainerList is a simple list of different types of elements
|
||||
*
|
||||
*/
|
||||
template<class OBJECT> struct ContainerList
|
||||
{
|
||||
OBJECT _element;
|
||||
};
|
||||
|
||||
/* TypeNull is underfined */
|
||||
template<> struct ContainerList<TypeNull> { };
|
||||
template<class H, class T> struct ContainerList<TypeList<H, T>>
|
||||
{
|
||||
ContainerList<H> _elements;
|
||||
ContainerMapList<T> _TailElements;
|
||||
};
|
||||
|
||||
#include "TypeContainerFunctions.h"
|
||||
|
||||
/*
|
||||
* @class TypeMapContainer contains a fixed number of types and is
|
||||
* determined at compile time. This is probably the most complicated
|
||||
* class and do its simplest thing, that is, holds objects
|
||||
* of different types.
|
||||
*/
|
||||
|
||||
template<class OBJECT_TYPES>
|
||||
class TypeMapContainer
|
||||
{
|
||||
public:
|
||||
template<class SPECIFIC_TYPE> [[nodiscard]] std::size_t Count() const { return Acore::Count(i_elements, (SPECIFIC_TYPE*)nullptr); }
|
||||
|
||||
/// inserts a specific object into the container
|
||||
template<class SPECIFIC_TYPE>
|
||||
bool insert(SPECIFIC_TYPE* obj)
|
||||
{
|
||||
SPECIFIC_TYPE* t = Acore::Insert(i_elements, obj);
|
||||
return (t != nullptr);
|
||||
}
|
||||
|
||||
/// Removes the object from the container, and returns the removed object
|
||||
//template<class SPECIFIC_TYPE>
|
||||
// bool remove(SPECIFIC_TYPE* obj)
|
||||
//{
|
||||
// SPECIFIC_TYPE* t = Acore::Remove(i_elements, obj);
|
||||
// return (t != nullptr);
|
||||
//}
|
||||
|
||||
ContainerMapList<OBJECT_TYPES>& GetElements() { return i_elements; }
|
||||
[[nodiscard]] const ContainerMapList<OBJECT_TYPES>& GetElements() const { return i_elements;}
|
||||
|
||||
private:
|
||||
ContainerMapList<OBJECT_TYPES> i_elements;
|
||||
};
|
||||
|
||||
template<class OBJECT_TYPES>
|
||||
class TypeVectorContainer
|
||||
{
|
||||
public:
|
||||
template<class SPECIFIC_TYPE> [[nodiscard]] std::size_t Count() const { return Acore::Count(i_elements, (SPECIFIC_TYPE*)nullptr); }
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
bool Insert(SPECIFIC_TYPE* obj)
|
||||
{
|
||||
SPECIFIC_TYPE* t = Acore::Insert(i_elements, obj);
|
||||
return (t != nullptr);
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
bool Remove(SPECIFIC_TYPE* obj)
|
||||
{
|
||||
SPECIFIC_TYPE* t = Acore::Remove(i_elements, obj);
|
||||
return (t != nullptr);
|
||||
}
|
||||
|
||||
ContainerVector<OBJECT_TYPES>& GetElements() { return i_elements; }
|
||||
[[nodiscard]] const ContainerVector<OBJECT_TYPES>& GetElements() const { return i_elements; }
|
||||
|
||||
private:
|
||||
ContainerVector<OBJECT_TYPES> i_elements;
|
||||
};
|
||||
|
||||
template<class OBJECT_TYPES, class KEY_TYPE>
|
||||
class TypeUnorderedMapContainer
|
||||
{
|
||||
public:
|
||||
template<class SPECIFIC_TYPE>
|
||||
bool Insert(KEY_TYPE const& handle, SPECIFIC_TYPE* obj)
|
||||
{
|
||||
return Acore::Insert(_elements, handle, obj);
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
bool Remove(KEY_TYPE const& handle)
|
||||
{
|
||||
return Acore::Remove(_elements, handle, (SPECIFIC_TYPE*)nullptr);
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
SPECIFIC_TYPE* Find(KEY_TYPE const& handle)
|
||||
{
|
||||
return Acore::Find(_elements, handle, (SPECIFIC_TYPE*)nullptr);
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
[[nodiscard]] std::size_t Size() const
|
||||
{
|
||||
std::size_t size = 0;
|
||||
Acore::Size(_elements, &size, (SPECIFIC_TYPE*)nullptr);
|
||||
return size;
|
||||
}
|
||||
|
||||
ContainerUnorderedMap<OBJECT_TYPES, KEY_TYPE>& GetElements() { return _elements; }
|
||||
[[nodiscard]] ContainerUnorderedMap<OBJECT_TYPES, KEY_TYPE> const& GetElements() const { return _elements; }
|
||||
|
||||
private:
|
||||
ContainerUnorderedMap<OBJECT_TYPES, KEY_TYPE> _elements;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TYPECONTAINER_FUNCTIONS_H
|
||||
#define TYPECONTAINER_FUNCTIONS_H
|
||||
|
||||
/*
|
||||
* Here you'll find a list of helper functions to make
|
||||
* the TypeContainer usefull. Without it, its hard
|
||||
* to access or mutate the container.
|
||||
*/
|
||||
|
||||
#include "Dynamic/TypeList.h"
|
||||
|
||||
namespace Acore
|
||||
{
|
||||
// Helpers
|
||||
// Insert helpers
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE>
|
||||
bool Insert(ContainerUnorderedMap<SPECIFIC_TYPE, KEY_TYPE>& elements, KEY_TYPE const& handle, SPECIFIC_TYPE* obj)
|
||||
{
|
||||
auto i = elements._element.find(handle);
|
||||
if (i == elements._element.end())
|
||||
{
|
||||
elements._element[handle] = obj;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(i->second == obj, "Object with certain key already in but objects are different!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE>
|
||||
bool Insert(ContainerUnorderedMap<TypeNull, KEY_TYPE>& /*elements*/, KEY_TYPE const& /*handle*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE, class T>
|
||||
bool Insert(ContainerUnorderedMap<T, KEY_TYPE>& /*elements*/, KEY_TYPE const& /*handle*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE, class H, class T>
|
||||
bool Insert(ContainerUnorderedMap<TypeList<H, T>, KEY_TYPE>& elements, KEY_TYPE const& handle, SPECIFIC_TYPE* obj)
|
||||
{
|
||||
bool ret = Insert(elements._elements, handle, obj);
|
||||
return ret ? ret : Insert(elements._TailElements, handle, obj);
|
||||
}
|
||||
|
||||
// Find helpers
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE>
|
||||
SPECIFIC_TYPE* Find(ContainerUnorderedMap<SPECIFIC_TYPE, KEY_TYPE> const& elements, KEY_TYPE const& handle, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
auto i = elements._element.find(handle);
|
||||
if (i == elements._element.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return i->second;
|
||||
}
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE>
|
||||
SPECIFIC_TYPE* Find(ContainerUnorderedMap<TypeNull, KEY_TYPE> const& /*elements*/, KEY_TYPE const& /*handle*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE, class T>
|
||||
SPECIFIC_TYPE* Find(ContainerUnorderedMap<T, KEY_TYPE> const& /*elements*/, KEY_TYPE const& /*handle*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE, class H, class T>
|
||||
SPECIFIC_TYPE* Find(ContainerUnorderedMap<TypeList<H, T>, KEY_TYPE> const& elements, KEY_TYPE const& handle, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
SPECIFIC_TYPE* ret = Find(elements._elements, handle, (SPECIFIC_TYPE*)nullptr);
|
||||
return ret ? ret : Find(elements._TailElements, handle, (SPECIFIC_TYPE*)nullptr);
|
||||
}
|
||||
|
||||
// Erase helpers
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE>
|
||||
bool Remove(ContainerUnorderedMap<SPECIFIC_TYPE, KEY_TYPE>& elements, KEY_TYPE const& handle, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
elements._element.erase(handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE>
|
||||
bool Remove(ContainerUnorderedMap<TypeNull, KEY_TYPE>& /*elements*/, KEY_TYPE const& /*handle*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE, class T>
|
||||
bool Remove(ContainerUnorderedMap<T, KEY_TYPE>& /*elements*/, KEY_TYPE const& /*handle*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE, class H, class T>
|
||||
bool Remove(ContainerUnorderedMap<TypeList<H, T>, KEY_TYPE>& elements, KEY_TYPE const& handle, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
bool ret = Remove(elements._elements, handle, (SPECIFIC_TYPE*)nullptr);
|
||||
return ret ? ret : Remove(elements._TailElements, handle, (SPECIFIC_TYPE*)nullptr);
|
||||
}
|
||||
|
||||
// Count helpers
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE>
|
||||
bool Size(ContainerUnorderedMap<SPECIFIC_TYPE, KEY_TYPE> const& elements, std::size_t* size, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
*size = elements._element.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE>
|
||||
bool Size(ContainerUnorderedMap<TypeNull, KEY_TYPE> const& /*elements*/, std::size_t* /*size*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE, class T>
|
||||
bool Size(ContainerUnorderedMap<T, KEY_TYPE> const& /*elements*/, std::size_t* /*size*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class KEY_TYPE, class H, class T>
|
||||
bool Size(ContainerUnorderedMap<TypeList<H, T>, KEY_TYPE> const& elements, std::size_t* size, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
bool ret = Size(elements._elements, size, (SPECIFIC_TYPE*)nullptr);
|
||||
return ret ? ret : Size(elements._TailElements, size, (SPECIFIC_TYPE*)nullptr);
|
||||
}
|
||||
|
||||
/* ContainerMapList Helpers */
|
||||
// count functions
|
||||
template<class SPECIFIC_TYPE>
|
||||
std::size_t Count(const ContainerMapList<SPECIFIC_TYPE>& elements, SPECIFIC_TYPE* /*fake*/)
|
||||
{
|
||||
return elements._element.getSize();
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
std::size_t Count(const ContainerMapList<TypeNull>& /*elements*/, SPECIFIC_TYPE* /*fake*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class T>
|
||||
std::size_t Count(const ContainerMapList<T>& /*elements*/, SPECIFIC_TYPE* /*fake*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class T>
|
||||
std::size_t Count(const ContainerMapList<TypeList<SPECIFIC_TYPE, T>>& elements, SPECIFIC_TYPE* fake)
|
||||
{
|
||||
return Count(elements._elements, fake);
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class H, class T>
|
||||
std::size_t Count(const ContainerMapList<TypeList<H, T>>& elements, SPECIFIC_TYPE* fake)
|
||||
{
|
||||
return Count(elements._TailElements, fake);
|
||||
}
|
||||
|
||||
// non-const insert functions
|
||||
template<class SPECIFIC_TYPE>
|
||||
SPECIFIC_TYPE* Insert(ContainerMapList<SPECIFIC_TYPE>& elements, SPECIFIC_TYPE* obj)
|
||||
{
|
||||
//elements._element[hdl] = obj;
|
||||
obj->AddToGrid(elements._element);
|
||||
return obj;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
SPECIFIC_TYPE* Insert(ContainerMapList<TypeNull>& /*elements*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// this is a missed
|
||||
template<class SPECIFIC_TYPE, class T>
|
||||
SPECIFIC_TYPE* Insert(ContainerMapList<T>& /*elements*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return nullptr; // a missed
|
||||
}
|
||||
|
||||
// Recursion
|
||||
template<class SPECIFIC_TYPE, class H, class T>
|
||||
SPECIFIC_TYPE* Insert(ContainerMapList<TypeList<H, T>>& elements, SPECIFIC_TYPE* obj)
|
||||
{
|
||||
SPECIFIC_TYPE* t = Insert(elements._elements, obj);
|
||||
return (t != nullptr ? t : Insert(elements._TailElements, obj));
|
||||
}
|
||||
|
||||
//// non-const remove method
|
||||
//template<class SPECIFIC_TYPE> SPECIFIC_TYPE* Remove(ContainerMapList<SPECIFIC_TYPE> & /*elements*/, SPECIFIC_TYPE *obj)
|
||||
//{
|
||||
// obj->GetGridRef().unlink();
|
||||
// return obj;
|
||||
//}
|
||||
|
||||
//template<class SPECIFIC_TYPE> SPECIFIC_TYPE* Remove(ContainerMapList<TypeNull> &/*elements*/, SPECIFIC_TYPE * /*obj*/)
|
||||
//{
|
||||
// return nullptr;
|
||||
//}
|
||||
|
||||
//// this is a missed
|
||||
//template<class SPECIFIC_TYPE, class T> SPECIFIC_TYPE* Remove(ContainerMapList<T> &/*elements*/, SPECIFIC_TYPE * /*obj*/)
|
||||
//{
|
||||
// return nullptr; // a missed
|
||||
//}
|
||||
|
||||
//template<class SPECIFIC_TYPE, class T, class H> SPECIFIC_TYPE* Remove(ContainerMapList<TypeList<H, T> > &elements, SPECIFIC_TYPE *obj)
|
||||
//{
|
||||
// // The head element is bad
|
||||
// SPECIFIC_TYPE* t = Remove(elements._elements, obj);
|
||||
// return ( t != nullptr ? t : Remove(elements._TailElements, obj));
|
||||
//}
|
||||
|
||||
/* ContainerVector Helpers */
|
||||
// count functions
|
||||
template<class SPECIFIC_TYPE>
|
||||
std::size_t Count(const ContainerVector<SPECIFIC_TYPE>& elements, SPECIFIC_TYPE* /*fake*/)
|
||||
{
|
||||
return elements._element.getSize();
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
std::size_t Count(const ContainerVector<TypeNull>& /*elements*/, SPECIFIC_TYPE* /*fake*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class T>
|
||||
std::size_t Count(const ContainerVector<T>& /*elements*/, SPECIFIC_TYPE* /*fake*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class T>
|
||||
std::size_t Count(const ContainerVector<TypeList<SPECIFIC_TYPE, T>>& elements, SPECIFIC_TYPE* fake)
|
||||
{
|
||||
return Count(elements._elements, fake);
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class H, class T>
|
||||
std::size_t Count(const ContainerVector<TypeList<H, T>>& elements, SPECIFIC_TYPE* fake)
|
||||
{
|
||||
return Count(elements._TailElements, fake);
|
||||
}
|
||||
|
||||
// non-const insert functions
|
||||
template<class SPECIFIC_TYPE>
|
||||
SPECIFIC_TYPE* Insert(ContainerVector<SPECIFIC_TYPE>& elements, SPECIFIC_TYPE* obj)
|
||||
{
|
||||
elements._element.push_back(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE>
|
||||
SPECIFIC_TYPE* Insert(ContainerVector<TypeNull>& /*elements*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// this is a missed
|
||||
template<class SPECIFIC_TYPE, class T>
|
||||
SPECIFIC_TYPE* Insert(ContainerVector<T>& /*elements*/, SPECIFIC_TYPE* /*obj*/)
|
||||
{
|
||||
return nullptr; // a missed
|
||||
}
|
||||
|
||||
// Recursion
|
||||
template<class SPECIFIC_TYPE, class H, class T>
|
||||
SPECIFIC_TYPE* Insert(ContainerVector<TypeList<H, T>>& elements, SPECIFIC_TYPE* obj)
|
||||
{
|
||||
SPECIFIC_TYPE* t = Insert(elements._elements, obj);
|
||||
return (t != nullptr ? t : Insert(elements._TailElements, obj));
|
||||
}
|
||||
|
||||
// non-const remove method
|
||||
template<class SPECIFIC_TYPE> SPECIFIC_TYPE* Remove(ContainerVector<SPECIFIC_TYPE>& elements, SPECIFIC_TYPE *obj)
|
||||
{
|
||||
// Simple vector find/swap/pop, this container should be very lightly used
|
||||
// so I don't suspect the linear search complexity to be an issue
|
||||
auto itr = std::find(elements._element.begin(), elements._element.end(), obj);
|
||||
if (itr != elements._element.end())
|
||||
{
|
||||
// Swap the element to be removed with the last element
|
||||
std::swap(*itr, elements._element.back());
|
||||
|
||||
// Remove the last element (which is now the element we wanted to remove)
|
||||
elements._element.pop_back();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE> SPECIFIC_TYPE* Remove(ContainerVector<TypeNull> &/*elements*/, SPECIFIC_TYPE * /*obj*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// this is a missed
|
||||
template<class SPECIFIC_TYPE, class T> SPECIFIC_TYPE* Remove(ContainerVector<T> &/*elements*/, SPECIFIC_TYPE * /*obj*/)
|
||||
{
|
||||
return nullptr; // a missed
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class T, class H> SPECIFIC_TYPE* Remove(ContainerVector<TypeList<H, T> > &elements, SPECIFIC_TYPE *obj)
|
||||
{
|
||||
// The head element is bad
|
||||
SPECIFIC_TYPE* t = Remove(elements._elements, obj);
|
||||
return ( t != nullptr ? t : Remove(elements._TailElements, obj));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TYPECONTAINER_FUNCTIONS_PTR_H
|
||||
#define TYPECONTAINER_FUNCTIONS_PTR_H
|
||||
|
||||
/*
|
||||
* Here you'll find a list of helper functions to make
|
||||
* the TypeContainer usefull. Without it, its hard
|
||||
* to access or mutate the container.
|
||||
*/
|
||||
|
||||
#include "Platform/Define.h"
|
||||
#include "Utilities/TypeList.h"
|
||||
#include <map>
|
||||
|
||||
namespace Acore
|
||||
{
|
||||
/* ContainerMapList Helpers */
|
||||
// count functions
|
||||
// template<class SPECIFIC_TYPE> std::size_t Count(const ContainerMapList<SPECIFIC_TYPE> &elements, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
// {
|
||||
// return elements._element.size();
|
||||
// };
|
||||
//
|
||||
// template<class SPECIFIC_TYPE> std::size_t Count(const ContainerMapList<TypeNull> &elements, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// template<class SPECIFIC_TYPE, class T> std::size_t Count(const ContainerMapList<T> &elements, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// template<class SPECIFIC_TYPE, class T> std::size_t Count(const ContainerMapList<TypeList<SPECIFIC_TYPE, T> >&elements, SPECIFIC_TYPE* fake)
|
||||
// {
|
||||
// return Count(elements._elements, fake);
|
||||
// }
|
||||
//
|
||||
// template<class SPECIFIC_TYPE, class H, class T> std::size_t Count(const ContainerMapList<TypeList<H, T> >&elements, SPECIFIC_TYPE* fake)
|
||||
// {
|
||||
// return Count(elements._TailElements, fake);
|
||||
// }
|
||||
|
||||
// non-const find functions
|
||||
template<class SPECIFIC_TYPE> CountedPtr<SPECIFIC_TYPE>& Find(ContainerMapList<SPECIFIC_TYPE>& elements, OBJECT_HANDLE hdl, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
{
|
||||
typename std::map<OBJECT_HANDLE, CountedPtr<SPECIFIC_TYPE>>::iterator iter = elements._element.find(hdl);
|
||||
return (iter == elements._element.end() ? NullPtr<SPECIFIC_TYPE>((SPECIFIC_TYPE*)nullptr) : iter->second);
|
||||
};
|
||||
|
||||
template<class SPECIFIC_TYPE> CountedPtr<SPECIFIC_TYPE>& Find(ContainerMapList<TypeNull>& elements, OBJECT_HANDLE hdl, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
{
|
||||
return NullPtr<SPECIFIC_TYPE>((SPECIFIC_TYPE*)nullptr);// terminate recursion
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class T> CountedPtr<SPECIFIC_TYPE>& Find(ContainerMapList<T>& elements, OBJECT_HANDLE hdl, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
{
|
||||
return NullPtr<SPECIFIC_TYPE>((SPECIFIC_TYPE*)nullptr);// this is a missed
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class H, class T> CountedPtr<SPECIFIC_TYPE>& Find(ContainerMapList<TypeList<H, T>>& elements, OBJECT_HANDLE hdl, CountedPtr<SPECIFIC_TYPE>* fake)
|
||||
{
|
||||
CountedPtr<SPECIFIC_TYPE>& t = Find(elements._elements, hdl, fake);
|
||||
return (!t ? Find(elements._TailElements, hdl, fake) : t);
|
||||
}
|
||||
|
||||
// const find functions
|
||||
template<class SPECIFIC_TYPE> const CountedPtr<SPECIFIC_TYPE>& Find(const ContainerMapList<SPECIFIC_TYPE>& elements, OBJECT_HANDLE hdl, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
{
|
||||
typename CountedPtr<SPECIFIC_TYPE>::iterator iter = elements._element.find(hdl);
|
||||
return (iter == elements._element.end() ? NullPtr<SPECIFIC_TYPE>((SPECIFIC_TYPE*)nullptr) : iter->second);
|
||||
};
|
||||
|
||||
template<class SPECIFIC_TYPE> const CountedPtr<SPECIFIC_TYPE>& Find(const ContainerMapList<TypeNull>& elements, OBJECT_HANDLE hdl, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
{
|
||||
return NullPtr<SPECIFIC_TYPE>((SPECIFIC_TYPE*)nullptr);
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class T> const CountedPtr<SPECIFIC_TYPE>& Find(const ContainerMapList<T>& elements, OBJECT_HANDLE hdl, CountedPtr<SPECIFIC_TYPE>* /*fake*/)
|
||||
{
|
||||
return NullPtr<SPECIFIC_TYPE>((SPECIFIC_TYPE*)nullptr);
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class H, class T> CountedPtr<SPECIFIC_TYPE>& Find(const ContainerMapList<TypeList<H, T>>& elements, OBJECT_HANDLE hdl, CountedPtr<SPECIFIC_TYPE>* fake)
|
||||
{
|
||||
CountedPtr<SPECIFIC_TYPE>& t = Find(elements._elements, hdl, fake);
|
||||
if (!t)
|
||||
{
|
||||
t = Find(elements._TailElement, hdl, fake);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
// non-const insert functions
|
||||
template<class SPECIFIC_TYPE> CountedPtr<SPECIFIC_TYPE>& Insert(ContainerMapList<SPECIFIC_TYPE>& elements, CountedPtr<SPECIFIC_TYPE>& obj, OBJECT_HANDLE hdl)
|
||||
{
|
||||
elements._element[hdl] = obj;
|
||||
return obj;
|
||||
};
|
||||
|
||||
template<class SPECIFIC_TYPE> CountedPtr<SPECIFIC_TYPE>& Insert(ContainerMapList<TypeNull>& elements, CountedPtr<SPECIFIC_TYPE>& obj, OBJECT_HANDLE hdl)
|
||||
{
|
||||
return NullPtr<SPECIFIC_TYPE>((SPECIFIC_TYPE*)nullptr);
|
||||
}
|
||||
|
||||
// this is a missed
|
||||
template<class SPECIFIC_TYPE, class T> CountedPtr<SPECIFIC_TYPE>& Insert(ContainerMapList<T>& elements, CountedPtr<SPECIFIC_TYPE>& obj, OBJECT_HANDLE hdl)
|
||||
{
|
||||
return NullPtr<SPECIFIC_TYPE>((SPECIFIC_TYPE*)nullptr);// a missed
|
||||
}
|
||||
|
||||
// Recursion
|
||||
template<class SPECIFIC_TYPE, class H, class T> CountedPtr<SPECIFIC_TYPE>& Insert(ContainerMapList<TypeList<H, T>>& elements, CountedPtr<SPECIFIC_TYPE>& obj, OBJECT_HANDLE hdl)
|
||||
{
|
||||
CountedPtr<SPECIFIC_TYPE>& t = Insert(elements._elements, obj, hdl);
|
||||
return (!t ? Insert(elements._TailElements, obj, hdl) : t);
|
||||
}
|
||||
|
||||
// non-const remove method
|
||||
template<class SPECIFIC_TYPE> bool Remove(ContainerMapList<SPECIFIC_TYPE>& elements, CountedPtr<SPECIFIC_TYPE>& obj, OBJECT_HANDLE hdl)
|
||||
{
|
||||
typename std::map<OBJECT_HANDLE, CountedPtr<SPECIFIC_TYPE>>::iterator iter = elements._element.find(hdl);
|
||||
if (iter != elements._element.end())
|
||||
{
|
||||
elements._element.erase(iter);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // found... terminate the search
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE> bool Remove(ContainerMapList<TypeNull>& elements, CountedPtr<SPECIFIC_TYPE>& obj, OBJECT_HANDLE hdl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// this is a missed
|
||||
template<class SPECIFIC_TYPE, class T> bool Remove(ContainerMapList<T>& elements, CountedPtr<SPECIFIC_TYPE>& obj, OBJECT_HANDLE hdl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class SPECIFIC_TYPE, class T, class H> bool Remove(ContainerMapList<TypeList<H, T>>& elements, CountedPtr<SPECIFIC_TYPE>& obj, OBJECT_HANDLE hdl)
|
||||
{
|
||||
// The head element is bad
|
||||
bool t = Remove(elements._elements, obj, hdl);
|
||||
return ( !t ? Remove(elements._TailElements, obj, hdl) : t );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_TYPECONTAINERVISITOR_H
|
||||
#define ACORE_TYPECONTAINERVISITOR_H
|
||||
|
||||
/*
|
||||
* @class TypeContainerVisitor is implemented as a visitor pattern. It is
|
||||
* a visitor to the TypeContainerList or TypeContainerMapList. The visitor has
|
||||
* to overload its types as a visit method is called.
|
||||
*/
|
||||
|
||||
#include "Dynamic/TypeContainer.h"
|
||||
|
||||
// forward declaration
|
||||
template<class T, class Y> class TypeContainerVisitor;
|
||||
|
||||
// visitor helper
|
||||
template<class VISITOR, class TYPE_CONTAINER> void VisitorHelper(VISITOR& v, TYPE_CONTAINER& c)
|
||||
{
|
||||
v.Visit(c);
|
||||
}
|
||||
|
||||
// terminate condition container map list
|
||||
template<class VISITOR> void VisitorHelper(VISITOR& /*v*/, ContainerMapList<TypeNull>& /*c*/) { }
|
||||
|
||||
template<class VISITOR, class T> void VisitorHelper(VISITOR& v, ContainerMapList<T>& c)
|
||||
{
|
||||
v.Visit(c._element);
|
||||
}
|
||||
|
||||
// recursion container map list
|
||||
template<class VISITOR, class H, class T> void VisitorHelper(VISITOR& v, ContainerMapList<TypeList<H, T>>& c)
|
||||
{
|
||||
VisitorHelper(v, c._elements);
|
||||
VisitorHelper(v, c._TailElements);
|
||||
}
|
||||
|
||||
// for TypeMapContainer
|
||||
template<class VISITOR, class OBJECT_TYPES> void VisitorHelper(VISITOR& v, TypeMapContainer<OBJECT_TYPES>& c)
|
||||
{
|
||||
VisitorHelper(v, c.GetElements());
|
||||
}
|
||||
|
||||
// VectorContainer
|
||||
template<class VISITOR> void VisitorHelper(VISITOR& /*v*/, ContainerVector<TypeNull>& /*c*/) {}
|
||||
|
||||
template<class VISITOR, class T> void VisitorHelper(VISITOR& v, ContainerVector<T>& c)
|
||||
{
|
||||
v.Visit(c._element);
|
||||
}
|
||||
|
||||
// recursion container map list
|
||||
template<class VISITOR, class H, class T> void VisitorHelper(VISITOR& v, ContainerVector<TypeList<H, T>>& c)
|
||||
{
|
||||
VisitorHelper(v, c._elements);
|
||||
VisitorHelper(v, c._TailElements);
|
||||
}
|
||||
|
||||
// for TypeMapContainer
|
||||
template<class VISITOR, class OBJECT_TYPES> void VisitorHelper(VISITOR& v, TypeVectorContainer<OBJECT_TYPES>& c)
|
||||
{
|
||||
VisitorHelper(v, c.GetElements());
|
||||
}
|
||||
|
||||
// TypeUnorderedMapContainer
|
||||
template<class VISITOR, class KEY_TYPE>
|
||||
void VisitorHelper(VISITOR& /*v*/, ContainerUnorderedMap<TypeNull, KEY_TYPE>& /*c*/) { }
|
||||
|
||||
template<class VISITOR, class KEY_TYPE, class T>
|
||||
void VisitorHelper(VISITOR& v, ContainerUnorderedMap<T, KEY_TYPE>& c)
|
||||
{
|
||||
v.Visit(c._element);
|
||||
}
|
||||
|
||||
template<class VISITOR, class KEY_TYPE, class H, class T>
|
||||
void VisitorHelper(VISITOR& v, ContainerUnorderedMap<TypeList<H, T>, KEY_TYPE>& c)
|
||||
{
|
||||
VisitorHelper(v, c._elements);
|
||||
VisitorHelper(v, c._TailElements);
|
||||
}
|
||||
|
||||
template<class VISITOR, class OBJECT_TYPES, class KEY_TYPE>
|
||||
void VisitorHelper(VISITOR& v, TypeUnorderedMapContainer<OBJECT_TYPES, KEY_TYPE>& c)
|
||||
{
|
||||
VisitorHelper(v, c.GetElements());
|
||||
}
|
||||
|
||||
template<class VISITOR, class TYPE_CONTAINER>
|
||||
class TypeContainerVisitor
|
||||
{
|
||||
public:
|
||||
TypeContainerVisitor(VISITOR& v) : i_visitor(v) { }
|
||||
|
||||
void Visit(TYPE_CONTAINER& c)
|
||||
{
|
||||
VisitorHelper(i_visitor, c);
|
||||
}
|
||||
|
||||
void Visit(const TYPE_CONTAINER& c) const
|
||||
{
|
||||
VisitorHelper(i_visitor, c);
|
||||
}
|
||||
|
||||
private:
|
||||
VISITOR& i_visitor;
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_TYPELIST_H
|
||||
#define ACORE_TYPELIST_H
|
||||
|
||||
/*
|
||||
@struct TypeList
|
||||
TypeList is the most simple but yet the most powerfull class of all. It holds
|
||||
at compile time the different type of objects in a linked list.
|
||||
*/
|
||||
|
||||
class TypeNull;
|
||||
|
||||
template<typename HEAD, typename TAIL>
|
||||
struct TypeList
|
||||
{
|
||||
typedef HEAD Head;
|
||||
typedef TAIL Tail;
|
||||
};
|
||||
|
||||
// enough for now.. can be expand at any point in time as needed
|
||||
#define TYPELIST_1(T1) TypeList<T1, TypeNull>
|
||||
#define TYPELIST_2(T1, T2) TypeList<T1, TYPELIST_1(T2) >
|
||||
#define TYPELIST_3(T1, T2, T3) TypeList<T1, TYPELIST_2(T2, T3) >
|
||||
#define TYPELIST_4(T1, T2, T3, T4) TypeList<T1, TYPELIST_3(T2, T3, T4) >
|
||||
#define TYPELIST_5(T1, T2, T3, T4, T5) TypeList<T1, TYPELIST_4(T2, T3, T4, T5) >
|
||||
#define TYPELIST_6(T1, T2, T3, T4, T5, T6) TypeList<T1, TYPELIST_5(T2, T3, T4, T5, T6) >
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Base32.h"
|
||||
#include "BaseEncoding.h"
|
||||
#include "Errors.h"
|
||||
|
||||
struct B32Impl
|
||||
{
|
||||
static constexpr std::size_t BITS_PER_CHAR = 5;
|
||||
|
||||
static constexpr char PADDING = '=';
|
||||
static constexpr char Encode(uint8 v)
|
||||
{
|
||||
ASSERT(v < 0x20);
|
||||
if (v < 26) { return 'A' + v; }
|
||||
else { return '2' + (v - 26); }
|
||||
}
|
||||
|
||||
static constexpr uint8 DECODE_ERROR = 0xff;
|
||||
static constexpr uint8 Decode(uint8 v)
|
||||
{
|
||||
if (v == '0') { return Decode('O'); }
|
||||
if (v == '1') { return Decode('l'); }
|
||||
if (v == '8') { return Decode('B'); }
|
||||
if (('A' <= v) && (v <= 'Z')) { return (v - 'A'); }
|
||||
if (('a' <= v) && (v <= 'z')) { return (v - 'a'); }
|
||||
if (('2' <= v) && (v <= '7')) { return (v - '2') + 26; }
|
||||
return DECODE_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
/*static*/ std::string Acore::Encoding::Base32::Encode(std::vector<uint8> const& data)
|
||||
{
|
||||
return Acore::Impl::GenericBaseEncoding<B32Impl>::Encode(data);
|
||||
}
|
||||
|
||||
/*static*/ Optional<std::vector<uint8>> Acore::Encoding::Base32::Decode(std::string const& data)
|
||||
{
|
||||
return Acore::Impl::GenericBaseEncoding<B32Impl>::Decode(data);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_BASE32_H
|
||||
#define ACORE_BASE32_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Optional.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Acore::Encoding
|
||||
{
|
||||
struct AC_COMMON_API Base32
|
||||
{
|
||||
static std::string Encode(std::vector<uint8> const& data);
|
||||
static Optional<std::vector<uint8>> Decode(std::string const& data);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Base64.h"
|
||||
#include "BaseEncoding.h"
|
||||
#include "Errors.h"
|
||||
|
||||
struct B64Impl
|
||||
{
|
||||
static constexpr std::size_t BITS_PER_CHAR = 6;
|
||||
|
||||
static constexpr char PADDING = '=';
|
||||
static constexpr char Encode(uint8 v)
|
||||
{
|
||||
ASSERT(v < 0x40);
|
||||
if (v < 26) { return 'A' + v; }
|
||||
if (v < 52) { return 'a' + (v - 26); }
|
||||
if (v < 62) { return '0' + (v - 52); }
|
||||
if (v == 62) { return '+'; }
|
||||
else { return '/'; }
|
||||
}
|
||||
|
||||
static constexpr uint8 DECODE_ERROR = 0xff;
|
||||
static constexpr uint8 Decode(uint8 v)
|
||||
{
|
||||
if (('A' <= v) && (v <= 'Z')) { return (v - 'A'); }
|
||||
if (('a' <= v) && (v <= 'z')) { return (v - 'a') + 26; }
|
||||
if (('0' <= v) && (v <= '9')) { return (v - '0') + 52; }
|
||||
if (v == '+') { return 62; }
|
||||
if (v == '/') { return 63; }
|
||||
return DECODE_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
/*static*/ std::string Acore::Encoding::Base64::Encode(std::vector<uint8> const& data)
|
||||
{
|
||||
return Acore::Impl::GenericBaseEncoding<B64Impl>::Encode(data);
|
||||
}
|
||||
|
||||
/*static*/ Optional<std::vector<uint8>> Acore::Encoding::Base64::Decode(std::string const& data)
|
||||
{
|
||||
return Acore::Impl::GenericBaseEncoding<B64Impl>::Decode(data);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_BASE64_H
|
||||
#define ACORE_BASE64_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Optional.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Acore::Encoding
|
||||
{
|
||||
struct AC_COMMON_API Base64
|
||||
{
|
||||
static std::string Encode(std::vector<uint8> const& data);
|
||||
static Optional<std::vector<uint8>> Decode(std::string const& data);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACORE_BASE_ENCODING_HPP
|
||||
#define ACORE_BASE_ENCODING_HPP
|
||||
|
||||
#include "Define.h"
|
||||
#include "Optional.h"
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Acore::Impl
|
||||
{
|
||||
template <typename Encoding>
|
||||
struct GenericBaseEncoding
|
||||
{
|
||||
static constexpr std::size_t BITS_PER_CHAR = Encoding::BITS_PER_CHAR;
|
||||
static constexpr std::size_t PAD_TO = std::lcm(8u, BITS_PER_CHAR);
|
||||
|
||||
static_assert(BITS_PER_CHAR < 8, "Encoding parameters are invalid");
|
||||
|
||||
static constexpr uint8 DECODE_ERROR = Encoding::DECODE_ERROR;
|
||||
static constexpr char PADDING = Encoding::PADDING;
|
||||
|
||||
static constexpr std::size_t EncodedSize(std::size_t size)
|
||||
{
|
||||
size *= 8; // bits in input
|
||||
if (size % PAD_TO) // pad to boundary
|
||||
{
|
||||
size += (PAD_TO - (size % PAD_TO));
|
||||
}
|
||||
return (size / BITS_PER_CHAR);
|
||||
}
|
||||
|
||||
static constexpr std::size_t DecodedSize(std::size_t size)
|
||||
{
|
||||
size *= BITS_PER_CHAR; // bits in input
|
||||
if (size % PAD_TO) // pad to boundary
|
||||
{
|
||||
size += (PAD_TO - (size % PAD_TO));
|
||||
}
|
||||
return (size / 8);
|
||||
}
|
||||
|
||||
static std::string Encode(std::vector<uint8> const& data)
|
||||
{
|
||||
auto it = data.begin(), end = data.end();
|
||||
if (it == end)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string s;
|
||||
s.reserve(EncodedSize(data.size()));
|
||||
|
||||
uint8 bitsLeft = 8; // in current byte
|
||||
do
|
||||
{
|
||||
uint8 thisC = 0;
|
||||
if (bitsLeft >= BITS_PER_CHAR)
|
||||
{
|
||||
bitsLeft -= BITS_PER_CHAR;
|
||||
thisC = ((*it >> bitsLeft) & ((1 << BITS_PER_CHAR) - 1));
|
||||
if (!bitsLeft)
|
||||
{
|
||||
++it;
|
||||
bitsLeft = 8;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
thisC = (*it & ((1 << bitsLeft) - 1)) << (BITS_PER_CHAR - bitsLeft);
|
||||
bitsLeft += (8 - BITS_PER_CHAR);
|
||||
if ((++it) != end)
|
||||
{
|
||||
thisC |= (*it >> bitsLeft);
|
||||
}
|
||||
}
|
||||
s.append(1, Encoding::Encode(thisC));
|
||||
} while (it != end);
|
||||
|
||||
while (bitsLeft != 8)
|
||||
{
|
||||
if (bitsLeft > BITS_PER_CHAR)
|
||||
{
|
||||
bitsLeft -= BITS_PER_CHAR;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitsLeft += (8 - BITS_PER_CHAR);
|
||||
}
|
||||
s.append(1, PADDING);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static Optional<std::vector<uint8>> Decode(std::string const& data)
|
||||
{
|
||||
auto it = data.begin(), end = data.end();
|
||||
if (it == end)
|
||||
{
|
||||
return std::vector<uint8>();
|
||||
}
|
||||
|
||||
std::vector<uint8> v;
|
||||
v.reserve(DecodedSize(data.size()));
|
||||
|
||||
uint8 currentByte = 0;
|
||||
uint8 bitsLeft = 8; // in current byte
|
||||
while ((it != end) && (*it != PADDING))
|
||||
{
|
||||
uint8 cur = Encoding::Decode(*(it++));
|
||||
if (cur == DECODE_ERROR)
|
||||
return {};
|
||||
|
||||
if (bitsLeft > BITS_PER_CHAR)
|
||||
{
|
||||
bitsLeft -= BITS_PER_CHAR;
|
||||
currentByte |= (cur << bitsLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
bitsLeft = BITS_PER_CHAR - bitsLeft; // in encoded char
|
||||
currentByte |= (cur >> bitsLeft);
|
||||
v.push_back(currentByte);
|
||||
currentByte = (cur & ((1 << bitsLeft) - 1));
|
||||
bitsLeft = 8 - bitsLeft; // in byte again
|
||||
currentByte <<= bitsLeft;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentByte)
|
||||
return {}; // decode error, trailing non-zero bits
|
||||
|
||||
// process padding
|
||||
while ((it != end) && (*it == PADDING) && (bitsLeft != 8))
|
||||
{
|
||||
if (bitsLeft > BITS_PER_CHAR)
|
||||
{
|
||||
bitsLeft -= BITS_PER_CHAR;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitsLeft += (8 - BITS_PER_CHAR);
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
// ok, all padding should be consumed, and we should be at end of string
|
||||
if (it == end)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
// anything else is an error
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "GitRevision.h"
|
||||
#include "revision.h"
|
||||
|
||||
char const* GitRevision::GetHash()
|
||||
{
|
||||
return _HASH;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetDate()
|
||||
{
|
||||
return _DATE;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetBranch()
|
||||
{
|
||||
return _BRANCH;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetCMakeCommand()
|
||||
{
|
||||
return _CMAKE_COMMAND;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetCMakeVersion()
|
||||
{
|
||||
return _CMAKE_VERSION;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetHostOSVersion()
|
||||
{
|
||||
return _CMAKE_HOST_SYSTEM;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetBuildDirectory()
|
||||
{
|
||||
return _BUILD_DIRECTORY;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetSourceDirectory()
|
||||
{
|
||||
return _SOURCE_DIRECTORY;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetMySQLExecutable()
|
||||
{
|
||||
return _MYSQL_EXECUTABLE;
|
||||
}
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
# ifdef _WIN64
|
||||
# define AZEROTH_PLATFORM_STR "Win64"
|
||||
# else
|
||||
# define AZEROTH_PLATFORM_STR "Win32"
|
||||
# endif
|
||||
#else // AC_PLATFORM
|
||||
# define AZEROTH_PLATFORM_STR "Unix"
|
||||
#endif
|
||||
|
||||
#ifndef ACORE_API_USE_DYNAMIC_LINKING
|
||||
# define ACORE_LINKAGE_TYPE_STR "Static"
|
||||
#else
|
||||
# define ACORE_LINKAGE_TYPE_STR "Dynamic"
|
||||
#endif
|
||||
|
||||
char const* GitRevision::GetFullVersion()
|
||||
{
|
||||
return AC_COMPANYNAME_STR " rev. " AC_PRODUCTVERSION_STR " (" AZEROTH_PLATFORM_STR ", " AC_BUILD_TYPE ", " ACORE_LINKAGE_TYPE_STR ")"; // cppcheck-suppress unknownMacro
|
||||
}
|
||||
|
||||
char const* GitRevision::GetCompanyNameStr()
|
||||
{
|
||||
return AC_COMPANYNAME_STR;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetLegalCopyrightStr()
|
||||
{
|
||||
return AC_LEGALCOPYRIGHT_STR;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetFileVersionStr()
|
||||
{
|
||||
return AC_FILEVERSION_STR;
|
||||
}
|
||||
|
||||
char const* GitRevision::GetProductVersionStr()
|
||||
{
|
||||
return AC_PRODUCTVERSION_STR;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GITREVISION_H__
|
||||
#define __GITREVISION_H__
|
||||
|
||||
#include "Define.h"
|
||||
|
||||
namespace GitRevision
|
||||
{
|
||||
AC_COMMON_API char const* GetHash();
|
||||
AC_COMMON_API char const* GetDate();
|
||||
AC_COMMON_API char const* GetBranch();
|
||||
AC_COMMON_API char const* GetCMakeCommand();
|
||||
AC_COMMON_API char const* GetCMakeVersion();
|
||||
AC_COMMON_API char const* GetHostOSVersion();
|
||||
AC_COMMON_API char const* GetBuildDirectory();
|
||||
AC_COMMON_API char const* GetSourceDirectory();
|
||||
AC_COMMON_API char const* GetMySQLExecutable();
|
||||
AC_COMMON_API char const* GetFullVersion();
|
||||
AC_COMMON_API char const* GetCompanyNameStr();
|
||||
AC_COMMON_API char const* GetLegalCopyrightStr();
|
||||
AC_COMMON_API char const* GetFileVersionStr();
|
||||
AC_COMMON_API char const* GetProductVersionStr();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "IPLocation.h"
|
||||
#include "Config.h"
|
||||
#include "Errors.h"
|
||||
#include "IpAddress.h"
|
||||
#include "Log.h"
|
||||
#include "StringConvert.h"
|
||||
#include <fstream>
|
||||
IpLocationStore::IpLocationStore()
|
||||
{
|
||||
}
|
||||
|
||||
IpLocationStore::~IpLocationStore()
|
||||
{
|
||||
}
|
||||
|
||||
void IpLocationStore::Load()
|
||||
{
|
||||
_ipLocationStore.clear();
|
||||
LOG_INFO("server.loading", "Loading IP Location Database...");
|
||||
|
||||
std::string databaseFilePath = sConfigMgr->GetOption<std::string>("IPLocationFile", "");
|
||||
if (databaseFilePath.empty())
|
||||
{
|
||||
LOG_INFO("server.loading", " ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
std::ifstream databaseFile(databaseFilePath);
|
||||
if (!databaseFile)
|
||||
{
|
||||
LOG_ERROR("server.loading", "IPLocation: No ip database file exists ({}).", databaseFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!databaseFile.is_open())
|
||||
{
|
||||
LOG_ERROR("server.loading", "IPLocation: Ip database file ({}) can not be opened.", databaseFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string ipFrom;
|
||||
std::string ipTo;
|
||||
std::string countryCode;
|
||||
std::string countryName;
|
||||
|
||||
while (databaseFile.good())
|
||||
{
|
||||
// Read lines
|
||||
if (!std::getline(databaseFile, ipFrom, ','))
|
||||
break;
|
||||
if (!std::getline(databaseFile, ipTo, ','))
|
||||
break;
|
||||
if (!std::getline(databaseFile, countryCode, ','))
|
||||
break;
|
||||
if (!std::getline(databaseFile, countryName, '\n'))
|
||||
break;
|
||||
|
||||
// Remove new lines and return
|
||||
countryName.erase(std::remove(countryName.begin(), countryName.end(), '\r'), countryName.end());
|
||||
countryName.erase(std::remove(countryName.begin(), countryName.end(), '\n'), countryName.end());
|
||||
|
||||
// Remove quotation marks
|
||||
ipFrom.erase(std::remove(ipFrom.begin(), ipFrom.end(), '"'), ipFrom.end());
|
||||
ipTo.erase(std::remove(ipTo.begin(), ipTo.end(), '"'), ipTo.end());
|
||||
countryCode.erase(std::remove(countryCode.begin(), countryCode.end(), '"'), countryCode.end());
|
||||
countryName.erase(std::remove(countryName.begin(), countryName.end(), '"'), countryName.end());
|
||||
|
||||
// Convert country code to lowercase
|
||||
std::transform(countryCode.begin(), countryCode.end(), countryCode.begin(), ::tolower);
|
||||
|
||||
auto IpFrom = Acore::StringTo<uint32>(ipFrom);
|
||||
auto IpTo = Acore::StringTo<uint32>(ipTo);
|
||||
|
||||
if (!IpFrom || !IpTo)
|
||||
continue;
|
||||
|
||||
_ipLocationStore.emplace_back(*IpFrom, *IpTo, std::move(countryCode), std::move(countryName));
|
||||
}
|
||||
|
||||
std::sort(_ipLocationStore.begin(), _ipLocationStore.end(), [](IpLocationRecord const& a, IpLocationRecord const& b) { return a.IpFrom < b.IpFrom; });
|
||||
ASSERT(std::is_sorted(_ipLocationStore.begin(), _ipLocationStore.end(), [](IpLocationRecord const& a, IpLocationRecord const& b) { return a.IpFrom < b.IpTo; }),
|
||||
"Overlapping IP ranges detected in database file");
|
||||
|
||||
databaseFile.close();
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded {} ip location entries.", static_cast<uint32>(_ipLocationStore.size()));
|
||||
LOG_INFO("server.loading", " ");
|
||||
}
|
||||
|
||||
IpLocationRecord const* IpLocationStore::GetLocationRecord(std::string const& ipAddress) const
|
||||
{
|
||||
uint32 ip;
|
||||
|
||||
try
|
||||
{
|
||||
ip = Acore::Net::address_to_uint(Acore::Net::make_address_v4(ipAddress));
|
||||
}
|
||||
catch (boost::system::system_error const&)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto itr = std::upper_bound(_ipLocationStore.begin(), _ipLocationStore.end(), ip, [](uint32 ip, IpLocationRecord const& loc) { return ip < loc.IpTo; });
|
||||
|
||||
if (itr == _ipLocationStore.end())
|
||||
return nullptr;
|
||||
|
||||
if (ip < itr->IpFrom)
|
||||
return nullptr;
|
||||
|
||||
return &(*itr);
|
||||
}
|
||||
|
||||
IpLocationStore* IpLocationStore::instance()
|
||||
{
|
||||
static IpLocationStore instance;
|
||||
return &instance;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Define.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct IpLocationRecord
|
||||
{
|
||||
IpLocationRecord() :
|
||||
IpFrom(0), IpTo(0) { }
|
||||
IpLocationRecord(uint32 ipFrom, uint32 ipTo, std::string countryCode, std::string countryName) :
|
||||
IpFrom(ipFrom), IpTo(ipTo), CountryCode(std::move(countryCode)), CountryName(std::move(countryName)) { }
|
||||
|
||||
uint32 IpFrom;
|
||||
uint32 IpTo;
|
||||
std::string CountryCode;
|
||||
std::string CountryName;
|
||||
};
|
||||
|
||||
class AC_COMMON_API IpLocationStore
|
||||
{
|
||||
public:
|
||||
IpLocationStore();
|
||||
~IpLocationStore();
|
||||
static IpLocationStore* instance();
|
||||
|
||||
void Load();
|
||||
IpLocationRecord const* GetLocationRecord(std::string const& ipAddress) const;
|
||||
|
||||
private:
|
||||
std::vector<IpLocationRecord> _ipLocationStore;
|
||||
};
|
||||
|
||||
#define sIPLocation IpLocationStore::instance()
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Appender.h"
|
||||
#include "LogMessage.h"
|
||||
#include "StringFormat.h"
|
||||
#include <sstream>
|
||||
|
||||
Appender::Appender(uint8 _id, std::string const& _name, LogLevel _level /* = LOG_LEVEL_DISABLED */, AppenderFlags _flags /* = APPENDER_FLAGS_NONE */):
|
||||
id(_id), name(_name), level(_level), flags(_flags) { }
|
||||
|
||||
Appender::~Appender() { }
|
||||
|
||||
uint8 Appender::getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
std::string const& Appender::getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
LogLevel Appender::getLogLevel() const
|
||||
{
|
||||
return level;
|
||||
}
|
||||
|
||||
AppenderFlags Appender::getFlags() const
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
void Appender::setLogLevel(LogLevel _level)
|
||||
{
|
||||
level = _level;
|
||||
}
|
||||
|
||||
void Appender::write(LogMessage* message)
|
||||
{
|
||||
if (!level || level < message->level)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::ostringstream ss;
|
||||
|
||||
if (flags & APPENDER_FLAGS_PREFIX_TIMESTAMP)
|
||||
{
|
||||
ss << message->getTimeStr() << ' ';
|
||||
}
|
||||
|
||||
if (flags & APPENDER_FLAGS_PREFIX_LOGLEVEL)
|
||||
{
|
||||
ss << Acore::StringFormat("{} ", Appender::getLogLevelString(message->level));
|
||||
}
|
||||
|
||||
if (flags & APPENDER_FLAGS_PREFIX_LOGFILTERTYPE)
|
||||
{
|
||||
ss << '[' << message->type << "] ";
|
||||
}
|
||||
|
||||
message->prefix = ss.str();
|
||||
_write(message);
|
||||
}
|
||||
|
||||
char const* Appender::getLogLevelString(LogLevel level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case LOG_LEVEL_FATAL:
|
||||
return "FATAL";
|
||||
case LOG_LEVEL_ERROR:
|
||||
return "ERROR";
|
||||
case LOG_LEVEL_WARN:
|
||||
return "WARN";
|
||||
case LOG_LEVEL_INFO:
|
||||
return "INFO";
|
||||
case LOG_LEVEL_DEBUG:
|
||||
return "DEBUG";
|
||||
case LOG_LEVEL_TRACE:
|
||||
return "TRACE";
|
||||
default:
|
||||
return "DISABLED";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef APPENDER_H
|
||||
#define APPENDER_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "LogCommon.h"
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
struct LogMessage;
|
||||
|
||||
class Appender
|
||||
{
|
||||
public:
|
||||
Appender(uint8 _id, std::string const& name, LogLevel level = LOG_LEVEL_DISABLED, AppenderFlags flags = APPENDER_FLAGS_NONE);
|
||||
virtual ~Appender();
|
||||
|
||||
uint8 getId() const;
|
||||
std::string const& getName() const;
|
||||
virtual AppenderType getType() const = 0;
|
||||
LogLevel getLogLevel() const;
|
||||
AppenderFlags getFlags() const;
|
||||
|
||||
void setLogLevel(LogLevel);
|
||||
void write(LogMessage* message);
|
||||
static char const* getLogLevelString(LogLevel level);
|
||||
virtual void setRealmId(uint32 /*realmId*/) { }
|
||||
|
||||
private:
|
||||
virtual void _write(LogMessage const* /*message*/) = 0;
|
||||
|
||||
uint8 id;
|
||||
std::string name;
|
||||
LogLevel level;
|
||||
AppenderFlags flags;
|
||||
};
|
||||
|
||||
class InvalidAppenderArgsException : public std::length_error
|
||||
{
|
||||
public:
|
||||
explicit InvalidAppenderArgsException(std::string const& message) : std::length_error(message) { }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AppenderConsole.h"
|
||||
#include "LogMessage.h"
|
||||
#include "SmartEnum.h"
|
||||
#include "StringConvert.h"
|
||||
#include "StringFormat.h"
|
||||
#include "Tokenize.h"
|
||||
#include "Util.h"
|
||||
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
AppenderConsole::AppenderConsole(uint8 id, std::string const& name, LogLevel level, AppenderFlags flags, std::vector<std::string_view> const& args)
|
||||
: Appender(id, name, level, flags), _colored(false)
|
||||
{
|
||||
for (uint8 i = 0; i < NUM_ENABLED_LOG_LEVELS; ++i)
|
||||
{
|
||||
_colors[i] = ColorTypes(NUM_COLOR_TYPES);
|
||||
}
|
||||
|
||||
if (3 < args.size())
|
||||
{
|
||||
InitColors(name, args[3]);
|
||||
}
|
||||
}
|
||||
|
||||
void AppenderConsole::InitColors(std::string const& name, std::string_view str)
|
||||
{
|
||||
if (str.empty())
|
||||
{
|
||||
_colored = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> colorStrs = Acore::Tokenize(str, ' ', false);
|
||||
if (colorStrs.size() != NUM_ENABLED_LOG_LEVELS)
|
||||
{
|
||||
throw InvalidAppenderArgsException(Acore::StringFormat("Log::CreateAppenderFromConfig: Invalid color data '{}' for console appender {} (expected {} entries, got {})",
|
||||
str, name, NUM_ENABLED_LOG_LEVELS, colorStrs.size()));
|
||||
}
|
||||
|
||||
for (uint8 i = 0; i < NUM_ENABLED_LOG_LEVELS; ++i)
|
||||
{
|
||||
if (Optional<uint8> color = Acore::StringTo<uint8>(colorStrs[i]); color && EnumUtils::IsValid<ColorTypes>(*color))
|
||||
{
|
||||
_colors[i] = static_cast<ColorTypes>(*color);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw InvalidAppenderArgsException(Acore::StringFormat("Log::CreateAppenderFromConfig: Invalid color '{}' for log level {} on console appender {}",
|
||||
colorStrs[i], EnumUtils::ToTitle(static_cast<LogLevel>(i)), name));
|
||||
}
|
||||
}
|
||||
|
||||
_colored = true;
|
||||
}
|
||||
|
||||
void AppenderConsole::SetColor(bool stdout_stream, ColorTypes color)
|
||||
{
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
static WORD WinColorFG[NUM_COLOR_TYPES] =
|
||||
{
|
||||
0, // BLACK
|
||||
FOREGROUND_RED, // RED
|
||||
FOREGROUND_GREEN, // GREEN
|
||||
FOREGROUND_RED | FOREGROUND_GREEN, // BROWN
|
||||
FOREGROUND_BLUE, // BLUE
|
||||
FOREGROUND_RED | FOREGROUND_BLUE, // MAGENTA
|
||||
FOREGROUND_GREEN | FOREGROUND_BLUE, // CYAN
|
||||
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // WHITE
|
||||
// YELLOW
|
||||
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY,
|
||||
// RED_BOLD
|
||||
FOREGROUND_RED | FOREGROUND_INTENSITY,
|
||||
// GREEN_BOLD
|
||||
FOREGROUND_GREEN | FOREGROUND_INTENSITY,
|
||||
FOREGROUND_BLUE | FOREGROUND_INTENSITY, // BLUE_BOLD
|
||||
// MAGENTA_BOLD
|
||||
FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY,
|
||||
// CYAN_BOLD
|
||||
FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY,
|
||||
// WHITE_BOLD
|
||||
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY
|
||||
};
|
||||
|
||||
HANDLE hConsole = GetStdHandle(stdout_stream ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
|
||||
SetConsoleTextAttribute(hConsole, WinColorFG[color]);
|
||||
#else
|
||||
enum ANSITextAttr
|
||||
{
|
||||
TA_NORMAL = 0,
|
||||
TA_BOLD = 1,
|
||||
TA_BLINK = 5,
|
||||
TA_REVERSE = 7
|
||||
};
|
||||
|
||||
enum ANSIFgTextAttr
|
||||
{
|
||||
FG_BLACK = 30,
|
||||
FG_RED,
|
||||
FG_GREEN,
|
||||
FG_BROWN,
|
||||
FG_BLUE,
|
||||
FG_MAGENTA,
|
||||
FG_CYAN,
|
||||
FG_WHITE,
|
||||
FG_YELLOW
|
||||
};
|
||||
|
||||
enum ANSIBgTextAttr
|
||||
{
|
||||
BG_BLACK = 40,
|
||||
BG_RED,
|
||||
BG_GREEN,
|
||||
BG_BROWN,
|
||||
BG_BLUE,
|
||||
BG_MAGENTA,
|
||||
BG_CYAN,
|
||||
BG_WHITE
|
||||
};
|
||||
|
||||
static uint8 UnixColorFG[NUM_COLOR_TYPES] =
|
||||
{
|
||||
FG_BLACK, // BLACK
|
||||
FG_RED, // RED
|
||||
FG_GREEN, // GREEN
|
||||
FG_BROWN, // BROWN
|
||||
FG_BLUE, // BLUE
|
||||
FG_MAGENTA, // MAGENTA
|
||||
FG_CYAN, // CYAN
|
||||
FG_WHITE, // WHITE
|
||||
FG_YELLOW, // YELLOW
|
||||
FG_RED, // LRED
|
||||
FG_GREEN, // LGREEN
|
||||
FG_BLUE, // LBLUE
|
||||
FG_MAGENTA, // LMAGENTA
|
||||
FG_CYAN, // LCYAN
|
||||
FG_WHITE // LWHITE
|
||||
};
|
||||
|
||||
fprintf((stdout_stream ? stdout : stderr), "\x1b[%d%sm", UnixColorFG[color], (color >= YELLOW && color < NUM_COLOR_TYPES ? ";1" : ""));
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppenderConsole::ResetColor(bool stdout_stream)
|
||||
{
|
||||
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
|
||||
HANDLE hConsole = GetStdHandle(stdout_stream ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
|
||||
SetConsoleTextAttribute(hConsole, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
|
||||
#else
|
||||
fprintf((stdout_stream ? stdout : stderr), "\x1b[0m");
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppenderConsole::_write(LogMessage const* message)
|
||||
{
|
||||
bool stdout_stream = !(message->level == LOG_LEVEL_ERROR || message->level == LOG_LEVEL_FATAL);
|
||||
|
||||
if (_colored)
|
||||
{
|
||||
uint8 index;
|
||||
|
||||
switch (message->level)
|
||||
{
|
||||
case LOG_LEVEL_TRACE:
|
||||
index = 5;
|
||||
break;
|
||||
case LOG_LEVEL_DEBUG:
|
||||
index = 4;
|
||||
break;
|
||||
case LOG_LEVEL_INFO:
|
||||
index = 3;
|
||||
break;
|
||||
case LOG_LEVEL_WARN:
|
||||
index = 2;
|
||||
break;
|
||||
case LOG_LEVEL_FATAL:
|
||||
index = 0;
|
||||
break;
|
||||
case LOG_LEVEL_ERROR:
|
||||
[[fallthrough]];
|
||||
default:
|
||||
index = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
SetColor(stdout_stream, _colors[index]);
|
||||
utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str());
|
||||
ResetColor(stdout_stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user