mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
1271 lines
37 KiB
C++
1271 lines
37 KiB
C++
// House code for deed creation
|
|
#include<algorithm>
|
|
|
|
#include "uox3.h"
|
|
#include "mapstuff.h"
|
|
#include "cServerDefinitions.h"
|
|
#include "ssection.h"
|
|
#include "CPacketSend.h"
|
|
#include "classes.h"
|
|
#include "regions.h"
|
|
#include "Dictionary.h"
|
|
#include "StringUtility.hpp"
|
|
#include "strutil.hpp"
|
|
|
|
#include "ObjectFactory.h"
|
|
|
|
using namespace std::string_literals;
|
|
|
|
bool CreateBoat( CSocket *s, CBoatObj *b, UI08 id2, UI08 boattype );
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - DoHouseTarget()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Triggered by double clicking a deed-> the deed's moreX is read for the house
|
|
//| section in house.dfn. Extra items can be added using HOUSE ITEM, (this includes
|
|
//| all doors!) and locked "LOCK" Space around the house with SPACEX/Y and CHAR
|
|
//| offset CHARX/Y/Z
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto DoHouseTarget( CSocket *mSock, UI16 houseEntry ) -> void
|
|
{
|
|
UI16 houseId = 0;
|
|
std::string tag, data;
|
|
auto sect = "HOUSE "s + util::ntos( houseEntry );
|
|
auto House = FileLookup->FindEntry( sect, house_def );
|
|
if( House )
|
|
{
|
|
for( const auto &sec : House->collection() )
|
|
{
|
|
auto tag = sec->tag;
|
|
auto data = sec->data;
|
|
if( util::upper( tag ) == "ID" )
|
|
{
|
|
houseId = static_cast<UI16>( std::stoul(std::string( util::trim( util::strip( data, "//" ))), nullptr, 0 ));
|
|
break;
|
|
}
|
|
}
|
|
if( !houseId )
|
|
{
|
|
Console.Error( util::format( "Bad house script: #%u\n", houseEntry ));
|
|
}
|
|
else
|
|
{
|
|
mSock->AddId( houseEntry );
|
|
if( houseId >= 0x4000 )
|
|
{
|
|
mSock->mtarget( static_cast<UI16>( houseId - 0x4000 ), 576 ); // Select a location for the building.
|
|
}
|
|
else
|
|
{
|
|
mSock->SendTargetCursor( 0, TARGET_BUILDHOUSE, 0, 9184 ); // Select a location for the house addon.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CreateHouseKey()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Create key for tent/house/boat
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void CreateHouseKey( CSocket *mSock, CChar *mChar, CMultiObj *house, UI16 houseId )
|
|
{
|
|
if( houseId >= 0x4000 )
|
|
{
|
|
std::string scriptName;
|
|
if(( houseId % 256 ) >= 0x70 && ( houseId % 256 ) <= 0x73 ) // It's a tent
|
|
{
|
|
scriptName = "tent_key";
|
|
}
|
|
else if(( houseId % 256 ) <= 0x18 ) // It's a boat
|
|
{
|
|
scriptName = "boat_key";
|
|
}
|
|
else // It's a house
|
|
{
|
|
scriptName = "house_key";
|
|
}
|
|
|
|
CItem *key = Items->CreateScriptItem( mSock, mChar, scriptName, 1, OT_ITEM, true );
|
|
if( ValidateObject( key ))
|
|
{
|
|
key->SetTempVar( CITV_MORE, house->GetSerial() );
|
|
key->SetType( IT_KEY );
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CreateHouseItems()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Create items for house as defined in house.dfn
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CreateHouseItems( CChar *mChar, std::vector<std::string> houseItems, CItem *house, UI16 houseId, SI16 x, SI16 y, SI08 z, UI08 worldNum ) -> void
|
|
{
|
|
std::string tag, data, UTag;
|
|
CScriptSection *HouseItem = nullptr;
|
|
CItem *hItem = nullptr;
|
|
|
|
if( ValidateObject( mChar ))
|
|
{
|
|
worldNum = mChar->WorldNumber();
|
|
}
|
|
|
|
for( const auto &entry : houseItems )
|
|
{
|
|
auto sect = "HOUSE ITEM "s + entry;
|
|
HouseItem = FileLookup->FindEntry( sect, house_def );
|
|
if( HouseItem )
|
|
{
|
|
SI16 hiX = x, hiY = y;
|
|
SI08 hiZ = z;
|
|
UI08 hWorld = house->WorldNumber();
|
|
UI16 hInstanceId = house->GetInstanceId();
|
|
hItem = nullptr; // clear it out
|
|
for( const auto &sec : HouseItem->collection() )
|
|
{
|
|
tag = sec->tag;
|
|
data = sec->data;
|
|
UTag = util::upper( tag );
|
|
data = util::trim( util::strip( data, "//" ));
|
|
if( UTag == "ITEM" )
|
|
{
|
|
hItem = Items->CreateBaseScriptItem( nullptr, data, worldNum, 1, hInstanceId );
|
|
if( hItem == nullptr )
|
|
{
|
|
Console << "Error in house creation, item " << data << " could not be made" << myendl;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
hItem->SetMovable( 2 );//Non-Moveable by default
|
|
hItem->SetLocation( house );
|
|
|
|
if( ValidateObject( mChar ))
|
|
{
|
|
hItem->SetOwner( mChar );
|
|
}
|
|
|
|
if( house->GetObjType() == OT_ITEM )
|
|
{
|
|
// House is not actually a house, but a house addon! Store reference to addon on item
|
|
hItem->SetTempVar( CITV_MORE, house->GetSerial() );
|
|
|
|
// Store a custom tag on addon to mark it as a house addon
|
|
TAGMAPOBJECT addonTagObject;
|
|
addonTagObject.m_Destroy = false;
|
|
addonTagObject.m_IntValue = 1;
|
|
addonTagObject.m_ObjectType = TAGMAP_TYPE_INT;
|
|
addonTagObject.m_StringValue = "";
|
|
hItem->SetTag( "addon", addonTagObject );
|
|
|
|
// Also add the addon's sub-items to the multi
|
|
CMultiObj *mMulti = house->GetMultiObj();
|
|
if( ValidateObject( mMulti ))
|
|
{
|
|
mMulti->AddToMulti( hItem );
|
|
}
|
|
|
|
// Do we need to rotate the addon?
|
|
if( hItem->GetTempVar( CITV_MOREZ ) != 0 )
|
|
{
|
|
// An alternate rotation for item exists, stored in CITV_MOREZ
|
|
// Let's assume default rotation is North/South oriented, and check for walls to the left of the addon:
|
|
bool wallFound = ( Map->CheckDynamicFlag( hItem->GetX() - 1, hItem->GetY(), hItem->GetZ(), worldNum, hInstanceId, TF_WALL ));
|
|
if( wallFound )
|
|
{
|
|
// What if it's placed in a corner? Look for north wall too:
|
|
bool northWallFound = ( Map->CheckDynamicFlag( hItem->GetX(), hItem->GetY() - 1, hItem->GetZ(), worldNum, hInstanceId, TF_WALL ));
|
|
if( northWallFound )
|
|
{
|
|
// Randomize between the two directions
|
|
if( RandomNum( 0, 1 ))
|
|
{
|
|
hItem->SetId( hItem->GetTempVar( CITV_MOREZ ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Found a wall west of addon, but none north! Let's rotate addon to face east:
|
|
hItem->SetId( hItem->GetTempVar( CITV_MOREZ ));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if( UTag == "DECAY" )
|
|
{
|
|
if( ValidateObject( hItem ))
|
|
{
|
|
hItem->SetDecayable( true );
|
|
}
|
|
}
|
|
else if( UTag == "NODECAY" )
|
|
{
|
|
if( ValidateObject( hItem ))
|
|
{
|
|
hItem->SetDecayable( false );
|
|
}
|
|
}
|
|
else if( UTag == "PACK" ) // put the item in the Builder's Backpack
|
|
{
|
|
if( ValidateObject( mChar ) && ValidateObject( hItem ))
|
|
{
|
|
CItem *pack = mChar->GetPackItem();
|
|
hItem->SetCont( pack );
|
|
hItem->PlaceInPack();
|
|
}
|
|
}
|
|
else if( UTag == "MOVEABLE" )
|
|
{
|
|
if( ValidateObject( hItem ))
|
|
{
|
|
hItem->SetMovable( 1 );
|
|
}
|
|
}
|
|
else if( UTag == "INVISIBLE" )
|
|
{
|
|
if( ValidateObject( hItem ))
|
|
{
|
|
hItem->SetVisible( VT_PERMHIDDEN );
|
|
}
|
|
}
|
|
else if( UTag == "LOCK" && houseId >= 0x4000 ) // lock it with the house key
|
|
{
|
|
if( ValidateObject( hItem ))
|
|
{
|
|
hItem->SetTempVar( CITV_MORE, house->GetSerial() );
|
|
if( hItem->GetType() == IT_HOUSESIGN )
|
|
{
|
|
// Also store a reference to the sign on the house itself
|
|
house->SetTempVar( CITV_MORE, hItem->GetSerial() );
|
|
}
|
|
}
|
|
}
|
|
else if( UTag == "FRONTDOOR" )
|
|
{
|
|
if( ValidateObject( hItem ))
|
|
{
|
|
TAGMAPOBJECT frontDoorTag;
|
|
frontDoorTag.m_Destroy = false;
|
|
frontDoorTag.m_StringValue = "front";
|
|
frontDoorTag.m_IntValue = static_cast<SI32>( frontDoorTag.m_StringValue.size() );
|
|
frontDoorTag.m_ObjectType = TAGMAP_TYPE_STRING;
|
|
hItem->SetTag( "DoorType", frontDoorTag );
|
|
|
|
// Lock it automatically since house is private when placed initially
|
|
hItem->SetType( IT_LOCKEDDOOR );
|
|
}
|
|
}
|
|
else if( UTag == "INTERIORDOOR" )
|
|
{
|
|
if( ValidateObject( hItem ))
|
|
{
|
|
TAGMAPOBJECT frontDoorTag;
|
|
frontDoorTag.m_Destroy = false;
|
|
frontDoorTag.m_StringValue = "interior";
|
|
frontDoorTag.m_IntValue = static_cast<SI32>( frontDoorTag.m_StringValue.size() );
|
|
frontDoorTag.m_ObjectType = TAGMAP_TYPE_STRING;
|
|
hItem->SetTag( "DoorType", frontDoorTag );
|
|
}
|
|
}
|
|
else if( UTag == "X" ) //offset + or - from the center of the house:
|
|
{
|
|
hiX += static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "Y" )
|
|
{
|
|
hiY += static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "Z" )
|
|
{
|
|
hiZ += static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
}
|
|
if( ValidateObject( hItem ))
|
|
{
|
|
if( hItem->GetCont() == nullptr )
|
|
{
|
|
hItem->SetLocation( hiX, hiY, hiZ, hWorld, hInstanceId );
|
|
}
|
|
|
|
if( hItem->GetMulti() == INVALIDSERIAL && hItem->GetType() != IT_HOUSESIGN && hItem->GetType() != IT_DOOR )
|
|
{
|
|
// Add item to multi. It's not a door, nor a sign - could be stairs in a custom foundation!
|
|
CMultiObj *mMulti = FindMulti( house );
|
|
if( ValidateObject( mMulti ))
|
|
{
|
|
hItem->SetMulti( mMulti );
|
|
mMulti->AddToMulti( hItem );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CheckForValidHouseLocation()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Check whether the chosen location is valid for house placement
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CheckForValidHouseLocation( CSocket *mSock, CChar *mChar, SI16 x, SI16 y, SI08 z,
|
|
SI16 spaceX, SI16 spaceY, UI08 worldNum, UI16 instanceId, bool isBoat, bool isMulti ) -> bool
|
|
{
|
|
auto [mapWidth, mapHeight] = Map->SizeOfMap(worldNum);
|
|
if(( x + spaceX > mapWidth || x - spaceX < 0 || y + spaceY > mapHeight || y - spaceY < 0 ) && !mChar->IsGM() )
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 577 ); // =You cannot build your house there!
|
|
}
|
|
return false;
|
|
}
|
|
|
|
SI16 curX, curY;
|
|
if( !isMulti )
|
|
{
|
|
// House addon
|
|
for( SI16 k = 0; k <= spaceX - 1; ++k )
|
|
{
|
|
curX = x + k;
|
|
for( SI16 l = 0; l <= spaceY - 1; ++l )
|
|
{
|
|
curY = y + l;
|
|
if( ValidateObject( mChar ) && !mChar->IsGM() )
|
|
{
|
|
CMultiObj *mMulti = FindMulti( curX, curY, z, worldNum, instanceId );
|
|
if( !ValidateObject( mMulti ) || !mMulti->IsOwner( mChar ))
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9027 ); // This object must be placed in your house
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check that house addon won't block a door/staircase when placed
|
|
auto itemList = mMulti->GetItemsInMultiList();
|
|
for( const auto &mItem : itemList->collection() )
|
|
{
|
|
if( !ValidateObject( mItem ))
|
|
continue;
|
|
|
|
if( mItem->GetType() == 12 || mItem->GetType() == 13 )
|
|
{
|
|
SI08 mItemZ = mItem->GetZ();
|
|
if(( mItemZ > z ) && (( mItemZ - z ) >= 20 )) // Ignore doors on floors above
|
|
{
|
|
continue;
|
|
}
|
|
else if(( mItemZ < z ) && ( z - mItemZ ) >= 20 ) // Ignore doors on floors below, too!
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( mItem->IsDoorOpen() )
|
|
{
|
|
// Make sure to check against the distance from the door in it's closed state, rather than it's open state!
|
|
auto doorX = mItem->GetTag( "DOOR_X" );
|
|
auto doorY = mItem->GetTag( "DOOR_Y" );
|
|
UI16 origX = mItem->GetX() - doorX.m_IntValue;
|
|
UI16 origY = mItem->GetY() - doorY.m_IntValue;
|
|
|
|
// Make sure to check absolute values for the distance, since values could be negative
|
|
if( abs( x - origX ) < 2 && abs( y - origY ) < 2 )
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9028 ); // You cannot place a house-addon adjacent to a door.
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( GetDist( Point3_st( x, y, z ), mItem->GetLocation() ) < 2 )
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9028 ); // You cannot place a house-addon adjacent to a door.
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Don't allow placing addon if it collides with a blocking tile at same height
|
|
bool locationBlocked = ( Map->CheckDynamicFlag( curX, curY, z, worldNum, instanceId, TF_BLOCKING ));
|
|
if( locationBlocked )
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9097 ); // You cannot place this house-addon there, location is blocked!
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Multi
|
|
// Loop through each tile in the multi's affected area, check if it contains any blocking tiles (dynamic, static or map)
|
|
spaceX += 1; // Extra space around left and right side of house
|
|
spaceY += 5; // Extra space in front and back of house
|
|
|
|
for( SI16 k = -spaceX; k <= spaceX; ++k )
|
|
{
|
|
curX = x + k;
|
|
for( SI16 l = (-spaceY + 1); l <= spaceY; ++l )
|
|
{
|
|
curY = y + l;
|
|
bool checkOnlyMultis = false;
|
|
bool checkOnlyNonMultis = false;
|
|
bool checkForRoads = true;
|
|
|
|
if(( k < ( -spaceX + 1 )) && ( l > -spaceY + 3 && l < spaceY -3 ))
|
|
{
|
|
// If looking at tiles in the immediate border around house
|
|
checkOnlyNonMultis = true;
|
|
}
|
|
else if(( l < ( -spaceY + 4 )) || ( l > ( spaceY - 5 )))
|
|
{
|
|
// If looking at extended area behind and/or in front of house
|
|
// Check only for other multis in the 4 tiles furthest in the back of the house,
|
|
// and in the 4 tiles furthest in front of the house; we don't care about blocking
|
|
// statics like trees in this area
|
|
checkOnlyMultis = true;
|
|
checkForRoads = false;
|
|
}
|
|
else
|
|
{
|
|
// check within main boundary of house itself
|
|
checkForRoads = true;
|
|
}
|
|
|
|
UI08 retVal1 = Map->ValidMultiLocation( curX, curY, z, worldNum, instanceId, !isBoat, checkOnlyMultis, checkOnlyNonMultis, checkForRoads );
|
|
auto retVal2 = FindMulti( curX, curY, z, worldNum, instanceId );
|
|
|
|
if( retVal1 != 1 || retVal2 != nullptr )
|
|
{
|
|
if( isBoat )
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 7 ); // You cannot place your boat there.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( retVal2 )
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 577 ); // You cannot build your house there!
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch( retVal1 )
|
|
{
|
|
case 0: // Blocked by terrain
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9029 ); // You cannot build your house on the selected terrain!
|
|
}
|
|
break;
|
|
case 2: // Blocked by static item
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9030 ); // You cannot build your house there - location is blocked by one or more items!
|
|
}
|
|
break;
|
|
case 3: // Blocked by region settings
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9031 ); // You cannot build your house there - houses not allowed in this region!
|
|
}
|
|
break;
|
|
default:
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 577 ); // You cannot build your house there!
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
auto FindNearbyChars( SI16 x, SI16 y, UI08 worldNumber, UI16 instanceId, UI16 distance ) -> std::vector<CChar *>;
|
|
auto FindNearbyItems( SI16 x, SI16 y, UI08 worldNumber, UI16 instanceId, UI16 distance ) -> std::vector<CItem *>;
|
|
UI16 AddRandomColor( const std::string& colorlist );
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - BuildHouse()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Called when player attempts to place a tent/house/boat
|
|
//o------------------------------------------------------------------------------------------------o
|
|
CMultiObj * BuildHouse( CSocket *mSock, UI16 houseEntry, bool checkLocation = true, SI16 xLoc = -1, SI16 yLoc = -1, SI08 zLoc = 127, UI08 worldNumber = 0, UI16 instanceId = 0 )
|
|
{
|
|
if( mSock && mSock->GetDWord( 11 ) == INVALIDSERIAL )
|
|
return nullptr;
|
|
|
|
if( !houseEntry )
|
|
return nullptr;
|
|
|
|
CChar *mChar = nullptr;
|
|
if( mSock )
|
|
{
|
|
mChar = mSock->CurrcharObj();
|
|
if( !ValidateObject( mChar ))
|
|
{
|
|
return nullptr;
|
|
}
|
|
else
|
|
{
|
|
worldNumber = mChar->WorldNumber();
|
|
instanceId = mChar->GetInstanceId();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No socket found, do we have coordinates?
|
|
if( xLoc == -1 || yLoc == -1 || zLoc == 127 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Use coordinates if provided in arguments, otherwise rely on values stored on socket
|
|
const SI16 x = xLoc > -1 ? xLoc : mSock->GetWord( 11 );
|
|
const SI16 y = yLoc > -1 ? yLoc : mSock->GetWord( 13 );
|
|
SI08 tileHeight = zLoc != 127 ? 0 : Map->TileHeight( mSock->GetWord( 17 ));
|
|
SI08 z = zLoc != 127 ? zLoc : static_cast<SI08>( mSock->GetByte( 16 ) + tileHeight );
|
|
|
|
if( mSock )
|
|
{
|
|
if( mSock->GetDWord( 7 ) != INVALIDSERIAL || GetDist( mChar->GetLocation(), Point3_st( x, y, z )) >= DIST_BUILDRANGE )
|
|
{
|
|
mSock->SysMessage( 577 ); // You cannot build your house there!
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
SI16 sx = 0, sy = 0, cx = 0, cy = 0, bx = 0, by = 0;
|
|
SI08 cz = 7;
|
|
std::vector<std::string> houseItems;
|
|
houseItems.resize( 0 );
|
|
bool itemsWillDecay = false;
|
|
bool isBoat = false;
|
|
bool isDamageable = false;
|
|
std::string houseDeed, tag, data, UTag;
|
|
UI16 houseId = 0;
|
|
// Use default values matching a small house if no tags present
|
|
UI16 maxLockdowns = 425;
|
|
UI16 maxSecureContainers = 3;
|
|
UI16 maxFriends = 50;
|
|
UI16 maxGuests = 50;
|
|
UI16 maxBans = 50;
|
|
UI16 maxOwners = 8;
|
|
UI16 maxVendors = 10;
|
|
UI16 maxTrashContainers = 1;
|
|
UI16 scriptTrigger = 0;
|
|
UI16 multiColour = 0;
|
|
SI32 weightMax = 400; // Default
|
|
UI16 maxItems = 125; // Default
|
|
std::string customTagName;
|
|
std::string customTagStringValue;
|
|
TAGMAPOBJECT customTag;
|
|
std::map<std::string, TAGMAPOBJECT> customTagMap;
|
|
|
|
std::string sect = "HOUSE " + util::ntos( houseEntry );
|
|
CScriptSection *House = FileLookup->FindEntry( sect, house_def );
|
|
if( House == nullptr )
|
|
return nullptr; // House entry not found
|
|
|
|
std::vector<UI16> scriptIds;
|
|
for( const auto &sec : House->collection() )
|
|
{
|
|
tag = sec->tag;
|
|
data = sec->data;
|
|
UTag = util::upper( tag );
|
|
data = util::trim( util::strip( data, "//" ));
|
|
|
|
if( UTag == "ID" )
|
|
{
|
|
houseId = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "SPACEX" )
|
|
{
|
|
sx = static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "SPACEY" )
|
|
{
|
|
sy = static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "CHARX" )
|
|
{
|
|
cx = static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "CHARY" )
|
|
{
|
|
cy =static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "CHARZ" )
|
|
{
|
|
cz = static_cast<SI08>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "BANX" )
|
|
{
|
|
bx = static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "BANY" )
|
|
{
|
|
by = static_cast<SI16>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "Z" ) //to allow offsetting houses in Z to fix multis like Farmer's Cabin
|
|
{
|
|
z = z + static_cast<SI08>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "ITEMSDECAY" )
|
|
{
|
|
itemsWillDecay = ( static_cast<SI16>( std::stoi( data, nullptr, 0 )) == 1 );
|
|
}
|
|
else if( UTag == "HOUSE_ITEM" )
|
|
{
|
|
houseItems.push_back( data );
|
|
}
|
|
else if( UTag == "HOUSE_DEED" )
|
|
{
|
|
houseDeed = data;
|
|
}
|
|
else if( UTag == "BOAT" )
|
|
{
|
|
isBoat = true; //Boats
|
|
}
|
|
else if( UTag == "DAMAGEABLE" )
|
|
{
|
|
isDamageable = ( static_cast<SI16>( std::stoi( data, nullptr, 0 )) == 1 );
|
|
}
|
|
else if( UTag == "MAXITEMS" )
|
|
{
|
|
maxItems = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "WEIGHTMAX" )
|
|
{
|
|
weightMax = static_cast<SI32>( std::stoi( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "MAXBANS" )
|
|
{
|
|
maxBans = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "MAXFRIENDS" )
|
|
{
|
|
maxFriends = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "MAXGUESTS" )
|
|
{
|
|
maxGuests = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "MAXLOCKDOWNS" )
|
|
{
|
|
maxLockdowns = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "MAXTRASHCONTAINERS" )
|
|
{
|
|
maxTrashContainers = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "MAXOWNERS" )
|
|
{
|
|
maxOwners = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "MAXSECURECONTAINERS" )
|
|
{
|
|
maxSecureContainers = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "MAXVENDORS" )
|
|
{
|
|
maxVendors = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "SCRIPT" )
|
|
{
|
|
scriptTrigger = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
if( scriptTrigger > 0 )
|
|
{
|
|
scriptIds.push_back( scriptTrigger );
|
|
}
|
|
}
|
|
else if( UTag == "COLOUR" || UTag == "COLOR" )
|
|
{
|
|
multiColour = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
|
|
}
|
|
else if( UTag == "COLOURLIST" || UTag == "COLORLIST" )
|
|
{
|
|
multiColour = AddRandomColor( data );
|
|
}
|
|
else if( UTag == "CUSTOMSTRINGTAG" )
|
|
{
|
|
auto ssecs = util::parse( data, " " );
|
|
auto count = 0;
|
|
std::string result;
|
|
for( auto &sec : ssecs )
|
|
{
|
|
if( count > 0 )
|
|
{
|
|
if( count == 1 )
|
|
{
|
|
result = util::trim( util::strip( sec, "//" ));
|
|
}
|
|
else
|
|
{
|
|
result = result + " "s + std::string(util::trim( util::strip( sec, "//" )));
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
customTagName = util::trim( util::strip( ssecs[0], "//" ));
|
|
customTagStringValue = result;
|
|
|
|
if( !customTagName.empty() && !customTagStringValue.empty() )
|
|
{
|
|
customTag.m_Destroy = false;
|
|
customTag.m_StringValue = customTagStringValue;
|
|
customTag.m_IntValue = static_cast<SI32>( customTag.m_StringValue.size() );
|
|
customTag.m_ObjectType = TAGMAP_TYPE_STRING;
|
|
customTagMap.insert( std::pair<std::string, TAGMAPOBJECT>( customTagName, customTag ));
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( util::format( "Invalid data found in CUSTOMSTRINGTAG tag inside House script [%s] - Supported data format: <tagName> <text>", sect.c_str() ));
|
|
}
|
|
break;
|
|
}
|
|
else if( UTag == "CUSTOMINTTAG" )
|
|
{
|
|
auto ssecs = util::parse( data, " " );
|
|
auto count = 0;
|
|
std::string result;
|
|
for( auto &sec : ssecs )
|
|
{
|
|
if( count > 0 )
|
|
{
|
|
if( count == 1 )
|
|
{
|
|
result = util::trim( util::strip( sec, "//" ));
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
customTagName = util::trim( util::strip( ssecs[0], "//" ));
|
|
customTagStringValue = result;
|
|
if( !customTagName.empty() && !customTagStringValue.empty() )
|
|
{
|
|
customTag.m_Destroy = false;
|
|
customTag.m_IntValue = std::stoi( std::string(util::trim( util::strip( customTagStringValue, "//" ))), nullptr, 0 );
|
|
customTag.m_ObjectType = TAGMAP_TYPE_INT;
|
|
customTag.m_StringValue = "";
|
|
customTagMap.insert( std::pair<std::string, TAGMAPOBJECT>( customTagName, customTag ));
|
|
if( count > 1 )
|
|
{
|
|
Console.Warning( util::format( "Multiple values detected for CUSTOMINTTAG in House script [%s] - only first value will be used! Supported data format: <tagName> <value>", sect.c_str() ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( util::format( "Invalid data found in CUSTOMINTTAG tag in House script [%s] - Supported data format: <tagName> <value>", sect.c_str() ));
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !houseId )
|
|
{
|
|
Console.Error( util::format( "Bad house script # %u!", houseEntry ));
|
|
return nullptr;
|
|
}
|
|
|
|
const bool isMulti = ( houseId >= 0x4000 );
|
|
if( !isMulti )
|
|
{
|
|
// Trying to place a house addon, let's subtract the tileHeight of the targeted tile so it doesn't mess with placement rules
|
|
z -= tileHeight;
|
|
}
|
|
|
|
if( checkLocation && !CheckForValidHouseLocation( mSock, mChar, x, y, z, sx, sy, worldNumber, instanceId, isBoat, isMulti ))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
constexpr UI16 maxSize = 1024;
|
|
std::string temp;
|
|
CMultiObj *house = nullptr;
|
|
CItem *fakeHouse = nullptr;
|
|
if( isMulti )
|
|
{
|
|
if(( houseId % 256 ) > 20 )
|
|
{
|
|
if( ValidateObject( mChar ))
|
|
{
|
|
temp = oldstrutil::format( maxSize, "%s's house", mChar->GetName().c_str() ); // This will make the little deed item you see when you have showhs on say the person's name, thought it might be helpful for GMs.
|
|
}
|
|
else
|
|
{
|
|
temp = "a house";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Assign name of boat deed/model ship to newly created boat
|
|
temp = "";
|
|
if( ValidateObject( mChar ))
|
|
{
|
|
temp = mChar->GetSpeechItem()->GetTitle();
|
|
}
|
|
|
|
if( temp == "" )
|
|
{
|
|
temp = "a ship";
|
|
}
|
|
}
|
|
house = Items->CreateMulti( temp, houseId, isBoat, worldNumber, instanceId );
|
|
if( house == nullptr )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
house->SetLocation( x, y, z );
|
|
house->SetDecayable( itemsWillDecay );
|
|
house->SetDeed( houseDeed ); // crackerjack 8/9/99 - for converting back *into* deeds
|
|
if( ValidateObject( mChar ))
|
|
{
|
|
house->SetOwner( mChar );
|
|
}
|
|
// Add all script IDs to multi's list of script IDs
|
|
for( auto scriptId : scriptIds )
|
|
{
|
|
house->AddScriptTrigger( scriptId );
|
|
}
|
|
house->SetMaxBans( maxBans );
|
|
house->SetMaxFriends( maxFriends );
|
|
house->SetMaxGuests( maxGuests );
|
|
house->SetMaxLockdowns( maxLockdowns );
|
|
house->SetMaxTrashContainers( maxTrashContainers );
|
|
house->SetMaxSecureContainers( maxSecureContainers );
|
|
house->SetMaxOwners( maxOwners );
|
|
house->SetMaxVendors( maxVendors );
|
|
house->SetColour( multiColour );
|
|
house->SetDamageable( isDamageable );
|
|
|
|
time_t buildTimestamp = std::chrono::system_clock::to_time_t( std::chrono::system_clock::now() );
|
|
house->SetBuildTimestamp( buildTimestamp );
|
|
|
|
// Add custom tags to multi
|
|
for( const auto& [key, value] : customTagMap )
|
|
{
|
|
house->SetTag( key, value );
|
|
}
|
|
|
|
// Find corners of new house
|
|
SI16 multiX1 = 0;
|
|
SI16 multiY1 = 0;
|
|
SI16 multiX2 = 0;
|
|
SI16 multiY2 = 0;
|
|
Map->MultiArea( house, multiX1, multiY1, multiX2, multiY2 );
|
|
|
|
// Set ban location X and Y offsets.
|
|
if( bx == 0 && by == 0 )
|
|
{
|
|
// If BANX/BANY were not specified in house DFN, use calculated SE corner of house instead
|
|
bx = multiX2;
|
|
by = multiY2;
|
|
}
|
|
else
|
|
{
|
|
// Use offset values specified in BANX/BANY
|
|
bx = x + bx;
|
|
by = y + by;
|
|
}
|
|
house->SetBanX( bx );
|
|
house->SetBanY( by );
|
|
|
|
// Move characters out of the way
|
|
for( auto &ourChar : FindNearbyChars( x, y, worldNumber, instanceId, std::max( sx, sy )))
|
|
{
|
|
if(( ourChar->GetX() >= multiX1 && ourChar->GetX() <= multiX2 ) &&
|
|
( ourChar->GetY() >= multiY1 && ourChar->GetY() <= multiY2 ) &&
|
|
( ourChar->GetZ() >= house->GetZ() - 16 ))
|
|
{
|
|
// Move character (NPC or PC, online or offline) to corner of multi so they don't get stuck in house!
|
|
ourChar->SetLocation( bx, by, house->GetZ() );
|
|
}
|
|
}
|
|
|
|
// Move items out of the way
|
|
for( auto &ourItem : FindNearbyItems( x, y, worldNumber, instanceId, std::max( sx, sy )))
|
|
{
|
|
if( ourItem->GetVisible() == 0 && ourItem->GetObjType() != OT_MULTI && ourItem->GetObjType() != OT_BOAT )
|
|
{
|
|
if(( ourItem->GetX() >= multiX1 && ourItem->GetX() <= multiX2 ) &&
|
|
( ourItem->GetY() >= multiY1 && ourItem->GetY() <= multiY2 ) &&
|
|
( ourItem->GetZ() >= house->GetZ() - 16 ))
|
|
{
|
|
// Move item to corner of multi so they don't get stuck underneath the house!
|
|
ourItem->SetLocation( bx, by, house->GetZ() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// House addon
|
|
if( ValidateObject( mChar ))
|
|
{
|
|
fakeHouse = Items->CreateItem( mSock, mChar, houseId, 1, 0, OT_ITEM );
|
|
}
|
|
else
|
|
{
|
|
fakeHouse = Items->CreateItem( nullptr, nullptr, houseId, 1, 0, OT_ITEM, false, true, worldNumber, instanceId, xLoc, yLoc, zLoc );
|
|
}
|
|
if( fakeHouse == nullptr )
|
|
{
|
|
return nullptr;
|
|
}
|
|
fakeHouse->SetLocation( x, y, z );
|
|
|
|
CMultiObj *mMulti = FindMulti( fakeHouse );
|
|
if( checkLocation && ValidateObject( mMulti ))
|
|
{
|
|
if( mMulti->GetLockdownCount() < mMulti->GetMaxLockdowns() )
|
|
{
|
|
mMulti->LockDownItem( fakeHouse );
|
|
}
|
|
else
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9032 ); // This house has reached the limit on locked down items already!
|
|
}
|
|
fakeHouse->Delete();
|
|
return nullptr;
|
|
}
|
|
}
|
|
else if( checkLocation )
|
|
{
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 9033 ); // House addons can only be placed in houses!
|
|
}
|
|
fakeHouse->Delete();
|
|
return nullptr;
|
|
}
|
|
|
|
if( ValidateObject( mChar ))
|
|
{
|
|
fakeHouse->SetOwner( mChar );
|
|
}
|
|
else
|
|
{
|
|
fakeHouse->SetOwner( nullptr );
|
|
}
|
|
fakeHouse->SetDecayable( false );
|
|
fakeHouse->SetVisible( VT_PERMHIDDEN );
|
|
|
|
// Store name of deed in a custom tag on the addon
|
|
TAGMAPOBJECT deedObject;
|
|
deedObject.m_Destroy = false;
|
|
deedObject.m_StringValue = houseDeed;
|
|
deedObject.m_IntValue = static_cast<SI32>( deedObject.m_StringValue.size() );
|
|
deedObject.m_ObjectType = TAGMAP_TYPE_STRING;
|
|
fakeHouse->SetTag( "deed", deedObject );
|
|
}
|
|
|
|
if( isBoat ) // Boats
|
|
{
|
|
CBoatObj *bObj = static_cast<CBoatObj *>( house );
|
|
if( bObj == nullptr || !CreateBoat( mSock, bObj, ( houseId % 256 ), houseEntry ))
|
|
{
|
|
house->Delete();
|
|
return nullptr;
|
|
}
|
|
|
|
bObj->SetWeightMax( weightMax );
|
|
bObj->SetMaxItems( maxItems );
|
|
bObj->SetDamageable( isDamageable );
|
|
}
|
|
|
|
if( ValidateObject( mChar ))
|
|
{
|
|
mChar->GetSpeechItem()->Delete();
|
|
mChar->SetSpeechItem( nullptr );
|
|
CreateHouseKey( mSock, mChar, house, houseId );
|
|
}
|
|
|
|
if( !houseItems.empty() )
|
|
{
|
|
if( isMulti )
|
|
{
|
|
fakeHouse = house;
|
|
}
|
|
CreateHouseItems( mChar, houseItems, fakeHouse, houseId, x, y, z, worldNumber );
|
|
}
|
|
|
|
if( ValidateObject( mChar ) && ( isMulti || isBoat ))
|
|
{
|
|
mChar->SetLocation( x + cx, y + cy, z + cz );
|
|
|
|
//Teleport pets as well
|
|
auto myPets = mChar->GetPetList();
|
|
for( const auto &pet : myPets->collection() )
|
|
{
|
|
if( ValidateObject( pet ) && pet->GetNpcAiType() != AI_PLAYERVENDOR )
|
|
{
|
|
if( !pet->GetMounted() && pet->IsNpc() && ObjInRange( mChar, pet, DIST_SAMESCREEN ))
|
|
{
|
|
pet->SetLocation( mChar );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return house;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - BuildBaseMulti()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Called when a script attempts to create a base multi from raw multi ID
|
|
//o------------------------------------------------------------------------------------------------o
|
|
CMultiObj * BuildBaseMulti( UI16 multiId, SI16 xLoc = -1, SI16 yLoc = -1, SI08 zLoc = 127, UI08 worldNumber = 0, UI16 instanceId = 0 )
|
|
{
|
|
if( !multiId )
|
|
return nullptr;
|
|
|
|
// Do we have coordinates?
|
|
if( xLoc == -1 || yLoc == -1 || zLoc == 127 )
|
|
return nullptr;
|
|
|
|
SI16 sx = 0, sy = 0;
|
|
[[maybe_unused]] SI16 cx = 0;
|
|
[[maybe_unused]] SI16 cy = 0;
|
|
[[maybe_unused]] SI16 bx = 0;
|
|
[[maybe_unused]] SI16 by = 0;
|
|
[[maybe_unused]] SI08 cz = 7;
|
|
|
|
// If multiId provided was lower than 0x4000, add 0x4000 so we get the ID the system expects
|
|
if( multiId < 0x4000 )
|
|
{
|
|
multiId += 0x4000;
|
|
}
|
|
|
|
CMultiObj *iMulti = nullptr;
|
|
std::string temp = "a multi";
|
|
iMulti = Items->CreateMulti( temp, multiId, false, worldNumber, instanceId, true );
|
|
if( iMulti == nullptr )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
iMulti->SetLocation( xLoc, yLoc, zLoc );
|
|
iMulti->SetDecayable( false );
|
|
|
|
// Add all script IDs to multi's list of script IDs
|
|
iMulti->SetDamageable( false );
|
|
|
|
time_t buildTimestamp = std::chrono::system_clock::to_time_t( std::chrono::system_clock::now() );
|
|
iMulti->SetBuildTimestamp( buildTimestamp );
|
|
|
|
// Find corners of new iMulti
|
|
SI16 multiX1 = 0;
|
|
SI16 multiY1 = 0;
|
|
SI16 multiX2 = 0;
|
|
SI16 multiY2 = 0;
|
|
Map->MultiArea( iMulti, multiX1, multiY1, multiX2, multiY2 );
|
|
|
|
// Move characters out of the way
|
|
for( auto &ourChar : FindNearbyChars( xLoc, yLoc, worldNumber, instanceId, std::max( sx, sy )))
|
|
{
|
|
if(( ourChar->GetX() >= multiX1 && ourChar->GetX() <= multiX2 ) &&
|
|
( ourChar->GetY() >= multiY1 && ourChar->GetY() <= multiY2 ) &&
|
|
( ourChar->GetZ() >= iMulti->GetZ() - 16 ))
|
|
{
|
|
// Move character (NPC or PC, online or offline) to corner of multi so they don't get stuck in multi!
|
|
ourChar->SetLocation( multiX2, multiY2, iMulti->GetZ() );
|
|
}
|
|
}
|
|
|
|
// Move items out of the way
|
|
for( auto &ourItem : FindNearbyItems( xLoc, yLoc, worldNumber, instanceId, std::max( sx, sy )))
|
|
{
|
|
if( ourItem->GetVisible() == 0 && ourItem->GetObjType() != OT_MULTI && ourItem->GetObjType() != OT_BOAT )
|
|
{
|
|
if(( ourItem->GetX() >= multiX1 && ourItem->GetX() <= multiX2 ) &&
|
|
( ourItem->GetY() >= multiY1 && ourItem->GetY() <= multiY2 ) &&
|
|
( ourItem->GetZ() >= iMulti->GetZ() - 16 ))
|
|
{
|
|
// Move item to corner of multi so they don't get stuck underneath the multi!
|
|
ourItem->SetLocation( multiX2, multiY2, iMulti->GetZ() );
|
|
}
|
|
}
|
|
}
|
|
|
|
return iMulti;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - KillKeysFunctor()
|
|
//| KillKeys()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Called when turning a house/boat into a deed, or when transferring to new owner
|
|
//|
|
|
//| Notes - This function is rather CPU-expensive, but AFAIK there is no
|
|
//| better way to find all keys than to do it this way.. :/
|
|
//o------------------------------------------------------------------------------------------------o
|
|
bool KillKeysFunctor( CBaseObject *a, [[maybe_unused]] UI32 &b, void *extraData )
|
|
{
|
|
UI32 *ourData = (UI32 *)extraData;
|
|
SERIAL targSerial = ourData[0];
|
|
SERIAL charSerial = ourData[1];
|
|
|
|
if( ValidateObject( a ) && a->CanBeObjType( OT_ITEM ))
|
|
{
|
|
CItem *i = static_cast<CItem *>( a );
|
|
if( !charSerial )
|
|
{
|
|
if( i->GetType() == IT_KEY && i->GetTempVar( CITV_MORE ) == targSerial )
|
|
{
|
|
// Delete all copies of the key!
|
|
if( ValidateObject( i->GetMultiObj() ))
|
|
{
|
|
// Remove from multi before deleting
|
|
i->SetMulti( INVALIDSERIAL, false );
|
|
}
|
|
i->Delete();
|
|
}
|
|
else if( i->GetId() >= 0x1769 && i->GetId() <= 0x176b ) // Keyrings
|
|
{
|
|
// Also look in keyrings - first get count of keys in keyring
|
|
SI32 keyCount = i->GetTag( "keys" ).m_IntValue;
|
|
if( keyCount > 0 )
|
|
{
|
|
for( SI32 j = 1; j < keyCount + 1; j++ )
|
|
{
|
|
std::string keyTag = "key" + std::to_string( j ) + "more";
|
|
TAGMAPOBJECT keyMore = i->GetTag( keyTag );
|
|
if( util::ston<SERIAL>( keyMore.m_StringValue ) == targSerial )
|
|
{
|
|
// More value of key in keyring matches house serial
|
|
TAGMAPOBJECT localObject;
|
|
localObject.m_Destroy = false;
|
|
localObject.m_IntValue = 0;
|
|
localObject.m_ObjectType = TAGMAP_TYPE_STRING;
|
|
localObject.m_StringValue = "0";
|
|
i->SetTag( keyTag, localObject );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( i->GetType() == IT_KEY && i->GetTempVar( CITV_MORE ) == targSerial )
|
|
{
|
|
CChar *itemPackOwner = FindItemOwner( i );
|
|
if( ValidateObject( itemPackOwner ))
|
|
{
|
|
// Key held somewhere in character's backpack or bankbox
|
|
if( itemPackOwner->GetSerial() == charSerial )
|
|
{
|
|
i->Delete();
|
|
}
|
|
}
|
|
else if( i->GetMovable() == 3 && ValidateObject( i->GetMultiObj() ))
|
|
{
|
|
// Locked down in character's house.
|
|
if( i->GetMultiObj()->GetOwnerObj()->GetSerial() == charSerial )
|
|
{
|
|
// Remove from multi before deleting! Maybe this should be in Cleanup()?
|
|
i->SetMulti( INVALIDSERIAL, false );
|
|
}
|
|
i->Delete();
|
|
}
|
|
else if( ValidateObject( i->GetCont() ) && FindRootContainer( i )->GetOwner() == charSerial )
|
|
{
|
|
// In a container owned by character
|
|
i->Delete();
|
|
}
|
|
else
|
|
{
|
|
// On the ground, not in a container, not locked down
|
|
if( i->GetMovable() != 3 && i->GetCont() == nullptr )
|
|
{
|
|
i->Delete();
|
|
}
|
|
}
|
|
}
|
|
else if( i->GetId() >= 0x1769 && i->GetId() <= 0x176b ) // Keyrings
|
|
{
|
|
CChar *itemPackOwner = FindItemOwner( i );
|
|
// If In backpack or bank, or locked down in character's house, or inside a container in character's house, or
|
|
// on the ground of character's house but not locked down
|
|
if(( ValidateObject( itemPackOwner ) && itemPackOwner->GetSerial() == charSerial ) ||
|
|
( i->GetMovable() == 3 && ValidateObject( i->GetMultiObj() ) && i->GetMultiObj()->GetOwnerObj()->GetSerial() == charSerial ) ||
|
|
( ValidateObject( i->GetCont() ) && FindRootContainer( i )->GetOwner() == charSerial ) ||
|
|
( i->GetMovable() != 3 && i->GetCont() == nullptr ))
|
|
{
|
|
// Also look in keyrings - first get count of keys in keyring
|
|
SI32 keyCount = i->GetTag( "keys" ).m_IntValue;
|
|
if( keyCount > 0 )
|
|
{
|
|
for( SI32 j = 1; j < keyCount + 1; j++ )
|
|
{
|
|
std::string keyTag = "key" + std::to_string( j ) + "more";
|
|
TAGMAPOBJECT keyMore = i->GetTag( keyTag );
|
|
if( util::ston<SERIAL>( keyMore.m_StringValue ) == targSerial )
|
|
{
|
|
// More value of key in keyring matches house serial
|
|
TAGMAPOBJECT localObject;
|
|
localObject.m_Destroy = false;
|
|
localObject.m_IntValue = 0;
|
|
localObject.m_ObjectType = TAGMAP_TYPE_STRING;
|
|
localObject.m_StringValue = "0";
|
|
i->SetTag( keyTag, localObject );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
void KillKeys( SERIAL targSerial, SERIAL charSerial = INVALIDSERIAL )
|
|
{
|
|
if( charSerial == INVALIDSERIAL )
|
|
{
|
|
// if no character serial is provided, just kill ALL keys that match the target serial
|
|
UI32 toPass[1];
|
|
toPass[0] = targSerial;
|
|
UI32 b = 0;
|
|
ObjectFactory::GetSingleton().IterateOver( OT_ITEM, b, toPass, &KillKeysFunctor );
|
|
}
|
|
else
|
|
{
|
|
// if character serial is provided, kill only keys belonging to said character, if key matches target serial
|
|
UI32 toPass[2];
|
|
toPass[0] = targSerial;
|
|
toPass[1] = charSerial;
|
|
UI32 b = 0;
|
|
ObjectFactory::GetSingleton().IterateOver( OT_ITEM, b, toPass, &KillKeysFunctor );
|
|
}
|
|
}
|
|
|