mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
- [ADD} Added new Creatures DFN tag that can mark a creature as a pack animal: PACKANIMAL - [ADD] New Character JS Property to check if NPC is a pack animal: .isPackAnimal
1928 lines
59 KiB
C++
1928 lines
59 KiB
C++
#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 <algorithm>
|
|
|
|
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<UI16>( 0x06DB ), static_cast<UI16>( 0x06F3 ));
|
|
break;
|
|
default: // human male/female
|
|
rValue = Capped( id, static_cast<UI16>( 0x03EA ), static_cast<UI16>( 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<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 - 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<CharacterDeletionResult>( 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<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0 ));
|
|
|
|
if( csecs.size() == 3 )
|
|
{
|
|
// Fetch newbie flag override
|
|
nNewbie = static_cast<UI16>( 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<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0 ));
|
|
|
|
if( csecs.size() >= 3 )
|
|
{
|
|
// Fetch newbie flag override
|
|
nNewbie = static_cast<UI16>( 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<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]->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<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 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<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_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<size_t>( 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<SI08>( toGo->z ), static_cast<UI08>( toGo->worldNum ), static_cast<UI16>( toGo->instanceId ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mChar->SetLocation( toGo->x, toGo->y, static_cast<SI08>( toGo->z ), static_cast<UI08>( toGo->worldNum ), static_cast<UI16>( 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<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() / static_cast<R32>( totalstats ));
|
|
mChar->SetStrength( static_cast<UI08>(Capped( percheck * 80, 10.0f, 60.0f )));
|
|
percheck = ( mChar->GetDexterity() / static_cast<R32>( totalstats ));
|
|
mChar->SetDexterity( static_cast<UI08>(Capped( percheck * 80, 10.0f, 60.0f )));
|
|
percheck = ( mChar->GetIntelligence() / static_cast<R32>( totalstats ));
|
|
mChar->SetIntelligence( static_cast<UI08>(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<R32>( totalstats ));
|
|
mChar->SetStrength( static_cast<UI08>(Capped( percheck * 90, 10.0f, 60.0f )));
|
|
percheck = ( mChar->GetDexterity() / static_cast<R32>( totalstats ));
|
|
mChar->SetDexterity( static_cast<UI08>(Capped( percheck * 90, 10.0f, 60.0f )));
|
|
percheck = ( mChar->GetIntelligence() / static_cast<R32>( totalstats ));
|
|
mChar->SetIntelligence( static_cast<UI08>(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( static_cast<UI16>( skillValue[0] * 10 ), i );
|
|
}
|
|
else if( i == secondSkill )
|
|
{
|
|
mChar->SetBaseSkill( static_cast<UI16>( skillValue[1] * 10 ), i );
|
|
}
|
|
else if( i == thirdSkill )
|
|
{
|
|
mChar->SetBaseSkill( static_cast<UI16>( skillValue[2] * 10 ), i );
|
|
}
|
|
else if( i == fourthSkill )
|
|
{
|
|
if( cwmWorldState->ServerData()->ExtendedStartingSkills() )
|
|
{
|
|
mChar->SetBaseSkill( static_cast<UI16>( 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<UI16>( 0 )); // 0 == global script
|
|
if( onCreateScp != nullptr )
|
|
{
|
|
onCreateScp->OnCreate( mChar, false, true );
|
|
}
|
|
}
|
|
|
|
bool loginEventHandled = false;
|
|
std::vector<UI16> 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<UI16>( 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<TIMERVAL>( 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<UI08>( 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<CItem *> 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(( cwmWorldState->creatures[mChar.GetId()].IsPackAnimal() ) || ( !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<SI16>( 20 + ( RandomNum( 0, 49 ))));
|
|
item->SetY( static_cast<SI16>( 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<SI16>( 20 + ( RandomNum( 0, 49 ))));
|
|
j->SetY( static_cast<SI16>( 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<UI08>( 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<UI16> 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<UI16>( 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 );
|
|
}
|
|
}
|