diff --git a/src/tools/pcb_reader/cache.h b/src/tools/pcb_reader/cache.h index 771ef2dc..a6cbced7 100644 --- a/src/tools/pcb_reader/cache.h +++ b/src/tools/pcb_reader/cache.h @@ -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 \ No newline at end of file diff --git a/src/tools/pcb_reader/navmesh_exporter.h b/src/tools/pcb_reader/navmesh_exporter.h index ca4763d5..e2727eb2 100644 --- a/src/tools/pcb_reader/navmesh_exporter.h +++ b/src/tools/pcb_reader/navmesh_exporter.h @@ -9,12 +9,17 @@ #include #include "exporter.h" - +/* #include #include #include #include - +#include +#include +#include +#include +#include +*/ 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 diff --git a/src/tools/pcb_reader/threadpool.h b/src/tools/pcb_reader/threadpool.h index 7ae96891..ebf4243d 100644 --- a/src/tools/pcb_reader/threadpool.h +++ b/src/tools/pcb_reader/threadpool.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; diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index 93de9409..78990c0f 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #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; +} diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index aa546747..171e5890 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -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; diff --git a/src/world/Network/PacketWrappers/NpcSpawnPacket.h b/src/world/Network/PacketWrappers/NpcSpawnPacket.h index caa0d072..77d50e7e 100644 --- a/src/world/Network/PacketWrappers/NpcSpawnPacket.h +++ b/src/world/Network/PacketWrappers/NpcSpawnPacket.h @@ -4,6 +4,7 @@ #include #include #include +#include #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() ); diff --git a/src/world/Territory/Zone.cpp b/src/world/Territory/Zone.cpp index e1594560..466b552b 100644 --- a/src/world/Territory/Zone.cpp +++ b/src/world/Territory/Zone.cpp @@ -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 ); + } } }