mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-27 22:57:45 +00:00
Merge branch 'develop' into pathfinding
This commit is contained in:
commit
6894996fba
7 changed files with 485 additions and 122 deletions
|
@ -75,7 +75,19 @@ private:
|
|||
{
|
||||
try
|
||||
{
|
||||
return std::make_shared< T >( &buf[0] );
|
||||
auto pFile = std::make_shared< T >( &buf[0] );
|
||||
|
||||
m_totalFiles++;
|
||||
if( m_totalFiles % 1000 == 0 )
|
||||
{
|
||||
m_lgbCache.clear();
|
||||
m_sgbCache.clear();
|
||||
m_pcbCache.clear();
|
||||
std::cout << "Purged PCB/SGB/PCB cache \n";
|
||||
m_totalFiles = 1;
|
||||
}
|
||||
|
||||
return pFile;
|
||||
}
|
||||
catch( std::exception& e )
|
||||
{
|
||||
|
@ -102,11 +114,13 @@ private:
|
|||
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
|
|
@ -9,12 +9,17 @@
|
|||
#include <chrono>
|
||||
|
||||
#include "exporter.h"
|
||||
|
||||
/*
|
||||
#include <recastnavigation/Recast/Include/Recast.h>
|
||||
#include <recastnavigation/Recast/Include/RecastAlloc.h>
|
||||
#include <recastnavigation/Detour/Include/DetourNavMesh.h>
|
||||
#include <recastnavigation/Detour/Include/DetourNavMeshBuilder.h>
|
||||
|
||||
#include <recastnavigation/DetourTileCache/Include/DetourTileCache.h>
|
||||
#include <recastnavigation/DetourTileCache/Include/DetourTileCacheBuilder.h>
|
||||
#include <recastnavigation/RecastDemo/Include/ChunkyTriMesh.h>
|
||||
#include <recastnavigation/RecastDemo/Include/InputGeom.h>
|
||||
#include <recastnavigation/RecastDemo/Include/Sample.h>
|
||||
*/
|
||||
class NavmeshExporter
|
||||
{
|
||||
public:
|
||||
|
@ -23,7 +28,7 @@ public:
|
|||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto fileName = zone.name + ".obj";
|
||||
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
printf( "[Navmesh] Finished exporting %s in %u ms\n",
|
||||
fileName.c_str(),
|
||||
|
@ -43,9 +48,335 @@ public:
|
|||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
}
|
||||
private:
|
||||
static void exportGroup( const ExportedGroup& group, std::ofstream& of, int& indicesOffset, int& modelCount )
|
||||
/*/
|
||||
static unsigned char* buildTileMesh( const ExportedGroup& group, int tx, int ty )
|
||||
{
|
||||
unsigned char* navData;
|
||||
rcConfig cfg;
|
||||
cfg.ch = 0.2f;
|
||||
cfg.cs = 0.2f;
|
||||
cfg.walkableHeight = 2.f;
|
||||
cfg.walkableRadius = 0.5;
|
||||
cfg.walkableClimb = 0.6;
|
||||
cfg.walkableSlopeAngle = 58.f;
|
||||
cfg.minRegionArea = 8.0f;
|
||||
cfg.mergeRegionArea = 20.f;
|
||||
cfg.maxEdgeLen = 12.f;
|
||||
cfg.maxSimplificationError = 1.4f;
|
||||
cfg.maxVertsPerPoly = 6.f;
|
||||
cfg.detailSampleDist = 6.f;
|
||||
cfg.detailSampleMaxError = 1.f;
|
||||
cfg.tileSize = 160.f;
|
||||
|
||||
cfg.walkableHeight = (int)ceilf( cfg.walkableHeight / cfg.ch );
|
||||
cfg.walkableClimb = (int)floorf( cfg.walkableClimb / cfg.ch );
|
||||
cfg.walkableRadius = (int)ceilf( cfg.walkableRadius / cfg.cs );
|
||||
cfg.maxEdgeLen = (int)( cfg.maxEdgeLen / cfg.cs );
|
||||
cfg.minRegionArea = (int)rcSqr( cfg.minRegionArea ); // Note: area = size*size
|
||||
cfg.mergeRegionArea = (int)rcSqr( cfg.mergeRegionArea ); // Note: area = size*size
|
||||
cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding.
|
||||
cfg.width = cfg.tileSize + cfg.borderSize*2;
|
||||
cfg.height = cfg.tileSize + cfg.borderSize*2;
|
||||
cfg.detailSampleDist = cfg.detailSampleDist < 0.9f ? 0 : cfg.cs * cfg.detailSampleDist;
|
||||
cfg.detailSampleMaxError = cfg.ch * cfg.detailSampleMaxError;
|
||||
|
||||
rcContext ctx;
|
||||
auto hf = rcAllocHeightfield();
|
||||
auto chf = rcAllocCompactHeightfield();
|
||||
auto cs = rcAllocContourSet();
|
||||
auto pmesh = rcAllocPolyMesh();
|
||||
auto pdetailmesh = rcAllocPolyMeshDetail();
|
||||
|
||||
std::vector< float > verts;
|
||||
std::vector< int > indices;
|
||||
|
||||
int i = 0;
|
||||
int numIndices = 0;
|
||||
for( const auto& model : group.models )
|
||||
{
|
||||
for( const auto& mesh : model.second.meshes )
|
||||
{
|
||||
auto size = mesh.verts.size();
|
||||
rcCalcBounds( mesh.verts.data(), size / 3, &cfg.bmin[0], &cfg.bmax[0] );
|
||||
verts.reserve( verts.size() + size );
|
||||
memcpy( &verts[i], mesh.verts.data(), size );
|
||||
i += size;
|
||||
|
||||
size = mesh.indices.size();
|
||||
indices.reserve( indices.size() + size );
|
||||
for( auto j = 0; j < mesh.indices.size(); j += 3 )
|
||||
{
|
||||
indices[j] = mesh.indices[j] + numIndices;
|
||||
indices[j + 1] = mesh.indices[j + 1] + numIndices;
|
||||
indices[j + 2] = mesh.indices[j + 2] + numIndices;
|
||||
}
|
||||
numIndices += size;
|
||||
}
|
||||
}
|
||||
|
||||
auto chunkyMesh = new rcChunkyTriMesh;
|
||||
rcCreateChunkyTriMesh( &verts[0], &indices[0], verts.size() / 3, 256, chunkyMesh );
|
||||
if( !rcCreateHeightfield( &ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch ) )
|
||||
{
|
||||
|
||||
}
|
||||
float tbmin[2], tbmax[2];
|
||||
tbmin[0] = cfg.bmin[0];
|
||||
tbmin[1] = cfg.bmin[2];
|
||||
tbmax[0] = cfg.bmax[0];
|
||||
tbmax[1] = cfg.bmax[2];
|
||||
int cid[512];// TODO: Make grow when returning too many items.
|
||||
const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512);
|
||||
if (!ncid)
|
||||
return 0;
|
||||
|
||||
auto tileTriCount = 0;
|
||||
auto triareas = new unsigned char[chunkyMesh->maxTrisPerChunk];
|
||||
for (int i = 0; i < ncid; ++i)
|
||||
{
|
||||
const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]];
|
||||
const int* ctris = &chunkyMesh->tris[node.i*3];
|
||||
const int nctris = node.n;
|
||||
|
||||
tileTriCount += nctris;
|
||||
|
||||
memset(triareas, 0, nctris*sizeof(unsigned char));
|
||||
rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle,
|
||||
&verts[0], verts.size() / 3, ctris, nctris, triareas);
|
||||
|
||||
if (!rcRasterizeTriangles(&ctx, &verts[0], verts.size() / 3, ctris, triareas, nctris, *hf, cfg.walkableClimb))
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
delete [] triareas;
|
||||
triareas = 0;
|
||||
}
|
||||
|
||||
// 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(&ctx, cfg.walkableClimb, *hf);
|
||||
|
||||
rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
|
||||
rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
|
||||
|
||||
// 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.
|
||||
chf = rcAllocCompactHeightfield();
|
||||
if (!chf)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build compact data.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
rcFreeHeightField(hf);
|
||||
hf = 0;
|
||||
}
|
||||
|
||||
// Erode the walkable area by agent radius.
|
||||
if (!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not erode.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 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(&ctx, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build distance field.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if (!rcBuildRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build watershed regions.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
//else if (m_partitionType == SAMPLE_PARTITION_MONOTONE)
|
||||
//{
|
||||
// // Partition the walkable surface into simple regions without holes.
|
||||
// // Monotone partitioning does not need distancefield.
|
||||
// if (!rcBuildRegionsMonotone(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
|
||||
// {
|
||||
// ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build monotone regions.");
|
||||
// return 0;
|
||||
// }
|
||||
//}
|
||||
//else // SAMPLE_PARTITION_LAYERS
|
||||
//{
|
||||
// // Partition the walkable surface into simple regions without holes.
|
||||
// if (!rcBuildLayerRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea))
|
||||
// {
|
||||
// ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build layer regions.");
|
||||
// return 0;
|
||||
// }
|
||||
//}
|
||||
|
||||
// Create contours.
|
||||
cs = rcAllocContourSet();
|
||||
if (!cs)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cs))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not create contours.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cs->nconts == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build polygon navmesh from the contours.
|
||||
pmesh = rcAllocPolyMesh();
|
||||
if (!pmesh)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildPolyMesh(&ctx, *cs, cfg.maxVertsPerPoly, *pmesh))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build detail mesh.
|
||||
pdetailmesh = rcAllocPolyMeshDetail();
|
||||
if (!pdetailmesh)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!rcBuildPolyMeshDetail(&ctx, *pmesh, *chf,
|
||||
cfg.detailSampleDist, cfg.detailSampleMaxError,
|
||||
*pdetailmesh))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
rcFreeCompactHeightfield(chf);
|
||||
chf = 0;
|
||||
rcFreeContourSet(cs);
|
||||
cs = 0;
|
||||
}
|
||||
|
||||
unsigned char* navData = 0;
|
||||
int navDataSize = 0;
|
||||
if (cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON)
|
||||
{
|
||||
if (pmesh->nverts >= 0xffff)
|
||||
{
|
||||
// The vertex indices are ushorts, and cannot point to more than 0xffff vertices.
|
||||
ctx.log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", pmesh->nverts, 0xffff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Update poly flags from areas.
|
||||
for (int i = 0; i < pmesh->npolys; ++i)
|
||||
{
|
||||
//pmesh->flags[i] = sampleAreaToFlags(pmesh->areas[i]);
|
||||
}
|
||||
|
||||
dtNavMeshCreateParams params;
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.verts = pmesh->verts;
|
||||
params.vertCount = pmesh->nverts;
|
||||
params.polys = pmesh->polys;
|
||||
params.polyAreas = pmesh->areas;
|
||||
params.polyFlags = pmesh->flags;
|
||||
params.polyCount = pmesh->npolys;
|
||||
params.nvp = pmesh->nvp;
|
||||
params.detailMeshes = pdetailmesh->meshes;
|
||||
params.detailVerts = pdetailmesh->verts;
|
||||
params.detailVertsCount = pdetailmesh->nverts;
|
||||
params.detailTris = pdetailmesh->tris;
|
||||
params.detailTriCount = pdetailmesh->ntris;
|
||||
params.offMeshConVerts = 0;
|
||||
params.offMeshConRad = 0;
|
||||
params.offMeshConDir = 0;
|
||||
params.offMeshConAreas = 0;
|
||||
params.offMeshConFlags = 0;
|
||||
params.offMeshConUserID = 0;
|
||||
params.offMeshConCount = 0;
|
||||
params.walkableHeight = cfg.walkableHeight;
|
||||
params.walkableRadius = cfg.walkableRadius;
|
||||
params.walkableClimb = cfg.walkableClimb;
|
||||
params.tileX = 0;
|
||||
params.tileY = 0;
|
||||
params.tileLayer = 0;
|
||||
rcVcopy(params.bmin, pmesh->bmin);
|
||||
rcVcopy(params.bmax, pmesh->bmax);
|
||||
params.cs = cfg.cs;
|
||||
params.ch = cfg.ch;
|
||||
params.buildBvTree = true;
|
||||
|
||||
if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "Could not build Detour navmesh.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
auto tileMemUsage = navDataSize/1024.0f;
|
||||
|
||||
ctx.stopTimer(RC_TIMER_TOTAL);
|
||||
|
||||
// Show performance stats.
|
||||
//duLogBuildTimes(*&ctx, ctx.getAccumulatedTime(RC_TIMER_TOTAL));
|
||||
ctx.log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", pmesh->nverts, pmesh->npolys);
|
||||
|
||||
auto tileBuildTime = ctx.getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f;
|
||||
|
||||
auto dataSize = navDataSize;
|
||||
return navData;
|
||||
|
||||
}
|
||||
//*/
|
||||
};
|
||||
#endif // !OBJ_EXPORTER_H
|
||||
|
|
|
@ -25,6 +25,9 @@ public:
|
|||
|
||||
void addWorkers( unsigned int num )
|
||||
{
|
||||
|
||||
std::unique_lock lock( m_mutex );
|
||||
m_runFlag = true;
|
||||
if( num == 0 )
|
||||
num = std::thread::hardware_concurrency() - 1;
|
||||
|
||||
|
@ -52,20 +55,23 @@ public:
|
|||
{
|
||||
std::unique_lock lock( m_mutex );
|
||||
m_pendingJobs.clear();
|
||||
for( auto&& worker : m_workers )
|
||||
{
|
||||
m_pendingJobs.emplace( {} );
|
||||
}
|
||||
}
|
||||
complete();
|
||||
m_cv.notify_all();
|
||||
m_workers.clear();
|
||||
}
|
||||
|
||||
bool complete()
|
||||
{
|
||||
{
|
||||
std::scoped_lock lock( m_mutex );
|
||||
for( auto&& worker : m_workers )
|
||||
{
|
||||
m_pendingJobs.push_back( {} );
|
||||
}
|
||||
}
|
||||
m_cv.notify_all();
|
||||
{
|
||||
std::unique_lock lock( m_mutex );
|
||||
m_runFlag = false;
|
||||
m_cv.wait( lock, [&]{ return m_pendingJobs.empty(); } );
|
||||
}
|
||||
m_workers.clear();
|
||||
return true;
|
||||
}
|
||||
|
@ -76,9 +82,14 @@ private:
|
|||
{
|
||||
std::packaged_task< void() > func;
|
||||
{
|
||||
std::unique_lock< std::mutex > lock( m_mutex );
|
||||
std::unique_lock lock( m_mutex );
|
||||
if( m_pendingJobs.empty() )
|
||||
{
|
||||
if( !m_runFlag )
|
||||
{
|
||||
m_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
m_cv.wait( lock, [&](){ return !m_pendingJobs.empty(); } );
|
||||
}
|
||||
func = std::move( m_pendingJobs.front() );
|
||||
|
@ -92,6 +103,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
bool m_runFlag{ true };
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::deque< std::packaged_task< void() > > m_pendingJobs;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <Network/CommonActorControl.h>
|
||||
#include <Network/PacketWrappers/EffectPacket.h>
|
||||
#include <Network/PacketDef/Zone/ClientZoneDef.h>
|
||||
#include <Logging/Logger.h>
|
||||
|
||||
#include "Forwards.h"
|
||||
#include "Action/Action.h"
|
||||
|
@ -70,6 +71,8 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX
|
|||
|
||||
m_spawnPos = m_pos;
|
||||
|
||||
m_timeOfDeath = 0;
|
||||
|
||||
m_maxHp = maxHp;
|
||||
m_maxMp = 200;
|
||||
m_hp = maxHp;
|
||||
|
@ -138,6 +141,7 @@ void Sapphire::Entity::BNpc::spawn( PlayerPtr 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
|
||||
|
@ -354,7 +358,7 @@ void Sapphire::Entity::BNpc::aggro( Sapphire::Entity::CharaPtr pChara )
|
|||
if( pChara->isPlayer() )
|
||||
{
|
||||
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
||||
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) );
|
||||
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 1, 1, 1 ) );
|
||||
tmpPlayer->onMobAggro( getAsBNpc() );
|
||||
}
|
||||
}
|
||||
|
@ -367,6 +371,7 @@ void Sapphire::Entity::BNpc::deaggro( Sapphire::Entity::CharaPtr pChara )
|
|||
if( pChara->isPlayer() )
|
||||
{
|
||||
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
||||
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) );
|
||||
tmpPlayer->onMobDeaggro( getAsBNpc() );
|
||||
}
|
||||
}
|
||||
|
@ -377,92 +382,91 @@ void Sapphire::Entity::BNpc::update( int64_t currTime )
|
|||
const uint8_t aggroRange = 8;
|
||||
const uint8_t maxDistanceToOrigin = 40;
|
||||
|
||||
if( m_status == ActorStatus::Dead )
|
||||
return;
|
||||
|
||||
switch( m_state )
|
||||
{
|
||||
case BNpcState::Retreat:
|
||||
{
|
||||
if( moveTo( m_spawnPos ) )
|
||||
m_state = BNpcState::Idle;
|
||||
}
|
||||
break;
|
||||
|
||||
case BNpcState::Idle:
|
||||
{
|
||||
// passive mobs should ignore players unless aggro'd
|
||||
if( m_aggressionMode == 1 )
|
||||
case BNpcState::Dead:
|
||||
case BNpcState::JustDied:
|
||||
return;
|
||||
|
||||
CharaPtr pClosestChara = getClosestChara();
|
||||
|
||||
if( pClosestChara && pClosestChara->isAlive() )
|
||||
case BNpcState::Retreat:
|
||||
{
|
||||
auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
|
||||
pClosestChara->getPos().x,
|
||||
pClosestChara->getPos().y,
|
||||
pClosestChara->getPos().z );
|
||||
|
||||
if( distance < aggroRange && pClosestChara->isPlayer() )
|
||||
aggro( pClosestChara );
|
||||
//if( distance < aggroRange && getbehavior() == 2 )
|
||||
// aggro( pClosestActor );
|
||||
if( moveTo( m_spawnPos ) )
|
||||
m_state = BNpcState::Idle;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
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() )
|
||||
case BNpcState::Idle:
|
||||
{
|
||||
hateListRemove( pHatedActor );
|
||||
pHatedActor = hateListGetHighest();
|
||||
}
|
||||
// passive mobs should ignore players unless aggro'd
|
||||
if( m_aggressionMode == 1 )
|
||||
return;
|
||||
|
||||
if( pHatedActor )
|
||||
{
|
||||
auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
|
||||
pHatedActor->getPos().x,
|
||||
pHatedActor->getPos().y,
|
||||
pHatedActor->getPos().z );
|
||||
CharaPtr pClosestChara = getClosestChara();
|
||||
|
||||
if( distanceOrig > maxDistanceToOrigin )
|
||||
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 < aggroRange && pClosestChara->isPlayer() )
|
||||
aggro( pClosestChara );
|
||||
}
|
||||
}
|
||||
|
||||
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_ID );
|
||||
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
|
||||
{
|
||||
hateListClear();
|
||||
changeTarget( INVALID_GAME_OBJECT_ID );
|
||||
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_ID );
|
||||
setStance( Stance::Passive );
|
||||
//setOwner( nullptr );
|
||||
m_state = BNpcState::Retreat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
step();
|
||||
|
@ -482,5 +486,16 @@ void Sapphire::Entity::BNpc::onDeath()
|
|||
setTargetId( INVALID_GAME_OBJECT_ID );
|
||||
m_currentStance = Stance::Passive;
|
||||
m_state = BNpcState::Dead;
|
||||
m_timeOfDeath = Util::getTimeSeconds();
|
||||
hateListClear();
|
||||
}
|
||||
|
||||
uint32_t Sapphire::Entity::BNpc::getTimeOfDeath() const
|
||||
{
|
||||
return m_timeOfDeath;
|
||||
}
|
||||
|
||||
void Sapphire::Entity::BNpc::setTimeOfDeath( uint32_t timeOfDeath )
|
||||
{
|
||||
m_timeOfDeath = timeOfDeath;
|
||||
}
|
||||
|
|
|
@ -86,6 +86,9 @@ namespace Sapphire::Entity
|
|||
|
||||
void onDeath() override;
|
||||
|
||||
uint32_t getTimeOfDeath() const;
|
||||
void setTimeOfDeath( uint32_t timeOfDeath);
|
||||
|
||||
private:
|
||||
uint32_t m_bNpcBaseId;
|
||||
uint32_t m_bNpcNameId;
|
||||
|
@ -99,6 +102,8 @@ namespace Sapphire::Entity
|
|||
uint32_t m_displayFlags;
|
||||
uint8_t m_level;
|
||||
|
||||
uint32_t m_timeOfDeath;
|
||||
|
||||
Common::FFXIVARR_POSITION3 m_spawnPos;
|
||||
|
||||
BNpcState m_state;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <Network/PacketDef/Zone/ServerZoneDef.h>
|
||||
#include <Network/GamePacketNew.h>
|
||||
#include <Util/Util.h>
|
||||
#include <Common.h>
|
||||
#include "Actor/Player.h"
|
||||
#include "Actor/BNpc.h"
|
||||
#include "Forwards.h"
|
||||
|
@ -57,6 +58,8 @@ namespace Sapphire::Network::Packets::Server
|
|||
|
||||
m_data.classJob = 0;
|
||||
|
||||
m_data.targetId = Common::INVALID_GAME_OBJECT_ID;
|
||||
|
||||
//m_data.u23 = 0x04;
|
||||
//m_data.u24 = 256;
|
||||
m_data.state = static_cast< uint8_t >( bnpc.getStatus() );
|
||||
|
|
|
@ -381,49 +381,27 @@ bool Sapphire::Zone::checkWeather()
|
|||
|
||||
void Sapphire::Zone::updateBNpcs( int64_t tickCount )
|
||||
{
|
||||
if( ( tickCount - m_lastMobUpdate ) > 250 )
|
||||
{
|
||||
m_lastMobUpdate = tickCount;
|
||||
uint32_t currTime = Sapphire::Util::getTimeSeconds();
|
||||
if( ( tickCount - m_lastMobUpdate ) <= 250 )
|
||||
return;
|
||||
|
||||
/*for( auto it3 = m_BattleNpcDeadMap.begin(); it3 != m_BattleNpcDeadMap.end(); ++it3 )
|
||||
{
|
||||
m_lastMobUpdate = tickCount;
|
||||
uint32_t currTime = Sapphire::Util::getTimeSeconds();
|
||||
|
||||
Entity::BattleNpcPtr pBNpc = *it3;
|
||||
for( auto entry : m_bNpcMap )
|
||||
{
|
||||
Entity::BNpcPtr pBNpc = entry.second;
|
||||
|
||||
if( ( currTime - pBNpc->getTimeOfDeath() ) > 60 )
|
||||
{
|
||||
if( !pBNpc )
|
||||
continue;
|
||||
|
||||
pBNpc->resetHp();
|
||||
pBNpc->resetMp();
|
||||
pBNpc->resetPos();
|
||||
pushActor( pBNpc );
|
||||
if( !pBNpc->isAlive() && currTime - pBNpc->getTimeOfDeath() > 10 )
|
||||
{
|
||||
removeActor( pBNpc );
|
||||
break;
|
||||
}
|
||||
|
||||
m_BattleNpcDeadMap.erase( it3 );
|
||||
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
for( auto entry : m_bNpcMap )
|
||||
{
|
||||
Entity::BNpcPtr pBNpc = entry.second;
|
||||
|
||||
if( !pBNpc )
|
||||
continue;
|
||||
|
||||
//if( !pBNpc->isAlive() && currTime - pBNpc->getTimeOfDeath() > ( 10 ) )
|
||||
//{
|
||||
// removeActor( pBNpc );
|
||||
// m_BattleNpcDeadMap.insert( pBNpc );
|
||||
// break;
|
||||
//}
|
||||
|
||||
pBNpc->update( tickCount );
|
||||
|
||||
}
|
||||
}
|
||||
pBNpc->update( tickCount );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -844,6 +822,11 @@ void Sapphire::Zone::updateSpawnPoints()
|
|||
|
||||
pushActor( pBNpc );
|
||||
}
|
||||
else if( point->getLinkedBNpc() && !point->getLinkedBNpc()->isAlive() )
|
||||
{
|
||||
point->setTimeOfDeath( Util::getTimeSeconds() );
|
||||
point->setLinkedBNpc( nullptr );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue