uox3/source/mapstuff.cpp
Xoduz 30ddfefd51 Small update
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
2025-06-27 09:04:16 +08:00

2113 lines
74 KiB
C++

#include "uox3.h"
#include "regions.h"
#include "townregion.h"
#include "cServerDefinitions.h"
#include "ssection.h"
#include "scriptc.h"
#include <filesystem>
#include <algorithm>
#include <array>
#include <fstream>
using namespace std::string_literals;
CMulHandler *Map = nullptr;
//const UI16 LANDDATA_SIZE = 0x4000; //(512 * 32)
/*!
** New rewrite plans, its way confusing having every function in here using 'Height' to mean
** elevation or actual height of a tile. Here's the new renaming plan:
** Elevation - the z value of the bottom edge of an item
** Height - the height of the item, it is independant of the elevation, like a person is 6ft tall
** Top - the z value of the top edge of an item, is always Elevation + Height of item
*/
//==================================================================================================
// CMulHandler
//==================================================================================================
//==================================================================================================
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::LoadMapsDFN()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Loads maps.dfn to see which map files to actually load into memory later
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::LoadMapsDFN( const std::string &uodir ) -> std::map<int, MapDfnData_st>
{
auto entrycount = FileLookup->CountOfEntries( maps_def );
std::map<int, MapDfnData_st> results;
auto uopath = std::filesystem::path( uodir );
for( auto i = 0; i < static_cast<int>( entrycount ); i++ )
{
auto toFind = FileLookup->FindEntry( "MAP "s + std::to_string( i ), maps_def );
if( toFind == nullptr )
break;
auto entry = MapDfnData_st();
for( const auto &sec :toFind->collection() )
{
auto uTag = oldstrutil::upper( sec->tag );
auto data = oldstrutil::trim( oldstrutil::removeTrailing( sec->data ));
switch( uTag.data()[0] )
{
case 'M':
if( uTag == "MAP" )
{
entry.mapFile = ( uopath / std::filesystem::path( data ));
}
else if( uTag == "MAPUOPWRAP" )
{
entry.mapUop = ( uopath / std::filesystem::path( data ));
}
else if( cwmWorldState->ServerData()->MapDiffsEnabled() )
{
if( uTag == "MAPDIFF" )
{
entry.mapDiff = ( uopath / std::filesystem::path( data ));
}
else if( uTag == "MAPDIFFLIST" )
{
entry.mapDiffl = ( uopath / std::filesystem::path( data ));
}
}
break;
case 'S':
if( uTag == "STATICS" )
{
entry.staMul = ( uopath / std::filesystem::path( data ));
}
else if( uTag == "STAIDX" )
{
entry.staIdx = ( uopath / std::filesystem::path( data ));
}
else if( cwmWorldState->ServerData()->MapDiffsEnabled() )
{
if( uTag == "STATICSDIFF" )
{
entry.staDiff = ( uopath / std::filesystem::path( data ));
}
else if( uTag == "STATICSDIFFLIST" )
{
entry.staDiffl = ( uopath / std::filesystem::path( data ));
}
else if( uTag == "STATICSDIFFINDEX" )
{
entry.staDiffi = ( uopath / std::filesystem::path( data ));
}
}
break;
case 'X':
if( uTag == "X" )
{
entry.width = std::stoi( data );
}
break;
case 'Y':
if( uTag == "Y" )
{
entry.height = std::stoi( data );
}
break;
}
results.insert_or_assign( i, entry );
}
}
return results;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::Load()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load UO data (tiledata, maps, statics, multis)
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::Load() -> void
{
auto uodir = cwmWorldState->ServerData()->Directory( CSDDP_DATA );
auto mapinfo = LoadMapsDFN( uodir );
Console.PrintSectionBegin();
Console << "Loading UO Data..." << myendl << "(If they fail to load, check your DATADIRECTORY path in uox.ini or filenames in maps.dfn)" << myendl;
LoadTileData( uodir );
LoadDFNOverrides();
LoadMapAndStatics( mapinfo );
if( uoWorlds.empty() )
{
Console.Error( " Fatal Error: No maps found" );
Console.Error( " Check the settings for DATADIRECTORY in uox.ini" );
Shutdown( FATAL_UOX3_MAP_NOT_FOUND );
}
LoadMultis( uodir );
FileLookup->Dispose( maps_def );
Console.PrintSectionBegin();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::LoadTileData()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Loads tiledata file into memory
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::LoadTileData( const std::string& uodir ) -> void
{
std::string lName = uodir + "tiledata.mul"s;
Console << "\t" << lName << "\t";
if( !tileInfo.LoadTiles( lName ))
{
Console.PrintFailed();
Shutdown( FATAL_UOX3_TILEDATA_NOT_FOUND );
}
Console.PrintDone();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::LoadDFNOverrides()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Override flags/properties for tiles based on DFNDATA/MAPS/tiles.dfn
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::LoadDFNOverrides() -> void
{
std::uint16_t entryNum;
for( auto &mapScp : FileLookup->ScriptListings[maps_def] )
{
if( mapScp == nullptr )
continue;
for( const auto &[entryName, toScan]: mapScp->collection() )
{
if( toScan == nullptr )
continue;
entryNum = oldstrutil::value<std::uint16_t>( oldstrutil::extractSection( entryName, " ", 1, 1 ));
auto titlePart = oldstrutil::upper( oldstrutil::extractSection( entryName, " ", 0, 0 ));
// have we got an entry starting with TILE ?
if( titlePart == "TILE" && entryNum )
{
if(( entryNum == INVALIDID ) || ( entryNum > tileInfo.SizeArt() ))
continue;
auto tile = &tileInfo.ArtInfo( entryNum );
if( tile )
{
for( const auto &sec : toScan->collection() )
{
auto UTag = oldstrutil::upper( sec->tag );
auto data = oldstrutil::trim( oldstrutil::removeTrailing( sec->data, "//" ));
// CTile properties
if( UTag == "WEIGHT" )
{
tile->Weight( static_cast<UI08>( std::stoul( data, nullptr, 0 )));
}
else if( UTag == "HEIGHT" )
{
tile->Height( static_cast<SI08>( std::stoi( data, nullptr, 0 )));
}
else if( UTag == "LAYER" )
{
tile->Layer( static_cast<SI08>( std::stoi( data, nullptr, 0 )));
}
else if( UTag == "HUE" )
{
tile->Hue( static_cast<UI08>( std::stoul( data, nullptr, 0 )));
}
else if( UTag == "QUANTITY" )
{
tile->Quantity( static_cast<UI08>( std::stoul( data, nullptr, 0 )));
}
else if( UTag == "ANIMATION" )
{
tile->Animation( static_cast<UI16>( std::stoul( data, nullptr, 0 )));
}
else if( UTag == "NAME" )
{
tile->Name( data );
}
// BaseTile Flag 1
else if( UTag == "ATFLOORLEVEL" )
{
tile->SetFlag( TF_FLOORLEVEL, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "HOLDABLE" )
{
tile->SetFlag( TF_HOLDABLE, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "SIGNGUILDBANNER" )
{
tile->SetFlag( TF_TRANSPARENT, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "WEBDIRTBLOOD" )
{
tile->SetFlag( TF_TRANSLUCENT, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "WALLVERTTILE" )
{
tile->SetFlag( TF_WALL, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "DAMAGING" )
{
tile->SetFlag( TF_DAMAGING, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "BLOCKING" )
{
tile->SetFlag( TF_BLOCKING, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "LIQUIDWET" )
{
tile->SetFlag( TF_WET, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
// BaseTile Flag 2
else if( UTag == "UNKNOWNFLAG1" )
{
tile->SetFlag( TF_UNKNOWN1, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "STANDABLE" )
{
tile->SetFlag( TF_SURFACE, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "CLIMBABLE" )
{
tile->SetFlag( TF_CLIMBABLE, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "STACKABLE" )
{
tile->SetFlag( TF_STACKABLE, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "WINDOWARCHDOOR" )
{
tile->SetFlag( TF_WINDOW, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "CANNOTSHOOTTHRU" )
{
tile->SetFlag( TF_NOSHOOT, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "DISPLAYASA" )
{
tile->SetFlag( TF_DISPLAYA, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "DISPLAYASAN" )
{
tile->SetFlag( TF_DISPLAYAN, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
// BaseTile Flag 3
else if( UTag == "DESCRIPTIONTILE" )
{
tile->SetFlag( TF_DESCRIPTION, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "FADEWITHTRANS" )
{
tile->SetFlag( TF_FOLIAGE, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "PARTIALHUE" )
{
tile->SetFlag( TF_PARTIALHUE, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "UNKNOWNFLAG2" )
{
tile->SetFlag( TF_UNKNOWN2, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "MAP" )
{
tile->SetFlag( TF_MAP, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "CONTAINER" )
{
tile->SetFlag( TF_CONTAINER, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "EQUIPABLE" )
{
tile->SetFlag( TF_WEARABLE, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "LIGHTSOURCE" )
{
tile->SetFlag( TF_LIGHT, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
// BaseTile Flag 4
else if( UTag == "ANIMATED" )
{
tile->SetFlag( TF_ANIMATED, (std::stoi(data, nullptr, 0) != 0 ));
}
else if( UTag == "NODIAGONAL" )
{
tile->SetFlag( TF_NODIAGONAL, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "UNKNOWNFLAG3" )
{
tile->SetFlag( TF_UNKNOWN3, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "WHOLEBODYITEM" )
{
tile->SetFlag( TF_ARMOR, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "WALLROOFWEAP" )
{
tile->SetFlag( TF_ROOF, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "DOOR" )
{
tile->SetFlag( TF_DOOR, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "CLIMBABLEBIT1" )
{
tile->SetFlag( TF_STAIRBACK, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
else if( UTag == "CLIMBABLEBIT2" )
{
tile->SetFlag( TF_STAIRRIGHT, ( std::stoi( data, nullptr, 0 ) != 0 ));
}
}
}
}
else if( titlePart == "LAND" && entryNum )
{ // let's not deal with this just yet
}
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::LoadMapAndStatics()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Loads map and statics files into memory
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::LoadMapAndStatics( const std::map<int, MapDfnData_st> &info ) -> void
{
for( const auto &[mapNum, dfndata]:info )
{
uoWorlds.insert_or_assign( mapNum, UltimaMap( mapNum, dfndata.width, dfndata.height, &tileInfo ));
auto rValue = false;
// Load Terrain/Map data from UOP > MUL
if( std::filesystem::exists( dfndata.mapUop ))
{
//Console << "\t" << dfndata.mapUop.string() << "(/" << dfndata.mapUop.filename().string() << ")\t\t";
Console << "\t" << dfndata.mapUop.string() << "\t\t";
rValue = uoWorlds[mapNum].LoadUOPTerrainFile( dfndata.mapUop.string() );
}
else
{
//Console << "\t" << dfndata.mapFile.string() << "(/" << dfndata.mapFile.filename().string() << ")\t\t";
Console << "\t" << dfndata.mapFile.string() << "\t\t";
rValue = uoWorlds[mapNum].LoadMulTerrainFile( dfndata.mapFile.string() );
if( rValue )
{
uoWorlds[mapNum].ApplyTerrainDiff( dfndata.mapDiffl.string(), dfndata.mapDiff.string() );
}
}
if( !rValue )
{
Console.PrintFailed();
}
else
{
Console.PrintDone();
}
// Load Art/Statics data
if( rValue )
{
Console << "\t" << dfndata.staMul.string() << " / " << dfndata.staIdx.filename().string() << "\t\t";
rValue = uoWorlds[mapNum].LoadArt( dfndata.staMul.string(), dfndata.staIdx.string() );
if( rValue )
{
uoWorlds[mapNum].ApplyDiff( dfndata.staDiffl.string(), dfndata.staDiffi.string(), dfndata.staDiff.string() );
}
else
{
// this is interesting, we found the terrain, but not the art. Regardless, lets delete it
auto iter = uoWorlds.find( mapNum );
if( iter != uoWorlds.end() )
{
uoWorlds.erase( iter );
}
}
if( !rValue )
{
Console.PrintFailed();
}
else
{
Console.PrintDone();
}
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::LoadMultis()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Loads multi files into memory
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::LoadMultis( const std::string& uodir ) -> void
{
// now main memory multiItems
// Odd we do no check?
if( !multiData.LoadMultiCollection( std::filesystem::path( uodir ), &tileInfo ))
{
Console.PrintFailed();
Shutdown( FATAL_UOX3_MULTI_DATA_NOT_FOUND );
}
Console.PrintDone();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::ArtAt()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Try to find and return map tile at specific location in a world
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::ArtAt( std::int16_t x, std::int16_t y, std::uint8_t world ) -> std::vector<Tile_st>&
{
static std::vector<Tile_st> empty;
try
{
return uoWorlds.at( world ).ArtAt( x, y );
}
catch(...)
{
return empty;
}
}
//=-----------------------------------------------------------------------------------------------=
auto CMulHandler::ArtAt( std::int16_t x, std::int16_t y, std::uint8_t world ) const -> const std::vector<Tile_st>&
{
static std::vector<Tile_st> empty;
try
{
return uoWorlds.at( world ).ArtAt( x, y );
}
catch(...)
{
return empty;
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::SizeOfMap()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Return x/y size of specified world
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::SizeOfMap( std::uint8_t worldNumber ) const -> std::pair<int, int>
{
return uoWorlds.at( worldNumber ).Size();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::DiffCountForMap()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Return count of map differences for specified world
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::DiffCountForMap( std::uint8_t worldNumber ) const -> std::pair<int, int>
{
return std::make_pair( uoWorlds.at( worldNumber ).DiffArt(), uoWorlds.at( worldNumber ).DiffTerrain() );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::MultiHeight()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Return the height of a multi item at the given x, y.
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::MultiHeight( CItem *i, std::int16_t x, std::int16_t y, std::int8_t oldZ, std::int8_t maxZ, bool checkHeight ) -> std::int8_t
{
UI16 multiId = static_cast<UI16>(i->GetId() - 0x4000);
SI08 mHeight = ILLEGAL_Z;
SI08 tmpTop = ILLEGAL_Z;
if( !MultiExists( multiId ))
return mHeight;
const SI16 baseX = i->GetX();
const SI16 baseY = i->GetY();
const SI08 baseZ = i->GetZ();
for( auto &multi : SeekMulti( multiId ).items )
{
if(( checkHeight || multi.flag ) && ( baseX + multi.offsetX ) == x && ( baseY + multi.offsetY ) == y )
{
if( checkHeight )
{
// Returns height of highest point of multi
tmpTop = static_cast<SI08>( baseZ + multi.altitude );
if(( tmpTop <= oldZ + maxZ ) && tmpTop > oldZ && tmpTop > mHeight )
{
mHeight = tmpTop;
}
}
else
{
tmpTop = static_cast<SI08>( baseZ + multi.altitude );
if( std::abs( tmpTop - oldZ ) <= maxZ )
{
mHeight = tmpTop + multi.info->ClimbHeight( true );
if( mHeight == oldZ ) // We found a surface at the suggested height
break;
}
}
}
}
return mHeight;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::MultiTile()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns ID of tile in multi at specified coordinates
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::MultiTile( CItem *i, std::int16_t x, std::int16_t y, std::int8_t oldz, bool checkVisible ) -> std::uint16_t
{
auto rValue = std::uint16_t();
if( !i->CanBeObjType( OT_MULTI ))
return 0;
UI16 multiId = static_cast<UI16>( i->GetId() - 0x4000 );
[[maybe_unused]] SI32 length = 0;
if( !MultiExists( multiId ))
{
Console << "CMulHandler::MultiTile->Bad length in multi file. Avoiding stall." << myendl;
auto map1 = Map->SeekMap( i->GetX(), i->GetY(), i->WorldNumber() );
if( map1.terrainInfo != nullptr && map1.CheckFlag( TF_WET )) // is it water?
{
i->SetId( 0x4001 );
}
else
{
i->SetId( 0x4064 );
}
length = 0;
}
else
{
// Loop through each item that makes up the multi
// If any of those items intersect with area were are checking, return the ID of that tile
for( auto &multi : SeekMulti( multiId ).items )
{
if( !checkVisible )
{
if(( i->GetX() + multi.offsetX == x ) && ( i->GetY() + multi.offsetY == y ))
{
rValue = multi.tileId;
break;
}
}
else if( multi.flag > 0 && ( std::abs( i->GetZ() + multi.altitude - oldz ) <= 1 ))
{
if(( i->GetX() + multi.offsetX == x ) && ( i->GetY() + multi.offsetY == y ))
{
rValue = multi.tileId;
break;
}
}
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::DynTile()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns which dynamic tile is present at (x,y) or -1 if no tile exists
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::DynTile( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::uint16_t instanceId, bool checkOnlyMultis, bool checkOnlyNonMultis ) -> CItem *
{
auto rValue = static_cast<CItem*>( nullptr );
for( auto &cellResponse : MapRegion->PopulateList( x, y, worldNumber ))
{
if( cellResponse == nullptr )
continue;
auto regItems = cellResponse->GetItemList();
for( const auto &item : regItems->collection() )
{
if( !ValidateObject( item ) || ( item->GetInstanceId() != instanceId ))
continue;
if( !checkOnlyNonMultis )
{
if( item->GetId( 1 ) >= 0x40 && ( item->GetObjType() == OT_MULTI || item->CanBeObjType( OT_MULTI )))
{
auto multiTileId = MultiTile( item, x, y, z, false );
if( multiTileId > 0 )
{
rValue = item;
break;
}
}
else if( item->GetMulti() != INVALIDSERIAL || ValidateObject( CalcMultiFromSer( item->GetOwner() )))
{
if(( item->GetX() == x ) && ( item->GetY() == y ))
{
rValue = item;
break;
}
}
}
else if( !checkOnlyMultis && ( item->GetX() == x ) && ( item->GetY() == y ))
{
rValue = item;
break;
}
}
if( rValue )
{
break;
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::DoesStaticBlock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if statics at/above given coordinates blocks movement, etc
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::DoesStaticBlock( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, bool checkWater ) -> bool
{
auto &artTiles = Map->ArtAt( x, y, worldNumber );
auto rValue = false;
for( auto &tile : artTiles )
{
auto elev = static_cast<SI08>( tile.altitude + tile.artInfo->ClimbHeight() );
if( checkWater )
{
if( elev >= z && tile.altitude <= z && ( tile.CheckFlag( TF_BLOCKING ) || tile.CheckFlag( TF_WET ) || tile.CheckFlag( TF_ROOF )))
{
rValue = true;
break;
}
}
else
{
if( elev >= z && tile.altitude <= z && ( tile.CheckFlag( TF_BLOCKING ) && !tile.CheckFlag( TF_WET ) || tile.CheckFlag( TF_ROOF )))
{
rValue = true;
break;
}
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::DoesDynamicBlock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if there are any dynamic tiles at given coordinates that block movement
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::DoesDynamicBlock( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::uint16_t instanceId, bool checkWater, bool waterWalk, bool checkOnlyMultis, bool checkOnlyNonMultis ) -> bool
{
auto rValue = false;
// get the tile id of any dynamic tiles at this spot
auto dtItem = DynTile( x, y, z, worldNumber, instanceId, checkOnlyMultis, checkOnlyNonMultis );
if( ValidateObject( dtItem ))
{
// Don't allow placing houses on top of immovable, visible dynamic items
if( dtItem->GetMovable() == 2 && dtItem->GetVisible() == 0 )
return true;
// Ignore items that are permanently invisible or visible to GMs only
if( dtItem->GetVisible() == 1 || dtItem->GetVisible() == 3 )
return false;
auto dtItemZ = dtItem->GetZ();
rValue = false;
const UI16 dt = dtItem->GetId();
if( IsValidTile( dt ) && dt != 0 )
{
auto tile = SeekTile( dt );
if( waterWalk )
{
if( !tile.CheckFlag( TF_WET ))
{
rValue = true;
}
}
else
{
auto elev = static_cast<SI08>( dtItemZ + tile.ClimbHeight() );
if( elev >= z && dtItemZ <= z && ( tile.CheckFlag( TF_ROOF ) || tile.CheckFlag( TF_BLOCKING ) || ( checkWater && tile.CheckFlag( TF_WET ))) /*|| !tile.CheckFlag( TF_SURFACE ) */ )
{
rValue = true;
}
}
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::DoesMapBlock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if the map tile at the given coordinates block movement
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::DoesMapBlock( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, bool checkWater, bool waterWalk, bool checkMultiPlacement, bool checkForRoad ) -> bool
{
if( checkWater || waterWalk )
{
const auto &map = SeekMap( x, y, worldNumber );
if( map.terrainInfo == nullptr )
return true;
if(( checkMultiPlacement && map.altitude == z ) || ( !checkMultiPlacement && map.altitude >= z && map.altitude - z < 16 ))
{
if( z == ILLEGAL_Z )
return true;
if( waterWalk )
{
if( !map.CheckFlag( TF_WET ))
{
return true;
}
}
else
{
if( map.CheckFlag( TF_WET ) || map.CheckFlag( TF_BLOCKING ))
{
return true;
}
}
// Check that player is not attempting to place a multi on a road
if( checkWater && checkMultiPlacement && checkForRoad )
{
if( map.terrainInfo->IsRoadId() )
{
return true;
}
}
}
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::DoesCharacterBlock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if there are any characters at given coordinates that block movement
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::DoesCharacterBlock( UI16 x, UI16 y, SI08 z, UI08 worldNumber, UI16 instanceId ) -> bool
{
for( auto &cellResponse : MapRegion->PopulateList( x, y, worldNumber ))
{
if( cellResponse == nullptr )
continue;
auto regChars = cellResponse->GetCharList();
for( const auto &tempChar : regChars->collection() )
{
if( !ValidateObject( tempChar ) || ( tempChar->GetInstanceId() != instanceId ))
continue;
// Is character within the range that normally blocks movement?
if( tempChar->GetX() == x && tempChar->GetY() == y && tempChar->GetZ() >= z - 2 && tempChar->GetZ() <= z + 2 )
{
// Is character a visible NPC, or a non-dead visible player?
if(( tempChar->IsNpc() && tempChar->GetVisible() == VT_VISIBLE ) || ( !tempChar->IsDead() && tempChar->GetVisible() == VT_VISIBLE ))
{
// Character found, potentially blocking
return true;
}
}
}
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::CheckStaticFlag()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks to see whether any statics at given coordinates has a specific flag
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::CheckStaticFlag( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, TileFlags toCheck, UI16 &foundtileID, bool checkSpawnSurface ) -> bool
{
auto rValue = checkSpawnSurface;
for( const auto &tile : Map->ArtAt( x, y, worldNumber ))
{
const SI08 elev = static_cast<SI08>( tile.altitude );
const SI08 tileHeight = static_cast<SI08>( tile.artInfo->ClimbHeight() );
if( checkSpawnSurface )
{
// Special case used when checking for spawn surfaces
if(( z >= elev && z <= ( elev + tileHeight )) && !tile.CheckFlag( toCheck ))
{
rValue = false;
break;
}
}
else
{
// Generic check exposed to JS
if(( z >= elev && z <= ( elev + tileHeight )) && tile.CheckFlag( toCheck ))
{
foundtileID = tile.tileId;
rValue = true; // Found static with specified flag
break;
}
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::CheckDynamicFlag()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks to see whether any dynamics at given coordinates has a specific flag
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::CheckDynamicFlag( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::uint16_t instanceId, TileFlags toCheck, UI16 &foundTileId ) -> bool
{
// Special case for handling multis that cross over between multiple map regions because of size
CMultiObj *tempMulti = FindMulti( x, y, z, worldNumber, instanceId );
if( ValidateObject( tempMulti ) )
{
// Look for a multi item at specific location
auto multiId = static_cast<UI16>( tempMulti->GetId() - 0x4000 );
for( auto &multiItem : SeekMulti( multiId ).items )
{
if( multiItem.flag > 0 && ( abs( tempMulti->GetZ() + multiItem.altitude - z ) <= 1 ))
{
if(( tempMulti->GetX() + multiItem.offsetX == x ) && ( tempMulti->GetY() + multiItem.offsetY == y ))
{
if( SeekTile( multiItem.tileId ).CheckFlag( toCheck ))
{
foundTileId = multiItem.tileId;
return true;
}
}
}
}
}
else
{
// Search map region of given location for all potentially blocking dynamic items
for( auto &cellResponse : MapRegion->PopulateList( x, y, worldNumber ))
{
if( cellResponse == nullptr )
continue;
auto regItems = cellResponse->GetItemList();
for( const auto &item : regItems->collection() )
{
if( !ValidateObject( item ) || item->GetInstanceId() != instanceId )
continue;
if(( item->GetId( 1 ) >= 0x40 ) && (( item->GetObjType() == OT_MULTI ) || (item->CanBeObjType( OT_MULTI ))))
{
// Found a multi
// Look for a multi item at specific location
auto multiId = static_cast<UI16>( item->GetId() - 0x4000 );
for( auto &multiItem : SeekMulti( multiId ).items )
{
if( multiItem.flag > 0 && ( abs( item->GetZ() + multiItem.altitude - z ) <= 1 ))
{
if(( item->GetX() + multiItem.offsetX == x ) && ( item->GetY() + multiItem.offsetY == y ))
{
if( SeekTile( multiItem.tileId ).CheckFlag( toCheck ))
{
foundTileId = multiItem.tileId;
return true;
}
}
}
}
}
else
{
// item is not a multi
if(( item->GetX() == x ) && ( item->GetY() == y ) && ( item->GetZ() == z ))
{
auto itemZ = item->GetZ();
const SI08 tileHeight = static_cast<SI08>( TileHeight( item->GetId() ));
if(( itemZ == z && itemZ + tileHeight > z ) || ( itemZ < z && itemZ + tileHeight >= z ))
{
if( SeekTile( item->GetId() ).CheckFlag( toCheck ))
{
foundTileId = item->GetId();
return true;
}
}
}
}
}
}
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::CheckTileFlag()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks to see whether given tile ID has a specified flag
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::CheckTileFlag( std::uint16_t itemId, TileFlags flagToCheck ) -> bool
{
if( !SeekTile( itemId ).CheckFlag( flagToCheck ))
{
return false;
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::StaticTop()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Top of statics at/above given coordinates
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::StaticTop( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::int8_t maxZ ) -> std::int8_t
{
auto top = ILLEGAL_Z;
const auto &artTiles = Map->ArtAt( x, y, worldNumber );
for( const auto &tile : artTiles )
{
auto tempTop = static_cast<std::int8_t>( tile.altitude + tile.artInfo->ClimbHeight() );
if(( tempTop <= z + maxZ ) && ( tempTop > top ))
{
top = tempTop;
}
}
return top;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::DynamicElevation()
//o------------------------------------------------------------------------------------------------o
//| Purpose - This was fixed to actually return the *elevation* of dynamic items at/above given coordinates
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::DynamicElevation( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::uint16_t instanceId, std::int8_t maxZ ) -> std::int8_t
{
auto dynZ = ILLEGAL_Z;
// Special case for handling multis that cross over between multiple map regions because of size
CMultiObj *tempMulti = FindMulti( x, y, z, worldNumber, instanceId );
if( ValidateObject( tempMulti ))
{
dynZ = MultiHeight( tempMulti, x, y, z, maxZ );
// Also check dynamic items inside the multi
for( const auto &tempMultiItem : tempMulti->GetItemsInMultiList()->collection() )
{
if( tempMultiItem->GetX() == x && tempMultiItem->GetY() == y )
{
SI08 zTemp = static_cast<SI08>( tempMultiItem->GetZ() + TileHeight( tempMultiItem->GetId() ));
if(( zTemp <= z + maxZ ) && ( zTemp > dynZ || ( dynZ >= z + maxZ )))
{
dynZ = zTemp;
}
}
}
}
else
{
auto MapArea = MapRegion->GetMapRegion( MapRegion->GetGridX( x ), MapRegion->GetGridY( y ), worldNumber );
if( MapArea )
{
auto regItems = MapArea->GetItemList();
for( const auto tempItem : regItems->collection() )
{
if( ValidateObject( tempItem ) && tempItem->GetInstanceId() == instanceId )
{
if( tempItem->GetId( 1 ) >= 0x40 && tempItem->CanBeObjType( OT_MULTI ))
{
dynZ = MultiHeight( tempItem, x, y, z, maxZ );
}
else if( tempItem->GetX() == x && tempItem->GetY() == y )
{
SI08 zTemp = static_cast<SI08>( tempItem->GetZ() + TileHeight( tempItem->GetId() ));
if(( zTemp <= z + maxZ ) && zTemp > dynZ )
{
dynZ = zTemp;
}
}
}
}
}
}
return dynZ;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::MapElevation()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns the map elevation at given coordinates, we'll assume since its land
//| the height is inherently 0
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::MapElevation( std::int16_t x, std::int16_t y, std::uint8_t worldNumber, bool ignoreVoid ) -> std::int8_t
{
const auto &map = SeekMap( x, y, worldNumber );
// make sure nothing can move into black areas
if( map.artInfo == nullptr || ( !ignoreVoid && map.isVoid() ))
{
return ILLEGAL_Z;
}
return map.altitude;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::TileHeight()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Height of a given tile (If climbable it return 1/2 its height).
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::TileHeight( std::uint16_t tilenum ) -> std::int8_t
{
return SeekTile( tilenum ).ClimbHeight();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::Height()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns new height of player who walked to X/Y but from OLDZ
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::Height( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::uint16_t instanceId ) -> std::int8_t
{
// let's check in this order.. dynamic, static, then the map
auto dynZ = DynamicElevation( x, y, z, worldNumber, instanceId, MAX_Z_STEP );
if( ILLEGAL_Z != dynZ )
return dynZ;
auto staticZ = StaticTop( x, y, z, worldNumber, MAX_Z_STEP );
if( ILLEGAL_Z != staticZ && abs( staticZ - z ) <= MAX_Z_STEP )
return staticZ;
return MapElevation( x, y, worldNumber );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::InBuilding()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns whether give coordinates are inside a building by checking if there is
//| a multi or static above them
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::InBuilding( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::uint16_t instanceId) -> bool
{
auto dynZ = Map->DynamicElevation( x, y, z, worldNumber, instanceId, static_cast<std::int8_t>( 127 ) );
if( dynZ > ( z + 10 ))
{
return true;
}
else
{
const SI08 staticZ = Map->StaticTop( x, y, z, worldNumber, static_cast<std::int8_t>( 127 ));
if( staticZ > ( z + 10 ))
{
return true;
}
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::MultiArea()
//o--------------------------------------------------------------------------
//| Purpose - Finds the corners of a multi object
//o--------------------------------------------------------------------------
auto CMulHandler::MultiArea( CMultiObj *i, std::int16_t &x1, std::int16_t &y1, std::int16_t &x2, std::int16_t &y2 ) -> void
{
if( ValidateObject( i ))
{
const SI16 xAdd = i->GetX();
const SI16 yAdd = i->GetY();
const UI16 multiNum = static_cast<UI16>( i->GetId() - 0x4000 );
if( MultiExists( multiNum ))
{
auto structure = SeekMulti( multiNum );
x1 = static_cast<SI16>( structure.minX + xAdd );
x2 = static_cast<SI16>( structure.maxX + xAdd );
y1 = static_cast<SI16>( structure.minY + yAdd );
y2 = static_cast<SI16>( structure.maxY + yAdd );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::MultiExists()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Verify that specified multi exists
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::MultiExists( std::uint16_t multinum ) const -> bool
{
return multiData.Exists( multinum );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::SeekMulti()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns data for items contained in given multi
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::SeekMulti( std::uint16_t multinum ) const -> const CollectionItem_st &
{
return multiData.Multi( multinum );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::IsValidTile()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if a given tile number falls within the range of tiles loaded in memory
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::IsValidTile( std::uint16_t tileNum ) const -> bool
{
bool retVal = true;
if(( tileNum == INVALIDID ) || ( tileNum >= tileInfo.SizeArt() ))
{
retVal = false;
}
return retVal;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::SeekTile()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Fetches data about a specific tile from memory
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::SeekTile( std::uint16_t tileNum ) -> CTile&
{
if( !IsValidTile( tileNum ))
{
Console.Warning( oldstrutil::format( "Invalid tile access, the offending tile number is %u", tileNum ));
static CTile emptyTile;
return emptyTile;
}
else
{
return tileInfo.ArtInfo( tileNum );
}
}
//=================================================================================================
auto CMulHandler::SeekTile( std::uint16_t tileNum ) const -> const CTile&
{
if( !IsValidTile( tileNum ))
{
Console.Warning( oldstrutil::format( "Invalid tile access, the offending tile number is %u", tileNum ));
static CTile emptyTile;
return emptyTile;
}
else
{
return tileInfo.ArtInfo( tileNum );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::SeekLand()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Fetches data about a specific land tile from memory.
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::SeekLand( std::uint16_t landNum ) ->CLand&
{
if( landNum == INVALIDID || landNum >= tileInfo.SizeTerrain() )
{
Console.Warning( oldstrutil::format( "Invalid land access, the offending land number is %u", landNum ));
static CLand emptyTile;
return emptyTile;
}
else
{
return tileInfo.TerrainInfo( landNum );
}
}
//=================================================================================================
auto CMulHandler::SeekLand( std::uint16_t landNum ) const -> const CLand&
{
if( landNum == INVALIDID || landNum >= tileInfo.SizeTerrain() )
{
Console.Warning( oldstrutil::format( "Invalid land access, the offending land number is %u", landNum ));
static CLand emptyTile;
return emptyTile;
}
else
{
return tileInfo.TerrainInfo( landNum );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::SeekMap()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Fetches map data for a specific map/world
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::SeekMap( std::int16_t x, std::int16_t y, std::uint8_t worldNumber ) ->Tile_st&
{
static Tile_st badTile;
try
{
return uoWorlds.at( worldNumber ).TerrainAt( x, y );
}
catch (...)
{
std::cerr << "Bad tile access attempted: " << x << "," << y << static_cast<int>( worldNumber ) << std::endl;
return badTile;
}
}
//=================================================================================================
auto CMulHandler::SeekMap( std::int16_t x, std::int16_t y, std::uint8_t worldNumber ) const -> const Tile_st&
{
static Tile_st badTile;
try
{
return uoWorlds.at( worldNumber ).TerrainAt( x, y );
}
catch (...)
{
std::cerr << "Bad tile access attempted: " << x << "," << y << static_cast<int>( worldNumber ) << std::endl;
return badTile;
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::ValidSpawnLocation()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if location at given coordinates is considered valid for spawning objects
//| Also used to verify teleport location for NPCs teleporting back to bounding box
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::ValidSpawnLocation( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::uint16_t instanceId, bool checkWater ) -> bool
{
if( !InsideValidWorld( x, y, worldNumber ))
return false;
// get the tile id of any dynamic tiles at this spot
if( DoesDynamicBlock( x, y, z, worldNumber, instanceId, checkWater, !checkWater, false, false ))
return false;
// if there's a static block here in our way, return false
if( DoesStaticBlock( x, y, z, worldNumber, checkWater ))
return false;
// if the static isn't a surface return false
[[maybe_unused]] UI16 ignoreMe = 0;
if( !CheckStaticFlag( x, y, z, worldNumber, ( checkWater ? TF_SURFACE : TF_WET ), ignoreMe, true ))
return false;
if( DoesMapBlock( x, y, z, worldNumber, checkWater, !checkWater, false, false ))
return false;
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::ValidMultiLocation()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks whether given location is valid for house/boat placement
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::ValidMultiLocation( std::int16_t x, std::int16_t y, std::int8_t z, std::uint8_t worldNumber, std::uint16_t instanceId,
bool checkWater, bool checkOnlyOtherMultis, bool checkOnlyNonMultis, bool checkForRoads ) -> std::uint8_t
{
if( !InsideValidWorld( x, y, worldNumber ))
return 0;
const SI08 elev = Height( x, y, z, worldNumber, instanceId );
if( ILLEGAL_Z == elev )
return 0;
// We don't want the house to be halfway embedded in a hill... or hanging off one for that matter.
if( z != ILLEGAL_Z )
{
if( elev - z > MAX_Z_STEP )
{
return 0;
}
else if( z - elev > MAX_Z_FALL )
{
return 0;
}
}
// get the tile id of any dynamic tiles at this spot
if( !checkOnlyNonMultis && DoesDynamicBlock( x, y, elev, worldNumber, instanceId, checkWater, false, checkOnlyOtherMultis, checkOnlyNonMultis ))
{
return 2;
}
// if there's a static block here in our way, return false
if( !checkOnlyOtherMultis && DoesStaticBlock( x, y, elev, worldNumber, checkWater ))
{
return 2;
}
if( DoesMapBlock( x, y, elev, worldNumber, checkWater, false, true, checkForRoads ))
{
return 0;
}
// Check if house placement is allowed in region
CTownRegion *calcReg = CalcRegionFromXY( x, y, worldNumber, instanceId );
if(( !calcReg->CanPlaceHouse() && checkWater) || calcReg->IsDungeon() || ( calcReg->IsGuarded() && checkWater ))
return 3;
// Else, all good!
return 1;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::MapExists()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns true if the server has that map in memory
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::MapExists( std::uint8_t worldNumber ) const -> bool
{
auto iter = uoWorlds.find( worldNumber );
return iter != uoWorlds.end();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::InsideValidWorld()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks if a given location is within the bounds of the specified world
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::InsideValidWorld( std::int16_t x, std::int16_t y, std::uint8_t worldNumber ) const -> bool
{
auto rValue = false;
try
{
auto [width, height] = uoWorlds.at( worldNumber ).Size();
if(( x >= 0 && x < width ) && ( y >= 0 && y < height ))
{
rValue = true;
}
}
catch(...)
{
// Do nothing, all rady set to false
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CMulHandler::MapCount()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns count of how many maps have been loaded
//o------------------------------------------------------------------------------------------------o
auto CMulHandler::MapCount() const -> std::uint8_t
{
// This ASSUMES the map numbers are consequtive. You dont have say map 0,1,3 <<<< missing 2
return static_cast<std::uint8_t>( uoWorlds.size() );
}
//o------------------------------------------------------------------------------------------------o
//| TileInfo
//o------------------------------------------------------------------------------------------------o
//o------------------------------------------------------------------------------------------------o
TileInfo::TileInfo( const std::string &filename ) : isHsFormat( false )
{
if( !filename.empty() )
{
this->LoadTiles( filename );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::LoadTiles()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Loads tiles from tiledata file into memory
//o------------------------------------------------------------------------------------------------o
auto TileInfo::LoadTiles( const std::string &filename ) -> bool
{
auto rValue = false;
artData.clear();
terrainData.clear();
isHsFormat = false;
auto path = std::filesystem::path( filename );
if( std::filesystem::exists( path ))
{
if( std::filesystem::file_size( path ) >= hsSize )
{
isHsFormat = true;
}
auto input = std::ifstream( filename, std::ios::binary );
if( input.is_open() )
{
rValue = true;
ProcessTerrain( input );
ProcessArt( input );
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::TerrainInfo()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns terrain data for given tileId
//o------------------------------------------------------------------------------------------------o
auto TileInfo::TerrainInfo( std::uint16_t tileId ) const -> const CLand&
{
return terrainData[tileId];
}
auto TileInfo::TerrainInfo( std::uint16_t tileId ) -> CLand&
{
return terrainData[tileId];
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::ArtInfo()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns art/statics data for given tileId
//o------------------------------------------------------------------------------------------------o
auto TileInfo::ArtInfo( std::uint16_t tileId ) const -> const CTile&
{
return artData[tileId];
}
auto TileInfo::ArtInfo( std::uint16_t tileId) -> CTile&
{
return artData[tileId];
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::SizeTerrain()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns size of terrain data loaded by UOX3
//o------------------------------------------------------------------------------------------------o
auto TileInfo::SizeTerrain() const -> size_t
{
return terrainData.size();
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::SizeArt()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns size of art/statics data loaded by UOX3
//o------------------------------------------------------------------------------------------------o
auto TileInfo::SizeArt() const -> size_t
{
return artData.size();
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::CollectionTerrain()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns collection of terrain tiles loaded by UOX3
//o------------------------------------------------------------------------------------------------o
auto TileInfo::CollectionTerrain() const -> const std::vector<CLand>&
{
return terrainData;
}
auto TileInfo::CollectionTerrain() -> std::vector<CLand> &
{
return terrainData;
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::CollectionArt()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns collection of art/statics tiles loaded by UOX3
//o------------------------------------------------------------------------------------------------o
auto TileInfo::CollectionArt() const -> const std::vector<CTile>&
{
return artData;
}
auto TileInfo::CollectionArt() -> std::vector<CTile>&
{
return artData;
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::ProcessTerrain()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Process terrain data read from tiledata file
//o------------------------------------------------------------------------------------------------o
auto TileInfo::ProcessTerrain( std::istream &input ) -> void
{
terrainData.reserve( 0x4000 );
std::uint32_t value32 = 0;
std::uint64_t value64 = 0;
std::array<char, 20> string_buffer;
string_buffer.fill( 0 );
for( auto i = 0; i < 0x4000; i++ )
{
// We have to get rid of the header on blocks of information
// HS has the first entry messed up
if( isHsFormat )
{
if(((( i & 0x1F ) == 0 ) && ( i > 0 )) || ( i == 1 ))
{
input.read( reinterpret_cast<char*>( &value32 ), 4 ); // read off the group header
}
}
else
{
if(( i & 0x1f ) == 0 )
{
input.read( reinterpret_cast<char*>( &value32 ), 4 ); // read off the group header
}
}
// now create the info_t we will use
auto info = CLand();
// read off the flags, 32 bit on pre-HS, 64 bit after
if( isHsFormat )
{
input.read( reinterpret_cast<char*>( &value64), 8 );
info.flags = std::bitset<64>( value64 );
}
else
{
input.read( reinterpret_cast<char*>( &value32 ), 4 );
info.flags = std::bitset<64>( value32 );
}
// only other thing for terrain is the texture
// and name
input.read( reinterpret_cast<char*>( &info.textureId ), 2 );
input.read( string_buffer.data(), 20 );
if( input.gcount() == 20 )
{
// We need to find the "0", if any
auto iter = std::find( string_buffer.begin(), string_buffer.end(), 0 );
info.name = std::string( string_buffer.begin(), iter );
terrainData.push_back( std::move( info ));
}
else
{
break;
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::ProcessArt()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Process art/statics data read from tiledata file
//o------------------------------------------------------------------------------------------------o
auto TileInfo::ProcessArt( std::istream &input ) -> void
{
artData.reserve( 0xFFFF );
std::uint32_t value32 = 0;
std::uint64_t value64 = 0;
std::array<char, 20> string_buffer;
string_buffer.fill( 0 );
if( !( input.eof() || input.bad() ))
{
auto loopCount = 0;
do
{
if(( loopCount & 0x1f ) == 0 )
{
input.read( reinterpret_cast<char*>( &value32 ), 4 ); // read off the group header
}
auto info = CTile();
if( isHsFormat )
{
input.read( reinterpret_cast<char*>( &value64 ), 8 );
info.flags = std::bitset<64>( value64 );
}
else
{
input.read( reinterpret_cast<char*>( &value32 ), 4 );
info.flags = std::bitset<64>( value32 );
}
input.read( reinterpret_cast<char*>( &info.weight ), 1 );
input.read( reinterpret_cast<char*>( &info.layer ), 1 );
//misc data
input.read( reinterpret_cast<char*>( &info.unknown1 ), 2 );
//Second unkown
input.read( reinterpret_cast<char*>( &info.unknown2), 1 );
// Quantity
input.read( reinterpret_cast<char*>( &info.quantity), 1 );
// Animation
input.read( reinterpret_cast<char*>( &info.animation), 2 );
//Third a byte
input.read( reinterpret_cast<char*>( &info.unknown3), 1 );
// Hue/Value
input.read( reinterpret_cast<char*>( &info.hue), 1 );
//stacking offset/value = 16 bits, but UOX3 doesn't know that, so two eight bit unkowns
input.read( reinterpret_cast<char*>( &info.unknown4 ), 1 );
input.read( reinterpret_cast<char*>( &info.unknown5 ), 1 );
// Height
input.read( reinterpret_cast<char*>( &info.height ), 1 );
// and name
input.read( string_buffer.data(), 20 );
if(( input.gcount() == 20 ) && input.good() )
{
// We need to find the "0", if any
auto iter = std::find( string_buffer.begin(), string_buffer.end(), 0 );
info.name = std::string( string_buffer.begin(), iter );
artData.push_back( std::move( info ));
}
loopCount++;
} while(( !input.eof() ) && input.good() );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - TileInfo::TotalMemory()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Calculate total memory used for loading art/terrain tiledata
//o------------------------------------------------------------------------------------------------o
auto TileInfo::TotalMemory() const -> size_t
{
auto flag = isHsFormat ? 8 : 4;
auto terrain = flag + 20 + 2;
auto art = flag + 20 + 23;
return static_cast<size_t>(( terrain * terrainData.size() ) + ( art * artData.size() ));
}
//o------------------------------------------------------------------------------------------------o
//| TerrainBlock::TerrainBlock()
//o------------------------------------------------------------------------------------------------o
//o------------------------------------------------------------------------------------------------o
TerrainBlock::TerrainBlock( std::uint8_t *data, const TileInfo *info )
{
if( data != nullptr )
{
LoadBlock( data, info );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - TerrainBlock::LoadBlock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load details for terrain tiles in a 8x8 block of the map
//o------------------------------------------------------------------------------------------------o
auto TerrainBlock::LoadBlock( std::uint8_t *data, const TileInfo *info ) -> void
{
if( data != nullptr )
{
data += 4;
for( auto y = 0; y < 8; ++y )
{
for( auto x = 0; x < 8; ++x )
{
auto tileId = std::uint16_t( 0 );
// we copy here, because of potential alignment issues
std::copy( data, data + 2, reinterpret_cast<std::uint8_t*>( &( tileId )));
_tiles[x][y].tileId = tileId;
if( info )
{
_tiles[x][y].terrainInfo = &info->TerrainInfo( tileId );
}
_tiles[x][y].altitude = *( reinterpret_cast<std::int8_t*>( data + 2 ));
data += 3; // advance data three bytes;
}
}
}
}
//=========================================================
auto TerrainBlock::TerrainTileAt( int x, int y ) -> Tile_st&
{
return _tiles.at( x ).at( y );
}
//=========================================================
auto TerrainBlock::TerrainTileAt( int x, int y ) const -> const Tile_st&
{
return _tiles.at( x ).at( y );
}
//================================================================================
// ArtBlock
//================================================================================
//=========================================================
ArtBlock::ArtBlock( int length, std::uint8_t *data, const TileInfo *info )
{
if( data != nullptr )
{
LoadArtBlock( length, data, info );
}
}
//=========================================================
auto ArtBlock::ArtTileAt( int x, int y ) -> std::vector<Tile_st>&
{
return _tiles.at( x ).at( y );
}
//=========================================================
auto ArtBlock::ArtTileAt( int x, int y ) const -> const std::vector<Tile_st>&
{
return _tiles.at( x ).at( y );
}
//=========================================================
auto ArtBlock::Clear() -> void
{
for( auto &row : _tiles )
{
for( auto &tile: row )
{
tile.clear();
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - ArtBlock::LoadArtBlock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load details for all art/statics tiles in a given location from statics files
//o------------------------------------------------------------------------------------------------o
auto ArtBlock::LoadArtBlock( int length, std::istream &input, const TileInfo *info ) -> void
{
this->Clear();
auto count = length / 7;
auto x = std::uint8_t( 0 );
auto y = std::uint8_t( 0 );
auto alt = std::int8_t( 0 );
for( auto i = 0; i < count; ++i )
{
auto artTile = Tile_st();
artTile.type = TileType_t::art;
input.read( reinterpret_cast<char*>( &artTile.tileId ), 2 );
input.read( reinterpret_cast<char*>( &x ), 1 );
input.read( reinterpret_cast<char*>( &y ), 1 );
input.read( reinterpret_cast<char*>( &alt ), 1 );
artTile.altitude = alt;
input.read( reinterpret_cast<char*>( &artTile.staticHue ), 2 );
if( info )
{
artTile.artInfo = &info->ArtInfo( artTile.tileId );
}
ArtTileAt( x, y ).push_back( std::move( artTile ));
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - ArtBlock::LoadArtBlock()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Retrieve details for all art/statics tiles in a given location from memory
//o------------------------------------------------------------------------------------------------o
auto ArtBlock::LoadArtBlock( int length, std::uint8_t *data, const TileInfo *info ) -> void
{
this->Clear();
auto count = length / 7;
auto x = std::uint8_t( 0 );
auto y = std::uint8_t( 0 );
for( auto i = 0; i < count; ++i )
{
auto artTile = Tile_st();
artTile.type = TileType_t::art;
std::copy( data, data + 2, reinterpret_cast<char*>( &artTile.tileId ));
x = * ( data + 2 );
y = * ( data + 3 );
artTile.altitude = static_cast<int>( *( reinterpret_cast<std::int8_t*>( data + 4 )));
std::copy( data + 5, data + 7, reinterpret_cast<char*>( &artTile.staticHue ));
if( info )
{
artTile.artInfo = &info->ArtInfo( artTile.tileId );
}
ArtTileAt( x, y ).push_back( std::move( artTile ));
data += 7;
}
}
//o------------------------------------------------------------------------------------------------o
//| UltimaMap
//o------------------------------------------------------------------------------------------------o
//o------------------------------------------------------------------------------------------------o
UltimaMap::UltimaMap() : tileInfo( nullptr ), _width( 0 ), _height( 0 ), _diffCount( 0 ), _diffTerrain( 0 )
{
}
//=========================================================
UltimaMap::UltimaMap( int mapNum, int width, int height, const TileInfo *info ) : UltimaMap()
{
_width = width;
_height= height;
tileInfo = info;
_mapNum = mapNum;
auto size = _mapSizes.at( mapNum );
if( _width == 0 )
{
_width = size.first;
}
if( _height == 0 )
{
_height = size.second;
}
_terrain.resize(( _width / 8 ) * ( _height / 8 ));
_art.resize( _terrain.size() );
isUop = true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::Width()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Get width of map
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::Width() const -> int
{
return _width;
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::Height()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Get height of map
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::Height() const -> int
{
return _height;
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::SetSize()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Set width and height of map
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::SetSize( int width, int height ) -> void
{
_width = width;
_height = height;
_terrain.resize(( _width / 8 ) * ( _height / 8 ));
_art.resize( _terrain.size() );
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::Size()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Get size of map as width/height pair
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::Size() const -> std::pair<int, int>
{
return std::make_pair( static_cast<int>( _width ), static_cast<int>( _height ));
}
//=========================================================
auto UltimaMap::DiffArt() const -> int
{
return _diffCount;
}
//=========================================================
auto UltimaMap::DiffTerrain() const -> int
{
return _diffTerrain;
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::LoadUOPTerrainFile()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load map data from uop-version of map files
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::LoadUOPTerrainFile( const std::string &fileName ) -> bool
{
auto hash = this->Format( "build/map%ilegacymul/%s", _mapNum, "%.8u.dat" );
//auto fileName = this->format( "map%iLegacyMUL.uop", _mapNum );
isUop = LoadUop( fileName, 0x300, hash );
return isUop;
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::LoadMulTerrainFile()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load map data from mul-version of map files
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::LoadMulTerrainFile(const std::string &fileName) -> bool {
auto rValue = false;
auto mul = std::ifstream( fileName, std::ios::binary );
if( mul.is_open() )
{
rValue = true;
isUop = false;
auto data = std::vector<std::uint8_t>( 196, 0 );
auto blockNum = 0;
while( mul.good() && !mul.eof() )
{
mul.read( reinterpret_cast<char*>( data.data() ), 196 );
if( mul.gcount() == 196 )
{
LoadTerrainBlock( blockNum, data.data() );
}
blockNum++;
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::LoadArt()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load data from statics/staidx files
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::LoadArt( const std::string &mulFile, const std::string &idxFile ) -> bool
{
auto idx = std::ifstream( idxFile, std::ios::binary );
auto rValue = idx.is_open();
if( rValue )
{
auto mul = std::ifstream( mulFile, std::ios::binary );
rValue = mul.is_open();
if( rValue )
{
auto offset = std::uint32_t( 0 );
auto length = std::int32_t( 0 );
auto extra = std::uint32_t( 0 );
size_t block = 0;
while( idx.good() && !idx.eof() )
{
idx.read( reinterpret_cast<char*>( &offset ), 4 );
idx.read( reinterpret_cast<char*>( &length ), 4 );
idx.read( reinterpret_cast<char*>( &extra ), 4 );
if(( length > 0 ) && ( idx.gcount() == 4 ))
{
mul.seekg( offset, std::ios::beg );
if( block < _art.size() )
{
_art[block].LoadArtBlock( length, mul, tileInfo );
}
}
++block;
}
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::ApplyDiff()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Apply patches from diff files to statics for map loaded by UOX3
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::ApplyDiff( const std::string &difflPath, const std::string &diffiPath, const std::string &diffPath ) -> int
{
_diffCount = 0;
auto diffBlock = std::ifstream( difflPath, std::ios::binary );
if( diffBlock.is_open() )
{
auto diffIdx = std::ifstream( diffiPath, std::ios::binary );
if( diffIdx.is_open() )
{
auto diff = std::ifstream( diffPath, std::ios::binary );
if( diff.is_open() )
{
auto blockNum = std::uint32_t( 0 );
auto offset = std::uint32_t( 0 );
auto length = std::int32_t( 0 );
auto extra = std::uint32_t( 0 );
while( diffBlock.good() && !diffBlock.eof() )
{
diffBlock.read( reinterpret_cast<char*>( &blockNum ), 4 );
diffIdx.read( reinterpret_cast<char*>( &offset ), 4 );
diffIdx.read( reinterpret_cast<char*>( &length ), 4 );
diffIdx.read( reinterpret_cast<char*>( &extra ), 4 );
if(( diffBlock.gcount() == 4 ) && ( diffIdx.gcount() == 4 ))
{
if( blockNum < _art.size() )
{
if( length > 0 )
{
diff.seekg( offset, std::ios::beg );
_art[blockNum].LoadArtBlock( length, diff, tileInfo );
}
else
{
_art[blockNum].Clear();
}
}
++_diffCount;
}
}
}
}
}
return _diffCount;
}
//o------------------------------------------------------------------------------------------------o
//| Function - UltimaMap::ApplyTerrainDiff()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Apply patches from diff files to map loaded by UOX3
//o------------------------------------------------------------------------------------------------o
auto UltimaMap::ApplyTerrainDiff( const std::string &difflPath, const std::string &diffPath ) -> int
{
auto diffblock = std::ifstream( difflPath, std::ios::binary );
_diffTerrain = 0;
if( diffblock.is_open() )
{
auto diff = std::ifstream( diffPath, std::ios::binary );
if( diff.is_open() )
{
auto blocknum = std::uint32_t( 0 );
auto data = std::vector<std::uint8_t>( 196, 0 );
while( diffblock.good() && !diffblock.eof() )
{
diffblock.read( reinterpret_cast<char*>( &blocknum ), 4 );
if( diffblock.gcount() == 4 )
{
diff.read( reinterpret_cast<char*>( data.data() ), 196 );
if( diff.gcount() == 196 )
{
_terrain[blocknum].LoadBlock( data.data(), tileInfo );
_diffTerrain++;
}
}
}
}
}
return _diffTerrain;
}
//=========================================================
auto UltimaMap::CalcBlock( int x, int y) const -> int
{
// How many blocks is x across?
return (( x / 8 ) * ( _height / 8 )) + ( y / 8 );
}
//=========================================================
auto UltimaMap::CalcXYOffset( int block ) const -> std::pair<int, int>
{
auto x = ( block / ( _height / 8 )) * 8;
auto y = ( block % ( _height / 8 )) * 8;
return std::make_pair( x, y );
}
//=========================================================
auto UltimaMap::BlockAndIndexFor( int x, int y ) const -> std::tuple<int, int, int>
{
auto block = CalcBlock( x, y );
auto offset = CalcXYOffset( block );
return std::make_tuple( block, x - offset.first, y - offset.second );
}
//=========================================================
auto UltimaMap::ProcessEntry( [[maybe_unused]] std::size_t entry, std::size_t index, std::vector<std::uint8_t> &data ) -> bool
{
auto count = data.size() / 196;
size_t block = ( static_cast<int>( index ) * 0xC4000 ) / 196;
for( auto i = 0; i < static_cast<int>( count ); ++i )
{
auto ptr = data.data() + ( i * 196 );
if( block < _terrain.size() )
{
LoadTerrainBlock( static_cast<int>( block ), ptr );
}
++block;
}
return true;
}
//=========================================================
auto UltimaMap::LoadTerrainBlock( int blocknum, std::uint8_t *data ) -> void
{
_terrain[blocknum].LoadBlock( data, tileInfo );
}
//=========================================================
auto UltimaMap::TerrainAt( int x, int y ) const -> const Tile_st&
{
auto [blocknum, xoffset, yoffset] = BlockAndIndexFor( x, y );
return _terrain[blocknum].TerrainTileAt( xoffset, yoffset );
}
//=========================================================
auto UltimaMap::TerrainAt( int x, int y ) -> Tile_st&
{
auto [blocknum, xoffset, yoffset] = BlockAndIndexFor( x, y );
return _terrain[blocknum].TerrainTileAt( xoffset, yoffset );
}
//=========================================================
auto UltimaMap::ArtAt( int x, int y ) const -> const std::vector<Tile_st>&
{
auto [blocknum, xoffset, yoffset] = BlockAndIndexFor( x, y );
return _art[blocknum].ArtTileAt( xoffset, yoffset );
}
//=========================================================
auto UltimaMap::ArtAt( int x, int y ) -> std::vector<Tile_st>&
{
auto [blocknum, xoffset,yoffset] = BlockAndIndexFor( x, y );
return _art[blocknum].ArtTileAt( xoffset, yoffset );
}