mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-26 14:37:44 +00:00
Merge remote-tracking branch 'SapphireServer/develop' into develop
This commit is contained in:
commit
a939f935f3
90 changed files with 5462 additions and 1462 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -4,3 +4,6 @@
|
||||||
[submodule "deps/spdlog"]
|
[submodule "deps/spdlog"]
|
||||||
path = deps/spdlog
|
path = deps/spdlog
|
||||||
url = https://github.com/gabime/spdlog.git
|
url = https://github.com/gabime/spdlog.git
|
||||||
|
[submodule "deps/recastnavigation"]
|
||||||
|
path = deps/recastnavigation
|
||||||
|
url = https://github.com/SapphireServer/recastnavigation
|
||||||
|
|
|
@ -19,7 +19,6 @@ add_custom_target( copy_runtime_files ALL
|
||||||
# Dependencies and compiler settings #
|
# Dependencies and compiler settings #
|
||||||
######################################
|
######################################
|
||||||
include( "cmake/paths.cmake" )
|
include( "cmake/paths.cmake" )
|
||||||
#include( "cmake/mysql.cmake" )
|
|
||||||
include( "cmake/compiler.cmake" )
|
include( "cmake/compiler.cmake" )
|
||||||
include( "cmake/cotire.cmake" )
|
include( "cmake/cotire.cmake" )
|
||||||
|
|
||||||
|
@ -44,6 +43,7 @@ add_subdirectory( "deps/zlib" )
|
||||||
add_subdirectory( "deps/MySQL" )
|
add_subdirectory( "deps/MySQL" )
|
||||||
add_subdirectory( "deps/datReader" )
|
add_subdirectory( "deps/datReader" )
|
||||||
add_subdirectory( "deps/mysqlConnector" )
|
add_subdirectory( "deps/mysqlConnector" )
|
||||||
|
add_subdirectory( "deps/recastnavigation" )
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# Main Sapphire Components #
|
# Main Sapphire Components #
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
if( UNIX )
|
if( UNIX )
|
||||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC" )
|
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fPIC" )
|
||||||
|
set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O3")
|
||||||
else()
|
else()
|
||||||
add_definitions( -D_WIN32_WINNT=0x601 )
|
add_definitions( -D_WIN32_WINNT=0x601 )
|
||||||
add_definitions( -D_CRT_SECURE_NO_WARNINGS )
|
add_definitions( -D_CRT_SECURE_NO_WARNINGS )
|
||||||
|
|
|
@ -12,3 +12,4 @@ endif()
|
||||||
|
|
||||||
# Create log folder
|
# Create log folder
|
||||||
file( MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/log )
|
file( MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/log )
|
||||||
|
file( MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/navi )
|
||||||
|
|
|
@ -12,6 +12,8 @@ ServerSecret = default
|
||||||
DataPath = C:\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack
|
DataPath = C:\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack
|
||||||
WorldID = 67
|
WorldID = 67
|
||||||
DefaultGMRank = 255
|
DefaultGMRank = 255
|
||||||
|
LogLevel = 1
|
||||||
|
LogFilter = 0
|
||||||
|
|
||||||
[Network]
|
[Network]
|
||||||
; Values definining how Users and other servers will access - these have to be set to your public IP when running a public server
|
; Values definining how Users and other servers will access - these have to be set to your public IP when running a public server
|
||||||
|
|
|
@ -15,6 +15,9 @@ DisconnectTimeout = 20
|
||||||
; Sent on login - each line must be shorter than 307 characters, split lines with ';'
|
; Sent on login - each line must be shorter than 307 characters, split lines with ';'
|
||||||
MotD = Welcome to Sapphire!;This is a very good server;You can change these messages by editing General.MotD in config/config.ini
|
MotD = Welcome to Sapphire!;This is a very good server;You can change these messages by editing General.MotD in config/config.ini
|
||||||
|
|
||||||
|
[Navigation]
|
||||||
|
MeshPath = navi
|
||||||
|
|
||||||
[Housing]
|
[Housing]
|
||||||
; Set the default estate name. {0} will be replaced with the plot number
|
; Set the default estate name. {0} will be replaced with the plot number
|
||||||
DefaultEstateName = Estate ${0}
|
DefaultEstateName = Estate ${0}
|
13
deps/datReader/GameData.cpp
vendored
13
deps/datReader/GameData.cpp
vendored
|
@ -58,8 +58,15 @@ GameData::GameData(const std::experimental::filesystem::path& path) try :
|
||||||
{
|
{
|
||||||
int maxExLevel = 0;
|
int maxExLevel = 0;
|
||||||
|
|
||||||
|
// msvc has retarded stdlib implementation
|
||||||
|
#ifdef _WIN32
|
||||||
|
static constexpr auto sep = "\\";
|
||||||
|
#else
|
||||||
|
static constexpr auto sep = std::experimental::filesystem::path::preferred_separator;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Determine which expansions are available
|
// Determine which expansions are available
|
||||||
while( std::experimental::filesystem::exists( std::experimental::filesystem::path( m_path.string() + "\\ex" + std::to_string( maxExLevel + 1) + "\\ex" + std::to_string( maxExLevel + 1) + ".ver" ) ) )
|
while( std::experimental::filesystem::exists( std::experimental::filesystem::path( m_path.string() + sep + "ex" + std::to_string( maxExLevel + 1 ) + sep + "ex" + std::to_string( maxExLevel + 1 ) + ".ver" ) ) )
|
||||||
{
|
{
|
||||||
maxExLevel++;
|
maxExLevel++;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +97,7 @@ GameData::GameData(const std::experimental::filesystem::path& path) try :
|
||||||
// Check for expansion
|
// Check for expansion
|
||||||
for( int exNum = 1; exNum <= maxExLevel; exNum++ )
|
for( int exNum = 1; exNum <= maxExLevel; exNum++ )
|
||||||
{
|
{
|
||||||
const std::string path = m_path.string() + "\\" + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, 0, "win32", "index" );
|
const std::string path = m_path.string() + sep + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, 0, "win32", "index" );
|
||||||
|
|
||||||
if( std::experimental::filesystem::exists( std::experimental::filesystem::path( path ) ) )
|
if( std::experimental::filesystem::exists( std::experimental::filesystem::path( path ) ) )
|
||||||
{
|
{
|
||||||
|
@ -99,7 +106,7 @@ GameData::GameData(const std::experimental::filesystem::path& path) try :
|
||||||
|
|
||||||
for(int chunkTest = 0; chunkTest < 256; chunkTest++ )
|
for(int chunkTest = 0; chunkTest < 256; chunkTest++ )
|
||||||
{
|
{
|
||||||
if( std::experimental::filesystem::exists( m_path.string() + "\\" + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, chunkTest, "win32", "index" ) ) )
|
if( std::experimental::filesystem::exists( m_path.string() + sep + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, chunkTest, "win32", "index" ) ) )
|
||||||
{
|
{
|
||||||
m_exCats[cat_nb].exNumToChunkMap[exNum].chunkToCatMap[chunkTest] = std::unique_ptr<Cat>();
|
m_exCats[cat_nb].exNumToChunkMap[exNum].chunkToCatMap[chunkTest] = std::unique_ptr<Cat>();
|
||||||
chunkCount++;
|
chunkCount++;
|
||||||
|
|
1
deps/recastnavigation
vendored
Submodule
1
deps/recastnavigation
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit d24db2de131d6be5a1bc38067fa8b649544a0dd2
|
|
@ -790,7 +790,7 @@ WARN_LOGFILE =
|
||||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||||
# Note: If this tag is empty the current directory is searched.
|
# Note: If this tag is empty the current directory is searched.
|
||||||
|
|
||||||
INPUT = ../src/servers ../src/common pages/
|
INPUT = pages/ ../src/api ../src/common ../src/dbm ../src/lobby ../src/world
|
||||||
|
|
||||||
# This tag can be used to specify the character encoding of the source files
|
# This tag can be used to specify the character encoding of the source files
|
||||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -713,6 +713,8 @@ int main( int argc, char* argv[] )
|
||||||
if( !loadSettings( argc, argv ) )
|
if( !loadSettings( argc, argv ) )
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
|
|
||||||
|
Logger::setLogLevel( m_config.global.general.logLevel );
|
||||||
|
|
||||||
server.resource[ "^/ZoneName/([0-9]+)$" ][ "GET" ] = &getZoneName;
|
server.resource[ "^/ZoneName/([0-9]+)$" ][ "GET" ] = &getZoneName;
|
||||||
server.resource[ "^/sapphire-api/lobby/createAccount" ][ "POST" ] = &createAccount;
|
server.resource[ "^/sapphire-api/lobby/createAccount" ][ "POST" ] = &createAccount;
|
||||||
server.resource[ "^/sapphire-api/lobby/login" ][ "POST" ] = &login;
|
server.resource[ "^/sapphire-api/lobby/login" ][ "POST" ] = &login;
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace Sapphire::Common
|
||||||
const uint8_t MAX_DISPLAYED_EOBJS = 40;
|
const uint8_t MAX_DISPLAYED_EOBJS = 40;
|
||||||
|
|
||||||
const int32_t INVALID_GAME_OBJECT_ID = 0xE0000000;
|
const int32_t INVALID_GAME_OBJECT_ID = 0xE0000000;
|
||||||
|
const uint64_t INVALID_GAME_OBJECT_ID64 = 0xE0000000;
|
||||||
|
|
||||||
struct FFXIVARR_POSITION3
|
struct FFXIVARR_POSITION3
|
||||||
{
|
{
|
||||||
|
@ -531,8 +532,7 @@ namespace Sapphire::Common
|
||||||
Unaspected = 7 // Doesn't imply magical unaspected damage - could be unaspected physical
|
Unaspected = 7 // Doesn't imply magical unaspected damage - could be unaspected physical
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ActionType :
|
enum class ActionType : int8_t
|
||||||
int8_t
|
|
||||||
{
|
{
|
||||||
WeaponOverride = -1, // Needs more investigation (takes the damage type of the equipped weapon)?
|
WeaponOverride = -1, // Needs more investigation (takes the damage type of the equipped weapon)?
|
||||||
Unknown_0 = 0,
|
Unknown_0 = 0,
|
||||||
|
@ -546,8 +546,7 @@ namespace Sapphire::Common
|
||||||
LimitBreak = 8,
|
LimitBreak = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ActionEffectType :
|
enum ActionEffectType : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
Nothing = 0,
|
Nothing = 0,
|
||||||
Miss = 1,
|
Miss = 1,
|
||||||
|
@ -565,11 +564,11 @@ namespace Sapphire::Common
|
||||||
TpGain = 13,
|
TpGain = 13,
|
||||||
GpGain = 14,
|
GpGain = 14,
|
||||||
Knockback = 33,
|
Knockback = 33,
|
||||||
Mount = 38
|
Mount = 38,
|
||||||
|
VFX = 59, // links to VFX sheet
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ActionHitSeverityType :
|
enum class ActionHitSeverityType : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
NormalDamage = 0,
|
NormalDamage = 0,
|
||||||
CritHeal = 0,
|
CritHeal = 0,
|
||||||
|
@ -579,16 +578,14 @@ namespace Sapphire::Common
|
||||||
CritDirectHitDamage = 3
|
CritDirectHitDamage = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ActionEffectDisplayType :
|
enum ActionEffectDisplayType : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
HideActionName = 0,
|
HideActionName = 0,
|
||||||
ShowActionName = 1,
|
ShowActionName = 1,
|
||||||
ShowItemName = 2,
|
ShowItemName = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ActionCollisionType :
|
enum class ActionCollisionType : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
SingleTarget,
|
SingleTarget,
|
||||||
|
@ -601,32 +598,29 @@ namespace Sapphire::Common
|
||||||
Unknown3
|
Unknown3
|
||||||
};
|
};
|
||||||
|
|
||||||
enum HandleActionType :
|
enum HandleActionType : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
Event,
|
Event,
|
||||||
Spell,
|
Spell,
|
||||||
Teleport
|
Teleport
|
||||||
};
|
};
|
||||||
|
|
||||||
enum HandleSkillType :
|
enum HandleSkillType : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
StdDamage,
|
StdDamage,
|
||||||
StdHeal,
|
StdHeal,
|
||||||
StdDot,
|
StdDot,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum InvincibilityType :
|
enum InvincibilityType : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
InvincibilityNone,
|
InvincibilityNone,
|
||||||
InvincibilityRefill,
|
InvincibilityRefill,
|
||||||
InvincibilityStayAlive,
|
InvincibilityStayAlive,
|
||||||
|
InvincibilityIgnoreDamage,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PlayerStateFlag :
|
enum PlayerStateFlag : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
HideUILockChar = 0, // as the name suggests, hides the ui and logs the char...
|
HideUILockChar = 0, // as the name suggests, hides the ui and logs the char...
|
||||||
InCombat = 1, // in Combat, locks gearchange/return/teleport
|
InCombat = 1, // in Combat, locks gearchange/return/teleport
|
||||||
|
@ -642,8 +636,7 @@ namespace Sapphire::Common
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum struct FateStatus :
|
enum struct FateStatus : uint8_t
|
||||||
uint8_t
|
|
||||||
{
|
{
|
||||||
Active = 2,
|
Active = 2,
|
||||||
Inactive = 4,
|
Inactive = 4,
|
||||||
|
@ -651,8 +644,7 @@ namespace Sapphire::Common
|
||||||
Completed = 8,
|
Completed = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum struct ChatType :
|
enum struct ChatType : uint16_t
|
||||||
uint16_t
|
|
||||||
{
|
{
|
||||||
LogKindError,
|
LogKindError,
|
||||||
ServerDebug,
|
ServerDebug,
|
||||||
|
@ -775,6 +767,7 @@ namespace Sapphire::Common
|
||||||
uint8_t
|
uint8_t
|
||||||
{
|
{
|
||||||
Normal = 0x1,
|
Normal = 0x1,
|
||||||
|
ItemAction = 0x2,
|
||||||
MountSkill = 0xD,
|
MountSkill = 0xD,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ namespace Sapphire::Common::Config
|
||||||
uint16_t worldID;
|
uint16_t worldID;
|
||||||
|
|
||||||
uint8_t defaultGMRank;
|
uint8_t defaultGMRank;
|
||||||
|
uint8_t logLevel;
|
||||||
|
uint32_t logFilter;
|
||||||
} general;
|
} general;
|
||||||
|
|
||||||
struct Network
|
struct Network
|
||||||
|
@ -42,6 +44,8 @@ namespace Sapphire::Common::Config
|
||||||
uint16_t listenPort;
|
uint16_t listenPort;
|
||||||
|
|
||||||
uint16_t disconnectTimeout;
|
uint16_t disconnectTimeout;
|
||||||
|
|
||||||
|
float inRangeDistance;
|
||||||
} network;
|
} network;
|
||||||
|
|
||||||
struct Housing
|
struct Housing
|
||||||
|
@ -56,6 +60,11 @@ namespace Sapphire::Common::Config
|
||||||
bool hotSwap;
|
bool hotSwap;
|
||||||
} scripts;
|
} scripts;
|
||||||
|
|
||||||
|
struct Navigation
|
||||||
|
{
|
||||||
|
std::string meshPath;
|
||||||
|
} navigation;
|
||||||
|
|
||||||
std::string motd;
|
std::string motd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,8 @@ bool Sapphire::ConfigMgr::loadGlobalConfig( Common::Config::GlobalConfig& config
|
||||||
config.general.serverSecret = getValue< std::string >( "General", "ServerSecret", "default" );
|
config.general.serverSecret = getValue< std::string >( "General", "ServerSecret", "default" );
|
||||||
config.general.worldID = getValue< uint16_t >( "General", "WorldID", 67 );
|
config.general.worldID = getValue< uint16_t >( "General", "WorldID", 67 );
|
||||||
config.general.defaultGMRank = getValue< uint8_t >( "General", "DefaultGMRank", 255 );
|
config.general.defaultGMRank = getValue< uint8_t >( "General", "DefaultGMRank", 255 );
|
||||||
|
config.general.logLevel = getValue< uint8_t >( "General", "LogLevel", 1 );
|
||||||
|
config.general.logFilter = getValue< uint32_t >( "General", "LogFilter", 0 );
|
||||||
|
|
||||||
// network
|
// network
|
||||||
config.network.zoneHost = getValue< std::string >( "Network", "ZoneHost", "127.0.0.1" );
|
config.network.zoneHost = getValue< std::string >( "Network", "ZoneHost", "127.0.0.1" );
|
||||||
|
|
|
@ -53,6 +53,11 @@ namespace Sapphire
|
||||||
spdlog::flush_on( spdlog::level::critical );
|
spdlog::flush_on( spdlog::level::critical );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Logger::setLogLevel( uint8_t logLevel )
|
||||||
|
{
|
||||||
|
spdlog::set_level( static_cast< spdlog::level::level_enum >( logLevel ) );
|
||||||
|
}
|
||||||
|
|
||||||
void Logger::error( const std::string& text )
|
void Logger::error( const std::string& text )
|
||||||
{
|
{
|
||||||
spdlog::get( "logger" )->error( text );
|
spdlog::get( "logger" )->error( text );
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace Sapphire
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static void init( const std::string& logPath );
|
static void init( const std::string& logPath );
|
||||||
|
static void setLogLevel( uint8_t logLevel );
|
||||||
|
|
||||||
// todo: this is a minor increase in build time because of fmtlib, but much less than including spdlog directly
|
// todo: this is a minor increase in build time because of fmtlib, but much less than including spdlog directly
|
||||||
|
|
||||||
|
|
|
@ -204,11 +204,11 @@ enum ActorControlType : uint16_t
|
||||||
SetFestival = 0x386, // param1: festival.exd index
|
SetFestival = 0x386, // param1: festival.exd index
|
||||||
|
|
||||||
ToggleOrchestrionUnlock = 0x396,
|
ToggleOrchestrionUnlock = 0x396,
|
||||||
Dismount = 0x3A0,
|
Dismount = 0x3A1, // updated 4.5
|
||||||
|
|
||||||
// Duty Recorder
|
// Duty Recorder
|
||||||
BeginReplayAck = 0x3A1,
|
BeginReplayAck = 0x3A2,
|
||||||
EndReplayAck = 0x3A2,
|
EndReplayAck = 0x3A3,
|
||||||
|
|
||||||
// Housing
|
// Housing
|
||||||
ShowHousingItemUI = 0x3F7,
|
ShowHousingItemUI = 0x3F7,
|
||||||
|
|
|
@ -258,7 +258,7 @@ namespace Sapphire::Network::Packets
|
||||||
|
|
||||||
// The IPC type itself.
|
// The IPC type itself.
|
||||||
m_ipcHdr.type = static_cast< ServerZoneIpcType >( m_data._ServerIpcType );
|
m_ipcHdr.type = static_cast< ServerZoneIpcType >( m_data._ServerIpcType );
|
||||||
m_ipcHdr.timestamp = static_cast< uint32_t >( Util::getTimeSeconds() );
|
m_ipcHdr.timestamp = Util::getTimeSeconds();
|
||||||
m_segHdr.size = sizeof( T ) + sizeof( FFXIVARR_IPC_HEADER ) + sizeof( FFXIVARR_PACKET_SEGMENT_HEADER );
|
m_segHdr.size = sizeof( T ) + sizeof( FFXIVARR_IPC_HEADER ) + sizeof( FFXIVARR_PACKET_SEGMENT_HEADER );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -119,35 +119,39 @@ namespace Sapphire::Network::Packets
|
||||||
|
|
||||||
PlayerSpawn = 0x0175, // updated 4.5
|
PlayerSpawn = 0x0175, // updated 4.5
|
||||||
NpcSpawn = 0x0176, // updated 4.5
|
NpcSpawn = 0x0176, // updated 4.5
|
||||||
|
NpcSpawn2 = 0x0177, // ( Bigger statuseffectlist? ) updated 4.5
|
||||||
ActorMove = 0x0178, // updated 4.5
|
ActorMove = 0x0178, // updated 4.5
|
||||||
|
|
||||||
ActorSetPos = 0x017A, // updated 4.5
|
ActorSetPos = 0x017A, // updated 4.5
|
||||||
|
|
||||||
ActorCast = 0x017C, // updated 4.5
|
ActorCast = 0x017C, // updated 4.5
|
||||||
|
|
||||||
PartyList = 0x017E, // updated 4.5
|
PartyList = 0x017E, // updated 4.5
|
||||||
HateList = 0x017F, // updated 4.5
|
|
||||||
|
|
||||||
|
HateList = 0x0180, // updated 4.5
|
||||||
ObjectSpawn = 0x0181, // updated 4.5
|
ObjectSpawn = 0x0181, // updated 4.5
|
||||||
ObjectDespawn = 0x0182, // updated 4.5
|
ObjectDespawn = 0x0182, // updated 4.5
|
||||||
|
|
||||||
UpdateClassInfo = 0x0183, // updated 4.5
|
UpdateClassInfo = 0x0183, // updated 4.5
|
||||||
SilentSetClassJob = 0x0184, // updated 4.5 - seems to be the case, not sure if it's actually used for anything
|
SilentSetClassJob = 0x0184, // updated 4.5 - seems to be the case, not sure if it's actually used for anything
|
||||||
|
|
||||||
InitUI = 0x0185, // updated 4.5
|
InitUI = 0x0185, // updated 4.5
|
||||||
PlayerStats = 0x0186, // updated 4.5
|
PlayerStats = 0x0186, // updated 4.5
|
||||||
ActorOwner = 0x0187, // updated 4.5
|
ActorOwner = 0x0187, // updated 4.5
|
||||||
PlayerStateFlags = 0x0188, // updated 4.5
|
PlayerStateFlags = 0x0188, // updated 4.5
|
||||||
PlayerClassInfo = 0x0189, // updated 4.5
|
PlayerClassInfo = 0x0189, // updated 4.5
|
||||||
|
|
||||||
ModelEquip = 0x018B, // updated 4.5
|
ModelEquip = 0x018B, // updated 4.5
|
||||||
Examine = 0x018C, // updated 4.5
|
Examine = 0x018C, // updated 4.5
|
||||||
CharaNameReq = 0x018D, // updated 4.5
|
CharaNameReq = 0x018D, // updated 4.5
|
||||||
|
|
||||||
SetLevelSync = 0x1186, // not updated for 4.4, not sure what it is anymore
|
SetLevelSync = 0x1186, // not updated for 4.4, not sure what it is anymore
|
||||||
|
|
||||||
ItemInfo = 0x0196, // updated 4.5
|
ItemInfo = 0x0196, // updated 4.5
|
||||||
ContainerInfo = 0x0197, // updated 4.5
|
ContainerInfo = 0x0197, // updated 4.5
|
||||||
InventoryTransactionFinish = 0x0198, // updated 4.5
|
InventoryTransactionFinish = 0x0198, // updated 4.5
|
||||||
InventoryTransaction = 0x0199, // updated 4.5
|
InventoryTransaction = 0x0199, // updated 4.5
|
||||||
|
|
||||||
CurrencyCrystalInfo = 0x019B, // updated 4.5
|
CurrencyCrystalInfo = 0x019B, // updated 4.5
|
||||||
|
|
||||||
InventoryActionAck = 0x019D, // updated 4.5
|
InventoryActionAck = 0x019D, // updated 4.5
|
||||||
UpdateInventorySlot = 0x019E, // updated 4.5
|
UpdateInventorySlot = 0x019E, // updated 4.5
|
||||||
|
|
||||||
|
@ -225,6 +229,17 @@ namespace Sapphire::Network::Packets
|
||||||
IPCTYPE_UNK_320 = 0x0253, // updated 4.5
|
IPCTYPE_UNK_320 = 0x0253, // updated 4.5
|
||||||
IPCTYPE_UNK_322 = 0x0255, // updated 4.5
|
IPCTYPE_UNK_322 = 0x0255, // updated 4.5
|
||||||
|
|
||||||
|
/// Doman Mahjong //////////////////////////////////////
|
||||||
|
MahjongOpenGui = 0x02BC, // only available in mahjong instance
|
||||||
|
MahjongNextRound = 0x02BD, // initial hands(baipai), # of riichi(wat), winds, honba, score and stuff
|
||||||
|
MahjongPlayerAction = 0x02BE, // tsumo(as in drawing a tile) called chi/pon/kan/riichi
|
||||||
|
MahjongEndRoundTsumo = 0x02BF, // called tsumo
|
||||||
|
MahjongEndRoundRon = 0x2C0, // called ron or double ron (waiting for action must be flagged from discard packet to call)
|
||||||
|
MahjongTileDiscard = 0x02C1, // giri (discarding a tile.) chi(1)/pon(2)/kan(4)/ron(8) flags etc..
|
||||||
|
MahjongPlayersInfo = 0x02C2, // actor id, name, rating and stuff..
|
||||||
|
// 2C3 and 2C4 are currently unknown
|
||||||
|
MahjongEndRoundDraw = 0x02C5, // self explanatory
|
||||||
|
MahjongEndGame = 0x02C6, // finished oorasu(all-last) round; shows a result screen.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -402,34 +402,48 @@ struct EffectEntry
|
||||||
struct EffectHeader
|
struct EffectHeader
|
||||||
{
|
{
|
||||||
uint64_t animationTargetId; // who the animation targets
|
uint64_t animationTargetId; // who the animation targets
|
||||||
|
|
||||||
uint32_t actionId; // what the casting player casts, shown in battle log/ui
|
uint32_t actionId; // what the casting player casts, shown in battle log/ui
|
||||||
|
|
||||||
uint32_t globalEffectCounter; // seems to only increment on retail?
|
uint32_t globalEffectCounter; // seems to only increment on retail?
|
||||||
float animationLockTime; // maybe? doesn't seem to do anything
|
|
||||||
|
|
||||||
|
float animationLockTime; // maybe? doesn't seem to do anything
|
||||||
uint32_t someTargetId; // always 00 00 00 E0, 0x0E000000 is the internal def for INVALID TARGET ID
|
uint32_t someTargetId; // always 00 00 00 E0, 0x0E000000 is the internal def for INVALID TARGET ID
|
||||||
|
|
||||||
uint16_t hiddenAnimation; // if 0, always shows animation, otherwise hides it. counts up by 1 for each animation skipped on a caster
|
uint16_t hiddenAnimation; // if 0, always shows animation, otherwise hides it. counts up by 1 for each animation skipped on a caster
|
||||||
|
|
||||||
uint16_t rotation;
|
uint16_t rotation;
|
||||||
|
|
||||||
uint16_t actionAnimationId; // the animation that is played by the casting character
|
uint16_t actionAnimationId; // the animation that is played by the casting character
|
||||||
uint8_t unknown1E; // can be 0,1,2 - maybe other values? - doesn't do anything?
|
uint8_t variation; // variation in the animation
|
||||||
|
|
||||||
Common::ActionEffectDisplayType effectDisplayType;
|
Common::ActionEffectDisplayType effectDisplayType;
|
||||||
|
|
||||||
uint8_t unknown20; // is read by handler, runs code which gets the LODWORD of animationLockTime (wtf?)
|
uint8_t unknown20; // is read by handler, runs code which gets the LODWORD of animationLockTime (wtf?)
|
||||||
uint8_t effectCount; // ignores effects if 0, otherwise parses all of them
|
uint8_t effectCount; // ignores effects if 0, otherwise parses all of them
|
||||||
|
uint16_t padding_21;
|
||||||
|
|
||||||
uint32_t padding_22[2];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FFXIVIpcEffect :
|
struct FFXIVIpcEffect : FFXIVIpcBasePacket< Effect >
|
||||||
FFXIVIpcBasePacket< Effect >
|
|
||||||
{
|
{
|
||||||
EffectHeader header;
|
uint64_t animationTargetId; // who the animation targets
|
||||||
|
|
||||||
EffectEntry effects[8];
|
uint32_t actionId; // what the casting player casts, shown in battle log/ui
|
||||||
|
uint32_t globalEffectCounter; // seems to only increment on retail?
|
||||||
|
|
||||||
|
float animationLockTime; // maybe? doesn't seem to do anything
|
||||||
|
uint32_t someTargetId; // always 00 00 00 E0, 0x0E000000 is the internal def for INVALID TARGET ID
|
||||||
|
|
||||||
|
uint16_t hiddenAnimation; // if 0, always shows animation, otherwise hides it. counts up by 1 for each animation skipped on a caster
|
||||||
|
uint16_t rotation;
|
||||||
|
uint16_t actionAnimationId; // the animation that is played by the casting character
|
||||||
|
uint8_t variation; // variation in the animation
|
||||||
|
Common::ActionEffectDisplayType effectDisplayType;
|
||||||
|
|
||||||
|
uint8_t unknown20; // is read by handler, runs code which gets the LODWORD of animationLockTime (wtf?)
|
||||||
|
uint8_t effectCount; // ignores effects if 0, otherwise parses all of them
|
||||||
|
uint16_t padding_21;
|
||||||
|
|
||||||
|
uint16_t padding_22[3];
|
||||||
|
|
||||||
|
uint8_t effects[8*8];
|
||||||
|
|
||||||
uint16_t padding_6A[3];
|
uint16_t padding_6A[3];
|
||||||
|
|
||||||
|
@ -652,7 +666,7 @@ struct FFXIVIpcActorMove :
|
||||||
{
|
{
|
||||||
/* 0000 */ uint8_t rotation;
|
/* 0000 */ uint8_t rotation;
|
||||||
/* 0001 */ uint8_t unknown_1;
|
/* 0001 */ uint8_t unknown_1;
|
||||||
/* 0002 */ uint8_t unknown_2;
|
/* 0002 */ uint8_t animationType;
|
||||||
/* 0003 */ uint8_t unknown_3;
|
/* 0003 */ uint8_t unknown_3;
|
||||||
/* 0004 */ uint16_t unknown_4;
|
/* 0004 */ uint16_t unknown_4;
|
||||||
/* 0006 */ uint16_t posX;
|
/* 0006 */ uint16_t posX;
|
||||||
|
@ -684,8 +698,7 @@ struct FFXIVIpcActorSetPos :
|
||||||
* Structural representation of the packet sent by the server
|
* Structural representation of the packet sent by the server
|
||||||
* to start an actors casting
|
* to start an actors casting
|
||||||
*/
|
*/
|
||||||
struct FFXIVIpcActorCast :
|
struct FFXIVIpcActorCast : FFXIVIpcBasePacket< ActorCast >
|
||||||
FFXIVIpcBasePacket< ActorCast >
|
|
||||||
{
|
{
|
||||||
uint16_t action_id;
|
uint16_t action_id;
|
||||||
Common::SkillType skillType;
|
Common::SkillType skillType;
|
||||||
|
@ -701,11 +714,10 @@ struct FFXIVIpcActorCast :
|
||||||
uint16_t unknown_3;
|
uint16_t unknown_3;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FFXIVIpcHateList :
|
struct FFXIVIpcHateList : FFXIVIpcBasePacket< HateList >
|
||||||
FFXIVIpcBasePacket< HateList >
|
|
||||||
{
|
{
|
||||||
uint32_t numEntries;
|
uint32_t numEntries;
|
||||||
struct LsEntry
|
struct
|
||||||
{
|
{
|
||||||
uint32_t actorId;
|
uint32_t actorId;
|
||||||
uint8_t hatePercent;
|
uint8_t hatePercent;
|
||||||
|
@ -715,8 +727,7 @@ struct FFXIVIpcHateList :
|
||||||
uint32_t padding;
|
uint32_t padding;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FFXIVIpcUpdateClassInfo :
|
struct FFXIVIpcUpdateClassInfo : FFXIVIpcBasePacket< UpdateClassInfo >
|
||||||
FFXIVIpcBasePacket< UpdateClassInfo >
|
|
||||||
{
|
{
|
||||||
uint8_t classId;
|
uint8_t classId;
|
||||||
uint8_t level1;
|
uint8_t level1;
|
||||||
|
@ -1563,6 +1574,11 @@ struct FFXIVIpcDirectorVars : FFXIVIpcBasePacket< DirectorVars >
|
||||||
uint8_t m_branch;
|
uint8_t m_branch;
|
||||||
/*! raw storage for flags/vars */
|
/*! raw storage for flags/vars */
|
||||||
uint8_t m_unionData[10];
|
uint8_t m_unionData[10];
|
||||||
|
/*! unknown */
|
||||||
|
uint16_t u20;
|
||||||
|
uint16_t u22;
|
||||||
|
uint16_t u24;
|
||||||
|
uint16_t u28;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1721,11 +1737,7 @@ struct FFXIVIpcHousingInternalObjectSpawn : FFXIVIpcBasePacket< HousingInternalO
|
||||||
uint8_t containerOffset;
|
uint8_t containerOffset;
|
||||||
uint8_t pad1;
|
uint8_t pad1;
|
||||||
|
|
||||||
uint16_t itemId;
|
Common::HousingObject object;
|
||||||
uint8_t unk2;
|
|
||||||
uint8_t pad2;
|
|
||||||
uint16_t rotation;
|
|
||||||
Common::FFXIVARR_POSITION3 pos;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FFXIVIpcHousingIndoorInitialize : FFXIVIpcBasePacket< HousingIndoorInitialize >
|
struct FFXIVIpcHousingIndoorInitialize : FFXIVIpcBasePacket< HousingIndoorInitialize >
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Sapphire::Util
|
||||||
{
|
{
|
||||||
auto it = m_actorIdToAllocatedMap.find( actorId );
|
auto it = m_actorIdToAllocatedMap.find( actorId );
|
||||||
if( it == m_actorIdToAllocatedMap.end() )
|
if( it == m_actorIdToAllocatedMap.end() )
|
||||||
return 0;
|
return getAllocFailId();
|
||||||
|
|
||||||
auto index = it->second;
|
auto index = it->second;
|
||||||
m_availableIds.push( index );
|
m_availableIds.push( index );
|
||||||
|
|
|
@ -119,10 +119,10 @@ uint64_t Sapphire::Util::getTimeMs()
|
||||||
return epoch.count();
|
return epoch.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t Sapphire::Util::getTimeSeconds()
|
uint32_t Sapphire::Util::getTimeSeconds()
|
||||||
{
|
{
|
||||||
std::chrono::seconds epoch = std::chrono::seconds( std::time( nullptr ) );
|
auto currClock = std::chrono::system_clock::now();
|
||||||
return epoch.count();
|
return std::chrono::time_point_cast< std::chrono::seconds >( currClock ).time_since_epoch().count();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Sapphire::Util::getEorzeanTimeStamp()
|
uint64_t Sapphire::Util::getEorzeanTimeStamp()
|
||||||
|
|
|
@ -21,7 +21,12 @@ namespace Sapphire::Util
|
||||||
|
|
||||||
uint64_t getTimeMs();
|
uint64_t getTimeMs();
|
||||||
|
|
||||||
int64_t getTimeSeconds();
|
/*!
|
||||||
|
* @brief Get a POSIX epoch representation of the current time
|
||||||
|
* @return 32-bit unsigned integer
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint32_t getTimeSeconds();
|
||||||
|
|
||||||
uint64_t getEorzeanTimeStamp();
|
uint64_t getEorzeanTimeStamp();
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,11 @@ float Sapphire::Util::distance( float x, float y, float z, float x1, float y1, f
|
||||||
return sqrtf( distanceSq( x, y, z, x1, y1, z1 ) );
|
return sqrtf( distanceSq( x, y, z, x1, y1, z1 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Sapphire::Util::distance( const Common::FFXIVARR_POSITION3& pos1, const Common::FFXIVARR_POSITION3& pos2 )
|
||||||
|
{
|
||||||
|
return sqrtf( distanceSq( pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z ) );
|
||||||
|
}
|
||||||
|
|
||||||
float Sapphire::Util::distance2DSq( float x, float y, float x1, float y1 )
|
float Sapphire::Util::distance2DSq( float x, float y, float x1, float y1 )
|
||||||
{
|
{
|
||||||
float deltaX = x - x1;
|
float deltaX = x - x1;
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace Sapphire::Util
|
||||||
float distanceSq( float x, float y, float z, float x1, float y1, float z1 );
|
float distanceSq( float x, float y, float z, float x1, float y1, float z1 );
|
||||||
|
|
||||||
float distance( float x, float y, float z, float x1, float y1, float z1 );
|
float distance( float x, float y, float z, float x1, float y1, float z1 );
|
||||||
|
float distance( const Common::FFXIVARR_POSITION3& pos1, const Common::FFXIVARR_POSITION3& pos2 );
|
||||||
|
|
||||||
float distance2DSq( float x, float y, float x1, float y1 );
|
float distance2DSq( float x, float y, float x1, float y1 );
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,8 @@ namespace Sapphire
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger::setLogLevel( m_config.global.general.logLevel );
|
||||||
|
|
||||||
auto pFw = std::make_shared< Framework >();
|
auto pFw = std::make_shared< Framework >();
|
||||||
Network::HivePtr hive( new Network::Hive() );
|
Network::HivePtr hive( new Network::Hive() );
|
||||||
Network::addServerToHive< Network::GameConnection >( m_ip, m_port, hive, pFw );
|
Network::addServerToHive< Network::GameConnection >( m_ip, m_port, hive, pFw );
|
||||||
|
|
|
@ -250,6 +250,22 @@ public:
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LGB_POPRANGE_ENTRY :
|
||||||
|
public LGB_ENTRY
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LGB_MAPRANGE_HEADER header;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
LGB_POPRANGE_ENTRY( char* buf, uint32_t offset ) :
|
||||||
|
LGB_ENTRY( buf, offset )
|
||||||
|
{
|
||||||
|
header = *reinterpret_cast< LGB_MAPRANGE_HEADER* >( buf + offset );
|
||||||
|
name = std::string( buf + offset + header.nameOffset );
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
struct LGB_GROUP_HEADER
|
struct LGB_GROUP_HEADER
|
||||||
{
|
{
|
||||||
uint32_t unknown;
|
uint32_t unknown;
|
||||||
|
@ -290,21 +306,9 @@ struct LGB_GROUP
|
||||||
{
|
{
|
||||||
const auto type = *reinterpret_cast<LgbEntryType*>( buf + entryOffset );
|
const auto type = *reinterpret_cast<LgbEntryType*>( buf + entryOffset );
|
||||||
// garbage to skip model loading
|
// garbage to skip model loading
|
||||||
if( !ignoreModels && type == LgbEntryType::BgParts )
|
if( type == LgbEntryType::PopRange )
|
||||||
{
|
{
|
||||||
entries.push_back( std::make_shared< LGB_BGPARTS_ENTRY >( buf, entryOffset ) );
|
entries.push_back( std::make_shared< LGB_POPRANGE_ENTRY >( buf, entryOffset ) );
|
||||||
}
|
|
||||||
else if( !ignoreModels && type == LgbEntryType::Gimmick )
|
|
||||||
{
|
|
||||||
entries.push_back( std::make_shared< LGB_GIMMICK_ENTRY >( buf, entryOffset ) );
|
|
||||||
}
|
|
||||||
else if( type == LgbEntryType::EventNpc )
|
|
||||||
{
|
|
||||||
entries.push_back( std::make_shared< LGB_ENPC_ENTRY >( buf, entryOffset ) );
|
|
||||||
}
|
|
||||||
else if( type == LgbEntryType::EventObject )
|
|
||||||
{
|
|
||||||
entries.push_back( std::make_shared< LGB_EOBJ_ENTRY >( buf, entryOffset ) );
|
|
||||||
}
|
}
|
||||||
else if( type == LgbEntryType::MapRange )
|
else if( type == LgbEntryType::MapRange )
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,6 +42,7 @@ std::unordered_map< uint32_t, std::string > eobjNameMap;
|
||||||
std::unordered_map< uint16_t, ZoneInfo > zoneInfoMap;
|
std::unordered_map< uint16_t, ZoneInfo > zoneInfoMap;
|
||||||
std::unordered_map< uint16_t, std::vector< std::pair< uint16_t, std::string > > > zoneInstanceMap;
|
std::unordered_map< uint16_t, std::vector< std::pair< uint16_t, std::string > > > zoneInstanceMap;
|
||||||
uint32_t zoneId;
|
uint32_t zoneId;
|
||||||
|
std::string zoneName;
|
||||||
|
|
||||||
std::set< std::string > zoneDumpList;
|
std::set< std::string > zoneDumpList;
|
||||||
|
|
||||||
|
@ -138,103 +139,6 @@ void initExd( const std::string& gamePath )
|
||||||
eData = eData ? eData : new xiv::exd::ExdData( *data1 );
|
eData = eData ? eData : new xiv::exd::ExdData( *data1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void getMapExdEntries( uint32_t zoneId )
|
|
||||||
{
|
|
||||||
static auto& cat = eData->get_category( "Map" );
|
|
||||||
static auto exd = static_cast< xiv::exd::Exd >( cat.get_data_ln( xiv::exd::Language::none ) );
|
|
||||||
//static std::unique_ptr< Converter > pConverter = std::make_unique< Converter >();
|
|
||||||
|
|
||||||
static auto& rows = exd.get_rows();
|
|
||||||
for( auto& row : rows )
|
|
||||||
{
|
|
||||||
// fields from SaintCoinach https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/ex.json#L6358
|
|
||||||
auto id = row.first;
|
|
||||||
|
|
||||||
auto& fields = row.second;
|
|
||||||
|
|
||||||
/* TYPES !!
|
|
||||||
case DataType::boolean: 1
|
|
||||||
case DataType::int8: 2
|
|
||||||
case DataType::uint8: 3
|
|
||||||
case DataType::int16: 4
|
|
||||||
case DataType::uint16: 5
|
|
||||||
case DataType::int32: 6
|
|
||||||
case DataType::uint32: 7
|
|
||||||
case DataType::float32: 8
|
|
||||||
case DataType::uint64: 9
|
|
||||||
*/
|
|
||||||
auto territory = std::get< uint16_t >( fields.at( 14 ) );
|
|
||||||
if( territory != zoneId )
|
|
||||||
continue;
|
|
||||||
auto mapZoneIndex = std::get< int8_t >( fields.at( 2 ) );
|
|
||||||
auto hierarchy = std::get< uint8_t >( fields.at( 3 ) );
|
|
||||||
auto pathStr = std::get< std::string >( fields.at( 5 ) );
|
|
||||||
auto sizeFactor = std::get< uint16_t >( fields.at( 6 ) );
|
|
||||||
auto mapOffsetX = std::get< int16_t >( fields.at( 7 ) );
|
|
||||||
auto mapOffsetY = std::get< int16_t >( fields.at( 8 ) );
|
|
||||||
auto discoveryIdx = std::get< int16_t >( fields.at( 12 ) );
|
|
||||||
auto discoveryCompleteBitmask = std::get< uint32_t >( fields.at( 13 ) );
|
|
||||||
char texStr[255];
|
|
||||||
auto teriStr = pathStr.substr( 0, pathStr.find_first_of( '/' ) );
|
|
||||||
char discoveryFileName[255];
|
|
||||||
sprintf( &discoveryFileName[ 0 ], "%s%02u", teriStr.c_str(), mapZoneIndex );
|
|
||||||
sprintf( &texStr[ 0 ], "ui/map/%s/%sd.tex", pathStr.c_str(), &discoveryFileName[ 0 ] );
|
|
||||||
|
|
||||||
if( discoveryMaps[ territory ].find( mapZoneIndex ) == discoveryMaps[ territory ].end() ||
|
|
||||||
discoveryMaps[ territory ][ mapZoneIndex ].find( hierarchy ) ==
|
|
||||||
discoveryMaps[ territory ][ mapZoneIndex ].end() )
|
|
||||||
{
|
|
||||||
std::string fileName( discoveryFileName );
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::string rawTexFile( texStr );
|
|
||||||
auto pDiscoveryMap = std::make_shared< DiscoveryMap >();
|
|
||||||
auto& discoveryMap = *pDiscoveryMap;
|
|
||||||
std::ifstream discoveryFile( fileName + ".img", std::ios::binary );
|
|
||||||
if( !discoveryFile.good() )
|
|
||||||
{
|
|
||||||
auto texFile = data1->getFile( rawTexFile );
|
|
||||||
texFile->exportToFile( fileName + ".tex" );
|
|
||||||
|
|
||||||
auto tex = TEX_FILE( fileName + ".tex" );
|
|
||||||
|
|
||||||
int mipMapDivide = 1;
|
|
||||||
int h = tex.header.uncompressedHeight;
|
|
||||||
int w = tex.header.uncompressedWidth;
|
|
||||||
discoveryMap.img = DecodeTexDXT1( tex, tex.header.mipMaps[ 0 ], h / mipMapDivide, w / mipMapDivide,
|
|
||||||
( h / mipMapDivide ) / 4, ( w / mipMapDivide ) / 4
|
|
||||||
);
|
|
||||||
discoveryMap.img.toFile( fileName + ".img" );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << discoveryFile.rdbuf();
|
|
||||||
discoveryMap.img = std::move( Image( &ss.str()[ 0 ] ) );
|
|
||||||
discoveryFile.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
discoveryMap.mapId = id;
|
|
||||||
discoveryMap.path = rawTexFile;
|
|
||||||
discoveryMap.mapOffsetX = mapOffsetX;
|
|
||||||
discoveryMap.mapOffsetY = mapOffsetY;
|
|
||||||
discoveryMap.mapScale = sizeFactor;
|
|
||||||
|
|
||||||
std::cout << "Image Height: " << discoveryMap.img.height << " Width: " << discoveryMap.img.width << "\n";
|
|
||||||
|
|
||||||
discoveryMaps[ territory ][ mapZoneIndex ][ hierarchy ] = pDiscoveryMap;
|
|
||||||
}
|
|
||||||
catch( std::exception& e )
|
|
||||||
{
|
|
||||||
std::cout << "[Error] " << std::string( texStr ) << " " << e.what() << "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string zoneNameToPath( const std::string& name )
|
std::string zoneNameToPath( const std::string& name )
|
||||||
{
|
{
|
||||||
std::string path;
|
std::string path;
|
||||||
|
@ -314,7 +218,7 @@ void loadEobjNames()
|
||||||
void writeEobjEntry( std::ofstream& out, LGB_ENTRY* pObj )
|
void writeEobjEntry( std::ofstream& out, LGB_ENTRY* pObj )
|
||||||
{
|
{
|
||||||
static std::string mapRangeStr( "\"MapRange\", " );
|
static std::string mapRangeStr( "\"MapRange\", " );
|
||||||
static std::ofstream discoverySql( "discovery.sql", std::ios::app );
|
std::ofstream discoverySql( zoneName + "_poprange.txt", std::ios::app );
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
uint32_t unknown2 = 0, unknown2_1 = 0, unknown3 = 0;
|
uint32_t unknown2 = 0, unknown2_1 = 0, unknown3 = 0;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
@ -340,89 +244,16 @@ void writeEobjEntry( std::ofstream& out, LGB_ENTRY* pObj )
|
||||||
bool found = false;
|
bool found = false;
|
||||||
float scale = 100.f; //pMapRange->header.unknown2
|
float scale = 100.f; //pMapRange->header.unknown2
|
||||||
|
|
||||||
if( pMapRange->header.mapId == 0 )
|
std::string outStr( pMapRange->name + " " + std::to_string( pMapRange->header.unknown ) + " " +
|
||||||
{
|
std::to_string( pMapRange->header.translation.x ) + " " +
|
||||||
auto it = discoveryMaps.find( zoneId );
|
std::to_string( pMapRange->header.translation.y ) + " " +
|
||||||
if( it != discoveryMaps.end() )
|
std::to_string( pMapRange->header.translation.z ) + " " +
|
||||||
{
|
std::to_string( pMapRange->header.rotation.y ) + "\n"
|
||||||
for( const auto& mapHierarchy : it->second )
|
);
|
||||||
{
|
|
||||||
if( subArea > 0 )
|
|
||||||
break;
|
|
||||||
for( const auto& levelHierarchy : mapHierarchy.second )
|
|
||||||
{
|
|
||||||
if( subArea > 0 )
|
|
||||||
break;
|
|
||||||
|
|
||||||
auto& map = *levelHierarchy.second;
|
|
||||||
pos = map.get2dPosFrom3d( translation.x, translation.z, scale );
|
|
||||||
|
|
||||||
mapId = map.mapId;
|
|
||||||
|
|
||||||
for( int i = 0; i < map.tiles; i++ )
|
|
||||||
{
|
|
||||||
auto colour = map.getColour( i, pos.x, pos.y, scale );
|
|
||||||
auto a = ( colour >> 24 ) & 0xFF;
|
|
||||||
auto r = ( colour >> 16 ) & 0xFF;
|
|
||||||
auto g = ( colour >> 8 ) & 0xFF;
|
|
||||||
auto b = ( colour >> 0 ) & 0xFF;
|
|
||||||
if( a > 0 && ( r + b + g ) > 0 )
|
|
||||||
{
|
|
||||||
if( r > 0 )
|
|
||||||
{
|
|
||||||
// out of bounds
|
|
||||||
if( i == 0 )
|
|
||||||
continue;
|
|
||||||
subArea = ( i * 3 ) + 1;
|
|
||||||
}
|
|
||||||
else if( g > 0 )
|
|
||||||
{
|
|
||||||
subArea = ( i * 3 ) + 2;
|
|
||||||
}
|
|
||||||
else if( b > 0 )
|
|
||||||
{
|
|
||||||
subArea = ( i * 3 ) + 3;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subArea--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mapId = pMapRange->header.mapId;
|
|
||||||
}
|
|
||||||
|
|
||||||
subArea = pMapRange->header.discoveryIndex + 1;
|
|
||||||
|
|
||||||
//if( discoveryIndex == subArea )
|
|
||||||
{
|
|
||||||
//std::cout << std::to_string( id ) << " discoveryIndex " << std::to_string( discoveryIndex ) << " subArea " << subArea << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if( discoveryIndex == 0 )
|
|
||||||
{
|
|
||||||
//std::cout << "\tUnable to find subarea for maprange " << std::to_string( id ) << " mapCoord " << pos.x << " " << pos.y <<
|
|
||||||
// "\tzoneCoord " << translation.x << " " << translation.y << " " << translation.z << " " << "\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if( mapId == -1 )
|
|
||||||
{
|
|
||||||
//std::cout << "\tUnable to find subarea for maprange " << std::to_string(id) << " " << "\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if( exportedMapRange[ id ][ mapId ] == discoveryIndex )
|
|
||||||
return;
|
|
||||||
exportedMapRange[ id ][ mapId ] = discoveryIndex;
|
|
||||||
std::string outStr( "INSERT INTO discoveryinfo VALUES (" +
|
|
||||||
std::to_string( id ) + ", " + std::to_string( mapId ) + ", " + std::to_string( discoveryIndex ) +
|
|
||||||
");\n"
|
|
||||||
//std::to_string( pObj->header.translation.x ) + ", " + std::to_string( pObj->header.translation.y ) + ", " + std::to_string( pObj->header.translation.z ) +
|
//std::to_string( pObj->header.translation.x ) + ", " + std::to_string( pObj->header.translation.y ) + ", " + std::to_string( pObj->header.translation.z ) +
|
||||||
//", " + std::to_string( subArea ) + "" + "\n"
|
//", " + std::to_string( subArea ) + "" + "\n"
|
||||||
);
|
//);
|
||||||
discoverySql.write( outStr.c_str(), outStr.size() );
|
discoverySql.write( outStr.c_str(), outStr.size() );
|
||||||
//out.write( outStr.c_str(), outStr.size() );
|
//out.write( outStr.c_str(), outStr.size() );
|
||||||
}
|
}
|
||||||
|
@ -453,7 +284,7 @@ int main( int argc, char* argv[] )
|
||||||
auto entryStartTime = std::chrono::system_clock::now();
|
auto entryStartTime = std::chrono::system_clock::now();
|
||||||
|
|
||||||
std::vector< std::string > argVec( argv + 1, argv + argc );
|
std::vector< std::string > argVec( argv + 1, argv + argc );
|
||||||
std::string zoneName = "s1d1";
|
zoneName = "s1h1";
|
||||||
|
|
||||||
bool dumpAll = ignoreModels = std::remove_if( argVec.begin(), argVec.end(), []( auto arg )
|
bool dumpAll = ignoreModels = std::remove_if( argVec.begin(), argVec.end(), []( auto arg )
|
||||||
{ return arg == "--dump-all"; } ) != argVec.end();
|
{ return arg == "--dump-all"; } ) != argVec.end();
|
||||||
|
@ -471,15 +302,17 @@ int main( int argc, char* argv[] )
|
||||||
}
|
}
|
||||||
|
|
||||||
initExd( gamePath );
|
initExd( gamePath );
|
||||||
std::ofstream discoverySql( "discovery.sql", std::ios::trunc );
|
std::ofstream discoverySql( zoneName + "_poprange.txt", std::ios::trunc );
|
||||||
discoverySql.close();
|
discoverySql.close();
|
||||||
|
|
||||||
if( dumpAll )
|
if( dumpAll )
|
||||||
{
|
{
|
||||||
zoneNameToPath( "r1f1" );
|
zoneNameToPath( "s1h1" );
|
||||||
|
|
||||||
for( const auto& zone : zoneInfoMap )
|
zoneDumpList.emplace( "s1h1" );
|
||||||
zoneDumpList.emplace( zone.second.name );
|
zoneDumpList.emplace( "f1h1" );
|
||||||
|
zoneDumpList.emplace( "w1h1" );
|
||||||
|
zoneDumpList.emplace( "e1h1" );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -489,6 +322,8 @@ int main( int argc, char* argv[] )
|
||||||
LABEL_DUMP:
|
LABEL_DUMP:
|
||||||
entryStartTime = std::chrono::system_clock::now();
|
entryStartTime = std::chrono::system_clock::now();
|
||||||
zoneName = *zoneDumpList.begin();
|
zoneName = *zoneDumpList.begin();
|
||||||
|
discoverySql.open( zoneName + "_poprange.txt", std::ios::trunc );
|
||||||
|
discoverySql.write( ( zoneName + "\n" ).c_str() , zoneName.size() + 1 );
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const auto zonePath = zoneNameToPath( zoneName );
|
const auto zonePath = zoneNameToPath( zoneName );
|
||||||
|
@ -517,7 +352,7 @@ int main( int argc, char* argv[] )
|
||||||
uint32_t offset1 = 0x20;
|
uint32_t offset1 = 0x20;
|
||||||
|
|
||||||
//loadEobjNames();
|
//loadEobjNames();
|
||||||
getMapExdEntries( zoneId );
|
//getMapExdEntries( zoneId );
|
||||||
|
|
||||||
std::string eobjFileName( zoneName + "_eobj.csv" );
|
std::string eobjFileName( zoneName + "_eobj.csv" );
|
||||||
std::ofstream eobjOut( eobjFileName, std::ios::trunc );
|
std::ofstream eobjOut( eobjFileName, std::ios::trunc );
|
||||||
|
@ -590,7 +425,7 @@ int main( int argc, char* argv[] )
|
||||||
totalGroups++;
|
totalGroups++;
|
||||||
for( const auto& pEntry : group.entries )
|
for( const auto& pEntry : group.entries )
|
||||||
{
|
{
|
||||||
if( pEntry->getType() == LgbEntryType::MapRange )
|
if( pEntry->getType() == LgbEntryType::PopRange )
|
||||||
{
|
{
|
||||||
totalGroupEntries++;
|
totalGroupEntries++;
|
||||||
writeEobjEntry( eobjOut, pEntry.get() );
|
writeEobjEntry( eobjOut, pEntry.get() );
|
||||||
|
|
|
@ -195,7 +195,7 @@ std::string delChar( std::string &str, char del )
|
||||||
int dumpSpawns()
|
int dumpSpawns()
|
||||||
{
|
{
|
||||||
|
|
||||||
Logger::init( "mob_parse" );
|
//Logger::init( "mob_parse" );
|
||||||
|
|
||||||
Logger::info( "Setting up EXD data" );
|
Logger::info( "Setting up EXD data" );
|
||||||
if( !g_exdData.init( datLocation ) )
|
if( !g_exdData.init( datLocation ) )
|
||||||
|
@ -250,7 +250,7 @@ int dumpSpawns()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream out("output_1.txt");
|
std::ofstream out("spawns.txt");
|
||||||
|
|
||||||
int spawngroups = 0;
|
int spawngroups = 0;
|
||||||
for( auto entry : zoneToPacketList )
|
for( auto entry : zoneToPacketList )
|
||||||
|
@ -462,7 +462,8 @@ int dumpTemplates()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream out("output.txt");
|
std::map< std::string, bool > mobDumped;
|
||||||
|
std::ofstream out("templates.txt");
|
||||||
|
|
||||||
for( auto entry : zoneToPacketList )
|
for( auto entry : zoneToPacketList )
|
||||||
{
|
{
|
||||||
|
@ -530,6 +531,11 @@ int dumpTemplates()
|
||||||
std::string name = delChar( nameStruct->singular, ' ' );
|
std::string name = delChar( nameStruct->singular, ' ' );
|
||||||
name = delChar( name, '\'' );
|
name = delChar( name, '\'' );
|
||||||
|
|
||||||
|
if( mobDumped.find( name + "_" + std::to_string( instance.bNPCBase ) ) != mobDumped.end() )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mobDumped[ name +"_" + std::to_string( instance.bNPCBase ) ] = true;
|
||||||
|
|
||||||
std::string output = "INSERT IGNORE INTO `bnpctemplate` ( `Name`, `bNPCBaseId`, `bNPCNameId`, `mainWeaponModel`, `secWeaponModel`, `aggressionMode`, `enemyType`, `pose`, `modelChara`, `displayFlags`, `Look`, `Models`) "
|
std::string output = "INSERT IGNORE INTO `bnpctemplate` ( `Name`, `bNPCBaseId`, `bNPCNameId`, `mainWeaponModel`, `secWeaponModel`, `aggressionMode`, `enemyType`, `pose`, `modelChara`, `displayFlags`, `Look`, `Models`) "
|
||||||
"VALUES ( \"" + name +"_" + std::to_string( instance.bNPCBase ) + "\", "
|
"VALUES ( \"" + name +"_" + std::to_string( instance.bNPCBase ) + "\", "
|
||||||
+ std::to_string( instance.bNPCBase ) + ", "
|
+ std::to_string( instance.bNPCBase ) + ", "
|
||||||
|
@ -593,7 +599,7 @@ int dumpTemplates()
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|
||||||
//dumpTemplates();
|
dumpTemplates();
|
||||||
dumpSpawns();
|
dumpSpawns();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -3,14 +3,20 @@ cmake_policy(SET CMP0015 NEW)
|
||||||
project(Tool_pcb_reader2)
|
project(Tool_pcb_reader2)
|
||||||
|
|
||||||
file(GLOB SERVER_PUBLIC_INCLUDE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*")
|
file(GLOB SERVER_PUBLIC_INCLUDE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*")
|
||||||
file(GLOB SERVER_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}*.c*")
|
file(GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
*.c*
|
||||||
|
nav/*.c*
|
||||||
|
nav/ext/*.c*)
|
||||||
|
|
||||||
add_executable(pcb_reader2 ${SERVER_PUBLIC_INCLUDE_FILES} ${SERVER_SOURCE_FILES})
|
add_executable(pcb_reader2 ${SERVER_PUBLIC_INCLUDE_FILES} ${SERVER_SOURCE_FILES})
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
target_link_libraries (pcb_reader2 common xivdat pthread mysqlclient dl z stdc++fs )
|
target_link_libraries( pcb_reader2 common xivdat pthread mysqlclient dl z stdc++fs Recast Detour DetourTileCache )
|
||||||
else()
|
else()
|
||||||
target_link_libraries (pcb_reader2 common xivdat mysql zlib)
|
target_link_libraries( pcb_reader2 common xivdat mysql zlib Recast Detour DetourTileCache )
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
target_include_directories( pcb_reader2
|
||||||
|
PUBLIC
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/../../deps/" )
|
||||||
|
|
||||||
|
|
135
src/tools/pcb_reader/cache.h
Normal file
135
src/tools/pcb_reader/cache.h
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
#ifndef CACHE_H
|
||||||
|
#define CACHE_H
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "pcb.h"
|
||||||
|
#include "lgb.h"
|
||||||
|
#include "sgb.h"
|
||||||
|
|
||||||
|
#include <datReader/GameData.h>
|
||||||
|
#include <datReader/File.h>
|
||||||
|
#include <datReader/DatCat.h>
|
||||||
|
|
||||||
|
class Cache : public std::enable_shared_from_this< Cache >
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Cache( xiv::dat::GameData* pData )
|
||||||
|
{
|
||||||
|
if( !pData )
|
||||||
|
throw std::runtime_error( "Unable to initialise cache without game data" );
|
||||||
|
m_pData = pData;
|
||||||
|
}
|
||||||
|
~Cache(){}
|
||||||
|
|
||||||
|
std::shared_ptr< SGB_FILE > getSgbFile( const std::string& filepath )
|
||||||
|
{
|
||||||
|
std::scoped_lock lock( m_mutex );
|
||||||
|
|
||||||
|
auto it = m_sgbCache.find( filepath );
|
||||||
|
if( it != m_sgbCache.end() )
|
||||||
|
return it->second;
|
||||||
|
|
||||||
|
auto pFile = loadFile< SGB_FILE >( filepath );
|
||||||
|
m_sgbCache[ filepath ] = pFile;
|
||||||
|
return pFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr< LGB_FILE > getLgbFile( const std::string& filepath )
|
||||||
|
{
|
||||||
|
std::scoped_lock lock( m_mutex );
|
||||||
|
|
||||||
|
auto it = m_lgbCache.find( filepath );
|
||||||
|
if( it != m_lgbCache.end() )
|
||||||
|
return it->second;
|
||||||
|
|
||||||
|
auto pFile = loadFile< LGB_FILE >( filepath );
|
||||||
|
m_lgbCache[ filepath ] = pFile;
|
||||||
|
return pFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr< PCB_FILE > getPcbFile( const std::string& filepath )
|
||||||
|
{
|
||||||
|
std::scoped_lock lock( m_mutex );
|
||||||
|
|
||||||
|
auto it = m_pcbCache.find( filepath );
|
||||||
|
if( it != m_pcbCache.end() )
|
||||||
|
return it->second;
|
||||||
|
|
||||||
|
auto pFile = loadFile< PCB_FILE >( filepath );
|
||||||
|
m_pcbCache[ filepath ] = pFile;
|
||||||
|
return pFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
void purge()
|
||||||
|
{
|
||||||
|
std::scoped_lock lock( m_mutex );
|
||||||
|
_purge();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _purge()
|
||||||
|
{
|
||||||
|
m_lgbCache.clear();
|
||||||
|
m_sgbCache.clear();
|
||||||
|
m_pcbCache.clear();
|
||||||
|
//std::cout << "Purged PCB/SGB/LGB cache \n";
|
||||||
|
}
|
||||||
|
template< typename T >
|
||||||
|
std::shared_ptr< T > loadFile( const std::string& filepath )
|
||||||
|
{
|
||||||
|
auto buf = getFileBuffer( filepath );
|
||||||
|
if( !buf.empty() )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto pFile = std::make_shared< T >( &buf[0] );
|
||||||
|
|
||||||
|
m_totalFiles++;
|
||||||
|
if( m_totalFiles % 1000 == 0 )
|
||||||
|
{
|
||||||
|
_purge();
|
||||||
|
m_totalFiles = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pFile;
|
||||||
|
}
|
||||||
|
catch( std::exception& e )
|
||||||
|
{
|
||||||
|
std::string err( filepath + " " + e.what() );
|
||||||
|
std::cout << err << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector< char > getFileBuffer( const std::string& filepath )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//std::cout << fileName << " \n";
|
||||||
|
auto pFile = m_pData->getFile( filepath );
|
||||||
|
auto& sections = pFile->get_data_sections();
|
||||||
|
auto& section = sections.at( 0 );
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
catch( std::exception& e )
|
||||||
|
{
|
||||||
|
std::vector< char > empty;
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex m_mutex;
|
||||||
|
xiv::dat::GameData* m_pData;
|
||||||
|
std::map< std::string, std::shared_ptr< LGB_FILE > > m_lgbCache;
|
||||||
|
std::map< std::string, std::shared_ptr< SGB_FILE > > m_sgbCache;
|
||||||
|
std::map< std::string, std::shared_ptr< PCB_FILE > > m_pcbCache;
|
||||||
|
int m_totalFiles{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
55
src/tools/pcb_reader/exporter.h
Normal file
55
src/tools/pcb_reader/exporter.h
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#ifndef EXPORTER_H
|
||||||
|
#define EXPORTER_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <future>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "threadpool.h"
|
||||||
|
|
||||||
|
enum ExportFileType : int
|
||||||
|
{
|
||||||
|
WavefrontObj = 0x01,
|
||||||
|
Navmesh = 0x02,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ExportSplitType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
SplitByGroup,
|
||||||
|
SingleZone
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExportedMesh
|
||||||
|
{
|
||||||
|
std::vector< float > verts;
|
||||||
|
std::vector< int > indices;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExportedModel
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::vector< ExportedMesh > meshes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExportedGroup
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::map< std::string, ExportedModel > models;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExportedZone
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::map< std::string, ExportedGroup > groups;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
66
src/tools/pcb_reader/exportmgr.h
Normal file
66
src/tools/pcb_reader/exportmgr.h
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#ifndef EXPORTMGR_H
|
||||||
|
#define EXPORTMGR_H
|
||||||
|
|
||||||
|
#include "exporter.h"
|
||||||
|
#include "navmesh_exporter.h"
|
||||||
|
#include "obj_exporter.h"
|
||||||
|
#include "threadpool.h"
|
||||||
|
|
||||||
|
class ExportMgr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExportMgr( unsigned int maxJobs = 0 )
|
||||||
|
{
|
||||||
|
m_threadpool.addWorkers( maxJobs );
|
||||||
|
}
|
||||||
|
~ExportMgr()
|
||||||
|
{
|
||||||
|
waitForTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
void restart( bool cancel = false, unsigned int maxJobs = 0 )
|
||||||
|
{
|
||||||
|
if( cancel )
|
||||||
|
m_threadpool.cancel();
|
||||||
|
|
||||||
|
m_threadpool.complete();
|
||||||
|
|
||||||
|
m_threadpool.addWorkers( maxJobs );
|
||||||
|
}
|
||||||
|
|
||||||
|
void exportZone(const ExportedZone& zone, ExportFileType exportFileTypes)
|
||||||
|
{
|
||||||
|
m_threadpool.queue( [zone, exportFileTypes]()
|
||||||
|
{
|
||||||
|
if( exportFileTypes & ExportFileType::WavefrontObj )
|
||||||
|
ObjExporter::exportZone( zone );
|
||||||
|
|
||||||
|
if( exportFileTypes & ExportFileType::Navmesh )
|
||||||
|
NavmeshExporter::exportZone( zone );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
void exportGroup( const std::string& zoneName, const ExportedGroup& group, ExportFileType exportFileTypes )
|
||||||
|
{
|
||||||
|
m_threadpool.queue( [zoneName, group, exportFileTypes]()
|
||||||
|
{
|
||||||
|
if( exportFileTypes & ExportFileType::WavefrontObj )
|
||||||
|
{
|
||||||
|
ObjExporter::exportGroup( zoneName, group );
|
||||||
|
}
|
||||||
|
if( exportFileTypes & ExportFileType::Navmesh )
|
||||||
|
{
|
||||||
|
NavmeshExporter::exportGroup( zoneName, group );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForTasks()
|
||||||
|
{
|
||||||
|
m_threadpool.complete();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
ThreadPool m_threadpool;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -9,12 +9,16 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
#include "matrix4.h"
|
#include "matrix4.h"
|
||||||
#include "vec3.h"
|
#include "vec3.h"
|
||||||
#include "sgb.h"
|
#include "sgb.h"
|
||||||
|
|
||||||
// garbage to skip model loading
|
// garbage to skip model loading
|
||||||
extern bool ignoreModels;
|
extern bool noObj;
|
||||||
|
|
||||||
// all credit to
|
// all credit to
|
||||||
// https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/Graphics/Lgb/
|
// https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/Graphics/Lgb/
|
||||||
|
@ -238,6 +242,47 @@ public:
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LGB_COLLISION_BOX_HEADER :
|
||||||
|
public LGB_ENTRY_HEADER
|
||||||
|
{
|
||||||
|
uint8_t unk[100];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LGB_COLLISION_BOX_ENTRY :
|
||||||
|
public LGB_ENTRY
|
||||||
|
{
|
||||||
|
LGB_COLLISION_BOX_HEADER header;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
LGB_COLLISION_BOX_ENTRY( char* buf, uint32_t offset ) :
|
||||||
|
LGB_ENTRY( buf, offset )
|
||||||
|
{
|
||||||
|
header = *reinterpret_cast< LGB_COLLISION_BOX_HEADER* >( buf + offset );
|
||||||
|
header.type = LgbEntryType::CollisionBox;
|
||||||
|
name = std::string( buf + offset + header.nameOffset );
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "\nName: " << name << "Id: " << header.unknown << "\n";
|
||||||
|
ss << "Pos: " << header.translation.x << " " << header.translation.y << " " << header.translation.z << "\n";
|
||||||
|
ss << "Rot?: " << header.rotation.x << " " << header.rotation.y << " " << header.rotation.z << "\n";
|
||||||
|
ss << "Scale?: " << header.scale.x << " " << header.scale.y << " " << header.scale.z << "\n";
|
||||||
|
ss << "00 01 02 03 04 05 06 07 | 08 09 0A 0B 0C 0D 0E 0F\n";
|
||||||
|
ss << "-------------------------------------------------\n";
|
||||||
|
ss << std::hex;
|
||||||
|
ss << std::setw( 2 );
|
||||||
|
ss << std::setfill( '0' );
|
||||||
|
|
||||||
|
for( auto i = 1; i < sizeof( header.unk ); ++i )
|
||||||
|
if( i % 16 == 0 )
|
||||||
|
ss << std::setw(2) << (int)header.unk[i - 1] << "\n";
|
||||||
|
else if( i % 8 == 0 )
|
||||||
|
ss << std::setw(2) << (int)header.unk[i - 1] << " | ";
|
||||||
|
else
|
||||||
|
ss << std::setw(2) << (int)header.unk[i - 1] << " ";
|
||||||
|
ss << "\n";
|
||||||
|
std::cout << ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct LGB_GROUP_HEADER
|
struct LGB_GROUP_HEADER
|
||||||
{
|
{
|
||||||
uint32_t unknown;
|
uint32_t unknown;
|
||||||
|
@ -278,37 +323,28 @@ struct LGB_GROUP
|
||||||
{
|
{
|
||||||
const auto type = *reinterpret_cast<LgbEntryType*>( buf + entryOffset );
|
const auto type = *reinterpret_cast<LgbEntryType*>( buf + entryOffset );
|
||||||
// garbage to skip model loading
|
// garbage to skip model loading
|
||||||
if( !ignoreModels && type == LgbEntryType::BgParts )
|
switch( type )
|
||||||
{
|
{
|
||||||
entries.push_back( std::make_shared< LGB_BGPARTS_ENTRY >( buf, entryOffset ) );
|
case LgbEntryType::BgParts:
|
||||||
|
entries.push_back( std::make_shared< LGB_BGPARTS_ENTRY >( buf, entryOffset ) );
|
||||||
|
break;
|
||||||
|
case LgbEntryType::Gimmick:
|
||||||
|
entries.push_back( std::make_shared< LGB_GIMMICK_ENTRY >( buf, entryOffset ) );
|
||||||
|
break;
|
||||||
|
case LgbEntryType::EventObject:
|
||||||
|
entries.push_back( std::make_shared< LGB_EOBJ_ENTRY >( buf, entryOffset ) );
|
||||||
|
break;
|
||||||
|
case LgbEntryType::CollisionBox:
|
||||||
|
//entries.push_back( std::make_shared< LGB_COLLISION_BOX_ENTRY >( buf, entryOffset ) );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//std::cout << "\t\tUnknown SGB entry! Group: " << name << " type: " << ( int )type << " index: " << i << " entryOffset: " << entryOffset << "\n";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if( !ignoreModels && type == LgbEntryType::Gimmick )
|
|
||||||
{
|
|
||||||
entries.push_back( std::make_shared< LGB_GIMMICK_ENTRY >( buf, entryOffset ) );
|
|
||||||
}
|
|
||||||
else if( type == LgbEntryType::EventNpc )
|
|
||||||
{
|
|
||||||
entries.push_back( std::make_shared< LGB_ENPC_ENTRY >( buf, entryOffset ) );
|
|
||||||
}
|
|
||||||
else if( type == LgbEntryType::EventObject )
|
|
||||||
{
|
|
||||||
entries.push_back( std::make_shared< LGB_EOBJ_ENTRY >( buf, entryOffset ) );
|
|
||||||
}
|
|
||||||
else if( type == LgbEntryType::MapRange )
|
|
||||||
{
|
|
||||||
entries.push_back( std::make_shared< LGB_MAPRANGE_ENTRY >( buf, entryOffset ) );
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
else
|
|
||||||
{
|
|
||||||
entries[i] = nullptr;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch( std::exception& e )
|
catch( std::exception& e )
|
||||||
{
|
{
|
||||||
std::cout << name << " " << e.what() << std::endl;
|
std::cout << ( name + " " + e.what() + "\n" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -331,9 +367,8 @@ struct LGB_FILE
|
||||||
{
|
{
|
||||||
LGB_FILE_HEADER header;
|
LGB_FILE_HEADER header;
|
||||||
std::vector< LGB_GROUP > groups;
|
std::vector< LGB_GROUP > groups;
|
||||||
std::string name;
|
|
||||||
|
|
||||||
LGB_FILE( char* buf, const std::string& name )
|
LGB_FILE( char* buf )
|
||||||
{
|
{
|
||||||
header = *reinterpret_cast< LGB_FILE_HEADER* >( buf );
|
header = *reinterpret_cast< LGB_FILE_HEADER* >( buf );
|
||||||
if( strncmp( &header.magic[ 0 ], "LGB1", 4 ) != 0 || strncmp( &header.magic2[ 0 ], "LGP1", 4 ) != 0 )
|
if( strncmp( &header.magic[ 0 ], "LGB1", 4 ) != 0 || strncmp( &header.magic2[ 0 ], "LGP1", 4 ) != 0 )
|
||||||
|
@ -351,38 +386,4 @@ struct LGB_FILE
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
#if __cplusplus >= 201703L
|
|
||||||
#include <experimental/filesystem>
|
|
||||||
std::map<std::string, LGB_FILE> getLgbFiles( const std::string& dir )
|
|
||||||
{
|
|
||||||
namespace fs = std::experimental::filesystem;
|
|
||||||
std::map<std::string, LGB_FILE> fileMap;
|
|
||||||
for( const auto& path : fs::recursive_directory_iterator( dir ) )
|
|
||||||
{
|
|
||||||
if( path.path().extension() == ".lgb" )
|
|
||||||
{
|
|
||||||
const auto& strPath = path.path().string();
|
|
||||||
auto f = fopen( strPath.c_str(), "rb" );
|
|
||||||
fseek( f, 0, SEEK_END );
|
|
||||||
const auto size = ftell( f );
|
|
||||||
std::vector<char> bytes( size );
|
|
||||||
rewind( f );
|
|
||||||
fread( bytes.data(), 1, size, f );
|
|
||||||
fclose( f );
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LGB_FILE lgbFile( bytes.data() );
|
|
||||||
fileMap.insert( std::make_pair( strPath, lgbFile ) );
|
|
||||||
}
|
|
||||||
catch( std::exception& e )
|
|
||||||
{
|
|
||||||
std::cout << "Unable to load " << strPath << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileMap;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
*/
|
|
||||||
#endif
|
#endif
|
File diff suppressed because it is too large
Load diff
585
src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp
Normal file
585
src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp
Normal file
|
@ -0,0 +1,585 @@
|
||||||
|
#include "TiledNavmeshGenerator.h"
|
||||||
|
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <recastnavigation/Detour/Include/DetourNavMeshBuilder.h>
|
||||||
|
|
||||||
|
namespace fs = std::experimental::filesystem;
|
||||||
|
|
||||||
|
|
||||||
|
inline unsigned int nextPow2( uint32_t v )
|
||||||
|
{
|
||||||
|
v--;
|
||||||
|
v |= v >> 1;
|
||||||
|
v |= v >> 2;
|
||||||
|
v |= v >> 4;
|
||||||
|
v |= v >> 8;
|
||||||
|
v |= v >> 16;
|
||||||
|
v++;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int ilog2( uint32_t v )
|
||||||
|
{
|
||||||
|
uint32_t r;
|
||||||
|
uint32_t shift;
|
||||||
|
r = (v > 0xffff) << 4; v >>= r;
|
||||||
|
shift = (v > 0xff) << 3; v >>= shift; r |= shift;
|
||||||
|
shift = (v > 0xf) << 2; v >>= shift; r |= shift;
|
||||||
|
shift = (v > 0x3) << 1; v >>= shift; r |= shift;
|
||||||
|
r |= (v >> 1);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TiledNavmeshGenerator::init( const std::string& path )
|
||||||
|
{
|
||||||
|
if( !fs::exists( path ) )
|
||||||
|
throw std::runtime_error( "what" );
|
||||||
|
|
||||||
|
// ignore logging/bullshit/etc
|
||||||
|
m_ctx = new rcContext( false );
|
||||||
|
|
||||||
|
printf( "[Navmesh] loading obj: %s\n", path.substr( path.find( "pcb_export" ) - 1 ).c_str() );
|
||||||
|
|
||||||
|
m_mesh = new rcMeshLoaderObj;
|
||||||
|
assert( m_mesh );
|
||||||
|
|
||||||
|
if( !m_mesh->load( path ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] Failed to allocate rcMeshLoaderObj\n" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcCalcBounds( m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax );
|
||||||
|
|
||||||
|
m_chunkyMesh = new rcChunkyTriMesh;
|
||||||
|
assert( m_chunkyMesh );
|
||||||
|
|
||||||
|
if( !rcCreateChunkyTriMesh( m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildTiledNavigation: Failed to build chunky mesh.\n" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: load some bullshit settings from exd
|
||||||
|
|
||||||
|
int gw = 0, gh = 0;
|
||||||
|
rcCalcGridSize( m_meshBMin, m_meshBMax, m_cellSize, &gw, &gh );
|
||||||
|
|
||||||
|
auto ts = static_cast< uint32_t >( m_tileSize );
|
||||||
|
const uint32_t tw = ( gw + ts - 1 ) / ts;
|
||||||
|
const uint32_t th = ( gh + ts - 1 ) / ts;
|
||||||
|
|
||||||
|
printf( "[Navmesh] - Tiles %d x %d\n", tw, th );
|
||||||
|
|
||||||
|
int tileBits = rcMin( ( int ) ilog2( nextPow2( tw * th ) ), 14 );
|
||||||
|
if( tileBits > 14 )
|
||||||
|
tileBits = 14;
|
||||||
|
int polyBits = 22 - tileBits;
|
||||||
|
m_maxTiles = 1 << tileBits;
|
||||||
|
m_maxPolysPerTile = 1 << polyBits;
|
||||||
|
|
||||||
|
printf( "[Navmesh] - %.1fK verts, %.1fK tris\n", m_mesh->getVertCount() / 1000.0f, m_mesh->getTriCount() / 1000.0f );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TiledNavmeshGenerator::~TiledNavmeshGenerator()
|
||||||
|
{
|
||||||
|
delete m_mesh;
|
||||||
|
delete m_chunkyMesh;
|
||||||
|
|
||||||
|
if( m_triareas )
|
||||||
|
delete[] m_triareas;
|
||||||
|
delete m_ctx;
|
||||||
|
|
||||||
|
rcFreeContourSet( m_cset );
|
||||||
|
rcFreeHeightField( m_solid );
|
||||||
|
rcFreeCompactHeightfield(m_chf);
|
||||||
|
rcFreePolyMesh( m_pmesh );
|
||||||
|
rcFreePolyMeshDetail( m_dmesh );
|
||||||
|
dtFreeNavMesh( m_navMesh );
|
||||||
|
dtFreeNavMeshQuery( m_navQuery );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TiledNavmeshGenerator::saveNavmesh( const std::string& name )
|
||||||
|
{
|
||||||
|
assert( m_navMesh );
|
||||||
|
|
||||||
|
// fuck this gay earth
|
||||||
|
auto mesh = const_cast< const dtNavMesh* >( m_navMesh );
|
||||||
|
|
||||||
|
auto dir = fs::current_path().string() + "/pcb_export/" + name + "/";
|
||||||
|
auto fileName = dir + name + ".nav";
|
||||||
|
|
||||||
|
fs::create_directories( dir );
|
||||||
|
|
||||||
|
FILE* fp = fopen( fileName.c_str(), "wb" );
|
||||||
|
if( !fp )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Store header.
|
||||||
|
NavMeshSetHeader header;
|
||||||
|
header.magic = NAVMESHSET_MAGIC;
|
||||||
|
header.version = NAVMESHSET_VERSION;
|
||||||
|
header.numTiles = 0;
|
||||||
|
for( int i = 0; i < mesh->getMaxTiles(); ++i )
|
||||||
|
{
|
||||||
|
auto tile = mesh->getTile( i );
|
||||||
|
if( !tile || !tile->header || !tile->dataSize )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
header.numTiles++;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy( &header.params, mesh->getParams(), sizeof( dtNavMeshParams ) );
|
||||||
|
fwrite( &header, sizeof( NavMeshSetHeader ), 1, fp );
|
||||||
|
|
||||||
|
// Store tiles.
|
||||||
|
for( int i = 0; i < mesh->getMaxTiles(); ++i )
|
||||||
|
{
|
||||||
|
auto tile = mesh->getTile( i );
|
||||||
|
if( !tile || !tile->header || !tile->dataSize )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
NavMeshTileHeader tileHeader;
|
||||||
|
tileHeader.tileRef = mesh->getTileRef( tile );
|
||||||
|
tileHeader.dataSize = tile->dataSize;
|
||||||
|
fwrite( &tileHeader, sizeof( tileHeader ), 1, fp );
|
||||||
|
|
||||||
|
fwrite( tile->data, tile->dataSize, 1, fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
auto pos = fileName.find( "pcb_export" );
|
||||||
|
fileName = fileName.substr( pos - 1 );
|
||||||
|
|
||||||
|
printf( "[Navmesh] Saved navmesh to '%s'\n", fileName.c_str() );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TiledNavmeshGenerator::buildNavmesh()
|
||||||
|
{
|
||||||
|
assert( m_mesh );
|
||||||
|
|
||||||
|
m_navMesh = dtAllocNavMesh();
|
||||||
|
if( !m_navMesh )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildTiledNavigation: Could not allocate navmesh.\n" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dtNavMeshParams params{};
|
||||||
|
rcVcopy( params.orig, m_meshBMin );
|
||||||
|
params.tileWidth = m_tileSize * m_cellSize;
|
||||||
|
params.tileHeight = m_tileSize * m_cellSize;
|
||||||
|
params.maxTiles = m_maxTiles;
|
||||||
|
params.maxPolys = m_maxPolysPerTile;
|
||||||
|
|
||||||
|
dtStatus status;
|
||||||
|
|
||||||
|
status = m_navMesh->init( ¶ms );
|
||||||
|
if( dtStatusFailed( status ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildTiledNavigation: Could not init navmesh.\n" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_navQuery = dtAllocNavMeshQuery();
|
||||||
|
assert( m_navQuery );
|
||||||
|
|
||||||
|
status = m_navQuery->init( m_navMesh, 2048 );
|
||||||
|
if( dtStatusFailed( status ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildTiledNavigation: Could not init Detour navmesh query\n" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: duplicated from above, we can probably cache all this and only do it once
|
||||||
|
int gw = 0, gh = 0;
|
||||||
|
rcCalcGridSize( m_meshBMin, m_meshBMax, m_cellSize, &gw, &gh );
|
||||||
|
auto ts = static_cast< uint32_t >( m_tileSize );
|
||||||
|
const int tw = ( gw + ts - 1 ) / ts;
|
||||||
|
const int th = ( gh + ts - 1 ) / ts;
|
||||||
|
const float tcs = m_tileSize * m_cellSize;
|
||||||
|
|
||||||
|
for( int y = 0; y < th; y++ )
|
||||||
|
{
|
||||||
|
for( int x = 0; x < tw; x++ )
|
||||||
|
{
|
||||||
|
m_lastBuiltTileBmin[ 0 ] = m_meshBMin[ 0 ] + x * tcs;
|
||||||
|
m_lastBuiltTileBmin[ 1 ] = m_meshBMin[ 1 ];
|
||||||
|
m_lastBuiltTileBmin[ 2 ] = m_meshBMin[ 2 ] + y * tcs;
|
||||||
|
|
||||||
|
m_lastBuiltTileBmax[ 0 ] = m_meshBMin[ 0 ] + ( x + 1 ) * tcs;
|
||||||
|
m_lastBuiltTileBmax[ 1 ] = m_meshBMax[ 1 ];
|
||||||
|
m_lastBuiltTileBmax[ 2 ] = m_meshBMin[ 2 ] + ( y + 1 ) * tcs;
|
||||||
|
|
||||||
|
int dataSize = 0;
|
||||||
|
|
||||||
|
unsigned char* data = buildTileMesh( x, y, m_lastBuiltTileBmin, m_lastBuiltTileBmax, dataSize );
|
||||||
|
if( data )
|
||||||
|
{
|
||||||
|
// Remove any previous data (navmesh owns and deletes the data).
|
||||||
|
m_navMesh->removeTile( m_navMesh->getTileRefAt( x, y, 0 ), nullptr, nullptr );
|
||||||
|
|
||||||
|
// Let the navmesh own the data.
|
||||||
|
status = m_navMesh->addTile( data, dataSize, DT_TILE_FREE_DATA, 0, nullptr );
|
||||||
|
|
||||||
|
if( dtStatusFailed( status ) )
|
||||||
|
{
|
||||||
|
dtFree( data );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsigned char* TiledNavmeshGenerator::buildTileMesh( const int tx, const int ty, const float* bmin, const float* bmax,
|
||||||
|
int& dataSize )
|
||||||
|
{
|
||||||
|
const float* verts = m_mesh->getVerts();
|
||||||
|
const int nverts = m_mesh->getVertCount();
|
||||||
|
const int ntris = m_mesh->getTriCount();
|
||||||
|
|
||||||
|
// Init build configuration from GUI
|
||||||
|
memset( &m_cfg, 0, sizeof( m_cfg ) );
|
||||||
|
m_cfg.cs = m_cellSize;
|
||||||
|
m_cfg.ch = m_cellHeight;
|
||||||
|
m_cfg.walkableSlopeAngle = m_agentMaxSlope;
|
||||||
|
m_cfg.walkableHeight = static_cast< int >( ceilf( m_agentHeight / m_cfg.ch ) );
|
||||||
|
m_cfg.walkableClimb = static_cast< int >( floorf( m_agentMaxClimb / m_cfg.ch ) );
|
||||||
|
m_cfg.walkableRadius = static_cast< int >( ceilf( m_agentRadius / m_cfg.cs ) );
|
||||||
|
m_cfg.maxEdgeLen = static_cast< int >( m_edgeMaxLen / m_cellSize );
|
||||||
|
m_cfg.maxSimplificationError = m_edgeMaxError;
|
||||||
|
m_cfg.minRegionArea = static_cast< int >( rcSqr( m_regionMinSize ) ); // Note: area = size*size
|
||||||
|
m_cfg.mergeRegionArea = static_cast< int >( rcSqr( m_regionMergeSize ) ); // Note: area = size*size
|
||||||
|
m_cfg.maxVertsPerPoly = static_cast< int >( m_vertsPerPoly );
|
||||||
|
m_cfg.tileSize = static_cast< int >( m_tileSize );
|
||||||
|
m_cfg.borderSize = m_cfg.walkableRadius + 3; // Reserve enough padding.
|
||||||
|
m_cfg.width = m_cfg.tileSize + m_cfg.borderSize * 2;
|
||||||
|
m_cfg.height = m_cfg.tileSize + m_cfg.borderSize * 2;
|
||||||
|
m_cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist;
|
||||||
|
m_cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError;
|
||||||
|
|
||||||
|
// Expand the heighfield bounding box by border size to find the extents of geometry we need to build this tile.
|
||||||
|
//
|
||||||
|
// This is done in order to make sure that the navmesh tiles connect correctly at the borders,
|
||||||
|
// and the obstacles close to the border work correctly with the dilation process.
|
||||||
|
// No polygons (or contours) will be created on the border area.
|
||||||
|
//
|
||||||
|
// IMPORTANT!
|
||||||
|
//
|
||||||
|
// :''''''''':
|
||||||
|
// : +-----+ :
|
||||||
|
// : | | :
|
||||||
|
// : | |<--- tile to build
|
||||||
|
// : | | :
|
||||||
|
// : +-----+ :<-- geometry needed
|
||||||
|
// :.........:
|
||||||
|
//
|
||||||
|
// You should use this bounding box to query your input geometry.
|
||||||
|
//
|
||||||
|
// For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size
|
||||||
|
// you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8 neighbours,
|
||||||
|
// or use the bounding box below to only pass in a sliver of each of the 8 neighbours.
|
||||||
|
rcVcopy( m_cfg.bmin, bmin );
|
||||||
|
rcVcopy( m_cfg.bmax, bmax );
|
||||||
|
m_cfg.bmin[ 0 ] -= m_cfg.borderSize * m_cfg.cs;
|
||||||
|
m_cfg.bmin[ 2 ] -= m_cfg.borderSize * m_cfg.cs;
|
||||||
|
m_cfg.bmax[ 0 ] += m_cfg.borderSize * m_cfg.cs;
|
||||||
|
m_cfg.bmax[ 2 ] += m_cfg.borderSize * m_cfg.cs;
|
||||||
|
|
||||||
|
m_solid = rcAllocHeightfield();
|
||||||
|
if( !m_solid )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Out of memory 'solid'.\n" );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !rcCreateHeightfield( m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not create solid heightfield.\n" );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate array that can hold triangle flags.
|
||||||
|
// If you have multiple meshes you need to process, allocate
|
||||||
|
// and array which can hold the max number of triangles you need to process.
|
||||||
|
m_triareas = new unsigned char[ m_chunkyMesh->maxTrisPerChunk ];
|
||||||
|
if( !m_triareas )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Out of memory 'm_triareas' (%d).\n", m_chunkyMesh->maxTrisPerChunk );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
float tbmin[ 2 ];
|
||||||
|
float tbmax[ 2 ];
|
||||||
|
tbmin[ 0 ] = m_cfg.bmin[ 0 ];
|
||||||
|
tbmin[ 1 ] = m_cfg.bmin[ 2 ];
|
||||||
|
tbmax[ 0 ] = m_cfg.bmax[ 0 ];
|
||||||
|
tbmax[ 1 ] = m_cfg.bmax[ 2 ];
|
||||||
|
|
||||||
|
int cid[512];// TODO: Make grow when returning too many items.
|
||||||
|
const int ncid = rcGetChunksOverlappingRect( m_chunkyMesh, tbmin, tbmax, cid, 512 );
|
||||||
|
|
||||||
|
if( !ncid )
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
m_tileTriCount = 0;
|
||||||
|
|
||||||
|
for( int i = 0; i < ncid; ++i )
|
||||||
|
{
|
||||||
|
const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[ cid[ i ] ];
|
||||||
|
const int* ctris = &m_chunkyMesh->tris[ node.i * 3 ];
|
||||||
|
const int nctris = node.n;
|
||||||
|
|
||||||
|
m_tileTriCount += nctris;
|
||||||
|
|
||||||
|
memset( m_triareas, 0, nctris * sizeof( unsigned char ) );
|
||||||
|
rcMarkWalkableTriangles( m_ctx, m_cfg.walkableSlopeAngle, verts, nverts, ctris, nctris, m_triareas );
|
||||||
|
if( !rcRasterizeTriangles( m_ctx, verts, nverts, ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb ) )
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] m_triareas;
|
||||||
|
m_triareas = nullptr;
|
||||||
|
|
||||||
|
// Once all geometry is rasterized, we do initial pass of filtering to
|
||||||
|
// remove unwanted overhangs caused by the conservative rasterization
|
||||||
|
// as well as filter spans where the character cannot possibly stand.
|
||||||
|
rcFilterLowHangingWalkableObstacles( m_ctx, m_cfg.walkableClimb, *m_solid );
|
||||||
|
rcFilterLedgeSpans( m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid );
|
||||||
|
rcFilterWalkableLowHeightSpans( m_ctx, m_cfg.walkableHeight, *m_solid );
|
||||||
|
|
||||||
|
// Compact the heightfield so that it is faster to handle from now on.
|
||||||
|
// This will result more cache coherent data as well as the neighbours
|
||||||
|
// between walkable cells will be calculated.
|
||||||
|
m_chf = rcAllocCompactHeightfield();
|
||||||
|
if( !m_chf )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Out of memory 'chf'." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if( !rcBuildCompactHeightfield( m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not build compact data." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcFreeHeightField( m_solid );
|
||||||
|
m_solid = nullptr;
|
||||||
|
|
||||||
|
// Erode the walkable area by agent radius.
|
||||||
|
if( !rcErodeWalkableArea( m_ctx, m_cfg.walkableRadius, *m_chf ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not erode." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Optional) Mark areas.
|
||||||
|
// const ConvexVolume* vols = m_mesh->getConvexVolumes();
|
||||||
|
// for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i)
|
||||||
|
// rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf);
|
||||||
|
|
||||||
|
// Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas.
|
||||||
|
// There are 3 martitioning methods, each with some pros and cons:
|
||||||
|
// 1) Watershed partitioning
|
||||||
|
// - the classic Recast partitioning
|
||||||
|
// - creates the nicest tessellation
|
||||||
|
// - usually slowest
|
||||||
|
// - partitions the heightfield into nice regions without holes or overlaps
|
||||||
|
// - the are some corner cases where this method creates produces holes and overlaps
|
||||||
|
// - holes may appear when a small obstacles is close to large open area (triangulation can handle this)
|
||||||
|
// - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail
|
||||||
|
// * generally the best choice if you precompute the nacmesh, use this if you have large open areas
|
||||||
|
// 2) Monotone partioning
|
||||||
|
// - fastest
|
||||||
|
// - partitions the heightfield into regions without holes and overlaps (guaranteed)
|
||||||
|
// - creates long thin polygons, which sometimes causes paths with detours
|
||||||
|
// * use this if you want fast navmesh generation
|
||||||
|
// 3) Layer partitoining
|
||||||
|
// - quite fast
|
||||||
|
// - partitions the heighfield into non-overlapping regions
|
||||||
|
// - relies on the triangulation code to cope with holes (thus slower than monotone partitioning)
|
||||||
|
// - produces better triangles than monotone partitioning
|
||||||
|
// - does not have the corner cases of watershed partitioning
|
||||||
|
// - can be slow and create a bit ugly tessellation (still better than monotone)
|
||||||
|
// if you have large open areas with small obstacles (not a problem if you use tiles)
|
||||||
|
// * good choice to use for tiled navmesh with medium and small sized tiles
|
||||||
|
|
||||||
|
if( m_partitionType == SAMPLE_PARTITION_WATERSHED )
|
||||||
|
{
|
||||||
|
// Prepare for region partitioning, by calculating distance field along the walkable surface.
|
||||||
|
if( !rcBuildDistanceField( m_ctx, *m_chf ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not build distance field." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partition the walkable surface into simple regions without holes.
|
||||||
|
if( !rcBuildRegions( m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not build watershed regions." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( m_partitionType == SAMPLE_PARTITION_MONOTONE )
|
||||||
|
{
|
||||||
|
// Partition the walkable surface into simple regions without holes.
|
||||||
|
// Monotone partitioning does not need distancefield.
|
||||||
|
if( !rcBuildRegionsMonotone( m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not build monotone regions." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // SAMPLE_PARTITION_LAYERS
|
||||||
|
{
|
||||||
|
// Partition the walkable surface into simple regions without holes.
|
||||||
|
if( !rcBuildLayerRegions( m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not build layer regions." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create contours.
|
||||||
|
m_cset = rcAllocContourSet();
|
||||||
|
if( !m_cset )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Out of memory 'cset'." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if( !rcBuildContours( m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not create contours." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( m_cset->nconts == 0 )
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build polygon navmesh from the contours.
|
||||||
|
m_pmesh = rcAllocPolyMesh();
|
||||||
|
if( !m_pmesh )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Out of memory 'pmesh'." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if( !rcBuildPolyMesh( m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could not triangulate contours." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build detail mesh.
|
||||||
|
m_dmesh = rcAllocPolyMeshDetail();
|
||||||
|
if( !m_dmesh )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Out of memory 'dmesh'." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !rcBuildPolyMeshDetail( m_ctx, *m_pmesh, *m_chf,
|
||||||
|
m_cfg.detailSampleDist, m_cfg.detailSampleMaxError,
|
||||||
|
*m_dmesh ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] buildNavigation: Could build polymesh detail." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcFreeCompactHeightfield( m_chf );
|
||||||
|
rcFreeContourSet( m_cset );
|
||||||
|
m_chf = nullptr;
|
||||||
|
m_cset = nullptr;
|
||||||
|
|
||||||
|
unsigned char* navData = 0;
|
||||||
|
int navDataSize = 0;
|
||||||
|
if( m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON )
|
||||||
|
{
|
||||||
|
if( m_pmesh->nverts >= 0xffff )
|
||||||
|
{
|
||||||
|
// The vertex indices are ushorts, and cannot point to more than 0xffff vertices.
|
||||||
|
printf( "[Navmesh] Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update poly flags from areas.
|
||||||
|
for( int i = 0; i < m_pmesh->npolys; ++i )
|
||||||
|
{
|
||||||
|
if( m_pmesh->areas[ i ] == RC_WALKABLE_AREA )
|
||||||
|
m_pmesh->areas[ i ] = SAMPLE_POLYAREA_GROUND;
|
||||||
|
|
||||||
|
if( m_pmesh->areas[ i ] == SAMPLE_POLYAREA_GROUND ||
|
||||||
|
m_pmesh->areas[ i ] == SAMPLE_POLYAREA_GRASS ||
|
||||||
|
m_pmesh->areas[ i ] == SAMPLE_POLYAREA_ROAD )
|
||||||
|
{
|
||||||
|
m_pmesh->flags[ i ] = SAMPLE_POLYFLAGS_WALK;
|
||||||
|
}
|
||||||
|
else if( m_pmesh->areas[ i ] == SAMPLE_POLYAREA_WATER )
|
||||||
|
{
|
||||||
|
m_pmesh->flags[ i ] = SAMPLE_POLYFLAGS_SWIM;
|
||||||
|
}
|
||||||
|
else if( m_pmesh->areas[ i ] == SAMPLE_POLYAREA_DOOR )
|
||||||
|
{
|
||||||
|
m_pmesh->flags[ i ] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dtNavMeshCreateParams params;
|
||||||
|
memset( ¶ms, 0, sizeof( params ) );
|
||||||
|
params.verts = m_pmesh->verts;
|
||||||
|
params.vertCount = m_pmesh->nverts;
|
||||||
|
params.polys = m_pmesh->polys;
|
||||||
|
params.polyAreas = m_pmesh->areas;
|
||||||
|
params.polyFlags = m_pmesh->flags;
|
||||||
|
params.polyCount = m_pmesh->npolys;
|
||||||
|
params.nvp = m_pmesh->nvp;
|
||||||
|
params.detailMeshes = m_dmesh->meshes;
|
||||||
|
params.detailVerts = m_dmesh->verts;
|
||||||
|
params.detailVertsCount = m_dmesh->nverts;
|
||||||
|
params.detailTris = m_dmesh->tris;
|
||||||
|
params.detailTriCount = m_dmesh->ntris;
|
||||||
|
|
||||||
|
params.offMeshConVerts = nullptr;
|
||||||
|
params.offMeshConRad = nullptr;
|
||||||
|
params.offMeshConDir = nullptr;
|
||||||
|
params.offMeshConAreas = nullptr;
|
||||||
|
params.offMeshConFlags = nullptr;
|
||||||
|
params.offMeshConUserID = nullptr;
|
||||||
|
params.offMeshConCount = 0;
|
||||||
|
|
||||||
|
params.walkableHeight = m_agentHeight;
|
||||||
|
params.walkableRadius = m_agentRadius;
|
||||||
|
params.walkableClimb = m_agentMaxClimb;
|
||||||
|
params.tileX = tx;
|
||||||
|
params.tileY = ty;
|
||||||
|
params.tileLayer = 0;
|
||||||
|
rcVcopy( params.bmin, m_pmesh->bmin );
|
||||||
|
rcVcopy( params.bmax, m_pmesh->bmax );
|
||||||
|
params.cs = m_cfg.cs;
|
||||||
|
params.ch = m_cfg.ch;
|
||||||
|
params.buildBvTree = true;
|
||||||
|
|
||||||
|
if( !dtCreateNavMeshData( ¶ms, &navData, &navDataSize ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] Could not build Detour navmesh." );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rcFreePolyMesh( m_pmesh );
|
||||||
|
rcFreePolyMeshDetail( m_dmesh );
|
||||||
|
m_pmesh = nullptr;
|
||||||
|
m_dmesh = nullptr;
|
||||||
|
|
||||||
|
dataSize = navDataSize;
|
||||||
|
return navData;
|
||||||
|
}
|
124
src/tools/pcb_reader/nav/TiledNavmeshGenerator.h
Normal file
124
src/tools/pcb_reader/nav/TiledNavmeshGenerator.h
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
#ifndef SAPPHIRE_TILEDNAVMESHGENERATOR_H
|
||||||
|
#define SAPPHIRE_TILEDNAVMESHGENERATOR_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "ext/MeshLoaderObj.h"
|
||||||
|
#include "ext/ChunkyTriMesh.h"
|
||||||
|
|
||||||
|
#include "recastnavigation/Detour/Include/DetourNavMesh.h"
|
||||||
|
#include "recastnavigation/Detour/Include/DetourNavMeshQuery.h"
|
||||||
|
#include "recastnavigation/Recast/Include/Recast.h"
|
||||||
|
|
||||||
|
class TiledNavmeshGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum SamplePartitionType
|
||||||
|
{
|
||||||
|
SAMPLE_PARTITION_WATERSHED,
|
||||||
|
SAMPLE_PARTITION_MONOTONE,
|
||||||
|
SAMPLE_PARTITION_LAYERS,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SamplePolyAreas
|
||||||
|
{
|
||||||
|
SAMPLE_POLYAREA_GROUND,
|
||||||
|
SAMPLE_POLYAREA_WATER,
|
||||||
|
SAMPLE_POLYAREA_ROAD,
|
||||||
|
SAMPLE_POLYAREA_DOOR,
|
||||||
|
SAMPLE_POLYAREA_GRASS,
|
||||||
|
SAMPLE_POLYAREA_JUMP,
|
||||||
|
};
|
||||||
|
enum SamplePolyFlags
|
||||||
|
{
|
||||||
|
SAMPLE_POLYFLAGS_WALK = 0x01, // Ability to walk (ground, grass, road)
|
||||||
|
SAMPLE_POLYFLAGS_SWIM = 0x02, // Ability to swim (water).
|
||||||
|
SAMPLE_POLYFLAGS_DOOR = 0x04, // Ability to move through doors.
|
||||||
|
SAMPLE_POLYFLAGS_JUMP = 0x08, // Ability to jump.
|
||||||
|
SAMPLE_POLYFLAGS_DISABLED = 0x10, // Disabled polygon
|
||||||
|
SAMPLE_POLYFLAGS_ALL = 0xffff // All abilities.
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET';
|
||||||
|
static const int NAVMESHSET_VERSION = 1;
|
||||||
|
|
||||||
|
struct NavMeshSetHeader
|
||||||
|
{
|
||||||
|
int magic;
|
||||||
|
int version;
|
||||||
|
int numTiles;
|
||||||
|
dtNavMeshParams params;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NavMeshTileHeader
|
||||||
|
{
|
||||||
|
dtTileRef tileRef;
|
||||||
|
int dataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TiledNavmeshGenerator() = default;
|
||||||
|
~TiledNavmeshGenerator();
|
||||||
|
|
||||||
|
bool init( const std::string& path );
|
||||||
|
unsigned char* buildTileMesh( const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize );
|
||||||
|
bool buildNavmesh();
|
||||||
|
void saveNavmesh( const std::string& name );
|
||||||
|
|
||||||
|
private:
|
||||||
|
rcConfig m_cfg;
|
||||||
|
|
||||||
|
rcMeshLoaderObj* m_mesh;
|
||||||
|
rcChunkyTriMesh* m_chunkyMesh;
|
||||||
|
|
||||||
|
rcContext* m_ctx;
|
||||||
|
dtNavMesh* m_navMesh;
|
||||||
|
dtNavMeshQuery* m_navQuery;
|
||||||
|
rcHeightfield* m_solid;
|
||||||
|
rcContourSet* m_cset;
|
||||||
|
rcPolyMesh* m_pmesh;
|
||||||
|
rcPolyMeshDetail* m_dmesh;
|
||||||
|
|
||||||
|
rcCompactHeightfield* m_chf;
|
||||||
|
|
||||||
|
unsigned char* m_triareas;
|
||||||
|
|
||||||
|
int m_maxTiles = 0;
|
||||||
|
int m_maxPolysPerTile = 0;
|
||||||
|
|
||||||
|
int m_tileTriCount = 0;
|
||||||
|
|
||||||
|
int m_partitionType = SamplePartitionType::SAMPLE_PARTITION_WATERSHED;
|
||||||
|
|
||||||
|
float m_meshBMin[ 3 ];
|
||||||
|
float m_meshBMax[ 3 ];
|
||||||
|
|
||||||
|
float m_lastBuiltTileBmin[ 3 ];
|
||||||
|
float m_lastBuiltTileBmax[ 3 ];
|
||||||
|
|
||||||
|
// options
|
||||||
|
float m_tileSize = 160.f;
|
||||||
|
float m_cellSize = 0.2f;
|
||||||
|
float m_cellHeight = 0.2f;
|
||||||
|
|
||||||
|
float m_agentMaxSlope = 56.f;
|
||||||
|
float m_agentHeight = 2.f;
|
||||||
|
float m_agentMaxClimb = 0.6f;
|
||||||
|
float m_agentRadius = 0.5f;
|
||||||
|
|
||||||
|
float m_regionMinSize = 8.f;
|
||||||
|
float m_regionMergeSize = 20.f;
|
||||||
|
|
||||||
|
float m_edgeMaxLen = 12.f;
|
||||||
|
float m_edgeMaxError = 1.4f;
|
||||||
|
float m_vertsPerPoly = 6.f;
|
||||||
|
|
||||||
|
float m_detailSampleDist = 6.f;
|
||||||
|
float m_detailSampleMaxError = 1.f;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //SAPPHIRE_TILEDNAVMESHGENERATOR_H
|
331
src/tools/pcb_reader/nav/ext/ChunkyTriMesh.cpp
Normal file
331
src/tools/pcb_reader/nav/ext/ChunkyTriMesh.cpp
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "ChunkyTriMesh.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
struct BoundsItem
|
||||||
|
{
|
||||||
|
float bmin[ 2 ];
|
||||||
|
float bmax[ 2 ];
|
||||||
|
int i;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int compareItemX( const void* va, const void* vb )
|
||||||
|
{
|
||||||
|
const BoundsItem* a = ( const BoundsItem* ) va;
|
||||||
|
const BoundsItem* b = ( const BoundsItem* ) vb;
|
||||||
|
if( a->bmin[ 0 ] < b->bmin[ 0 ] )
|
||||||
|
return -1;
|
||||||
|
if( a->bmin[ 0 ] > b->bmin[ 0 ] )
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compareItemY( const void* va, const void* vb )
|
||||||
|
{
|
||||||
|
const BoundsItem* a = ( const BoundsItem* ) va;
|
||||||
|
const BoundsItem* b = ( const BoundsItem* ) vb;
|
||||||
|
if( a->bmin[ 1 ] < b->bmin[ 1 ] )
|
||||||
|
return -1;
|
||||||
|
if( a->bmin[ 1 ] > b->bmin[ 1 ] )
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void calcExtends( const BoundsItem* items, const int /*nitems*/,
|
||||||
|
const int imin, const int imax,
|
||||||
|
float* bmin, float* bmax )
|
||||||
|
{
|
||||||
|
bmin[ 0 ] = items[ imin ].bmin[ 0 ];
|
||||||
|
bmin[ 1 ] = items[ imin ].bmin[ 1 ];
|
||||||
|
|
||||||
|
bmax[ 0 ] = items[ imin ].bmax[ 0 ];
|
||||||
|
bmax[ 1 ] = items[ imin ].bmax[ 1 ];
|
||||||
|
|
||||||
|
for( int i = imin + 1; i < imax; ++i )
|
||||||
|
{
|
||||||
|
const BoundsItem& it = items[ i ];
|
||||||
|
if( it.bmin[ 0 ] < bmin[ 0 ] )
|
||||||
|
bmin[ 0 ] = it.bmin[ 0 ];
|
||||||
|
if( it.bmin[ 1 ] < bmin[ 1 ] )
|
||||||
|
bmin[ 1 ] = it.bmin[ 1 ];
|
||||||
|
|
||||||
|
if( it.bmax[ 0 ] > bmax[ 0 ] )
|
||||||
|
bmax[ 0 ] = it.bmax[ 0 ];
|
||||||
|
if( it.bmax[ 1 ] > bmax[ 1 ] )
|
||||||
|
bmax[ 1 ] = it.bmax[ 1 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int longestAxis( float x, float y )
|
||||||
|
{
|
||||||
|
return y > x ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void subdivide( BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk,
|
||||||
|
int& curNode, rcChunkyTriMeshNode* nodes, const int maxNodes,
|
||||||
|
int& curTri, int* outTris, const int* inTris )
|
||||||
|
{
|
||||||
|
int inum = imax - imin;
|
||||||
|
int icur = curNode;
|
||||||
|
|
||||||
|
if( curNode > maxNodes )
|
||||||
|
return;
|
||||||
|
|
||||||
|
rcChunkyTriMeshNode& node = nodes[ curNode++ ];
|
||||||
|
|
||||||
|
if( inum <= trisPerChunk )
|
||||||
|
{
|
||||||
|
// Leaf
|
||||||
|
calcExtends( items, nitems, imin, imax, node.bmin, node.bmax );
|
||||||
|
|
||||||
|
// Copy triangles.
|
||||||
|
node.i = curTri;
|
||||||
|
node.n = inum;
|
||||||
|
|
||||||
|
for( int i = imin; i < imax; ++i )
|
||||||
|
{
|
||||||
|
const int* src = &inTris[ items[ i ].i * 3 ];
|
||||||
|
int* dst = &outTris[ curTri * 3 ];
|
||||||
|
curTri++;
|
||||||
|
dst[ 0 ] = src[ 0 ];
|
||||||
|
dst[ 1 ] = src[ 1 ];
|
||||||
|
dst[ 2 ] = src[ 2 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Split
|
||||||
|
calcExtends( items, nitems, imin, imax, node.bmin, node.bmax );
|
||||||
|
|
||||||
|
int axis = longestAxis( node.bmax[ 0 ] - node.bmin[ 0 ],
|
||||||
|
node.bmax[ 1 ] - node.bmin[ 1 ] );
|
||||||
|
|
||||||
|
if( axis == 0 )
|
||||||
|
{
|
||||||
|
// Sort along x-axis
|
||||||
|
qsort( items + imin, static_cast<size_t>(inum), sizeof( BoundsItem ), compareItemX );
|
||||||
|
}
|
||||||
|
else if( axis == 1 )
|
||||||
|
{
|
||||||
|
// Sort along y-axis
|
||||||
|
qsort( items + imin, static_cast<size_t>(inum), sizeof( BoundsItem ), compareItemY );
|
||||||
|
}
|
||||||
|
|
||||||
|
int isplit = imin + inum / 2;
|
||||||
|
|
||||||
|
// Left
|
||||||
|
subdivide( items, nitems, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris );
|
||||||
|
// Right
|
||||||
|
subdivide( items, nitems, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris );
|
||||||
|
|
||||||
|
int iescape = curNode - icur;
|
||||||
|
// Negative index means escape.
|
||||||
|
node.i = -iescape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rcCreateChunkyTriMesh( const float* verts, const int* tris, int ntris,
|
||||||
|
int trisPerChunk, rcChunkyTriMesh* cm )
|
||||||
|
{
|
||||||
|
int nchunks = ( ntris + trisPerChunk - 1 ) / trisPerChunk;
|
||||||
|
|
||||||
|
cm->nodes = new rcChunkyTriMeshNode[nchunks * 4];
|
||||||
|
if( !cm->nodes )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cm->tris = new int[ntris * 3];
|
||||||
|
if( !cm->tris )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cm->ntris = ntris;
|
||||||
|
|
||||||
|
// Build tree
|
||||||
|
BoundsItem* items = new BoundsItem[ntris];
|
||||||
|
if( !items )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for( int i = 0; i < ntris; i++ )
|
||||||
|
{
|
||||||
|
const int* t = &tris[ i * 3 ];
|
||||||
|
BoundsItem& it = items[ i ];
|
||||||
|
it.i = i;
|
||||||
|
// Calc triangle XZ bounds.
|
||||||
|
it.bmin[ 0 ] = it.bmax[ 0 ] = verts[ t[ 0 ] * 3 + 0 ];
|
||||||
|
it.bmin[ 1 ] = it.bmax[ 1 ] = verts[ t[ 0 ] * 3 + 2 ];
|
||||||
|
for( int j = 1; j < 3; ++j )
|
||||||
|
{
|
||||||
|
const float* v = &verts[ t[ j ] * 3 ];
|
||||||
|
if( v[ 0 ] < it.bmin[ 0 ] )
|
||||||
|
it.bmin[ 0 ] = v[ 0 ];
|
||||||
|
if( v[ 2 ] < it.bmin[ 1 ] )
|
||||||
|
it.bmin[ 1 ] = v[ 2 ];
|
||||||
|
|
||||||
|
if( v[ 0 ] > it.bmax[ 0 ] )
|
||||||
|
it.bmax[ 0 ] = v[ 0 ];
|
||||||
|
if( v[ 2 ] > it.bmax[ 1 ] )
|
||||||
|
it.bmax[ 1 ] = v[ 2 ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int curTri = 0;
|
||||||
|
int curNode = 0;
|
||||||
|
subdivide( items, ntris, 0, ntris, trisPerChunk, curNode, cm->nodes, nchunks * 4, curTri, cm->tris, tris );
|
||||||
|
|
||||||
|
delete[] items;
|
||||||
|
|
||||||
|
cm->nnodes = curNode;
|
||||||
|
|
||||||
|
// Calc max tris per node.
|
||||||
|
cm->maxTrisPerChunk = 0;
|
||||||
|
for( int i = 0; i < cm->nnodes; ++i )
|
||||||
|
{
|
||||||
|
rcChunkyTriMeshNode& node = cm->nodes[ i ];
|
||||||
|
const bool isLeaf = node.i >= 0;
|
||||||
|
if( !isLeaf )
|
||||||
|
continue;
|
||||||
|
if( node.n > cm->maxTrisPerChunk )
|
||||||
|
cm->maxTrisPerChunk = node.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline bool checkOverlapRect( const float amin[2], const float amax[2],
|
||||||
|
const float bmin[2], const float bmax[2] )
|
||||||
|
{
|
||||||
|
bool overlap = true;
|
||||||
|
overlap = ( amin[ 0 ] > bmax[ 0 ] || amax[ 0 ] < bmin[ 0 ] ) ? false : overlap;
|
||||||
|
overlap = ( amin[ 1 ] > bmax[ 1 ] || amax[ 1 ] < bmin[ 1 ] ) ? false : overlap;
|
||||||
|
return overlap;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rcGetChunksOverlappingRect( const rcChunkyTriMesh* cm,
|
||||||
|
float bmin[2], float bmax[2],
|
||||||
|
int* ids, const int maxIds )
|
||||||
|
{
|
||||||
|
// Traverse tree
|
||||||
|
int i = 0;
|
||||||
|
int n = 0;
|
||||||
|
while( i < cm->nnodes )
|
||||||
|
{
|
||||||
|
const rcChunkyTriMeshNode* node = &cm->nodes[ i ];
|
||||||
|
const bool overlap = checkOverlapRect( bmin, bmax, node->bmin, node->bmax );
|
||||||
|
const bool isLeafNode = node->i >= 0;
|
||||||
|
|
||||||
|
if( isLeafNode && overlap )
|
||||||
|
{
|
||||||
|
if( n < maxIds )
|
||||||
|
{
|
||||||
|
ids[ n ] = i;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( overlap || isLeafNode )
|
||||||
|
i++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const int escapeIndex = -node->i;
|
||||||
|
i += escapeIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool checkOverlapSegment( const float p[2], const float q[2],
|
||||||
|
const float bmin[2], const float bmax[2] )
|
||||||
|
{
|
||||||
|
static const float EPSILON = 1e-6f;
|
||||||
|
|
||||||
|
float tmin = 0;
|
||||||
|
float tmax = 1;
|
||||||
|
float d[2];
|
||||||
|
d[ 0 ] = q[ 0 ] - p[ 0 ];
|
||||||
|
d[ 1 ] = q[ 1 ] - p[ 1 ];
|
||||||
|
|
||||||
|
for( int i = 0; i < 2; i++ )
|
||||||
|
{
|
||||||
|
if( fabsf( d[ i ] ) < EPSILON )
|
||||||
|
{
|
||||||
|
// Ray is parallel to slab. No hit if origin not within slab
|
||||||
|
if( p[ i ] < bmin[ i ] || p[ i ] > bmax[ i ] )
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Compute intersection t value of ray with near and far plane of slab
|
||||||
|
float ood = 1.0f / d[ i ];
|
||||||
|
float t1 = ( bmin[ i ] - p[ i ] ) * ood;
|
||||||
|
float t2 = ( bmax[ i ] - p[ i ] ) * ood;
|
||||||
|
if( t1 > t2 )
|
||||||
|
{
|
||||||
|
float tmp = t1;
|
||||||
|
t1 = t2;
|
||||||
|
t2 = tmp;
|
||||||
|
}
|
||||||
|
if( t1 > tmin )
|
||||||
|
tmin = t1;
|
||||||
|
if( t2 < tmax )
|
||||||
|
tmax = t2;
|
||||||
|
if( tmin > tmax )
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rcGetChunksOverlappingSegment( const rcChunkyTriMesh* cm,
|
||||||
|
float p[2], float q[2],
|
||||||
|
int* ids, const int maxIds )
|
||||||
|
{
|
||||||
|
// Traverse tree
|
||||||
|
int i = 0;
|
||||||
|
int n = 0;
|
||||||
|
while( i < cm->nnodes )
|
||||||
|
{
|
||||||
|
const rcChunkyTriMeshNode* node = &cm->nodes[ i ];
|
||||||
|
const bool overlap = checkOverlapSegment( p, q, node->bmin, node->bmax );
|
||||||
|
const bool isLeafNode = node->i >= 0;
|
||||||
|
|
||||||
|
if( isLeafNode && overlap )
|
||||||
|
{
|
||||||
|
if( n < maxIds )
|
||||||
|
{
|
||||||
|
ids[ n ] = i;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( overlap || isLeafNode )
|
||||||
|
i++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const int escapeIndex = -node->i;
|
||||||
|
i += escapeIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
68
src/tools/pcb_reader/nav/ext/ChunkyTriMesh.h
Normal file
68
src/tools/pcb_reader/nav/ext/ChunkyTriMesh.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef CHUNKYTRIMESH_H
|
||||||
|
#define CHUNKYTRIMESH_H
|
||||||
|
|
||||||
|
struct rcChunkyTriMeshNode
|
||||||
|
{
|
||||||
|
float bmin[ 2 ];
|
||||||
|
float bmax[ 2 ];
|
||||||
|
int i;
|
||||||
|
int n;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rcChunkyTriMesh
|
||||||
|
{
|
||||||
|
inline rcChunkyTriMesh() :
|
||||||
|
nodes( 0 ), nnodes( 0 ), tris( 0 ), ntris( 0 ), maxTrisPerChunk( 0 )
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ~rcChunkyTriMesh()
|
||||||
|
{
|
||||||
|
delete[] nodes;
|
||||||
|
delete[] tris;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcChunkyTriMeshNode* nodes;
|
||||||
|
int nnodes;
|
||||||
|
int* tris;
|
||||||
|
int ntris;
|
||||||
|
int maxTrisPerChunk;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Explicitly disabled copy constructor and copy assignment operator.
|
||||||
|
rcChunkyTriMesh( const rcChunkyTriMesh& );
|
||||||
|
|
||||||
|
rcChunkyTriMesh& operator=( const rcChunkyTriMesh& );
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates partitioned triangle mesh (AABB tree),
|
||||||
|
/// where each node contains at max trisPerChunk triangles.
|
||||||
|
bool rcCreateChunkyTriMesh( const float* verts, const int* tris, int ntris,
|
||||||
|
int trisPerChunk, rcChunkyTriMesh* cm );
|
||||||
|
|
||||||
|
/// Returns the chunk indices which overlap the input rectable.
|
||||||
|
int rcGetChunksOverlappingRect( const rcChunkyTriMesh* cm, float bmin[2], float bmax[2], int* ids, const int maxIds );
|
||||||
|
|
||||||
|
/// Returns the chunk indices which overlap the input segment.
|
||||||
|
int rcGetChunksOverlappingSegment( const rcChunkyTriMesh* cm, float p[2], float q[2], int* ids, const int maxIds );
|
||||||
|
|
||||||
|
|
||||||
|
#endif // CHUNKYTRIMESH_H
|
252
src/tools/pcb_reader/nav/ext/MeshLoaderObj.cpp
Normal file
252
src/tools/pcb_reader/nav/ext/MeshLoaderObj.cpp
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "MeshLoaderObj.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
rcMeshLoaderObj::rcMeshLoaderObj() :
|
||||||
|
m_scale( 1.0f ),
|
||||||
|
m_verts( 0 ),
|
||||||
|
m_tris( 0 ),
|
||||||
|
m_normals( 0 ),
|
||||||
|
m_vertCount( 0 ),
|
||||||
|
m_triCount( 0 )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
rcMeshLoaderObj::~rcMeshLoaderObj()
|
||||||
|
{
|
||||||
|
delete[] m_verts;
|
||||||
|
delete[] m_normals;
|
||||||
|
delete[] m_tris;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcMeshLoaderObj::addVertex( float x, float y, float z, int& cap )
|
||||||
|
{
|
||||||
|
if( m_vertCount + 1 > cap )
|
||||||
|
{
|
||||||
|
cap = !cap ? 8 : cap * 2;
|
||||||
|
float* nv = new float[cap * 3];
|
||||||
|
if( m_vertCount )
|
||||||
|
memcpy( nv, m_verts, m_vertCount * 3 * sizeof( float ) );
|
||||||
|
delete[] m_verts;
|
||||||
|
m_verts = nv;
|
||||||
|
}
|
||||||
|
float* dst = &m_verts[ m_vertCount * 3 ];
|
||||||
|
*dst++ = x * m_scale;
|
||||||
|
*dst++ = y * m_scale;
|
||||||
|
*dst++ = z * m_scale;
|
||||||
|
m_vertCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcMeshLoaderObj::addTriangle( int a, int b, int c, int& cap )
|
||||||
|
{
|
||||||
|
if( m_triCount + 1 > cap )
|
||||||
|
{
|
||||||
|
cap = !cap ? 8 : cap * 2;
|
||||||
|
int* nv = new int[cap * 3];
|
||||||
|
if( m_triCount )
|
||||||
|
memcpy( nv, m_tris, m_triCount * 3 * sizeof( int ) );
|
||||||
|
delete[] m_tris;
|
||||||
|
m_tris = nv;
|
||||||
|
}
|
||||||
|
int* dst = &m_tris[ m_triCount * 3 ];
|
||||||
|
*dst++ = a;
|
||||||
|
*dst++ = b;
|
||||||
|
*dst++ = c;
|
||||||
|
m_triCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* parseRow( char* buf, char* bufEnd, char* row, int len )
|
||||||
|
{
|
||||||
|
bool start = true;
|
||||||
|
bool done = false;
|
||||||
|
int n = 0;
|
||||||
|
while( !done && buf < bufEnd )
|
||||||
|
{
|
||||||
|
char c = *buf;
|
||||||
|
buf++;
|
||||||
|
// multirow
|
||||||
|
switch( c )
|
||||||
|
{
|
||||||
|
case '\\':
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
if( start )
|
||||||
|
break;
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
case ' ':
|
||||||
|
if( start )
|
||||||
|
break;
|
||||||
|
// else falls through
|
||||||
|
default:
|
||||||
|
start = false;
|
||||||
|
row[ n++ ] = c;
|
||||||
|
if( n >= len - 1 )
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row[ n ] = '\0';
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parseFace( char* row, int* data, int n, int vcnt )
|
||||||
|
{
|
||||||
|
int j = 0;
|
||||||
|
while( *row != '\0' )
|
||||||
|
{
|
||||||
|
// Skip initial white space
|
||||||
|
while( *row != '\0' && ( *row == ' ' || *row == '\t' ) )
|
||||||
|
row++;
|
||||||
|
char* s = row;
|
||||||
|
// Find vertex delimiter and terminated the string there for conversion.
|
||||||
|
while( *row != '\0' && *row != ' ' && *row != '\t' )
|
||||||
|
{
|
||||||
|
if( *row == '/' )
|
||||||
|
*row = '\0';
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
if( *s == '\0' )
|
||||||
|
continue;
|
||||||
|
int vi = atoi( s );
|
||||||
|
data[ j++ ] = vi < 0 ? vi + vcnt : vi - 1;
|
||||||
|
if( j >= n )
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rcMeshLoaderObj::load( const std::string& filename )
|
||||||
|
{
|
||||||
|
char* buf = 0;
|
||||||
|
FILE* fp = fopen( filename.c_str(), "rb" );
|
||||||
|
if( !fp )
|
||||||
|
return false;
|
||||||
|
if( fseek( fp, 0, SEEK_END ) != 0 )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long bufSize = ftell( fp );
|
||||||
|
if( bufSize < 0 )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if( fseek( fp, 0, SEEK_SET ) != 0 )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buf = new char[bufSize];
|
||||||
|
if( !buf )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t readLen = fread( buf, bufSize, 1, fp );
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
if( readLen != 1 )
|
||||||
|
{
|
||||||
|
delete[] buf;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* src = buf;
|
||||||
|
char* srcEnd = buf + bufSize;
|
||||||
|
char row[512];
|
||||||
|
int face[32];
|
||||||
|
float x, y, z;
|
||||||
|
int nv;
|
||||||
|
int vcap = 0;
|
||||||
|
int tcap = 0;
|
||||||
|
|
||||||
|
while( src < srcEnd )
|
||||||
|
{
|
||||||
|
// Parse one row
|
||||||
|
row[ 0 ] = '\0';
|
||||||
|
src = parseRow( src, srcEnd, row, sizeof( row ) / sizeof( char ) );
|
||||||
|
// Skip comments
|
||||||
|
if( row[ 0 ] == '#' )
|
||||||
|
continue;
|
||||||
|
if( row[ 0 ] == 'v' && row[ 1 ] != 'n' && row[ 1 ] != 't' )
|
||||||
|
{
|
||||||
|
// Vertex pos
|
||||||
|
sscanf( row + 1, "%f %f %f", &x, &y, &z );
|
||||||
|
addVertex( x, y, z, vcap );
|
||||||
|
}
|
||||||
|
if( row[ 0 ] == 'f' )
|
||||||
|
{
|
||||||
|
// Faces
|
||||||
|
nv = parseFace( row + 1, face, 32, m_vertCount );
|
||||||
|
for( int i = 2; i < nv; ++i )
|
||||||
|
{
|
||||||
|
const int a = face[ 0 ];
|
||||||
|
const int b = face[ i - 1 ];
|
||||||
|
const int c = face[ i ];
|
||||||
|
if( a < 0 || a >= m_vertCount || b < 0 || b >= m_vertCount || c < 0 || c >= m_vertCount )
|
||||||
|
continue;
|
||||||
|
addTriangle( a, b, c, tcap );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] buf;
|
||||||
|
|
||||||
|
// Calculate normals.
|
||||||
|
m_normals = new float[m_triCount * 3];
|
||||||
|
for( int i = 0; i < m_triCount * 3; i += 3 )
|
||||||
|
{
|
||||||
|
const float* v0 = &m_verts[ m_tris[ i ] * 3 ];
|
||||||
|
const float* v1 = &m_verts[ m_tris[ i + 1 ] * 3 ];
|
||||||
|
const float* v2 = &m_verts[ m_tris[ i + 2 ] * 3 ];
|
||||||
|
float e0[3], e1[3];
|
||||||
|
for( int j = 0; j < 3; ++j )
|
||||||
|
{
|
||||||
|
e0[ j ] = v1[ j ] - v0[ j ];
|
||||||
|
e1[ j ] = v2[ j ] - v0[ j ];
|
||||||
|
}
|
||||||
|
float* n = &m_normals[ i ];
|
||||||
|
n[ 0 ] = e0[ 1 ] * e1[ 2 ] - e0[ 2 ] * e1[ 1 ];
|
||||||
|
n[ 1 ] = e0[ 2 ] * e1[ 0 ] - e0[ 0 ] * e1[ 2 ];
|
||||||
|
n[ 2 ] = e0[ 0 ] * e1[ 1 ] - e0[ 1 ] * e1[ 0 ];
|
||||||
|
float d = sqrtf( n[ 0 ] * n[ 0 ] + n[ 1 ] * n[ 1 ] + n[ 2 ] * n[ 2 ] );
|
||||||
|
if( d > 0 )
|
||||||
|
{
|
||||||
|
d = 1.0f / d;
|
||||||
|
n[ 0 ] *= d;
|
||||||
|
n[ 1 ] *= d;
|
||||||
|
n[ 2 ] *= d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_filename = filename;
|
||||||
|
return true;
|
||||||
|
}
|
82
src/tools/pcb_reader/nav/ext/MeshLoaderObj.h
Normal file
82
src/tools/pcb_reader/nav/ext/MeshLoaderObj.h
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MESHLOADER_OBJ
|
||||||
|
#define MESHLOADER_OBJ
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class rcMeshLoaderObj
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
rcMeshLoaderObj();
|
||||||
|
|
||||||
|
~rcMeshLoaderObj();
|
||||||
|
|
||||||
|
bool load( const std::string& fileName );
|
||||||
|
|
||||||
|
const float* getVerts() const
|
||||||
|
{
|
||||||
|
return m_verts;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float* getNormals() const
|
||||||
|
{
|
||||||
|
return m_normals;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int* getTris() const
|
||||||
|
{
|
||||||
|
return m_tris;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getVertCount() const
|
||||||
|
{
|
||||||
|
return m_vertCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getTriCount() const
|
||||||
|
{
|
||||||
|
return m_triCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& getFileName() const
|
||||||
|
{
|
||||||
|
return m_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Explicitly disabled copy constructor and copy assignment operator.
|
||||||
|
rcMeshLoaderObj( const rcMeshLoaderObj& );
|
||||||
|
|
||||||
|
rcMeshLoaderObj& operator=( const rcMeshLoaderObj& );
|
||||||
|
|
||||||
|
void addVertex( float x, float y, float z, int& cap );
|
||||||
|
|
||||||
|
void addTriangle( int a, int b, int c, int& cap );
|
||||||
|
|
||||||
|
std::string m_filename;
|
||||||
|
float m_scale;
|
||||||
|
float* m_verts;
|
||||||
|
int* m_tris;
|
||||||
|
float* m_normals;
|
||||||
|
int m_vertCount;
|
||||||
|
int m_triCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MESHLOADER_OBJ
|
97
src/tools/pcb_reader/navmesh_exporter.h
Normal file
97
src/tools/pcb_reader/navmesh_exporter.h
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#ifndef NAVMESH_EXPORTER_H
|
||||||
|
#define NAVMESH_EXPORTER_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "exporter.h"
|
||||||
|
#include "obj_exporter.h"
|
||||||
|
#include "nav/TiledNavmeshGenerator.h"
|
||||||
|
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
|
||||||
|
namespace fs = std::experimental::filesystem;
|
||||||
|
|
||||||
|
class NavmeshExporter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void exportZone( const ExportedZone& zone )
|
||||||
|
{
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
static std::string currPath = std::experimental::filesystem::current_path().string();
|
||||||
|
|
||||||
|
auto dir = fs::current_path().string() + "/pcb_export/" + zone.name + "/";
|
||||||
|
auto fileName = dir + zone.name;
|
||||||
|
auto objName = fileName + ".obj";
|
||||||
|
|
||||||
|
std::error_code e;
|
||||||
|
if( !fs::exists( objName, e ) )
|
||||||
|
ObjExporter::exportZone( zone );
|
||||||
|
{
|
||||||
|
TiledNavmeshGenerator gen;
|
||||||
|
|
||||||
|
if( !gen.init( objName ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] failed to init TiledNavmeshGenerator for file '%s'\n", zone.name.c_str() );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !gen.buildNavmesh() )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] Failed to build navmesh for '%s'\n", zone.name.c_str() );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gen.saveNavmesh( zone.name );
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
printf( "[Navmesh] Finished exporting %s in %lu ms\n",
|
||||||
|
zone.name.c_str(),
|
||||||
|
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exportGroup( const std::string& zoneName, const ExportedGroup& group )
|
||||||
|
{
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
static std::string currPath = std::experimental::filesystem::current_path().string();
|
||||||
|
|
||||||
|
|
||||||
|
auto dir = fs::current_path().string() + "/pcb_export/" + zoneName + "/";
|
||||||
|
auto fileName = dir + zoneName + "_" + group.name;
|
||||||
|
auto objName = fileName + ".obj";
|
||||||
|
|
||||||
|
std::error_code e;
|
||||||
|
if( !fs::exists( objName, e ) )
|
||||||
|
ObjExporter::exportGroup( zoneName, group );
|
||||||
|
|
||||||
|
|
||||||
|
TiledNavmeshGenerator gen;
|
||||||
|
|
||||||
|
if( !gen.init( objName ) )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] failed to init TiledNavmeshGenerator for file '%s'\n", fileName.c_str() );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !gen.buildNavmesh() )
|
||||||
|
{
|
||||||
|
printf( "[Navmesh] Failed to build navmesh for '%s'\n", fileName.c_str() );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gen.saveNavmesh( fileName );
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
printf( "[Navmesh] Finished exporting %s in %lu ms\n",
|
||||||
|
fileName.c_str(),
|
||||||
|
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // !OBJ_EXPORTER_H
|
137
src/tools/pcb_reader/obj_exporter.h
Normal file
137
src/tools/pcb_reader/obj_exporter.h
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
#ifndef OBJ_EXPORTER_H
|
||||||
|
#define OBJ_EXPORTER_H
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
#include "exporter.h"
|
||||||
|
|
||||||
|
|
||||||
|
class ObjExporter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::string exportZone( const ExportedZone& zone )
|
||||||
|
{
|
||||||
|
static std::string currPath = std::experimental::filesystem::current_path().string();
|
||||||
|
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
auto dir = currPath + "/pcb_export/" + zone.name + "/";
|
||||||
|
auto fileName = dir + zone.name + ".obj";
|
||||||
|
|
||||||
|
std::error_code e;
|
||||||
|
|
||||||
|
if( !std::experimental::filesystem::exists( dir, e ) )
|
||||||
|
{
|
||||||
|
if( !std::experimental::filesystem::create_directories( dir, e ) )
|
||||||
|
{
|
||||||
|
printf( "Unable to create directory '%s'", ( dir ).c_str() );
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ofstream of( fileName, std::ios::trunc );
|
||||||
|
int indicesOffset = 0;
|
||||||
|
int meshesCount = 0;
|
||||||
|
|
||||||
|
if( of.good() )
|
||||||
|
{
|
||||||
|
of.close();
|
||||||
|
of.open( fileName, std::ios::app );
|
||||||
|
for( const auto& group : zone.groups )
|
||||||
|
{
|
||||||
|
exportGroup( group.second, of, indicesOffset, meshesCount );
|
||||||
|
}
|
||||||
|
of.flush();
|
||||||
|
of.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
printf( "[Obj] Finished exporting %s in %lu ms\n",
|
||||||
|
fileName.substr( fileName.find( "pcb_export" ) - 1 ).c_str(),
|
||||||
|
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string exportGroup( const std::string& zoneName, const ExportedGroup& group )
|
||||||
|
{
|
||||||
|
static std::string currPath = std::experimental::filesystem::current_path().string();
|
||||||
|
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
auto dir = currPath + "/pcb_export/" + zoneName + "/groups/";
|
||||||
|
auto fileName = dir + group.name + ".obj";
|
||||||
|
|
||||||
|
std::error_code e;
|
||||||
|
if( !std::experimental::filesystem::exists( dir, e ) )
|
||||||
|
{
|
||||||
|
if( !std::experimental::filesystem::create_directories( dir, e ) )
|
||||||
|
{
|
||||||
|
printf( "Unable to create directory '%s'", ( dir ).c_str() );
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ofstream of( fileName, std::ios::trunc );
|
||||||
|
int indicesOffset = 0;
|
||||||
|
int modelCount = 0;
|
||||||
|
|
||||||
|
if( of.good() )
|
||||||
|
{
|
||||||
|
of.close();
|
||||||
|
of.open( fileName, std::ios::app );
|
||||||
|
exportGroup( group, of, indicesOffset, modelCount );
|
||||||
|
of.flush();
|
||||||
|
of.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
printf( "[Obj] Finished exporting %s in %lu ms\n",
|
||||||
|
fileName.substr( fileName.find( "pcb_export" ) - 1 ).c_str(),
|
||||||
|
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
static void exportGroup( const ExportedGroup& group, std::ofstream& of, int& indicesOffset, int& modelCount )
|
||||||
|
{
|
||||||
|
int currModelCount = modelCount;
|
||||||
|
|
||||||
|
of << "o " << group.name << '_' << std::to_string( currModelCount ) << '\n';
|
||||||
|
for( const auto& model : group.models )
|
||||||
|
{
|
||||||
|
modelCount++;
|
||||||
|
of << "o " << model.second.name << '_' << std::to_string( currModelCount ) << '_' << std::to_string( modelCount ) << '\n';
|
||||||
|
|
||||||
|
int meshCount = 0;
|
||||||
|
for( const auto& mesh : model.second.meshes )
|
||||||
|
{
|
||||||
|
for( int i = 0; i < mesh.verts.size(); i += 3 )
|
||||||
|
{
|
||||||
|
of << "v " <<
|
||||||
|
std::to_string( mesh.verts[ i ] ) << ' ' <<
|
||||||
|
std::to_string( mesh.verts[ i + 1 ] ) << ' ' <<
|
||||||
|
std::to_string( mesh.verts[ i + 2 ] ) << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
of << "g " <<
|
||||||
|
model.second.name << '_' <<
|
||||||
|
std::to_string( currModelCount ) << '_' << std::to_string( modelCount ) << '_' << std::to_string( meshCount++ ) << '\n';
|
||||||
|
|
||||||
|
for( int i = 0; i < mesh.indices.size(); i += 3 )
|
||||||
|
{
|
||||||
|
of << "f " <<
|
||||||
|
std::to_string( mesh.indices[ i ] + indicesOffset + 1 ) << ' ' <<
|
||||||
|
std::to_string( mesh.indices[ i + 1 ] + indicesOffset + 1 ) << ' ' +
|
||||||
|
std::to_string( mesh.indices[ i + 2 ] + indicesOffset + 1 ) << '\n';
|
||||||
|
}
|
||||||
|
indicesOffset += mesh.verts.size() / 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//of.flush();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // !OBJ_EXPORTER_H
|
|
@ -70,6 +70,90 @@ struct PCB_FILE
|
||||||
{
|
{
|
||||||
PCB_HEADER header;
|
PCB_HEADER header;
|
||||||
std::vector< PCB_BLOCK_ENTRY > entries;
|
std::vector< PCB_BLOCK_ENTRY > entries;
|
||||||
|
|
||||||
|
PCB_FILE( char* buf )
|
||||||
|
{
|
||||||
|
uint32_t offset = 0;
|
||||||
|
memcpy( &header, buf, sizeof( header ));
|
||||||
|
offset += sizeof( header );
|
||||||
|
entries.resize( header.num_entries );
|
||||||
|
bool isgroup = true;
|
||||||
|
while( isgroup )
|
||||||
|
{
|
||||||
|
PCB_BLOCK_ENTRY block_entry;
|
||||||
|
memcpy( &block_entry.header, buf + offset, sizeof( block_entry.header ) );
|
||||||
|
isgroup = block_entry.header.type == 0x30;
|
||||||
|
|
||||||
|
//printf( "BLOCKHEADER_%X: type: %i, group_size: %i\n", offset, block_entry.header.type, block_entry.header.group_size );
|
||||||
|
//
|
||||||
|
if( isgroup )
|
||||||
|
{
|
||||||
|
parseBlockEntry( buf + offset + 0x30, entries, offset);
|
||||||
|
offset += block_entry.header.group_size;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parseBlockEntry( buf + offset, entries, offset );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseBlockEntry( char* data, std::vector< PCB_BLOCK_ENTRY >& entries, int gOff )
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
bool isgroup = true;
|
||||||
|
while( isgroup )
|
||||||
|
{
|
||||||
|
PCB_BLOCK_ENTRY block_entry;
|
||||||
|
memcpy( &block_entry.header, data + offset, sizeof( block_entry.header ) );
|
||||||
|
isgroup = block_entry.header.type == 0x30;
|
||||||
|
|
||||||
|
//printf( " BLOCKHEADER_%X: type: %i, group_size: %i\n", gOff + offset, block_entry.header.type, block_entry.header.group_size );
|
||||||
|
|
||||||
|
if( isgroup )
|
||||||
|
{
|
||||||
|
parseBlockEntry( data + offset + 0x30, entries, gOff + offset );
|
||||||
|
offset += block_entry.header.group_size;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* printf( "\tnum_v16: %i, num_indices: %i, num_vertices: %i\n\n",
|
||||||
|
block_entry.header.num_v16, block_entry.header.num_indices, block_entry.header.num_vertices );*/
|
||||||
|
int doffset = sizeof( block_entry.header ) + offset;
|
||||||
|
uint16_t block_size = sizeof( block_entry.header ) +
|
||||||
|
block_entry.header.num_vertices * 3 * 4 +
|
||||||
|
block_entry.header.num_v16 * 6 +
|
||||||
|
block_entry.header.num_indices * 6;
|
||||||
|
|
||||||
|
if( block_entry.header.num_vertices != 0 )
|
||||||
|
{
|
||||||
|
block_entry.data.vertices.resize( block_entry.header.num_vertices );
|
||||||
|
|
||||||
|
int32_t size_vertexbuffer = block_entry.header.num_vertices * 3;
|
||||||
|
memcpy( &block_entry.data.vertices[ 0 ], data + doffset, size_vertexbuffer * 4 );
|
||||||
|
doffset += size_vertexbuffer * 4;
|
||||||
|
}
|
||||||
|
if( block_entry.header.num_v16 != 0 )
|
||||||
|
{
|
||||||
|
block_entry.data.vertices_i16.resize( block_entry.header.num_v16 );
|
||||||
|
int32_t size_unknownbuffer = block_entry.header.num_v16 * 6;
|
||||||
|
memcpy( &block_entry.data.vertices_i16[ 0 ], data + doffset, size_unknownbuffer );
|
||||||
|
doffset += block_entry.header.num_v16 * 6;
|
||||||
|
}
|
||||||
|
if( block_entry.header.num_indices != 0 )
|
||||||
|
{
|
||||||
|
block_entry.data.indices.resize( block_entry.header.num_indices );
|
||||||
|
int32_t size_indexbuffer = block_entry.header.num_indices * 12;
|
||||||
|
memcpy( &block_entry.data.indices[ 0 ], data + doffset, size_indexbuffer );
|
||||||
|
doffset += size_indexbuffer;
|
||||||
|
}
|
||||||
|
entries.push_back( block_entry );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PCB_LIST_ENTRY
|
struct PCB_LIST_ENTRY
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#include "vec3.h"
|
#include "vec3.h"
|
||||||
|
|
||||||
// garbage to skip model loading
|
// garbage to skip model loading
|
||||||
extern bool ignoreModels;
|
extern bool noObj;
|
||||||
|
|
||||||
//
|
//
|
||||||
// ported from https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/Graphics/Sgb/SgbDataType.cs
|
// ported from https://github.com/ufx/SaintCoinach/blob/master/SaintCoinach/Graphics/Sgb/SgbDataType.cs
|
||||||
|
@ -36,6 +36,7 @@ enum SgbGroupEntryType :
|
||||||
uint32_t
|
uint32_t
|
||||||
{
|
{
|
||||||
Model = 0x01,
|
Model = 0x01,
|
||||||
|
Gimmick = 0x06,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SGB_GROUP_HEADER
|
struct SGB_GROUP_HEADER
|
||||||
|
@ -64,6 +65,35 @@ struct SGB_GROUP_HEADER
|
||||||
uint32_t unknown44;
|
uint32_t unknown44;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SGB_GROUP1C_HEADER
|
||||||
|
{
|
||||||
|
SgbDataType type;
|
||||||
|
int32_t nameOffset;
|
||||||
|
uint32_t unknown08;
|
||||||
|
|
||||||
|
int32_t entryCount;
|
||||||
|
uint32_t unknown14;
|
||||||
|
int32_t modelFileOffset;
|
||||||
|
vec3 unknownFloat3;
|
||||||
|
vec3 unknownFloat3_2;
|
||||||
|
int32_t stateOffset;
|
||||||
|
int32_t modelFileOffset2;
|
||||||
|
uint32_t unknown3;
|
||||||
|
float unknown4;
|
||||||
|
int32_t nameOffset2;
|
||||||
|
vec3 unknownFloat3_3;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SGB_GROUP1C_ENTRY
|
||||||
|
{
|
||||||
|
uint32_t unk;
|
||||||
|
uint32_t unk2;
|
||||||
|
int32_t nameOffset;
|
||||||
|
uint32_t index;
|
||||||
|
uint32_t unk3;
|
||||||
|
int32_t modelFileOffset;
|
||||||
|
};
|
||||||
|
|
||||||
struct SGB_GROUP_ENTRY
|
struct SGB_GROUP_ENTRY
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -113,8 +143,9 @@ struct SGB_MODEL_ENTRY :
|
||||||
std::string modelFileName;
|
std::string modelFileName;
|
||||||
std::string collisionFileName;
|
std::string collisionFileName;
|
||||||
|
|
||||||
SGB_MODEL_ENTRY( char* buf, uint32_t offset )
|
SGB_MODEL_ENTRY( char* buf, uint32_t offset, SgbGroupEntryType type )
|
||||||
{
|
{
|
||||||
|
this->type = type;
|
||||||
header = *reinterpret_cast< SGB_MODEL_HEADER* >( buf + offset );
|
header = *reinterpret_cast< SGB_MODEL_HEADER* >( buf + offset );
|
||||||
name = std::string( buf + offset + header.nameOffset );
|
name = std::string( buf + offset + header.nameOffset );
|
||||||
modelFileName = std::string( buf + offset + header.modelFileOffset );
|
modelFileName = std::string( buf + offset + header.modelFileOffset );
|
||||||
|
@ -129,23 +160,45 @@ struct SGB_GROUP
|
||||||
SGB_FILE* parent;
|
SGB_FILE* parent;
|
||||||
std::vector< std::shared_ptr< SGB_GROUP_ENTRY > > entries;
|
std::vector< std::shared_ptr< SGB_GROUP_ENTRY > > entries;
|
||||||
|
|
||||||
SGB_GROUP( char* buf, SGB_FILE* file, uint32_t fileSize, uint32_t offset )
|
SGB_GROUP( char* buf, SGB_FILE* file, std::set< std::string >* offset1cObjects, uint32_t fileSize, uint32_t offset, bool isOffset1C = false )
|
||||||
{
|
{
|
||||||
parent = file;
|
parent = file;
|
||||||
|
|
||||||
|
|
||||||
|
if( isOffset1C )
|
||||||
|
{
|
||||||
|
auto header1c = *reinterpret_cast< SGB_GROUP1C_HEADER* >( buf + offset );
|
||||||
|
|
||||||
|
auto entriesOffset = offset + sizeof( header1c );
|
||||||
|
|
||||||
|
auto entryCount = header1c.entryCount;
|
||||||
|
for( auto i = 0; i < entryCount; ++i )
|
||||||
|
{
|
||||||
|
auto entryOffset = entriesOffset + ( i * 24 );
|
||||||
|
auto entry = *reinterpret_cast< SGB_GROUP1C_ENTRY* >( buf + entryOffset );
|
||||||
|
|
||||||
|
std::string entryModelFile( buf + entryOffset + entry.modelFileOffset + 9 );
|
||||||
|
if( entryModelFile.find( ".sgb" ) != std::string::npos )
|
||||||
|
{
|
||||||
|
offset1cObjects->emplace( entryModelFile );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto entriesOffset = offset + sizeof( header );
|
||||||
|
|
||||||
header = *reinterpret_cast< SGB_GROUP_HEADER* >( buf + offset );
|
header = *reinterpret_cast< SGB_GROUP_HEADER* >( buf + offset );
|
||||||
name = std::string( buf + offset + header.nameOffset );
|
name = std::string( buf + offset + header.nameOffset );
|
||||||
|
|
||||||
auto entriesOffset = offset + sizeof( header );
|
|
||||||
|
|
||||||
for( auto i = 0; i < header.entryCount; ++i )
|
for( auto i = 0; i < header.entryCount; ++i )
|
||||||
{
|
{
|
||||||
auto entryOffset = entriesOffset + *reinterpret_cast< uint32_t* >( buf + ( entriesOffset + ( i * 4 ) ) );
|
auto entryOffset = entriesOffset + *reinterpret_cast< uint32_t* >( buf + ( entriesOffset + ( i * 4 ) ) );
|
||||||
if( entryOffset > fileSize )
|
if( entryOffset > fileSize )
|
||||||
throw std::runtime_error( "SGB_GROUP entry offset was larger than SGB file size!" );
|
throw std::runtime_error( "SGB_GROUP entry offset was larger than SGB file size!" );
|
||||||
auto type = *reinterpret_cast< uint32_t* >( buf + entryOffset );
|
auto type = *reinterpret_cast< uint32_t* >( buf + entryOffset );
|
||||||
if( type == SgbGroupEntryType::Model && !ignoreModels )
|
if( type == SgbGroupEntryType::Model || type == SgbGroupEntryType::Gimmick )
|
||||||
{
|
{
|
||||||
entries.push_back( std::make_shared< SGB_MODEL_ENTRY >( buf, entryOffset ) );
|
entries.push_back( std::make_shared< SGB_MODEL_ENTRY >( buf, entryOffset, ( SgbGroupEntryType )type ) );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -190,6 +243,7 @@ struct SGB_FILE
|
||||||
{
|
{
|
||||||
SGB_HEADER header;
|
SGB_HEADER header;
|
||||||
std::vector< SGB_GROUP > entries;
|
std::vector< SGB_GROUP > entries;
|
||||||
|
std::set< std::string > offset1cObjects;
|
||||||
|
|
||||||
SGB_FILE()
|
SGB_FILE()
|
||||||
{
|
{
|
||||||
|
@ -206,14 +260,14 @@ struct SGB_FILE
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto group = SGB_GROUP( buf, this, header.fileSize, baseOffset + header.sharedOffset );
|
auto group = SGB_GROUP( buf, this, &offset1cObjects, header.fileSize, baseOffset + header.sharedOffset );
|
||||||
entries.push_back( group );
|
entries.push_back( group );
|
||||||
auto group2 = SGB_GROUP( buf, this, header.fileSize, baseOffset + header.offset1C );
|
auto group2 = SGB_GROUP( buf, this, &offset1cObjects, header.fileSize, baseOffset+ header.offset1C, true );
|
||||||
entries.push_back( group2 );
|
entries.push_back( group2 );
|
||||||
}
|
}
|
||||||
catch( std::exception& e )
|
catch( std::exception& e )
|
||||||
{
|
{
|
||||||
std::cout << e.what() << "\n";
|
std::cout << ( std::string( e.what() ) + "\n" );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
108
src/tools/pcb_reader/threadpool.h
Normal file
108
src/tools/pcb_reader/threadpool.h
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
#ifndef THREADPOOL_H
|
||||||
|
#define THREADPOOL_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
// credit to
|
||||||
|
// https://riptutorial.com/cplusplus/example/15806/create-a-simple-thread-pool
|
||||||
|
|
||||||
|
class ThreadPool
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ThreadPool()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
~ThreadPool()
|
||||||
|
{
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addWorkers( unsigned int num )
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_lock lock( m_mutex );
|
||||||
|
m_runFlag = true;
|
||||||
|
if( num == 0 )
|
||||||
|
num = std::thread::hardware_concurrency() - 1;
|
||||||
|
|
||||||
|
for( auto i = 0; i < num; ++i )
|
||||||
|
{
|
||||||
|
m_workers.push_back( std::async( std::launch::async, [this]{ run(); } ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template< class Func, class Ret = std::result_of_t< Func&() > >
|
||||||
|
std::future< Ret > queue( Func&& f )
|
||||||
|
{
|
||||||
|
std::packaged_task< Ret() > task( std::forward< Func >( f ) );
|
||||||
|
auto ret = task.get_future();
|
||||||
|
{
|
||||||
|
std::unique_lock lock( m_mutex );
|
||||||
|
m_pendingJobs.emplace_back( std::move( task ) );
|
||||||
|
}
|
||||||
|
m_cv.notify_one();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::unique_lock lock( m_mutex );
|
||||||
|
m_pendingJobs.clear();
|
||||||
|
}
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool complete()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::unique_lock lock( m_mutex );
|
||||||
|
for( auto&& worker : m_workers )
|
||||||
|
{
|
||||||
|
m_pendingJobs.push_back( {} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_cv.notify_all();
|
||||||
|
m_workers.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
while( 1 )
|
||||||
|
{
|
||||||
|
std::packaged_task< void() > func;
|
||||||
|
{
|
||||||
|
std::unique_lock lock( m_mutex );
|
||||||
|
if( m_pendingJobs.empty() )
|
||||||
|
{
|
||||||
|
m_cv.wait( lock, [&](){ return !m_pendingJobs.empty(); } );
|
||||||
|
}
|
||||||
|
func = std::move( m_pendingJobs.front() );
|
||||||
|
m_pendingJobs.pop_front();
|
||||||
|
}
|
||||||
|
if( !func.valid() )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool m_runFlag{ true };
|
||||||
|
std::mutex m_mutex;
|
||||||
|
std::condition_variable m_cv;
|
||||||
|
std::deque< std::packaged_task< void() > > m_pendingJobs;
|
||||||
|
std::vector< std::future< void > > m_workers;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -251,13 +251,13 @@ bool Sapphire::Entity::Actor::isInRangeSet( ActorPtr pActor ) const
|
||||||
|
|
||||||
|
|
||||||
/*! \return ActorPtr of the closest actor in range, if none, nullptr */
|
/*! \return ActorPtr of the closest actor in range, if none, nullptr */
|
||||||
Sapphire::Entity::ActorPtr Sapphire::Entity::Actor::getClosestActor()
|
Sapphire::Entity::CharaPtr Sapphire::Entity::Actor::getClosestChara()
|
||||||
{
|
{
|
||||||
if( m_inRangeActor.empty() )
|
if( m_inRangeActor.empty() )
|
||||||
// no actors in range, don't bother
|
// no actors in range, don't bother
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
ActorPtr tmpActor = nullptr;
|
CharaPtr tmpActor = nullptr;
|
||||||
|
|
||||||
// arbitrary high number
|
// arbitrary high number
|
||||||
float minDistance = 10000;
|
float minDistance = 10000;
|
||||||
|
@ -270,7 +270,7 @@ Sapphire::Entity::ActorPtr Sapphire::Entity::Actor::getClosestActor()
|
||||||
if( distance < minDistance )
|
if( distance < minDistance )
|
||||||
{
|
{
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
tmpActor = pCurAct;
|
tmpActor = pCurAct->getAsChara();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ namespace Sapphire::Entity
|
||||||
// check if another actor is in the actors in range set
|
// check if another actor is in the actors in range set
|
||||||
bool isInRangeSet( ActorPtr pActor ) const;
|
bool isInRangeSet( ActorPtr pActor ) const;
|
||||||
|
|
||||||
ActorPtr getClosestActor();
|
CharaPtr getClosestChara();
|
||||||
|
|
||||||
void sendToInRangeSet( Network::Packets::FFXIVPacketBasePtr pPacket, bool bToSelf = false );
|
void sendToInRangeSet( Network::Packets::FFXIVPacketBasePtr pPacket, bool bToSelf = false );
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <Network/CommonActorControl.h>
|
#include <Network/CommonActorControl.h>
|
||||||
#include <Network/PacketWrappers/EffectPacket.h>
|
#include <Network/PacketWrappers/EffectPacket.h>
|
||||||
|
#include <Network/PacketDef/Zone/ClientZoneDef.h>
|
||||||
|
#include <Logging/Logger.h>
|
||||||
|
|
||||||
#include "Forwards.h"
|
#include "Forwards.h"
|
||||||
#include "Action/Action.h"
|
#include "Action/Action.h"
|
||||||
|
@ -17,6 +19,8 @@
|
||||||
#include "Network/PacketWrappers/ActorControlPacket144.h"
|
#include "Network/PacketWrappers/ActorControlPacket144.h"
|
||||||
#include "Network/PacketWrappers/UpdateHpMpTpPacket.h"
|
#include "Network/PacketWrappers/UpdateHpMpTpPacket.h"
|
||||||
#include "Network/PacketWrappers/NpcSpawnPacket.h"
|
#include "Network/PacketWrappers/NpcSpawnPacket.h"
|
||||||
|
#include "Network/PacketWrappers/MoveActorPacket.h"
|
||||||
|
#include "Navi/NaviProvider.h"
|
||||||
|
|
||||||
#include "StatusEffect/StatusEffect.h"
|
#include "StatusEffect/StatusEffect.h"
|
||||||
#include "Action/ActionCollision.h"
|
#include "Action/ActionCollision.h"
|
||||||
|
@ -29,6 +33,9 @@
|
||||||
#include "BNpcTemplate.h"
|
#include "BNpcTemplate.h"
|
||||||
#include "Manager/TerritoryMgr.h"
|
#include "Manager/TerritoryMgr.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
#include "Framework.h"
|
||||||
|
#include <Logging/Logger.h>
|
||||||
|
#include <Manager/NaviMgr.h>
|
||||||
|
|
||||||
using namespace Sapphire::Common;
|
using namespace Sapphire::Common;
|
||||||
using namespace Sapphire::Network::Packets;
|
using namespace Sapphire::Network::Packets;
|
||||||
|
@ -41,7 +48,7 @@ Sapphire::Entity::BNpc::BNpc( FrameworkPtr pFw ) :
|
||||||
}
|
}
|
||||||
|
|
||||||
Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX, float posY, float posZ, float rot,
|
Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX, float posY, float posZ, float rot,
|
||||||
uint8_t level, uint32_t maxHp, FrameworkPtr pFw ) :
|
uint8_t level, uint32_t maxHp, ZonePtr pZone, FrameworkPtr pFw ) :
|
||||||
Npc( ObjKind::BattleNpc, pFw )
|
Npc( ObjKind::BattleNpc, pFw )
|
||||||
{
|
{
|
||||||
m_id = id;
|
m_id = id;
|
||||||
|
@ -59,18 +66,31 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX
|
||||||
m_pos.z = posZ;
|
m_pos.z = posZ;
|
||||||
m_rot = rot;
|
m_rot = rot;
|
||||||
m_level = level;
|
m_level = level;
|
||||||
|
m_invincibilityType = InvincibilityNone;
|
||||||
|
m_currentStance = Common::Stance::Passive;
|
||||||
|
|
||||||
|
m_pCurrentZone = pZone;
|
||||||
|
|
||||||
|
m_spawnPos = m_pos;
|
||||||
|
|
||||||
|
m_timeOfDeath = 0;
|
||||||
|
m_targetId = Common::INVALID_GAME_OBJECT_ID64;
|
||||||
|
|
||||||
m_maxHp = maxHp;
|
m_maxHp = maxHp;
|
||||||
m_maxMp = 200;
|
m_maxMp = 200;
|
||||||
m_hp = maxHp;
|
m_hp = maxHp;
|
||||||
m_mp = 200;
|
m_mp = 200;
|
||||||
|
|
||||||
|
m_state = BNpcState::Idle;
|
||||||
|
m_status = ActorStatus::Idle;
|
||||||
|
|
||||||
m_baseStats.max_hp = maxHp;
|
m_baseStats.max_hp = maxHp;
|
||||||
m_baseStats.max_mp = 200;
|
m_baseStats.max_mp = 200;
|
||||||
|
|
||||||
memcpy( m_customize, pTemplate->getCustomize(), sizeof( m_customize ) );
|
memcpy( m_customize, pTemplate->getCustomize(), sizeof( m_customize ) );
|
||||||
memcpy( m_modelEquip, pTemplate->getModelEquip(), sizeof( m_modelEquip ) );
|
memcpy( m_modelEquip, pTemplate->getModelEquip(), sizeof( m_modelEquip ) );
|
||||||
|
|
||||||
|
m_lastTickTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sapphire::Entity::BNpc::~BNpc()
|
Sapphire::Entity::BNpc::~BNpc()
|
||||||
|
@ -119,5 +139,456 @@ uint32_t Sapphire::Entity::BNpc::getBNpcNameId() const
|
||||||
|
|
||||||
void Sapphire::Entity::BNpc::spawn( PlayerPtr pTarget )
|
void Sapphire::Entity::BNpc::spawn( PlayerPtr pTarget )
|
||||||
{
|
{
|
||||||
pTarget->queuePacket( std::make_shared< NpcSpawnPacket >( *getAsBNpc(), *pTarget ) );
|
pTarget->queuePacket( std::make_shared< NpcSpawnPacket >( *this, *pTarget ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::despawn( PlayerPtr pTarget )
|
||||||
|
{
|
||||||
|
pTarget->freePlayerSpawnId( getId() );
|
||||||
|
pTarget->queuePacket( makeActorControl143( m_id, DespawnZoneScreenMsg, 0x04, getId(), 0x01 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Sapphire::Entity::BNpcState Sapphire::Entity::BNpc::getState() const
|
||||||
|
{
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::setState( BNpcState state )
|
||||||
|
{
|
||||||
|
m_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::step()
|
||||||
|
{
|
||||||
|
if( m_naviLastPath.empty() )
|
||||||
|
// No path to track
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto stepPos = m_naviLastPath[ m_naviPathStep ];
|
||||||
|
|
||||||
|
auto distanceToStep = Util::distance( getPos(), stepPos );
|
||||||
|
auto distanceToDest = Util::distance( getPos(), m_naviTarget );
|
||||||
|
|
||||||
|
if( distanceToStep <= 4 && m_naviPathStep < m_naviLastPath.size() - 1 )
|
||||||
|
{
|
||||||
|
// Reached step in path
|
||||||
|
m_naviPathStep++;
|
||||||
|
stepPos = m_naviLastPath[ m_naviPathStep ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is probably not a good way to do it but works fine for now
|
||||||
|
float angle = Util::calcAngFrom( getPos().x, getPos().z, stepPos.x, stepPos.z ) + PI;
|
||||||
|
|
||||||
|
auto delta = static_cast< float >( Util::getTimeMs() - m_lastUpdate ) / 1000.f;
|
||||||
|
|
||||||
|
float speed = 7.5f * delta;
|
||||||
|
|
||||||
|
if( m_state == BNpcState::Roaming )
|
||||||
|
speed *= 0.27f;
|
||||||
|
|
||||||
|
// this seems to fix it but i don't know why :(
|
||||||
|
if( speed > distanceToDest )
|
||||||
|
speed = distanceToDest / delta;
|
||||||
|
|
||||||
|
auto x = ( cosf( angle ) * speed );
|
||||||
|
auto y = stepPos.y;
|
||||||
|
auto z = ( sinf( angle ) * speed );
|
||||||
|
|
||||||
|
|
||||||
|
face( stepPos );
|
||||||
|
setPos( { getPos().x + x, y, getPos().z + z } );
|
||||||
|
sendPositionUpdate();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos )
|
||||||
|
{
|
||||||
|
if( Util::distance( getPos(), pos ) <= 4 )
|
||||||
|
{
|
||||||
|
// Reached destination
|
||||||
|
m_naviLastPath.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pNaviMgr = m_pFw->get< World::Manager::NaviMgr >();
|
||||||
|
auto pNaviProvider = pNaviMgr->getNaviProvider( m_pCurrentZone->getBgPath() );
|
||||||
|
|
||||||
|
if( !pNaviProvider )
|
||||||
|
{
|
||||||
|
Logger::error( "No NaviProvider for zone#{0} - {1}",
|
||||||
|
m_pCurrentZone->getGuId(),
|
||||||
|
m_pCurrentZone->getInternalName() );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path = pNaviProvider->findFollowPath( m_pos, pos );
|
||||||
|
|
||||||
|
if( !path.empty() )
|
||||||
|
{
|
||||||
|
m_naviLastPath = path;
|
||||||
|
m_naviTarget = pos;
|
||||||
|
m_naviPathStep = 0;
|
||||||
|
m_naviLastUpdate = Util::getTimeMs();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::debug( "No path found from x{0} y{1} z{2} to x{3} y{4} z{5} in {6}",
|
||||||
|
getPos().x, getPos().y, getPos().z, pos.x, pos.y, pos.z, m_pCurrentZone->getInternalName() );
|
||||||
|
|
||||||
|
|
||||||
|
hateListClear();
|
||||||
|
|
||||||
|
if( m_state == BNpcState::Roaming )
|
||||||
|
{
|
||||||
|
Logger::warn( "BNpc Base#{0} Name#{1} unable to path from x{2} y{3} z{4} while roaming. "
|
||||||
|
"Possible pathing error in area. Returning BNpc to spawn position x{5} y{6} z{7}.",
|
||||||
|
m_bNpcBaseId, m_bNpcNameId,
|
||||||
|
getPos().x, getPos().y, getPos().z,
|
||||||
|
m_spawnPos.x, m_spawnPos.y, m_spawnPos.z );
|
||||||
|
|
||||||
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
||||||
|
m_state = BNpcState::Idle;
|
||||||
|
|
||||||
|
m_naviLastPath.clear();
|
||||||
|
|
||||||
|
setPos( m_spawnPos );
|
||||||
|
sendPositionUpdate();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
step();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::sendPositionUpdate()
|
||||||
|
{
|
||||||
|
uint8_t unk1 = 0x3a;
|
||||||
|
uint8_t animationType = 2;
|
||||||
|
|
||||||
|
if( m_state == BNpcState::Combat || m_state == BNpcState::Retreat )
|
||||||
|
animationType = 0;
|
||||||
|
|
||||||
|
auto movePacket = std::make_shared< MoveActorPacket >( *getAsChara(), unk1, animationType, 0, 0x5A );
|
||||||
|
sendToInRangeSet( movePacket );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::hateListClear()
|
||||||
|
{
|
||||||
|
auto it = m_hateList.begin();
|
||||||
|
for( auto listEntry : m_hateList )
|
||||||
|
{
|
||||||
|
if( isInRangeSet( listEntry->m_pChara ) )
|
||||||
|
deaggro( listEntry->m_pChara );
|
||||||
|
}
|
||||||
|
m_hateList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Sapphire::Entity::CharaPtr Sapphire::Entity::BNpc::hateListGetHighest()
|
||||||
|
{
|
||||||
|
auto it = m_hateList.begin();
|
||||||
|
uint32_t maxHate = 0;
|
||||||
|
std::shared_ptr< HateListEntry > entry;
|
||||||
|
for( ; it != m_hateList.end(); ++it )
|
||||||
|
{
|
||||||
|
if( ( *it )->m_hateAmount > maxHate )
|
||||||
|
{
|
||||||
|
maxHate = ( *it )->m_hateAmount;
|
||||||
|
entry = *it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( entry && maxHate != 0 )
|
||||||
|
return entry->m_pChara;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::hateListAdd( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount )
|
||||||
|
{
|
||||||
|
auto hateEntry = std::make_shared< HateListEntry >();
|
||||||
|
hateEntry->m_hateAmount = hateAmount;
|
||||||
|
hateEntry->m_pChara = pChara;
|
||||||
|
|
||||||
|
m_hateList.insert( hateEntry );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::hateListUpdate( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount )
|
||||||
|
{
|
||||||
|
for( auto listEntry : m_hateList )
|
||||||
|
{
|
||||||
|
if( listEntry->m_pChara == pChara )
|
||||||
|
{
|
||||||
|
listEntry->m_hateAmount += hateAmount;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hateEntry = std::make_shared< HateListEntry >();
|
||||||
|
hateEntry->m_hateAmount = hateAmount;
|
||||||
|
hateEntry->m_pChara = pChara;
|
||||||
|
m_hateList.insert( hateEntry );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::hateListRemove( Sapphire::Entity::CharaPtr pChara )
|
||||||
|
{
|
||||||
|
for( auto listEntry : m_hateList )
|
||||||
|
{
|
||||||
|
if( listEntry->m_pChara == pChara )
|
||||||
|
{
|
||||||
|
|
||||||
|
m_hateList.erase( listEntry );
|
||||||
|
if( pChara->isPlayer() )
|
||||||
|
{
|
||||||
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
||||||
|
tmpPlayer->onMobDeaggro( getAsBNpc() );
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::Entity::BNpc::hateListHasActor( Sapphire::Entity::CharaPtr pChara )
|
||||||
|
{
|
||||||
|
for( auto& listEntry : m_hateList )
|
||||||
|
{
|
||||||
|
if( listEntry->m_pChara == pChara )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::aggro( Sapphire::Entity::CharaPtr pChara )
|
||||||
|
{
|
||||||
|
m_lastAttack = Util::getTimeMs();
|
||||||
|
hateListUpdate( pChara, 1 );
|
||||||
|
|
||||||
|
changeTarget( pChara->getId() );
|
||||||
|
setStance( Stance::Active );
|
||||||
|
m_state = BNpcState::Combat;
|
||||||
|
|
||||||
|
if( pChara->isPlayer() )
|
||||||
|
{
|
||||||
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
||||||
|
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 1, 1, 1 ) );
|
||||||
|
tmpPlayer->onMobAggro( getAsBNpc() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::deaggro( Sapphire::Entity::CharaPtr pChara )
|
||||||
|
{
|
||||||
|
if( !hateListHasActor( pChara ) )
|
||||||
|
hateListRemove( pChara );
|
||||||
|
|
||||||
|
if( pChara->isPlayer() )
|
||||||
|
{
|
||||||
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
||||||
|
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) );
|
||||||
|
tmpPlayer->onMobDeaggro( getAsBNpc() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::onTick()
|
||||||
|
{
|
||||||
|
if( m_state == BNpcState::Retreat )
|
||||||
|
{
|
||||||
|
regainHp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::update( int64_t currTime )
|
||||||
|
{
|
||||||
|
const uint8_t minActorDistance = 4;
|
||||||
|
const uint8_t aggroRange = 8;
|
||||||
|
const uint8_t maxDistanceToOrigin = 40;
|
||||||
|
const uint32_t roamTick = 20;
|
||||||
|
|
||||||
|
|
||||||
|
switch( m_state )
|
||||||
|
{
|
||||||
|
case BNpcState::Dead:
|
||||||
|
case BNpcState::JustDied:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case BNpcState::Retreat:
|
||||||
|
{
|
||||||
|
setInvincibilityType( InvincibilityType::InvincibilityIgnoreDamage );
|
||||||
|
|
||||||
|
if( moveTo( m_spawnPos ) )
|
||||||
|
{
|
||||||
|
setInvincibilityType( InvincibilityType::InvincibilityNone );
|
||||||
|
|
||||||
|
// retail doesn't seem to roam straight after retreating
|
||||||
|
// todo: perhaps requires more investigation?
|
||||||
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
||||||
|
|
||||||
|
// resetHp
|
||||||
|
setHp( getMaxHp() );
|
||||||
|
|
||||||
|
m_state = BNpcState::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BNpcState::Roaming:
|
||||||
|
{
|
||||||
|
if( moveTo( m_roamPos ) )
|
||||||
|
{
|
||||||
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
||||||
|
m_state = BNpcState::Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAggro( aggroRange );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BNpcState::Idle:
|
||||||
|
{
|
||||||
|
if( Util::getTimeSeconds() - m_lastRoamTargetReached > roamTick )
|
||||||
|
{
|
||||||
|
auto pNaviMgr = m_pFw->get< World::Manager::NaviMgr >();
|
||||||
|
auto pNaviProvider = pNaviMgr->getNaviProvider( m_pCurrentZone->getBgPath() );
|
||||||
|
|
||||||
|
if( !pNaviProvider )
|
||||||
|
{
|
||||||
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_roamPos = pNaviProvider->findRandomPositionInCircle( m_spawnPos, 5 );
|
||||||
|
m_state = BNpcState::Roaming;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAggro( aggroRange );
|
||||||
|
}
|
||||||
|
|
||||||
|
case BNpcState::Combat:
|
||||||
|
{
|
||||||
|
auto pHatedActor = hateListGetHighest();
|
||||||
|
if( !pHatedActor )
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto distanceOrig = Util::distance( getPos().x, getPos().y, getPos().z,
|
||||||
|
m_spawnPos.x,
|
||||||
|
m_spawnPos.y,
|
||||||
|
m_spawnPos.z );
|
||||||
|
|
||||||
|
if( pHatedActor && !pHatedActor->isAlive() )
|
||||||
|
{
|
||||||
|
hateListRemove( pHatedActor );
|
||||||
|
pHatedActor = hateListGetHighest();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( pHatedActor )
|
||||||
|
{
|
||||||
|
auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
|
||||||
|
pHatedActor->getPos().x,
|
||||||
|
pHatedActor->getPos().y,
|
||||||
|
pHatedActor->getPos().z );
|
||||||
|
|
||||||
|
if( distanceOrig > maxDistanceToOrigin )
|
||||||
|
{
|
||||||
|
hateListClear();
|
||||||
|
changeTarget( INVALID_GAME_OBJECT_ID64 );
|
||||||
|
setStance( Stance::Passive );
|
||||||
|
//setOwner( nullptr );
|
||||||
|
m_state = BNpcState::Retreat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( distance > minActorDistance )
|
||||||
|
moveTo( pHatedActor->getPos() );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( face( pHatedActor->getPos() ) )
|
||||||
|
sendPositionUpdate();
|
||||||
|
// in combat range. ATTACK!
|
||||||
|
autoAttack( pHatedActor );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changeTarget( INVALID_GAME_OBJECT_ID64 );
|
||||||
|
setStance( Stance::Passive );
|
||||||
|
//setOwner( nullptr );
|
||||||
|
m_state = BNpcState::Retreat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Chara::update( currTime );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::regainHp()
|
||||||
|
{
|
||||||
|
if( this->m_hp < this->getMaxHp() )
|
||||||
|
{
|
||||||
|
auto addHp = static_cast< uint32_t >( this->getMaxHp() * 0.1f + 1 );
|
||||||
|
|
||||||
|
if( this->m_hp + addHp < this->getMaxHp() )
|
||||||
|
this->m_hp += addHp;
|
||||||
|
else
|
||||||
|
this->m_hp = this->getMaxHp();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sendStatusUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::onActionHostile( Sapphire::Entity::CharaPtr pSource )
|
||||||
|
{
|
||||||
|
if( !hateListGetHighest() )
|
||||||
|
aggro( pSource );
|
||||||
|
|
||||||
|
//if( !getClaimer() )
|
||||||
|
// setOwner( pSource->getAsPlayer() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::onDeath()
|
||||||
|
{
|
||||||
|
setTargetId( INVALID_GAME_OBJECT_ID );
|
||||||
|
m_currentStance = Stance::Passive;
|
||||||
|
m_state = BNpcState::Dead;
|
||||||
|
m_timeOfDeath = Util::getTimeSeconds();
|
||||||
|
|
||||||
|
for( auto& pHateEntry : m_hateList )
|
||||||
|
{
|
||||||
|
// TODO: handle drops
|
||||||
|
auto pPlayer = pHateEntry->m_pChara->getAsPlayer();
|
||||||
|
if( pPlayer )
|
||||||
|
pPlayer->onMobKill( m_bNpcNameId );
|
||||||
|
}
|
||||||
|
hateListClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Sapphire::Entity::BNpc::getTimeOfDeath() const
|
||||||
|
{
|
||||||
|
return m_timeOfDeath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::setTimeOfDeath( uint32_t timeOfDeath )
|
||||||
|
{
|
||||||
|
m_timeOfDeath = timeOfDeath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::BNpc::checkAggro( uint32_t range )
|
||||||
|
{
|
||||||
|
// passive mobs should ignore players unless aggro'd
|
||||||
|
if( m_aggressionMode == 1 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
CharaPtr pClosestChara = getClosestChara();
|
||||||
|
|
||||||
|
if( pClosestChara && pClosestChara->isAlive() )
|
||||||
|
{
|
||||||
|
auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
|
||||||
|
pClosestChara->getPos().x,
|
||||||
|
pClosestChara->getPos().y,
|
||||||
|
pClosestChara->getPos().z );
|
||||||
|
|
||||||
|
if( distance < range && pClosestChara->isPlayer() )
|
||||||
|
aggro( pClosestChara );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,22 @@
|
||||||
namespace Sapphire::Entity
|
namespace Sapphire::Entity
|
||||||
{
|
{
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t m_hateAmount;
|
||||||
|
CharaPtr m_pChara;
|
||||||
|
} HateListEntry;
|
||||||
|
|
||||||
|
enum class BNpcState
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
Combat,
|
||||||
|
Retreat,
|
||||||
|
Roaming,
|
||||||
|
JustDied,
|
||||||
|
Dead,
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\class BNpc
|
\class BNpc
|
||||||
\brief Base class for all BNpcs
|
\brief Base class for all BNpcs
|
||||||
|
@ -24,11 +40,12 @@ namespace Sapphire::Entity
|
||||||
public:
|
public:
|
||||||
BNpc( FrameworkPtr pFw );
|
BNpc( FrameworkPtr pFw );
|
||||||
BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX, float posY, float posZ, float rot,
|
BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX, float posY, float posZ, float rot,
|
||||||
uint8_t level, uint32_t maxHp, FrameworkPtr pFw );
|
uint8_t level, uint32_t maxHp, ZonePtr pZone,FrameworkPtr pFw );
|
||||||
|
|
||||||
virtual ~BNpc() override;
|
virtual ~BNpc() override;
|
||||||
|
|
||||||
void spawn( PlayerPtr pTarget ) override;
|
void spawn( PlayerPtr pTarget ) override;
|
||||||
|
void despawn( PlayerPtr pTarget ) override;
|
||||||
|
|
||||||
uint16_t getModelChara() const;
|
uint16_t getModelChara() const;
|
||||||
uint8_t getLevel() const override;
|
uint8_t getLevel() const override;
|
||||||
|
@ -43,6 +60,41 @@ namespace Sapphire::Entity
|
||||||
|
|
||||||
uint8_t getAggressionMode() const;
|
uint8_t getAggressionMode() const;
|
||||||
|
|
||||||
|
// return true if it reached the position
|
||||||
|
bool moveTo( const Common::FFXIVARR_POSITION3& pos );
|
||||||
|
|
||||||
|
// processes movement
|
||||||
|
void step();
|
||||||
|
|
||||||
|
void sendPositionUpdate();
|
||||||
|
|
||||||
|
BNpcState getState() const;
|
||||||
|
void setState( BNpcState state );
|
||||||
|
|
||||||
|
void hateListClear();
|
||||||
|
CharaPtr hateListGetHighest();
|
||||||
|
void hateListAdd( CharaPtr pChara, int32_t hateAmount );
|
||||||
|
void hateListUpdate( CharaPtr pChara, int32_t hateAmount );
|
||||||
|
void hateListRemove( CharaPtr pChara );
|
||||||
|
bool hateListHasActor( CharaPtr pChara );
|
||||||
|
|
||||||
|
void aggro( CharaPtr pChara );
|
||||||
|
void deaggro( CharaPtr pChara );
|
||||||
|
|
||||||
|
void update( int64_t currTime ) override;
|
||||||
|
void onTick() override;
|
||||||
|
|
||||||
|
void onActionHostile( CharaPtr pSource ) override;
|
||||||
|
|
||||||
|
void onDeath() override;
|
||||||
|
|
||||||
|
uint32_t getTimeOfDeath() const;
|
||||||
|
void setTimeOfDeath( uint32_t timeOfDeath );
|
||||||
|
|
||||||
|
void regainHp();
|
||||||
|
|
||||||
|
void checkAggro( uint32_t range );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_bNpcBaseId;
|
uint32_t m_bNpcBaseId;
|
||||||
uint32_t m_bNpcNameId;
|
uint32_t m_bNpcNameId;
|
||||||
|
@ -56,6 +108,20 @@ namespace Sapphire::Entity
|
||||||
uint32_t m_displayFlags;
|
uint32_t m_displayFlags;
|
||||||
uint8_t m_level;
|
uint8_t m_level;
|
||||||
|
|
||||||
|
uint32_t m_timeOfDeath;
|
||||||
|
uint32_t m_lastRoamTargetReached;
|
||||||
|
|
||||||
|
Common::FFXIVARR_POSITION3 m_spawnPos;
|
||||||
|
Common::FFXIVARR_POSITION3 m_roamPos;
|
||||||
|
|
||||||
|
BNpcState m_state;
|
||||||
|
std::set< std::shared_ptr< HateListEntry > > m_hateList;
|
||||||
|
|
||||||
|
uint64_t m_naviLastUpdate;
|
||||||
|
std::vector< Common::FFXIVARR_POSITION3 > m_naviLastPath;
|
||||||
|
uint8_t m_naviPathStep;
|
||||||
|
Common::FFXIVARR_POSITION3 m_naviTarget;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ Sapphire::Entity::Chara::Chara( ObjKind type, FrameworkPtr pFw ) :
|
||||||
Actor( type ),
|
Actor( type ),
|
||||||
m_pose( 0 ),
|
m_pose( 0 ),
|
||||||
m_targetId( INVALID_GAME_OBJECT_ID ),
|
m_targetId( INVALID_GAME_OBJECT_ID ),
|
||||||
m_pFw( pFw )
|
m_pFw( std::move( std::move( pFw ) ) )
|
||||||
{
|
{
|
||||||
// initialize the free slot queue
|
// initialize the free slot queue
|
||||||
for( uint8_t i = 0; i < MAX_STATUS_EFFECTS; i++ )
|
for( uint8_t i = 0; i < MAX_STATUS_EFFECTS; i++ )
|
||||||
|
@ -48,8 +48,7 @@ Sapphire::Entity::Chara::Chara( ObjKind type, FrameworkPtr pFw ) :
|
||||||
}
|
}
|
||||||
|
|
||||||
Sapphire::Entity::Chara::~Chara()
|
Sapphire::Entity::Chara::~Chara()
|
||||||
{
|
= default;
|
||||||
}
|
|
||||||
|
|
||||||
/*! \return the actors name */
|
/*! \return the actors name */
|
||||||
std::string Sapphire::Entity::Chara::getName() const
|
std::string Sapphire::Entity::Chara::getName() const
|
||||||
|
@ -152,35 +151,35 @@ uint32_t Sapphire::Entity::Chara::getMaxMp() const
|
||||||
void Sapphire::Entity::Chara::resetHp()
|
void Sapphire::Entity::Chara::resetHp()
|
||||||
{
|
{
|
||||||
m_hp = getMaxHp();
|
m_hp = getMaxHp();
|
||||||
sendStatusUpdate( true );
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \return reset mp to current max mp */
|
/*! \return reset mp to current max mp */
|
||||||
void Sapphire::Entity::Chara::resetMp()
|
void Sapphire::Entity::Chara::resetMp()
|
||||||
{
|
{
|
||||||
m_mp = getMaxMp();
|
m_mp = getMaxMp();
|
||||||
sendStatusUpdate( true );
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \param hp amount to set ( caps to maxHp ) */
|
/*! \param hp amount to set ( caps to maxHp ) */
|
||||||
void Sapphire::Entity::Chara::setHp( uint32_t hp )
|
void Sapphire::Entity::Chara::setHp( uint32_t hp )
|
||||||
{
|
{
|
||||||
m_hp = hp < getMaxHp() ? hp : getMaxHp();
|
m_hp = hp < getMaxHp() ? hp : getMaxHp();
|
||||||
sendStatusUpdate( true );
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \param mp amount to set ( caps to maxMp ) */
|
/*! \param mp amount to set ( caps to maxMp ) */
|
||||||
void Sapphire::Entity::Chara::setMp( uint32_t mp )
|
void Sapphire::Entity::Chara::setMp( uint32_t mp )
|
||||||
{
|
{
|
||||||
m_mp = mp < getMaxMp() ? mp : getMaxMp();
|
m_mp = mp < getMaxMp() ? mp : getMaxMp();
|
||||||
sendStatusUpdate( true );
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \param gp amount to set*/
|
/*! \param gp amount to set*/
|
||||||
void Sapphire::Entity::Chara::setGp( uint32_t gp )
|
void Sapphire::Entity::Chara::setGp( uint32_t gp )
|
||||||
{
|
{
|
||||||
m_gp = gp;
|
m_gp = gp;
|
||||||
sendStatusUpdate( true );
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \param type invincibility type to set */
|
/*! \param type invincibility type to set */
|
||||||
|
@ -245,7 +244,7 @@ bool Sapphire::Entity::Chara::face( const Common::FFXIVARR_POSITION3& p )
|
||||||
|
|
||||||
setRot( newRot );
|
setRot( newRot );
|
||||||
|
|
||||||
return oldRot != newRot ? true : false;
|
return oldRot != newRot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -281,6 +280,18 @@ bool Sapphire::Entity::Chara::checkAction()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::Chara::update( int64_t currTime )
|
||||||
|
{
|
||||||
|
if( std::difftime( currTime, m_lastTickTime ) > 3000 )
|
||||||
|
{
|
||||||
|
onTick();
|
||||||
|
|
||||||
|
m_lastTickTime = currTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastUpdate = currTime;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Change the current target and propagate to in range players
|
Change the current target and propagate to in range players
|
||||||
|
|
||||||
|
@ -326,12 +337,14 @@ void Sapphire::Entity::Chara::takeDamage( uint32_t damage )
|
||||||
case InvincibilityStayAlive:
|
case InvincibilityStayAlive:
|
||||||
setHp( 0 );
|
setHp( 0 );
|
||||||
break;
|
break;
|
||||||
|
case InvincibilityIgnoreDamage:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
m_hp -= damage;
|
m_hp -= damage;
|
||||||
|
|
||||||
sendStatusUpdate( false );
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -350,7 +363,7 @@ void Sapphire::Entity::Chara::heal( uint32_t amount )
|
||||||
else
|
else
|
||||||
m_hp += amount;
|
m_hp += amount;
|
||||||
|
|
||||||
sendStatusUpdate( false );
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -360,7 +373,7 @@ so players can have their own version and we can abolish the param.
|
||||||
|
|
||||||
\param true if the update should also be sent to the actor ( player ) himself
|
\param true if the update should also be sent to the actor ( player ) himself
|
||||||
*/
|
*/
|
||||||
void Sapphire::Entity::Chara::sendStatusUpdate( bool toSelf )
|
void Sapphire::Entity::Chara::sendStatusUpdate()
|
||||||
{
|
{
|
||||||
FFXIVPacketBasePtr packet = std::make_shared< UpdateHpMpTpPacket >( *this );
|
FFXIVPacketBasePtr packet = std::make_shared< UpdateHpMpTpPacket >( *this );
|
||||||
sendToInRangeSet( packet );
|
sendToInRangeSet( packet );
|
||||||
|
@ -375,7 +388,7 @@ Sapphire::Action::ActionPtr Sapphire::Entity::Chara::getCurrentAction() const
|
||||||
/*! \param ActionPtr of the action to be registered */
|
/*! \param ActionPtr of the action to be registered */
|
||||||
void Sapphire::Entity::Chara::setCurrentAction( Sapphire::Action::ActionPtr pAction )
|
void Sapphire::Entity::Chara::setCurrentAction( Sapphire::Action::ActionPtr pAction )
|
||||||
{
|
{
|
||||||
m_pCurrentAction = pAction;
|
m_pCurrentAction = std::move( pAction );
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -392,42 +405,38 @@ void Sapphire::Entity::Chara::autoAttack( CharaPtr pTarget )
|
||||||
|
|
||||||
uint64_t tick = Util::getTimeMs();
|
uint64_t tick = Util::getTimeMs();
|
||||||
|
|
||||||
|
// todo: this needs to use the auto attack delay for the equipped weapon
|
||||||
if( ( tick - m_lastAttack ) > 2500 )
|
if( ( tick - m_lastAttack ) > 2500 )
|
||||||
{
|
{
|
||||||
pTarget->onActionHostile( *this );
|
pTarget->onActionHostile( getAsChara() );
|
||||||
m_lastAttack = tick;
|
m_lastAttack = tick;
|
||||||
srand( static_cast< uint32_t >( tick ) );
|
srand( static_cast< uint32_t >( tick ) );
|
||||||
|
|
||||||
uint16_t damage = static_cast< uint16_t >( 10 + rand() % 12 );
|
auto damage = static_cast< uint16_t >( 10 + rand() % 12 );
|
||||||
uint32_t variation = static_cast< uint32_t >( 0 + rand() % 4 );
|
|
||||||
|
|
||||||
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 0x336 );
|
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
|
||||||
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
||||||
|
|
||||||
Server::EffectEntry effectEntry{};
|
Server::EffectEntry effectEntry{};
|
||||||
effectEntry.value = damage;
|
effectEntry.value = damage;
|
||||||
effectEntry.effectType = ActionEffectType::Damage;
|
effectEntry.effectType = ActionEffectType::Damage;
|
||||||
effectEntry.hitSeverity = static_cast< ActionHitSeverityType >( variation );
|
effectEntry.hitSeverity = ActionHitSeverityType::NormalDamage;
|
||||||
|
|
||||||
effectPacket->addEffect( effectEntry );
|
effectPacket->addEffect( effectEntry );
|
||||||
|
|
||||||
sendToInRangeSet( effectPacket );
|
sendToInRangeSet( effectPacket );
|
||||||
|
|
||||||
if( isPlayer() )
|
|
||||||
getAsPlayer()->queuePacket( effectPacket );
|
|
||||||
|
|
||||||
pTarget->takeDamage( damage );
|
pTarget->takeDamage( damage );
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
ChaiScript Skill Handler.
|
Skill Handler.
|
||||||
|
|
||||||
\param GamePacketPtr to send
|
\param GamePacketPtr to send
|
||||||
\param bool should be send to self?
|
\param bool should be send to self?
|
||||||
*/
|
*/
|
||||||
void Sapphire::Entity::Chara::handleScriptSkill( uint32_t type, uint16_t actionId, uint64_t param1,
|
void Sapphire::Entity::Chara::handleScriptSkill( uint32_t type, uint16_t actionId, uint64_t param1,
|
||||||
uint64_t param2, Entity::Chara& target )
|
uint64_t param2, Entity::Chara& target )
|
||||||
{
|
{
|
||||||
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
|
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
|
||||||
if( isPlayer() )
|
if( isPlayer() )
|
||||||
|
@ -467,7 +476,7 @@ void Sapphire::Entity::Chara::handleScriptSkill( uint32_t type, uint16_t actionI
|
||||||
sendToInRangeSet( effectPacket, true );
|
sendToInRangeSet( effectPacket, true );
|
||||||
|
|
||||||
if( target.isAlive() )
|
if( target.isAlive() )
|
||||||
target.onActionHostile( *this );
|
target.onActionHostile( getAsChara() );
|
||||||
|
|
||||||
target.takeDamage( static_cast< uint32_t >( param1 ) );
|
target.takeDamage( static_cast< uint32_t >( param1 ) );
|
||||||
|
|
||||||
|
@ -487,7 +496,7 @@ void Sapphire::Entity::Chara::handleScriptSkill( uint32_t type, uint16_t actionI
|
||||||
|
|
||||||
|
|
||||||
if( pHitActor->getAsChara()->isAlive() )
|
if( pHitActor->getAsChara()->isAlive() )
|
||||||
pHitActor->getAsChara()->onActionHostile( *this );
|
pHitActor->getAsChara()->onActionHostile( getAsChara() );
|
||||||
|
|
||||||
pHitActor->getAsChara()->takeDamage( static_cast< uint32_t >( param1 ) );
|
pHitActor->getAsChara()->takeDamage( static_cast< uint32_t >( param1 ) );
|
||||||
|
|
||||||
|
@ -534,7 +543,7 @@ void Sapphire::Entity::Chara::handleScriptSkill( uint32_t type, uint16_t actionI
|
||||||
auto actorsCollided = ActionCollision::getActorsHitFromAction( target.getPos(), getInRangeActors( true ),
|
auto actorsCollided = ActionCollision::getActorsHitFromAction( target.getPos(), getInRangeActors( true ),
|
||||||
actionInfoPtr, TargetFilter::Allies );
|
actionInfoPtr, TargetFilter::Allies );
|
||||||
|
|
||||||
for( auto pHitActor : actorsCollided )
|
for( const auto& pHitActor : actorsCollided )
|
||||||
{
|
{
|
||||||
effectPacket->setTargetActor( pHitActor->getId() );
|
effectPacket->setTargetActor( pHitActor->getId() );
|
||||||
|
|
||||||
|
@ -599,7 +608,7 @@ void Sapphire::Entity::Chara::addStatusEffectById( uint32_t id, int32_t duration
|
||||||
|
|
||||||
/*! \param StatusEffectPtr to be applied to the actor */
|
/*! \param StatusEffectPtr to be applied to the actor */
|
||||||
void Sapphire::Entity::Chara::addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source,
|
void Sapphire::Entity::Chara::addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source,
|
||||||
uint16_t param )
|
uint16_t param )
|
||||||
{
|
{
|
||||||
if( hasStatusEffect( id ) )
|
if( hasStatusEffect( id ) )
|
||||||
return;
|
return;
|
||||||
|
@ -781,8 +790,6 @@ void Sapphire::Entity::Chara::updateStatusEffects()
|
||||||
|
|
||||||
bool Sapphire::Entity::Chara::hasStatusEffect( uint32_t id )
|
bool Sapphire::Entity::Chara::hasStatusEffect( uint32_t id )
|
||||||
{
|
{
|
||||||
if( m_statusEffectMap.find( id ) != m_statusEffectMap.end() )
|
return m_statusEffectMap.find( id ) != m_statusEffectMap.end();
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,11 +68,11 @@ namespace Sapphire::Entity
|
||||||
protected:
|
protected:
|
||||||
char m_name[34];
|
char m_name[34];
|
||||||
/*! Last tick time for the actor ( in ms ) */
|
/*! Last tick time for the actor ( in ms ) */
|
||||||
uint64_t m_lastTickTime;
|
int64_t m_lastTickTime;
|
||||||
/*! Last time the actor performed an autoAttack ( in ms ) */
|
/*! Last time the actor performed an autoAttack ( in ms ) */
|
||||||
uint64_t m_lastAttack;
|
uint64_t m_lastAttack;
|
||||||
/*! Last time the actor was updated ( in ms ) */
|
/*! Last time the actor was updated ( in ms ) */
|
||||||
uint64_t m_lastUpdate;
|
int64_t m_lastUpdate;
|
||||||
/*! Current stance of the actor */
|
/*! Current stance of the actor */
|
||||||
Common::Stance m_currentStance;
|
Common::Stance m_currentStance;
|
||||||
/*! Current staus of the actor */
|
/*! Current staus of the actor */
|
||||||
|
@ -219,7 +219,7 @@ namespace Sapphire::Entity
|
||||||
|
|
||||||
virtual void onDamageTaken( Chara& pSource ) {};
|
virtual void onDamageTaken( Chara& pSource ) {};
|
||||||
|
|
||||||
virtual void onActionHostile( Chara& source ) {};
|
virtual void onActionHostile( CharaPtr pSource ) {};
|
||||||
|
|
||||||
virtual void onActionFriendly( Chara& pSource ) {};
|
virtual void onActionFriendly( Chara& pSource ) {};
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ namespace Sapphire::Entity
|
||||||
|
|
||||||
virtual uint8_t getLevel() const;
|
virtual uint8_t getLevel() const;
|
||||||
|
|
||||||
virtual void sendStatusUpdate( bool toSelf = true );
|
virtual void sendStatusUpdate();
|
||||||
|
|
||||||
virtual void takeDamage( uint32_t damage );
|
virtual void takeDamage( uint32_t damage );
|
||||||
|
|
||||||
|
@ -237,7 +237,7 @@ namespace Sapphire::Entity
|
||||||
|
|
||||||
virtual bool checkAction();
|
virtual bool checkAction();
|
||||||
|
|
||||||
virtual void update( int64_t currTime ) {};
|
virtual void update( int64_t currTime );
|
||||||
|
|
||||||
Action::ActionPtr getCurrentAction() const;
|
Action::ActionPtr getCurrentAction() const;
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,14 @@
|
||||||
|
|
||||||
#include "Session.h"
|
#include "Session.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
|
#include "BNpc.h"
|
||||||
#include "Manager/TerritoryMgr.h"
|
|
||||||
#include "Territory/Zone.h"
|
|
||||||
#include "Territory/ZonePosition.h"
|
|
||||||
|
|
||||||
#include "Manager/HousingMgr.h"
|
#include "Manager/HousingMgr.h"
|
||||||
|
#include "Manager/TerritoryMgr.h"
|
||||||
|
|
||||||
|
#include "Territory/Zone.h"
|
||||||
|
#include "Territory/ZonePosition.h"
|
||||||
|
#include "Territory/InstanceContent.h"
|
||||||
#include "Territory/Land.h"
|
#include "Territory/Land.h"
|
||||||
|
|
||||||
#include "Network/GameConnection.h"
|
#include "Network/GameConnection.h"
|
||||||
|
@ -419,13 +421,13 @@ void Sapphire::Entity::Player::setZone( uint32_t zoneId )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendZonePackets();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sapphire::Entity::Player::setInstance( uint32_t instanceContentId )
|
bool Sapphire::Entity::Player::setInstance( uint32_t instanceContentId )
|
||||||
{
|
{
|
||||||
auto pTeriMgr = m_pFw->get< TerritoryMgr >();
|
|
||||||
m_onEnterEventDone = false;
|
m_onEnterEventDone = false;
|
||||||
|
auto pTeriMgr = m_pFw->get< TerritoryMgr >();
|
||||||
|
|
||||||
auto instance = pTeriMgr->getInstanceZonePtr( instanceContentId );
|
auto instance = pTeriMgr->getInstanceZonePtr( instanceContentId );
|
||||||
if( !instance )
|
if( !instance )
|
||||||
return false;
|
return false;
|
||||||
|
@ -440,7 +442,6 @@ bool Sapphire::Entity::Player::setInstance( ZonePtr instance )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto pTeriMgr = m_pFw->get< TerritoryMgr >();
|
auto pTeriMgr = m_pFw->get< TerritoryMgr >();
|
||||||
|
|
||||||
auto currentZone = getCurrentZone();
|
auto currentZone = getCurrentZone();
|
||||||
|
|
||||||
// zoning within the same zone won't cause the prev data to be overwritten
|
// zoning within the same zone won't cause the prev data to be overwritten
|
||||||
|
@ -452,35 +453,30 @@ bool Sapphire::Entity::Player::setInstance( ZonePtr instance )
|
||||||
m_prevTerritoryId = getTerritoryId();
|
m_prevTerritoryId = getTerritoryId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !pTeriMgr->movePlayer( instance, getAsPlayer() ) )
|
return pTeriMgr->movePlayer( instance, getAsPlayer() );
|
||||||
return false;
|
|
||||||
|
|
||||||
sendZonePackets();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sapphire::Entity::Player::setInstance( ZonePtr instance, Common::FFXIVARR_POSITION3 pos )
|
bool Sapphire::Entity::Player::setInstance( ZonePtr instance, Common::FFXIVARR_POSITION3 pos )
|
||||||
{
|
{
|
||||||
|
m_onEnterEventDone = false;
|
||||||
if( !instance )
|
if( !instance )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
m_onEnterEventDone = false;
|
|
||||||
|
|
||||||
auto pTeriMgr = m_pFw->get< TerritoryMgr >();
|
auto pTeriMgr = m_pFw->get< TerritoryMgr >();
|
||||||
auto currentZone = getCurrentZone();
|
auto currentZone = getCurrentZone();
|
||||||
|
|
||||||
m_prevPos = m_pos;
|
// zoning within the same zone won't cause the prev data to be overwritten
|
||||||
m_prevRot = m_rot;
|
if( instance->getTerritoryTypeId() != m_territoryTypeId )
|
||||||
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
|
{
|
||||||
m_prevTerritoryId = getTerritoryId();
|
m_prevPos = m_pos;
|
||||||
|
m_prevRot = m_rot;
|
||||||
|
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
|
||||||
|
m_prevTerritoryId = getTerritoryId();
|
||||||
|
}
|
||||||
|
|
||||||
if( pTeriMgr->movePlayer( instance, getAsPlayer() ) )
|
if( pTeriMgr->movePlayer( instance, getAsPlayer() ) )
|
||||||
{
|
{
|
||||||
m_pos = pos;
|
m_pos = pos;
|
||||||
|
|
||||||
sendZonePackets();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,6 +487,9 @@ bool Sapphire::Entity::Player::exitInstance()
|
||||||
{
|
{
|
||||||
auto pTeriMgr = m_pFw->get< TerritoryMgr >();
|
auto pTeriMgr = m_pFw->get< TerritoryMgr >();
|
||||||
|
|
||||||
|
auto pZone = getCurrentZone();
|
||||||
|
auto pInstance = pZone->getAsInstanceContent();
|
||||||
|
|
||||||
// check if housing zone
|
// check if housing zone
|
||||||
if( pTeriMgr->isHousingTerritory( m_prevTerritoryTypeId ) )
|
if( pTeriMgr->isHousingTerritory( m_prevTerritoryTypeId ) )
|
||||||
{
|
{
|
||||||
|
@ -508,7 +507,7 @@ bool Sapphire::Entity::Player::exitInstance()
|
||||||
m_territoryTypeId = m_prevTerritoryTypeId;
|
m_territoryTypeId = m_prevTerritoryTypeId;
|
||||||
m_territoryId = m_prevTerritoryId;
|
m_territoryId = m_prevTerritoryId;
|
||||||
|
|
||||||
sendZonePackets();
|
//m_queuedZoneing = std::make_shared< QueuedZoning >( m_territoryTypeId, m_pos, Util::getTimeMs(), m_rot );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -535,7 +534,20 @@ void Sapphire::Entity::Player::initSpawnIdQueue()
|
||||||
|
|
||||||
uint8_t Sapphire::Entity::Player::getSpawnIdForActorId( uint32_t actorId )
|
uint8_t Sapphire::Entity::Player::getSpawnIdForActorId( uint32_t actorId )
|
||||||
{
|
{
|
||||||
return m_actorSpawnIndexAllocator.getNextFreeSpawnIndex( actorId );
|
auto index = m_actorSpawnIndexAllocator.getNextFreeSpawnIndex( actorId );
|
||||||
|
|
||||||
|
if( index == m_actorSpawnIndexAllocator.getAllocFailId() )
|
||||||
|
{
|
||||||
|
Logger::warn( "Failed to spawn Chara#{0} for Player#{1} - no remaining spawn indexes available. "
|
||||||
|
"Consider lowering InRangeDistance in world config.",
|
||||||
|
actorId, getId() );
|
||||||
|
|
||||||
|
sendUrgent( "Failed to spawn Chara#{0} for you - no remaining spawn slots. See world log.", actorId );
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sapphire::Entity::Player::isActorSpawnIdValid( uint8_t spawnIndex )
|
bool Sapphire::Entity::Player::isActorSpawnIdValid( uint8_t spawnIndex )
|
||||||
|
@ -740,7 +752,7 @@ void Sapphire::Entity::Player::gainLevel()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::Entity::Player::sendStatusUpdate( bool toSelf )
|
void Sapphire::Entity::Player::sendStatusUpdate()
|
||||||
{
|
{
|
||||||
sendToInRangeSet( std::make_shared< UpdateHpMpTpPacket >( *this ), true );
|
sendToInRangeSet( std::make_shared< UpdateHpMpTpPacket >( *this ), true );
|
||||||
}
|
}
|
||||||
|
@ -811,7 +823,7 @@ void Sapphire::Entity::Player::setClassJob( Common::ClassJob classJob )
|
||||||
|
|
||||||
sendToInRangeSet( makeActorControl142( getId(), ClassJobChange, 0x04 ), true );
|
sendToInRangeSet( makeActorControl142( getId(), ClassJobChange, 0x04 ), true );
|
||||||
|
|
||||||
sendStatusUpdate( true );
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::Entity::Player::setLevel( uint8_t level )
|
void Sapphire::Entity::Player::setLevel( uint8_t level )
|
||||||
|
@ -1044,7 +1056,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
|
||||||
|
|
||||||
void Sapphire::Entity::Player::update( int64_t currTime )
|
void Sapphire::Entity::Player::update( int64_t currTime )
|
||||||
{
|
{
|
||||||
|
|
||||||
// a zoning is pending, lets do it
|
// a zoning is pending, lets do it
|
||||||
if( m_queuedZoneing && ( currTime - m_queuedZoneing->m_queueTime ) > 800 )
|
if( m_queuedZoneing && ( currTime - m_queuedZoneing->m_queueTime ) > 800 )
|
||||||
{
|
{
|
||||||
|
@ -1116,13 +1127,7 @@ void Sapphire::Entity::Player::update( int64_t currTime )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( ( currTime - m_lastTickTime ) > 3000 )
|
Chara::update( currTime );
|
||||||
{
|
|
||||||
// add 3 seconds to total play time
|
|
||||||
m_playTime += 3;
|
|
||||||
m_lastTickTime = currTime;
|
|
||||||
onTick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::Entity::Player::onMobKill( uint16_t nameId )
|
void Sapphire::Entity::Player::onMobKill( uint16_t nameId )
|
||||||
|
@ -1135,11 +1140,14 @@ void Sapphire::Entity::Player::freePlayerSpawnId( uint32_t actorId )
|
||||||
{
|
{
|
||||||
auto spawnId = m_actorSpawnIndexAllocator.freeUsedSpawnIndex( actorId );
|
auto spawnId = m_actorSpawnIndexAllocator.freeUsedSpawnIndex( actorId );
|
||||||
|
|
||||||
|
// actor was never spawned for this player
|
||||||
|
if( spawnId == m_actorSpawnIndexAllocator.getAllocFailId() )
|
||||||
|
return;
|
||||||
|
|
||||||
auto freeActorSpawnPacket = makeZonePacket< FFXIVIpcActorFreeSpawn >( getId() );
|
auto freeActorSpawnPacket = makeZonePacket< FFXIVIpcActorFreeSpawn >( getId() );
|
||||||
freeActorSpawnPacket->data().actorId = actorId;
|
freeActorSpawnPacket->data().actorId = actorId;
|
||||||
freeActorSpawnPacket->data().spawnId = spawnId;
|
freeActorSpawnPacket->data().spawnId = spawnId;
|
||||||
queuePacket( freeActorSpawnPacket );
|
queuePacket( freeActorSpawnPacket );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* Sapphire::Entity::Player::getAetheryteArray()
|
uint8_t* Sapphire::Entity::Player::getAetheryteArray()
|
||||||
|
@ -1350,6 +1358,45 @@ void Sapphire::Entity::Player::initHateSlotQueue()
|
||||||
m_freeHateSlotQueue.push( i );
|
m_freeHateSlotQueue.push( i );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::Player::hateListAdd( BNpcPtr pBNpc )
|
||||||
|
{
|
||||||
|
if( !m_freeHateSlotQueue.empty() )
|
||||||
|
{
|
||||||
|
uint8_t hateId = m_freeHateSlotQueue.front();
|
||||||
|
m_freeHateSlotQueue.pop();
|
||||||
|
m_actorIdTohateSlotMap[ pBNpc->getId() ] = hateId;
|
||||||
|
sendHateList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::Player::hateListRemove( BNpcPtr pBNpc )
|
||||||
|
{
|
||||||
|
|
||||||
|
auto it = m_actorIdTohateSlotMap.begin();
|
||||||
|
for( ; it != m_actorIdTohateSlotMap.end(); ++it )
|
||||||
|
{
|
||||||
|
if( it->first == pBNpc->getId() )
|
||||||
|
{
|
||||||
|
uint8_t hateSlot = it->second;
|
||||||
|
m_freeHateSlotQueue.push( hateSlot );
|
||||||
|
m_actorIdTohateSlotMap.erase( it );
|
||||||
|
sendHateList();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::Entity::Player::hateListHasEntry( BNpcPtr pBNpc )
|
||||||
|
{
|
||||||
|
for( const auto& entry : m_actorIdTohateSlotMap )
|
||||||
|
{
|
||||||
|
if( entry.first == pBNpc->getId() )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Sapphire::Entity::Player::sendHateList()
|
void Sapphire::Entity::Player::sendHateList()
|
||||||
{
|
{
|
||||||
auto hateListPacket = makeZonePacket< FFXIVIpcHateList >( getId() );
|
auto hateListPacket = makeZonePacket< FFXIVIpcHateList >( getId() );
|
||||||
|
@ -1363,6 +1410,19 @@ void Sapphire::Entity::Player::sendHateList()
|
||||||
queuePacket( hateListPacket );
|
queuePacket( hateListPacket );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::Player::onMobAggro( BNpcPtr pBNpc )
|
||||||
|
{
|
||||||
|
hateListAdd( pBNpc );
|
||||||
|
queuePacket( makeActorControl142( getId(), ToggleAggro, 1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::Player::onMobDeaggro( BNpcPtr pBNpc )
|
||||||
|
{
|
||||||
|
hateListRemove( pBNpc );
|
||||||
|
if( m_actorIdTohateSlotMap.empty() )
|
||||||
|
queuePacket( makeActorControl142( getId(), ToggleAggro ) );
|
||||||
|
}
|
||||||
|
|
||||||
bool Sapphire::Entity::Player::isLogin() const
|
bool Sapphire::Entity::Player::isLogin() const
|
||||||
{
|
{
|
||||||
return m_bIsLogin;
|
return m_bIsLogin;
|
||||||
|
@ -1464,7 +1524,7 @@ void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
|
||||||
|
|
||||||
auto mainWeap = getItemAt( Common::GearSet0, Common::GearSetSlot::MainHand );
|
auto mainWeap = getItemAt( Common::GearSet0, Common::GearSetSlot::MainHand );
|
||||||
|
|
||||||
pTarget->onActionHostile( *this );
|
pTarget->onActionHostile( getAsChara() );
|
||||||
//uint64_t tick = Util::getTimeMs();
|
//uint64_t tick = Util::getTimeMs();
|
||||||
//srand(static_cast< uint32_t >(tick));
|
//srand(static_cast< uint32_t >(tick));
|
||||||
|
|
||||||
|
@ -1476,7 +1536,7 @@ void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
|
||||||
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 8 );
|
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 8 );
|
||||||
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
||||||
|
|
||||||
Server::EffectEntry entry;
|
Server::EffectEntry entry{};
|
||||||
entry.value = damage;
|
entry.value = damage;
|
||||||
entry.effectType = Common::ActionEffectType::Damage;
|
entry.effectType = Common::ActionEffectType::Damage;
|
||||||
entry.hitSeverity = Common::ActionHitSeverityType::NormalDamage;
|
entry.hitSeverity = Common::ActionHitSeverityType::NormalDamage;
|
||||||
|
@ -1490,7 +1550,7 @@ void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
|
||||||
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
|
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
|
||||||
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
||||||
|
|
||||||
Server::EffectEntry entry;
|
Server::EffectEntry entry{};
|
||||||
entry.value = damage;
|
entry.value = damage;
|
||||||
entry.effectType = Common::ActionEffectType::Damage;
|
entry.effectType = Common::ActionEffectType::Damage;
|
||||||
entry.hitSeverity = Common::ActionHitSeverityType::NormalDamage;
|
entry.hitSeverity = Common::ActionHitSeverityType::NormalDamage;
|
||||||
|
@ -1535,7 +1595,7 @@ uint32_t Sapphire::Entity::Player::getCFPenaltyMinutes() const
|
||||||
void Sapphire::Entity::Player::setCFPenaltyMinutes( uint32_t minutes )
|
void Sapphire::Entity::Player::setCFPenaltyMinutes( uint32_t minutes )
|
||||||
{
|
{
|
||||||
auto currentTimestamp = Sapphire::Util::getTimeSeconds();
|
auto currentTimestamp = Sapphire::Util::getTimeSeconds();
|
||||||
setCFPenaltyTimestamp( static_cast< uint32_t >( currentTimestamp + minutes * 60 ) );
|
setCFPenaltyTimestamp( currentTimestamp + minutes * 60 );
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t Sapphire::Entity::Player::getOpeningSequence() const
|
uint8_t Sapphire::Entity::Player::getOpeningSequence() const
|
||||||
|
@ -1788,7 +1848,20 @@ void Sapphire::Entity::Player::teleportQuery( uint16_t aetheryteId, FrameworkPtr
|
||||||
|
|
||||||
uint8_t Sapphire::Entity::Player::getNextObjSpawnIndexForActorId( uint32_t actorId )
|
uint8_t Sapphire::Entity::Player::getNextObjSpawnIndexForActorId( uint32_t actorId )
|
||||||
{
|
{
|
||||||
return m_objSpawnIndexAllocator.getNextFreeSpawnIndex( actorId );
|
auto index = m_objSpawnIndexAllocator.getNextFreeSpawnIndex( actorId );
|
||||||
|
|
||||||
|
if( index == m_objSpawnIndexAllocator.getAllocFailId() )
|
||||||
|
{
|
||||||
|
Logger::warn( "Failed to spawn EObj#{0} for Player#{1} - no remaining spawn indexes available. "
|
||||||
|
"Consider lowering InRangeDistance in world config.",
|
||||||
|
actorId, getId() );
|
||||||
|
|
||||||
|
sendUrgent( "Failed to spawn EObj#{0} for you - no remaining spawn slots. See world log.", actorId );
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::Entity::Player::resetObjSpawnIndex()
|
void Sapphire::Entity::Player::resetObjSpawnIndex()
|
||||||
|
@ -1800,6 +1873,10 @@ void Sapphire::Entity::Player::freeObjSpawnIndexForActorId( uint32_t actorId )
|
||||||
{
|
{
|
||||||
auto spawnId = m_objSpawnIndexAllocator.freeUsedSpawnIndex( actorId );
|
auto spawnId = m_objSpawnIndexAllocator.freeUsedSpawnIndex( actorId );
|
||||||
|
|
||||||
|
// obj was never spawned for this player
|
||||||
|
if( spawnId == m_objSpawnIndexAllocator.getAllocFailId() )
|
||||||
|
return;
|
||||||
|
|
||||||
auto freeObjectSpawnPacket = makeZonePacket< FFXIVIpcObjectDespawn >( getId() );
|
auto freeObjectSpawnPacket = makeZonePacket< FFXIVIpcObjectDespawn >( getId() );
|
||||||
freeObjectSpawnPacket->data().spawnIndex = spawnId;
|
freeObjectSpawnPacket->data().spawnIndex = spawnId;
|
||||||
queuePacket( freeObjectSpawnPacket );
|
queuePacket( freeObjectSpawnPacket );
|
||||||
|
|
|
@ -707,7 +707,7 @@ namespace Sapphire::Entity
|
||||||
void sendStateFlags();
|
void sendStateFlags();
|
||||||
|
|
||||||
/*! send status update */
|
/*! send status update */
|
||||||
void sendStatusUpdate( bool toSelf = true ) override;
|
void sendStatusUpdate() override;
|
||||||
|
|
||||||
/*! send the entire inventory sequence */
|
/*! send the entire inventory sequence */
|
||||||
void sendInventory();
|
void sendInventory();
|
||||||
|
@ -798,6 +798,11 @@ namespace Sapphire::Entity
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
void initHateSlotQueue();
|
void initHateSlotQueue();
|
||||||
|
|
||||||
|
void hateListAdd( BNpcPtr pBNpc );
|
||||||
|
void hateListRemove( BNpcPtr pBNpc );
|
||||||
|
|
||||||
|
bool hateListHasEntry( BNpcPtr pBNpc );
|
||||||
|
|
||||||
void sendHateList();
|
void sendHateList();
|
||||||
|
|
||||||
bool actionHasCastTime( uint32_t actionId );
|
bool actionHasCastTime( uint32_t actionId );
|
||||||
|
@ -831,6 +836,9 @@ namespace Sapphire::Entity
|
||||||
|
|
||||||
bool isAutoattackOn() const;
|
bool isAutoattackOn() const;
|
||||||
|
|
||||||
|
void onMobAggro( BNpcPtr pBNpc );
|
||||||
|
void onMobDeaggro( BNpcPtr pBNpc );
|
||||||
|
|
||||||
// Content Finder handling
|
// Content Finder handling
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/*! Get an unix time when the player can register into content finder again. */
|
/*! Get an unix time when the player can register into content finder again. */
|
||||||
|
|
|
@ -355,6 +355,9 @@ void Sapphire::Entity::Player::onDeath()
|
||||||
void Sapphire::Entity::Player::onTick()
|
void Sapphire::Entity::Player::onTick()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// add 3 seconds to total play time
|
||||||
|
m_playTime += 3;
|
||||||
|
|
||||||
bool sendUpdate = false;
|
bool sendUpdate = false;
|
||||||
|
|
||||||
if( !isAlive() || !isLoadingComplete() )
|
if( !isAlive() || !isLoadingComplete() )
|
||||||
|
|
|
@ -19,7 +19,8 @@ file( GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
Script/*.c*
|
Script/*.c*
|
||||||
StatusEffect/*.c*
|
StatusEffect/*.c*
|
||||||
Territory/*.c*
|
Territory/*.c*
|
||||||
Territory/Housing/*.c*)
|
Territory/Housing/*.c*
|
||||||
|
Navi/*.c*)
|
||||||
|
|
||||||
add_executable( world ${SERVER_SOURCE_FILES} )
|
add_executable( world ${SERVER_SOURCE_FILES} )
|
||||||
|
|
||||||
|
@ -30,10 +31,12 @@ set_target_properties( world
|
||||||
|
|
||||||
target_link_libraries( world
|
target_link_libraries( world
|
||||||
PUBLIC
|
PUBLIC
|
||||||
common )
|
common
|
||||||
|
Detour)
|
||||||
target_include_directories( world
|
target_include_directories( world
|
||||||
PUBLIC
|
PUBLIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}" )
|
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
|
Detour )
|
||||||
|
|
||||||
|
|
||||||
if( UNIX )
|
if( UNIX )
|
||||||
|
|
|
@ -35,6 +35,11 @@ namespace World
|
||||||
TYPE_FORWARD( Session );
|
TYPE_FORWARD( Session );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace World::Navi
|
||||||
|
{
|
||||||
|
TYPE_FORWARD( NaviProvider );
|
||||||
|
}
|
||||||
|
|
||||||
namespace World::Territory::Housing
|
namespace World::Territory::Housing
|
||||||
{
|
{
|
||||||
TYPE_FORWARD( HousingInteriorTerritory );
|
TYPE_FORWARD( HousingInteriorTerritory );
|
||||||
|
|
|
@ -367,6 +367,18 @@ void Sapphire::World::Manager::DebugCommandMgr::set( char* data, Entity::Player&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if( subCommand == "mobaggro" )
|
||||||
|
{
|
||||||
|
auto inRange = player.getInRangeActors();
|
||||||
|
|
||||||
|
for( auto actor : inRange )
|
||||||
|
{
|
||||||
|
if( actor->getId() == player.getTargetId() && actor->getAsChara()->isAlive() )
|
||||||
|
{
|
||||||
|
actor->getAsBNpc()->onActionHostile( player.getAsChara() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
player.sendUrgent( "{0} is not a valid SET command.", subCommand );
|
player.sendUrgent( "{0} is not a valid SET command.", subCommand );
|
||||||
|
@ -439,7 +451,7 @@ void Sapphire::World::Manager::DebugCommandMgr::add( char* data, Entity::Player&
|
||||||
player.getPos().y,
|
player.getPos().y,
|
||||||
player.getPos().z,
|
player.getPos().z,
|
||||||
player.getRot(),
|
player.getRot(),
|
||||||
1, 1000, framework() );
|
1, 1000, playerZone, framework() );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -794,14 +806,14 @@ Sapphire::World::Manager::DebugCommandMgr::instance( char* data, Entity::Player&
|
||||||
|
|
||||||
if( subCommand == "create" || subCommand == "cr" )
|
if( subCommand == "create" || subCommand == "cr" )
|
||||||
{
|
{
|
||||||
uint32_t instanceContentId;
|
uint32_t contentFinderConditionId;
|
||||||
sscanf( params.c_str(), "%d", &instanceContentId );
|
sscanf( params.c_str(), "%d", &contentFinderConditionId );
|
||||||
|
|
||||||
auto instance = pTeriMgr->createInstanceContent( instanceContentId );
|
auto instance = pTeriMgr->createInstanceContent( contentFinderConditionId );
|
||||||
if( instance )
|
if( instance )
|
||||||
player.sendDebug( "Created instance with id#{0} -> {1}", instance->getGuId(), instance->getName() );
|
player.sendDebug( "Created instance with id#{0} -> {1}", instance->getGuId(), instance->getName() );
|
||||||
else
|
else
|
||||||
player.sendDebug( "Failed to create instance with id#{0}", instanceContentId );
|
player.sendDebug( "Failed to create instance with id#{0}", contentFinderConditionId );
|
||||||
}
|
}
|
||||||
else if( subCommand == "bind" )
|
else if( subCommand == "bind" )
|
||||||
{
|
{
|
||||||
|
|
|
@ -956,7 +956,7 @@ bool Sapphire::World::Manager::HousingMgr::isPlacedItemsInventory( Sapphire::Com
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::World::Manager::HousingMgr::reqPlaceHousingItem( Sapphire::Entity::Player& player, uint16_t landId,
|
void Sapphire::World::Manager::HousingMgr::reqPlaceHousingItem( Sapphire::Entity::Player& player, uint16_t landId,
|
||||||
uint16_t containerId, uint16_t slotId,
|
uint16_t containerId, uint8_t slotId,
|
||||||
Sapphire::Common::FFXIVARR_POSITION3 pos,
|
Sapphire::Common::FFXIVARR_POSITION3 pos,
|
||||||
float rotation )
|
float rotation )
|
||||||
{
|
{
|
||||||
|
@ -1038,7 +1038,7 @@ void Sapphire::World::Manager::HousingMgr::reqPlaceHousingItem( Sapphire::Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::World::Manager::HousingMgr::reqPlaceItemInStore( Sapphire::Entity::Player& player, uint16_t landId,
|
void Sapphire::World::Manager::HousingMgr::reqPlaceItemInStore( Sapphire::Entity::Player& player, uint16_t landId,
|
||||||
uint16_t containerId, uint16_t slotId )
|
uint16_t containerId, uint8_t slotId )
|
||||||
{
|
{
|
||||||
LandPtr land;
|
LandPtr land;
|
||||||
bool isOutside = false;
|
bool isOutside = false;
|
||||||
|
@ -1229,7 +1229,7 @@ void Sapphire::World::Manager::HousingMgr::sendInternalEstateInventoryBatch( Sap
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::World::Manager::HousingMgr::reqMoveHousingItem( Entity::Player& player,
|
void Sapphire::World::Manager::HousingMgr::reqMoveHousingItem( Entity::Player& player,
|
||||||
Common::LandIdent ident, uint16_t slot,
|
Common::LandIdent ident, uint8_t slot,
|
||||||
Common::FFXIVARR_POSITION3 pos, float rot )
|
Common::FFXIVARR_POSITION3 pos, float rot )
|
||||||
{
|
{
|
||||||
auto landSet = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
auto landSet = toLandSetId( ident.territoryTypeId, ident.wardNum );
|
||||||
|
@ -1254,7 +1254,7 @@ void Sapphire::World::Manager::HousingMgr::reqMoveHousingItem( Entity::Player& p
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sapphire::World::Manager::HousingMgr::moveInternalItem( Entity::Player& player, Common::LandIdent ident,
|
bool Sapphire::World::Manager::HousingMgr::moveInternalItem( Entity::Player& player, Common::LandIdent ident,
|
||||||
Territory::Housing::HousingInteriorTerritory& terri, uint16_t slot,
|
Territory::Housing::HousingInteriorTerritory& terri, uint8_t slot,
|
||||||
Common::FFXIVARR_POSITION3 pos, float rot )
|
Common::FFXIVARR_POSITION3 pos, float rot )
|
||||||
{
|
{
|
||||||
auto containerIdx = static_cast< uint16_t >( slot / 50 );
|
auto containerIdx = static_cast< uint16_t >( slot / 50 );
|
||||||
|
@ -1300,7 +1300,7 @@ bool Sapphire::World::Manager::HousingMgr::moveInternalItem( Entity::Player& pla
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sapphire::World::Manager::HousingMgr::moveExternalItem( Entity::Player& player,
|
bool Sapphire::World::Manager::HousingMgr::moveExternalItem( Entity::Player& player,
|
||||||
Common::LandIdent ident, uint16_t slot,
|
Common::LandIdent ident, uint8_t slot,
|
||||||
Sapphire::HousingZone& terri, Common::FFXIVARR_POSITION3 pos,
|
Sapphire::HousingZone& terri, Common::FFXIVARR_POSITION3 pos,
|
||||||
float rot )
|
float rot )
|
||||||
{
|
{
|
||||||
|
@ -1336,7 +1336,7 @@ bool Sapphire::World::Manager::HousingMgr::moveExternalItem( Entity::Player& pla
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::World::Manager::HousingMgr::reqRemoveHousingItem( Sapphire::Entity::Player& player, uint16_t plot,
|
void Sapphire::World::Manager::HousingMgr::reqRemoveHousingItem( Sapphire::Entity::Player& player, uint16_t plot,
|
||||||
uint16_t containerId, uint16_t slot,
|
uint16_t containerId, uint8_t slot,
|
||||||
bool sendToStoreroom )
|
bool sendToStoreroom )
|
||||||
{
|
{
|
||||||
if( auto terri = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ) )
|
if( auto terri = std::dynamic_pointer_cast< Territory::Housing::HousingInteriorTerritory >( player.getCurrentZone() ) )
|
||||||
|
@ -1456,7 +1456,7 @@ bool Sapphire::World::Manager::HousingMgr::removeInternalItem( Entity::Player& p
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sapphire::World::Manager::HousingMgr::removeExternalItem( Entity::Player& player, HousingZone& terri, Land& land,
|
bool Sapphire::World::Manager::HousingMgr::removeExternalItem( Entity::Player& player, HousingZone& terri, Land& land,
|
||||||
Common::InventoryType containerType, uint16_t slotId,
|
Common::InventoryType containerType, uint8_t slotId,
|
||||||
bool sendToStoreroom )
|
bool sendToStoreroom )
|
||||||
{
|
{
|
||||||
auto& containers = getEstateInventory( land.getLandIdent() );
|
auto& containers = getEstateInventory( land.getLandIdent() );
|
||||||
|
|
|
@ -156,10 +156,10 @@ namespace Sapphire::World::Manager
|
||||||
*/
|
*/
|
||||||
bool initHouseModels( Entity::Player& player, LandPtr land, uint32_t presetCatalogId );
|
bool initHouseModels( Entity::Player& player, LandPtr land, uint32_t presetCatalogId );
|
||||||
|
|
||||||
void reqPlaceHousingItem( Entity::Player& player, uint16_t landId, uint16_t containerId, uint16_t slotId,
|
void reqPlaceHousingItem( Entity::Player& player, uint16_t landId, uint16_t containerId, uint8_t slotId,
|
||||||
Common::FFXIVARR_POSITION3 pos, float rotation );
|
Common::FFXIVARR_POSITION3 pos, float rotation );
|
||||||
|
|
||||||
void reqPlaceItemInStore( Entity::Player& player, uint16_t landId, uint16_t containerId, uint16_t slotId );
|
void reqPlaceItemInStore( Entity::Player& player, uint16_t landId, uint16_t containerId, uint8_t slotId );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Returns the equivalent YardObject for a HousingItem
|
* @brief Returns the equivalent YardObject for a HousingItem
|
||||||
|
@ -169,13 +169,12 @@ namespace Sapphire::World::Manager
|
||||||
Common::HousingObject getYardObjectForItem( Inventory::HousingItemPtr item ) const;
|
Common::HousingObject getYardObjectForItem( Inventory::HousingItemPtr item ) const;
|
||||||
|
|
||||||
|
|
||||||
void reqMoveHousingItem( Entity::Player& player, Common::LandIdent ident, uint16_t slot,
|
void reqMoveHousingItem( Entity::Player& player, Common::LandIdent ident, uint8_t slot,
|
||||||
Common::FFXIVARR_POSITION3 pos, float rot );
|
Common::FFXIVARR_POSITION3 pos, float rot );
|
||||||
|
|
||||||
|
|
||||||
void reqRemoveHousingItem( Sapphire::Entity::Player& player, uint16_t plot,
|
void reqRemoveHousingItem( Sapphire::Entity::Player& player, uint16_t plot,
|
||||||
uint16_t containerId, uint16_t slot,
|
uint16_t containerId, uint8_t slot, bool sendToStoreroom );
|
||||||
bool sendToStoreroom );
|
|
||||||
|
|
||||||
void reqEstateExteriorRemodel( Entity::Player& player, uint16_t plot );
|
void reqEstateExteriorRemodel( Entity::Player& player, uint16_t plot );
|
||||||
|
|
||||||
|
@ -212,7 +211,7 @@ namespace Sapphire::World::Manager
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
bool removeExternalItem( Entity::Player& player, HousingZone& terri, Land& land,
|
bool removeExternalItem( Entity::Player& player, HousingZone& terri, Land& land,
|
||||||
Common::InventoryType containerType, uint16_t slotId,
|
Common::InventoryType containerType, uint8_t slotId,
|
||||||
bool sendToStoreroom );
|
bool sendToStoreroom );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -228,7 +227,7 @@ namespace Sapphire::World::Manager
|
||||||
* @param rot The new rotation
|
* @param rot The new rotation
|
||||||
* @return true if moved successfully
|
* @return true if moved successfully
|
||||||
*/
|
*/
|
||||||
bool moveExternalItem( Entity::Player& player, Common::LandIdent ident, uint16_t slot,
|
bool moveExternalItem( Entity::Player& player, Common::LandIdent ident, uint8_t slot,
|
||||||
Sapphire::HousingZone& terri, Common::FFXIVARR_POSITION3 pos, float rot );
|
Sapphire::HousingZone& terri, Common::FFXIVARR_POSITION3 pos, float rot );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -245,7 +244,7 @@ namespace Sapphire::World::Manager
|
||||||
* @return true if moved successfully
|
* @return true if moved successfully
|
||||||
*/
|
*/
|
||||||
bool moveInternalItem( Entity::Player& player, Common::LandIdent ident,
|
bool moveInternalItem( Entity::Player& player, Common::LandIdent ident,
|
||||||
Territory::Housing::HousingInteriorTerritory& terri, uint16_t slot,
|
Territory::Housing::HousingInteriorTerritory& terri, uint8_t slot,
|
||||||
Common::FFXIVARR_POSITION3 pos, float rot );
|
Common::FFXIVARR_POSITION3 pos, float rot );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -83,12 +83,11 @@ void Sapphire::World::Manager::MarketMgr::requestItemListingInfo( Sapphire::Enti
|
||||||
|
|
||||||
listing.itemCatalogId = catalogId;
|
listing.itemCatalogId = catalogId;
|
||||||
listing.quantity = i + 1;
|
listing.quantity = i + 1;
|
||||||
listing.purchaseTime = time( nullptr );
|
listing.purchaseTime = Sapphire::Util::getTimeSeconds();
|
||||||
listing.salePrice = 69420420;
|
listing.salePrice = 69420420;
|
||||||
listing.isHq = 1;
|
listing.isHq = 1;
|
||||||
listing.onMannequin = 1;
|
listing.onMannequin = 1;
|
||||||
|
|
||||||
|
|
||||||
strcpy( listing.buyerName, name.c_str() );
|
strcpy( listing.buyerName, name.c_str() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
47
src/world/Manager/NaviMgr.cpp
Normal file
47
src/world/Manager/NaviMgr.cpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#include "NaviMgr.h"
|
||||||
|
#include "Navi/NaviProvider.h"
|
||||||
|
#include <Logging/Logger.h>
|
||||||
|
|
||||||
|
Sapphire::World::Manager::NaviMgr::NaviMgr( FrameworkPtr pFw ) :
|
||||||
|
BaseManager( pFw ),
|
||||||
|
m_pFw( pFw )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::World::Manager::NaviMgr::setupTerritory( const std::string& bgPath )
|
||||||
|
{
|
||||||
|
std::string bg = getBgName( bgPath );
|
||||||
|
|
||||||
|
// check if a provider exists already
|
||||||
|
if( m_naviProviderTerritoryMap.find( bg ) != m_naviProviderTerritoryMap.end() )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto provider = Navi::make_NaviProvider( bg, m_pFw );
|
||||||
|
|
||||||
|
if( provider->init() )
|
||||||
|
{
|
||||||
|
m_naviProviderTerritoryMap.insert( std::make_pair( bg, provider ) );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sapphire::World::Navi::NaviProviderPtr Sapphire::World::Manager::NaviMgr::getNaviProvider( const std::string& bgPath )
|
||||||
|
{
|
||||||
|
std::string bg = getBgName( bgPath );
|
||||||
|
|
||||||
|
if( m_naviProviderTerritoryMap.find( bg ) != m_naviProviderTerritoryMap.end() )
|
||||||
|
return m_naviProviderTerritoryMap[ bg ];
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Sapphire::World::Manager::NaviMgr::getBgName( const std::string& bgPath )
|
||||||
|
{
|
||||||
|
auto findPos = bgPath.find_last_of( "/" );
|
||||||
|
if( findPos != std::string::npos )
|
||||||
|
return bgPath.substr( findPos + 1 );
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
32
src/world/Manager/NaviMgr.h
Normal file
32
src/world/Manager/NaviMgr.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef SAPPHIRE_NAVIMGR_H
|
||||||
|
#define SAPPHIRE_NAVIMGR_H
|
||||||
|
|
||||||
|
#include "ForwardsZone.h"
|
||||||
|
#include "BaseManager.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Sapphire::World::Manager
|
||||||
|
{
|
||||||
|
class NaviMgr : public BaseManager
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
NaviMgr( FrameworkPtr pFw );
|
||||||
|
virtual ~NaviMgr() = default;
|
||||||
|
|
||||||
|
bool setupTerritory( const std::string& bgPath );
|
||||||
|
Navi::NaviProviderPtr getNaviProvider( const std::string& bgPath );
|
||||||
|
|
||||||
|
private:
|
||||||
|
FrameworkPtr m_pFw;
|
||||||
|
|
||||||
|
std::string getBgName( const std::string& bgPath );
|
||||||
|
|
||||||
|
std::unordered_map< std::string, Navi::NaviProviderPtr > m_naviProviderTerritoryMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SAPPHIRE_NAVIMGR_H
|
|
@ -67,6 +67,5 @@ void Sapphire::World::Manager::PlayerMgr::movePlayerToLandDestination( Sapphire:
|
||||||
player.setPos( terriPos->getTargetPosition() );
|
player.setPos( terriPos->getTargetPosition() );
|
||||||
player.setRot( terriPos->getTargetRotation() );
|
player.setRot( terriPos->getTargetRotation() );
|
||||||
|
|
||||||
if( terriMgr->movePlayer( destinationZone, player.getAsPlayer() ) )
|
terriMgr->movePlayer( destinationZone, player.getAsPlayer() );
|
||||||
player.sendZonePackets();
|
|
||||||
}
|
}
|
||||||
|
|
10
src/world/Manager/RNGMgr.cpp
Normal file
10
src/world/Manager/RNGMgr.cpp
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#include "RNGMgr.h"
|
||||||
|
#include <Logging/Logger.h>
|
||||||
|
|
||||||
|
Sapphire::World::Manager::RNGMgr::RNGMgr( FrameworkPtr pFw ) :
|
||||||
|
BaseManager( pFw ),
|
||||||
|
m_engine( *engineSeed< std::mt19937::state_size >() )
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
94
src/world/Manager/RNGMgr.h
Normal file
94
src/world/Manager/RNGMgr.h
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
#ifndef SAPPHIRE_RNGMGR_H
|
||||||
|
#define SAPPHIRE_RNGMGR_H
|
||||||
|
|
||||||
|
#include "Forwards.h"
|
||||||
|
#include "BaseManager.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <random>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace Sapphire::World::Manager
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
* @brief Generator object that is used on multiple state situations
|
||||||
|
*/
|
||||||
|
template< typename T, typename = typename std::enable_if< std::is_arithmetic< T >::value, T >::type >
|
||||||
|
class RandGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RandGenerator( T minRange = std::numeric_limits< T >::min(), T maxRange = std::numeric_limits< T >::max() )
|
||||||
|
: m_engine( *engineSeed< std::mt19937::state_size >() ), m_dist( minRange, maxRange )
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
T next()
|
||||||
|
{
|
||||||
|
return m_dist( m_engine );
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template< std::size_t STATE_SIZE >
|
||||||
|
std::unique_ptr< std::seed_seq > engineSeed()
|
||||||
|
{
|
||||||
|
std::array< uint32_t, STATE_SIZE > seedArray;
|
||||||
|
std::random_device rd;
|
||||||
|
|
||||||
|
std::generate_n( seedArray.data(), seedArray.size(), std::ref( rd ) );
|
||||||
|
auto pSeq = std::make_unique< std::seed_seq >( std::begin( seedArray ), std::end( seedArray ) );
|
||||||
|
|
||||||
|
return pSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uniform_real_distribution< T > m_dist;
|
||||||
|
std::mt19937 m_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RNGMgr : public BaseManager
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
|
||||||
|
RNGMgr( FrameworkPtr pFw );
|
||||||
|
virtual ~RNGMgr() = default;
|
||||||
|
|
||||||
|
RNGMgr( const RNGMgr& pRNGMgr ) = delete;
|
||||||
|
RNGMgr& operator=( const RNGMgr& pRNGMgr ) = delete;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Creates a RNG with specified parameters for multiple uses
|
||||||
|
* @tparam Numeric type to be used for the generator
|
||||||
|
* @param Minimum value possible for the random value
|
||||||
|
* @param Maximum value possible for the random value
|
||||||
|
* @return Random number generator object
|
||||||
|
*/
|
||||||
|
template< typename T, typename = typename std::enable_if< std::is_arithmetic< T >::value, T >::type >
|
||||||
|
RandGenerator< T > getRandGenerator( T minRange, T maxRange )
|
||||||
|
{
|
||||||
|
return RandGenerator< T >( minRange, maxRange );
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
template< std::size_t STATE_SIZE >
|
||||||
|
std::unique_ptr< std::seed_seq > engineSeed()
|
||||||
|
{
|
||||||
|
std::array< uint32_t, STATE_SIZE > seedArray;
|
||||||
|
std::random_device rd;
|
||||||
|
|
||||||
|
std::generate_n( seedArray.data(), seedArray.size(), std::ref( rd ) );
|
||||||
|
auto pSeq = std::make_unique< std::seed_seq >( std::begin( seedArray ), std::end( seedArray ) );
|
||||||
|
|
||||||
|
return pSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mt19937 m_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SAPPHIRE_RNGMGR_H
|
|
@ -2,6 +2,8 @@
|
||||||
#include <Database/DatabaseDef.h>
|
#include <Database/DatabaseDef.h>
|
||||||
#include <Exd/ExdDataGenerated.h>
|
#include <Exd/ExdDataGenerated.h>
|
||||||
|
|
||||||
|
#include "ServerMgr.h"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "Actor/Player.h"
|
#include "Actor/Player.h"
|
||||||
|
@ -16,6 +18,7 @@
|
||||||
#include "Territory/Land.h"
|
#include "Territory/Land.h"
|
||||||
#include "Territory/House.h"
|
#include "Territory/House.h"
|
||||||
#include "Territory/Housing/HousingInteriorTerritory.h"
|
#include "Territory/Housing/HousingInteriorTerritory.h"
|
||||||
|
#include "NaviMgr.h"
|
||||||
|
|
||||||
Sapphire::World::Manager::TerritoryMgr::TerritoryMgr( Sapphire::FrameworkPtr pFw ) :
|
Sapphire::World::Manager::TerritoryMgr::TerritoryMgr( Sapphire::FrameworkPtr pFw ) :
|
||||||
BaseManager( pFw ),
|
BaseManager( pFw ),
|
||||||
|
@ -58,6 +61,10 @@ bool Sapphire::World::Manager::TerritoryMgr::init()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto& cfg = framework()->get< World::ServerMgr >()->getConfig();
|
||||||
|
|
||||||
|
m_inRangeDistance = cfg.network.inRangeDistance;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,12 +170,17 @@ bool Sapphire::World::Manager::TerritoryMgr::createDefaultTerritories()
|
||||||
|
|
||||||
uint32_t guid = getNextInstanceId();
|
uint32_t guid = getNextInstanceId();
|
||||||
|
|
||||||
Logger::info( "{0}\t{1}\t{2}\t{3:<10}\t{4}\t{5}",
|
auto pNaviMgr = framework()->get< Manager::NaviMgr >();
|
||||||
|
std::string bgPath = territoryInfo->bg;
|
||||||
|
bool hasNaviMesh = pNaviMgr->setupTerritory( bgPath );
|
||||||
|
|
||||||
|
Logger::info( "{0}\t{1}\t{2}\t{3:<10}\t{4}\t{5}\t{6}",
|
||||||
territoryTypeId,
|
territoryTypeId,
|
||||||
guid,
|
guid,
|
||||||
territoryInfo->territoryIntendedUse,
|
territoryInfo->territoryIntendedUse,
|
||||||
territoryInfo->name,
|
territoryInfo->name,
|
||||||
( isPrivateTerritory( territoryTypeId ) ? "PRIVATE" : "PUBLIC" ),
|
( isPrivateTerritory( territoryTypeId ) ? "PRIVATE" : "PUBLIC" ),
|
||||||
|
hasNaviMesh ? "NAVI" : "",
|
||||||
pPlaceName->name );
|
pPlaceName->name );
|
||||||
|
|
||||||
auto pZone = make_Zone( territoryTypeId, guid, territoryInfo->name, pPlaceName->name, framework() );
|
auto pZone = make_Zone( territoryTypeId, guid, territoryInfo->name, pPlaceName->name, framework() );
|
||||||
|
@ -194,7 +206,7 @@ bool Sapphire::World::Manager::TerritoryMgr::createHousingTerritories()
|
||||||
auto territoryTypeId = territory.first;
|
auto territoryTypeId = territory.first;
|
||||||
auto territoryInfo = territory.second;
|
auto territoryInfo = territory.second;
|
||||||
uint32_t wardNum;
|
uint32_t wardNum;
|
||||||
uint32_t wardMaxNum = 1;
|
uint32_t wardMaxNum = 18;
|
||||||
|
|
||||||
if( territoryInfo->name.empty() )
|
if( territoryInfo->name.empty() )
|
||||||
continue;
|
continue;
|
||||||
|
@ -208,7 +220,7 @@ bool Sapphire::World::Manager::TerritoryMgr::createHousingTerritories()
|
||||||
{
|
{
|
||||||
uint32_t guid = getNextInstanceId();
|
uint32_t guid = getNextInstanceId();
|
||||||
|
|
||||||
Logger::info( "{0}\t{1}\t{2}\t{3:<10}\tHOUSING\t{4}#{5}",
|
Logger::info( "{0}\t{1}\t{2}\t{3:<10}\tHOUSING\t\t{4}#{5}",
|
||||||
territoryTypeId,
|
territoryTypeId,
|
||||||
guid,
|
guid,
|
||||||
territoryInfo->territoryIntendedUse,
|
territoryInfo->territoryIntendedUse,
|
||||||
|
@ -219,7 +231,6 @@ bool Sapphire::World::Manager::TerritoryMgr::createHousingTerritories()
|
||||||
auto pHousingZone = make_HousingZone( wardNum, territoryTypeId, guid, territoryInfo->name,
|
auto pHousingZone = make_HousingZone( wardNum, territoryTypeId, guid, territoryInfo->name,
|
||||||
pPlaceName->name, framework() );
|
pPlaceName->name, framework() );
|
||||||
pHousingZone->init();
|
pHousingZone->init();
|
||||||
wardMaxNum = 18;
|
|
||||||
|
|
||||||
InstanceIdToZonePtrMap instanceMap;
|
InstanceIdToZonePtrMap instanceMap;
|
||||||
instanceMap[ guid ] = pHousingZone;
|
instanceMap[ guid ] = pHousingZone;
|
||||||
|
@ -535,8 +546,8 @@ bool Sapphire::World::Manager::TerritoryMgr::movePlayer( ZonePtr pZone, Sapphire
|
||||||
// mark character as zoning in progress
|
// mark character as zoning in progress
|
||||||
pPlayer->setLoadingComplete( false );
|
pPlayer->setLoadingComplete( false );
|
||||||
|
|
||||||
//if( pPlayer->getLastPing() != 0 )
|
if( pPlayer->getLastPing() != 0 && pPlayer->getCurrentZone() )
|
||||||
// pPlayer->getCurrentZone()->removeActor( pPlayer );
|
pPlayer->getCurrentZone()->removeActor( pPlayer );
|
||||||
|
|
||||||
pPlayer->setCurrentZone( pZone );
|
pPlayer->setCurrentZone( pZone );
|
||||||
pZone->pushActor( pPlayer );
|
pZone->pushActor( pPlayer );
|
||||||
|
@ -544,6 +555,8 @@ bool Sapphire::World::Manager::TerritoryMgr::movePlayer( ZonePtr pZone, Sapphire
|
||||||
// map player to instanceId so it can be tracked.
|
// map player to instanceId so it can be tracked.
|
||||||
m_playerIdToInstanceMap[ pPlayer->getId() ] = pZone->getGuId();
|
m_playerIdToInstanceMap[ pPlayer->getId() ] = pZone->getGuId();
|
||||||
|
|
||||||
|
pPlayer->sendZonePackets();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,5 +590,9 @@ void Sapphire::World::Manager::TerritoryMgr::disableCurrentFestival()
|
||||||
setCurrentFestival( 0 );
|
setCurrentFestival( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Sapphire::World::Manager::TerritoryMgr::getInRangeDistance() const
|
||||||
|
{
|
||||||
|
return m_inRangeDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -155,6 +155,8 @@ namespace Sapphire::World::Manager
|
||||||
*/
|
*/
|
||||||
const std::pair< uint16_t, uint16_t >& getCurrentFestival() const;
|
const std::pair< uint16_t, uint16_t >& getCurrentFestival() const;
|
||||||
|
|
||||||
|
float getInRangeDistance() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using TerritoryTypeDetailCache = std::unordered_map< uint16_t, Data::TerritoryTypePtr >;
|
using TerritoryTypeDetailCache = std::unordered_map< uint16_t, Data::TerritoryTypePtr >;
|
||||||
using InstanceIdToZonePtrMap = std::unordered_map< uint32_t, ZonePtr >;
|
using InstanceIdToZonePtrMap = std::unordered_map< uint32_t, ZonePtr >;
|
||||||
|
@ -202,6 +204,9 @@ namespace Sapphire::World::Manager
|
||||||
/*! current festival(s) to set for public zones from festival.exd */
|
/*! current festival(s) to set for public zones from festival.exd */
|
||||||
std::pair< uint16_t, uint16_t > m_currentFestival;
|
std::pair< uint16_t, uint16_t > m_currentFestival;
|
||||||
|
|
||||||
|
/*! Max distance at which actors in range of a player are sent */
|
||||||
|
float m_inRangeDistance;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/*! returns a list of instanceContent InstanceIds currently active */
|
/*! returns a list of instanceContent InstanceIds currently active */
|
||||||
InstanceIdList getInstanceContentIdList( uint16_t instanceContentId ) const;
|
InstanceIdList getInstanceContentIdList( uint16_t instanceContentId ) const;
|
||||||
|
|
523
src/world/Navi/NaviProvider.cpp
Normal file
523
src/world/Navi/NaviProvider.cpp
Normal file
|
@ -0,0 +1,523 @@
|
||||||
|
#include <Common.h>
|
||||||
|
#include <Framework.h>
|
||||||
|
#include <Territory/Zone.h>
|
||||||
|
#include <Logging/Logger.h>
|
||||||
|
#include <ServerMgr.h>
|
||||||
|
|
||||||
|
#include <Manager/RNGMgr.h>
|
||||||
|
|
||||||
|
#include "NaviProvider.h"
|
||||||
|
|
||||||
|
#include <recastnavigation/Detour/Include/DetourNavMesh.h>
|
||||||
|
#include <recastnavigation/Detour/Include/DetourNavMeshQuery.h>
|
||||||
|
#include <DetourCommon.h>
|
||||||
|
#include <recastnavigation/Recast/Include/Recast.h>
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
|
||||||
|
Sapphire::World::Navi::NaviProvider::NaviProvider( const std::string& internalName, FrameworkPtr pFw ) :
|
||||||
|
m_naviMesh( nullptr ),
|
||||||
|
m_naviMeshQuery( nullptr ),
|
||||||
|
m_internalName( internalName ),
|
||||||
|
m_pFw( pFw )
|
||||||
|
{
|
||||||
|
// Set defaults
|
||||||
|
m_polyFindRange[ 0 ] = 10;
|
||||||
|
m_polyFindRange[ 1 ] = 20;
|
||||||
|
m_polyFindRange[ 2 ] = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::World::Navi::NaviProvider::init()
|
||||||
|
{
|
||||||
|
auto& cfg = m_pFw->get< Sapphire::World::ServerMgr >()->getConfig();
|
||||||
|
|
||||||
|
auto meshesFolder = std::experimental::filesystem::path( cfg.navigation.meshPath );
|
||||||
|
auto meshFolder = meshesFolder / std::experimental::filesystem::path( m_internalName );
|
||||||
|
|
||||||
|
if( std::experimental::filesystem::exists( meshFolder ) )
|
||||||
|
{
|
||||||
|
auto baseMesh = meshFolder / std::experimental::filesystem::path( m_internalName + ".nav" );
|
||||||
|
|
||||||
|
if( !loadMesh( baseMesh.string() ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
initQuery();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::World::Navi::NaviProvider::hasNaviMesh() const
|
||||||
|
{
|
||||||
|
return m_naviMesh != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::World::Navi::NaviProvider::initQuery()
|
||||||
|
{
|
||||||
|
if( m_naviMeshQuery )
|
||||||
|
dtFreeNavMeshQuery( m_naviMeshQuery );
|
||||||
|
|
||||||
|
m_naviMeshQuery = dtAllocNavMeshQuery();
|
||||||
|
m_naviMeshQuery->init( m_naviMesh, 2048 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Sapphire::World::Navi::NaviProvider::fixupCorridor( dtPolyRef* path, const int32_t npath, const int32_t maxPath,
|
||||||
|
const dtPolyRef* visited, const int32_t nvisited )
|
||||||
|
{
|
||||||
|
int32_t furthestPath = -1;
|
||||||
|
int32_t furthestVisited = -1;
|
||||||
|
|
||||||
|
// Find furthest common polygon.
|
||||||
|
for( int32_t i = npath - 1; i >= 0; --i )
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
for( int32_t j = nvisited - 1; j >= 0; --j )
|
||||||
|
{
|
||||||
|
if( path[ i ] == visited[ j ] )
|
||||||
|
{
|
||||||
|
furthestPath = i;
|
||||||
|
furthestVisited = j;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( found )
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no intersection found just return current path.
|
||||||
|
if( furthestPath == -1 || furthestVisited == -1 )
|
||||||
|
return npath;
|
||||||
|
|
||||||
|
// Concatenate paths.
|
||||||
|
|
||||||
|
// Adjust beginning of the buffer to include the visited.
|
||||||
|
const int32_t req = nvisited - furthestVisited;
|
||||||
|
const int32_t orig = rcMin( furthestPath + 1, npath );
|
||||||
|
int32_t size = rcMax( 0, npath - orig );
|
||||||
|
if( req + size > maxPath )
|
||||||
|
size = maxPath - req;
|
||||||
|
if( size )
|
||||||
|
memmove( path + req, path + orig, size * sizeof( dtPolyRef ) );
|
||||||
|
|
||||||
|
// Store visited
|
||||||
|
for( int32_t i = 0; i < req; ++i )
|
||||||
|
path[i] = visited[( nvisited - 1 ) - i];
|
||||||
|
|
||||||
|
return req + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Sapphire::World::Navi::NaviProvider::fixupShortcuts( dtPolyRef* path, int32_t npath, dtNavMeshQuery* navQuery )
|
||||||
|
{
|
||||||
|
if( npath < 3 )
|
||||||
|
return npath;
|
||||||
|
|
||||||
|
// Get connected polygons
|
||||||
|
const int32_t maxNeis = 16;
|
||||||
|
dtPolyRef neis[ maxNeis ];
|
||||||
|
int32_t nneis = 0;
|
||||||
|
|
||||||
|
const dtMeshTile* tile = 0;
|
||||||
|
const dtPoly* poly = 0;
|
||||||
|
if( dtStatusFailed( navQuery->getAttachedNavMesh()->getTileAndPolyByRef( path[ 0 ], &tile, &poly ) ) )
|
||||||
|
return npath;
|
||||||
|
|
||||||
|
for( uint32_t k = poly->firstLink; k != DT_NULL_LINK; k = tile->links[ k ].next )
|
||||||
|
{
|
||||||
|
const dtLink* link = &tile->links[ k ];
|
||||||
|
if( link->ref != 0 )
|
||||||
|
{
|
||||||
|
if( nneis < maxNeis )
|
||||||
|
neis[ nneis++ ] = link->ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the neighbour polygons is within the next few polygons
|
||||||
|
// in the path, short cut to that polygon directly.
|
||||||
|
const int32_t maxLookAhead = 6;
|
||||||
|
int32_t cut = 0;
|
||||||
|
for( int32_t i = dtMin( maxLookAhead, npath ) - 1; i > 1 && cut == 0; i-- )
|
||||||
|
{
|
||||||
|
for( int32_t j = 0; j < nneis; j++ )
|
||||||
|
{
|
||||||
|
if( path[ i ] == neis[ j ] )
|
||||||
|
{
|
||||||
|
cut = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( cut > 1 )
|
||||||
|
{
|
||||||
|
int32_t offset = cut - 1;
|
||||||
|
npath -= offset;
|
||||||
|
for( int32_t i = 1; i < npath; i++ )
|
||||||
|
path[ i ] = path[ i + offset ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return npath;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::World::Navi::NaviProvider::inRange( const float* v1, const float* v2, const float r, const float h )
|
||||||
|
{
|
||||||
|
const float dx = v2[ 0 ] - v1[ 0 ];
|
||||||
|
const float dy = v2[ 1 ] - v1[ 1 ];
|
||||||
|
const float dz = v2[ 2 ] - v1[ 2 ];
|
||||||
|
return ( dx * dx + dz * dz ) < r * r && fabsf( dy ) < h;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::World::Navi::NaviProvider::getSteerTarget( dtNavMeshQuery* navQuery, const float* startPos, const float* endPos,
|
||||||
|
const float minTargetDist, const dtPolyRef* path, const int32_t pathSize,
|
||||||
|
float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef,
|
||||||
|
float* outPoints, int32_t* outPointCount )
|
||||||
|
{
|
||||||
|
// Find steer target.
|
||||||
|
const int32_t MAX_STEER_POINTS = 3;
|
||||||
|
float steerPath[ MAX_STEER_POINTS * 3 ];
|
||||||
|
uint8_t steerPathFlags[ MAX_STEER_POINTS ];
|
||||||
|
dtPolyRef steerPathPolys[ MAX_STEER_POINTS ];
|
||||||
|
int32_t nsteerPath = 0;
|
||||||
|
navQuery->findStraightPath( startPos, endPos, path, pathSize,
|
||||||
|
steerPath, steerPathFlags, steerPathPolys, &nsteerPath, MAX_STEER_POINTS );
|
||||||
|
if( !nsteerPath )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if( outPoints && outPointCount )
|
||||||
|
{
|
||||||
|
*outPointCount = nsteerPath;
|
||||||
|
for( int32_t i = 0; i < nsteerPath; ++i )
|
||||||
|
dtVcopy( &outPoints[ i * 3 ], &steerPath[ i * 3 ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find vertex far enough to steer to.
|
||||||
|
int32_t ns = 0;
|
||||||
|
while( ns < nsteerPath )
|
||||||
|
{
|
||||||
|
// Stop at Off-Mesh link or when point is further than slop away.
|
||||||
|
if( ( steerPathFlags[ ns ] & DT_STRAIGHTPATH_OFFMESH_CONNECTION ) ||
|
||||||
|
!inRange( &steerPath[ ns * 3 ], startPos, minTargetDist, 1000.0f ) )
|
||||||
|
break;
|
||||||
|
ns++;
|
||||||
|
}
|
||||||
|
// Failed to find good point to steer to.
|
||||||
|
if( ns >= nsteerPath )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
dtVcopy( steerPos, &steerPath[ ns * 3 ] );
|
||||||
|
steerPos[ 1 ] = startPos[ 1 ];
|
||||||
|
steerPosFlag = steerPathFlags[ ns ];
|
||||||
|
steerPosRef = steerPathPolys[ ns ];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float frand()
|
||||||
|
{
|
||||||
|
return ( float ) rand() / (float)RAND_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Sapphire::Common::FFXIVARR_POSITION3
|
||||||
|
Sapphire::World::Navi::NaviProvider::findRandomPositionInCircle( const Sapphire::Common::FFXIVARR_POSITION3& startPos,
|
||||||
|
float maxRadius )
|
||||||
|
{
|
||||||
|
dtStatus status;
|
||||||
|
|
||||||
|
float spos[ 3 ] = { startPos.x, startPos.y, startPos.z };
|
||||||
|
|
||||||
|
float polyPickExt[ 3 ];
|
||||||
|
polyPickExt[ 0 ] = 30;
|
||||||
|
polyPickExt[ 1 ] = 60;
|
||||||
|
polyPickExt[ 2 ] = 30;
|
||||||
|
|
||||||
|
float randomPt[ 3 ];
|
||||||
|
float snearest[ 3 ];
|
||||||
|
|
||||||
|
dtQueryFilter filter;
|
||||||
|
filter.setIncludeFlags( 0xffff );
|
||||||
|
filter.setExcludeFlags( 0 );
|
||||||
|
|
||||||
|
dtPolyRef startRef;
|
||||||
|
dtPolyRef randomRef;
|
||||||
|
|
||||||
|
status = m_naviMeshQuery->findNearestPoly( spos, polyPickExt, &filter, &startRef, snearest );
|
||||||
|
|
||||||
|
if( dtStatusFailed( status ) )
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !m_naviMesh->isValidPolyRef( startRef ) )
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
|
||||||
|
auto rng = pRNGMgr->getRandGenerator< float >( 0.f, 1.f );
|
||||||
|
status = m_naviMeshQuery->findRandomPointAroundCircle( startRef, spos, maxRadius, &filter, frand,
|
||||||
|
&randomRef, randomPt );
|
||||||
|
|
||||||
|
if( dtStatusFailed( status ) )
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { randomPt[ 0 ], randomPt[ 1 ], randomPt[ 2 ] };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector< Sapphire::Common::FFXIVARR_POSITION3 >
|
||||||
|
Sapphire::World::Navi::NaviProvider::findFollowPath( const Common::FFXIVARR_POSITION3& startPos,
|
||||||
|
const Common::FFXIVARR_POSITION3& endPos )
|
||||||
|
{
|
||||||
|
if( !m_naviMesh || !m_naviMeshQuery )
|
||||||
|
throw std::runtime_error( "No navimesh loaded" );
|
||||||
|
|
||||||
|
auto resultCoords = std::vector< Common::FFXIVARR_POSITION3 >();
|
||||||
|
|
||||||
|
dtPolyRef startRef, endRef = 0;
|
||||||
|
|
||||||
|
float spos[ 3 ] = { startPos.x, startPos.y, startPos.z };
|
||||||
|
float epos[ 3 ] = { endPos.x, endPos.y, endPos.z };
|
||||||
|
|
||||||
|
dtQueryFilter filter;
|
||||||
|
filter.setIncludeFlags( 0xffff );
|
||||||
|
filter.setExcludeFlags( 0 );
|
||||||
|
|
||||||
|
m_naviMeshQuery->findNearestPoly( spos, m_polyFindRange, &filter, &startRef, 0 );
|
||||||
|
m_naviMeshQuery->findNearestPoly( epos, m_polyFindRange, &filter, &endRef, 0 );
|
||||||
|
|
||||||
|
// Couldn't find any close polys to navigate from
|
||||||
|
if( !startRef || !endRef )
|
||||||
|
return resultCoords;
|
||||||
|
|
||||||
|
dtPolyRef polys[ MAX_POLYS ];
|
||||||
|
int32_t numPolys = 0;
|
||||||
|
|
||||||
|
m_naviMeshQuery->findPath( startRef, endRef, spos, epos, &filter, polys, &numPolys, MAX_POLYS );
|
||||||
|
|
||||||
|
// Check if we got polys back for navigation
|
||||||
|
if( numPolys )
|
||||||
|
{
|
||||||
|
// Iterate over the path to find smooth path on the detail mesh surface.
|
||||||
|
memcpy( polys, polys, sizeof( dtPolyRef )*numPolys );
|
||||||
|
int32_t npolys = numPolys;
|
||||||
|
|
||||||
|
float iterPos[3], targetPos[3];
|
||||||
|
m_naviMeshQuery->closestPointOnPoly( startRef, spos, iterPos, 0 );
|
||||||
|
m_naviMeshQuery->closestPointOnPoly( polys[ npolys - 1 ], epos, targetPos, 0 );
|
||||||
|
|
||||||
|
//Logger::debug( "IterPos: {0} {1} {2}; TargetPos: {3} {4} {5}",
|
||||||
|
// iterPos[ 0 ], iterPos[ 1 ], iterPos[ 2 ],
|
||||||
|
// targetPos[ 0 ], targetPos[ 1 ], targetPos[ 2 ] );
|
||||||
|
|
||||||
|
const float STEP_SIZE = 1.2f;
|
||||||
|
const float SLOP = 0.15f;
|
||||||
|
|
||||||
|
int32_t numSmoothPath = 0;
|
||||||
|
float smoothPath[ MAX_SMOOTH * 3 ];
|
||||||
|
|
||||||
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], iterPos );
|
||||||
|
numSmoothPath++;
|
||||||
|
|
||||||
|
// Move towards target a small advancement at a time until target reached or
|
||||||
|
// when ran out of memory to store the path.
|
||||||
|
while( npolys && numSmoothPath < MAX_SMOOTH )
|
||||||
|
{
|
||||||
|
// Find location to steer towards.
|
||||||
|
float steerPos[ 3 ];
|
||||||
|
uint8_t steerPosFlag;
|
||||||
|
dtPolyRef steerPosRef;
|
||||||
|
|
||||||
|
if( !getSteerTarget( m_naviMeshQuery, iterPos, targetPos, SLOP,
|
||||||
|
polys, npolys, steerPos, steerPosFlag, steerPosRef ) )
|
||||||
|
break;
|
||||||
|
|
||||||
|
bool endOfPath = ( steerPosFlag & DT_STRAIGHTPATH_END ) ? true : false;
|
||||||
|
bool offMeshConnection = ( steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION ) ? true : false;
|
||||||
|
|
||||||
|
// Find movement delta.
|
||||||
|
float delta[ 3 ], len;
|
||||||
|
dtVsub( delta, steerPos, iterPos );
|
||||||
|
len = dtMathSqrtf( dtVdot( delta, delta ) );
|
||||||
|
// If the steer target is end of path or off-mesh link, do not move past the location.
|
||||||
|
if( ( endOfPath || offMeshConnection ) && len < STEP_SIZE )
|
||||||
|
len = 1;
|
||||||
|
else
|
||||||
|
len = STEP_SIZE / len;
|
||||||
|
float moveTgt[ 3 ];
|
||||||
|
dtVmad( moveTgt, iterPos, delta, len );
|
||||||
|
|
||||||
|
// Move
|
||||||
|
float result[ 3 ];
|
||||||
|
dtPolyRef visited[ 16 ];
|
||||||
|
int32_t nvisited = 0;
|
||||||
|
m_naviMeshQuery->moveAlongSurface( polys[ 0 ], iterPos, moveTgt, &filter,
|
||||||
|
result, visited, &nvisited, 16 );
|
||||||
|
|
||||||
|
npolys = fixupCorridor( polys, npolys, MAX_POLYS, visited, nvisited );
|
||||||
|
npolys = fixupShortcuts( polys, npolys, m_naviMeshQuery );
|
||||||
|
|
||||||
|
float h = 0;
|
||||||
|
m_naviMeshQuery->getPolyHeight( polys[0], result, &h );
|
||||||
|
result[ 1 ] = h;
|
||||||
|
dtVcopy( iterPos, result );
|
||||||
|
|
||||||
|
// Handle end of path and off-mesh links when close enough.
|
||||||
|
if( endOfPath && inRange( iterPos, steerPos, SLOP, 1.0f ) )
|
||||||
|
{
|
||||||
|
// Reached end of path.
|
||||||
|
dtVcopy( iterPos, targetPos );
|
||||||
|
if( numSmoothPath < MAX_SMOOTH )
|
||||||
|
{
|
||||||
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], iterPos );
|
||||||
|
numSmoothPath++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if( offMeshConnection && inRange( iterPos, steerPos, SLOP, 1.0f ) )
|
||||||
|
{
|
||||||
|
// Reached off-mesh connection.
|
||||||
|
float startPos[ 3 ], endPos[ 3 ];
|
||||||
|
|
||||||
|
// Advance the path up to and over the off-mesh connection.
|
||||||
|
dtPolyRef prevRef = 0, polyRef = polys[ 0 ];
|
||||||
|
int32_t npos = 0;
|
||||||
|
while( npos < npolys && polyRef != steerPosRef )
|
||||||
|
{
|
||||||
|
prevRef = polyRef;
|
||||||
|
polyRef = polys[ npos ];
|
||||||
|
npos++;
|
||||||
|
}
|
||||||
|
for( int32_t i = npos; i < npolys; ++i )
|
||||||
|
polys[ i - npos ] = polys[ i ];
|
||||||
|
npolys -= npos;
|
||||||
|
|
||||||
|
// Handle the connection.
|
||||||
|
dtStatus status = m_naviMesh->getOffMeshConnectionPolyEndPoints( prevRef, polyRef, startPos, endPos );
|
||||||
|
if( dtStatusSucceed( status ) )
|
||||||
|
{
|
||||||
|
if( numSmoothPath < MAX_SMOOTH )
|
||||||
|
{
|
||||||
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], startPos );
|
||||||
|
numSmoothPath++;
|
||||||
|
// Hack to make the dotted path not visible during off-mesh connection.
|
||||||
|
if( numSmoothPath & 1 )
|
||||||
|
{
|
||||||
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], startPos );
|
||||||
|
numSmoothPath++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Move position at the other side of the off-mesh link.
|
||||||
|
dtVcopy( iterPos, endPos );
|
||||||
|
float eh = 0.0f;
|
||||||
|
m_naviMeshQuery->getPolyHeight( polys[ 0 ], iterPos, &eh );
|
||||||
|
iterPos[ 1 ] = eh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store results.
|
||||||
|
if( numSmoothPath < MAX_SMOOTH )
|
||||||
|
{
|
||||||
|
dtVcopy( &smoothPath[ numSmoothPath * 3 ], iterPos );
|
||||||
|
numSmoothPath++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for( int32_t i = 0; i < numSmoothPath; i += 3 )
|
||||||
|
{
|
||||||
|
resultCoords.emplace_back( Common::FFXIVARR_POSITION3{ smoothPath[ i ], smoothPath[ i + 1 ], smoothPath[ i + 2 ] } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::World::Navi::NaviProvider::loadMesh( const std::string& path )
|
||||||
|
{
|
||||||
|
FILE* fp = fopen( path.c_str(), "rb" );
|
||||||
|
if( !fp )
|
||||||
|
{
|
||||||
|
Logger::error( "Couldn't open navimesh file: {0}", path );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header.
|
||||||
|
NavMeshSetHeader header;
|
||||||
|
|
||||||
|
size_t readLen = fread( &header, sizeof( NavMeshSetHeader ), 1, fp );
|
||||||
|
if( readLen != 1 )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
Logger::error( "Couldn't read NavMeshSetHeader for {0}", path );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( header.magic != NAVMESHSET_MAGIC )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
Logger::error( "'{0}' has an incorrect NavMeshSet header.", path );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( header.version != NAVMESHSET_VERSION )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
Logger::error( "'{0}' has an incorrect NavMeshSet version. Expected '{1}', got '{2}'", path, NAVMESHSET_VERSION, header.version );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !m_naviMesh )
|
||||||
|
{
|
||||||
|
m_naviMesh = dtAllocNavMesh();
|
||||||
|
if( !m_naviMesh )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
Logger::error( "Couldn't allocate dtNavMesh" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dtStatus status = m_naviMesh->init( &header.params );
|
||||||
|
if( dtStatusFailed( status ) )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
Logger::error( "Couldn't initialise dtNavMesh" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read tiles.
|
||||||
|
for( int32_t i = 0; i < header.numTiles; ++i )
|
||||||
|
{
|
||||||
|
NavMeshTileHeader tileHeader;
|
||||||
|
readLen = fread( &tileHeader, sizeof( tileHeader ), 1, fp );
|
||||||
|
if( readLen != 1 )
|
||||||
|
{
|
||||||
|
fclose( fp );
|
||||||
|
Logger::error( "Couldn't read NavMeshTileHeader from '{0}'", path );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !tileHeader.tileRef || !tileHeader.dataSize )
|
||||||
|
break;
|
||||||
|
|
||||||
|
auto data = reinterpret_cast< uint8_t* >( dtAlloc( tileHeader.dataSize, DT_ALLOC_PERM ) );
|
||||||
|
if( !data )
|
||||||
|
break;
|
||||||
|
memset( data, 0, tileHeader.dataSize );
|
||||||
|
readLen = fread( data, tileHeader.dataSize, 1, fp );
|
||||||
|
if( readLen != 1 )
|
||||||
|
{
|
||||||
|
dtFree( data );
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
Logger::error( "Couldn't read tile data from '{0}'", path );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_naviMesh->addTile( data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
71
src/world/Navi/NaviProvider.h
Normal file
71
src/world/Navi/NaviProvider.h
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#ifndef _NAVIPROVIDER_H_
|
||||||
|
#define _NAVIPROVIDER_H_
|
||||||
|
|
||||||
|
#include <Common.h>
|
||||||
|
#include "ForwardsZone.h"
|
||||||
|
#include <recastnavigation/Detour/Include/DetourNavMesh.h>
|
||||||
|
#include <recastnavigation/Detour/Include/DetourNavMeshQuery.h>
|
||||||
|
|
||||||
|
namespace Sapphire::World::Navi
|
||||||
|
{
|
||||||
|
const int32_t MAX_POLYS = 32;
|
||||||
|
const int32_t MAX_SMOOTH = 2048;
|
||||||
|
|
||||||
|
const int32_t NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'
|
||||||
|
const int32_t NAVMESHSET_VERSION = 1;
|
||||||
|
|
||||||
|
class NaviProvider
|
||||||
|
{
|
||||||
|
struct NavMeshSetHeader
|
||||||
|
{
|
||||||
|
int32_t magic;
|
||||||
|
int32_t version;
|
||||||
|
int32_t numTiles;
|
||||||
|
dtNavMeshParams params;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NavMeshTileHeader
|
||||||
|
{
|
||||||
|
dtTileRef tileRef;
|
||||||
|
int32_t dataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NaviProvider( const std::string& internalName, FrameworkPtr pFw );
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
bool loadMesh( const std::string& path );
|
||||||
|
void initQuery();
|
||||||
|
|
||||||
|
void toDetourPos( const Common::FFXIVARR_POSITION3& position, float* out );
|
||||||
|
Common::FFXIVARR_POSITION3 toGamePos( float* pos );
|
||||||
|
|
||||||
|
std::vector< Common::FFXIVARR_POSITION3 > findFollowPath( const Common::FFXIVARR_POSITION3& startPos,
|
||||||
|
const Common::FFXIVARR_POSITION3& endPos );
|
||||||
|
Common::FFXIVARR_POSITION3 findRandomPositionInCircle( const Sapphire::Common::FFXIVARR_POSITION3& startPos,
|
||||||
|
float maxRadius );
|
||||||
|
|
||||||
|
bool hasNaviMesh() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string m_internalName;
|
||||||
|
|
||||||
|
dtNavMesh* m_naviMesh;
|
||||||
|
dtNavMeshQuery* m_naviMeshQuery;
|
||||||
|
|
||||||
|
float m_polyFindRange[ 3 ];
|
||||||
|
|
||||||
|
private:
|
||||||
|
int32_t fixupCorridor( dtPolyRef* path, int32_t npath, int32_t maxPath, const dtPolyRef* visited, int32_t nvisited );
|
||||||
|
int32_t fixupShortcuts( dtPolyRef* path, int32_t npath, dtNavMeshQuery* navQuery );
|
||||||
|
inline bool inRange( const float* v1, const float* v2, const float r, const float h );
|
||||||
|
bool getSteerTarget( dtNavMeshQuery* navQuery, const float* startPos, const float* endPos, const float minTargetDist,
|
||||||
|
const dtPolyRef* path, const int32_t pathSize, float* steerPos, uint8_t& steerPosFlag,
|
||||||
|
dtPolyRef& steerPosRef, float* outPoints = 0, int32_t* outPointCount = 0 );
|
||||||
|
|
||||||
|
FrameworkPtr m_pFw;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -315,7 +315,7 @@ void Sapphire::Network::GameConnection::processOutQueue()
|
||||||
totalSize += pPacket->getSize();
|
totalSize += pPacket->getSize();
|
||||||
|
|
||||||
// todo: figure out a good max set size and make it configurable
|
// todo: figure out a good max set size and make it configurable
|
||||||
if( totalSize > 15000 )
|
if( totalSize > 10000 )
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +385,7 @@ void Sapphire::Network::GameConnection::handlePackets( const Sapphire::Network::
|
||||||
const std::vector< Sapphire::Network::Packets::FFXIVARR_PACKET_RAW >& packetData )
|
const std::vector< Sapphire::Network::Packets::FFXIVARR_PACKET_RAW >& packetData )
|
||||||
{
|
{
|
||||||
auto pServerZone = m_pFw->get< World::ServerMgr >();
|
auto pServerZone = m_pFw->get< World::ServerMgr >();
|
||||||
|
|
||||||
// if a session is set, update the last time it recieved a game packet
|
// if a session is set, update the last time it recieved a game packet
|
||||||
if( m_pSession )
|
if( m_pSession )
|
||||||
m_pSession->updateLastDataTime();
|
m_pSession->updateLastDataTime();
|
||||||
|
@ -427,7 +428,7 @@ void Sapphire::Network::GameConnection::handlePackets( const Sapphire::Network::
|
||||||
|
|
||||||
auto pe = std::make_shared< FFXIVRawPacket >( 0x07, 0x18, 0, 0 );
|
auto pe = std::make_shared< FFXIVRawPacket >( 0x07, 0x18, 0, 0 );
|
||||||
*( unsigned int* ) ( &pe->data()[ 0 ] ) = 0xE0037603;
|
*( unsigned int* ) ( &pe->data()[ 0 ] ) = 0xE0037603;
|
||||||
*( unsigned int* ) ( &pe->data()[ 4 ] ) = static_cast< uint32_t >( time( nullptr ) );
|
*( unsigned int* ) ( &pe->data()[ 4 ] ) = Sapphire::Util::getTimeSeconds();
|
||||||
sendSinglePacket( pe );
|
sendSinglePacket( pe );
|
||||||
|
|
||||||
// main connection, assinging it to the session
|
// main connection, assinging it to the session
|
||||||
|
|
|
@ -294,7 +294,7 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( FrameworkPtr pFw,
|
||||||
}
|
}
|
||||||
case ClientTriggerType::RequestInstanceLeave:
|
case ClientTriggerType::RequestInstanceLeave:
|
||||||
{
|
{
|
||||||
// todo: apply cf penalty if applicable, make sure player isnt in combat
|
// todo: apply cf penalty if applicable, make sure player isn't in combat
|
||||||
player.exitInstance();
|
player.exitInstance();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ void Sapphire::Network::GameConnection::eventHandlerTalk( FrameworkPtr pFw,
|
||||||
std::string eventName = "onTalk";
|
std::string eventName = "onTalk";
|
||||||
std::string objName = pEventMgr->getEventName( eventId );
|
std::string objName = pEventMgr->getEventName( eventId );
|
||||||
|
|
||||||
player.sendDebug( "Chara: {0} -> {1} \neventId: {2} ({3:08X}",
|
player.sendDebug( "Chara: {0} -> {1} \neventId: {2} ({3:08X})",
|
||||||
actorId, pEventMgr->mapEventActorToRealActor( static_cast< uint32_t >( actorId ) ),
|
actorId, pEventMgr->mapEventActorToRealActor( static_cast< uint32_t >( actorId ) ),
|
||||||
eventId, eventId );
|
eventId, eventId );
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ void Sapphire::Network::GameConnection::eventHandlerEmote( FrameworkPtr pFw,
|
||||||
std::string eventName = "onEmote";
|
std::string eventName = "onEmote";
|
||||||
std::string objName = pEventMgr->getEventName( eventId );
|
std::string objName = pEventMgr->getEventName( eventId );
|
||||||
|
|
||||||
player.sendDebug( "Chara: {0} -> {1} \neventId: {2} ({3:08X}",
|
player.sendDebug( "Chara: {0} -> {1} \neventId: {2} ({3:08X})",
|
||||||
actorId, pEventMgr->mapEventActorToRealActor( static_cast< uint32_t >( actorId ) ),
|
actorId, pEventMgr->mapEventActorToRealActor( static_cast< uint32_t >( actorId ) ),
|
||||||
eventId, eventId );
|
eventId, eventId );
|
||||||
|
|
||||||
|
@ -269,7 +269,7 @@ void Sapphire::Network::GameConnection::eventHandlerShop( FrameworkPtr pFw,
|
||||||
std::string eventName = "onOpen";
|
std::string eventName = "onOpen";
|
||||||
std::string objName = pEventMgr->getEventName( eventId );
|
std::string objName = pEventMgr->getEventName( eventId );
|
||||||
|
|
||||||
player.sendDebug( "EventId: {0} ({0:08X}", eventId );
|
player.sendDebug( "EventId: {0} ({0:08X})", eventId );
|
||||||
|
|
||||||
player.sendDebug( "Calling: {0}.{1}", objName, eventName );
|
player.sendDebug( "Calling: {0}.{1}", objName, eventName );
|
||||||
player.eventStart( player.getId(), eventId, Event::EventHandler::UI, 0, packet.data().param );
|
player.eventStart( player.getId(), eventId, Event::EventHandler::UI, 0, packet.data().param );
|
||||||
|
|
|
@ -275,8 +275,13 @@ void Sapphire::Network::GameConnection::gm1Handler( FrameworkPtr pFw,
|
||||||
}
|
}
|
||||||
case GmCommand::Hp:
|
case GmCommand::Hp:
|
||||||
{
|
{
|
||||||
targetPlayer->setHp( param1 );
|
auto chara = targetActor->getAsChara();
|
||||||
player.sendNotice( "Hp for {0} was set to {1}", targetPlayer->getName(), param1 );
|
if( chara )
|
||||||
|
{
|
||||||
|
chara->setHp( param1 );
|
||||||
|
player.sendNotice( "Hp for {0} was set to {1}", chara->getName(), param1 );
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GmCommand::Mp:
|
case GmCommand::Mp:
|
||||||
|
@ -394,13 +399,38 @@ void Sapphire::Network::GameConnection::gm1Handler( FrameworkPtr pFw,
|
||||||
}
|
}
|
||||||
case GmCommand::GC:
|
case GmCommand::GC:
|
||||||
{
|
{
|
||||||
|
if( param1 > 3 )
|
||||||
|
{
|
||||||
|
player.sendUrgent( "Invalid Grand Company ID: {0}", param1 );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
targetPlayer->setGc( param1 );
|
targetPlayer->setGc( param1 );
|
||||||
|
|
||||||
|
// if we're changing them to a GC, check if they have a rank and if not, set it to the lowest rank
|
||||||
|
if( param1 > 0 )
|
||||||
|
{
|
||||||
|
auto gcRankIdx = static_cast< uint8_t >( param1 ) - 1;
|
||||||
|
if( targetPlayer->getGcRankArray()[ gcRankIdx ] == 0 )
|
||||||
|
{
|
||||||
|
player.setGcRankAt( gcRankIdx, 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
player.sendNotice( "GC for {0} was set to {1}", targetPlayer->getName(), targetPlayer->getGc() );
|
player.sendNotice( "GC for {0} was set to {1}", targetPlayer->getName(), targetPlayer->getGc() );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GmCommand::GCRank:
|
case GmCommand::GCRank:
|
||||||
{
|
{
|
||||||
targetPlayer->setGcRankAt( targetPlayer->getGc() - 1, param1 );
|
auto gcId = targetPlayer->getGc() - 1;
|
||||||
|
|
||||||
|
if( gcId > 2 )
|
||||||
|
{
|
||||||
|
player.sendUrgent( "{0} has an invalid Grand Company ID: {0}", targetPlayer->getName(), gcId );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPlayer->setGcRankAt( gcId, param1 );
|
||||||
player.sendNotice( "GC Rank for {0} for GC {1} was set to {2}", targetPlayer->getName(), targetPlayer->getGc(),
|
player.sendNotice( "GC Rank for {0} for GC {1} was set to {2}", targetPlayer->getName(), targetPlayer->getGc(),
|
||||||
targetPlayer->getGcRankArray()[ targetPlayer->getGc() - 1 ] );
|
targetPlayer->getGcRankArray()[ targetPlayer->getGc() - 1 ] );
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -14,10 +14,8 @@
|
||||||
|
|
||||||
#include "Network/GameConnection.h"
|
#include "Network/GameConnection.h"
|
||||||
|
|
||||||
#include "Manager/TerritoryMgr.h"
|
|
||||||
#include "Territory/Zone.h"
|
#include "Territory/Zone.h"
|
||||||
#include "Territory/HousingZone.h"
|
#include "Territory/HousingZone.h"
|
||||||
#include "Manager/HousingMgr.h"
|
|
||||||
#include "Territory/Land.h"
|
#include "Territory/Land.h"
|
||||||
#include "Territory/ZonePosition.h"
|
#include "Territory/ZonePosition.h"
|
||||||
#include "Territory/House.h"
|
#include "Territory/House.h"
|
||||||
|
@ -37,6 +35,9 @@
|
||||||
#include "Manager/DebugCommandMgr.h"
|
#include "Manager/DebugCommandMgr.h"
|
||||||
#include "Manager/EventMgr.h"
|
#include "Manager/EventMgr.h"
|
||||||
#include "Manager/MarketMgr.h"
|
#include "Manager/MarketMgr.h"
|
||||||
|
#include "Manager/TerritoryMgr.h"
|
||||||
|
#include "Manager/HousingMgr.h"
|
||||||
|
#include "Manager/RNGMgr.h"
|
||||||
|
|
||||||
#include "Action/Action.h"
|
#include "Action/Action.h"
|
||||||
#include "Action/ActionTeleport.h"
|
#include "Action/ActionTeleport.h"
|
||||||
|
@ -440,7 +441,7 @@ void Sapphire::Network::GameConnection::pingHandler( FrameworkPtr pFw,
|
||||||
|
|
||||||
queueOutPacket( std::make_shared< Server::PingPacket >( player, packet.data().timestamp ) );
|
queueOutPacket( std::make_shared< Server::PingPacket >( player, packet.data().timestamp ) );
|
||||||
|
|
||||||
player.setLastPing( static_cast< uint32_t >( time( nullptr ) ) );
|
player.setLastPing( Sapphire::Util::getTimeSeconds() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,25 +17,28 @@ namespace Sapphire::Network::Packets::Server
|
||||||
EffectPacket( uint64_t sourceId, uint32_t targetId, uint32_t actionId ) :
|
EffectPacket( uint64_t sourceId, uint32_t targetId, uint32_t actionId ) :
|
||||||
ZoneChannelPacket< FFXIVIpcEffect >( static_cast< uint32_t >( sourceId ), targetId )
|
ZoneChannelPacket< FFXIVIpcEffect >( static_cast< uint32_t >( sourceId ), targetId )
|
||||||
{
|
{
|
||||||
m_data.header.actionId = actionId;
|
m_data.effectCount = 0;
|
||||||
m_data.header.actionAnimationId = static_cast< uint16_t >( actionId );
|
m_data.actionId = actionId;
|
||||||
|
m_data.actionAnimationId = static_cast< uint16_t >( actionId );
|
||||||
|
|
||||||
m_data.header.animationTargetId = targetId;
|
m_data.animationTargetId = targetId;
|
||||||
m_data.effectTargetId = targetId;
|
m_data.effectTargetId = targetId;
|
||||||
|
|
||||||
m_data.header.effectDisplayType = Common::ActionEffectDisplayType::ShowActionName;
|
m_data.effectDisplayType = Common::ActionEffectDisplayType::ShowActionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addEffect( const Server::EffectEntry& effect )
|
void addEffect( const Server::EffectEntry& effect )
|
||||||
{
|
{
|
||||||
assert( m_data.header.effectCount <= 8 );
|
assert( m_data.effectCount <= 8 );
|
||||||
|
|
||||||
std::memcpy( &m_data.effects[ m_data.header.effectCount++ ], &effect, sizeof( Server::EffectEntry ) );
|
std::memset( m_data.effects, 0, sizeof( Server::EffectEntry ) * 8 );
|
||||||
|
std::memcpy( &m_data.effects[ m_data.effectCount * 8 ], &effect, sizeof( Server::EffectEntry ) );
|
||||||
|
m_data.effectCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAnimationId( uint16_t animationId )
|
void setAnimationId( uint16_t animationId )
|
||||||
{
|
{
|
||||||
m_data.header.actionAnimationId = animationId;
|
m_data.actionAnimationId = animationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEffectFlags( uint32_t effectFlags )
|
void setEffectFlags( uint32_t effectFlags )
|
||||||
|
@ -45,12 +48,12 @@ namespace Sapphire::Network::Packets::Server
|
||||||
|
|
||||||
void setRotation( uint16_t rotation )
|
void setRotation( uint16_t rotation )
|
||||||
{
|
{
|
||||||
m_data.header.rotation = rotation;
|
m_data.rotation = rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTargetActor( const uint32_t targetId )
|
void setTargetActor( const uint32_t targetId )
|
||||||
{
|
{
|
||||||
m_data.header.animationTargetId = targetId;
|
m_data.animationTargetId = targetId;
|
||||||
m_data.effectTargetId = targetId;
|
m_data.effectTargetId = targetId;
|
||||||
|
|
||||||
FFXIVPacketBase::setTargetActor( targetId );
|
FFXIVPacketBase::setTargetActor( targetId );
|
||||||
|
|
|
@ -19,19 +19,19 @@ namespace Sapphire::Network::Packets::Server
|
||||||
public ZoneChannelPacket< FFXIVIpcActorMove >
|
public ZoneChannelPacket< FFXIVIpcActorMove >
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MoveActorPacket( Entity::Chara& actor, uint8_t unk1, uint8_t unk2, uint8_t unk3, uint16_t unk4 ) :
|
MoveActorPacket( Entity::Chara& actor, uint8_t unk1, uint8_t animationType, uint8_t unk3, uint16_t unk4 ) :
|
||||||
ZoneChannelPacket< FFXIVIpcActorMove >( actor.getId(), actor.getId() )
|
ZoneChannelPacket< FFXIVIpcActorMove >( actor.getId(), actor.getId() )
|
||||||
{
|
{
|
||||||
initialize( actor, unk1, unk2, unk3, unk4 );
|
initialize( actor, unk1, animationType, unk3, unk4 );
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initialize( Entity::Chara& actor, uint8_t unk1, uint8_t unk2, uint8_t unk3, uint16_t unk4 )
|
void initialize( Entity::Chara& actor, uint8_t unk1, uint8_t animationType, uint8_t unk3, uint16_t unk4 )
|
||||||
{
|
{
|
||||||
|
|
||||||
m_data.rotation = Util::floatToUInt8Rot( actor.getRot() );
|
m_data.rotation = Util::floatToUInt8Rot( actor.getRot() );
|
||||||
m_data.unknown_1 = unk1;
|
m_data.unknown_1 = unk1;
|
||||||
m_data.unknown_2 = unk2;
|
m_data.animationType = animationType;
|
||||||
m_data.unknown_3 = unk3;
|
m_data.unknown_3 = unk3;
|
||||||
m_data.unknown_4 = unk4;
|
m_data.unknown_4 = unk4;
|
||||||
m_data.posX = Util::floatToUInt16( actor.getPos().x );
|
m_data.posX = Util::floatToUInt16( actor.getPos().x );
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <Network/PacketDef/Zone/ServerZoneDef.h>
|
#include <Network/PacketDef/Zone/ServerZoneDef.h>
|
||||||
#include <Network/GamePacketNew.h>
|
#include <Network/GamePacketNew.h>
|
||||||
#include <Util/Util.h>
|
#include <Util/Util.h>
|
||||||
|
#include <Common.h>
|
||||||
#include "Actor/Player.h"
|
#include "Actor/Player.h"
|
||||||
#include "Actor/BNpc.h"
|
#include "Actor/BNpc.h"
|
||||||
#include "Forwards.h"
|
#include "Forwards.h"
|
||||||
|
@ -39,17 +40,12 @@ namespace Sapphire::Network::Packets::Server
|
||||||
m_data.mPMax = bnpc.getMaxMp();
|
m_data.mPMax = bnpc.getMaxMp();
|
||||||
m_data.subtype = 5;
|
m_data.subtype = 5;
|
||||||
|
|
||||||
//m_data.tPMax = 3000;
|
|
||||||
m_data.level = bnpc.getLevel();
|
m_data.level = bnpc.getLevel();
|
||||||
m_data.pose = bnpc.getPose();
|
m_data.pose = bnpc.getPose();
|
||||||
|
|
||||||
memcpy( m_data.look, bnpc.getLookArray(), sizeof( m_data.look ) );
|
memcpy( m_data.look, bnpc.getLookArray(), sizeof( m_data.look ) );
|
||||||
|
|
||||||
auto models = bnpc.getModelArray();
|
|
||||||
memcpy( m_data.models, bnpc.getModelArray(), sizeof( m_data.models ) );
|
memcpy( m_data.models, bnpc.getModelArray(), sizeof( m_data.models ) );
|
||||||
|
|
||||||
memcpy( m_data.look, bnpc.getLookArray(), sizeof( m_data.look ) );
|
|
||||||
|
|
||||||
m_data.pos.x = bnpc.getPos().x;
|
m_data.pos.x = bnpc.getPos().x;
|
||||||
m_data.pos.y = bnpc.getPos().y;
|
m_data.pos.y = bnpc.getPos().y;
|
||||||
m_data.pos.z = bnpc.getPos().z;
|
m_data.pos.z = bnpc.getPos().z;
|
||||||
|
@ -61,10 +57,10 @@ namespace Sapphire::Network::Packets::Server
|
||||||
m_data.aggressionMode = bnpc.getAggressionMode();
|
m_data.aggressionMode = bnpc.getAggressionMode();
|
||||||
|
|
||||||
m_data.classJob = 0;
|
m_data.classJob = 0;
|
||||||
//m_data.voice = bnpc.getVoiceId();
|
|
||||||
//m_data.currentMount = bnpc.getCurrentMount();
|
|
||||||
|
|
||||||
//m_data.onlineStatus = static_cast< uint8_t >( bnpc.getOnlineStatus() );
|
m_data.targetId = Common::INVALID_GAME_OBJECT_ID64;
|
||||||
|
m_data.spawnerId = Common::INVALID_GAME_OBJECT_ID64;
|
||||||
|
m_data.parentActorId = Common::INVALID_GAME_OBJECT_ID64;
|
||||||
|
|
||||||
//m_data.u23 = 0x04;
|
//m_data.u23 = 0x04;
|
||||||
//m_data.u24 = 256;
|
//m_data.u24 = 256;
|
||||||
|
@ -75,36 +71,19 @@ namespace Sapphire::Network::Packets::Server
|
||||||
m_data.bNPCBase = bnpc.getBNpcBaseId();
|
m_data.bNPCBase = bnpc.getBNpcBaseId();
|
||||||
m_data.bNPCName = bnpc.getBNpcNameId();
|
m_data.bNPCName = bnpc.getBNpcNameId();
|
||||||
|
|
||||||
m_data.state = 1;
|
assert( target.getId() != bnpc.getId() );
|
||||||
if( target.getId() == bnpc.getId() )
|
|
||||||
{
|
|
||||||
m_data.spawnIndex = 0x00;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_data.spawnIndex = target.getSpawnIdForActorId( bnpc.getId() );
|
|
||||||
|
|
||||||
if( !target.isActorSpawnIdValid( m_data.spawnIndex ) )
|
m_data.spawnIndex = target.getSpawnIdForActorId( bnpc.getId() );
|
||||||
return;
|
|
||||||
}
|
if( !target.isActorSpawnIdValid( m_data.spawnIndex ) )
|
||||||
|
return;
|
||||||
// 0x20 == spawn hidden to be displayed by the spawneffect control
|
// 0x20 == spawn hidden to be displayed by the spawneffect control
|
||||||
//m_data.displayFlags = bnpc.getDisplayFlags();
|
//m_data.displayFlags = bnpc.getDisplayFlags();
|
||||||
|
|
||||||
/*if( bnpc.getZoningType() != Common::ZoneingType::None )
|
|
||||||
{
|
|
||||||
m_data.displayFlags |= static_cast< uint16_t >( Common::DisplayFlags::Invisible );
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//m_data.currentMount = bnpc.getCurrentMount();
|
//m_data.currentMount = bnpc.getCurrentMount();
|
||||||
//m_data.persistentEmote = bnpc.getPersistentEmote();
|
//m_data.persistentEmote = bnpc.getPersistentEmote();
|
||||||
|
|
||||||
m_data.targetId = static_cast< uint64_t >( bnpc.getTargetId() );
|
m_data.targetId = static_cast< uint64_t >( bnpc.getTargetId() );
|
||||||
//m_data.type = 1;
|
|
||||||
//m_data.unknown_33 = 4;
|
|
||||||
//m_data.unknown_38 = 0x70;
|
|
||||||
//m_data.unknown_60 = 3;
|
|
||||||
//m_data.unknown_61 = 7;
|
|
||||||
|
|
||||||
|
|
||||||
uint64_t currentTimeMs = Sapphire::Util::getTimeMs();
|
uint64_t currentTimeMs = Sapphire::Util::getTimeMs();
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ bool Sapphire::Scripting::ScriptMgr::init()
|
||||||
|
|
||||||
if( !status )
|
if( !status )
|
||||||
{
|
{
|
||||||
Logger::error( "ScriptMgr: failed to load scripts, the server will not function correctly without scripts loaded." );
|
Logger::error( "ScriptMgr: failed to load modules, the server will not function correctly without scripts loaded." );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,12 +285,12 @@ bool Sapphire::Scripting::ScriptMgr::onMobKill( Entity::Player& player, uint16_t
|
||||||
if( !activeQuests )
|
if( !activeQuests )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
uint16_t questId = activeQuests->c.questId;
|
uint32_t questId = activeQuests->c.questId | 0x00010000;
|
||||||
|
|
||||||
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::EventScript >( questId );
|
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::EventScript >( questId );
|
||||||
if( script )
|
if( script )
|
||||||
{
|
{
|
||||||
std::string objName = pEventMgr->getEventName( 0x00010000 | questId );
|
std::string objName = pEventMgr->getEventName( questId );
|
||||||
|
|
||||||
player.sendDebug( "Calling: {0}.{1}", objName, eventName );
|
player.sendDebug( "Calling: {0}.{1}", objName, eventName );
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
#include "Manager/EventMgr.h"
|
#include "Manager/EventMgr.h"
|
||||||
#include "Manager/ItemMgr.h"
|
#include "Manager/ItemMgr.h"
|
||||||
#include "Manager/MarketMgr.h"
|
#include "Manager/MarketMgr.h"
|
||||||
|
#include "Manager/RNGMgr.h"
|
||||||
|
#include "Manager/NaviMgr.h"
|
||||||
|
|
||||||
using namespace Sapphire::World::Manager;
|
using namespace Sapphire::World::Manager;
|
||||||
|
|
||||||
|
@ -94,9 +96,12 @@ bool Sapphire::World::ServerMgr::loadSettings( int32_t argc, char* argv[] )
|
||||||
m_config.scripts.path = pConfig->getValue< std::string >( "Scripts", "Path", "./compiledscripts/" );
|
m_config.scripts.path = pConfig->getValue< std::string >( "Scripts", "Path", "./compiledscripts/" );
|
||||||
m_config.scripts.cachePath = pConfig->getValue< std::string >( "Scripts", "CachePath", "./cache/" );
|
m_config.scripts.cachePath = pConfig->getValue< std::string >( "Scripts", "CachePath", "./cache/" );
|
||||||
|
|
||||||
|
m_config.navigation.meshPath = pConfig->getValue< std::string >( "Navigation", "MeshPath", "navi" );
|
||||||
|
|
||||||
m_config.network.disconnectTimeout = pConfig->getValue< uint16_t >( "Network", "DisconnectTimeout", 20 );
|
m_config.network.disconnectTimeout = pConfig->getValue< uint16_t >( "Network", "DisconnectTimeout", 20 );
|
||||||
m_config.network.listenIp = pConfig->getValue< std::string >( "Network", "ListenIp", "0.0.0.0" );
|
m_config.network.listenIp = pConfig->getValue< std::string >( "Network", "ListenIp", "0.0.0.0" );
|
||||||
m_config.network.listenPort = pConfig->getValue< uint16_t >( "Network", "ListenPort", 54992 );
|
m_config.network.listenPort = pConfig->getValue< uint16_t >( "Network", "ListenPort", 54992 );
|
||||||
|
m_config.network.inRangeDistance = pConfig->getValue< float >( "Network", "InRangeDistance", 80.f );
|
||||||
|
|
||||||
m_config.motd = pConfig->getValue< std::string >( "General", "MotD", "" );
|
m_config.motd = pConfig->getValue< std::string >( "General", "MotD", "" );
|
||||||
|
|
||||||
|
@ -125,6 +130,8 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger::setLogLevel( m_config.global.general.logLevel );
|
||||||
|
|
||||||
Logger::info( "Setting up generated EXD data" );
|
Logger::info( "Setting up generated EXD data" );
|
||||||
auto pExdData = std::make_shared< Data::ExdDataGenerated >();
|
auto pExdData = std::make_shared< Data::ExdDataGenerated >();
|
||||||
auto dataPath = m_config.global.general.dataPath;
|
auto dataPath = m_config.global.general.dataPath;
|
||||||
|
@ -165,6 +172,9 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] )
|
||||||
|
|
||||||
loadBNpcTemplates();
|
loadBNpcTemplates();
|
||||||
|
|
||||||
|
auto pNaviMgr = std::make_shared< Manager::NaviMgr >( framework() );
|
||||||
|
framework()->set< Manager::NaviMgr >( pNaviMgr );
|
||||||
|
|
||||||
Logger::info( "TerritoryMgr: Setting up zones" );
|
Logger::info( "TerritoryMgr: Setting up zones" );
|
||||||
auto pTeriMgr = std::make_shared< Manager::TerritoryMgr >( framework() );
|
auto pTeriMgr = std::make_shared< Manager::TerritoryMgr >( framework() );
|
||||||
auto pHousingMgr = std::make_shared< Manager::HousingMgr >( framework() );
|
auto pHousingMgr = std::make_shared< Manager::HousingMgr >( framework() );
|
||||||
|
@ -205,6 +215,7 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] )
|
||||||
auto pInventoryMgr = std::make_shared< Manager::InventoryMgr >( framework() );
|
auto pInventoryMgr = std::make_shared< Manager::InventoryMgr >( framework() );
|
||||||
auto pEventMgr = std::make_shared< Manager::EventMgr >( framework() );
|
auto pEventMgr = std::make_shared< Manager::EventMgr >( framework() );
|
||||||
auto pItemMgr = std::make_shared< Manager::ItemMgr >( framework() );
|
auto pItemMgr = std::make_shared< Manager::ItemMgr >( framework() );
|
||||||
|
auto pRNGMgr = std::make_shared< Manager::RNGMgr >( framework() );
|
||||||
|
|
||||||
framework()->set< DebugCommandMgr >( pDebugCom );
|
framework()->set< DebugCommandMgr >( pDebugCom );
|
||||||
framework()->set< Manager::PlayerMgr >( pPlayerMgr );
|
framework()->set< Manager::PlayerMgr >( pPlayerMgr );
|
||||||
|
@ -212,6 +223,7 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] )
|
||||||
framework()->set< Manager::InventoryMgr >( pInventoryMgr );
|
framework()->set< Manager::InventoryMgr >( pInventoryMgr );
|
||||||
framework()->set< Manager::EventMgr >( pEventMgr );
|
framework()->set< Manager::EventMgr >( pEventMgr );
|
||||||
framework()->set< Manager::ItemMgr >( pItemMgr );
|
framework()->set< Manager::ItemMgr >( pItemMgr );
|
||||||
|
framework()->set< Manager::RNGMgr >( pRNGMgr );
|
||||||
|
|
||||||
Logger::info( "World server running on {0}:{1}", m_ip, m_port );
|
Logger::info( "World server running on {0}:{1}", m_ip, m_port );
|
||||||
|
|
||||||
|
@ -256,7 +268,7 @@ void Sapphire::World::ServerMgr::mainLoop()
|
||||||
|
|
||||||
auto currTime = Util::getTimeSeconds();
|
auto currTime = Util::getTimeSeconds();
|
||||||
|
|
||||||
pTeriMgr->updateTerritoryInstances( static_cast< uint32_t >( currTime ) );
|
pTeriMgr->updateTerritoryInstances( currTime );
|
||||||
|
|
||||||
pScriptMgr->update();
|
pScriptMgr->update();
|
||||||
|
|
||||||
|
@ -473,7 +485,7 @@ Sapphire::Entity::BNpcTemplatePtr Sapphire::World::ServerMgr::getBNpcTemplate( c
|
||||||
|
|
||||||
Sapphire::Entity::BNpcTemplatePtr Sapphire::World::ServerMgr::getBNpcTemplate( uint32_t id )
|
Sapphire::Entity::BNpcTemplatePtr Sapphire::World::ServerMgr::getBNpcTemplate( uint32_t id )
|
||||||
{
|
{
|
||||||
for( auto entry : m_bNpcTemplateMap )
|
for( const auto& entry : m_bNpcTemplateMap )
|
||||||
{
|
{
|
||||||
if( entry.second->getId() == id )
|
if( entry.second->getId() == id )
|
||||||
return entry.second;
|
return entry.second;
|
||||||
|
|
|
@ -206,7 +206,7 @@ void Sapphire::World::Session::update()
|
||||||
// SESSION LOGIC
|
// SESSION LOGIC
|
||||||
m_pPlayer->update( Util::getTimeMs() );
|
m_pPlayer->update( Util::getTimeMs() );
|
||||||
|
|
||||||
if( ( static_cast< uint32_t >( Util::getTimeSeconds() ) - static_cast< uint32_t >( getLastSqlTime() ) ) > 10 )
|
if( Util::getTimeSeconds() - static_cast< uint32_t >( getLastSqlTime() ) > 10 )
|
||||||
{
|
{
|
||||||
updateLastSqlTime();
|
updateLastSqlTime();
|
||||||
m_pPlayer->updateSql();
|
m_pPlayer->updateSql();
|
||||||
|
|
|
@ -56,9 +56,9 @@ namespace Sapphire::World
|
||||||
|
|
||||||
Entity::PlayerPtr m_pPlayer;
|
Entity::PlayerPtr m_pPlayer;
|
||||||
|
|
||||||
int64_t m_lastDataTime;
|
uint32_t m_lastDataTime;
|
||||||
|
|
||||||
int64_t m_lastSqlTime;
|
uint32_t m_lastSqlTime;
|
||||||
bool m_isValid;
|
bool m_isValid;
|
||||||
|
|
||||||
bool m_isReplaying;
|
bool m_isReplaying;
|
||||||
|
|
|
@ -45,7 +45,7 @@ public:
|
||||||
|
|
||||||
bool hasPlayers() const
|
bool hasPlayers() const
|
||||||
{
|
{
|
||||||
return ( ( m_playerCount > 0 ) ? true : false );
|
return m_playerCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t getActorCount() const
|
size_t getActorCount() const
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#define TilesCount 32
|
#define TilesCount 32
|
||||||
#define TileSize 250.0f
|
#define TileSize 325.0f
|
||||||
#define _minY (-TilesCount*TileSize/2)
|
#define _minY (-TilesCount*TileSize/2)
|
||||||
#define _minX (-TilesCount*TileSize/2)
|
#define _minX (-TilesCount*TileSize/2)
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ Sapphire::World::Territory::Housing::HousingInteriorTerritory::HousingInteriorTe
|
||||||
Zone( territoryTypeId, guId, internalName, contentName, pFw ),
|
Zone( territoryTypeId, guId, internalName, contentName, pFw ),
|
||||||
m_landIdent( ident )
|
m_landIdent( ident )
|
||||||
{
|
{
|
||||||
m_lastActivityTime = static_cast< uint32_t >( Util::getTimeSeconds() );
|
m_lastActivityTime = Util::getTimeSeconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
Housing::HousingInteriorTerritory::~HousingInteriorTerritory() = default;
|
Housing::HousingInteriorTerritory::~HousingInteriorTerritory() = default;
|
||||||
|
@ -186,9 +186,9 @@ void Sapphire::World::Territory::Housing::HousingInteriorTerritory::spawnHousing
|
||||||
objectSpawnPkt->data().containerId = containerType;
|
objectSpawnPkt->data().containerId = containerType;
|
||||||
objectSpawnPkt->data().containerOffset = slot;
|
objectSpawnPkt->data().containerOffset = slot;
|
||||||
|
|
||||||
objectSpawnPkt->data().itemId = item->getAdditionalData() & 0xFFFF;
|
objectSpawnPkt->data().object.itemId = item->getAdditionalData() & 0xFFFF;
|
||||||
objectSpawnPkt->data().rotation = item->getRot();
|
objectSpawnPkt->data().object.rotation = item->getRot();
|
||||||
objectSpawnPkt->data().pos = item->getPos();
|
objectSpawnPkt->data().object.pos = item->getPos();
|
||||||
|
|
||||||
player.second->queuePacket( objectSpawnPkt );
|
player.second->queuePacket( objectSpawnPkt );
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,11 +89,8 @@ void Sapphire::InstanceContent::onLeaveTerritory( Entity::Player& player )
|
||||||
{
|
{
|
||||||
Logger::debug( "InstanceContent::onLeaveTerritory: Zone#{0}|{1}, Entity#{2}",
|
Logger::debug( "InstanceContent::onLeaveTerritory: Zone#{0}|{1}, Entity#{2}",
|
||||||
getGuId(), getTerritoryTypeId(), player.getId() );
|
getGuId(), getTerritoryTypeId(), player.getId() );
|
||||||
sendDirectorClear( player );
|
|
||||||
|
|
||||||
player.setDirectorInitialized( false );
|
clearDirector( player );
|
||||||
// remove "bound by duty" state
|
|
||||||
player.unsetStateFlag( PlayerStateFlag::BoundByDuty );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::InstanceContent::onUpdate( uint32_t currTime )
|
void Sapphire::InstanceContent::onUpdate( uint32_t currTime )
|
||||||
|
@ -450,3 +447,11 @@ void Sapphire::InstanceContent::unbindPlayer( uint32_t playerId )
|
||||||
if( it != m_playerMap.end() )
|
if( it != m_playerMap.end() )
|
||||||
it->second->exitInstance();
|
it->second->exitInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Sapphire::InstanceContent::clearDirector( Entity::Player& player )
|
||||||
|
{
|
||||||
|
sendDirectorClear( player );
|
||||||
|
|
||||||
|
player.setDirectorInitialized( false );
|
||||||
|
// remove "bound by duty" state
|
||||||
|
player.unsetStateFlag( PlayerStateFlag::BoundByDuty );}
|
||||||
|
|
|
@ -64,6 +64,8 @@ public:
|
||||||
|
|
||||||
void endEventCutscene();
|
void endEventCutscene();
|
||||||
|
|
||||||
|
void clearDirector( Entity::Player& player );
|
||||||
|
|
||||||
/*! set the current bgm index (inside bgm.exd) */
|
/*! set the current bgm index (inside bgm.exd) */
|
||||||
void setCurrentBGM( uint16_t bgmId );
|
void setCurrentBGM( uint16_t bgmId );
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ Sapphire::Land::Land( uint16_t territoryTypeId, uint8_t wardNum, uint8_t landId,
|
||||||
Sapphire::Data::HousingLandSetPtr info, FrameworkPtr pFw ) :
|
Sapphire::Data::HousingLandSetPtr info, FrameworkPtr pFw ) :
|
||||||
m_currentPrice( 0 ),
|
m_currentPrice( 0 ),
|
||||||
m_minPrice( 0 ),
|
m_minPrice( 0 ),
|
||||||
m_nextDrop( static_cast< uint32_t >( Util::getTimeSeconds() ) + 21600 ),
|
m_nextDrop( Util::getTimeSeconds() + 21600 ),
|
||||||
m_ownerId( 0 ),
|
m_ownerId( 0 ),
|
||||||
m_landSetId( landSetId ),
|
m_landSetId( landSetId ),
|
||||||
m_landInfo( info ),
|
m_landInfo( info ),
|
||||||
|
@ -185,7 +185,7 @@ uint64_t Sapphire::Land::getOwnerId()
|
||||||
|
|
||||||
uint32_t Sapphire::Land::getDevaluationTime()
|
uint32_t Sapphire::Land::getDevaluationTime()
|
||||||
{
|
{
|
||||||
return m_nextDrop - static_cast< uint32_t >( Util::getTimeSeconds() );
|
return m_nextDrop - Util::getTimeSeconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::Land::setCurrentPrice( uint32_t currentPrice )
|
void Sapphire::Land::setCurrentPrice( uint32_t currentPrice )
|
||||||
|
|
|
@ -39,6 +39,8 @@
|
||||||
#include "Zone.h"
|
#include "Zone.h"
|
||||||
#include "Framework.h"
|
#include "Framework.h"
|
||||||
|
|
||||||
|
#include <Manager/RNGMgr.h>
|
||||||
|
|
||||||
using namespace Sapphire::Common;
|
using namespace Sapphire::Common;
|
||||||
using namespace Sapphire::Network::Packets;
|
using namespace Sapphire::Network::Packets;
|
||||||
using namespace Sapphire::Network::Packets::Server;
|
using namespace Sapphire::Network::Packets::Server;
|
||||||
|
@ -64,6 +66,7 @@ Sapphire::Zone::Zone( uint16_t territoryTypeId, uint32_t guId,
|
||||||
FrameworkPtr pFw ) :
|
FrameworkPtr pFw ) :
|
||||||
m_currentWeather( Weather::FairSkies ),
|
m_currentWeather( Weather::FairSkies ),
|
||||||
m_nextEObjId( 0x400D0000 ),
|
m_nextEObjId( 0x400D0000 ),
|
||||||
|
m_nextActorId( 0x500D0000 ),
|
||||||
m_pFw( pFw )
|
m_pFw( pFw )
|
||||||
{
|
{
|
||||||
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
|
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
|
||||||
|
@ -76,6 +79,7 @@ Sapphire::Zone::Zone( uint16_t territoryTypeId, uint32_t guId,
|
||||||
|
|
||||||
m_weatherOverride = Weather::None;
|
m_weatherOverride = Weather::None;
|
||||||
m_territoryTypeInfo = pExdData->get< Sapphire::Data::TerritoryType >( territoryTypeId );
|
m_territoryTypeInfo = pExdData->get< Sapphire::Data::TerritoryType >( territoryTypeId );
|
||||||
|
m_bgPath = m_territoryTypeInfo->bg;
|
||||||
|
|
||||||
loadWeatherRates();
|
loadWeatherRates();
|
||||||
loadSpawnGroups();
|
loadSpawnGroups();
|
||||||
|
@ -158,7 +162,7 @@ void Sapphire::Zone::loadCellCache()
|
||||||
|
|
||||||
Weather Sapphire::Zone::getNextWeather()
|
Weather Sapphire::Zone::getNextWeather()
|
||||||
{
|
{
|
||||||
uint32_t unixTime = static_cast< uint32_t >( Util::getTimeSeconds() );
|
uint32_t unixTime = Util::getTimeSeconds();
|
||||||
// Get Eorzea hour for weather start
|
// Get Eorzea hour for weather start
|
||||||
uint32_t bell = unixTime / 175;
|
uint32_t bell = unixTime / 175;
|
||||||
// Do the magic 'cause for calculations 16:00 is 0, 00:00 is 8 and 08:00 is 16
|
// Do the magic 'cause for calculations 16:00 is 0, 00:00 is 8 and 08:00 is 16
|
||||||
|
@ -349,6 +353,11 @@ const std::string& Sapphire::Zone::getInternalName() const
|
||||||
return m_internalName;
|
return m_internalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string& Sapphire::Zone::getBgPath() const
|
||||||
|
{
|
||||||
|
return m_bgPath;
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t Sapphire::Zone::getPopCount() const
|
std::size_t Sapphire::Zone::getPopCount() const
|
||||||
{
|
{
|
||||||
return m_playerMap.size();
|
return m_playerMap.size();
|
||||||
|
@ -376,55 +385,53 @@ bool Sapphire::Zone::checkWeather()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
void Sapphire::Zone::updateBNpcs( int64_t tickCount )
|
||||||
void Sapphire::Zone::updateBnpcs( int64_t tickCount )
|
|
||||||
{
|
{
|
||||||
if( ( tickCount - m_lastMobUpdate ) > 250 )
|
if( ( tickCount - m_lastMobUpdate ) <= 250 )
|
||||||
{
|
return;
|
||||||
|
|
||||||
m_lastMobUpdate = tickCount;
|
m_lastMobUpdate = tickCount;
|
||||||
uint32_t currTime = static_cast< uint32_t >( time( nullptr ) );
|
uint32_t currTime = Sapphire::Util::getTimeSeconds();
|
||||||
|
|
||||||
for( auto it3 = m_BattleNpcDeadMap.begin(); it3 != m_BattleNpcDeadMap.end(); ++it3 )
|
for( const auto& entry : m_bNpcMap )
|
||||||
|
{
|
||||||
|
Entity::BNpcPtr pBNpc = entry.second;
|
||||||
|
|
||||||
|
if( !pBNpc )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if( !pBNpc->isAlive() )
|
||||||
|
if( currTime - pBNpc->getTimeOfDeath() > 10 )
|
||||||
{
|
{
|
||||||
|
removeActor( pBNpc );
|
||||||
Entity::BattleNpcPtr pBNpc = *it3;
|
break;
|
||||||
|
|
||||||
if( ( currTime - pBNpc->getTimeOfDeath() ) > 60 )
|
|
||||||
{
|
|
||||||
|
|
||||||
pBNpc->resetHp();
|
|
||||||
pBNpc->resetMp();
|
|
||||||
pBNpc->resetPos();
|
|
||||||
pushActor( pBNpc );
|
|
||||||
|
|
||||||
m_BattleNpcDeadMap.erase( it3 );
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for( uint32_t y = 0; y < _sizeY; ++y )
|
||||||
|
{
|
||||||
|
for( uint32_t x = 0; x < _sizeX; ++x )
|
||||||
|
{
|
||||||
|
auto cell = getCellPtr( x, y );
|
||||||
|
if( !cell )
|
||||||
|
continue;
|
||||||
|
|
||||||
for( auto entry : m_BattleNpcMap )
|
// todo: this is a pretty shit because we will visit the same cells multiple times over
|
||||||
|
// ideally we run a pass every tick and cache active cells during that initial pass over every cell
|
||||||
|
// that way we don't have an expensive lookup for every actor
|
||||||
|
|
||||||
|
if( !isCellActive( x, y ) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for( const auto& actor : cell->m_actors )
|
||||||
{
|
{
|
||||||
Entity::BattleNpcPtr pBNpc = entry.second;
|
if( actor->isBattleNpc() )
|
||||||
|
actor->getAsBNpc()->update( tickCount );
|
||||||
if( !pBNpc )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if( !pBNpc->isAlive() && currTime - pBNpc->getTimeOfDeath() > ( 10 ) )
|
|
||||||
{
|
|
||||||
removeActor( pBNpc );
|
|
||||||
m_BattleNpcDeadMap.insert( pBNpc );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
pBNpc->update( tickCount );
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
bool Sapphire::Zone::update( uint32_t currTime )
|
bool Sapphire::Zone::update( uint32_t currTime )
|
||||||
{
|
{
|
||||||
|
@ -434,7 +441,7 @@ bool Sapphire::Zone::update( uint32_t currTime )
|
||||||
bool changedWeather = checkWeather();
|
bool changedWeather = checkWeather();
|
||||||
|
|
||||||
updateSessions( changedWeather );
|
updateSessions( changedWeather );
|
||||||
//updateBnpcs( tickCount );
|
updateBNpcs( tickCount );
|
||||||
onUpdate( currTime );
|
onUpdate( currTime );
|
||||||
|
|
||||||
updateSpawnPoints();
|
updateSpawnPoints();
|
||||||
|
@ -639,7 +646,7 @@ void Sapphire::Zone::updateInRangeSet( Entity::ActorPtr pActor, Cell* pCell )
|
||||||
|
|
||||||
auto iter = pCell->m_actors.begin();
|
auto iter = pCell->m_actors.begin();
|
||||||
|
|
||||||
float fRange = 70.0f;
|
float fRange = pTeriMgr->getInRangeDistance();
|
||||||
int32_t count = 0;
|
int32_t count = 0;
|
||||||
while( iter != pCell->m_actors.end() )
|
while( iter != pCell->m_actors.end() )
|
||||||
{
|
{
|
||||||
|
@ -649,8 +656,7 @@ void Sapphire::Zone::updateInRangeSet( Entity::ActorPtr pActor, Cell* pCell )
|
||||||
if( !pCurAct || pCurAct == pActor )
|
if( !pCurAct || pCurAct == pActor )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
float distance = Util::distance( pCurAct->getPos().x, pCurAct->getPos().y, pCurAct->getPos().z,
|
float distance = Util::distance( pCurAct->getPos(), pActor->getPos() );
|
||||||
pActor->getPos().x, pActor->getPos().y, pActor->getPos().z );
|
|
||||||
|
|
||||||
bool isInRange = ( fRange == 0.0f || distance <= fRange );
|
bool isInRange = ( fRange == 0.0f || distance <= fRange );
|
||||||
bool isInRangeSet = pActor->isInRangeSet( pCurAct );
|
bool isInRangeSet = pActor->isInRangeSet( pCurAct );
|
||||||
|
@ -668,9 +674,6 @@ void Sapphire::Zone::updateInRangeSet( Entity::ActorPtr pActor, Cell* pCell )
|
||||||
pActor->addInRangeActor( pCurAct );
|
pActor->addInRangeActor( pCurAct );
|
||||||
pCurAct->addInRangeActor( pActor );
|
pCurAct->addInRangeActor( pActor );
|
||||||
|
|
||||||
// this is a hack to limit actor spawn in one packetset
|
|
||||||
if( count++ > 10 )
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else if( !isInRange && isInRangeSet )
|
else if( !isInRange && isInRangeSet )
|
||||||
{
|
{
|
||||||
|
@ -781,7 +784,7 @@ bool Sapphire::Zone::loadSpawnGroups()
|
||||||
|
|
||||||
m_spawnGroups.emplace_back( id, templateId, level, maxHp );
|
m_spawnGroups.emplace_back( id, templateId, level, maxHp );
|
||||||
|
|
||||||
Logger::debug( "id: {0}, template: {1}, level: {2}, maxHp: {3}", id, m_spawnGroups.back().getTemplateId(), level, maxHp );
|
Logger::trace( "id: {0}, template: {1}, level: {2}, maxHp: {3}", id, m_spawnGroups.back().getTemplateId(), level, maxHp );
|
||||||
}
|
}
|
||||||
|
|
||||||
res.reset();
|
res.reset();
|
||||||
|
@ -802,9 +805,9 @@ bool Sapphire::Zone::loadSpawnGroups()
|
||||||
float r = res->getFloat( 5 );
|
float r = res->getFloat( 5 );
|
||||||
uint32_t gimmickId = res->getUInt( 6 );
|
uint32_t gimmickId = res->getUInt( 6 );
|
||||||
|
|
||||||
group.getSpawnPointList().push_back( std::make_shared< Entity::SpawnPoint >( x, y, z, r, gimmickId ) );
|
group.getSpawnPointList().emplace_back( std::make_shared< Entity::SpawnPoint >( x, y, z, r, gimmickId ) );
|
||||||
|
|
||||||
Logger::debug( "id: {0}, x: {1}, y: {2}, z: {3}, gimmickId: {4}", id, x, y, z, gimmickId );
|
Logger::trace( "id: {0}, x: {1}, y: {2}, z: {3}, gimmickId: {4}", id, x, y, z, gimmickId );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -812,9 +815,8 @@ bool Sapphire::Zone::loadSpawnGroups()
|
||||||
|
|
||||||
void Sapphire::Zone::updateSpawnPoints()
|
void Sapphire::Zone::updateSpawnPoints()
|
||||||
{
|
{
|
||||||
std::random_device rd;
|
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
|
||||||
std::mt19937 mt( rd() );
|
auto rng = pRNGMgr->getRandGenerator< float >( 0.f, PI * 2 );
|
||||||
std::uniform_real_distribution< float > dist( 0.0, PI * 2 );
|
|
||||||
|
|
||||||
for( auto& group : m_spawnGroups )
|
for( auto& group : m_spawnGroups )
|
||||||
{
|
{
|
||||||
|
@ -832,21 +834,25 @@ void Sapphire::Zone::updateSpawnPoints()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t random = rand() % 20;
|
|
||||||
|
|
||||||
auto pBNpc = std::make_shared< Entity::BNpc >( getNextActorId(),
|
auto pBNpc = std::make_shared< Entity::BNpc >( getNextActorId(),
|
||||||
bNpcTemplate,
|
bNpcTemplate,
|
||||||
point->getPosX(),
|
point->getPosX(),
|
||||||
point->getPosY(),
|
point->getPosY(),
|
||||||
point->getPosZ(),
|
point->getPosZ(),
|
||||||
dist( mt ),
|
rng.next(),
|
||||||
group.getLevel(),
|
group.getLevel(),
|
||||||
group.getMaxHp(), m_pFw );
|
group.getMaxHp(), shared_from_this(), m_pFw );
|
||||||
point->setLinkedBNpc( pBNpc );
|
point->setLinkedBNpc( pBNpc );
|
||||||
|
|
||||||
pushActor( pBNpc );
|
pushActor( pBNpc );
|
||||||
}
|
}
|
||||||
|
else if( point->getLinkedBNpc() && !point->getLinkedBNpc()->isAlive() )
|
||||||
|
{
|
||||||
|
point->setTimeOfDeath( Util::getTimeSeconds() );
|
||||||
|
point->setLinkedBNpc( nullptr );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ namespace Sapphire
|
||||||
|
|
||||||
std::string m_placeName;
|
std::string m_placeName;
|
||||||
std::string m_internalName;
|
std::string m_internalName;
|
||||||
|
std::string m_bgPath;
|
||||||
|
|
||||||
std::unordered_map< int32_t, Entity::PlayerPtr > m_playerMap;
|
std::unordered_map< int32_t, Entity::PlayerPtr > m_playerMap;
|
||||||
std::unordered_map< int32_t, Entity::BNpcPtr > m_bNpcMap;
|
std::unordered_map< int32_t, Entity::BNpcPtr > m_bNpcMap;
|
||||||
|
@ -135,6 +136,8 @@ namespace Sapphire
|
||||||
|
|
||||||
const std::string& getInternalName() const;
|
const std::string& getInternalName() const;
|
||||||
|
|
||||||
|
const std::string& getBgPath() const;
|
||||||
|
|
||||||
std::size_t getPopCount() const;
|
std::size_t getPopCount() const;
|
||||||
|
|
||||||
void loadWeatherRates();
|
void loadWeatherRates();
|
||||||
|
@ -142,7 +145,7 @@ namespace Sapphire
|
||||||
bool loadSpawnGroups();
|
bool loadSpawnGroups();
|
||||||
|
|
||||||
bool checkWeather();
|
bool checkWeather();
|
||||||
//void updateBnpcs( int64_t tickCount );
|
void updateBNpcs( int64_t tickCount );
|
||||||
|
|
||||||
bool update( uint32_t currTime );
|
bool update( uint32_t currTime );
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue