mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
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.
1095 lines
33 KiB
C++
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 );
|
|
}
|
|
}
|
|
|
|
|