uox3/source/boats.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

1095 lines
33 KiB
C++

#include "uox3.h"
#include "weight.h"
#include "CPacketSend.h"
#include "classes.h"
#include "regions.h"
#include "CResponse.h"
#include "cEffects.h"
#include "Dictionary.h"
#include "StringUtility.hpp"
#define XP 0
#define YP 1
enum ShipOffsets
{
PORT_PLANK = 0,
STARB_PLANK,
HOLD,
TILLER
};
enum ShipItems
{
PORT_P_C = 0,// Port Plank Closed
PORT_P_O, // Port Plank Opened
STAR_P_C, // Starboard Plank Closed
STAR_P_O, // Starb Plank Open
HOLDID, // Hold
TILLERID // Tiller
};
//============================================================================================
//UooS Item translations - You guys are the men! :o)
//[4]=direction of ship
//[4]=Which Item (PT Plank, SB Plank, Hatch, TMan)
//[2]=Coord (x,y) offsets
const SI16 iSmallShipOffsets[4][4][2] =
// X Y X Y X Y X Y
{
{ {-2,0}, {2,0}, {0,-4}, {1,4} },//Dir
{ {0,-2}, {0,2}, {4,0}, {-4,0}},
{ {2,0}, {-2,0}, { 0,4}, {0,-4}},
{ {0,2}, {0,-2}, {-4,0}, {4,0} }
};
// P1 P2 Hold Tiller
const SI16 iMediumShipOffsets[4][4][2] =
// X Y X Y X Y X Y
{
{ {-2,0}, {2,0}, {0,-4}, {1,5} },
{ {0,-2}, {0,2}, {4,0}, {-5,0}},
{ {2,0}, {-2,0}, {0,4}, {0,-5}},
{ {0,2}, {0,-2}, {-4,0}, {5,0} }
};
const SI16 iLargeShipOffsets[4][4][2] =
// X Y X Y X Y X Y
{
{ {-2,-1}, {2,-1}, {0,-5}, {1,5} },
{ {1,-2}, {1,2}, {5,0}, {-5,0}},
{ {2,1}, {-2,1}, {0,5}, {0,-5} },
{ {-1,2}, {-1,-2},{-5, 0},{5,0} }
};
//Ship Items
//[4] = direction
//[6] = Which Item (PT Plank Up,PT Plank Down, SB Plank Up, SB Plank Down, Hatch, TMan)
const UI08 cShipItems[4][6]=
{
{0xB1, 0xD5, 0xB2, 0xD4, 0xAE, 0x4E},
{0x8A, 0x89, 0x85, 0x84, 0x65, 0x53},
{0xB2, 0xD4, 0xB1, 0xD5, 0xB9, 0x4B},
{0x85, 0x84, 0x8A, 0x89, 0x93, 0x50}
};
//============================================================================================
//o-----------------------------------------------------------------------------------------------o
//| Function - CBoatObj * GetBoat( CSocket *s )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Get the boat a character is on
//o-----------------------------------------------------------------------------------------------o
CBoatObj * GetBoat( CSocket *s )
{
CChar *mChar = s->CurrcharObj();
if( ValidateObject( mChar->GetMultiObj() ) )
return static_cast<CBoatObj *>(mChar->GetMultiObj());
return static_cast<CBoatObj *>(findMulti( mChar ));
}
//o------------------------------------------------------------------------------------------------o
//| Function - LeaveBoat()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Teleport player and their pets off the boat and to a nearby valid location
//o------------------------------------------------------------------------------------------------o
auto LeaveBoat( CSocket *s, CItem *p ) ->bool
{
CBoatObj *boat = GetBoat( s );
if( !ValidateObject( boat ))
return false;
const SI16 x2 = p->GetX();
const SI16 y2 = p->GetY();
CChar *mChar = s->CurrcharObj();
UI08 worldNumber = mChar->WorldNumber();
UI16 instanceID = mChar->GetInstanceID();
for( SI16 x = x2 - 3; x <= x2 + 4; ++x )
{
for( SI16 y = y2 - 3; y <= y2 + 4; ++y )
{
SI08 z = Map->Height( x, y, mChar->GetZ(), worldNumber, instanceID );
if( Map->ValidSpawnLocation( x, y, z, worldNumber, instanceID, true ) && !findMulti( x, y, z, worldNumber, instanceID ))
{
mChar->SetLocation( x, y, z, worldNumber, instanceID );
// Freeze player temporarily after teleporting
Effects->tempeffect( nullptr, mChar, 1, 1, 1, 5 ); // 1 second, divided by 5 for 0.2s duration freeze
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( x, y, z, worldNumber, instanceID );
}
}
}
s->sysmessage( 3 );
return true;
}
}
}
s->sysmessage( 4 );
return false;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void PlankStuff( CSocket *s, CItem *p )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - If not on a boat, will send character to the plank, other
//| wise will call OpenPlank to open/close the plank
//o-----------------------------------------------------------------------------------------------o
void PlankStuff( CSocket *s, CItem *p )
{
CChar *mChar = s->CurrcharObj();
CBoatObj *boat = GetBoat( s );
if( !ValidateObject( boat ))
{
mChar->SetLocation( p->GetX(), p->GetY(), p->GetZ() + 3 );
// Freeze player temporarily after teleporting
Effects->tempeffect( nullptr, mChar, 1, 1, 1, 5 ); // 1 second, divided by 5 for 0.2s duration freeze
CMultiObj *boat2 = p->GetMultiObj();
if( ValidateObject( boat2 ))
{
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 );
}
}
}
}
if( ValidateObject( boat2 ))
{
s->sysmessage( 1 );
}
else
{
s->sysmessage( 2 );
}
}
else
{
LeaveBoat( s, p );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void OpenPlank( CItem *p )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Open / Close a plank
//o-----------------------------------------------------------------------------------------------o
void OpenPlank( CItem *p )
{
switch( p->GetID( 2 ) )
{
//Open plank->
case 0xE9: p->SetID( 0x84, 2 ); break;
case 0xB1: p->SetID( 0xD5, 2 ); break;
case 0xB2: p->SetID( 0xD4, 2 ); break;
case 0x8A: p->SetID( 0x89, 2 ); break;
case 0x85: p->SetID( 0x84, 2 ); break;
//Close Plank->
case 0x84: p->SetID( 0xE9, 2 ); break;
case 0xD5: p->SetID( 0xB1, 2 ); break;
case 0xD4: p->SetID( 0xB2, 2 ); break;
case 0x89: p->SetID( 0x8A, 2 ); break;
default: Console.warning( oldstrutil::format("Invalid plank ID called! Plank 0x%X '%s' [%u]", p->GetSerial(), p->GetName().c_str(), p->GetID() ));
break;
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool BlockBoat( CBoatObj *b, SI16 xmove, SI16 ymove, UI08 moveDir, UI08 boatDir, bool turnBoat )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check if a boat can move to a certain loc
//o-----------------------------------------------------------------------------------------------o
bool BlockBoat( CBoatObj *b, SI16 xmove, SI16 ymove, UI08 moveDir, UI08 boatDir, bool turnBoat )
{
SI16 cx = b->GetX(), cy = b->GetY();
const SI08 cz = b->GetZ();
SI16 x1 = 0, y1 = 0, x2 = 0, y2 = 0;
UI08 type = 0;
cx += xmove;
cy += ymove;
switch( b->GetID( 2 ) )
{
case 0:
case 1:
case 2:
case 3://small
case 4:
case 5:
case 6:
case 7://small dragon
type = 1;
break;
case 8:
case 9:
case 10:
case 11://medium
case 12:
case 13:
case 14:
case 15://medium dragon
type = 2;
break;
case 16:
case 17:
case 18:
case 19://large
case 20:
case 21:
case 22:
case 23://large dragon
type = 3;
break;
default:
return true;
}
// If boat-movement includes turning, boat-direction needs to be switched around
if( turnBoat )
{
switch( boatDir&0x0F )
{
case NORTHEAST: // U
case SOUTHWEST: // D
case NORTH: // N
case SOUTH: // S
boatDir = 2;
break;
case EAST: // E
case WEST: // W
case SOUTHEAST: // E
case NORTHWEST: // W
boatDir = 0;
break;
default: break;
}
}
//small = 5,11
//medium = 5, 13
//large = 5, 15
switch( moveDir&0x0F )
{
case NORTHEAST: // U
case SOUTHWEST: // D
case NORTH: // N
case SOUTH: // S
switch( boatDir&0x0F )
{
case NORTHEAST: // U
case SOUTHWEST: // D
case NORTH: // N
case SOUTH: // S
x1 = cx - 2; //Width of N/S ship as it moves N/S
x2 = cx + 3; //Width of N/S ship as it moves N/S
switch( type )
{
case 1: y1 = cy - 6; y2 = cy + 6; break; //Length of N/S ship as it moves N/S
case 2: y1 = cy - 7; y2 = cy + 7; break; //Length of N/S ship as it moves N/S
case 3: y1 = cy - 8; y2 = cy + 8; break; //Length of N/S ship as it moves N/S
default: Console.error( " Fallout of North/South switch() statement in cBoats::BlockBoat()" ); break;
}
break;
case EAST: // E
case WEST: // W
case SOUTHEAST: // E
case NORTHWEST: // W
y1 = cy - 2; //Width of E/W ship as it moves N/S
y2 = cy + 3; //Width of E/W ship as it moves N/S
switch( type )
{
case 1: x1 = cx - 6; x2 = cx + 6; break; //Length of E/W ship as it moves N/S
case 2: x1 = cx - 7; x2 = cx + 7; break; //Length of E/W ship as it moves N/S
case 3: x1 = cx - 8; x2 = cx + 8; break; //Length of E/W ship as it moves N/S
default: Console.error( " Fallout of East/West switch() statement in cBoats::BlockBoat()" ); break;
}
break;
default: Console.error( " Fallout of boatDir.switch() statement in cBoats::BlockBoat()" ); break;
}
break;
case EAST: // E
case WEST: // W
case SOUTHEAST: // E
case NORTHWEST: // W
switch( boatDir&0x0F )
{
case EAST: // E
case WEST: // W
case SOUTHEAST: // E
case NORTHWEST: // W
y1 = cy - 2; //Width of E/W ship as it moves E/W
y2 = cy + 3; //Width of E/W ship as it moves E/W
switch( type )
{
case 1: x1 = cx - 6; x2 = cx + 6; break; //Length of E/W ship as it moves E/W
case 2: x1 = cx - 7; x2 = cx + 7; break; //Length of E/W ship as it moves E/W
case 3: x1 = cx - 8; x2 = cx + 8; break; //Length of E/W ship as it moves E/W
default: Console.error( " Fallout of East/West switch() statement in cBoats::BlockBoat()" ); break;
}
break;
case NORTHEAST: // U
case SOUTHWEST: // D
case NORTH: // N
case SOUTH: // S
x1 = cx - 2; //Width of N/S ship as it moves E/W
x2 = cx + 3; //Width of N/S ship as it moves E/W
switch( type )
{
case 1: y1 = cy - 6; y2 = cy + 6; break; //Length of N/S ship as it moves E/W
case 2: y1 = cy - 7; y2 = cy + 7; break; //Length of N/S ship as it moves E/W
case 3: y1 = cy - 8; y2 = cy + 8; break; //Length of N/S ship as it moves E/W
default: Console.error( " Fallout of North/South switch() statement in cBoats::BlockBoat()" ); break;
}
break;
default: Console.error( " Fallout of boatDir.switch() statement in cBoats::BlockBoat()" ); break;
}
break;
default: return true;
}
UI08 worldNumber = b->WorldNumber();
UI16 instanceID = b->GetInstanceID();
SI08 boatZ = b->GetZ();
for( SI16 x = x1; x < x2; ++x )
{
for( SI16 y = y1; y < y2; ++y )
{
// Look for other boats
CMultiObj * tempBoat = findMulti( x, y, boatZ, worldNumber, instanceID );
if( ValidateObject( tempBoat ) && tempBoat->GetSerial() != b->GetSerial() )
return true;
// Look for blocking dynamic items at boat's Z level
CItem *tempItem = GetItemAtXYZ( x, y, boatZ, worldNumber, instanceID );
if( ValidateObject( tempItem ))
{
auto multiSerial = tempItem->GetMulti();
//auto boatSerial = b->GetSerial();
if( multiSerial != INVALIDSERIAL && multiSerial != b->GetSerial() )
{
CTile& tile = Map->SeekTile( tempItem->GetID() );
if( tile.CheckFlag( TF_BLOCKING ) )
return true;
}
}
SI08 sz = Map->StaticTop( x, y, boatZ, worldNumber, MAX_Z_STEP );
if( sz == ILLEGAL_Z ) //map tile
{
auto map = Map->SeekMap( x, y, worldNumber );
if( map.altitude >= cz && !map.CheckFlag( TF_WET ) && map.name() != "water" )//only tiles on/above the water
return true;
}
else
{
auto artwork = Map->artAt(x, y, worldNumber);
for (auto &tile:artwork){
SI08 zt = tile.altitude + tile.height();
if( !tile.CheckFlag( TF_WET ) && zt >= cz && zt <= (cz + 20) && (tile.name() != "water") )
{
return true;
}
}
}
}
}
return false;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CreateBoat( CSocket *s, CBoatObj *b, UI08 id2, UI08 boattype )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Create a boat
//o-----------------------------------------------------------------------------------------------o
bool CreateBoat( CSocket *s, CBoatObj *b, UI08 id2, UI08 boattype )
{
if( !ValidateObject( b ) )
{
s->sysmessage( 5 );
return false;
}
switch( id2 )
{
case 0x00:
case 0x04:
case 0x08:
case 0x0C:
case 0x10:
case 0x14:
break;
default:
s->sysmessage( 6 );
return false;
}
if( BlockBoat( b, 0, 0, 0, 0, false ) )
{
s->sysmessage( 7 );
return false;
}
const SERIAL serial = b->GetSerial();
const UI08 worldNumber = b->WorldNumber();
const UI16 instanceID = b->GetInstanceID();
SI32 maxWeight = b->GetWeightMax();
if( maxWeight == 0 )
{
maxWeight = 40000; // 400 stones default, if nothing else is defined
}
UI16 maxItems = b->GetMaxItems();
if( maxItems == 0 )
{
maxItems = 125; // Default if nothing else is defined
}
const SI16 x = b->GetX(), y = b->GetY();
SI08 z = Map->MapElevation( x, y, worldNumber );
const SI08 dynz = Map->DynamicElevation( x, y, z, worldNumber, 20, instanceID );
if( ILLEGAL_Z != dynz )
z = dynz;
else
{
const SI08 staticz = Map->StaticTop( x, y, z, worldNumber, 20 );
if( ILLEGAL_Z != staticz )
z = staticz;
}
b->SetZ( z );// Z in water
b->SetTempVar( CITV_MOREZ, calcserial( id2, id2+3, b->GetTempVar( CITV_MOREZ, 3 ), b->GetTempVar( CITV_MOREZ, 4 ) ) );
b->SetMoveType( BOAT_ANCHORED );
CChar *mChar = s->CurrcharObj();
CItem *tiller = Items->CreateItem( nullptr, mChar, 0x3E4E, 1, 0, OT_ITEM );
if( tiller == nullptr )
return false;
if( b->GetName().length() > 0 && b->GetName() != Dictionary->GetEntry( 2035, s->Language() )) // a ship
{
std::string tillerNameDict = Dictionary->GetEntry( 2033, s->Language() ); // The tiller man of %s
auto sPos = tillerNameDict.find("%s");
tiller->SetName( tillerNameDict.replace( sPos, 2, b->GetName() ));
}
else
{
tiller->SetName( Dictionary->GetEntry( 1409 ) ); // a tiller man
}
tiller->SetType( IT_TILLER );
tiller->SetTempVar( CITV_MOREX, boattype );
tiller->SetDecayable( false );
CItem *p2 = Items->CreateItem( nullptr, mChar, 0x3EB2, 1, 0, OT_ITEM );//Plank2 is on the RIGHT side of the boat
if( p2 == nullptr )
return false;
p2->SetType( IT_PLANK );
p2->SetDecayable( false );
// Lock the plank
TAGMAPOBJECT tagvalObject;
tagvalObject.m_ObjectType = TAGMAP_TYPE_INT;
tagvalObject.m_IntValue = 1;
tagvalObject.m_Destroy = false;
tagvalObject.m_StringValue = "";
p2->SetTag( "plankLocked", tagvalObject );
CItem *p1 = Items->CreateItem( nullptr, mChar, 0x3EB1, 1, 0, OT_ITEM );//Plank1 is on the LEFT side of the boat
if( p1 == nullptr )
return false;
p1->SetType( IT_PLANK ); // Boat type
p1->SetDecayable( false );
// Lock the plank
tagvalObject.m_ObjectType = TAGMAP_TYPE_INT;
tagvalObject.m_IntValue = 1;
tagvalObject.m_Destroy = false;
tagvalObject.m_StringValue = "";
p1->SetTag( "plankLocked", tagvalObject );
CItem *hold = Items->CreateItem( nullptr, mChar, 0x3EAE, 1, 0, OT_ITEM );
if( hold == nullptr )
return false;
hold->SetType( IT_CONTAINER );//Conatiner
hold->SetDecayable( false );
hold->SetWeightMax( maxWeight );
hold->SetMaxItems( maxItems );
p2->SetTempVar( CITV_MORE, serial );
p1->SetTempVar( CITV_MORE, serial );
b->SetTiller( tiller->GetSerial() );
b->SetPlank( 0, p1->GetSerial() );// Store the other stuff anywhere it will fit :-)
b->SetPlank( 1, p2->GetSerial() );
b->SetHold( hold->GetSerial() );
switch( id2 ) //Give everything the right Z for it size boat
{
case 0x00:
case 0x04:
tiller->SetLocation( x + 1, y + 4, z );
p1->SetLocation( x - 2, y, z );
p2->SetLocation( x + 2, y, z );
hold->SetLocation( x, y - 4, z );
break;
case 0x08:
case 0x0C:
tiller->SetLocation( x + 1, y + 5, z );
p1->SetLocation( x - 2, y, z );
p2->SetLocation( x + 2, y, z );
hold->SetLocation( x, y - 4, z );
break;
case 0x10:
case 0x14:
tiller->SetLocation( x + 1, y + 5, z );
p1->SetLocation( x - 2, y - 1, z );
p2->SetLocation( x + 2, y - 1, z );
hold->SetLocation( x, y - 5, z );
break;
}
return true;
}
void CheckDirection( UI08 dir, SI16& tx, SI16& ty )
{
switch( dir )
{
case NORTH: --ty; break;
case NORTHEAST: ++tx; --ty; break;
case EAST: ++tx; break;
case SOUTHEAST: ++tx; ++ty; break;
case SOUTH: ++ty; break;
case SOUTHWEST: --tx; ++ty; break;
case WEST: --tx; break;
case NORTHWEST: --tx; --ty; break;
default: break;
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void MoveBoat( UI08 dir, CBoatObj *boat )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Move the boat and everything on it 1 tile
//o-----------------------------------------------------------------------------------------------o
void MoveBoat( UI08 dir, CBoatObj *boat )
{
CItem *tiller = calcItemObjFromSer( boat->GetTiller() );
CItem *p1 = calcItemObjFromSer( boat->GetPlank( 0 ) );
CItem *p2 = calcItemObjFromSer( boat->GetPlank( 1 ) );
CItem *hold = calcItemObjFromSer( boat->GetHold() );
if( !ValidateObject( boat ) )
return;
if( !ValidateObject( tiller ) || !ValidateObject( p1 ) || !ValidateObject( p2 ) || !ValidateObject( hold ) )
return;
CPPauseResume prSend( 0 );
auto nearbyChars = FindNearbyPlayers( boat, DIST_BUILDRANGE );
for (auto &tSock:nearbyChars){
tSock->Send( &prSend );
}
SI16 tx = 0, ty = 0;
CheckDirection( dir & 0x0F, tx, ty );
SI16 x = boat->GetX(), y = boat->GetY();
auto worldNumber = boat->WorldNumber();
bool teleportBoat = false;
if( worldNumber <= 1 && ( x + tx ) < 5100 )
{
// Handle wrapping map from one edge to the other when sailing
if( ( x + tx ) <= 50 && tx < 0 )
{
// Sailing west
tx = 5050;
teleportBoat = true;
}
else if( ( x + tx ) >= 5050 && tx > 0 )
{
// Sailing east
tx = -5050;
teleportBoat = true;
}
else if( ( y + ty ) <= 100 && ty < 0 )
{
// Sailing north
ty = 3896;
teleportBoat = true;
}
else if( ( y + ty ) >= 3996 && ty > 0 )
{
// Sailing south
ty = -3896;
teleportBoat = true;
}
}
// Check if anything blocks boat from moving to new location
if( BlockBoat( boat, tx, ty, dir, boat->GetDir(), false ) )
{
boat->SetMoveType( 0 );
for (auto &tSock:nearbyChars){
tSock->Send( &prSend );
tiller->TextMessage( tSock, 9 );
}
return;
}
// Move all the special items along with the boat
boat->IncLocation( tx, ty );
tiller->IncLocation( tx, ty );
p1->IncLocation( tx, ty );
p2->IncLocation( tx, ty );
hold->IncLocation( tx, ty );
// If boat got teleported due to wrapping of map, make sure to
// remove the boat and everything on it from sight of nearby players
if( teleportBoat )
{
boat->RemoveFromSight();
boat->Update();
tiller->RemoveFromSight();
tiller->Update();
p1->RemoveFromSight();
p1->Update();
p2->RemoveFromSight();
p2->Update();
hold->RemoveFromSight();
hold->Update();
}
// Move all items aboard the boat along with the boat
auto itemList = boat->GetItemsInMultiList();
for (const auto &bItem : itemList->collection()){
if( ValidateObject( bItem ) ){
if( !(bItem == tiller || bItem == p1 || bItem == p2 || bItem == hold) ){
bItem->IncLocation( tx, ty );
// Remember to remove any items on board from sight of nearby players if boat got teleported
if( teleportBoat ) {
bItem->RemoveFromSight();
bItem->Update();
}
}
}
}
// Move all characters aboard the boat along with the boat
auto charList = boat->GetCharsInMultiList();
for (const auto &bChar : charList->collection()){
if( ValidateObject( bChar ) ){
bChar->SetLocation( bChar->GetX() + tx, bChar->GetY() + ty, bChar->GetZ() );
if( teleportBoat ) {
bChar->Update();
}
}
}
for( auto &tSock: nearbyChars) {
tSock->Send( &prSend );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void TurnStuff( CBoatObj *b, CBaseObject *i, bool rightTurn )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Turn an item on a boat
//o-----------------------------------------------------------------------------------------------o
void TurnStuff( CBoatObj *b, CBaseObject *i, bool rightTurn )
{
if( !ValidateObject( b ) )
return;
SI16 dx = static_cast<SI16>(i->GetX() - b->GetX()); //get their distance x
SI16 dy = static_cast<SI16>(i->GetY() - b->GetY());//and distance Y
if( rightTurn )
i->SetLocation( static_cast<SI16>(b->GetX() - dy), static_cast<SI16>(b->GetY() + dx), i->GetZ() );
else
i->SetLocation( static_cast<SI16>(b->GetX() + dy), static_cast<SI16>(b->GetY() - dx), i->GetZ() );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void TurnBoat( CItem *b, bool rightTurn )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Turn the boat and use TurnStuff() to turn all items/chars on it
//o-----------------------------------------------------------------------------------------------o
void TurnBoat( CBoatObj *b, bool rightTurn, bool disableChecks )
{
if( !ValidateObject( b ) )
return;
SI16 id2 = b->GetID( 2 );
UI08 olddir = b->GetDir();
CPPauseResume prSend( 0 );
auto nearbyChars =FindNearbyPlayers( b, DIST_BUILDRANGE );
for (auto &tSock :nearbyChars ){
tSock->Send( &prSend );
}
CItem *tiller = calcItemObjFromSer( b->GetTiller() );
CItem *p1 = calcItemObjFromSer( b->GetPlank( 0 ) );
CItem *p2 = calcItemObjFromSer( b->GetPlank( 1 ) );
CItem *hold = calcItemObjFromSer( b->GetHold() );
if( !ValidateObject( tiller ) || !ValidateObject( p1 ) || !ValidateObject( p2 ) || !ValidateObject( hold ) )
return;
if( rightTurn )
{
b->SetDir( olddir + 2 );
++id2;
}
else
{
b->SetDir( olddir - 2 );
--id2;
}
if( b->GetDir() > 250 )
b->SetDir( b->GetDir() + 4 );
if( b->GetDir() > NORTHWEST )
b->SetDir( b->GetDir() - 4 );//Make sure we dont have any DIR errors
if( id2 < b->GetTempVar( CITV_MOREZ, 1 ) )
id2 += 4;//make sure we don't have any id errors either
if( id2 > b->GetTempVar( CITV_MOREZ, 2 ) )
id2 -= 4;//Now you know what the min/max id is for :-)
prSend.Mode( 0 );
if( !disableChecks )
{
if( BlockBoat( b, 0, 0, b->GetDir(), olddir, true ) )
{
b->SetDir( olddir );
for (auto &tSock :nearbyChars ){
tSock->Send( &prSend );
tiller->TextMessage( tSock, 1410, 0, 0x0481 );
}
return;
}
}
b->SetID( static_cast<UI08>(id2), 2 );//set the id
if( b->GetID( 2 ) == b->GetTempVar( CITV_MOREZ, 1 ) )
b->SetDir( NORTH );//extra DIR error checking
if( b->GetID( 2 ) == b->GetTempVar( CITV_MOREZ, 2 ) )
b->SetDir( WEST );
auto itemList = b->GetItemsInMultiList();
for (const auto &bItem : itemList->collection()){
if( ValidateObject( bItem ) ){
TurnStuff( b, bItem, rightTurn );
}
}
auto charList = b->GetCharsInMultiList();
for (const auto &bChar : charList->collection()){
if( ValidateObject( bChar ) ){
TurnStuff( b, bChar, rightTurn );
}
}
UI08 dir = (b->GetDir()&0x0F)/2;
p1->SetLocation( b );
p1->SetID( cShipItems[dir][PORT_P_C], 2 );//change the ID
p2->SetLocation( b );
p2->SetID( cShipItems[dir][STAR_P_C], 2 );
tiller->SetLocation( b );
tiller->SetID( cShipItems[dir][TILLERID], 2 );
hold->SetLocation( b );
hold->SetID( cShipItems[dir][HOLDID], 2 );
switch( b->GetTempVar( CITV_MOREZ, 1 ) )//Now set what size boat it is and move the specail items
{
case 0x00:
case 0x04:
p1->IncLocation( iSmallShipOffsets[dir][PORT_PLANK][XP], iSmallShipOffsets[dir][PORT_PLANK][YP] );
p2->IncLocation( iSmallShipOffsets[dir][STARB_PLANK][XP], iSmallShipOffsets[dir][STARB_PLANK][YP] );
tiller->IncLocation( iSmallShipOffsets[dir][TILLER][XP], iSmallShipOffsets[dir][TILLER][YP] );
hold->IncLocation( iSmallShipOffsets[dir][HOLD][XP], iSmallShipOffsets[dir][HOLD][YP] );
break;
case 0x08:
case 0x0C:
p1->IncLocation( iMediumShipOffsets[dir][PORT_PLANK][XP], iMediumShipOffsets[dir][PORT_PLANK][YP] );
p2->IncLocation( iMediumShipOffsets[dir][STARB_PLANK][XP], iMediumShipOffsets[dir][STARB_PLANK][YP] );
tiller->IncLocation( iMediumShipOffsets[dir][TILLER][XP], iMediumShipOffsets[dir][TILLER][YP] );
hold->IncLocation( iMediumShipOffsets[dir][HOLD][XP], iMediumShipOffsets[dir][HOLD][YP] );
break;
case 0x10:
case 0x14:
p1->IncLocation( iLargeShipOffsets[dir][PORT_PLANK][XP], iLargeShipOffsets[dir][PORT_PLANK][YP] );
p2->IncLocation( iLargeShipOffsets[dir][STARB_PLANK][XP], iLargeShipOffsets[dir][STARB_PLANK][YP] );
tiller->IncLocation( iLargeShipOffsets[dir][TILLER][XP], iLargeShipOffsets[dir][TILLER][YP] );
hold->IncLocation( iLargeShipOffsets[dir][HOLD][XP], iLargeShipOffsets[dir][HOLD][YP] );
break;
default: Console.error( oldstrutil::format("TurnBoat() more1 error! more1 = %c not found!", b->GetTempVar( CITV_MOREZ, 1 ) ));
}
for (auto &tSock :nearbyChars ){
tSock->Send( &prSend );
}
}
void TurnBoat( CSocket *mSock, CBoatObj *myBoat, CItem *tiller, UI08 dir, bool rightTurn )
{
SI16 tx = 0, ty = 0;
CheckDirection( dir&0x0F, tx, ty );
if( !BlockBoat( myBoat, tx, ty, dir, myBoat->GetDir(), true ) )
{
tiller->TextMessage( mSock, 10 );
TurnBoat( myBoat, rightTurn, false );
}
else
{
myBoat->SetMoveType( 0 );
tiller->TextMessage( mSock, 9 );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void CBoatResponse::Handle( CSocket *mSock, CChar *mChar )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check for boat commands
//o-----------------------------------------------------------------------------------------------o
void CBoatResponse::Handle( CSocket *mSock, CChar *mChar )
{
if( mSock == nullptr )
return;
CBoatObj *boat = GetBoat( mSock );
if( !ValidateObject( boat ) )
return;
UI08 dir = boat->GetDir()&0x0F;
CItem *tiller = calcItemObjFromSer( boat->GetTiller() );
UnicodeTypes mLang = mSock->Language();
if( trigWord != TW_SETNAME && trigWord != TW_BOATANCHORRAISE && trigWord != TW_BOATANCHORDROP && boat->GetMoveType() == -1 )
{
tiller->TextMessage( mSock, 2024 ); // Ar, the anchor is down sir!
mSock->sysmessage( 2023 ); // You must raise the anchor to pilot the ship.
return;
}
switch( trigWord )
{
case TW_BOATANCHORDROP:
if( boat->GetMoveType() != BOAT_ANCHORED )
{
boat->SetMoveType( BOAT_ANCHORED );
tiller->TextMessage( mSock, 2025 ); // Ar, anchor dropped sir.
}
else
{
tiller->TextMessage( mSock, 2026 ); // Ar, the anchor was already dropped sir.
}
break;
case TW_BOATANCHORRAISE:
if( boat->GetMoveType() == BOAT_ANCHORED )
{
boat->SetMoveType( BOAT_STOP );
tiller->TextMessage( mSock, 2027 ); // Ar, anchor raised sir.
}
else
{
tiller->TextMessage( mSock, 2028 ); // Ar, the anchor has not been dropped sir.
}
break;
case TW_BOATTURNRIGHT:
case TW_BOATSTARBOARD:
if( dir >= 2 )
dir -= 2;
else
dir += 6;
TurnBoat( mSock, boat, tiller, dir, true );
break;
case TW_BOATTURNLEFT:
case TW_BOATPORT:
dir += 2;
if( dir > 7 )
dir -= 8;
TurnBoat( mSock, boat, tiller, dir, false );
break;
case TW_BOATTURNAROUND:
tiller->TextMessage( mSock, 10 ); // Aye, sir.
TurnBoat( boat, true, true );
TurnBoat( boat, true, true );
break;
case TW_SETNAME:
{
// Check if player trying to rename the boat is actually the owner
if( mChar->GetSerial() != boat->GetOwner() )
{
tiller->TextMessage( mSock, 2034 ); // Arr! Only the owner of the ship may change its name!
return;
}
// Check if player provided anything after the actual set name command
std::string cmdString = oldstrutil::upper( Dictionary->GetEntry( 1425, mLang )); // SET NAME
if( oldstrutil::upper( ourText ).size() == cmdString.size() )
{
tiller->TextMessage( mSock, 12 ); // Can ya say that again with an actual name, sir?
return;
}
// Check if we can find the dictionary-based command string in the player's speech
std::string upperOurText = oldstrutil::upper( ourText );
size_t cmdStringPos = upperOurText.find(cmdString);
if( cmdStringPos == std::string::npos )
{
// Command string not found in text, something went wrong!
tiller->TextMessage( mSock, 11 ); // What be that, sir?
return;
}
// Erase cmdString from ourText, leaving us with just the new name of the boat
size_t pos = upperOurText.find(cmdString);
if( pos != std::string::npos )
ourText.erase(pos, cmdString.size());
// Trim spaces from start and end of player provided name, and see if there's actually a name there and not just empty spaces!
ourText = oldstrutil::trim(ourText);
if( ourText.size() == 0 )
{
boat->SetName( Dictionary->GetEntry( 2035, mLang )); // a ship
tiller->SetName( Dictionary->GetEntry( 1409, mLang )); // a tiller man
tiller->TextMessage( mSock, 2030 ); // This ship now has no name.
return;
}
std::string tillerNameDict = Dictionary->GetEntry( 2033, mLang ); // The tiller man of %s
if( tillerNameDict.size() + ourText.size() > MAX_NAME - 1 )
{
mSock->sysmessage( 1944 ); // That name is too long.
return;
}
// Give boat the new name
boat->SetName( ourText );
// Give tiller man a fitting name as well
auto sPos = tillerNameDict.find("%s");
tiller->SetName( tillerNameDict.replace( sPos, 2, ourText ));
break;
}
default:
break;
}
}
void killKeys( SERIAL targSerial, SERIAL charSerial = INVALIDSERIAL );
//o-----------------------------------------------------------------------------------------------o
//| Function - void ModelBoat( CSocket *s, CBoatObj *i )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Turn a boat into a boat model that can be re-placed
//o-----------------------------------------------------------------------------------------------o
void ModelBoat( CSocket *s, CBoatObj *i )
{
if( s == nullptr )
return;
CItem *tiller = calcItemObjFromSer( i->GetTiller() );
CItem *p1 = calcItemObjFromSer( i->GetPlank( 0 ) );
CItem *p2 = calcItemObjFromSer( i->GetPlank( 1 ) );
CItem *hold = calcItemObjFromSer( i->GetHold() );
CChar *mChar = s->CurrcharObj();
if( !ValidateObject( tiller ) || !ValidateObject( p1 ) || !ValidateObject( p2 ) || !ValidateObject( hold ) )
{
s->sysmessage( 9014 ); // Something is not right - unable to find tiller, planks or hold! This boat might be bugged...
return;
}
SERIAL serial = i->GetSerial();
if( i->GetOwnerObj() == mChar )
{
// Check if character's backpack can hold more items
// By default, CreateItem drops item at character's feet if it cannot hold more items, but
// this is a high-value item, so let's prevent the action instead of dropping the item on the ground!
CItem *playerPack = mChar->GetPackItem();
if( ValidateObject( playerPack ) )
{
if( playerPack->GetContainsList()->Num() >= playerPack->GetMaxItems() )
{
s->sysmessage( 1819 ); // Your backpack cannot hold any more items!
return;
}
}
if( p1->GetID( 2 ) == 0x84 || p1->GetID( 2 ) == 0xD5 || p1->GetID( 2 ) == 0xD4 || p1->GetID( 2 ) == 0x89
|| p2->GetID( 2 ) == 0x84 || p2->GetID( 2 ) == 0xD5 || p2->GetID( 2 ) == 0xD4 || p2->GetID( 2 ) == 0x89 )
{
s->sysmessage( 9015 ); // Planks must be closed before you pack up your ship!
return;
}
if( hold->GetContainsList()->Num() > 0 )
{
s->sysmessage( 9016 ); // Make sure your hold is empty, and try again!
return;
}
if( i->GetItemsInMultiList()->Num() > 4 || i->GetCharsInMultiList()->Num() > 0 )
{
s->sysmessage( 9017 ); // This boat must be empty before it can be converted to a model!
return;
}
CItem *model = Items->CreateItem( s, mChar, 0x14f3, 1, 0, OT_ITEM, true );
if( model == nullptr )
return;
model->SetTempVar( CITV_MOREX, tiller->GetTempVar( CITV_MOREX ) );
Weight->subtractItemWeight( mChar, model );
model->SetWeight( 0 );
Weight->addItemWeight( mChar, model );
model->SetType( IT_MODELMULTI );
model->SetMovable( 0 );
if( i->GetName().length() > 0 )
{
// Apply boat's name to the ship model
std::string shipModelNameDict = Dictionary->GetEntry( 2037, s->Language() ); // %s [Dry Docked]
auto sPos = shipModelNameDict.find("%s");
model->SetName( shipModelNameDict.replace( sPos, 2, i->GetName() ));
// Also store the original name in the item's title
model->SetTitle( i->GetName() );
}
tiller->Delete();
p1->Delete();
p2->Delete();
hold->Delete();
i->Delete();
killKeys( serial );
}
}