mirror of
https://github.com/mehah/otclient
synced 2026-08-15 16:29:06 -04:00
perf: optimize spectator retrieval and add unit tests (gtest) (#1319)
Refactors the build system by introducing the static library otclient_core, separating core client code from platform-specific entry points. Centralizes compiler flags, precompiled headers, unity build, and sanitizer configuration to ensure consistent behavior across all platforms. Updates include paths and dependencies, ensuring that every platform links correctly against the new core target. Simplifies Discord RPC support by using a dedicated CMake option. Modernizes C++ code by making type-checking methods const and adding new silent state-update methods to Creature, along with a const getter for retrieving the previous position. Updates Lua bindings to expose overloaded const and non-const methods, preventing ambiguity correctly. Improves code hygiene in Map by reorganizing includes, adding a helper function to clean duplicated spectators, and optimizing spectator retrieval with memory reservations and more efficient data structures. Adds Google Test support and introduces a CMake option to enable or disable building unit tests.
This commit is contained in:
parent
d22a2f9b78
commit
294606e47c
21 changed files with 1395 additions and 235 deletions
|
|
@ -104,3 +104,13 @@ endif()
|
|||
add_subdirectory(src/protobuf)
|
||||
# Src
|
||||
add_subdirectory(src)
|
||||
|
||||
option(OTCLIENT_BUILD_TESTS "Build unit tests" ON)
|
||||
|
||||
if(OTCLIENT_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
log_option_enabled("Build tests")
|
||||
else()
|
||||
log_option_disabled("Build tests")
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -75,71 +75,12 @@ if (ANDROID OR WASM)
|
|||
set(FRAMEWORK_DEFINITIONS ${FRAMEWORK_DEFINITIONS} -DOPENGL_ES=2)
|
||||
endif()
|
||||
|
||||
# Set for use precompiled header
|
||||
if(TOGGLE_PRE_COMPILED_HEADER)
|
||||
# === PRECOMPILED HEADER ===
|
||||
target_precompile_headers(${PROJECT_NAME} PRIVATE framework/pch.h)
|
||||
message(STATUS "Use precompiled header: ON")
|
||||
else()
|
||||
message(STATUS "Use precompiled header: OFF")
|
||||
endif(TOGGLE_PRE_COMPILED_HEADER)
|
||||
|
||||
# === UNITY BUILD (compile time reducer) ===
|
||||
if(SPEED_UP_BUILD_UNITY)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
|
||||
log_option_enabled("Build unity for speed up compilation")
|
||||
endif(SPEED_UP_BUILD_UNITY)
|
||||
|
||||
set(VERSION "1.0.0")
|
||||
|
||||
# *****************************************************************************
|
||||
# Build flags
|
||||
# *****************************************************************************
|
||||
if (NOT MSVC)
|
||||
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated-declarations)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(THREADS_HAVE_PTHREAD_ARG)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC "-pthread")
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
add_definitions(-DNDEBUG)
|
||||
endif()
|
||||
|
||||
# *****************************************************************************
|
||||
# Definitions code
|
||||
# *****************************************************************************
|
||||
|
||||
|
||||
# === ASAN ===
|
||||
if(ASAN_ENABLED)
|
||||
log_option_enabled("asan")
|
||||
|
||||
if(MSVC)
|
||||
string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC /fsanitize=address)
|
||||
target_link_options(${PROJECT_NAME} PUBLIC /fsanitize=address)
|
||||
else()
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -fsanitize=address)
|
||||
target_link_options(${PROJECT_NAME} PUBLIC -fsanitize=address)
|
||||
endif()
|
||||
else()
|
||||
log_option_disabled("asan")
|
||||
endif()
|
||||
|
||||
|
||||
# === DEBUG LOG ===
|
||||
# cmake -DDEBUG_LOG=ON ..
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE -DDEBUG_LOG=ON )
|
||||
log_option_enabled("DEBUG LOG")
|
||||
else()
|
||||
log_option_disabled("DEBUG LOG")
|
||||
endif(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
|
||||
# *****************************************************************************
|
||||
# Sanity Check
|
||||
# *****************************************************************************
|
||||
|
|
@ -212,12 +153,12 @@ if(NOT OPENSSL_FOUND)
|
|||
find_package(GMP REQUIRED)
|
||||
endif()
|
||||
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||
find_package(DiscordRPC REQUIRED)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_DISCORD_RPC=1)
|
||||
log_option_enabled("Discord Rich Presence")
|
||||
find_package(DiscordRPC REQUIRED)
|
||||
set(OTCLIENT_ENABLE_DISCORD_RPC 1)
|
||||
log_option_enabled("Discord Rich Presence")
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_DISCORD_RPC=0)
|
||||
log_option_disabled("Discord Rich Presence")
|
||||
set(OTCLIENT_ENABLE_DISCORD_RPC 0)
|
||||
log_option_disabled("Discord Rich Presence")
|
||||
endif()
|
||||
if(TOGGLE_DIRECTX)
|
||||
find_package(DirectX REQUIRED)
|
||||
|
|
@ -368,9 +309,6 @@ set(SOURCE_FILES
|
|||
client/uiminimap.cpp
|
||||
client/uiprogressrect.cpp
|
||||
client/uisprite.cpp
|
||||
|
||||
main.cpp
|
||||
androidmain.cpp
|
||||
)
|
||||
|
||||
if (TOGGLE_FRAMEWORK_GRAPHICS)
|
||||
|
|
@ -449,9 +387,79 @@ if (WASM)
|
|||
)
|
||||
endif()
|
||||
|
||||
add_library(otclient_core STATIC ${SOURCE_FILES})
|
||||
if(NOT MSVC)
|
||||
target_link_options(otclient_core PUBLIC -flto=auto)
|
||||
endif()
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${SOURCE_FILES})
|
||||
target_link_options(${PROJECT_NAME} PUBLIC -flto=auto)
|
||||
if(TOGGLE_PRE_COMPILED_HEADER)
|
||||
# === PRECOMPILED HEADER ===
|
||||
target_precompile_headers(otclient_core PRIVATE framework/pch.h)
|
||||
message(STATUS "Use precompiled header: ON")
|
||||
else()
|
||||
message(STATUS "Use precompiled header: OFF")
|
||||
endif(TOGGLE_PRE_COMPILED_HEADER)
|
||||
|
||||
if(SPEED_UP_BUILD_UNITY)
|
||||
set_target_properties(otclient_core PROPERTIES UNITY_BUILD ON)
|
||||
log_option_enabled("Build unity for speed up compilation")
|
||||
endif(SPEED_UP_BUILD_UNITY)
|
||||
|
||||
if(ANDROID)
|
||||
target_sources(${PROJECT_NAME} PRIVATE androidmain.cpp)
|
||||
else()
|
||||
target_sources(${PROJECT_NAME} PRIVATE main.cpp)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE otclient_core)
|
||||
|
||||
# *****************************************************************************
|
||||
# Build flags
|
||||
# *****************************************************************************
|
||||
if (NOT MSVC)
|
||||
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||
target_compile_options(otclient_core PRIVATE -Wno-deprecated-declarations)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(THREADS_HAVE_PTHREAD_ARG)
|
||||
target_compile_options(otclient_core PUBLIC "-pthread")
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
add_definitions(-DNDEBUG)
|
||||
endif()
|
||||
|
||||
# *****************************************************************************
|
||||
# Definitions code
|
||||
# *****************************************************************************
|
||||
|
||||
# === ASAN ===
|
||||
if(ASAN_ENABLED)
|
||||
log_option_enabled("asan")
|
||||
|
||||
if(MSVC)
|
||||
string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
target_compile_options(otclient_core PUBLIC /fsanitize=address)
|
||||
target_link_options(${PROJECT_NAME} PUBLIC /fsanitize=address)
|
||||
else()
|
||||
target_compile_options(otclient_core PUBLIC -fsanitize=address)
|
||||
target_link_options(${PROJECT_NAME} PUBLIC -fsanitize=address)
|
||||
endif()
|
||||
else()
|
||||
log_option_disabled("asan")
|
||||
endif()
|
||||
|
||||
# === DEBUG LOG ===
|
||||
# cmake -DDEBUG_LOG=ON ..
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
target_compile_definitions(otclient_core PRIVATE -DDEBUG_LOG=ON )
|
||||
log_option_enabled("DEBUG LOG")
|
||||
else()
|
||||
log_option_disabled("DEBUG LOG")
|
||||
endif(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
|
||||
target_compile_definitions(otclient_core PRIVATE ENABLE_DISCORD_RPC=${OTCLIENT_ENABLE_DISCORD_RPC})
|
||||
|
||||
# *****************************************************************************
|
||||
# Includes and librarys
|
||||
|
|
@ -459,13 +467,13 @@ target_link_options(${PROJECT_NAME} PUBLIC -flto=auto)
|
|||
if(MSVC)
|
||||
|
||||
# Set variables to have Windows Vista Value so httplib will build 'inet_pton'
|
||||
target_compile_definitions(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_compile_definitions(otclient_core
|
||||
PUBLIC
|
||||
NTDDI_VERSION=0x06000000
|
||||
_WIN32_WINNT=0x0600
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC /MP /FS /Zf /EHsc /bigobj)
|
||||
target_compile_options(otclient_core PUBLIC /MP /FS /Zf /EHsc /bigobj)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
|
||||
|
|
@ -485,15 +493,24 @@ if(MSVC)
|
|||
if(ANDROID)
|
||||
find_package(Vorbis CONFIG REQUIRED)
|
||||
endif()
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
foreach(msvc_target IN ITEMS otclient_core ${PROJECT_NAME})
|
||||
if(TARGET ${msvc_target})
|
||||
set_property(TARGET ${msvc_target} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
endif()
|
||||
endforeach()
|
||||
set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "")
|
||||
else()
|
||||
log_option_disabled("STATIC_LIBRARY")
|
||||
set(VCPKG_TARGET_TRIPLET "x64-windows" CACHE STRING "")
|
||||
foreach(msvc_target IN ITEMS otclient_core ${PROJECT_NAME})
|
||||
if(TARGET ${msvc_target})
|
||||
set_property(TARGET ${msvc_target} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_include_directories(otclient_core
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
${LUAJIT_INCLUDE_DIR}
|
||||
|
|
@ -506,8 +523,8 @@ if(MSVC)
|
|||
${NLOHMANN_JSON_INCLUDE_DIR}
|
||||
${CPPCODEC_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_link_libraries(otclient_core
|
||||
PUBLIC
|
||||
${LUAJIT_LIBRARY}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${PHYSFS_LIBRARY}
|
||||
|
|
@ -536,8 +553,8 @@ if(MSVC)
|
|||
fmt::fmt-header-only
|
||||
)
|
||||
elseif(ANDROID)
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_include_directories(otclient_core
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
${LUAJIT_INCLUDE_DIR}
|
||||
|
|
@ -552,8 +569,8 @@ elseif(ANDROID)
|
|||
${MINIZIP_INCLUDE_DIR}
|
||||
${CPPCODEC_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_link_libraries(otclient_core
|
||||
PUBLIC
|
||||
${LUA_LIBRARY}
|
||||
${LUAJIT_LIBRARY}
|
||||
${PHYSFS_LIBRARY}
|
||||
|
|
@ -586,8 +603,8 @@ elseif(ANDROID)
|
|||
)
|
||||
|
||||
elseif(WASM)
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_include_directories(otclient_core
|
||||
PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${Protobuf_INCLUDE_DIRS}
|
||||
|
|
@ -600,8 +617,8 @@ elseif(WASM)
|
|||
${BROWSER_INCLUDE_DIR}
|
||||
${CPPCODEC_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_link_libraries(otclient_core
|
||||
PUBLIC
|
||||
${LUA_LIBRARY}
|
||||
${PHYSFS_LIBRARY}
|
||||
${ZLIB_LIBRARY}
|
||||
|
|
@ -644,7 +661,7 @@ elseif(WASM)
|
|||
get_property(linkflags TARGET ${PROJECT_NAME} PROPERTY LINK_FLAGS)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
target_compile_options(otclient_core
|
||||
PRIVATE
|
||||
-Wall -Wextra -Wpedantic
|
||||
)
|
||||
|
|
@ -670,8 +687,8 @@ elseif(WASM)
|
|||
set(VCPKG_TARGET_TRIPLET "wasm32-emscripten" CACHE STRING "")
|
||||
|
||||
else() # Linux
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_include_directories(otclient_core
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
${LUAJIT_INCLUDE_DIR}
|
||||
|
|
@ -685,8 +702,8 @@ else() # Linux
|
|||
${OPENSSL_INCLUDE_DIR}
|
||||
${CPPCODEC_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
target_link_libraries(otclient_core
|
||||
PUBLIC
|
||||
${LUAJIT_LIBRARY}
|
||||
${PHYSFS_LIBRARY}
|
||||
${ZLIB_LIBRARY}
|
||||
|
|
@ -725,7 +742,7 @@ else() # Linux
|
|||
)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
target_compile_options(otclient_core
|
||||
PRIVATE
|
||||
-Wall -Wextra -Wpedantic
|
||||
)
|
||||
|
|
@ -733,7 +750,7 @@ else() # Linux
|
|||
|
||||
endif()
|
||||
if(ENABLE_DISCORD_RPC AND NOT ANDROID)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${DISCORDRPC_LIBRARY})
|
||||
target_link_libraries(otclient_core PUBLIC ${DISCORDRPC_LIBRARY})
|
||||
endif()
|
||||
|
||||
# *****************************************************************************
|
||||
|
|
|
|||
|
|
@ -148,11 +148,13 @@ public:
|
|||
bool isWalking() { return m_walking; }
|
||||
|
||||
bool isRemoved() { return m_removed; }
|
||||
bool isRemoved() const { return m_removed; }
|
||||
const Position& getOldPosition() const { return m_oldPosition; }
|
||||
bool isInvisible() { return m_outfit.isEffect() && m_outfit.getAuxId() == 13; }
|
||||
bool isDead() { return m_healthPercent <= 0; }
|
||||
bool isFullHealth() { return m_healthPercent == 100; }
|
||||
bool canBeSeen() { return !isInvisible() || isPlayer(); }
|
||||
bool isCreature() override { return true; }
|
||||
bool isCreature() const override { return true; }
|
||||
bool isCovered() { return m_isCovered; }
|
||||
|
||||
void setCovered(bool covered);
|
||||
|
|
@ -206,6 +208,9 @@ protected:
|
|||
void updateWalkOffset(uint8_t totalPixelsWalked);
|
||||
void updateWalk();
|
||||
|
||||
void setOldPositionSilently(const Position& pos) { m_oldPosition = pos; }
|
||||
void setRemovedSilently(const bool removed) { m_removed = removed; }
|
||||
|
||||
ThingType* getThingType() const override;
|
||||
ThingType* getMountThingType() const;
|
||||
|
||||
|
|
@ -357,12 +362,12 @@ private:
|
|||
class Npc final : public Creature
|
||||
{
|
||||
public:
|
||||
bool isNpc() override { return true; }
|
||||
bool isNpc() const override { return true; }
|
||||
};
|
||||
|
||||
// @bindclass
|
||||
class Monster final : public Creature
|
||||
{
|
||||
public:
|
||||
bool isMonster() override { return true; }
|
||||
bool isMonster() const override { return true; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public:
|
|||
void setId(uint32_t id) override;
|
||||
void setPosition(const Position& position, uint8_t stackPos = 0) override;
|
||||
|
||||
bool isEffect() override { return true; }
|
||||
bool isEffect() const override { return true; }
|
||||
bool waitFor(const EffectPtr&);
|
||||
|
||||
EffectPtr asEffect() { return static_self_cast<Effect>(); }
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ public:
|
|||
|
||||
ItemPtr clone();
|
||||
ItemPtr asItem() { return static_self_cast<Item>(); }
|
||||
bool isItem() override { return true; }
|
||||
bool isItem() const override { return true; }
|
||||
|
||||
void updatePatterns();
|
||||
int calculateAnimationPhase();
|
||||
|
|
@ -148,7 +148,7 @@ public:
|
|||
|
||||
bool isHouseDoor() { return m_attribs.has(ATTR_HOUSEDOORID); }
|
||||
bool isDepot() { return m_attribs.has(ATTR_DEPOT_ID); }
|
||||
bool isContainer() override { return m_attribs.has(ATTR_CONTAINER_ITEMS) || Thing::isContainer(); }
|
||||
bool isContainer() const override { return m_attribs.has(ATTR_CONTAINER_ITEMS) || Thing::isContainer(); }
|
||||
bool isDoor() { return m_attribs.has(ATTR_HOUSEDOORID); }
|
||||
bool isTeleport() { return m_attribs.has(ATTR_TELE_DEST); }
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ public:
|
|||
bool isParalyzed() const { return (m_states & Otc::IconParalyze) == Otc::IconParalyze; }
|
||||
|
||||
LocalPlayerPtr asLocalPlayer() { return static_self_cast<LocalPlayer>(); }
|
||||
bool isLocalPlayer() override { return true; }
|
||||
bool isLocalPlayer() const override { return true; }
|
||||
|
||||
void onPositionChange(const Position& newPos, const Position& oldPos) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -611,7 +611,10 @@ void Client::registerLuaFunctions()
|
|||
g_lua.bindClassMemberFunction<Creature>("isWalking", &Creature::isWalking);
|
||||
g_lua.bindClassMemberFunction<Creature>("isInvisible", &Creature::isInvisible);
|
||||
g_lua.bindClassMemberFunction<Creature>("isDead", &Creature::isDead);
|
||||
g_lua.bindClassMemberFunction<Creature>("isRemoved", &Creature::isRemoved);
|
||||
g_lua.bindClassMemberFunction<Creature>(
|
||||
"isRemoved",
|
||||
static_cast<bool (Creature::*)() const>(&Creature::isRemoved)
|
||||
);
|
||||
g_lua.bindClassMemberFunction<Creature>("canBeSeen", &Creature::canBeSeen);
|
||||
g_lua.bindClassMemberFunction<Creature>("jump", &Creature::jump);
|
||||
g_lua.bindClassMemberFunction<Creature>("setMountShader", &Creature::setMountShader);
|
||||
|
|
@ -933,7 +936,10 @@ void Client::registerLuaFunctions()
|
|||
g_lua.bindClassMemberFunction<Tile>("isClickable", &Tile::isClickable);
|
||||
g_lua.bindClassMemberFunction<Tile>("isPathable", &Tile::isPathable);
|
||||
|
||||
g_lua.bindClassMemberFunction<Tile>("hasCreatures", &Tile::hasCreatures);
|
||||
g_lua.bindClassMemberFunction<Tile>(
|
||||
"hasCreatures",
|
||||
static_cast<bool (Tile::*)() const>(&Tile::hasCreatures)
|
||||
);
|
||||
|
||||
g_lua.bindClassMemberFunction<Tile>("select", &Tile::select);
|
||||
g_lua.bindClassMemberFunction<Tile>("unselect", &Tile::unselect);
|
||||
|
|
|
|||
|
|
@ -33,11 +33,27 @@
|
|||
#include "missile.h"
|
||||
#include "thing.h"
|
||||
#include "tile.h"
|
||||
#include "framework/core/asyncdispatcher.h"
|
||||
#include "framework/core/eventdispatcher.h"
|
||||
|
||||
#include <framework/core/asyncdispatcher.h>
|
||||
#include <framework/core/eventdispatcher.h>
|
||||
#include "framework/graphics/drawpoolmanager.h"
|
||||
#include "framework/graphics/painter.h"
|
||||
#include "framework/ui/uiwidget.h"
|
||||
#include <framework/ui/uiwidget.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
void cleanNewSpectators(std::vector<CreaturePtr>& creatures, std::unordered_set<uint32_t>& seenIds, const std::size_t startIndex)
|
||||
{
|
||||
auto it = creatures.begin() + startIndex;
|
||||
while (it != creatures.end()) {
|
||||
if (const auto& creature = *it; !creature || !seenIds.insert(creature->getId()).second) {
|
||||
it = creatures.erase(it);
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef FRAMEWORK_EDITOR
|
||||
#include "houses.h"
|
||||
|
|
@ -634,6 +650,10 @@ void Map::setLight(const Light& light)
|
|||
std::vector<CreaturePtr> Map::getSpectatorsInRangeEx(const Position& centerPos, const bool multiFloor, const int32_t minXRange, const int32_t maxXRange, const int32_t minYRange, const int32_t maxYRange)
|
||||
{
|
||||
std::vector<CreaturePtr> creatures;
|
||||
creatures.reserve(m_knownCreatures.size());
|
||||
std::unordered_set<uint32_t> seenIds;
|
||||
seenIds.reserve(m_knownCreatures.size());
|
||||
|
||||
uint8_t minZRange = 0;
|
||||
uint8_t maxZRange = 0;
|
||||
|
||||
|
|
@ -642,19 +662,31 @@ std::vector<CreaturePtr> Map::getSpectatorsInRangeEx(const Position& centerPos,
|
|||
maxZRange = getLastAwareFloor() - centerPos.z;
|
||||
}
|
||||
|
||||
//TODO: optimize
|
||||
//TODO: delivery creatures in distance order
|
||||
for (int iz = -minZRange; iz <= maxZRange; ++iz) {
|
||||
for (int iy = -minYRange; iy <= maxYRange; ++iy) {
|
||||
for (int ix = -minXRange; ix <= maxXRange; ++ix) {
|
||||
if (const auto& tile = getTile(centerPos.translated(ix, iy, iz))) {
|
||||
const auto& tileCreatures = tile->getCreatures();
|
||||
creatures.insert(creatures.end(), tileCreatures.rbegin(), tileCreatures.rend());
|
||||
const int startZ = centerPos.z - minZRange;
|
||||
const int endZ = centerPos.z + maxZRange;
|
||||
const int startY = centerPos.y - minYRange;
|
||||
const int endY = centerPos.y + maxYRange;
|
||||
const int startX = centerPos.x - minXRange;
|
||||
const int endX = centerPos.x + maxXRange;
|
||||
|
||||
const auto appendSpectatorsFromLayer = [&](const int z) {
|
||||
for (int y = startY; y <= endY; ++y) {
|
||||
for (int x = startX; x <= endX; ++x) {
|
||||
const auto tile = getTile(Position(x, y, z));
|
||||
if (!tile || !tile->hasCreatures()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto sizeBeforeAppend = creatures.size();
|
||||
tile->appendSpectators(creatures);
|
||||
cleanNewSpectators(creatures, seenIds, sizeBeforeAppend);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (int z = startZ; z <= endZ; ++z) {
|
||||
appendSpectatorsFromLayer(z);
|
||||
}
|
||||
return creatures;
|
||||
}
|
||||
|
||||
|
|
@ -1471,15 +1503,24 @@ std::vector<CreaturePtr> Map::getSpectatorsByPattern(const Position& centerPos,
|
|||
}
|
||||
|
||||
p = 0;
|
||||
std::unordered_set<uint32_t> seenIds;
|
||||
seenIds.reserve(m_knownCreatures.size());
|
||||
for (int y = centerPos.y - height / 2, endy = centerPos.y + height / 2; y <= endy; ++y) {
|
||||
for (int x = centerPos.x - width / 2, endx = centerPos.x + width / 2; x <= endx; ++x) {
|
||||
if (!finalPattern[p++])
|
||||
const auto enabled = finalPattern[p];
|
||||
++p;
|
||||
if (!enabled) {
|
||||
continue;
|
||||
TilePtr tile = getTile(Position(x, y, centerPos.z));
|
||||
if (!tile)
|
||||
}
|
||||
|
||||
const auto tile = getTile(Position(x, y, centerPos.z));
|
||||
if (!tile || !tile->hasCreatures()) {
|
||||
continue;
|
||||
auto tileCreatures = tile->getCreatures();
|
||||
creatures.insert(creatures.end(), tileCreatures.rbegin(), tileCreatures.rend());
|
||||
}
|
||||
|
||||
const auto sizeBeforeAppend = creatures.size();
|
||||
tile->appendSpectators(creatures);
|
||||
cleanNewSpectators(creatures, seenIds, sizeBeforeAppend);
|
||||
}
|
||||
}
|
||||
return creatures;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public:
|
|||
void setId(uint32_t id) override;
|
||||
void setPath(const Position& fromPosition, const Position& toPosition);
|
||||
|
||||
bool isMissile() override { return true; }
|
||||
bool isMissile() const override { return true; }
|
||||
|
||||
MissilePtr asMissile() { return static_self_cast<Missile>(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public:
|
|||
~Player() override = default;
|
||||
|
||||
PlayerPtr asPlayer() { return static_self_cast<Player>(); }
|
||||
bool isPlayer() override { return true; }
|
||||
bool isPlayer() const override { return true; }
|
||||
bool isMage() const;
|
||||
void setVocation(uint8_t vocation);
|
||||
uint8_t getVocation() { return m_vocation; }
|
||||
|
|
|
|||
|
|
@ -114,107 +114,447 @@ void Thing::setAttachedEffectDirection(const Otc::Direction dir) const
|
|||
}
|
||||
}
|
||||
|
||||
Animator* Thing::getAnimator() const { return getThingType()->getAnimator(); }
|
||||
Animator* Thing::getIdleAnimator() const { return getThingType()->getIdleAnimator(); }
|
||||
|
||||
Point Thing::getDisplacement() const { return getThingType()->getDisplacement(); }
|
||||
int Thing::getDisplacementX() const { return getThingType()->getDisplacementX(); }
|
||||
int Thing::getDisplacementY() const { return getThingType()->getDisplacementY(); }
|
||||
int Thing::getExactSize(const int layer, const int xPattern, const int yPattern, const int zPattern, const int animationPhase) {
|
||||
return getThingType()->getExactSize(layer, xPattern, yPattern, zPattern, animationPhase);
|
||||
Animator* Thing::getAnimator() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getAnimator();
|
||||
return nullptr;
|
||||
}
|
||||
Animator* Thing::getIdleAnimator() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getIdleAnimator();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Light& Thing::getLight() const { return getThingType()->getLight(); }
|
||||
bool Thing::hasLight() const { return getThingType()->hasLight(); }
|
||||
Point Thing::getDisplacement() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getDisplacement();
|
||||
return Point();
|
||||
}
|
||||
int Thing::getDisplacementX() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getDisplacementX();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getDisplacementY() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getDisplacementY();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getExactSize(const int layer, const int xPattern, const int yPattern, const int zPattern, const int animationPhase) {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getExactSize(layer, xPattern, yPattern, zPattern, animationPhase);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const MarketData& Thing::getMarketData() { return getThingType()->getMarketData(); }
|
||||
const std::vector<NPCData>& Thing::getNpcSaleData() { return getThingType()->getNpcSaleData(); }
|
||||
int Thing::getMeanPrice() { return getThingType()->getMeanPrice(); }
|
||||
const Size& Thing::getSize() const { return getThingType()->getSize(); }
|
||||
const Light& Thing::getLight() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getLight();
|
||||
static const Light kEmptyLight;
|
||||
return kEmptyLight;
|
||||
}
|
||||
bool Thing::hasLight() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasLight();
|
||||
return false;
|
||||
}
|
||||
|
||||
int Thing::getWidth() const { return getThingType()->getWidth(); }
|
||||
int Thing::getHeight() const { return getThingType()->getHeight(); }
|
||||
int Thing::getRealSize() const { return getThingType()->getRealSize(); }
|
||||
int Thing::getLayers() const { return getThingType()->getLayers(); }
|
||||
int Thing::getNumPatternX() const { return getThingType()->getNumPatternX(); }
|
||||
int Thing::getNumPatternY() const { return getThingType()->getNumPatternY(); }
|
||||
int Thing::getNumPatternZ() const { return getThingType()->getNumPatternZ(); }
|
||||
int Thing::getAnimationPhases() const { return getThingType()->getAnimationPhases(); }
|
||||
int Thing::getGroundSpeed() const { return getThingType()->getGroundSpeed(); }
|
||||
int Thing::getMaxTextLength() const { return getThingType()->getMaxTextLength(); }
|
||||
int Thing::getMinimapColor() const { return getThingType()->getMinimapColor(); }
|
||||
int Thing::getLensHelp() const { return getThingType()->getLensHelp(); }
|
||||
int Thing::getElevation() const { return getThingType()->getElevation(); }
|
||||
const MarketData& Thing::getMarketData() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getMarketData();
|
||||
static const MarketData kEmptyMarketData{};
|
||||
return kEmptyMarketData;
|
||||
}
|
||||
const std::vector<NPCData>& Thing::getNpcSaleData() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getNpcSaleData();
|
||||
static const std::vector<NPCData> kEmptyNpcData;
|
||||
return kEmptyNpcData;
|
||||
}
|
||||
int Thing::getMeanPrice() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getMeanPrice();
|
||||
return 0;
|
||||
}
|
||||
const Size& Thing::getSize() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getSize();
|
||||
static const Size kEmptySize;
|
||||
return kEmptySize;
|
||||
}
|
||||
|
||||
int Thing::getClothSlot() { return getThingType()->getClothSlot(); }
|
||||
int Thing::getWidth() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getWidth();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getHeight() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getHeight();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getRealSize() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getRealSize();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getLayers() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getLayers();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getNumPatternX() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getNumPatternX();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getNumPatternY() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getNumPatternY();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getNumPatternZ() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getNumPatternZ();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getAnimationPhases() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getAnimationPhases();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getGroundSpeed() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getGroundSpeed();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getMaxTextLength() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getMaxTextLength();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getMinimapColor() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getMinimapColor();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getLensHelp() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getLensHelp();
|
||||
return 0;
|
||||
}
|
||||
int Thing::getElevation() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getElevation();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Thing::blockProjectile() const { return getThingType()->blockProjectile(); }
|
||||
int Thing::getClothSlot() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getClothSlot();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Thing::isContainer() { return getThingType()->isContainer(); }
|
||||
bool Thing::blockProjectile() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->blockProjectile();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Thing::isTopGround() { return !isCreature() && getThingType()->isTopGround(); }
|
||||
bool Thing::isTopGroundBorder() { return !isCreature() && getThingType()->isTopGroundBorder(); }
|
||||
bool Thing::isSingleGround() { return !isCreature() && getThingType()->isSingleGround(); }
|
||||
bool Thing::isSingleGroundBorder() { return !isCreature() && getThingType()->isSingleGroundBorder(); }
|
||||
bool Thing::isGround() { return !isCreature() && getThingType()->isGround(); }
|
||||
bool Thing::isGroundBorder() { return !isCreature() && getThingType()->isGroundBorder(); }
|
||||
bool Thing::isOnBottom() { return !isCreature() && getThingType()->isOnBottom(); }
|
||||
bool Thing::isOnTop() { return !isCreature() && getThingType()->isOnTop(); }
|
||||
bool Thing::isContainer() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isContainer();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Thing::isMarketable() { return getThingType()->isMarketable(); }
|
||||
bool Thing::isStackable() { return getThingType()->isStackable(); }
|
||||
bool Thing::isFluidContainer() { return getThingType()->isFluidContainer(); }
|
||||
bool Thing::isForceUse() { return getThingType()->isForceUse(); }
|
||||
bool Thing::isMultiUse() { return getThingType()->isMultiUse(); }
|
||||
bool Thing::isWritable() { return getThingType()->isWritable(); }
|
||||
bool Thing::isChargeable() { return getThingType()->isChargeable(); }
|
||||
bool Thing::isWritableOnce() { return getThingType()->isWritableOnce(); }
|
||||
bool Thing::isSplash() { return getThingType()->isSplash(); }
|
||||
bool Thing::isNotWalkable() { return getThingType()->isNotWalkable(); }
|
||||
bool Thing::isNotMoveable() { return getThingType()->isNotMoveable(); }
|
||||
bool Thing::isMoveable() { return !getThingType()->isNotMoveable(); }
|
||||
bool Thing::isNotPathable() { return getThingType()->isNotPathable(); }
|
||||
bool Thing::isPickupable() { return getThingType()->isPickupable(); }
|
||||
bool Thing::isHangable() { return getThingType()->isHangable(); }
|
||||
bool Thing::isHookSouth() { return getThingType()->isHookSouth(); }
|
||||
bool Thing::isHookEast() { return getThingType()->isHookEast(); }
|
||||
bool Thing::isRotateable() { return getThingType()->isRotateable(); }
|
||||
bool Thing::isDontHide() { return getThingType()->isDontHide(); }
|
||||
bool Thing::isTranslucent() { return getThingType()->isTranslucent(); }
|
||||
bool Thing::isLyingCorpse() { return getThingType()->isLyingCorpse(); }
|
||||
bool Thing::isAnimateAlways() { return getThingType()->isAnimateAlways(); }
|
||||
bool Thing::isFullGround() { return getThingType()->isFullGround(); }
|
||||
bool Thing::isIgnoreLook() { return getThingType()->isIgnoreLook(); }
|
||||
bool Thing::isCloth() { return getThingType()->isCloth(); }
|
||||
bool Thing::isUsable() { return getThingType()->isUsable(); }
|
||||
bool Thing::isWrapable() { return getThingType()->isWrapable(); }
|
||||
bool Thing::isUnwrapable() { return getThingType()->isUnwrapable(); }
|
||||
bool Thing::isTopEffect() { return getThingType()->isTopEffect(); }
|
||||
bool Thing::isPodium() const { return getThingType()->isPodium(); }
|
||||
bool Thing::isOpaque() const { return getThingType()->isOpaque(); }
|
||||
bool Thing::isLoading() const { return getThingType()->isLoading(); }
|
||||
bool Thing::isSingleDimension() const { return getThingType()->isSingleDimension(); }
|
||||
bool Thing::isTall(const bool useRealSize) const { return getThingType()->isTall(useRealSize); }
|
||||
bool Thing::isTopGround() {
|
||||
if (const auto t = getThingType(); !isCreature() && t)
|
||||
return t->isTopGround();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isTopGroundBorder() {
|
||||
if (const auto t = getThingType(); !isCreature() && t)
|
||||
return t->isTopGroundBorder();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isSingleGround() {
|
||||
if (const auto t = getThingType(); !isCreature() && t)
|
||||
return t->isSingleGround();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isSingleGroundBorder() {
|
||||
if (const auto t = getThingType(); !isCreature() && t)
|
||||
return t->isSingleGroundBorder();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isGround() {
|
||||
if (const auto t = getThingType(); !isCreature() && t)
|
||||
return t->isGround();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isGroundBorder() {
|
||||
if (const auto t = getThingType(); !isCreature() && t)
|
||||
return t->isGroundBorder();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isOnBottom() {
|
||||
if (const auto t = getThingType(); !isCreature() && t)
|
||||
return t->isOnBottom();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isOnTop() {
|
||||
if (const auto t = getThingType(); !isCreature() && t)
|
||||
return t->isOnTop();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Thing::hasMiniMapColor() const { return getThingType()->hasMiniMapColor(); }
|
||||
bool Thing::hasLensHelp() const { return getThingType()->hasLensHelp(); }
|
||||
bool Thing::hasDisplacement() const { return getThingType()->hasDisplacement(); }
|
||||
bool Thing::hasElevation() const { return getThingType()->hasElevation(); }
|
||||
bool Thing::hasAction() const { return getThingType()->hasAction(); }
|
||||
bool Thing::hasWearOut() const { return getThingType()->hasWearOut(); }
|
||||
bool Thing::hasClockExpire() const { return getThingType()->hasClockExpire(); }
|
||||
bool Thing::hasExpire() const { return getThingType()->hasExpire(); }
|
||||
bool Thing::hasExpireStop() const { return getThingType()->hasExpireStop(); }
|
||||
bool Thing::hasAnimationPhases() const { return getThingType()->getAnimationPhases() > 1; }
|
||||
bool Thing::isDecoKit() const { return getThingType()->isDecoKit(); }
|
||||
bool Thing::isAmmo() { return getThingType()->isAmmo(); }
|
||||
bool Thing::isMarketable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isMarketable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isStackable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isStackable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isFluidContainer() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isFluidContainer();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isForceUse() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isForceUse();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isMultiUse() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isMultiUse();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isWritable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isWritable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isChargeable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isChargeable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isWritableOnce() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isWritableOnce();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isSplash() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isSplash();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isNotWalkable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isNotWalkable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isNotMoveable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isNotMoveable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isMoveable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return !t->isNotMoveable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isNotPathable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isNotPathable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isPickupable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isPickupable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isHangable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isHangable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isHookSouth() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isHookSouth();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isHookEast() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isHookEast();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isRotateable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isRotateable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isDontHide() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isDontHide();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isTranslucent() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isTranslucent();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isLyingCorpse() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isLyingCorpse();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isAnimateAlways() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isAnimateAlways();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isFullGround() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isFullGround();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isIgnoreLook() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isIgnoreLook();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isCloth() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isCloth();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isUsable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isUsable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isWrapable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isWrapable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isUnwrapable() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isUnwrapable();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isTopEffect() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isTopEffect();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isPodium() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isPodium();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isOpaque() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isOpaque();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isLoading() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isLoading();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isSingleDimension() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isSingleDimension();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isTall(const bool useRealSize) const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isTall(useRealSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
PLAYER_ACTION Thing::getDefaultAction() { return getThingType()->getDefaultAction(); }
|
||||
bool Thing::hasMiniMapColor() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasMiniMapColor();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasLensHelp() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasLensHelp();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasDisplacement() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasDisplacement();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasElevation() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasElevation();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasAction() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasAction();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasWearOut() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasWearOut();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasClockExpire() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasClockExpire();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasExpire() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasExpire();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasExpireStop() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->hasExpireStop();
|
||||
return false;
|
||||
}
|
||||
bool Thing::hasAnimationPhases() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getAnimationPhases() > 1;
|
||||
return false;
|
||||
}
|
||||
bool Thing::isDecoKit() const {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isDecoKit();
|
||||
return false;
|
||||
}
|
||||
bool Thing::isAmmo() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->isAmmo();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t Thing::getClassification() { return getThingType()->getClassification(); }
|
||||
PLAYER_ACTION Thing::getDefaultAction() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getDefaultAction();
|
||||
return static_cast<PLAYER_ACTION>(0);
|
||||
}
|
||||
|
||||
uint16_t Thing::getClassification() {
|
||||
if (const auto t = getThingType(); t)
|
||||
return t->getClassification();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Thing::canDraw(const Color& color) const {
|
||||
return m_canDraw && m_clientId > 0 && color.aF() > Fw::MIN_ALPHA && getThingType() && getThingType()->getOpacity() > Fw::MIN_ALPHA;
|
||||
if (const auto t = getThingType(); t)
|
||||
return m_canDraw && m_clientId > 0 && color.aF() > Fw::MIN_ALPHA && t->getOpacity() > Fw::MIN_ALPHA;
|
||||
return false;
|
||||
}
|
||||
|
||||
const Color& Thing::getMarkedColor() {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
#include "staticdata.h"
|
||||
#include <framework/core/timer.h>
|
||||
|
||||
// @bindclass
|
||||
// @bindclass
|
||||
#pragma pack(push,1) // disable memory alignment
|
||||
class Thing : public AttachableObject
|
||||
{
|
||||
|
|
@ -52,15 +52,15 @@ public:
|
|||
int getStackPos();
|
||||
int getStackPriority();
|
||||
|
||||
virtual bool isItem() { return false; }
|
||||
virtual bool isEffect() { return false; }
|
||||
virtual bool isMissile() { return false; }
|
||||
virtual bool isCreature() { return false; }
|
||||
virtual bool isItem() const { return false; }
|
||||
virtual bool isEffect() const { return false; }
|
||||
virtual bool isMissile() const { return false; }
|
||||
virtual bool isCreature() const { return false; }
|
||||
|
||||
virtual bool isNpc() { return false; }
|
||||
virtual bool isMonster() { return false; }
|
||||
virtual bool isPlayer() { return false; }
|
||||
virtual bool isLocalPlayer() { return false; }
|
||||
virtual bool isNpc() const { return false; }
|
||||
virtual bool isMonster() const { return false; }
|
||||
virtual bool isPlayer() const { return false; }
|
||||
virtual bool isLocalPlayer() const { return false; }
|
||||
|
||||
bool isCommon() { return !isGround() && !isGroundBorder() && !isOnTop() && !isCreature() && !isOnBottom(); }
|
||||
void canDraw(const bool canDraw) { m_canDraw = canDraw; }
|
||||
|
|
|
|||
|
|
@ -209,6 +209,9 @@ void Tile::clean()
|
|||
|
||||
m_thingTypeFlag = 0;
|
||||
|
||||
m_firstCreatureIndex = -1;
|
||||
m_lastCreatureIndex = -1;
|
||||
|
||||
#ifdef FRAMEWORK_EDITOR
|
||||
m_flags = 0;
|
||||
#endif
|
||||
|
|
@ -313,6 +316,8 @@ void Tile::addThing(const ThingPtr& thing, int stackPos)
|
|||
|
||||
m_things.insert(m_things.begin() + stackPos, thing);
|
||||
|
||||
updateCreatureRangeForInsert(static_cast<int16_t>(stackPos), thing);
|
||||
|
||||
setThingFlag(thing);
|
||||
|
||||
if (size > g_gameConfig.getTileMaxThings())
|
||||
|
|
@ -385,16 +390,74 @@ ThingPtr Tile::getThing(const int stackPos)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void Tile::updateCreatureRangeForInsert(const int16_t stackPos, const ThingPtr& thing)
|
||||
{
|
||||
if (m_firstCreatureIndex != -1 && stackPos <= m_firstCreatureIndex) {
|
||||
++m_firstCreatureIndex;
|
||||
}
|
||||
|
||||
if (m_lastCreatureIndex != -1 && stackPos <= m_lastCreatureIndex) {
|
||||
++m_lastCreatureIndex;
|
||||
}
|
||||
|
||||
if (!thing->isCreature()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_firstCreatureIndex == -1 || stackPos < m_firstCreatureIndex) {
|
||||
m_firstCreatureIndex = stackPos;
|
||||
}
|
||||
|
||||
if (stackPos > m_lastCreatureIndex) {
|
||||
m_lastCreatureIndex = stackPos;
|
||||
}
|
||||
}
|
||||
|
||||
void Tile::rebuildCreatureRange()
|
||||
{
|
||||
m_firstCreatureIndex = -1;
|
||||
m_lastCreatureIndex = -1;
|
||||
|
||||
const auto count = static_cast<int32_t>(m_things.size());
|
||||
for (int32_t i = 0; i < count; ++i) {
|
||||
if (!m_things[i]->isCreature()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_firstCreatureIndex == -1) {
|
||||
m_firstCreatureIndex = static_cast<int16_t>(i);
|
||||
}
|
||||
|
||||
m_lastCreatureIndex = static_cast<int16_t>(i);
|
||||
}
|
||||
}
|
||||
|
||||
void Tile::appendSpectators(std::vector<CreaturePtr>& out) const
|
||||
{
|
||||
if (!hasCreatures() || m_lastCreatureIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto size = static_cast<int32_t>(m_things.size());
|
||||
const auto beginOffset = size - 1 - static_cast<int32_t>(m_lastCreatureIndex);
|
||||
const auto endOffset = size - static_cast<int32_t>(m_firstCreatureIndex);
|
||||
|
||||
auto it = m_things.rbegin() + beginOffset;
|
||||
const auto end = m_things.rbegin() + endOffset;
|
||||
|
||||
for (; it != end; ++it) {
|
||||
const auto& thing = *it;
|
||||
if (thing->isCreature()) {
|
||||
out.emplace_back(thing->static_self_cast<Creature>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CreaturePtr> Tile::getCreatures()
|
||||
{
|
||||
std::vector<CreaturePtr> creatures;
|
||||
if (hasCreatures()) {
|
||||
for (const auto& thing : m_things) {
|
||||
if (thing->isCreature())
|
||||
creatures.emplace_back(thing->static_self_cast<Creature>());
|
||||
}
|
||||
}
|
||||
|
||||
appendSpectators(creatures);
|
||||
std::ranges::reverse(creatures);
|
||||
return creatures;
|
||||
}
|
||||
|
||||
|
|
@ -427,8 +490,9 @@ std::vector<ItemPtr> Tile::getItems()
|
|||
{
|
||||
std::vector<ItemPtr> items;
|
||||
for (const auto& thing : m_things) {
|
||||
if (!thing->isItem())
|
||||
if (!thing->isItem()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
items.emplace_back(thing->static_self_cast<Item>());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,10 @@ public:
|
|||
bool hasGround();
|
||||
bool hasTopGround(const bool ignoreBorder = false);
|
||||
|
||||
bool hasCreatures() { return m_thingTypeFlag & HAS_CREATURE; }
|
||||
bool hasCreatures() const { return (m_thingTypeFlag & HAS_CREATURE) != 0; }
|
||||
bool hasCreatures() { return static_cast<const Tile&>(*this).hasCreatures(); }
|
||||
|
||||
void appendSpectators(std::vector<CreaturePtr>& out) const;
|
||||
|
||||
bool hasTopItem() const { return m_thingTypeFlag & HAS_TOP_ITEM; }
|
||||
bool hasCommonItem() const { return m_thingTypeFlag & HAS_COMMON_ITEM; }
|
||||
|
|
@ -160,11 +163,15 @@ private:
|
|||
void drawTop(const Point& dest, int flags, bool forceDraw, uint8_t drawElevation);
|
||||
void drawCreature(const Point& dest, int flags, bool forceDraw, uint8_t drawElevation, LightView* lightView = nullptr);
|
||||
|
||||
void updateCreatureRangeForInsert(int16_t stackPos, const ThingPtr& thing);
|
||||
void rebuildCreatureRange();
|
||||
|
||||
void setThingFlag(const ThingPtr& thing);
|
||||
|
||||
void recalculateThingFlag()
|
||||
{
|
||||
m_thingTypeFlag = 0;
|
||||
rebuildCreatureRange();
|
||||
for (const auto& thing : m_things)
|
||||
setThingFlag(thing);
|
||||
}
|
||||
|
|
@ -199,6 +206,9 @@ private:
|
|||
uint8_t m_minimapColor{ 0 };
|
||||
uint8_t m_elevation{ 0 };
|
||||
|
||||
int16_t m_firstCreatureIndex{ -1 };
|
||||
int16_t m_lastCreatureIndex{ -1 };
|
||||
|
||||
int8_t m_highlightThingStackPos = -1;
|
||||
|
||||
TileSelectType m_selectType{ TileSelectType::NONE };
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "stdext/compiler.h"
|
||||
|
|
@ -31,6 +29,8 @@ using namespace std::literals;
|
|||
// common C/C++ headers
|
||||
#include "pch.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
// global constants
|
||||
#include "const.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include <framework/stdext/traits.h>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
/// This namespace contains some dirty metaprogamming that uses a lot of C++0x features
|
||||
/// The purpose here is to create templates that can bind any function from C++
|
||||
|
|
@ -37,6 +38,18 @@
|
|||
/// pushes the result to lua.
|
||||
namespace luabinder
|
||||
{
|
||||
template<typename Ret>
|
||||
Ret make_default_return_value()
|
||||
{
|
||||
if constexpr (std::is_reference_v<Ret>) {
|
||||
using ValueType = std::remove_cv_t<std::remove_reference_t<Ret>>;
|
||||
static ValueType value{};
|
||||
return value;
|
||||
} else {
|
||||
return std::decay_t<Ret>{};
|
||||
}
|
||||
}
|
||||
|
||||
/// Pack arguments from lua stack into a tuple recursively
|
||||
template<int N>
|
||||
struct pack_values_into_tuple
|
||||
|
|
@ -169,8 +182,10 @@ namespace luabinder
|
|||
{
|
||||
auto mf = std::mem_fn(f);
|
||||
return [=](const std::shared_ptr<C>& obj, const Args&... args) mutable -> Ret {
|
||||
if (!obj)
|
||||
throw LuaException("failed to call a member function because the passed object is nil");
|
||||
if (!obj) {
|
||||
g_logger.warning("Lua warning: member function call skipped because the passed object is nil");
|
||||
return make_default_return_value<Ret>();
|
||||
}
|
||||
return mf(obj.get(), args...);
|
||||
};
|
||||
}
|
||||
|
|
@ -179,8 +194,10 @@ namespace luabinder
|
|||
{
|
||||
auto mf = std::mem_fn(f);
|
||||
return [=](const std::shared_ptr<C>& obj, const Args&... args) mutable {
|
||||
if (!obj)
|
||||
throw LuaException("failed to call a member function because the passed object is nil");
|
||||
if (!obj) {
|
||||
g_logger.warning("Lua warning: member function call skipped because the passed object is nil");
|
||||
return;
|
||||
}
|
||||
mf(obj.get(), args...);
|
||||
};
|
||||
}
|
||||
|
|
@ -233,4 +250,83 @@ namespace luabinder
|
|||
return mf(obj, lua);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Ret, typename C, typename... Args>
|
||||
std::function<Ret(const std::shared_ptr<C>&, const Args&...)>
|
||||
make_mem_func(Ret (C::*f)(Args...) const)
|
||||
{
|
||||
auto mf = std::mem_fn(f);
|
||||
return [=](const std::shared_ptr<C>& obj, const Args&... args) mutable -> Ret {
|
||||
if (!obj) {
|
||||
g_logger.warning("Lua warning: member function call skipped because the passed object is nil");
|
||||
if constexpr (std::is_void_v<Ret>) {
|
||||
return;
|
||||
} else {
|
||||
return make_default_return_value<Ret>();
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_void_v<Ret>) {
|
||||
mf(obj.get(), args...);
|
||||
return;
|
||||
}
|
||||
return mf(obj.get(), args...);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename C, typename... Args>
|
||||
std::function<void(const std::shared_ptr<C>&, const Args&...)>
|
||||
make_mem_func(void (C::*f)(Args...) const)
|
||||
{
|
||||
auto mf = std::mem_fn(f);
|
||||
return [=](const std::shared_ptr<C>& obj, const Args&... args) mutable {
|
||||
if (!obj) {
|
||||
g_logger.warning("Lua warning: member function call skipped because the passed object is nil");
|
||||
return;
|
||||
}
|
||||
mf(obj.get(), args...);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename C, typename Ret, class FC, typename... Args>
|
||||
LuaCppFunction bind_mem_fun(Ret (FC::*f)(Args...) const)
|
||||
{
|
||||
using Tuple = std::tuple<std::shared_ptr<FC>, typename stdext::remove_const_ref<Args>::type...>;
|
||||
auto lambda = make_mem_func<Ret, FC>(f);
|
||||
return bind_fun_specializer<typename stdext::remove_const_ref<Ret>::type,
|
||||
decltype(lambda),
|
||||
Tuple>(lambda);
|
||||
}
|
||||
|
||||
template<typename Ret, typename C, typename... Args>
|
||||
std::function<Ret(const Args&...)>
|
||||
make_mem_func_singleton(Ret (C::*f)(Args...) const, C* instance)
|
||||
{
|
||||
auto mf = std::mem_fn(f);
|
||||
return [=](Args... args) mutable -> Ret {
|
||||
if constexpr (std::is_void_v<Ret>) {
|
||||
mf(instance, args...);
|
||||
return;
|
||||
}
|
||||
return mf(instance, args...);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename C, typename... Args>
|
||||
std::function<void(const Args&...)>
|
||||
make_mem_func_singleton(void (C::*f)(Args...) const, C* instance)
|
||||
{
|
||||
auto mf = std::mem_fn(f);
|
||||
return [=](Args... args) mutable { mf(instance, args...); };
|
||||
}
|
||||
|
||||
template<typename C, typename Ret, class FC, typename... Args>
|
||||
LuaCppFunction bind_singleton_mem_fun(Ret (FC::*f)(Args...) const, C* instance)
|
||||
{
|
||||
using Tuple = std::tuple<typename stdext::remove_const_ref<Args>::type...>;
|
||||
assert(instance);
|
||||
auto lambda = make_mem_func_singleton<Ret, FC>(f, static_cast<FC*>(instance));
|
||||
return bind_fun_specializer<typename stdext::remove_const_ref<Ret>::type,
|
||||
decltype(lambda),
|
||||
Tuple>(lambda);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,19 +92,23 @@ public:
|
|||
template<class C, class B = LuaObject>
|
||||
void registerClass()
|
||||
{
|
||||
registerClass(stdext::demangle_class<C>(), stdext::demangle_class<B>());
|
||||
const std::string className = stdext::demangle_class<C>();
|
||||
const std::string baseClassName = stdext::demangle_class<B>();
|
||||
registerClass(className, baseClassName);
|
||||
}
|
||||
|
||||
template<class C>
|
||||
void registerClassStaticFunction(const std::string_view functionName, const LuaCppFunction& function)
|
||||
{
|
||||
registerClassStaticFunction(stdext::demangle_class<C>(), functionName, function);
|
||||
const std::string className = stdext::demangle_class<C>();
|
||||
registerClassStaticFunction(className, functionName, function);
|
||||
}
|
||||
|
||||
template<class C>
|
||||
void registerClassMemberFunction(const std::string_view functionName, const LuaCppFunction& function)
|
||||
{
|
||||
registerClassMemberFunction(stdext::demangle_class<C>(), functionName, function);
|
||||
const std::string className = stdext::demangle_class<C>();
|
||||
registerClassMemberFunction(className, functionName, function);
|
||||
}
|
||||
|
||||
template<class C>
|
||||
|
|
@ -112,7 +116,8 @@ public:
|
|||
const LuaCppFunction& getFunction,
|
||||
const LuaCppFunction& setFunction)
|
||||
{
|
||||
registerClassMemberField(stdext::demangle_class<C>(), field, getFunction, setFunction);
|
||||
const std::string className = stdext::demangle_class<C>();
|
||||
registerClassMemberField(className, field, getFunction, setFunction);
|
||||
}
|
||||
|
||||
// methods for binding functions
|
||||
|
|
|
|||
45
tests/CMakeLists.txt
Normal file
45
tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
find_package(GTest CONFIG REQUIRED)
|
||||
|
||||
include(GoogleTest)
|
||||
|
||||
function(otclient_add_gtest TARGET_NAME)
|
||||
add_executable(${TARGET_NAME} ${ARGN})
|
||||
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
|
||||
if(TOGGLE_PRE_COMPILED_HEADER)
|
||||
target_precompile_headers(${TARGET_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/framework/pch.h)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
PRIVATE
|
||||
otclient_core
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_compile_definitions(${TARGET_NAME}
|
||||
PRIVATE
|
||||
CLIENT
|
||||
FRAMEWORK_GRAPHICS
|
||||
FRAMEWORK_NET
|
||||
FRAMEWORK_SOUND
|
||||
FRAMEWORK_XML
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(${TARGET_NAME} PRIVATE /utf-8)
|
||||
if(BUILD_STATIC_LIBRARY)
|
||||
set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
else()
|
||||
set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
gtest_discover_tests(${TARGET_NAME})
|
||||
endfunction()
|
||||
|
||||
add_subdirectory(map)
|
||||
5
tests/map/CMakeLists.txt
Normal file
5
tests/map/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
set(MAP_TEST_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/map_spectators_test.cpp
|
||||
)
|
||||
|
||||
otclient_add_gtest(otclient_map_spectator_tests ${MAP_TEST_SOURCES})
|
||||
515
tests/map/map_spectators_test.cpp
Normal file
515
tests/map/map_spectators_test.cpp
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#define private public
|
||||
#define protected public
|
||||
#include "client/map.h"
|
||||
|
||||
#include "client/creature.h"
|
||||
#include "client/gameconfig.h"
|
||||
#include "client/tile.h"
|
||||
#include "client/thingtype.h"
|
||||
|
||||
#undef protected
|
||||
#undef private
|
||||
|
||||
#include <framework/core/logger.h>
|
||||
#include <framework/core/resourcemanager.h>
|
||||
#include <framework/graphics/texturemanager.h>
|
||||
|
||||
namespace {
|
||||
|
||||
class DummyCreature final : public Creature
|
||||
{
|
||||
public:
|
||||
DummyCreature()
|
||||
{
|
||||
setRemovedSilently(false);
|
||||
}
|
||||
|
||||
void onPositionChange(const Position&, const Position& oldPos) override
|
||||
{
|
||||
setOldPositionSilently(oldPos);
|
||||
}
|
||||
|
||||
void onAppear() override
|
||||
{
|
||||
setRemovedSilently(false);
|
||||
}
|
||||
|
||||
void onDisappear() override
|
||||
{
|
||||
setRemovedSilently(true);
|
||||
setOldPositionSilently({});
|
||||
}
|
||||
|
||||
ThingType* getThingType() const override
|
||||
{
|
||||
static ThingType type;
|
||||
|
||||
static const bool initialized = [] {
|
||||
type.m_null = false;
|
||||
type.m_category = ThingCategoryCreature;
|
||||
type.m_size = Size(1, 1);
|
||||
type.m_realSize = 32;
|
||||
type.m_layers = 1;
|
||||
type.m_animationPhases = 1;
|
||||
type.m_opacity = 1.f;
|
||||
return true;
|
||||
}();
|
||||
|
||||
(void)initialized;
|
||||
return &type;
|
||||
}
|
||||
|
||||
void terminateWalk() override
|
||||
{
|
||||
m_walking = false;
|
||||
m_walkedPixels = 0;
|
||||
m_walkOffset = {};
|
||||
}
|
||||
};
|
||||
|
||||
class DummyItem final : public Thing
|
||||
{
|
||||
public:
|
||||
DummyItem()
|
||||
{
|
||||
m_clientId = 1;
|
||||
}
|
||||
|
||||
bool isItem() const override { return true; }
|
||||
|
||||
ThingType* getThingType() const override
|
||||
{
|
||||
static ThingType type;
|
||||
|
||||
static const bool initialized = [] {
|
||||
type.m_null = false;
|
||||
type.m_category = ThingCategoryItem;
|
||||
type.m_size = Size(1, 1);
|
||||
type.m_realSize = 32;
|
||||
type.m_layers = 1;
|
||||
type.m_animationPhases = 1;
|
||||
type.m_opacity = 1.f;
|
||||
return true;
|
||||
}();
|
||||
|
||||
(void)initialized;
|
||||
return &type;
|
||||
}
|
||||
};
|
||||
|
||||
class FrameworkEnvironment : public testing::Environment
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
m_previousLogLevel = g_logger.getLevel();
|
||||
g_logger.setLevel(Fw::LogFatal);
|
||||
g_resources.init(".");
|
||||
g_resources.addSearchPath(".");
|
||||
g_textures.init();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
g_textures.terminate();
|
||||
g_resources.terminate();
|
||||
g_logger.setLevel(m_previousLogLevel);
|
||||
}
|
||||
|
||||
private:
|
||||
Fw::LogLevel m_previousLogLevel{ Fw::LogFatal };
|
||||
};
|
||||
|
||||
[[maybe_unused]] testing::Environment* const g_frameworkEnv = testing::AddGlobalTestEnvironment(new FrameworkEnvironment);
|
||||
|
||||
CreaturePtr makeCreature(const uint32_t id, const Position& position)
|
||||
{
|
||||
auto creature = std::make_shared<DummyCreature>();
|
||||
creature->setId(id);
|
||||
creature->setPosition(position);
|
||||
return creature;
|
||||
}
|
||||
|
||||
ThingPtr makeItem(const Position& position)
|
||||
{
|
||||
auto item = std::make_shared<DummyItem>();
|
||||
item->setPosition(position);
|
||||
return item;
|
||||
}
|
||||
|
||||
std::vector<CreaturePtr> expectedSpectatorsFromTile(Tile& tile)
|
||||
{
|
||||
std::vector<CreaturePtr> creatures;
|
||||
for (const auto& thing : tile.getThings()) {
|
||||
if (thing->isCreature()) {
|
||||
creatures.emplace_back(thing->static_self_cast<Creature>());
|
||||
}
|
||||
}
|
||||
return { creatures.rbegin(), creatures.rend() };
|
||||
}
|
||||
|
||||
std::pair<int16_t, int16_t> expectedCreatureSpan(const Tile& tile)
|
||||
{
|
||||
int16_t first = -1;
|
||||
int16_t last = -1;
|
||||
|
||||
for (int16_t i = 0; i < static_cast<int16_t>(tile.m_things.size()); ++i) {
|
||||
if (!tile.m_things[i]->isCreature()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first == -1) {
|
||||
first = i;
|
||||
}
|
||||
|
||||
last = i;
|
||||
}
|
||||
|
||||
return { first, last };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(TileSpectators, AppendSpectatorsMatchesThingOrder)
|
||||
{
|
||||
const Position position(100, 100, 7);
|
||||
Tile tile(position);
|
||||
|
||||
EXPECT_FALSE(tile.hasCreatures());
|
||||
EXPECT_TRUE(tile.getCreatures().empty());
|
||||
|
||||
auto first = makeCreature(1, position);
|
||||
auto second = makeCreature(2, position);
|
||||
auto third = makeCreature(3, position);
|
||||
|
||||
tile.addThing(first, -1);
|
||||
tile.addThing(second, -1);
|
||||
tile.addThing(third, -1);
|
||||
|
||||
const auto expected = expectedSpectatorsFromTile(tile);
|
||||
|
||||
std::vector<CreaturePtr> actual;
|
||||
tile.appendSpectators(actual);
|
||||
|
||||
ASSERT_EQ(expected.size(), actual.size());
|
||||
EXPECT_EQ(expected, actual);
|
||||
|
||||
const auto forward = tile.getCreatures();
|
||||
EXPECT_EQ(forward.size(), expected.size());
|
||||
|
||||
for (size_t i = 0; i < forward.size(); ++i) {
|
||||
EXPECT_EQ(forward[i], expected[expected.size() - 1 - i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TileSpectators, CreatureRangeTracksMixedInsertions)
|
||||
{
|
||||
const Position position(200, 200, 7);
|
||||
Tile tile(position);
|
||||
|
||||
tile.addThing(makeItem(position), -1);
|
||||
|
||||
tile.addThing(makeItem(position), 0);
|
||||
|
||||
auto bottom = makeCreature(1, position);
|
||||
tile.addThing(bottom, -1);
|
||||
|
||||
tile.addThing(makeItem(position), -1);
|
||||
|
||||
auto middle = makeCreature(2, position);
|
||||
tile.addThing(middle, -1);
|
||||
|
||||
tile.addThing(makeItem(position), -1);
|
||||
|
||||
auto top = makeCreature(3, position);
|
||||
tile.addThing(top, -1);
|
||||
|
||||
tile.addThing(makeItem(position), -1);
|
||||
|
||||
const auto expected = expectedSpectatorsFromTile(tile);
|
||||
|
||||
std::vector<CreaturePtr> actual;
|
||||
tile.appendSpectators(actual);
|
||||
|
||||
EXPECT_EQ(expected, actual);
|
||||
|
||||
const auto [expectedFirst, expectedLast] = expectedCreatureSpan(tile);
|
||||
EXPECT_EQ(expectedFirst, tile.m_firstCreatureIndex);
|
||||
EXPECT_EQ(expectedLast, tile.m_lastCreatureIndex);
|
||||
}
|
||||
|
||||
TEST(TileSpectators, RemovingCreaturesUpdatesSpan)
|
||||
{
|
||||
const Position position(210, 210, 7);
|
||||
Tile tile(position);
|
||||
|
||||
auto first = makeCreature(1, position);
|
||||
auto second = makeCreature(2, position);
|
||||
|
||||
tile.addThing(makeItem(position), -1);
|
||||
tile.addThing(first, -1);
|
||||
tile.addThing(makeItem(position), -1);
|
||||
tile.addThing(second, -1);
|
||||
|
||||
ASSERT_TRUE(tile.hasCreatures());
|
||||
|
||||
const auto expectedBeforeRemoval = expectedSpectatorsFromTile(tile);
|
||||
ASSERT_FALSE(expectedBeforeRemoval.empty());
|
||||
|
||||
std::vector<CreaturePtr> actualBeforeRemoval;
|
||||
tile.appendSpectators(actualBeforeRemoval);
|
||||
EXPECT_EQ(expectedBeforeRemoval, actualBeforeRemoval);
|
||||
|
||||
const auto forwardBeforeRemoval = tile.getCreatures();
|
||||
ASSERT_EQ(expectedBeforeRemoval.size(), forwardBeforeRemoval.size());
|
||||
EXPECT_EQ(expectedBeforeRemoval.back(), forwardBeforeRemoval.front());
|
||||
|
||||
const auto [expectedFirst, expectedLast] = expectedCreatureSpan(tile);
|
||||
EXPECT_EQ(expectedFirst, tile.m_firstCreatureIndex);
|
||||
EXPECT_EQ(expectedLast, tile.m_lastCreatureIndex);
|
||||
|
||||
ASSERT_TRUE(tile.removeThing(first));
|
||||
EXPECT_TRUE(tile.hasCreatures());
|
||||
|
||||
const auto expectedAfterFirstRemoval = expectedSpectatorsFromTile(tile);
|
||||
ASSERT_FALSE(expectedAfterFirstRemoval.empty());
|
||||
|
||||
std::vector<CreaturePtr> actualAfterFirstRemoval;
|
||||
tile.appendSpectators(actualAfterFirstRemoval);
|
||||
EXPECT_EQ(expectedAfterFirstRemoval, actualAfterFirstRemoval);
|
||||
|
||||
const auto forwardAfterFirstRemoval = tile.getCreatures();
|
||||
ASSERT_EQ(expectedAfterFirstRemoval.size(), forwardAfterFirstRemoval.size());
|
||||
EXPECT_EQ(expectedAfterFirstRemoval.back(), forwardAfterFirstRemoval.front());
|
||||
|
||||
const auto [expectedFirstAfterRemoval, expectedLastAfterRemoval] = expectedCreatureSpan(tile);
|
||||
EXPECT_EQ(expectedFirstAfterRemoval, tile.m_firstCreatureIndex);
|
||||
EXPECT_EQ(expectedLastAfterRemoval, tile.m_lastCreatureIndex);
|
||||
|
||||
ASSERT_TRUE(tile.removeThing(second));
|
||||
EXPECT_FALSE(tile.hasCreatures());
|
||||
EXPECT_TRUE(tile.getCreatures().empty());
|
||||
|
||||
const auto [expectedFirstAfterSecondRemoval, expectedLastAfterSecondRemoval] = expectedCreatureSpan(tile);
|
||||
EXPECT_EQ(expectedFirstAfterSecondRemoval, tile.m_firstCreatureIndex);
|
||||
EXPECT_EQ(expectedLastAfterSecondRemoval, tile.m_lastCreatureIndex);
|
||||
}
|
||||
|
||||
TEST(MapSpectators, AggregatesCreaturesFromTiles)
|
||||
{
|
||||
const Position center(105, 205, 7);
|
||||
|
||||
Map map;
|
||||
map.m_floors.resize(g_gameConfig.getMapMaxZ() + 1);
|
||||
map.m_centralPosition = center;
|
||||
|
||||
const auto& tile = map.createTile(center);
|
||||
|
||||
auto first = makeCreature(10, center);
|
||||
auto second = makeCreature(20, center);
|
||||
|
||||
tile->addThing(first, -1);
|
||||
tile->addThing(second, -1);
|
||||
|
||||
map.m_knownCreatures.try_emplace(first->getId(), first);
|
||||
map.m_knownCreatures.try_emplace(second->getId(), second);
|
||||
|
||||
const auto expected = expectedSpectatorsFromTile(*tile);
|
||||
|
||||
const auto inRange = map.getSpectatorsInRangeEx(center, false, 0, 0, 0, 0);
|
||||
EXPECT_EQ(expected, inRange);
|
||||
|
||||
const auto byPattern = map.getSpectatorsByPattern(center, "1", Otc::North);
|
||||
EXPECT_EQ(expected, byPattern);
|
||||
}
|
||||
|
||||
std::vector<CreaturePtr> emulateLegacySpectatorCollection(Map& map, const Position& center, const bool multiFloor, const int32_t minXRange, const int32_t maxXRange, const int32_t minYRange, const int32_t maxYRange)
|
||||
{
|
||||
std::vector<CreaturePtr> result;
|
||||
|
||||
uint8_t minZRange = 0;
|
||||
uint8_t maxZRange = 0;
|
||||
|
||||
if (multiFloor) {
|
||||
minZRange = center.z - map.getFirstAwareFloor();
|
||||
maxZRange = map.getLastAwareFloor() - center.z;
|
||||
}
|
||||
|
||||
for (int iz = -minZRange; iz <= maxZRange; ++iz) {
|
||||
for (int iy = -minYRange; iy <= maxYRange; ++iy) {
|
||||
for (int ix = -minXRange; ix <= maxXRange; ++ix) {
|
||||
const Position position = center.translated(ix, iy, iz);
|
||||
if (const auto& tile = map.getTile(position)) {
|
||||
auto tileCreatures = tile->getCreatures();
|
||||
result.insert(result.end(), tileCreatures.rbegin(), tileCreatures.rend());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(MapSpectators, AggregationMatchesLegacyTraversal)
|
||||
{
|
||||
const Position center(250, 350, 6);
|
||||
|
||||
Map map;
|
||||
map.m_floors.resize(g_gameConfig.getMapMaxZ() + 1);
|
||||
map.m_centralPosition = center;
|
||||
|
||||
const auto centerTile = map.createTile(center);
|
||||
const auto eastTile = map.createTile(center.translated(2, 0));
|
||||
const auto northTile = map.createTile(center.translated(0, -1));
|
||||
const auto aboveTile = map.createTile(center.translated(0, 0, 1));
|
||||
|
||||
auto first = makeCreature(30, center);
|
||||
auto second = makeCreature(40, center.translated(2, 0));
|
||||
auto third = makeCreature(50, center.translated(0, -1));
|
||||
auto fourth = makeCreature(60, center.translated(0, 0, 1));
|
||||
|
||||
centerTile->addThing(first, -1);
|
||||
eastTile->addThing(second, -1);
|
||||
northTile->addThing(third, -1);
|
||||
aboveTile->addThing(fourth, -1);
|
||||
|
||||
map.m_knownCreatures.try_emplace(first->getId(), first);
|
||||
map.m_knownCreatures.try_emplace(second->getId(), second);
|
||||
map.m_knownCreatures.try_emplace(third->getId(), third);
|
||||
map.m_knownCreatures.try_emplace(fourth->getId(), fourth);
|
||||
|
||||
const bool multiFloor = true;
|
||||
const int range = 3;
|
||||
|
||||
const auto expected = emulateLegacySpectatorCollection(map, center, multiFloor, range, range, range, range);
|
||||
const auto actual = map.getSpectatorsInRangeEx(center, multiFloor, range, range, range, range);
|
||||
|
||||
EXPECT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
TEST(MapSpectators, RangeFiltering)
|
||||
{
|
||||
const Position center(101, 101, 8);
|
||||
|
||||
Map map;
|
||||
map.m_floors.resize(g_gameConfig.getMapMaxZ() + 1);
|
||||
map.m_centralPosition = center;
|
||||
|
||||
const auto centerTile = map.createTile(center);
|
||||
const auto adjTile = map.createTile(center.translated(-1, -1));
|
||||
const auto farTile = map.createTile(center.translated(2, 2));
|
||||
|
||||
auto c1 = makeCreature(1, center);
|
||||
auto c2 = makeCreature(2, adjTile->getPosition());
|
||||
auto c3 = makeCreature(3, farTile->getPosition());
|
||||
|
||||
centerTile->addThing(c1, -1);
|
||||
adjTile->addThing(c2, -1);
|
||||
farTile->addThing(c3, -1);
|
||||
|
||||
map.m_knownCreatures.try_emplace(c1->getId(), c1);
|
||||
map.m_knownCreatures.try_emplace(c2->getId(), c2);
|
||||
map.m_knownCreatures.try_emplace(c3->getId(), c3);
|
||||
|
||||
{
|
||||
const auto nearSpectators = map.getSpectatorsInRangeEx(center, false, 1, 1, 1, 1);
|
||||
const std::vector<CreaturePtr> expected{ c2, c1 };
|
||||
EXPECT_EQ(expected, nearSpectators);
|
||||
}
|
||||
|
||||
{
|
||||
const auto farSpectators = map.getSpectatorsInRangeEx(center, false, 2, 2, 2, 2);
|
||||
const std::vector<CreaturePtr> expected{ c2, c1, c3 };
|
||||
EXPECT_EQ(expected, farSpectators);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MapSpectators, CreatureOrderingIsDeterministic)
|
||||
{
|
||||
const Position center(180, 280, 7);
|
||||
|
||||
Map map;
|
||||
map.m_floors.resize(g_gameConfig.getMapMaxZ() + 1);
|
||||
map.m_centralPosition = center;
|
||||
|
||||
const auto westTile = map.createTile(center.translated(-1, 0));
|
||||
const auto centerTile = map.createTile(center);
|
||||
const auto eastTile = map.createTile(center.translated(1, 0));
|
||||
|
||||
auto west = makeCreature(1, westTile->getPosition());
|
||||
auto middle = makeCreature(2, centerTile->getPosition());
|
||||
auto east = makeCreature(3, eastTile->getPosition());
|
||||
|
||||
westTile->addThing(west, -1);
|
||||
centerTile->addThing(middle, -1);
|
||||
eastTile->addThing(east, -1);
|
||||
|
||||
map.m_knownCreatures.try_emplace(west->getId(), west);
|
||||
map.m_knownCreatures.try_emplace(middle->getId(), middle);
|
||||
map.m_knownCreatures.try_emplace(east->getId(), east);
|
||||
|
||||
const auto expected = std::vector<CreaturePtr>{ west, middle, east };
|
||||
|
||||
const auto spectators = map.getSpectatorsInRangeEx(center, false, 1, 1, 0, 0);
|
||||
ASSERT_EQ(expected.size(), spectators.size());
|
||||
EXPECT_EQ(expected, spectators);
|
||||
|
||||
const auto repeated = map.getSpectatorsInRangeEx(center, false, 1, 1, 0, 0);
|
||||
EXPECT_EQ(spectators, repeated);
|
||||
}
|
||||
|
||||
TEST(MapSpectators, MultiFloorRangeIncludesVerticalNeighbors)
|
||||
{
|
||||
const Position center(190, 290, 8);
|
||||
|
||||
Map map;
|
||||
map.m_floors.resize(g_gameConfig.getMapMaxZ() + 1);
|
||||
map.m_centralPosition = center;
|
||||
|
||||
const auto belowTile = map.createTile(center.translated(0, 0, -1));
|
||||
const auto centerTile = map.createTile(center);
|
||||
const auto aboveTile = map.createTile(center.translated(0, 0, 1));
|
||||
|
||||
auto below = makeCreature(11, belowTile->getPosition());
|
||||
auto middle = makeCreature(12, centerTile->getPosition());
|
||||
auto above = makeCreature(13, aboveTile->getPosition());
|
||||
|
||||
belowTile->addThing(below, -1);
|
||||
centerTile->addThing(middle, -1);
|
||||
aboveTile->addThing(above, -1);
|
||||
|
||||
map.m_knownCreatures.try_emplace(below->getId(), below);
|
||||
map.m_knownCreatures.try_emplace(middle->getId(), middle);
|
||||
map.m_knownCreatures.try_emplace(above->getId(), above);
|
||||
|
||||
const auto spectators = map.getSpectatorsInRangeEx(center, true, 0, 0, 0, 0);
|
||||
const auto expected = std::vector<CreaturePtr>{ below, middle, above };
|
||||
|
||||
ASSERT_EQ(expected.size(), spectators.size());
|
||||
EXPECT_EQ(expected, spectators);
|
||||
}
|
||||
|
||||
TEST(MapSpectators, UniqueCreatures)
|
||||
{
|
||||
const Position center(220, 320, 7);
|
||||
|
||||
Map map;
|
||||
map.m_floors.resize(g_gameConfig.getMapMaxZ() + 1);
|
||||
map.m_centralPosition = center;
|
||||
|
||||
const auto firstTile = map.createTile(center);
|
||||
const auto secondTile = map.createTile(center.translated(1, 0));
|
||||
|
||||
auto shared = makeCreature(42, firstTile->getPosition());
|
||||
|
||||
firstTile->addThing(shared, -1);
|
||||
secondTile->addThing(shared, -1);
|
||||
|
||||
map.m_knownCreatures.try_emplace(shared->getId(), shared);
|
||||
|
||||
const auto spectators = map.getSpectatorsInRangeEx(center, false, 1, 1, 0, 0);
|
||||
ASSERT_EQ(1u, spectators.size());
|
||||
EXPECT_EQ(shared, spectators.front());
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
"zlib",
|
||||
"bshoshany-thread-pool",
|
||||
"fmt",
|
||||
"gtest",
|
||||
{
|
||||
"name": "luajit",
|
||||
"platform": "!android & !wasm32"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue