2018-11-22 01:17:19 +11:00
|
|
|
#include "PlayerMgr.h"
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
#include <Service.h>
|
|
|
|
|
|
|
|
#include <Exd/ExdData.h>
|
2025-01-02 09:22:10 +01:00
|
|
|
#include <Util/Util.h>
|
2023-01-27 11:13:57 +01:00
|
|
|
#include <Territory/Land.h>
|
2018-11-22 01:17:19 +11:00
|
|
|
|
2025-01-02 13:25:18 +01:00
|
|
|
#include <Manager/AchievementMgr.h>
|
2021-11-27 00:53:57 +01:00
|
|
|
#include <Manager/TerritoryMgr.h>
|
2023-01-27 11:13:57 +01:00
|
|
|
#include <Manager/HousingMgr.h>
|
2023-02-18 23:05:15 +01:00
|
|
|
#include <Manager/QuestMgr.h>
|
2025-01-02 09:48:51 +01:00
|
|
|
#include <Manager/WarpMgr.h>
|
2025-01-02 13:25:18 +01:00
|
|
|
#include <Manager/MapMgr.h>
|
2018-11-22 01:17:19 +11:00
|
|
|
|
2023-02-10 21:28:34 +01:00
|
|
|
#include <Script/ScriptMgr.h>
|
2021-11-27 00:53:57 +01:00
|
|
|
#include <Common.h>
|
|
|
|
|
2023-02-25 15:58:11 +01:00
|
|
|
#include <Database/ZoneDbConnection.h>
|
|
|
|
#include <Database/DbWorkerPool.h>
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
#include <Network/CommonActorControl.h>
|
2023-03-07 10:25:55 +01:00
|
|
|
#include <Network/Util/PacketUtil.h>
|
2021-11-27 00:53:57 +01:00
|
|
|
|
2018-11-22 01:17:19 +11:00
|
|
|
#include <Actor/Player.h>
|
2022-01-27 20:03:51 -03:00
|
|
|
#include <Actor/BNpc.h>
|
2018-11-22 01:17:19 +11:00
|
|
|
|
2023-02-10 21:28:34 +01:00
|
|
|
#include <Inventory/Item.h>
|
|
|
|
|
|
|
|
#include <Util/UtilMath.h>
|
|
|
|
|
2022-01-27 21:08:43 +01:00
|
|
|
using namespace Sapphire;
|
2018-12-01 00:27:16 +11:00
|
|
|
using namespace Sapphire::World::Manager;
|
2021-11-27 00:53:57 +01:00
|
|
|
using namespace Sapphire::Network::Packets;
|
|
|
|
using namespace Sapphire::Network::Packets::WorldPackets::Server;
|
|
|
|
using namespace Sapphire::Network::ActorControl;
|
2018-11-22 01:17:19 +11:00
|
|
|
|
2022-01-27 21:08:43 +01:00
|
|
|
|
2023-02-25 15:58:11 +01:00
|
|
|
Sapphire::Entity::PlayerPtr PlayerMgr::getPlayer( uint32_t entityId )
|
|
|
|
{
|
|
|
|
//std::lock_guard<std::mutex> lock( m_sessionMutex );
|
|
|
|
auto it = m_playerMapById.find( entityId );
|
|
|
|
|
|
|
|
if( it != m_playerMapById.end() )
|
|
|
|
return ( it->second );
|
|
|
|
|
|
|
|
// not found (new character?) - we'll load from DB and hope it's there
|
|
|
|
return loadPlayer( entityId );
|
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Entity::PlayerPtr PlayerMgr::getPlayer( uint64_t characterId )
|
|
|
|
{
|
|
|
|
//std::lock_guard<std::mutex> lock( m_sessionMutex );
|
|
|
|
auto it = m_playerMapByCharacterId.find( characterId );
|
|
|
|
|
|
|
|
if( it != m_playerMapByCharacterId.end() )
|
|
|
|
return ( it->second );
|
|
|
|
|
|
|
|
// not found (new character?) - we'll load from DB and hope it's there
|
|
|
|
return loadPlayer( characterId );
|
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Entity::PlayerPtr PlayerMgr::getPlayer( const std::string& playerName )
|
|
|
|
{
|
|
|
|
//std::lock_guard<std::mutex> lock( m_sessionMutex );
|
|
|
|
auto it = m_playerMapByName.find( playerName );
|
|
|
|
|
|
|
|
if( it != m_playerMapByName.end() )
|
|
|
|
return ( it->second );
|
|
|
|
|
|
|
|
// not found (new character?) - we'll load from DB and hope it's there
|
|
|
|
return loadPlayer( playerName );
|
|
|
|
}
|
|
|
|
|
2023-02-27 08:58:08 +01:00
|
|
|
std::vector< Sapphire::Entity::PlayerPtr > PlayerMgr::searchPlayersByName( const std::string& playerName )
|
|
|
|
{
|
|
|
|
std::vector< Sapphire::Entity::PlayerPtr > results{};
|
|
|
|
|
|
|
|
for( auto& it : m_playerMapByName )
|
|
|
|
{
|
|
|
|
if( it.first.find( playerName ) != std::string::npos )
|
|
|
|
results.push_back( it.second );
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2023-02-25 15:58:11 +01:00
|
|
|
|
|
|
|
std::string PlayerMgr::getPlayerNameFromDb( uint64_t characterId, bool forceDbLoad )
|
|
|
|
{
|
|
|
|
if( !forceDbLoad )
|
|
|
|
{
|
|
|
|
auto it = m_playerMapByCharacterId.find( characterId );
|
|
|
|
|
|
|
|
if( it != m_playerMapByCharacterId.end() )
|
|
|
|
return ( it->second->getName() );
|
|
|
|
}
|
|
|
|
|
|
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
|
|
auto res = db.query( "SELECT name FROM charainfo WHERE characterid = " + std::to_string( characterId ) );
|
|
|
|
|
|
|
|
if( !res->next() )
|
|
|
|
return "Unknown";
|
|
|
|
|
|
|
|
std::string playerName = res->getString( 1 );
|
|
|
|
|
|
|
|
return playerName;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Sapphire::Entity::PlayerPtr PlayerMgr::addPlayer( uint64_t characterId )
|
|
|
|
{
|
|
|
|
auto pPlayer = Entity::make_Player();
|
|
|
|
|
|
|
|
if( !pPlayer->loadFromDb( characterId ) )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
m_playerMapById[ pPlayer->getId() ] = pPlayer;
|
|
|
|
m_playerMapByCharacterId[ pPlayer->getCharacterId() ] = pPlayer;
|
|
|
|
m_playerMapByName[ pPlayer->getName() ] = pPlayer;
|
|
|
|
|
|
|
|
return pPlayer;
|
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Entity::PlayerPtr PlayerMgr::loadPlayer( uint32_t entityId )
|
|
|
|
{
|
|
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
|
|
auto res = db.query( "SELECT CharacterId FROM charainfo WHERE EntityId = " + std::to_string( entityId ) );
|
|
|
|
if( !res || !res->next() )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
uint64_t characterId = res->getUInt64( 1 );
|
|
|
|
|
|
|
|
return addPlayer( characterId );
|
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Entity::PlayerPtr PlayerMgr::loadPlayer( uint64_t characterId )
|
|
|
|
{
|
|
|
|
return addPlayer( characterId );
|
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Entity::PlayerPtr PlayerMgr::loadPlayer( const std::string& playerName )
|
|
|
|
{
|
|
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
|
|
auto res = db.query( "SELECT CharacterId FROM charainfo WHERE Name = " + playerName );
|
|
|
|
if( !res || !res->next() )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
uint64_t characterId = res->getUInt64( 1 );
|
|
|
|
|
|
|
|
return addPlayer( characterId );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PlayerMgr::loadPlayers()
|
|
|
|
{
|
|
|
|
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
|
|
|
|
auto res = db.query( "SELECT CharacterId FROM charainfo" );
|
|
|
|
|
|
|
|
// no players or failed
|
|
|
|
while( res->next() )
|
|
|
|
{
|
|
|
|
uint64_t characterId = res->getUInt64( 1 );
|
|
|
|
if( !addPlayer( characterId ) )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Entity::PlayerPtr PlayerMgr::syncPlayer( uint64_t characterId )
|
|
|
|
{
|
|
|
|
auto pPlayer = getPlayer( characterId );
|
|
|
|
if( !pPlayer )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
// get our cached last db write
|
|
|
|
auto lastCacheSync = pPlayer->getLastDBWrite();
|
|
|
|
|
|
|
|
// update this player's last db write
|
|
|
|
if( !pPlayer->syncLastDBWrite() )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
// get db last write
|
|
|
|
auto dbSync = pPlayer->getLastDBWrite();
|
|
|
|
|
|
|
|
|
|
|
|
// db was updated and we lost track of it - update
|
|
|
|
// @todo for now, always reload the player on login.
|
|
|
|
//if( dbSync != lastCacheSync )
|
|
|
|
{
|
|
|
|
// clear current maps
|
|
|
|
m_playerMapById[ pPlayer->getId() ] = nullptr;
|
|
|
|
m_playerMapByName[ pPlayer->getName() ] = nullptr;
|
|
|
|
m_playerMapByCharacterId[ pPlayer->getCharacterId() ] = nullptr;
|
|
|
|
|
|
|
|
if( !pPlayer->loadFromDb( characterId ) )
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
m_playerMapById[ pPlayer->getId() ] = pPlayer;
|
|
|
|
m_playerMapByCharacterId[ pPlayer->getCharacterId() ] = pPlayer;
|
|
|
|
m_playerMapByName[ pPlayer->getName() ] = pPlayer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pPlayer;
|
|
|
|
}
|
|
|
|
|
2022-02-23 08:36:23 +01:00
|
|
|
void PlayerMgr::onMobKill( Entity::Player& player, Entity::BNpc& bnpc )
|
2021-11-27 00:53:57 +01:00
|
|
|
{
|
|
|
|
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
|
2022-02-23 08:36:23 +01:00
|
|
|
scriptMgr.onBNpcKill( player, bnpc );
|
2021-11-27 00:53:57 +01:00
|
|
|
|
2022-02-10 18:50:44 +01:00
|
|
|
if( player.hasReward( Common::UnlockEntry::HuntingLog ) )
|
2025-01-02 09:22:10 +01:00
|
|
|
onUpdateHuntingLog( player, bnpc.getBNpcNameId() );
|
2021-11-27 00:53:57 +01:00
|
|
|
}
|
|
|
|
|
2023-02-22 14:55:59 +01:00
|
|
|
void PlayerMgr::sendLoginMessage( Entity::Player& player )
|
2021-11-27 00:53:57 +01:00
|
|
|
{
|
2023-02-17 15:34:51 +01:00
|
|
|
auto motd = server().getConfig().motd;
|
2021-11-27 00:53:57 +01:00
|
|
|
|
|
|
|
std::istringstream ss( motd );
|
|
|
|
std::string msg;
|
|
|
|
while( std::getline( ss, msg, ';' ) )
|
|
|
|
{
|
2023-01-27 11:13:57 +01:00
|
|
|
sendServerNotice( player, msg );
|
2021-11-27 00:53:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-22 14:55:59 +01:00
|
|
|
void PlayerMgr::onLogin( Entity::Player &player )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-02-10 21:38:08 +01:00
|
|
|
void PlayerMgr::onLogout( Entity::Player &player )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-02-10 13:05:53 -03:00
|
|
|
void PlayerMgr::onDeath( Entity::Player& player )
|
2023-01-15 22:03:44 +01:00
|
|
|
{
|
|
|
|
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
|
|
|
|
scriptMgr.onPlayerDeath( player );
|
|
|
|
}
|
2021-11-27 00:53:57 +01:00
|
|
|
|
2023-02-20 15:25:57 +01:00
|
|
|
void PlayerMgr::onMoveZone( Sapphire::Entity::Player& player )
|
2023-01-27 11:13:57 +01:00
|
|
|
{
|
|
|
|
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
|
|
|
|
auto& housingMgr = Common::Service< HousingMgr >::ref();
|
|
|
|
|
|
|
|
auto pZone = teriMgr.getTerritoryByGuId( player.getTerritoryId() );
|
|
|
|
if( !pZone )
|
|
|
|
{
|
|
|
|
Logger::error( "Territory GuID#{} not found!", player.getTerritoryId() );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto& teri = *pZone;
|
|
|
|
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendLogin( player );
|
2023-01-27 11:13:57 +01:00
|
|
|
|
|
|
|
player.sendInventory();
|
|
|
|
|
|
|
|
if( player.isLogin() )
|
|
|
|
{
|
2023-03-09 21:54:30 +01:00
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), SetConfigFlags, player.getConfigFlags(), 1 );
|
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), SetMaxGearSets, player.getMaxGearSets() );
|
2023-01-27 11:13:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// set flags, will be reset automatically by zoning ( only on client side though )
|
|
|
|
//setStateFlag( PlayerStateFlag::BetweenAreas );
|
|
|
|
//setStateFlag( PlayerStateFlag::BetweenAreas1 );
|
|
|
|
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendHuntingLog( player );
|
2023-01-27 11:13:57 +01:00
|
|
|
|
2023-02-14 13:50:08 +01:00
|
|
|
if( player.isLogin() )
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendPlayerSetup( player );
|
2023-02-14 13:50:08 +01:00
|
|
|
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendRecastGroups( player );
|
|
|
|
Network::Util::Packet::sendBaseParams( player );
|
2023-03-09 21:54:30 +01:00
|
|
|
Network::Util::Packet::sendActorControl( player, player.getId(), SetItemLevel, player.getItemLevel() );
|
2023-01-27 11:13:57 +01:00
|
|
|
if( player.isLogin() )
|
|
|
|
{
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendChangeClass( player );
|
2023-03-09 21:54:30 +01:00
|
|
|
Network::Util::Packet::sendActorControl( player, player.getId(), 0x112, 0x24 ); // unknown
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendContentAttainFlags( player );
|
2023-01-27 11:13:57 +01:00
|
|
|
player.clearSoldItems();
|
|
|
|
}
|
|
|
|
|
|
|
|
housingMgr.sendLandFlags( player );
|
|
|
|
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendInitZone( player );
|
2023-01-27 11:13:57 +01:00
|
|
|
|
|
|
|
if( player.isLogin() )
|
|
|
|
{
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendDailyQuests( player );
|
|
|
|
Network::Util::Packet::sendQuestRepeatFlags( player );
|
2023-02-18 23:05:15 +01:00
|
|
|
|
|
|
|
auto &questMgr = Common::Service< World::Manager::QuestMgr >::ref();
|
|
|
|
questMgr.sendQuestsInfo( player );
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendGrandCompany( player );
|
2023-01-27 11:13:57 +01:00
|
|
|
}
|
|
|
|
|
2023-03-06 13:27:56 +01:00
|
|
|
teri.onPlayerZoneIn( player );
|
|
|
|
|
2023-01-27 11:13:57 +01:00
|
|
|
}
|
2021-11-27 00:53:57 +01:00
|
|
|
|
2023-02-10 21:28:34 +01:00
|
|
|
void PlayerMgr::onUpdate( Entity::Player& player, uint64_t tickCount )
|
|
|
|
{
|
|
|
|
if( player.getHp() <= 0 && player.getStatus() != Common::ActorStatus::Dead )
|
|
|
|
{
|
|
|
|
player.die();
|
|
|
|
onDeath( player );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !player.isAlive() )
|
|
|
|
return;
|
|
|
|
|
2023-03-07 15:08:28 +01:00
|
|
|
checkAutoAttack( player, tickCount );
|
|
|
|
}
|
|
|
|
|
|
|
|
void PlayerMgr::checkAutoAttack( Entity::Player& player, uint64_t tickCount ) const
|
|
|
|
{
|
|
|
|
auto mainWeap = player.getItemAt( Common::GearSet0, Common::MainHand );
|
2023-03-24 22:52:38 +01:00
|
|
|
if( !mainWeap || player.checkAction() || !player.isAutoattackOn() || !player.getTargetId() || player.getStance() != Common::Active )
|
2023-03-07 15:08:28 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
for( const auto& actor : player.getInRangeActors() )
|
2023-02-10 21:28:34 +01:00
|
|
|
{
|
2023-03-07 15:08:28 +01:00
|
|
|
if( actor->getId() != player.getTargetId() || !actor->getAsChara()->isAlive() )
|
|
|
|
continue;
|
|
|
|
auto chara = actor->getAsChara();
|
|
|
|
|
|
|
|
// default autoattack range
|
|
|
|
float range = 3.f + chara->getRadius() + player.getRadius() * 0.5f;
|
|
|
|
|
|
|
|
// default autoattack range for ranged classes
|
|
|
|
auto classJob = player.getClass();
|
|
|
|
|
|
|
|
if( classJob == Common::ClassJob::Machinist || classJob == Common::ClassJob::Bard || classJob == Common::ClassJob::Archer )
|
|
|
|
range = 25.f + chara->getRadius() + player.getRadius() * 0.5f;
|
|
|
|
|
|
|
|
if( ( Common::Util::distance( player.getPos(), actor->getPos() ) <= range ) &&
|
|
|
|
( ( tickCount - player.getLastAttack() ) > mainWeap->getDelay() ) )
|
2023-02-17 15:34:51 +01:00
|
|
|
{
|
2023-03-07 15:08:28 +01:00
|
|
|
player.setLastAttack( tickCount );
|
|
|
|
player.autoAttack( actor->getAsChara() );
|
2023-02-10 21:28:34 +01:00
|
|
|
}
|
|
|
|
}
|
2023-03-07 15:08:28 +01:00
|
|
|
|
2023-02-10 21:28:34 +01:00
|
|
|
}
|
|
|
|
|
2025-01-02 09:22:10 +01:00
|
|
|
void PlayerMgr::onGainExp( Entity::Player& player, uint32_t exp )
|
|
|
|
{
|
|
|
|
uint32_t currentExp = player.getCurrentExp();
|
|
|
|
uint16_t level = player.getLevel();
|
|
|
|
auto currentClass = static_cast< uint8_t >( player.getClass() );
|
|
|
|
|
|
|
|
if( level >= Common::MAX_PLAYER_LEVEL )
|
|
|
|
{
|
|
|
|
player.setCurrentExp( 0 );
|
|
|
|
if( currentExp != 0 )
|
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), UpdateUiExp, currentClass, 0 );
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
|
|
|
|
|
|
uint32_t neededExpToLevel = exdData.getRow< Excel::ParamGrow >( level )->data().NextExp;
|
|
|
|
uint32_t neededExpToLevelPlus1 = exdData.getRow< Excel::ParamGrow >( level + 1 )->data().NextExp;
|
|
|
|
|
|
|
|
if( ( currentExp + exp ) >= neededExpToLevel )
|
|
|
|
{
|
|
|
|
// levelup
|
|
|
|
exp = ( currentExp + exp - neededExpToLevel ) > neededExpToLevelPlus1 ? neededExpToLevelPlus1 - 1 : ( currentExp + exp - neededExpToLevel );
|
|
|
|
|
|
|
|
if( level + 1 >= Common::MAX_PLAYER_LEVEL )
|
|
|
|
exp = 0;
|
2025-01-02 13:25:18 +01:00
|
|
|
else
|
|
|
|
onLevelChanged( player, level + 1 );
|
2025-01-02 09:22:10 +01:00
|
|
|
|
|
|
|
player.setCurrentExp( exp );
|
2025-01-02 13:25:18 +01:00
|
|
|
|
2025-01-02 09:22:10 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
player.setCurrentExp( currentExp + exp );
|
|
|
|
|
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), GainExpMsg, currentClass, exp );
|
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), UpdateUiExp, currentClass, player.getCurrentExp() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void PlayerMgr::onDiscoverArea( Entity::Player& player, int16_t mapId, int16_t subId )
|
|
|
|
{
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
|
|
|
|
|
|
int32_t offset;
|
|
|
|
|
|
|
|
auto info = exdData.getRow< Excel::Map >( mapId );
|
|
|
|
if( !info )
|
|
|
|
{
|
|
|
|
sendDebug( player, "discover(): Could not obtain map data for map_id == {0}", mapId );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto& mapData = info->data();
|
|
|
|
|
|
|
|
if( mapData.IsUint16Discovery )
|
|
|
|
offset = 2 * mapData.DiscoveryIndex;
|
|
|
|
else
|
|
|
|
offset = 320 + 4 * mapData.DiscoveryIndex;
|
|
|
|
|
|
|
|
uint16_t index;
|
|
|
|
uint8_t value;
|
|
|
|
Common::Util::valueToFlagByteIndexValue( subId, value, index );
|
|
|
|
|
|
|
|
auto& discovery = player.getDiscoveryBitmask();
|
|
|
|
|
|
|
|
discovery[ offset + index ] |= value;
|
|
|
|
|
|
|
|
uint16_t level = player.getLevel();
|
|
|
|
|
|
|
|
uint32_t exp = ( exdData.getRow< Excel::ParamGrow >( level )->data().NextExp * 5 / 100 );
|
|
|
|
onGainExp( player, exp );
|
|
|
|
|
|
|
|
// gain 10x additional EXP if entire map is completed
|
|
|
|
uint32_t mask = mapData.DiscoveryFlag;
|
|
|
|
uint32_t discoveredAreas;
|
|
|
|
if( info->data().IsUint16Discovery )
|
|
|
|
{
|
|
|
|
discoveredAreas = ( discovery[ offset + 1 ] << 8 ) | discovery[ offset ];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
discoveredAreas = ( discovery[ offset + 3 ] << 24 ) |
|
|
|
|
( discovery[ offset + 2 ] << 16 ) |
|
|
|
|
( discovery[ offset + 1 ] << 8 ) |
|
|
|
|
discovery[ offset ];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool allDiscovered = ( ( discoveredAreas & mask ) == mask );
|
|
|
|
|
|
|
|
if( allDiscovered )
|
|
|
|
{
|
|
|
|
onGainExp( player, exp * 10 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-10 21:28:34 +01:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
////////// Helper ///////////
|
|
|
|
|
2022-01-27 21:08:43 +01:00
|
|
|
void PlayerMgr::sendServerNotice( Entity::Player& player, const std::string& message ) //Purple Text
|
2021-11-27 00:53:57 +01:00
|
|
|
{
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendServerNotice( player, message );
|
2021-11-27 00:53:57 +01:00
|
|
|
}
|
|
|
|
|
2022-01-27 21:08:43 +01:00
|
|
|
void PlayerMgr::sendUrgent( Entity::Player& player, const std::string& message ) //Red Text
|
2021-11-27 00:53:57 +01:00
|
|
|
{
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendChat( player, Common::ChatType::ServerUrgent, message );
|
2021-11-27 00:53:57 +01:00
|
|
|
}
|
|
|
|
|
2022-01-27 21:08:43 +01:00
|
|
|
void PlayerMgr::sendDebug( Entity::Player& player, const std::string& message ) //Grey Text
|
2021-11-27 00:53:57 +01:00
|
|
|
{
|
2023-03-07 10:25:55 +01:00
|
|
|
Network::Util::Packet::sendChat( player, Common::ChatType::SystemMessage, message );
|
2021-11-27 00:53:57 +01:00
|
|
|
}
|
|
|
|
|
2022-01-27 21:08:43 +01:00
|
|
|
void PlayerMgr::sendLogMessage( Entity::Player& player, uint32_t messageId, uint32_t param2, uint32_t param3,
|
2021-11-27 00:53:57 +01:00
|
|
|
uint32_t param4, uint32_t param5, uint32_t param6 )
|
|
|
|
{
|
2023-03-09 21:54:30 +01:00
|
|
|
Network::Util::Packet::sendActorControlTarget( player, player.getId(), LogMsg, messageId, param2, param3, param4, param5, param6 );
|
2018-12-22 22:25:03 +01:00
|
|
|
}
|
2025-01-02 09:22:10 +01:00
|
|
|
|
|
|
|
void PlayerMgr::onUpdateHuntingLog( Entity::Player& player, uint8_t id )
|
|
|
|
{
|
|
|
|
std::vector< uint32_t > rankRewards{ 2500, 10000, 20000, 30000, 40000 };
|
|
|
|
const auto maxRank = 4;
|
|
|
|
auto& pExdData = Common::Service< Data::ExdData >::ref();
|
|
|
|
|
|
|
|
// make sure we get the matching base-class if a job is being used
|
|
|
|
auto classJobInfo = pExdData.getRow< Excel::ClassJob >( static_cast< uint8_t >( player.getClass() ) );
|
|
|
|
if( !classJobInfo )
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto currentClassId = classJobInfo->data().MainClass;
|
|
|
|
|
|
|
|
auto& logEntry = player.getHuntingLogEntry( currentClassId - 1 );
|
|
|
|
|
|
|
|
bool logChanged = false;
|
|
|
|
|
|
|
|
|
|
|
|
bool allSectionsComplete = true;
|
|
|
|
for( int i = 1; i <= 10; ++i )
|
|
|
|
{
|
|
|
|
bool sectionComplete = true;
|
|
|
|
bool sectionChanged = false;
|
|
|
|
auto monsterNoteId = static_cast< uint32_t >( classJobInfo->data().MainClass * 10000 + logEntry.rank * 10 + i );
|
|
|
|
auto note = pExdData.getRow< Excel::MonsterNote >( monsterNoteId );
|
|
|
|
|
|
|
|
// for classes that don't have entries, if the first fails the rest will fail
|
|
|
|
if( !note )
|
|
|
|
break;
|
|
|
|
|
|
|
|
for( auto x = 0; x < 4; ++x )
|
|
|
|
{
|
|
|
|
auto note1 = pExdData.getRow< Excel::MonsterNoteTarget >( note->data().Target[ x ] );
|
|
|
|
if( note1->data().Monster == id && logEntry.entries[ i - 1 ][ x ] < note->data().NeededKills[ x ] )
|
|
|
|
{
|
|
|
|
logEntry.entries[ i - 1 ][ x ]++;
|
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), HuntingLogEntryUpdate, monsterNoteId, x, logEntry.entries[ i - 1 ][ x ] );
|
|
|
|
logChanged = true;
|
|
|
|
sectionChanged = true;
|
|
|
|
}
|
|
|
|
if( logEntry.entries[ i - 1 ][ x ] != note->data().NeededKills[ x ] )
|
|
|
|
sectionComplete = false;
|
|
|
|
}
|
|
|
|
if( logChanged && sectionComplete && sectionChanged )
|
|
|
|
{
|
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), HuntingLogSectionFinish, monsterNoteId, i, 0 );
|
|
|
|
onGainExp( player, note->data().RewardExp );
|
|
|
|
}
|
|
|
|
if( !sectionComplete )
|
|
|
|
{
|
|
|
|
allSectionsComplete = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( logChanged && allSectionsComplete )
|
|
|
|
{
|
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), HuntingLogRankFinish, 4 );
|
|
|
|
onGainExp( player, rankRewards[ logEntry.rank ] );
|
|
|
|
if( logEntry.rank < 4 )
|
|
|
|
{
|
|
|
|
logEntry.rank++;
|
|
|
|
memset( logEntry.entries, 0, 40 );
|
|
|
|
Network::Util::Packet::sendActorControlSelf( player, player.getId(), HuntingLogRankUnlock, currentClassId, logEntry.rank + 1, 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( logChanged )
|
|
|
|
Network::Util::Packet::sendHuntingLog( player );
|
|
|
|
}
|
|
|
|
|
2025-01-02 09:48:51 +01:00
|
|
|
void PlayerMgr::onExitInstance( Entity::Player& player )
|
|
|
|
{
|
|
|
|
auto& warpMgr = Common::Service< WarpMgr >::ref();
|
|
|
|
|
|
|
|
player.resetHp();
|
|
|
|
player.resetMp();
|
|
|
|
|
|
|
|
warpMgr.requestMoveTerritory( player, Common::WarpType::WARP_TYPE_CONTENT_END_RETURN,
|
|
|
|
player.getPrevTerritoryId(), player.getPrevPos(), player.getPrevRot() );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2025-01-02 13:25:18 +01:00
|
|
|
void PlayerMgr::onClassJobChanged( Entity::Player& player, Common::ClassJob classJob )
|
|
|
|
{
|
|
|
|
player.setClassJob( classJob );
|
|
|
|
if( player.getHp() > player.getMaxHp() )
|
|
|
|
player.setHp( player.getMaxHp() );
|
|
|
|
|
|
|
|
if( player.getMp() > player.getMaxMp() )
|
|
|
|
player.setMp( player.getMaxMp() );
|
|
|
|
|
|
|
|
player.setTp( 0 );
|
|
|
|
|
|
|
|
Network::Util::Packet::sendChangeClass( player );
|
|
|
|
Network::Util::Packet::sendStatusUpdate( player );
|
|
|
|
Network::Util::Packet::sendActorControl( player.getInRangePlayerIds( true ), player.getId(), ClassJobChange, 4 );
|
|
|
|
Network::Util::Packet::sendHudParam( player );
|
|
|
|
Common::Service< World::Manager::MapMgr >::ref().updateQuests( player );
|
|
|
|
}
|
|
|
|
|
|
|
|
void PlayerMgr::onLevelChanged( Entity::Player& player, uint8_t level )
|
|
|
|
{
|
|
|
|
player.setLevel( level );
|
|
|
|
player.calculateStats();
|
|
|
|
|
|
|
|
player.setHp( player.getMaxHp() );
|
|
|
|
player.setMp( player.getMaxMp() );
|
|
|
|
Network::Util::Packet::sendBaseParams( player );
|
|
|
|
Network::Util::Packet::sendHudParam( player );
|
|
|
|
Network::Util::Packet::sendStatusUpdate( player );
|
|
|
|
Network::Util::Packet::sendActorControl( player.getInRangePlayerIds( true ), player.getId(), LevelUpEffect, static_cast< uint8_t >( player.getClass() ), player.getLevel(), player.getLevel() - 1 );
|
|
|
|
|
|
|
|
auto& achvMgr = Common::Service< World::Manager::AchievementMgr >::ref();
|
|
|
|
achvMgr.progressAchievementByType< Common::Achievement::Type::Classjob >( player, static_cast< uint32_t >( player.getClass() ) );
|
|
|
|
Common::Service< World::Manager::MapMgr >::ref().updateQuests( player );
|
|
|
|
}
|
|
|
|
|
2025-01-02 09:22:10 +01:00
|
|
|
|
|
|
|
|