uox3/source/pcmanage.cpp
Xoduz 68e57b0756 UOX3 0.99.5b-6
Increased world load speed by almost 20% by reducing how often loading progress is written to the console
Increased world save speed by almost 50% through the use of binary mode and buffer tweaks for ofstream combined with a cached newline char and std::to_string instead of static_cast
Fixed a server crash involving killing NPCs of a specific spawn region using the 'spawnkill # command
Fixed a server crash involving moving items from decaying corpses to the ground
Fixed a server crash involving carving up human corpses
Fixed a server crash related to AreaCharacterFunction JS function, which could trigger for any script that potentially kills any NPC this function iterates over (like explosion potions)
Fixed a server crash related to GMs attempting to add items from add-menu while having no backpack
Fixed a client crash issue caused by attempting to sell house deeds back to architect NPCs
Fixed an issue where players would get incorrect amounts of gold for selling house deeds back to architect NPCs
Fixed an issue with characters dying onboard boats which prevented players from packing up the boats later
Added new item property in code (via bools bitset) that can be used to enable/disable maker's mark on crafted items. This property can be set to true for items crafted at exceptional quality by GM craftsmen
Exposed the new item property to JS engine:
	.isMarkedByMaker	 	// If true, maker's mark will be displayed
Updated default item tooltip to display " of exceptional quality" for items crafted with exceptional quality
Updated default item tooltip to display "[Crafted] by [Crafter's Name]" for exceptional quality items crafted by GM craftsmen. The [Crafted] text comes from the MADEWORD defined in dfndata/skills/skills.dfn for the primary skill used to craft the item
Updated old-school single-click names for items (when AoS bit is disabled in client/server features) to display exceptional status and/or maker's mark for appropriate items
Fixed an issue with the display of titles for NPCs that used dictionary lookups for their title
Added new UOX.INI settings under the [settings] section:
	DISPLAYMAKERSMARK=1/0			// Controls if maker's marks on crafted items are shown on a global level
	SHOWNPCTITLESOVERHEAD=1/0 		// Controls whether NPC titles are shown over their heads
	SHOWINVULNERABLETAGOVERHEAD=0/1 // Controls whether invulnerable tags are shown overhead
	GLOBALRESTOCKMULTIPLIER=1.0 	// Global multiplier applied to RESTOCK property of items when loaded from DFNs
Added new UOX.INI settings under the [combat] section:
	PETCOMBATTRAINING=1/0 			// Controls whether pets can gain skills/stats from combat
	HIRELINGCOMBATTRAINING=1/0 		// Controls whether hirelings can gain skills/stats from combat
	NPCCOMBATTRAINING=0/1 			// Controls whether NPCs in general can gain skills/stats from combat
Fixed an issue where a player meeting the exact minimum requirements for crafting an item would always fail
Fixed a bug with teleporting off boats to valid nearby locations when double-clicking boat planks while standing on it
Updated behaviour of planks on boats:
	Boat key can unlock plank, but won't automatically open it
	Boat key used on an open, unlocked plank will lock and automatically close the plank
	Boat key used on an open, locked plank will unlock the plank and leave it open
	Player using an unlocked plank now opens it
	Player using an open plank will disembark (if onboard) or embark (if not) the boat
	Player can always open a locked plank if they're on the boat, even with no key, to allow disembarking the boat.
	Planks that are open, but locked cannot be accessed from outside the boat, and will close automatically after five of seconds
Updated behaviour of Magic Lock/Unlock spells:
	Magic Lock cannot be used on objects inside a multi
	Magic Lock applies difficulty to lock based on caster's Magery skill
	Magic Lock effect will last between 7 to 50 seconds (depending on caster's Magery skill)
	Any object that can be locked via Magic Lock spell can also be unlocked via Magic Unlock spell, if caster has high enough Magery skill
Fixed a bug where certain JS Functions, Methods and Properties would occasionally pass incorrect values to scripts that called upon them. Scripts that rely on the GetCurrentClock() Function, the .GetTimer() Method, .decayTime, .logTime, .oreTime and .fishTime Properties might need to be updated to account for the fix, which causes larger values to be passed in than before.
Fixed a bug related to GetCurrentClock() that caused decay and respawning of dungeon chests from working properly
Fixed a bug related to GetCurrentClock() and .logTime/.fishTime that prevented consumption and regeneration of log/fish resources from working properly
Added a new JS Function:
	GetStartTime()	// Returns a timestamp for when server started up
Fixed a bug related to GetCurrentClock() where server uptime stats in help menu was showing up incorrectly
Fixed a bug related to GetCurrentClock() and purging of registrations of visitors in public houses so their visits can be counted again later
Fixed a bug related to GetCurrentClock() that caused monster speech to not work properly
Fixed a Z-related issue that sometimes prevented players from fishing when standing on the shoreline next to the sea
Fixed an issue with Animal Lore skill that would always return a pet's loyalty level as "Wild"
Increased max range pets can be from their owner in order to still be included in moongate/teleporter travel from 12 to 24 tiles
Fixed an issue where the alchemy bonus damage for explosion potions was off by an order of magnitude
Fixed an issue where food IDs from foodlists referenced by other foodlists was not detected as valid food for tamed pets or NPCs
Fixed an issue where Alchemy crafting skill did not properly check for the existence of the crafting tool before proceeding with the crafting process
Fixed an issue that prevented the Teleport spell from working as intended
Fixed an issue where players would still get 1 gold even if STARTGOLD in uox.ini was set to 0
Fixed an issue with mount restrictions script that prevented players from mounting Unicorns, Ki-rins and Cu Sidhes
Fixed an issue where aggressive creatures wouldn't stop attacking player after being tamed (js/skill/taming.js)
Added a system message to inform the player when they've dropped out of stealth due to taking too many steps
Fixed an issue where hunger level of NPCs instantly dropped upon spawning because hunger timer was not active yet
Fixed a bug where character corpses would appear with a [unidentified] tag attached
Fixed an issue where the [unidentified] tag did not show up for unidentified magic items
Added ANIMAL tag to a small bunch of creature definitions (dfndata/creatures/creatures.dfn)
Fixed an issue with Bowcraft skill where player could not gain enough skill from crafting bows to reach min requirement for crossbows, and similiarly could not reach min requirement for heavy crossbows from crossbows
Updated default value of MURDERDECAYTIMER INI setting from 60 seconds to 28800 seconds (8 hours), to match the amount of time it would take for one (short term) murder count to decay on LBR-era OSI shards.
2022-08-25 02:13:00 +08:00

1659 lines
54 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 - 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<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 - 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<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 - 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<UI16>(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<UI16>(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<size_t>( 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<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 );
// 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<UI08>(Capped( percheck * 80, 10.0f, 60.0f ) ) );
percheck = ( mChar->GetIntelligence() / (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() / (R32)totalstats );
mChar->SetStrength( static_cast< UI08 >(Capped( percheck * 90, 10.0f, 60.0f ) ) );
percheck = ( mChar->GetDexterity() / (R32)totalstats );
mChar->SetDexterity( static_cast<UI08>(Capped( percheck * 90, 10.0f, 60.0f ) ) );
percheck = ( mChar->GetIntelligence() / (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( (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<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( 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<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 );
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<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(( 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<SI16>(20 + ( RandomNum( 0, 49 ))));
item->SetY( static_cast<SI16>(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<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 - 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<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 )
{
break;
}
}
}
if( mChar->IsNpc() )
mChar->Delete();
else
mChar->Dirty( UT_LOCATION );
// Play death music
if( pSock != nullptr )
Effects->doSocketMusic( pSock );
}