#include "uox3.h" #include "cVersionClass.h" #include "cSkillClass.h" #include "cServerDefinitions.h" #include "wholist.h" #include "skills.h" #include "ssection.h" #include "CJSMapping.h" #include "cScript.h" #include "CPacketSend.h" #include "PartySystem.h" #include "classes.h" #include "townregion.h" #include "Dictionary.h" #include "cEffects.h" #include "cRaces.h" #include "StringUtility.hpp" #include "cServerData.h" #include "ObjectFactory.h" #include using namespace std::string_literals; template< class T > T Capped( const T value, const T minimum, const T maximum ) { return std::max( std::min( value, maximum ), minimum ); } //o------------------------------------------------------------------------------------------------o //| Function - ValidHairStyle() //o------------------------------------------------------------------------------------------------o //| Purpose - Checks if selected hair is a valid hair type //o------------------------------------------------------------------------------------------------o bool ValidHairStyle( UI16 id, UI16 bodyId ) { bool rValue = false; switch( bodyId ) { case 0x025D: // elven male switch( id ) { case 0x2FBF: // mid long (male) case 0x2FC0: // long feather (both) case 0x2FC1: // short (both) case 0x2FC2: // mullet (both) case 0x2FCD: // long (male) case 0x2FCE: // topknot (both) case 0x2FCF: // long braid (both) case 0x2FD1: // spiked (male) rValue = true; break; default: break; } break; case 0x025E: // elven female switch( id ) { case 0x2FC0: // long feather (both) case 0x2FC1: // short (both) case 0x2FC2: // mullet (both) case 0x2FCC: // flower (female) case 0x2FCE: // topknot (both) case 0x2FCF: // long braid (both) case 0x2FD0: // buns (female) case 0x2FD1: // spiked (male) rValue = true; break; default: break; } break; case 0x029A: // gargoyle male switch( id ) { case 0x4258: // case 0x4259: // case 0x425A: // case 0x425B: // case 0x425C: // case 0x425D: // case 0x425E: // case 0x425F: // rValue = true; break; default: break; } break; case 0x029B: // gargoyle female switch( id ) { case 0x4261: // case 0x4262: // case 0x4273: // case 0x4274: // case 0x4275: // case 0x4276: // case 0x42AA: // case 0x42AB: // rValue = true; break; default: break; } break; default: // human otherwise switch( id ) { case 0x203B: case 0x203C: case 0x203D: case 0x2044: case 0x2045: case 0x2046: case 0x2047: case 0x2048: case 0x2049: case 0x204A: rValue = true; break; default: break; } break; } return rValue; } //o------------------------------------------------------------------------------------------------o //| Function - ValidBeard() //o------------------------------------------------------------------------------------------------o //| Purpose - Check if selected beard is a valid beard type //| in his pack, on his paperdoll or in his hands //o------------------------------------------------------------------------------------------------o bool ValidBeard( UI16 id, UI16 bodyId ) { bool rValue = false; switch( bodyId ) { case 0x029A: // gargoyle male switch( id ) { case 0x42AD: case 0x42AE: case 0x42AF: //1701? case 0x42B0: rValue = true; break; } break; default: // human male switch( id ) { case 0x203E: case 0x203F: case 0x2040: case 0x2041: case 0x204B: case 0x204C: case 0x204D: rValue = true; break; } } return rValue; } //o------------------------------------------------------------------------------------------------o //| Function - ValidSkinColour() //| Date - 22nd January, 2006 //| Return - A validated skin colour (capped into range) //o------------------------------------------------------------------------------------------------o //| Purpose - Check if selected skin colour is a valid colour, based on //| the body types involved (elves and humans not the same) //o------------------------------------------------------------------------------------------------o COLOUR ValidSkinColour( UI16 id, UI16 bodyId ) { COLOUR rValue; switch( bodyId ) { case 0x025D: // elven male case 0x025E: // elven female switch( id ) // let's see if what is passed in is valid { case 191: case 589: case 590: case 591: case 851: case 865: case 871: case 884: case 885: case 886: case 897: case 898: case 899: case 900: case 901: case 905: case 990: case 997: case 998: case 1000: case 1001: case 1072: case 1191: case 1246: case 1309: case 1343: case 1401: case 1899: case 1900: case 1901: case 2101: case 2307: rValue = id; break; default: rValue = 191; break; // nope, it's not, let's default } break; case 0x029A: case 0x029B: //gargoyle male/female rValue = Capped( id, static_cast( 0x06DB ), static_cast( 0x06F3 )); break; default: // human male/female rValue = Capped( id, static_cast( 0x03EA ), static_cast( 0x0422 )); break; } return rValue; } //o------------------------------------------------------------------------------------------------o //| Function - ValidHairColour() //| Date - 22nd January, 2006 //| Return - A validated hair colour (capped into range) //o------------------------------------------------------------------------------------------------o //| Purpose - Check if selected hair colour is a valid colour, based on //| the body types involved (elves and humans not the same) //o------------------------------------------------------------------------------------------------o COLOUR ValidHairColour( UI16 id, UI16 bodyId ) { COLOUR rValue; switch( bodyId ) { case 0x025D: // elven male case 0x025E: // elven female switch( id ) // let's see if what is passed in is valid { case 52: case 53: case 54: case 55: case 56: case 57: case 88: case 142: case 143: case 144: case 145: case 146: case 257: case 296: case 303: case 345: case 346: case 347: case 348: case 349: case 350: case 445: case 484: case 499: case 519: case 529: case 569: case 593: case 620: case 707: case 713: case 797: case 798: case 799: case 800: case 801: case 802: case 803: case 804: case 805: case 806: case 873: case 902: case 903: case 904: case 905: case 906: case 1437: case 1720: case 1829: case 2131: rValue = id; break; default: rValue = 52; break; // nope, it's not, let's default } break; case 0x29A: // gargoyle male case 0x29B: // gargoyle female // not all colors used, but easier than specialcasing each one rValue = Capped( id, static_cast( 0x06E0 ), static_cast( 0x076B )); break; default: // human male/female rValue = Capped( id, static_cast( 0x044E ), static_cast( 0x04AD )); break; } return rValue; } void StartChar( CSocket *mSock, bool onCreate = false ); //o------------------------------------------------------------------------------------------------o //| Function - CPIPlayCharacter::Handle() //o------------------------------------------------------------------------------------------------o //| Purpose - Called when "Play Character" button is hit //o------------------------------------------------------------------------------------------------o bool CPIPlayCharacter::Handle( void ) { if( tSock != nullptr ) { if( tSock->AcctNo() != AB_INVALID_ID ) { bool disconnect = false; CAccountBlock_st& actbRec = tSock->GetAccount(); CChar *kChar = nullptr; CChar *ourChar = nullptr; if( actbRec.wAccountIndex == AB_INVALID_ID ) { Network->Disconnect( tSock ); disconnect = true; } else { ourChar = actbRec.lpCharacters[slotChosen]; if( ourChar != nullptr ) { if( !ourChar->IsNpc() && !ourChar->IsFree() ) { kChar = ourChar; } } else { Network->Disconnect( tSock ); disconnect = true; } } if( ValidateObject( kChar ) && !disconnect ) { // Added for Krrios client... if( tSock->GetByte( 7 ) == 0xFF ) { // If we dont accept Krrios client we should // disconnect it here now if( kChar->IsGM() ) { CPKAccept AckGM( 0x02 ); tSock->Send( &AckGM ); Console.Print( "Accepted a Krrios client with GM Privs\n" ); } else { CPKAccept AckNoGM( 0x01 ); tSock->Send( &AckNoGM ); Console.Print( "Accepted a Krrios client without GM Privs\n" ); } } if( !disconnect ) { WhoList->FlagUpdate(); OffList->FlagUpdate(); // offline list changes too if( actbRec.dwInGame != INVALIDSERIAL ) { actbRec.dwInGame = kChar->GetSerial(); } else { actbRec.dwInGame = INVALIDSERIAL; } if( actbRec.dwInGame == INVALIDSERIAL || actbRec.dwInGame == kChar->GetSerial() ) { actbRec.dwInGame = kChar->GetSerial(); kChar->SetTimer( tPC_LOGOUT, 0 ); kChar->SetAccount( actbRec ); tSock->CurrcharObj( kChar ); StartChar( tSock ); } else { CPIdleWarning rejSend( 0x05 ); /* 0x53 Client Message: 0x01=Char doesn't exist 0x02=Char Already Exists 0x03,0x04=Cannot Attach to server 0x05=Char in world Msg 0x06=Login server Syc Error 0x07=Idle msg 0x08 (and up?)=Can't Attach */ tSock->Send( &rejSend ); } } } } else { Network->Disconnect( tSock ); } } return true; } //o------------------------------------------------------------------------------------------------o //| Function - CPIDeleteCharacter::Handle() //o------------------------------------------------------------------------------------------------o //| Purpose - Remove a character from the accounts system, due to an account gump button press //o------------------------------------------------------------------------------------------------o bool CPIDeleteCharacter::Handle( void ) { if( tSock != nullptr ) { SI08 deleteResult = -1; CAccountBlock_st * actbTemp = &tSock->GetAccount(); UI08 slot = tSock->GetByte( 0x22 ); if( actbTemp->wAccountIndex != AB_INVALID_ID ) { CChar *ourObj = actbTemp->lpCharacters[slot]; if( ValidateObject( ourObj )) // we have a char { deleteResult = Accounts->DelCharacter( actbTemp->wAccountIndex, slot ); if( deleteResult == CDR_SUCCESS ) { ourObj->Delete(); } } } else { // Support for accounts. The current copy of the account isn't correct. So get a new copy to work with. Accounts->Load(); actbTemp = &Accounts->GetAccountById( actbTemp->wAccountIndex ); } // For modern clients (?) we need to send packets 0x85 and 0x86 here! if( deleteResult != CDR_SUCCESS ) { // 0x85 - DeleteResult - only send if deletion failed! CPCharDeleteResult pckDelResult( static_cast( deleteResult )); tSock->Send( &pckDelResult ); } else { UI08 charCount = 0; for( UI08 i = 0; i < 7; ++i ) { if( ValidateObject( actbTemp->lpCharacters[i] )) { ++charCount; } } // 0x86 - Resend Characters after Delete CharacterListUpdate pckCharList( charCount ); for( UI08 i = 0; i < charCount; ++i ) { if( ValidateObject( actbTemp->lpCharacters[i] )) { pckCharList.AddCharName( i, actbTemp->lpCharacters[i]->GetName() ); } else { pckCharList.AddCharName( i, "" ); } } // If no characters exist, send an empty character list if( charCount == 0 ) { pckCharList.AddCharName( 0, "" ); } tSock->Send( &pckCharList ); } } return true; } //o------------------------------------------------------------------------------------------------o //| Function - AddNewbieItem() //o------------------------------------------------------------------------------------------------o //| Purpose - Adds a newbie item defined in newbie.dfn //o------------------------------------------------------------------------------------------------o //| Changes - PACKITEM now supports item,amount //o------------------------------------------------------------------------------------------------o auto AddNewbieItem( CSocket *socket, CChar *c, const char* str, COLOUR pantsColour, COLOUR shirtColour ) -> void { auto newbieData = FileLookup->FindEntry( str, newbie_def ); if( newbieData == nullptr ) return; CItem *n = nullptr; for( const auto &sec : newbieData->collection() ) { auto tag = sec->tag; auto data = sec->data; data = oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )); if( data.empty() ) continue; std::string nItemSection; UI16 itemHue = 0; UI16 nAmount = 1; SI08 nNewbie = -1; auto UTag = oldstrutil::upper( tag ); if( UTag == "PACKITEM" ) { auto csecs = oldstrutil::sections( data, "," ); if( csecs.size() >= 1 ) { // Fetch item section nItemSection = oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" ) ); if( csecs.size() >= 2 ) { // Fetch amount override nAmount = static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0 )); if( csecs.size() == 3 ) { // Fetch newbie flag override nNewbie = static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[2], "//" )), nullptr, 0 )); } } } else { // No data found! Console.Error( "No valid data found for PACKITEM tag in newbie.dfn!" ); continue; } } else if( UTag == "EQUIPITEM" ) { auto csecs = oldstrutil::sections( data, "," ); if( csecs.size() >= 1 ) { // Fetch item section nItemSection = oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )); if( csecs.size() >= 2 ) { // Fetch item hue itemHue = static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0 )); if( csecs.size() >= 3 ) { // Fetch newbie flag override nNewbie = static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[2], "//" ) ), nullptr, 0 ) ); } } } else { // No data found! Console.Error( "No valid data found for EQUIPITEM tag in newbie.dfn!" ); continue; } } n = Items->CreateScriptItem( socket, c, nItemSection.c_str(), nAmount, OT_ITEM, true, itemHue ); if( n != nullptr && n->GetLayer() != IL_NONE ) { bool conflictItem = true; CItem *j = c->GetItemAtLayer( n->GetLayer() ); if( !ValidateObject( j )) { if( n->GetLayer() == IL_RIGHTHAND ) { j = c->GetItemAtLayer( IL_LEFTHAND ); } else if( n->GetLayer() == IL_LEFTHAND ) { j = c->GetItemAtLayer( IL_RIGHTHAND ); } // GetDir-check is to allow for torches and lanterns, // which use left-hand layer but are not 2-handers or shields if( ValidateObject( j ) && !n->IsShieldType() && ( n->GetDir() == 0 )) { if( j->IsShieldType() || j->GetDir() != 0 ) { conflictItem = false; } } else { conflictItem = false; } } if( conflictItem ) { n->SetCont( c->GetPackItem() ); } else { n->SetCont( c ); } //Apply the choosen colour if(( n->GetLayer() == IL_PANTS || n->GetLayer() == IL_OUTERLEGGINGS ) && pantsColour != 0 ) { n->SetColour( pantsColour ); n->SetDye( true ); } if(( n->GetLayer() == IL_INNERSHIRT || n->GetLayer() == IL_ROBE ) && shirtColour != 0 ) { n->SetColour( shirtColour ); n->SetDye( true ); } } if( n != nullptr && !n->IsPileable() && nNewbie != -1 ) { // Use newbie override flag to determine newbie status of item n->SetNewbie( nNewbie ); } } } //o------------------------------------------------------------------------------------------------o //| Function - NewbieItems() //o------------------------------------------------------------------------------------------------o //| Purpose - Adds newbie items based on characters chosen skills //o------------------------------------------------------------------------------------------------o void CPICreateCharacter::NewbieItems( CChar *mChar ) { enum NewbieItems { HAIR = 0, BEARD, PACK, BANK, GOLD, ITOTAL }; CItem *CreatedItems[ITOTAL] = { nullptr, nullptr, nullptr, nullptr, nullptr }; UI16 ItemId, ItemColour; if( ValidHairStyle( hairStyle, mChar->GetId() )) { ItemId = hairStyle; ItemColour = ValidHairColour( hairColour, mChar->GetId() ); CreatedItems[HAIR] = Items->CreateItem( tSock, mChar, ItemId, 1, ItemColour, OT_ITEM ); if( CreatedItems[HAIR] != nullptr ) { CreatedItems[HAIR]->SetDecayable( false ); CreatedItems[HAIR]->SetLayer( IL_HAIR ); CreatedItems[HAIR]->SetCont( mChar ); mChar->SetHairStyle( ItemId ); mChar->SetHairColour( ItemColour ); } } if( ValidBeard( facialHair, mChar->GetId() ) && ( mChar->GetId() == 0x0190 || mChar->GetId() == 0x029A )) //Male human or male gargoyle { ItemId = facialHair; if( mChar->GetId() == 0x029A ) // gargoyle male { ItemColour = Capped( facialHairColour, static_cast( 0x06E0 ), static_cast( 0x076B )); } else // human male { ItemColour = Capped( facialHairColour, static_cast( 0x044E ), static_cast( 0x04AD )); } CreatedItems[BEARD] = Items->CreateItem( tSock, mChar, ItemId, 1, ItemColour, OT_ITEM ); if( CreatedItems[BEARD] != nullptr ) { CreatedItems[BEARD]->SetDecayable( false ); CreatedItems[BEARD]->SetLayer( IL_FACIALHAIR ); CreatedItems[BEARD]->SetCont( mChar ); mChar->SetBeardStyle( ItemId ); mChar->SetBeardColour( ItemColour ); } } CreatedItems[PACK] = Items->CreateItem( tSock, mChar, 0x0E75, 1, 0, OT_ITEM ); if( CreatedItems[PACK] != nullptr ) { mChar->SetPackItem( CreatedItems[PACK] ); CreatedItems[PACK]->SetWeightMax( cwmWorldState->ServerData()->MaxPlayerPackWeight() ); CreatedItems[PACK]->SetMaxItems( cwmWorldState->ServerData()->MaxPlayerPackItems() ); CreatedItems[PACK]->SetDecayable( false ); CreatedItems[PACK]->SetLayer( IL_PACKITEM ); CreatedItems[PACK]->SetCont( mChar ); CreatedItems[PACK]->SetType( IT_CONTAINER ); CreatedItems[PACK]->SetDye( true ); } CreatedItems[BANK] = Items->CreateItem( tSock, mChar, 0x09AB, 1, 0, OT_ITEM ); if( CreatedItems[BANK] != nullptr ) { CreatedItems[BANK]->SetName( oldstrutil::format(1024,Dictionary->GetEntry( 1283 ), mChar->GetName().c_str() )); CreatedItems[BANK]->SetDecayable( false ); CreatedItems[BANK]->SetLayer( IL_BANKBOX ); CreatedItems[BANK]->SetType( IT_CONTAINER ); CreatedItems[BANK]->SetTempVar( CITV_MOREX, 1 ); CreatedItems[BANK]->SetWeightMax( cwmWorldState->ServerData()->MaxPlayerPackWeight() ); CreatedItems[BANK]->SetMaxItems( cwmWorldState->ServerData()->MaxPlayerBankItems() ); CreatedItems[BANK]->SetOwner( mChar ); CreatedItems[BANK]->SetCont( mChar ); } std::vector vecSkills; std::string whichsect; for( UI08 sCtr = 0; sCtr < ALLSKILLS; ++sCtr ) { vecSkills.push_back( cSkillClass( sCtr, mChar->GetBaseSkill( sCtr ))); } // Give scripted newbie items precedence over default clothing std::sort( vecSkills.rbegin(), vecSkills.rend() ); UI08 numStartSkills = 3; // 0 included if( cwmWorldState->ServerData()->ExtendedStartingSkills() ) { numStartSkills = 4; } for( UI08 i = 0; i < numStartSkills; ++i ) { if( vecSkills[i].value > 0 ) { whichsect = oldstrutil::format( "BESTSKILL %i", vecSkills[i].skill ); AddNewbieItem( tSock, mChar, whichsect.c_str(), 0, 0 ); } } // Add default for all characters AddNewbieItem( tSock, mChar, "DEFAULT ALL", pantsColour, shirtColour ); // Add gender/race-specific items if( mChar->GetId() == 0x0190 ) { AddNewbieItem( tSock, mChar, "DEFAULT MALE", pantsColour, shirtColour ); } else if( mChar->GetId() == 0x0191 ) { AddNewbieItem( tSock, mChar, "DEFAULT FEMALE", pantsColour, shirtColour ); } else if( mChar->GetId() == 0x025D ) { AddNewbieItem( tSock, mChar, "DEFAULT ELF MALE", pantsColour, shirtColour ); } else if( mChar->GetId() == 0x025E ) { AddNewbieItem( tSock, mChar, "DEFAULT ELF FEMALE", pantsColour, shirtColour ); } else if( mChar->GetId() == 0x029A ) { AddNewbieItem( tSock, mChar, "DEFAULT GARG MALE", pantsColour, shirtColour ); } else if( mChar->GetId() == 0x029B ) { AddNewbieItem( tSock, mChar, "DEFAULT GARG FEMALE", pantsColour, shirtColour ); } // Give the character some gold if( cwmWorldState->ServerData()->ServerStartGold() > 0 ) { CreatedItems[GOLD] = Items->CreateScriptItem( tSock, mChar, "0x0EED", cwmWorldState->ServerData()->ServerStartGold(), OT_ITEM, true ); } else { CreatedItems[GOLD] = 0; } } void MakeStatusTarget( CSocket *sock, CChar *optionalTargChar = nullptr, const std::string cmdLvlString = "" ); //o------------------------------------------------------------------------------------------------o //| Function - bool CPICreateCharacter::Handle( void ) //o------------------------------------------------------------------------------------------------o //| Purpose - Character creation stuff //o------------------------------------------------------------------------------------------------o bool CPICreateCharacter::Handle( void ) { // This function needs to be decomposed // Way too large from a maintenance perspective // --> I split certain things out into a few new functions to make this a bit more manageable if( ValidateObject( tSock->CurrcharObj() )) { Network->Disconnect( tSock ); return false; } if( tSock != nullptr ) { CChar *mChar = static_cast( ObjectFactory::GetSingleton().CreateObject( OT_CHAR )); if( mChar != nullptr ) { CPClientVersion verCheck; tSock->Send( &verCheck ); tSock->CurrcharObj( mChar ); mChar->SetName( charName ); Accounts->AddCharacter( tSock->AcctNo(), mChar ); CAccountBlock_st &actbRec = tSock->GetAccount(); if( actbRec.dwInGame != INVALIDSERIAL ) { actbRec.dwInGame = mChar->GetSerial(); } if( actbRec.dwInGame == INVALIDSERIAL || actbRec.dwInGame == mChar->GetSerial() ) { actbRec.dwInGame = mChar->GetSerial(); mChar->SetAccount( actbRec ); } SetNewCharGenderAndRace( mChar ); mChar->SetPriv( cwmWorldState->ServerData()->ServerStartPrivs() ); if( tSock->ClientType() == CV_SA3D || tSock->ClientType() == CV_HS3D ) { locationNumber = clientFlags; } // Fetch player's chosen start location size_t serverCount = 0; __STARTLOCATIONDATA__ *toGo = nullptr; auto useYoungLocations = false; if( cwmWorldState->ServerData()->YoungPlayerSystem() && tSock->GetAccount().wFlags.test( AB_FLAGS_YOUNG ) ) { // Young player! Let's use young player start locations toGo = cwmWorldState->ServerData()->YoungServerLocation( locationNumber ); serverCount = cwmWorldState->ServerData()->NumYoungServerLocations(); useYoungLocations = true; } else { // Use normal player start locations toGo = cwmWorldState->ServerData()->ServerLocation( locationNumber ); serverCount = cwmWorldState->ServerData()->NumServerLocations(); } // Use random start location if enabled in uox.ini if( cwmWorldState->ServerData()->ServerRandomStartingLocation() ) { if( serverCount > 0 ) { size_t rndStartingLocation = RandomNum( static_cast( 0 ), serverCount - 1 ); if( useYoungLocations ) { toGo = cwmWorldState->ServerData()->YoungServerLocation( rndStartingLocation ); } else { toGo = cwmWorldState->ServerData()->ServerLocation( rndStartingLocation ); } } } if( toGo == nullptr ) { // Invalid locationNumber; check if there are ANY start locations loaded if( serverCount == 0 ) { // No start locations found, use a default hardcoded one if( useYoungLocations ) { Console.Error( "No young starting locations found in ini file; sending new character to Sweet Dreams Inn (1495, 1629, 10)." ); } else { Console.Error( "No starting locations found in ini file; sending new character to Sweet Dreams Inn (1495, 1629, 10)." ); } SI16 startX; SI16 startY; SI08 startZ; UI08 startWorld; UI16 startInstanceId; startX = 1495; startY = 1629; startZ = 10; startWorld = 0; startInstanceId = 0; mChar->SetLocation( startX, startY, startZ, startWorld, startInstanceId ); } else { // Use first start location, which we know exists if( useYoungLocations ) { toGo = cwmWorldState->ServerData()->YoungServerLocation( 0 ); } else { toGo = cwmWorldState->ServerData()->ServerLocation( 0 ); } mChar->SetLocation( toGo->x, toGo->y, static_cast( toGo->z ), static_cast( toGo->worldNum ), static_cast( toGo->instanceId )); } } else { mChar->SetLocation( toGo->x, toGo->y, static_cast( toGo->z ), static_cast( toGo->worldNum ), static_cast( toGo->instanceId )); } mChar->SetDir( SOUTH ); SetNewCharSkillsStats( mChar ); NewbieItems( mChar ); CAccountBlock_st& actbTemp2 = mChar->GetAccount(); if( actbTemp2.wAccountIndex != AB_INVALID_ID ) { if( actbTemp2.wFlags.test( AB_FLAGS_GM )) { mChar->SetPriv( 0xFF ); mChar->SetCommandLevel( CL_GM ); } else if( actbTemp2.wFlags.test( AB_FLAGS_SEER )) { MakeStatusTarget( tSock, mChar, "SEER" ); mChar->SetPriv( 0x4F ); mChar->SetCommandLevel( CL_SEER ); } else if( actbTemp2.wFlags.test( AB_FLAGS_COUNSELOR )) { MakeStatusTarget( tSock, mChar, "CNS" ); mChar->SetPriv( 0x38); mChar->SetCommandLevel( CL_CNS ); } } // Added for my client - Krrios if( pattern3 == 0xFF ) // Signal that this is not the standard client { if( mChar->IsGM() ) { CPKAccept AckGM( 0x02 ); tSock->Send( &AckGM ); Console.Print( "Accepted a Krrios client with GM Privs\n" ); } else { CPKAccept AckNoGM( 0x01 ); tSock->Send( &AckNoGM ); Console.Print( "Accepted a Krrios client without GM Privs\n" ); } } // Set character creation timestamp mChar->SetCreatedOn( GetMinutesSinceEpoch() ); StartChar( tSock, true ); } } return true; } //o------------------------------------------------------------------------------------------------o //| Function - CPICreateCharacter::SetNewCharSkillsStats() //o------------------------------------------------------------------------------------------------o //| Purpose - Sets skills and stats for newly created characters //o------------------------------------------------------------------------------------------------o void CPICreateCharacter::SetNewCharSkillsStats( CChar *mChar ) { SI32 totalstats, totalskills; UI08 i; R32 percheck; if( profession != 0 ) //player picked a character profession during character creation { bool extStats = cwmWorldState->ServerData()->ExtendedStartingStats(); bool extSkills = cwmWorldState->ServerData()->ExtendedStartingSkills(); // Which skills and stats a character starts with depends on chosen profession, // plus ExtendedStartingStats/Skills-setting in UOX.INI. // Matches old (4.x - 7.0.8.x) or new (7.0.9+) profession-templates in client, but we // shouldn't trust client-data! switch( profession ) { case 1: // Warrior skill[0] = ( extSkills ? 40 : 27 ); // Swordsmanship or Tactics skill[1] = ( extSkills ? 27 : 17 ); // Tactics or Healing skill[2] = ( extSkills ? 17 : 40 ); // Healing or Swordsmanship skill[3] = ( extSkills ? 1 : 0xFF ); // Anatomy or nothing skillValue[0] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[1] = ( extSkills ? 30 : 45 ); // 30 or 45 skillpoints skillValue[2] = ( extSkills ? 30 : 5 ); // 30 or 5 skillpoints skillValue[3] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints mChar->SetStrength( extStats ? 45 : 35 ); mChar->SetDexterity( 35 ); mChar->SetIntelligence( 10 ); break; case 2: // Mage skill[0] = 25; // Magery skill[1] = ( extSkills ? 16 : 46 ); // Evaulate Intelligence or Meditation skill[2] = ( extSkills ? 46 : 43 ); // Meditation or Wrestling skill[3] = ( extSkills ? 43 : 0xFF ); // Wrestling or nothing skillValue[0] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[1] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[2] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints skillValue[3] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints mChar->SetStrength( 25 ); mChar->SetDexterity( extStats ? 20 : 10 ); mChar->SetIntelligence( 45 ); break; case 3: // Blacksmith skill[0] = 7; // Blacksmithing skill[1] = ( extSkills ? 45 : 37 ); // Mining or Tinkering skill[2] = ( extSkills ? 37 : 45 ); // Tinkering or Mining skill[3] = ( extSkills ? 34 : 0xFF ); // Tailoring or nothing skillValue[0] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[1] = ( extSkills ? 30 : 45 ); // 30 or 45 skillpoints skillValue[2] = ( extSkills ? 30 : 5 ); // 30 or 5 skillpoints skillValue[3] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints mChar->SetStrength( 60 ); mChar->SetDexterity( extStats ? 15 : 10 ); mChar->SetIntelligence( extStats ? 15 : 10); break; case 4: // Necromancer skill[0] = 49; // Necromancy skill[1] = ( extSkills ? 32 : 40 ); // Spirit Speak or Swordsmanship skill[2] = ( extSkills ? 40 : 27 ); // Swordsmanship or Tactics skill[3] = ( extSkills ? 46 : 0xFF ); // Meditation or nothing skillValue[0] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[1] = ( extSkills ? 30 : 30 ); // 30 or 30 skillpoints skillValue[2] = ( extSkills ? 30 : 20 ); // 30 or 20 skillpoints skillValue[3] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints mChar->SetStrength( 25 ); mChar->SetDexterity( 20 ); mChar->SetIntelligence( extStats ? 45 : 35 ); break; case 5: // Paladin skill[0] = 51; // Chivalry skill[1] = ( extSkills ? 40 : 27 ); // Swordsmanship or Tactics skill[2] = ( extSkills ? 27 : 43 ); // Tactics or Wrestling skill[3] = ( extSkills ? 50 : 0xFF ); // Focus or nothing skillValue[0] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[1] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[2] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints skillValue[3] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints mChar->SetStrength( 45 ); mChar->SetDexterity( 20 ); mChar->SetIntelligence( extStats ? 25 : 15 ); break; case 6: // Samurai skill[0] = 52; // Bushido skill[1] = ( extSkills ? 40 : 40 ); // Swordsmanship skill[2] = ( extSkills ? 50 : 43 ); // Focus or Wrestling skill[3] = ( extSkills ? 5 : 0xFF ); // Parrying or nothing skillValue[0] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[1] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[2] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints skillValue[3] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints mChar->SetStrength( 40 ); mChar->SetDexterity( 30 ); mChar->SetIntelligence( extStats ? 20 : 10 ); break; case 7: // Ninja skill[0] = 53; // Ninjitsu skill[1] = ( extSkills ? 42 : 21 ); // Fencing or Hiding skill[2] = ( extSkills ? 21 : 43 ); // Hiding or Wrestling skill[3] = ( extSkills ? 47 : 0xFF ); // Stealth or nothing skillValue[0] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[1] = ( extSkills ? 30 : 50 ); // 30 or 50 skillpoints skillValue[2] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints skillValue[3] = ( extSkills ? 30 : 0 ); // 30 or 0 skillpoints mChar->SetStrength( 40 ); mChar->SetDexterity( 30 ); mChar->SetIntelligence( extStats ? 20 : 10 ); break; default: Console.Error( oldstrutil::format( "Character created with invalid profession - no skills or stats assigned! (0x%X, %s)", mChar->GetSerial(), mChar->GetName().c_str() )); break; } } else // Player did not pick a profession during character creation, but chose to customize his character { // Date Unknown - Modified to fit in with new client, and 80 total starting stats. The highest any one stat can be is 60, and the lowest is 10. mChar->SetStrength( Capped( str, static_cast( 10 ), static_cast( 60 ))); mChar->SetDexterity( Capped( dex, static_cast( 10 ), static_cast( 60 ))); mChar->SetIntelligence( Capped( intel, static_cast( 10 ), static_cast( 60 ))); totalstats = mChar->GetStrength() + mChar->GetDexterity() + mChar->GetIntelligence(); if( totalstats != 80 && !cwmWorldState->ServerData()->ExtendedStartingStats() ) { if( totalstats > 80 ) { // If ExtendedStartingStats() is false, allow a total of 80 starting statpoints Console.Error( oldstrutil::format( "Character created with invalid stats (over 80 total): 0x%X, (%s)", mChar->GetSerial(), mChar->GetName().c_str() )); percheck = ( mChar->GetStrength() / static_cast( totalstats )); mChar->SetStrength( static_cast(Capped( percheck * 80, 10.0f, 60.0f ))); percheck = ( mChar->GetDexterity() / static_cast( totalstats )); mChar->SetDexterity( static_cast(Capped( percheck * 80, 10.0f, 60.0f ))); percheck = ( mChar->GetIntelligence() / static_cast( totalstats )); mChar->SetIntelligence( static_cast(Capped( percheck * 80, 10.0f, 60.0f ))); } else { Console.Error( oldstrutil::format( "Character created with invalid stats (under 80 total): 0x%X, (%s)", mChar->GetSerial(), mChar->GetName().c_str() )); } } else if( totalstats != 90 && cwmWorldState->ServerData()->ExtendedStartingStats() ) { if( totalstats > 90 ) { // If ExtendedStartingStats() is true, allow a total of 90 starting statpoints Console.Error( oldstrutil::format( "Character created with invalid stats (over 90 total): 0x%X, (%s)", mChar->GetSerial(), mChar->GetName().c_str() )); percheck = ( mChar->GetStrength() / static_cast( totalstats )); mChar->SetStrength( static_cast(Capped( percheck * 90, 10.0f, 60.0f ))); percheck = ( mChar->GetDexterity() / static_cast( totalstats )); mChar->SetDexterity( static_cast(Capped( percheck * 90, 10.0f, 60.0f ))); percheck = ( mChar->GetIntelligence() / static_cast( totalstats )); mChar->SetIntelligence( static_cast(Capped( percheck * 90, 10.0f, 60.0f ))); } else { Console.Error( oldstrutil::format( "Character created with invalid stats (under 90 total): 0x%X, (%s)", mChar->GetSerial(), mChar->GetName().c_str() )); } } if( skillValue[0] > 50 ) { skillValue[0] = 50; } totalskills = skillValue[0]; if( skillValue[1] > 50 ) { skillValue[1] = 50; } totalskills += skillValue[1]; if( skillValue[2] > 50 ) { skillValue[2] = 50; } if( !cwmWorldState->ServerData()->ExtendedStartingSkills() ) { if( skillValue[2] + totalskills > 100 ) { skillValue[2] = static_cast( 100 - totalskills ); } totalskills += skillValue[2]; if( totalskills < 100 ) { Console.Error( oldstrutil::format( "Character created with invalid skills (under 100 total): 0x%X, (%s)", mChar->GetSerial(), mChar->GetName().c_str() )); } } else // If ExtendedStartingSkills is enabled, allow for the fourth starting skill { if( skillValue[2] + totalskills > 120 ) { skillValue[2] = static_cast( 120 - totalskills ); } totalskills += skillValue[2]; if( skillValue[3] > 50 ) { skillValue[3] = 50; } if( skillValue[3] + totalskills > 120 ) { skillValue[3] = static_cast( 120 - totalskills ); } totalskills += skillValue[3]; if( totalskills < 120 ) { Console.Error( oldstrutil::format( "Character created with invalid skills (under 120 total): 0x%X, (%s)", mChar->GetSerial(), mChar->GetName().c_str() )); } } } mChar->SetHP( mChar->GetMaxHP() ); mChar->SetStamina( mChar->GetMaxStam() ); mChar->SetMana( mChar->GetMaxMana() ); mChar->SetRaceGate( 65535 ); UI08 firstSkill = skill[0]; UI08 secondSkill = skill[1]; UI08 thirdSkill = skill[2]; UI08 fourthSkill = skill[3]; for( i = 0; i < ALLSKILLS; ++i ) { mChar->SetBaseSkill( 0, i ); if( i == firstSkill ) { mChar->SetBaseSkill( static_cast( skillValue[0] * 10 ), i ); } else if( i == secondSkill ) { mChar->SetBaseSkill( static_cast( skillValue[1] * 10 ), i ); } else if( i == thirdSkill ) { mChar->SetBaseSkill( static_cast( skillValue[2] * 10 ), i ); } else if( i == fourthSkill ) { if( cwmWorldState->ServerData()->ExtendedStartingSkills() ) { mChar->SetBaseSkill( static_cast( skillValue[3] * 10 ), i ); } } Skills->UpdateSkillLevel( mChar, i ); } } //o------------------------------------------------------------------------------------------------o //| Function - CPICreateCharacter::SetNewCharGenderAndRace() //o------------------------------------------------------------------------------------------------o //| Purpose - Sets gender and race for newly created characters //o------------------------------------------------------------------------------------------------o void CPICreateCharacter::SetNewCharGenderAndRace( CChar *mChar ) { UI16 pGenderId = 0x0190; bool gargCreation = false; bool elfCreation = false; if( cwmWorldState->ServerData()->GetClientFeature( CF_BIT_SA )) { gargCreation = true; } if( cwmWorldState->ServerData()->GetClientFeature( CF_BIT_ML )) { elfCreation = true; } if( tSock->ClientType() == CV_SA3D || tSock->ClientType() == CV_HS3D ) { switch( sex ) { case 0: switch( race ) { case 0: pGenderId = 0x0190; break; // human male case 1: pGenderId = ( elfCreation ? 0x025D : 0x0190 ); break; // elf male case 2: pGenderId = ( gargCreation ? 0x029A : 0x0190 ); break; // Gargoyle male } break; case 1: switch( race ) { case 0: pGenderId = 0x0191; break; // human female case 1: pGenderId = ( elfCreation ? 0x025E : 0x0191 ); break; // elf female case 2: pGenderId = ( gargCreation ? 0x029B : 0x0191 ); break; // Gargoyle female } break; default: break; } // Set race to match data sent from client. This should correspond with race setup in races.dfn // 0 = human, 1 = elf, 2 = gargoyle mChar->SetRace( race ); } else if( tSock->ClientType() >= CV_SA2D ) { switch( sex ) { case 0: // human male pGenderId = 0x0190; mChar->SetRace( 0 ); // Human break; case 1: // human female pGenderId = 0x0191; mChar->SetRace( 0 ); // Human break; case 2: // human male pGenderId = 0x0190; mChar->SetRace( 0 ); // Human break; case 3: // human female pGenderId = 0x0191; mChar->SetRace( 0 ); // Human break; case 4: // elf male pGenderId = ( elfCreation ? 0x025D : 0x0190 ); mChar->SetRace( 1 ); // Elf break; case 5: // elf female pGenderId = ( elfCreation ? 0x025E : 0x0191 ); mChar->SetRace( 1 ); // Elf break; case 6: // Gargoyle male pGenderId = ( gargCreation ? 0x029A : 0x0190 ); mChar->SetLevitate( true ); // Enable potential for flying mChar->SetRace( 2 ); // Gargoyle break; case 7: // Gargoyle female pGenderId = ( gargCreation ? 0x029B : 0x0191 ); mChar->SetLevitate( true ); // Enable potential for flying mChar->SetRace( 2 ); // Gargoyle break; default: break; } } else { switch( sex ) { case 0: pGenderId = 0x0190; break; // human male case 1: pGenderId = 0x0191; break; // human female case 2: pGenderId = ( elfCreation ? 0x025D : 0x0190 ); break; // elf male (human if server doesn't allow elf but client tried anyway) case 3: pGenderId = ( elfCreation ? 0x025E : 0x0191 ); break; // elf female (human if server doesn't allow elf but client tried anyway) default: break; } } mChar->SetId( pGenderId ); mChar->SetOrgId( pGenderId ); mChar->SetSkin( ValidSkinColour( skinColour, pGenderId ) | 0x8000 ); mChar->SetOrgSkin( mChar->GetSkin() ); } //o------------------------------------------------------------------------------------------------o //| Function - ShowMessageOfTheDay() //o------------------------------------------------------------------------------------------------o //| Purpose - Opens the Message of the Day window //o------------------------------------------------------------------------------------------------o auto ShowMessageOfTheDay( CSocket *s ) -> void { if( s ) { auto Updates = FileLookup->FindEntry( "MOTD", misc_def ); if( Updates ) { std::string updateData = ""; for( const auto &sec : Updates->collection() ) { updateData += sec->data + " "s; } CPUpdScroll toSend( 2 ); toSend.AddString( updateData.c_str() ); toSend.Finalize(); s->Send( &toSend ); } } } void SysBroadcast( const std::string& txt ); //o------------------------------------------------------------------------------------------------o //| Function - StartChar() //o------------------------------------------------------------------------------------------------o //| Purpose - Sends character startup stuff to player //o------------------------------------------------------------------------------------------------o void StartChar( CSocket *mSock, bool onCreate ) { if( mSock != nullptr ) { /* //Login Packet Sequence per Krrios Login Confirm (0x1B) tick Map Change (0xBFx08) tick SendMapChange Map Patches (0xBFx18) don't send Supported Features (0xB9) // 0x801F Season Change (0xBC) added SendWorldChange Mobile Update (0x20) added CPDrawGamePlayer Personal Light Level (0x4E) added CPPersonalLightLevel Mobile Incoming (0x78) Mobile Attributes (0x2D) Set War Mode (0x72) tick [everything on screen] (0x1A/0x78) Login Complete (0x55) */ CChar *mChar = mSock->CurrcharObj(); if( ValidateObject( mChar )) { CPClientVersion verCheck; mSock->Send( &verCheck ); CAccountBlock_st& actbTemp = mSock->GetAccount(); if( actbTemp.wAccountIndex != AB_INVALID_ID ) { actbTemp.wFlags.set( AB_FLAGS_ONLINE, true ); } mSock->TargetOK( false ); CPCharLocBody startup = (*mChar); if( mChar->GetPoisoned() ) { startup.Flag( 0x04 ); } else { startup.Flag( 0x00 ); } mSock->Send( &startup ); mSock->SetTimer( tPC_SPIRITSPEAK, 0 ); mChar->SetStealth( -1 ); if( mChar->GetVisible() != VT_PERMHIDDEN ) { mChar->SetVisible( VT_VISIBLE ); } mChar->SetWar( false ); SendMapChange( mChar->WorldNumber(), mSock, true ); if( cwmWorldState->ServerData()->MapDiffsEnabled() ) { CPEnableMapDiffs mDiff; mSock->Send( &mDiff ); } CPWorldChange wrldChange( mChar->GetRegion()->GetAppearance(), 1 ); mSock->Send( &wrldChange ); // need to add this? CPPersonalLightLevel pllToSend = ( *mChar ); // need to add this? pllToSend.Level( 0 ); mSock->Send( &pllToSend ); CItem *nItem = mChar->GetItemAtLayer( IL_PACKITEM ); mChar->SetPackItem( nItem ); if( mChar->IsDead() ) { CPResurrectMenu toSend( 0 ); mSock->Send( &toSend ); } mChar->Dirty( UT_LOCATION ); mChar->SetStep( 1 ); CPLoginComplete lc; mSock->Send( &lc ); Network->SetLastOn( mSock ); UI08 currentHour = cwmWorldState->ServerData()->ServerTimeHours(); UI08 currentMins = cwmWorldState->ServerData()->ServerTimeMinutes(); UI08 currentSecs = cwmWorldState->ServerData()->ServerTimeSeconds(); CPTime tmPckt( currentHour, currentMins, currentSecs ); mSock->Send( &tmPckt ); mSock->SysMessage( "%s v%s.%s [%s] ", CVersionClass::GetProductName().c_str(), CVersionClass::GetVersion().c_str(), CVersionClass::GetBuild().c_str(), OS_STR ); if( cwmWorldState->ServerData()->ServerJoinPartAnnouncementsStatus() ) { //message upon entering a server SysBroadcast( oldstrutil::format(1024, Dictionary->GetEntry( 1208 ), mChar->GetName().c_str() )); // message upon entering a server } ShowMessageOfTheDay( mSock ); #if defined( _MSC_VER ) #pragma note( "HTML Status for Players" ) #endif if( onCreate ) { cScript *onCreateScp = JSMapping->GetScript( static_cast( 0 )); // 0 == global script if( onCreateScp != nullptr ) { onCreateScp->OnCreate( mChar, false, true ); } } bool loginEventHandled = false; std::vector scriptTriggers = mChar->GetScriptTriggers(); for( auto scriptTrig : scriptTriggers ) { cScript *toExecute = JSMapping->GetScript( scriptTrig ); if( toExecute != nullptr ) { // 0 == no such event was found // 1 == event was found, and executed if( toExecute->OnLogin( mSock, mChar ) == 1 ) { loginEventHandled = true; } } } if( !loginEventHandled ) { // No script attached to character handled onLoginevent. Let's check global script! cScript *toExecute = JSMapping->GetScript( static_cast( 0 )); if( toExecute != nullptr ) { toExecute->OnLogin( mSock, mChar ); } } // Store hair and beard (if they have any) properly for characters created pre-0.99.2j CItem *hairObject = mChar->GetItemAtLayer( IL_HAIR ); CItem *beardObject = mChar->GetItemAtLayer( IL_FACIALHAIR ); if( ValidateObject( hairObject )) { if( mChar->GetHairStyle() == 0xFFFF ) { mChar->SetHairStyle( hairObject->GetId() ); mChar->SetHairColour( hairObject->GetColour() ); } } if( ValidateObject( beardObject )) { if( mChar->GetBeardStyle() == 0xFFFF ) { mChar->SetBeardStyle( beardObject->GetId() ); mChar->SetBeardColour( beardObject->GetColour() ); } } mSock->LoginComplete( true ); // Negotiate features with assistant tools like Razor and AssistUO if ASSISTANTNEGOTIATION is enabled in uox.ini if( cwmWorldState->ServerData()->GetAssistantNegotiation() ) { CPNegotiateAssistantFeatures ii( mSock ); mSock->Send( &ii ); mSock->SysMessage( 9012 ); // Attempting to negotiate features with assistant tool... // Set 30s negotiation timer if KICKONASSISTANTSILENCE setting is enabled in uox.ini if( cwmWorldState->ServerData()->KickOnAssistantSilence() ) { // Start timer to kick player if assistant tool hasn't responded in 30 seconds mSock->SysMessage( 9013 ); // This server requires use of an assistant tool that supports feature negotiation. Enable the tool's option for negotiating features with server, or get kicked in 30 seconds. mSock->NegotiateTimeout( static_cast( cwmWorldState->GetUICurrentTime() + ( 30.0 * 1000.0 ))); } } mChar->Dirty( UT_LOCATION ); if( mChar->WorldNumber() > 0 ) { // Without this, world will not be properly updated in regular UO clients until they take the first step mChar->Update( nullptr, false, true, true ); } // Re-add player to party, if they are in one! Party *mCharParty = PartyFactory::GetSingleton().Get( mChar ); if( mCharParty != nullptr ) { // Player's in a party! Send the details mCharParty->SendList( nullptr ); mSock->SysMessage( 9073 ); // You have rejoined the party. } // Play music associated with region player is logging into! Effects->DoSocketMusic( mSock ); } } } //o------------------------------------------------------------------------------------------------o //| Function - CreateCorpseItem() //o------------------------------------------------------------------------------------------------o //| Purpose - Generates a corpse or backpack based on the character killed. //o------------------------------------------------------------------------------------------------o CItem *CreateCorpseItem( CChar& mChar, CChar *killer, UI08 fallDirection ) { std::string corpseName = GetNpcDictName( &mChar, nullptr, NRS_SYSTEM ); CItem *iCorpse = nullptr; bool shouldSave = mChar.ShouldSave(); iCorpse = Items->CreateItem( nullptr, &mChar, 0x2006, 1, mChar.GetSkin(), OT_ITEM, false, shouldSave ); if( !ValidateObject( iCorpse )) return nullptr; iCorpse->SetName( oldstrutil::format( 512, Dictionary->GetEntry( 1612 ), corpseName.c_str() )); // Corpse of %s iCorpse->SetCarve( mChar.GetCarve() ); iCorpse->SetMovable( 2 );//non-movable if( fallDirection ) { iCorpse->SetDir( mChar.GetDir() | 0x80 ); } else { iCorpse->SetDir( mChar.GetDir() ); } iCorpse->SetAmount( mChar.GetId() ); iCorpse->SetWeightMax( 50000 ); // 500 stones iCorpse->SetMaxItems( cwmWorldState->ServerData()->MaxPlayerPackItems() + 25 ); iCorpse->SetCorpse( true ); UI08 canCarve = 0; if( mChar.GetId( 1 ) == 0x00 && ( mChar.GetId( 2 ) == 0x0C || ( mChar.GetId( 2 ) >= 0x3B && mChar.GetId( 2 ) <= 0x3D ))) // If it's a dragon, 50/50 chance you can carve it { canCarve = static_cast( RandomNum( 0, 1 )); } iCorpse->SetDecayable( true ); iCorpse->SetName2( corpseName ); iCorpse->SetType( IT_CONTAINER ); iCorpse->SetTempVar( CITV_MOREY, 1, canCarve ); iCorpse->SetTempVar( CITV_MOREY, 2, cwmWorldState->creatures[mChar.GetId()].IsHuman() ); iCorpse->SetTempVar( CITV_MOREZ, mChar.GetFlag() ); iCorpse->SetTempTimer( cwmWorldState->GetUICurrentTime() ); if( !mChar.IsNpc() ) { iCorpse->SetOwner( &mChar ); mChar.AddCorpse( iCorpse ); // Add to list that tracks player's corpses iCorpse->SetDecayTime( BuildTimeValue( cwmWorldState->ServerData()->SystemTimer( tSERVER_CORPSEDECAY ))); } else { iCorpse->SetSectionId( mChar.GetSectionId() ); // Store NPC section ID on corpse iCorpse->SetDecayTime( BuildTimeValue( cwmWorldState->ServerData()->SystemTimer( tSERVER_NPCCORPSEDECAY ))); } if( !ValidateObject( killer )) { iCorpse->SetTempVar( CITV_MOREX, INVALIDSERIAL ); } else { iCorpse->SetTempVar( CITV_MOREX, killer->GetSerial() ); } // Add default corpse carving script to all corpses iCorpse->AddScriptTrigger( 5046 ); // js/item/corpse.js return iCorpse; } //o------------------------------------------------------------------------------------------------o //| Function - MoveItemsToCorpse() //o------------------------------------------------------------------------------------------------o //| Purpose - Moves Items from Character to Corpse //o------------------------------------------------------------------------------------------------o auto MoveItemsToCorpse( CChar &mChar, CItem *iCorpse ) -> void { CItem *packItem = mChar.GetPackItem(); CItem *dupeItem = nullptr; bool packIsValid = ValidateObject( packItem ); for( CItem *j = mChar.FirstItem(); !mChar.FinishedItems(); j = mChar.NextItem() ) { if( !ValidateObject( j )) continue; ItemLayers iLayer = j->GetLayer(); switch( iLayer ) { case IL_NONE: case IL_SELLCONTAINER: case IL_BOUGHTCONTAINER: case IL_BUYCONTAINER: case IL_BANKBOX: continue; case IL_HAIR: case IL_FACIALHAIR: if( mChar.GetBodyType() != BT_GARGOYLE ) // Ignore if gargoyle - doesn't seem to display properly on corpses { dupeItem = j->Dupe(); dupeItem->SetCont( iCorpse ); dupeItem->SetName( "Hair/Beard" ); dupeItem->SetX( 0x47 ); dupeItem->SetY( 0x93 ); dupeItem->SetZ( 0 ); dupeItem->SetMovable( 2 ); } break; case IL_PACKITEM: { // Only move player's items from backpack to corpse if young system is disabled OR player is not on a young account if( mChar.IsNpc() || ( !cwmWorldState->ServerData()->YoungPlayerSystem() || !mChar.GetAccount().wFlags.test( AB_FLAGS_YOUNG ))) { std::vector moveItems; auto jCont = j->GetContainsList(); for( const auto &k : jCont->collection() ) { if( ValidateObject( k )) { // If the character dying is a pack animal, drop everything they're carrying - including newbie items and spellbooks if(( mChar.GetId() == 0x0123 || mChar.GetId() == 0x0124 || mChar.GetId() == 0x0317 ) || ( !k->IsNewbie() && k->GetType() != IT_SPELLBOOK )) { // Store a reference to the item we want to move... moveItems.push_back( k ); } } } // Loop through the items we want to move - and move them! std::for_each( moveItems.begin(), moveItems.end(), [iCorpse]( CItem *item ) { item->SetCont( iCorpse ); item->SetX( static_cast( 20 + ( RandomNum( 0, 49 )))); item->SetY( static_cast( 85 + ( RandomNum( 0, 75 )))); item->SetZ( 9 ); }); if( !mChar.IsShop() ) { // Move player's backpack to the BUYCONTAINER layer for safekeeping j->SetLayer( IL_BUYCONTAINER ); } } break; } default: // Move equipped items from character to pack if newbie, or if player is on a Young account if( packIsValid && ( j->IsNewbie() || ( cwmWorldState->ServerData()->YoungPlayerSystem() && mChar.GetAccount().wFlags.test( AB_FLAGS_YOUNG )))) { j->SetCont( packItem ); } else { // Otherwise, move them to corpse j->SetCont( iCorpse ); j->SetX( static_cast( 20 + ( RandomNum( 0, 49 )))); j->SetY( static_cast( 85 + ( RandomNum( 0, 74 )))); j->SetZ( 9 ); } break; } } } void KillTrades( CChar *i ); //o------------------------------------------------------------------------------------------------o //| Function - HandleDeath() //o------------------------------------------------------------------------------------------------o //| Purpose - Performs death stuff. I.E.- creates a corpse, moves items //| to it, take out of war mode, does animation and sound, etc. //o------------------------------------------------------------------------------------------------o void HandleDeath( CChar *mChar, CChar *attacker ) { if( !ValidateObject( mChar ) || mChar->IsDead() || mChar->IsInvulnerable() ) // don't kill them if they are dead or invulnerable! return; CSocket *pSock = nullptr; if( !mChar->IsNpc() ) { pSock = mChar->GetSocket(); } DismountCreature( mChar ); if( pSock != nullptr ) { KillTrades( mChar ); } if( mChar->GetId() != mChar->GetOrgId() ) { mChar->SetId( mChar->GetOrgId() ); } UI08 fallDirection = static_cast( RandomNum( 1, 100 ) % 2 ); mChar->SetDead( true ); Effects->PlayDeathSound( mChar ); CItem *iCorpse = CreateCorpseItem(( *mChar ), attacker, fallDirection ); if( iCorpse != nullptr ) { // Move items on player to corpse (special exceptions for Young players) MoveItemsToCorpse(( *mChar ), iCorpse ); if( cwmWorldState->ServerData()->DeathAnimationStatus() ) { Effects->DeathAction( mChar, iCorpse, fallDirection ); } // Store information on the corpse about the origins of the poison that was on character when they died if( mChar->GetPoisoned() > 0 ) { iCorpse->SetPoisonedBy( mChar->GetPoisonedBy() ); } // Prevent followers from following ghost of dead player auto mFollowerList = mChar->GetFollowerList(); for( const auto &tempChar : mFollowerList->collection() ) { if( ValidateObject( tempChar )) { if( !tempChar->GetStabled() && tempChar->GetNpcWander() == WT_FOLLOW && tempChar->GetFTarg() == mChar ) { tempChar->SetFTarg( nullptr ); tempChar->SetOldNpcWander( WT_NONE ); tempChar->SetNpcWander( WT_NONE ); tempChar->SetGuarding( iCorpse ); iCorpse->SetGuarded( true ); } } } // Spawn blood effect below corpse UI16 bloodColour = Races->BloodColour( mChar->GetRace() ); // Fetch blood color from race property if( bloodColour == 0xffff ) { // If blood colour is 0xffff in the race setup, inherit color of NPC instead! bloodColour = mChar->GetSkin(); } CItem * bloodEffect = Effects->SpawnBloodEffect( iCorpse->WorldNumber(), iCorpse->GetInstanceId(), bloodColour, BLOOD_DEATH ); if( ValidateObject( bloodEffect )) { // Set a timestamp for when the bloodeffect was created, and match it to the corpse! bloodEffect->SetTempTimer( iCorpse->GetTempTimer() ); if( !mChar->IsNpc() ) { // Set character as owner of blood-effects, if not an NPC bloodEffect->SetOwner( mChar ); } // If there's a valid attacker still, store the serial on the blood item (just like the corpse) // so it could be accessed by something like forensics skill if( ValidateObject( attacker )) { bloodEffect->SetTempVar( CITV_MOREX, attacker->GetSerial() ); } else { bloodEffect->SetTempVar( CITV_MOREX, INVALIDSERIAL ); } // Finally, set blood's location to match that of the corpse bloodEffect->SetLocation( iCorpse ); } } mChar->SetWar( false ); mChar->StopSpell(); mChar->SetFrozen( false ); mChar->SetHP( 0 ); mChar->SetPoisoned( 0 ); mChar->SetPoisonedBy( INVALIDSERIAL ); mChar->SetPoisonStrength( 0 ); // Remove any permagrey flags on the character, but replace with a temporary criminal flag instead if( mChar->IsPermaGrey( true )) { mChar->ClearPermaGreyFlags(); MakeCriminal( mChar ); } // Also clear all aggressorflags mChar->ClearAggressorFlags(); if( !mChar->IsNpc() ) { switch( mChar->GetOrgId() ) { case 0x0190: mChar->SetId( 0x0192 ); break; // human male default: case 0x0191: mChar->SetId( 0x0193 ); break; // human female, or others case 0x025D: mChar->SetId( 0x025F ); break; // elf male case 0x025E: mChar->SetId( 0x0260 ); break; // elf female case 0x029A: mChar->SetId( 0x02B6 ); break; // gargoyle male case 0x029B: mChar->SetId( 0x02B7 ); break; // gargoyle female case 0x00B7: case 0x00B9: case 0x02EE: mChar->SetId( 0x0192 ); break; // savage male case 0x00B8: case 0x00BA: case 0x02EF: mChar->SetId( 0x0192 ); break; // savage female } CItem *c = Items->CreateItem( nullptr, mChar, 0x204E, 1, 0, OT_ITEM ); if( c == nullptr ) return; c->SetName( Dictionary->GetEntry( 1610 )); // A Death Shroud mChar->SetRobe( c->GetSerial() ); c->SetLayer( IL_ROBE ); if( c->SetCont( mChar )) { c->SetResist( 1, PHYSICAL ); } if( mChar->GetAccount().wAccountIndex != AB_INVALID_ID ) { if( pSock != nullptr ) { CPResurrectMenu toSend( 0 ); pSock->Send( &toSend ); } // The fade to gray is done by the CPResurrectMenu packet } } auto onDeathEventHandled = false; std::vector scriptTriggers = mChar->GetScriptTriggers(); for( auto scriptTrig : scriptTriggers ) { cScript *toExecute = JSMapping->GetScript( scriptTrig ); if( toExecute != nullptr ) { // If script returns true/1, prevent other scripts with event from running if( toExecute->OnDeath( mChar, iCorpse ) == 1 ) { onDeathEventHandled = true; break; } } } if( !onDeathEventHandled ) { // No script attached to character handled onDeath. Let's check global script! cScript *toExecute = JSMapping->GetScript( static_cast( 0 )); if( toExecute != nullptr ) { toExecute->OnDeath( mChar, iCorpse ); } } if( mChar->IsNpc() ) { mChar->Delete(); } else { mChar->Dirty( UT_LOCATION ); } // Reset skill usage status for all skills for( UI08 i = 0; i < ALLSKILLS; ++i ) { mChar->SkillUsed( false, i ); } // Play death music if( pSock != nullptr ) { Effects->DoSocketMusic( pSock ); } }