mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
915 lines
31 KiB
C++
915 lines
31 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 "ObjectFactory.h"
|
|
|
|
using namespace std::string_literals ;
|
|
|
|
bool CreateBoat( CSocket *s, CBoatObj *b, UI08 id2, UI08 boattype );
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void DoHouseTarget( CSocket *mSock, UI08 houseEntry )
|
|
//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, UI08 houseEntry ) ->void {
|
|
UI16 houseID = 0;
|
|
std::string tag, data;
|
|
auto sect = "HOUSE "s + oldstrutil::number( 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( oldstrutil::upper( tag ) == "ID" ) {
|
|
houseID = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( data, "//" )), nullptr, 0));
|
|
break;
|
|
}
|
|
}
|
|
if( !houseID ) {
|
|
Console.error(oldstrutil::format( "Bad house script: #%u\n", houseEntry) );
|
|
}
|
|
else {
|
|
mSock->AddID1( houseEntry );
|
|
if( houseID >= 0x4000 ) {
|
|
mSock->mtarget( static_cast<UI16>(houseID - 0x4000), 576 );
|
|
}
|
|
else {
|
|
mSock->target( 0, TARGET_BUILDHOUSE, 0, 576 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - void CreateHouseKey( CSocket *mSock, CChar *mChar, CMultiObj *house, UI16 houseID )
|
|
//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
|
|
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 - void CreateHouseItems( CChar *mChar, std::vector< std::string > houseItems, CItem *house, UI16 houseID, SI16 x, SI16 y, SI08 z )
|
|
//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 ) ->void {
|
|
std::string tag, data, UTag;
|
|
ScriptSection *HouseItem = nullptr;
|
|
CItem *hItem = nullptr;
|
|
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 = oldstrutil::upper( tag );
|
|
data = oldstrutil::trim( oldstrutil::removeTrailing( data, "//" ));
|
|
if( UTag == "ITEM" ) {
|
|
hItem = Items->CreateBaseScriptItem( nullptr, data, mChar->WorldNumber(), 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 );
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
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( 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 ) && 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 - bool CheckForValidHouseLocation( CSocket *mSock, CChar *mChar, SI16 x, SI16 y, SI08 z,
|
|
//| SI16 spaceX, SI16 spaceY, bool isBoat, bool isMulti )
|
|
//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, bool isBoat, bool isMulti ) ->bool {
|
|
const UI08 worldNum = mChar->WorldNumber();
|
|
const UI16 instanceID = mChar->GetInstanceID();
|
|
|
|
auto [width,height] = Map->sizeOfMap(worldNum);
|
|
if( ( x+spaceX > width || x-spaceX < 0 || y+spaceY > height || y-spaceY < 0 ) && !mChar->IsGM() ) {
|
|
mSock->sysmessage( 577 );
|
|
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( mChar->GetCommandLevel() < CL_GM ) {
|
|
CMultiObj *mMulti = findMulti( curX, curY, z, worldNum, instanceID );
|
|
if( !ValidateObject( mMulti ) || !mMulti->IsOwner( mChar ) ) {
|
|
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 ) ){
|
|
|
|
if( mItem->GetType() == 12 || mItem->GetType() == 13 ) {
|
|
SI08 mItemZ = mItem->GetZ();
|
|
if ((mItemZ <= z) || ((mItemZ -z)<20)){
|
|
// Ignore doors on floors above
|
|
if ((mItemZ>z) || ((z-mItemZ)<20)){
|
|
// Ignore doors on floors below, too!
|
|
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;
|
|
if( x - origX < 2 || origX - x < 2 || y - origY < 2 || origY - y < 2 ) {
|
|
mSock->sysmessage( 9028 ); // You cannot place a house-addon adjacent to a door.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( getDist( point3( x, y, z ), mItem->GetLocation() ) < 2 ) {
|
|
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 ) {
|
|
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 )
|
|
mSock->sysmessage( 7 );
|
|
else
|
|
{
|
|
if( retVal2 )
|
|
{
|
|
mSock->sysmessage( 577 );
|
|
}
|
|
else
|
|
{
|
|
switch( retVal1 )
|
|
{
|
|
case 0: // Blocked by terrain
|
|
mSock->sysmessage( 9029 ); // You cannot build your house on the selected terrain!
|
|
break;
|
|
case 2: // Blocked by static item
|
|
mSock->sysmessage( 9030 ); // You cannot build your house there - location is blocked by one or more items!
|
|
break;
|
|
case 3: // Blocked by region settings
|
|
mSock->sysmessage( 9031 ); // You cannot build your house there - houses not allowed in this region!
|
|
break;
|
|
default:
|
|
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 - void BuildHouse( CSocket *mSock, UI08 houseEntry )
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Purpose - Called when player attempts to place a tent/house/boat
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
void BuildHouse( CSocket *mSock, UI08 houseEntry )
|
|
{
|
|
if( mSock->GetDWord( 11 ) == INVALIDSERIAL )
|
|
return;
|
|
if( !houseEntry )
|
|
return;
|
|
|
|
CChar *mChar = mSock->CurrcharObj();
|
|
if( !ValidateObject( mChar ) )
|
|
return;
|
|
|
|
const SI16 x = mSock->GetWord( 11 );
|
|
const SI16 y = mSock->GetWord( 13 );
|
|
SI08 z = static_cast<SI08>(mSock->GetByte( 16 ) + Map->TileHeight( mSock->GetWord( 17 ) ));
|
|
//UI32 targetSerial = mSock->GetDWord( 7 );
|
|
mSock->GetDWord( 7 );
|
|
if( mSock->GetDWord( 7 ) != INVALIDSERIAL || getDist( mChar->GetLocation(), point3( x, y, z ) ) >= DIST_BUILDRANGE )
|
|
{
|
|
mSock->sysmessage( 577 );
|
|
return;
|
|
}
|
|
|
|
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 " + oldstrutil::number( houseEntry );
|
|
ScriptSection *House = FileLookup->FindEntry( sect, house_def );
|
|
if( House == nullptr )
|
|
return;
|
|
|
|
std::vector<UI16> scriptIDs;
|
|
for (const auto &sec : House->collection()){
|
|
tag = sec->tag ;
|
|
data = sec->data ;
|
|
UTag = oldstrutil::upper( tag );
|
|
data = oldstrutil::trim( oldstrutil::removeTrailing( 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 = oldstrutil::sections( data, " " );
|
|
auto count = 0;
|
|
std::string result;
|
|
for( auto &sec : ssecs ) {
|
|
if( count > 0 ) {
|
|
if( count == 1 ) {
|
|
result = oldstrutil::trim( oldstrutil::removeTrailing( sec, "//" ));
|
|
}
|
|
else {
|
|
result = result + " " + oldstrutil::trim( oldstrutil::removeTrailing( sec, "//" ));
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
customTagName = oldstrutil::trim( oldstrutil::removeTrailing( 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( oldstrutil::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 = oldstrutil::sections( data, " " );
|
|
auto count = 0;
|
|
std::string result;
|
|
for( auto &sec : ssecs ) {
|
|
if( count > 0 ) {
|
|
if( count == 1 ) {
|
|
result = oldstrutil::trim( oldstrutil::removeTrailing( sec, "//" ));
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
customTagName = oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" ));
|
|
customTagStringValue = result;
|
|
if( !customTagName.empty() && !customTagStringValue.empty() ) {
|
|
customTag.m_Destroy = false;
|
|
customTag.m_IntValue = std::stoi(oldstrutil::trim( oldstrutil::removeTrailing( 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( oldstrutil::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( oldstrutil::format( "Invalid data found in CUSTOMINTTAG tag in House script [%s] - Supported data format: <tagName> <value>", sect.c_str() ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !houseID ) {
|
|
Console.error( oldstrutil::format("Bad house script # %u!", houseEntry) );
|
|
return;
|
|
}
|
|
|
|
const bool isMulti = (houseID >= 0x4000);
|
|
if( !CheckForValidHouseLocation( mSock, mChar, x, y, z, sx, sy, isBoat, isMulti ) ) {
|
|
return;
|
|
}
|
|
|
|
constexpr std::uint16_t maxsize = 1024 ;
|
|
std::string temp ;
|
|
CMultiObj *house = nullptr;
|
|
CItem *fakeHouse = nullptr;
|
|
if( isMulti ) {
|
|
if( (houseID%256) > 20 ) {
|
|
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 {
|
|
// Assign name of boat deed/model ship to newly created boat
|
|
temp = mChar->GetSpeechItem()->GetTitle();
|
|
if( temp == "" ) {
|
|
temp = "a ship";
|
|
}
|
|
}
|
|
house = Items->CreateMulti( mChar, temp, houseID, isBoat );
|
|
if( house == nullptr ) {
|
|
return;
|
|
}
|
|
|
|
house->SetLocation( x, y, z );
|
|
house->SetDecayable( itemsWillDecay );
|
|
house->SetDeed( houseDeed ); // crackerjack 8/9/99 - for converting back *into* deeds
|
|
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, mChar->WorldNumber(), mChar->GetInstanceID(), 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, mChar->WorldNumber(), mChar->GetInstanceID(), 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
|
|
fakeHouse = Items->CreateItem( mSock, mChar, houseID, 1, 0, OT_ITEM );
|
|
if( fakeHouse == nullptr ) {
|
|
return;
|
|
}
|
|
fakeHouse->SetLocation( x, y, z );
|
|
|
|
CMultiObj *mMulti = findMulti( fakeHouse );
|
|
if( ValidateObject( mMulti ) ) {
|
|
if( mMulti->GetLockdownCount() < mMulti->GetMaxLockdowns() ) {
|
|
mMulti->LockDownItem( fakeHouse );
|
|
}
|
|
else {
|
|
mSock->sysmessage( 9032 ); // This house has reached the limit on locked down items already!
|
|
fakeHouse->Delete();
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
mSock->sysmessage( 9033 ); // House addons can only be placed in houses!
|
|
fakeHouse->Delete();
|
|
return;
|
|
}
|
|
|
|
fakeHouse->SetOwner( mChar );
|
|
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;
|
|
}
|
|
|
|
bObj->SetWeightMax( weightMax );
|
|
bObj->SetMaxItems( maxItems );
|
|
bObj->SetDamageable( isDamageable );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
if( 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 ) ){
|
|
if( !pet->GetMounted() && pet->IsNpc() && objInRange( mChar, pet, DIST_SAMESCREEN ) ){
|
|
pet->SetLocation( mChar );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o-----------------------------------------------------------------------------------------------o
|
|
//| Function - bool KillKeysFunctor( CBaseObject *a, UI32 &b, void *extraData )
|
|
//| void killKeys( SERIAL targSerial )
|
|
//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, 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( oldstrutil::value<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( oldstrutil::value<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 );
|
|
}
|
|
}
|
|
|