// *************************************************************************** // // Reality - The Matrix Online Server Emulator // Copyright (C) 2006-2010 Rajko Stojadinovic // http://mxoemu.info // // --------------------------------------------------------------------------- // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // // --------------------------------------------------------------------------- // // *************************************************************************** #include "Common.h" #include "PlayerObject.h" #include "RsiData.h" #include "Database/Database.h" #include "GameServer.h" #include "MessageTypes.h" #include "Log.h" #include "GameClient.h" #include "Timer.h" #include PlayerObject::PlayerObject( GameClient &parent,uint64 charUID ) :m_parent(parent),m_characterUID(charUID),m_spawnedInWorld(false),m_worldPopulated(false) { loadFromDB(true); m_goId=0; INFO_LOG(format("Player object for %1% constructed") % m_handle); testCount=0; m_lastStore = getTime(); m_storeCntr = 0; m_currAnimation=0; m_currMood=0; m_emoteCounter=0; setOnlineStatus(true); } void PlayerObject::loadFromDB( bool updatePos ) { //grab data from characters table { format sql = format("SELECT `handle`, `firstName`, `lastName`, `background`,\ `x`, `y`, `z`, `rot`, \ `healthC`, `healthM`, `innerStrC`, `innerStrM`,\ `level`, `profession`, `alignment`, `pvpflag`, `exp`, `cash`, `district`, `adminFlags`\ FROM `characters` WHERE `charId` = '%1%' LIMIT 1") % m_characterUID; scoped_ptr result(sDatabase.Query(sql)); if (!result) throw CharacterNotFound(); Field *field = result->Fetch(); if (field[0].GetString() != NULL) m_handle = field[0].GetString(); else throw CharacterNotFound(); if (field[1].GetString() != NULL) m_firstName = field[1].GetString(); else m_firstName = "NOFIRST"; if (field[2].GetString() != NULL) m_lastName = field[2].GetString(); else m_lastName = "NOLAST"; if (field[3].GetString() != NULL) m_background = field[3].GetString(); else m_background = ""; if (updatePos) { m_pos.ChangeCoords( field[4].GetDouble(), field[5].GetDouble(), field[6].GetDouble()); m_pos.rot = field[7].GetDouble(); m_savedPos = m_pos; } m_healthC = field[8].GetUInt16(); m_healthM = field[9].GetUInt16(); m_innerStrC = field[10].GetUInt16(); m_innerStrM = field[11].GetUInt16(); m_lvl = field[12].GetUInt8(); m_prof = field[13].GetUInt32(); m_alignment = field[14].GetUInt8(); m_pvpflag = field[15].GetBool(); m_exp = field[16].GetUInt64(); m_cash = field[17].GetUInt64(); m_district = field[18].GetUInt8(); m_isAdmin = field[19].GetBool(); } //grab data from rsi table { scoped_ptr result(sDatabase.Query(format("SELECT `sex`, `body`, `hat`, `face`, `shirt`,\ `coat`, `pants`, `shoes`, `gloves`, `glasses`,\ `hair`, `facialdetail`, `shirtcolor`, `pantscolor`,\ `coatcolor`, `shoecolor`, `glassescolor`, `haircolor`,\ `skintone`, `tattoo`, `facialdetailcolor`, `leggings` FROM `rsivalues` WHERE `charId` = '%1%' LIMIT 1") % m_characterUID) ); if (result == NULL) { INFO_LOG(format("SpawnRSI(%1%): Character's RSI doesn't exist") % m_handle ); m_rsi.reset(new RsiDataMale); const byte defaultRsiValues[] = {0x00,0x0C,0x71,0x48,0x18,0x0C,0xE2,0x00,0x23,0x00,0xB0,0x00,0x40,0x00,0x00}; m_rsi->FromBytes(defaultRsiValues,sizeof(defaultRsiValues)); } else { Field *field = result->Fetch(); uint8 sex = field[0].GetUInt8(); if (sex == 0) //male m_rsi.reset(new RsiDataMale); else m_rsi.reset(new RsiDataFemale); RsiData &playerRef = *m_rsi; if (sex == 0) //male playerRef["Sex"]=0; else playerRef["Sex"]=1; playerRef["Body"] = field[1].GetUInt8(); playerRef["Hat"] = field[2].GetUInt8(); playerRef["Face"] = field[3].GetUInt8(); playerRef["Shirt"] = field[4].GetUInt8(); playerRef["Coat"] = field[5].GetUInt8(); playerRef["Pants"] = field[6].GetUInt8(); playerRef["Shoes"] = field[7].GetUInt8(); playerRef["Gloves"] = field[8].GetUInt8(); playerRef["Glasses"] = field[9].GetUInt8(); playerRef["Hair"] = field[10].GetUInt8(); playerRef["FacialDetail"]= field[11].GetUInt8(); playerRef["ShirtColor"] = field[12].GetUInt8(); playerRef["PantsColor"] = field[13].GetUInt8(); playerRef["CoatColor"] = field[14].GetUInt8(); playerRef["ShoeColor"] = field[15].GetUInt8(); playerRef["GlassesColor"]= field[16].GetUInt8(); playerRef["HairColor"] = field[17].GetUInt8(); playerRef["SkinTone"] = field[18].GetUInt8(); playerRef["Tattoo"] = field[19].GetUInt8(); playerRef["FacialDetailColor"] = field[20].GetUInt8(); if (sex != 0) playerRef["Leggings"] = field[21].GetUInt8(); } } } void PlayerObject::initGoId(uint32 theGoId) { m_goId = theGoId; INFO_LOG(format("Player name %1% has goid %2%") % m_handle % m_goId); m_parent.QueueCommand(make_shared((format("Your Object Id is %1%")%m_goId).str())); sGame.AnnounceCommand(&m_parent,make_shared((format("Player %1% connected with object id %2%")%m_handle%m_goId).str())); } PlayerObject::~PlayerObject() { if (m_spawnedInWorld == true) { //commit position changes saveDataToDB(); setOnlineStatus(false); INFO_LOG(format("Player object for %1%:%2% deconstructing") % m_handle % m_goId); sGame.AnnounceStateUpdate(&m_parent,make_shared(m_goId)); sGame.AnnounceCommand(&m_parent,make_shared((format("Player %1% with object id %2% disconnected")%m_handle%m_goId).str())); m_spawnedInWorld=false; } } uint8 PlayerObject::getRsiData( byte* outputBuf, size_t maxBufLen ) const { if (m_rsi == NULL) return 0; return m_rsi->ToBytes(outputBuf,maxBufLen); } void PlayerObject::checkAndStore() { if (getTime() - m_lastStore > 10) //every 10 seconds { saveDataToDB(); m_lastStore = getTime(); } } void PlayerObject::saveDataToDB() { if (m_savedPos == m_pos) return setOnlineStatus(true); bool storeSuccess = sDatabase.Execute(format("UPDATE `characters` SET `x` = '%1%', `y` = '%2%', `z` = '%3%', `rot` = '%4%', `lastOnline` = NOW() WHERE `charId` = '%5%'") % m_pos.x % m_pos.y % m_pos.z % m_pos.rot % m_characterUID ); if (!storeSuccess) WARNING_LOG(format("%1%:%2% failed to save data to database") % m_handle % m_goId ); else { m_savedPos = m_pos; if (m_storeCntr >= 10) { m_parent.QueueCommand(make_shared( (format("Character data for %1% has been written to the database.") % m_handle).str() )); m_storeCntr=0; } m_storeCntr++; } } void PlayerObject::setOnlineStatus( bool isOnline ) { sDatabase.Execute(format("UPDATE `characters` SET `lastOnline` = NOW(), `isOnline` = '%1%' WHERE `charId` = '%2%'") % int(isOnline) % m_characterUID ); } void PlayerObject::InitializeWorld() { m_parent.QueueCommand(make_shared((LoadWorldCmd::mxoLocation)m_district,"Massive")); m_parent.QueueCommand(make_shared(m_exp)); m_parent.QueueCommand(make_shared(m_cash)); /* m_parent.QueueCommand(make_shared("80b24e0008000802")); m_parent.QueueCommand(make_shared("80b2520005000802")); m_parent.QueueCommand(make_shared("80b2540008000802")); m_parent.QueueCommand(make_shared("80b24f0008000802")); m_parent.QueueCommand(make_shared("80b251000b000802")); m_parent.QueueCommand(make_shared("80b2110001000802")); m_parent.QueueCommand(make_shared("80bc4503110000020000001100010000000000000000")); m_parent.QueueCommand(make_shared("80bc450002000002000000cc00000000000000000000")); m_parent.QueueCommand(make_shared("80bc1500030000f70300000802000000000000000000")); m_parent.QueueCommand(make_shared("80bc1500040000f70300000702ecffffff0000000000")); m_parent.QueueCommand(make_shared("80bc1500050000f70300005004000000000000000000")); m_parent.QueueCommand(make_shared("80bc1500060000f7030000f403000000000000000000")); m_parent.QueueCommand(make_shared("80bc1500070000f70300005104f6ffffff0000000000")); m_parent.QueueCommand(make_shared("80bc1500080000f703000052040f0000000000000000")); m_parent.QueueCommand(make_shared("47000004010000000000000000000000000000001a0006000000010000000001010000000000800000000000000000")); m_parent.QueueCommand(make_shared("2e0700000000000000000000005900002e00000000000000000000000000000000000000")); m_parent.QueueCommand(make_shared("80865220060000000000000000000000000000000000000000210000000000230000000000"));*/ m_parent.QueueCommand(make_shared("http://mxoemu.info/forum/index.php")); m_parent.QueueCommand(make_shared("{c:FF0000}W{/c}{c:DD0000}e{/c}{c:EE0000}l{/c}{c:CC0000}c{/c}{c:BB0000}o{/c}{c:AA0000}m{/c}{c:990000}e{/c} {c:880000}B{/c}{c:770000}a{/c}{c:660000}c{/c}{c:550000}k{/c}{c:440000}.{/c}{c:330000}.{/c}{c:220000}.{/c}")); } void PlayerObject::UpdateAppearance() { loadFromDB(false); sGame.AnnounceStateUpdate(NULL,make_shared(m_goId)); } void PlayerObject::SpawnSelf() { if (m_spawnedInWorld == false) { shared_ptr dMsg = make_shared(m_goId); m_parent.QueueState(dMsg,false,boost::bind(&PlayerObject::PopulateWorld,this)); sGame.AnnounceStateUpdate(&m_parent,dMsg); m_spawnedInWorld=true; } } void PlayerObject::PopulateWorld() { if (m_worldPopulated) return; //we need to get all other world entities and populate our client with it vector allWorldObjects = sObjMgr.getAllGOIds(); for (vector::iterator it=allWorldObjects.begin();it!=allWorldObjects.end();++it) { PlayerObject *theOtherObject = NULL; try { theOtherObject = sObjMgr.getGOPtr(*it);; } catch (ObjectMgr::ObjectNotAvailable) { continue; } //we self spawned already, so no if (theOtherObject!=this) { vector objectsPackets = theOtherObject->getCurrentStatePackets(); for (vector::iterator it2=objectsPackets.begin();it2!=objectsPackets.end();++it2) { if (m_spawnedInWorld) m_parent.QueueState(*it2); else m_sendAfterSpawn.push(*it2); } } } //open doors that are opened vector openedDoorPackets = sObjMgr.GetAllOpenDoors(&m_parent); for (vector::iterator it=openedDoorPackets.begin();it!=openedDoorPackets.end();++it) { if (m_spawnedInWorld) m_parent.QueueState(*it); else m_sendAfterSpawn.push(*it); } m_worldPopulated=true; } void PlayerObject::HandleStateUpdate( ByteBuffer &srcData ) { /* testCount++; m_parent.QueueCommand(make_shared( (format("CMD %1%")%testCount).str() ));*/ uint8 zeroThree; if (srcData.remaining() < sizeof(zeroThree)) return; srcData >> zeroThree; if (zeroThree != 3) return; uint16 viewIdToUpdate; if (srcData.remaining() < sizeof(viewIdToUpdate)) return; srcData >> viewIdToUpdate; if (viewIdToUpdate != sObjMgr.getViewForGO(&m_parent,m_goId)) { WARNING_LOG(format("Client %1% Player %2%:%3% trying to update someone else's object view %4%") % m_parent.Address() % m_handle % m_goId % viewIdToUpdate); return; } size_t restOfDataPos = srcData.rpos(); uint8 shouldBeOne; if (srcData.remaining() < sizeof(shouldBeOne)) return; srcData >> shouldBeOne; if (shouldBeOne != 1) { WARNING_LOG(format("Client %1% Player %2%:%3% 03 doesn't have number 1 after viewId, packet: %4%") % m_parent.Address() % m_handle % m_goId % Bin2Hex(srcData)); return; } uint8 updateType; if (srcData.remaining() < sizeof(updateType)) return; srcData >> updateType; bool validUpdate=false; bool movementUpdate=false; switch (updateType) { //change angle case 0x04: { movementUpdate=true; uint8 theRotByte; if (srcData.remaining() < sizeof(theRotByte)) return; srcData >> theRotByte; m_pos.setMxoRot(theRotByte); validUpdate=true; break; } //change angle with extra param case 0x06: { movementUpdate=true; uint8 theAnimation; if (srcData.remaining() < sizeof(theAnimation)) return; srcData >> theAnimation; //we will just ignore the animation for now uint8 theRotByte; if (srcData.remaining() < sizeof(theRotByte)) return; srcData >> theRotByte; m_pos.setMxoRot(theRotByte); validUpdate=true; break; } //update xyz case 0x08: { movementUpdate=true; validUpdate = m_pos.fromFloatBuf(srcData); break; } //update xyz, extra byte before xyz case 0x0A: case 0x0C: { movementUpdate=true; uint8 extraByte; if (srcData.remaining() < sizeof(extraByte)) return; srcData >> extraByte; validUpdate = m_pos.fromFloatBuf(srcData); break; } //update xyz, extra 2 bytes before xyz case 0x0E: { movementUpdate=true; uint8 extraByte1,extraByte2; if (srcData.remaining() < sizeof(uint8)*2) return; srcData >> extraByte1; srcData >> extraByte2; validUpdate = m_pos.fromFloatBuf(srcData); break; } //sometimes happens, no info inside case 0x02: { validUpdate = true; break; } } if (validUpdate) { if(movementUpdate) { size_t cancelled = this->cancelEvents(EVENT_JACKOUT); if (cancelled > 0) { m_parent.QueueCommand(make_shared("Jackout cancelled.")); m_parent.QueueState(make_shared(m_goId,false)); } } //propagate state to all other players srcData.rpos(restOfDataPos); ByteBuffer theStateData; theStateData.append(&srcData.contents()[srcData.rpos()],srcData.remaining()); //m_parent.QueueState(make_shared(m_goId,theStateData)); sGame.AnnounceStateUpdate(&m_parent,make_shared(m_goId,theStateData),true); } else { srcData.rpos(0); DEBUG_LOG(format("(%1%) %2%:%3% 03 data: %4%") % m_parent.Address() % m_handle % m_goId % Bin2Hex(srcData) ); } } #include using boost::iequals; void PlayerObject::HandleCommand( ByteBuffer &srcCmd ) { // DEBUG_LOG(format(" HandleCommand: %1%")% Bin2Hex(srcCmd) ); //set up handler ptrs if (!m_RPCbyte.size()) { m_RPCbyte[0x05] = &PlayerObject::RPC_HandleReadyForSpawn; m_RPCbyte[0x33] = &PlayerObject::RPC_HandleStopAnimation; m_RPCbyte[0x34] = &PlayerObject::RPC_HandleStartAnimtion; m_RPCbyte[0x35] = &PlayerObject::RPC_HandleChangeMood; m_RPCbyte[0x30] = &PlayerObject::RPC_HandlePerformEmote; } if (!m_RPCshort.size()) { m_RPCshort[0x2810] = &PlayerObject::RPC_HandleChat; m_RPCshort[0x2907] = &PlayerObject::RPC_HandleWhisper; m_RPCshort[0x80c7] = &PlayerObject::RPC_HandleDynamicObjInteraction; m_RPCshort[0x80c8] = &PlayerObject::RPC_HandleStaticObjInteraction; m_RPCshort[0x80c2] = &PlayerObject::RPC_HandleJump; m_RPCshort[0x80c9] = &PlayerObject::RPC_HandleRegionLoadedNotification; m_RPCshort[0x8108] = &PlayerObject::RPC_HandleReadyForWorldChange; m_RPCshort[0x8152] = &PlayerObject::RPC_HandleWho; m_RPCshort[0x8154] = &PlayerObject::RPC_HandleWhereAmI; m_RPCshort[0x8192] = &PlayerObject::RPC_HandleGetPlayerDetails; m_RPCshort[0x8194] = &PlayerObject::RPC_HandleGetBackground; m_RPCshort[0x8196] = &PlayerObject::RPC_HandleSetBackground; m_RPCshort[0x818e] = &PlayerObject::RPC_HandleHardlineTeleport; m_RPCshort[0x8151] = &PlayerObject::RPC_HandleObjectSelected; m_RPCshort[0x80fc] = &PlayerObject::RPC_HandleJackoutRequest; m_RPCshort[0x80fe] = &PlayerObject::RPC_HandleJackoutFinished; } uint8 firstByte = srcCmd.read(); try { if (m_RPCbyte.count(firstByte)) { CALL_METHOD_PTR(this,m_RPCbyte[firstByte])(srcCmd); return; } else { if (srcCmd.remaining()) { uint8 secondByte = srcCmd.read(); uint16 shortCommand = (uint16(firstByte) << 8) | (secondByte & 0xFF); if (m_RPCshort.count(shortCommand)) { CALL_METHOD_PTR(this,m_RPCshort[shortCommand])(srcCmd); return; } } } } catch ( ByteBuffer::out_of_range ) { srcCmd.rpos(0); DEBUG_LOG(format("(%1%) Out of range error processing RPC data: %2%") % m_parent.Address() % Bin2Hex(srcCmd) ); return; } srcCmd.rpos(0); DEBUG_LOG(format("(%1%) unhandled RPC data: %2%") % m_parent.Address() % Bin2Hex(srcCmd) ); } bool PlayerObject::setBackground(string newBackground) { m_background = newBackground; return sDatabase.Execute(format("UPDATE `characters` SET `background` = '%1%' WHERE `charId` = '%2%'") % sDatabase.EscapeString(this->getBackground()) % m_characterUID ); } vector PlayerObject::getCurrentStatePackets() { vector tempVect; tempVect.push_back(make_shared(m_goId)); if (m_currAnimation != 0 || m_currMood != 0) { tempVect.push_back(make_shared(m_goId)); } return tempVect; } void PlayerObject::GoAhead(double distanceToGo) { //double angle = this->getPosition().getMxoRot(); //string debugMsg = (format("X:%1% Y:%2% Z:%3% Rotation:%4% Angle:%5%") % this->getPosition().x % this->getPosition().y % this->getPosition().z % this->getPosition().rot % angle).str(); //this->getClient().QueueCommand(make_shared("TW",debugMsg)); LocationVector newLoc = this->getPosition(); double xInc = 0; double zInc = 0; double sAngle = sin(newLoc.rot); xInc = distanceToGo * sAngle; zInc = sqrt(distanceToGo * distanceToGo - xInc * xInc); xInc *= 100; zInc *= 100; newLoc.x -= xInc; if (abs(newLoc.rot) > M_PI/2) { newLoc.z += zInc; } else { newLoc.z -= zInc; } this->setPosition(newLoc); sGame.AnnounceStateUpdate(NULL,make_shared(m_goId),true); } void PlayerObject::Update() { if (m_spawnedInWorld) { //flush any updates that queued up while we were spawning while(m_sendAfterSpawn.size()) { m_parent.QueueState(m_sendAfterSpawn.front()); m_sendAfterSpawn.pop(); } checkAndStore(); //fire events that occurred for(list::iterator it=m_events.begin();it!=m_events.end();) { if (getFloatTime() >= it->fireTime) { it->func(); it=m_events.erase(it); } else { ++it; } } } } void PlayerObject::addEvent( eventType type, eventFunc func, float activationTime ) { m_events.push_back(eventStruct(type,func,getFloatTime()+activationTime)); } size_t PlayerObject::cancelEvents( eventType type ) { size_t cancelledEvents=0; for(list::iterator it=m_events.begin();it!=m_events.end();) { if (it->type == type) { it = m_events.erase(it); cancelledEvents++; } else { ++it; } } return cancelledEvents; }