mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
Fixed an issue with an internal movement check that prevented characters from moving in valid map areas in Felucca/Trammel from X 6144 to X 7168 Fixed an issue where .instanceID property was misspelled in 'xgo GM command script, causing characters to be teleported to an out of bounds area where they would no longer be saved (js/commands/targeting/x.js) Added some additional error-checking to .Teleport()/.SetLocation() JS Method to prevent script-accidents from sending characters out of bounds. Updated Poisoning skill to add poison-charges to weapons being poisoned. Charges are consumed when applying poison in combat (js/skill/poisoning.js) Updated combat code to consume poison-charges if a poisoned weapon is used to poison an opponent Updated shoplists for Blacksmiths and Weaponsmiths - they now buy/sell longswords (dfndata/items/shoplist.dfn) Updated MageShopping shoplist for mage vendors - they now buy/sell mass curse scrolls (dfndata/items/shoplist.dfn) Added new Spawn Region DFN tag that can specify which era or eras of UO (Multiple comma-separated entries supported) a given spawn region is valid for. Spawn region not valid for core shard era will be ignored. Syntax: ERAS=UO,T2A,UOR,LBR,AOS,SE,ML,SA,HS,TOL Added new Spawn Region DFN tag that allows using another spawn region as a "parent". All properties of this parent will be inherited except for these, which are ignored: ERAS, NPCLIST, ITEMLIST, NPC and ITEM. Syntax for new tag: GET=# // Inherit a specified spawn region Added spawn region for animal trainer vendor in Vesper stable Added spawn region for fur trader/tanner vendors in The Best Hides of Britain Added spawn region for spinner vendor in The Lord's Clothier's and The Right Fit shops in Britain Added spawn region for tanner NPC to Nujel'm Tannery (and fixed leatherworker spawn region for same area) Added spawn regions for banker NPCs in East Bank of Britain, First Bank of Moonglow, Jhelom Bank and Jeweler, Bank of Skara Brae Added spawn regions for stables, tailor and blacksmith inside Castle British Added spawn regions for mage shops, mage guilds and farm houses in Moonglow Added spawn regions for misc vendors and townfolk in multiple cities Added spawn regions for Fire Island Updated spawn regions for Dagger Isle/Ice Island Added spawn regions for forests and jungles in southern Britannia and on various islands including Bucc's Den, Moonglow, Serpent's Hold, and misc unpopulated islands Added spawn regions for area east of Skara Brae, around Hedge Maze and south of Britain Added spawn regions for wandering healers outside every dungeon entrance in Britannia and Lost Lands Re-integrated ocean spawn regions for ocean creatures (dolphin, walrus, water elemental, sea serpent) from the original community-based spawn file Added reagent spawns in overworld spawn regions in Britannia/Lost Lands Added special reagent spawns in swamp areas Added additional overworld spawn regions in Britannia/Lost Lands Added spawn regions for Khaldun dungeon that spawn some new and some old NPCs: Old: Zombies, skeletons, skeletal mages, skeletal knights, ancient liches New: Cursed (and named) NPCs, shadowfiends, zealots of khaldun (knights/summoners), tentacles of the harrower Updated size and positions of some existing overworld spawn regions Fixed incorrect spawn region size for small island south of Trinsic Added switch/door puzzle functionality in Khaldun dungeon using new switch/door combo script (js/item/dungeons/switch_door_combo.js) Added "smart objects" in Khaldun dungeon that activate when you come close enough (js/item/dungeons/smart_activate_item.js) Added teleport locations within Khaldun dungeon (js/teleport.scp) Added custom AIs to existing NPC spawning in Khaldun dungeon: Ancient Lich (summons other undead, and can turn into one of them for disguise) Added DFN entries for new NPCs spawning in Khaldun dungeon, some with custom AIs: [tentacles_of_the_harrower] -> Tentacles of the Harrower (life steal) [shadowfiend] -> Shadowfiends (seek out and reveal hidden players) [zealot_knight]/[zealot_summoner] -> Zealots of Khaldun (turn into undead on death, male/female variants) [cursed] -> Cursed (male/female variants) [spectralarmor] -> Spectral Armor Added DFN entries for new (named) NPCs spawning in Khaldun dungeon: [lysander_gathenwale] -> Lysander Gathenwale [grimmoch_drummel] -> Grimmoch Drummel [morg_bergen] -> Morg Bergen [tavara_sewel] -> Tavara Sewel Added new NPC DFN entries: [leatherworker] -> alias for male/female leatherworker vendors [mapmaker] -> alias for male/female mapmaker vendors Added new Race DFN entries: [RACE 29] -> Cursed (enemies of RACE 30) [RACE 30] -> Zealots (enemies of RACE 29) Added new book DFN entries for Khaldun journals of Lysander, Tavara and Grimmoch - available as loot from the respective NPCs (dfndata/misc/books.dfn and dfndata/items/misc/books.dfn) Added 25% chance for hit-animations and hit-SFX to play for targets in combat, instead of playing every time - can get very spammy Monsters with actual weapons equipped (like Ophidian Enforcer with Halberd/Bardiche) will now get appropriate combat SFX for hitting with that weapon Updated Healing skill to base cure/resurrection skill requirements on the calculated skill, rather than base skill (js/skill/healing.js) Fixed an issue where healing with bandages and dying in the process would not properly reset the healing-related tags and skills used Updated healing script to show a resurrection confirmation menu for players targeted with bandages for resurrect (js/skill/healing.js) All skill usage states now reset on death, to prevent cases of players being "busy" while trying to use skills that got stuck due to script bugs. The 'regspawn GM command now supports a new parameter called "max". Updated command syntax: 'regspawn # // perform a single respawn cycle in a specific spawn region 'regspawn all // perform a single respawn cycle across all spawn regions 'regspawn max // respawn ALL spawn regions to MAX capacity in one go Added new UOX.INI setting that determines the maximum range at which NPCs can initiate attacks on players/NPCs (the old MAXRANGE=10 setting now instead defines max range at which players can initiate attacks on players/NPCs): MAXNPCAGGRORANGE=10 Optimized performance by significantly reducing the number of line-of-sight checks performed by characters, especially in combat scenarios, or when evaluating potential targets for NPC AIs. The amount of checks done is also affected by the new ini setting for max aggro range. Fixed an issue where both NPC and player characters would instantly drop one hunger level (from 6 to 5) upon creation since hungerrate was not initialized until after first hunger-event Updated character priv property from UI16 to UI32, and added a new flag that determines if fame/karma title is hidden for character: HIDEFAMEKARMATITLE (0x10000) NPCs will no longer aggro other NPCs if the Z difference between them is greater or equal to 20 (i.e. they're on different floors), unless both are using ranged weapons and both are in range Fixed an issue where players could get discounts from shopkeepers even though neither player nor shopkeeper were members of a NPC guild (js/npc/ai/shopkeeper.js) Fixed an issue where the "premium" a player would get when selling items to a shopkeeper in the same NPC guild as them would be 110% extra on top of the item's value, instead of the intended 10% (js/npc/ai/shopkeeper.js) (Thanks, cobrag0318!) Added missing tall straw hat to tailoring crafting scripts (Thanks, Dragon Slayer!) Improved error reporting during compilation of individual scripts being reloaded - will now show more context and line number onCombatEnd JS Event now also triggers when NPCs ignore their target and/or evade during combat Updated 'decorate command to support saving/loading custom tags on items for world templates. These are stored after a @ symbol in the world template, each custom tag separated by a | symbol, and with each custom tag saved using this syntax: key$type$value Updated resource-harvesting scripts to use script-specific global (const) variables, to avoid interference between different scripts (js/server/resource/*) Added new resource-harvesting scripts and added spawn regions for these in fields on farms all over Britannia/Lost Lands cabbages (js/server/resource/cabbages.js) canteloupes (js/server/resource/canteloupes.js) carrots (js/server/resource/carrots.js) garlic (js/server/resource/garlic.js) gourds (js/server/resource/gourds.js) honeydew melons (js/server/resource/honeydewmelon.js) onions (js/server/resource/onions.js) pumpkins (js/server/resource/pumpkins.js) squashes (js/server/resource/squashes.js) turnips (js/server/resource/turnips.js) watermelons (js/server/resource/watermelon.js) Updated CSpawnRegion::FindItemSpotToSpawn() function in cSpawnRegion.cpp to prevent duplicate spawning of exact same item in exact same location Updated sectionid of all potion and scroll aliases to match main item, so they'll get correctly picked up by NPC shopkeepers (dfndata/items/magic/potions.dfn and scrolls.dfn) Specified sectionid for backpacks, so it also gets applied to packs added using the alias [backpack] (dfndata/items/misc/provisions.dfn) Fixed a code issue that prevented players from selling empty containers to NPC shopkeepers willing to buy such containers Fixed an issue where field-spells (wall of stone, poison field, etc) would ignore dynamic/static items that should block these Fixed an issue where field-spells could be cast into houses from the outside by standing next to a wall and targeting self Fixed a War/Peace-mode desync between client and server by having server always notify client (using war-toggle packet) when the "at war" flag is toggled for player character Fixed an issue with hiding skill that wouldn't allow player to hide after having acquired self as target in combat (through spellcasting, for instance) (Thanks, Dragon Slayer!) Fixed an issue where mana/reagents were consumed and skillcheck performed before all spellcast-validations had succeeded, resulting in the occasional loss of reagents/mana when spellcasting was disallowed (code and js/magic/clumsy.js and level1targ.js) Fixed an issue with lack of criminal-flagging for casting of hostile spells scripted in JS (clumsy, magic arrow, feeblemind) vs blue targets (js/magic/clumsy.js and level1targ.js) Fixed some issues with Line-of-Sight checking code that returned incorrect results, which amongst other things affected spellcasting in areas with uneven terrain Fixed some issues with movement code and climbing of ladders, in particular the rope ladder in the 2-story log cabin. Code now sets a "is climbing" flag when character steps onto a rope ladder, and uses that to help resolve some edge cases, and then unsets it when they step off. Renamed section headers of some map-items in DFNs from "locationname" to "locationnamemap" for clarity (example: "britain" to "britainmap") Fixed broken "map wrapping" when sailing to edges of map in Felucca or Trammel Fixed a server crash related to attempts at reading data from invalid map tiles
1165 lines
34 KiB
C++
1165 lines
34 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"
|
|
#include "CJSMapping.h"
|
|
#include "cScript.h"
|
|
#include "CJSEngine.h"
|
|
|
|
|
|
#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 - GetBoat()
|
|
//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 followers 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 myFollowers = mChar->GetFollowerList();
|
|
for( const auto &follower : myFollowers->collection() )
|
|
{
|
|
if( ValidateObject( follower ))
|
|
{
|
|
// Only teleport followers with player if they're set to follow player, and within range
|
|
if( !follower->GetMounted() && follower->GetNpcWander() == WT_FOLLOW && ObjInRange( mChar, follower, DIST_SAMESCREEN ))
|
|
{
|
|
follower->SetLocation( x, y, z, worldNumber, instanceId );
|
|
}
|
|
}
|
|
}
|
|
s->SysMessage( 3 ); // You left the boat.
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
s->SysMessage( 4 ); // You cannot get off here!
|
|
return false;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - PlankStuff()
|
|
//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 myFollowers = mChar->GetFollowerList();
|
|
for( const auto &follower : myFollowers->collection() )
|
|
{
|
|
if( ValidateObject( follower ))
|
|
{
|
|
// Only teleport followers with player if they're set to follow and within range
|
|
if( !follower->GetMounted() && follower->GetNpcWander() == WT_FOLLOW && ObjInRange( mChar, follower, DIST_SAMESCREEN ))
|
|
{
|
|
follower->SetLocation( mChar );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( ValidateObject( boat2 ))
|
|
{
|
|
s->SysMessage( 1 ); // You board the boat.
|
|
}
|
|
else
|
|
{
|
|
s->SysMessage( 2 ); // There was no boat found there.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LeaveBoat( s, p );
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - OpenPlank()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Open / Close the plank on a boat
|
|
//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 - BlockBoat()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Check if a boat will be blocked if trying to move/turn in a specific direction
|
|
//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.terrainInfo == nullptr || ( 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 - CreateBoat()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Create a boat
|
|
//o------------------------------------------------------------------------------------------------o
|
|
bool CreateBoat( CSocket *s, CBoatObj *b, UI08 id2, UI08 boattype )
|
|
{
|
|
if( !ValidateObject( b ))
|
|
{
|
|
if( s != nullptr )
|
|
{
|
|
s->SysMessage( 5 ); // There was an error creating that boat.
|
|
}
|
|
return false;
|
|
}
|
|
switch( id2 )
|
|
{
|
|
case 0x00:
|
|
case 0x04:
|
|
case 0x08:
|
|
case 0x0C:
|
|
case 0x10:
|
|
case 0x14:
|
|
case 0x18:
|
|
break;
|
|
default:
|
|
if( s != nullptr )
|
|
{
|
|
s->SysMessage( 6 ); // The deed is broken, please contact a Game Master
|
|
}
|
|
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, instanceId, 20 );
|
|
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 = nullptr;
|
|
if( s != nullptr )
|
|
{
|
|
mChar = s->CurrcharObj();
|
|
}
|
|
|
|
CItem *tiller = Items->CreateItem( nullptr, mChar, 0x3E4E, 1, 0, OT_ITEM, false, true, worldNumber, instanceId, x, y, z );
|
|
if( tiller == nullptr )
|
|
return false;
|
|
|
|
std::string dictName = s != nullptr ? Dictionary->GetEntry( 2035, s->Language() ) : Dictionary->GetEntry( 2035 );
|
|
if( b->GetName().length() > 0 && b->GetName() != dictName ) // a ship
|
|
{
|
|
std::string tillerNameDict = s != nullptr ? Dictionary->GetEntry( 2033, s->Language() ) : Dictionary->GetEntry( 2033 ); // 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, false, true, worldNumber, instanceId, x, y, z ); // 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, false, true, worldNumber, instanceId, x, y, z ); // 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, false, true, worldNumber, instanceId, x, y, z );
|
|
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 - MoveBoat()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Move the boat and everything on it 1 tile in its current direction
|
|
//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 = 4970;
|
|
teleportBoat = true;
|
|
}
|
|
else if(( x + tx ) >= 5030 && tx > 0 )
|
|
{
|
|
// Sailing east
|
|
tx = -4970;
|
|
teleportBoat = true;
|
|
}
|
|
else if(( y + ty ) <= 15 && ty < 0 )
|
|
{
|
|
// Sailing north
|
|
ty = 4020;
|
|
teleportBoat = true;
|
|
}
|
|
else if(( y + ty ) >= 4060 && ty > 0 )
|
|
{
|
|
// Sailing south
|
|
ty = -4040;
|
|
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 - TurnStuff()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Turn an item on a boat to match the boat's turning
|
|
//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 - TurnBoat()
|
|
//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 );
|
|
}
|
|
|
|
auto scriptTriggers = b->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
auto toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute )
|
|
{
|
|
if( toExecute->OnBoatTurn( b, olddir, b->GetDir(), tiller ) == 1 )
|
|
{
|
|
// A script with the event returned true; prevent other scripts from running
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 - CBoatResponse::Handle()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Check for spoken boat commands and handle the response
|
|
//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 - ModelBoat()
|
|
//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 );
|
|
}
|
|
}
|
|
|
|
|