#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 - bool validHairStyle( UI16 id, UI16 bodyID ) //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 - bool validBeard( UI16 id, UI16 bodyID ) //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 - COLOUR validSkinColour( UI16 id, UI16 bodyID ) //| 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 - COLOUR validHairColour( UI16 id, UI16 bodyID ) //| 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< UI16 >(0x06E0), static_cast< UI16 >(0x076B) ); break; default: // human male/female rvalue = Capped( id, static_cast< UI16 >(0x044E), static_cast< UI16 >(0x04AD) ); break; } return rvalue; } void startChar( CSocket *mSock, bool onCreate = false ); //o-----------------------------------------------------------------------------------------------o //| Function - bool CPIPlayCharacter::Handle( void ) //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& 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 - bool CPIDeleteCharacter::Handle( void ) //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 * 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 - void addNewbieItem( CSocket *socket, CChar *c, const char* str, COLOUR pantsColour, COLOUR shirtColour ) //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 ) { 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() ) { auto UTag = oldstrutil::upper( tag ); if( UTag == "PACKITEM" ) { auto csecs = oldstrutil::sections( data, "," ); if( csecs.size() > 1 ) { UI16 nAmount = static_cast(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0)); n = Items->CreateScriptItem( socket, c, oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )), nAmount, OT_ITEM, true ); } else { n = Items->CreateScriptItem( socket, c, data.c_str(), 1, OT_ITEM, true ); } } else if( UTag == "EQUIPITEM" ) { UI16 itemHue = 0; std::string itemSection; auto csecs = oldstrutil::sections( data, "," ); if( csecs.size() > 1 ) { itemSection = oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )); itemHue = static_cast(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0 )); } else { itemSection = data; } n = Items->CreateScriptItem( socket, c, itemSection.c_str(), 1, 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() ) { // Set item as newbiefied/blessed by default - as long as it's not pileable! n->SetNewbie( true ); } } } } } //o-----------------------------------------------------------------------------------------------o //| Function - void newbieItems( CChar *mChar ) //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< UI16 >(0x06E0), static_cast< UI16 >(0x076B) ); else // human male ItemColour = Capped( facialHairColour, static_cast< UI16 >(0x044E), static_cast< UI16 >(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]->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]->SetMaxItems( cwmWorldState->ServerData()->MaxPlayerBankItems() ); CreatedItems[BANK]->SetOwner( mChar ); CreatedItems[BANK]->SetCont( mChar ); } std::vector< cSkillClass > 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 FEMALE", 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; } } //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( tSock != nullptr ) { CChar *mChar = static_cast< CChar * >(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 &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() ); CAccountBlock& actbTemp2 = mChar->GetAccount(); if( actbTemp2.wAccountIndex != AB_INVALID_ID && actbTemp2.wFlags.test( AB_FLAGS_GM ) ) { mChar->SetPriv( 0xFF ); mChar->SetCommandLevel( CL_GM ); } if( tSock->ClientType() == CV_SA3D || tSock->ClientType() == CV_HS3D ) locationNumber = clientFlags; // Fetch player's chosen start location auto toGo = cwmWorldState->ServerData()->ServerLocation( locationNumber ); CServerData *sd = cwmWorldState->ServerData(); size_t serverCount = sd->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 ); 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 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 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 ); // 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 - void SetNewCharSkillsStats( CChar *mChar ) //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< UI08 >(10), static_cast< UI08 >(60) ) ); mChar->SetDexterity( Capped( dex, static_cast< UI08 >(10), static_cast< UI08 >(60) ) ); mChar->SetIntelligence( Capped( intel, static_cast< UI08 >(10), static_cast< UI08 >(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() / (R32)totalstats ); mChar->SetStrength( static_cast< UI08 >(Capped( percheck * 80, 10.0f, 60.0f ) ) ); percheck = ( mChar->GetDexterity() / (R32)totalstats ); mChar->SetDexterity( static_cast(Capped( percheck * 80, 10.0f, 60.0f ) ) ); percheck = ( mChar->GetIntelligence() / (R32)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() / (R32)totalstats ); mChar->SetStrength( static_cast< UI08 >(Capped( percheck * 90, 10.0f, 60.0f ) ) ); percheck = ( mChar->GetDexterity() / (R32)totalstats ); mChar->SetDexterity( static_cast(Capped( percheck * 90, 10.0f, 60.0f ) ) ); percheck = ( mChar->GetIntelligence() / (R32)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< UI08 >(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< UI08 >(120 - totalskills); totalskills += skillValue[2]; if( skillValue[3] > 50 ) skillValue[3] = 50; if( skillValue[3] + totalskills > 120 ) skillValue[3] = static_cast< UI08 >(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( (UI16)(skillValue[0] * 10), i ); else if( i == secondSkill ) mChar->SetBaseSkill( (UI16)(skillValue[1] * 10), i ); else if( i == thirdSkill ) mChar->SetBaseSkill( (UI16)(skillValue[2] * 10), i ); else if( i == fourthSkill ) { if( cwmWorldState->ServerData()->ExtendedStartingSkills() ) mChar->SetBaseSkill( (UI16)(skillValue[3] * 10), i ); } Skills->updateSkillLevel( mChar, i ); } } //o-----------------------------------------------------------------------------------------------o //| Function - void SetNewCharGenderAndRace( CChar *mChar ) //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 - void updates( CSocket *s ) //o-----------------------------------------------------------------------------------------------o //| Purpose - Opens the Updates window //o-----------------------------------------------------------------------------------------------o auto updates( 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 - void startChar( CSocket *mSock, bool onCreate ) //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& 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 ); CPWarMode wMode( 0 ); mSock->Send( &wMode ); 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 } updates( mSock ); #if defined( _MSC_VER ) #pragma note( "HTML Status for Players" ) #endif if( onCreate ) { cScript *onCreateScp = JSMapping->GetScript( (UI16)0 ); // 0 == global script if( onCreateScp != nullptr ) onCreateScp->OnCreate( mChar, 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( cwmWorldState->GetUICurrentTime() + ( 30 * 1000 )); } } 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(); } // 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 - CItem *CreateCorpseItem( CChar& mChar, CChar *killer, UI08 fallDirection ) //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 ); 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() ) ); 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 ); iCorpse->SetDecayTime( BuildTimeValue( cwmWorldState->ServerData()->SystemTimer( tSERVER_CORPSEDECAY ) ) ); } else iCorpse->SetDecayTime( BuildTimeValue( cwmWorldState->ServerData()->SystemTimer( tSERVER_NPCCORPSEDECAY ) ) ); if( !ValidateObject( killer ) ) iCorpse->SetTempVar( CITV_MOREX, INVALIDSERIAL ); else iCorpse->SetTempVar( CITV_MOREX, killer->GetSerial() ); 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: { 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() ) { j->SetLayer( IL_BUYCONTAINER ); } break; } default: if( packIsValid && j->isNewbie() ) { j->SetCont( packItem ); } else { 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 - void HandleDeath( CChar *mChar, CChar *attacker ) //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 = (UI08)(RandomNum( 0, 100 ) % 2); mChar->SetDead( true ); Effects->playDeathSound( mChar ); CItem *iCorpse = CreateCorpseItem( (*mChar), attacker, fallDirection ); if( iCorpse != nullptr ) { MoveItemsToCorpse( (*mChar), iCorpse ); if( cwmWorldState->ServerData()->DeathAnimationStatus() ) Effects->deathAction( mChar, iCorpse, fallDirection ); // Prevent pets from following ghost of dead player auto mPetList = mChar->GetPetList(); for (const auto &tempChar:mPetList->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->SetPoisonStrength( 0 ); 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 ) ); 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 } } 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 ) { break; } } } if( mChar->IsNpc() ) mChar->Delete(); else mChar->Dirty( UT_LOCATION ); // Play death music if( pSock != nullptr ) Effects->doSocketMusic( pSock ); }