mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
Removed arbitrary and hidden restrictions on refreshing list of online players shown with 'wholist command
Fixed an issue where player ghosts that get teleported would not see themselves get updated to new location
Fixed a server crash from clients disconnecting while running server in debug mode
Improved how container updates are sent to players; now only sends to clients that have actually opened the container, and who are still within range
Added more information about UO data files being loaded during server startup
Enhanced the GM 'add menu to allow clicking directly on images of items/npcs to add them, and adjusted size of menus to accommodate this
Fixed an issue with fleeing NPCs that could cause them to attempt to flee across time and space, regardless of distance to target/attacker in combat
Added NPC AI that runs away from players if they get too close (even outside combat). Applied to hind, rabbits, squirrels, ferrets and various birds by default:
AI_ANIMAL_SCARED (aitype 12)
Fixed issues with NPCs fleeing forever, or getting stuck in flee/don't flee loop, by introducing max fleeing distance (50 tiles) and cooldown (30 sec) on fleeing
Updated onNameRequest JS Event to include third parameter with origin of name request, so script responses can be tailored appropriately:
onNameRequest( myObj, requestedBy, requestSource )
Potential values for requestSource:
0 - Speech/System Messages
1 - Guild Menus
2 - Stat Window (self)
3 - Stat Window (other)
4 - Tooltip
5 - Paperdoll Journal
6 - Paperdoll
7 - Single Click / All-Names
8 - System
9 - Secure Trade Window
Added Adaptive Performance System (APS) that dynamically adjust how often NPC AI/movement is checked based on overall shard performance. If performance drops below defined threshold, UOX3 gradually slows down checks for NPC AI/movement to prioritize player movement/speech/command responsiveness. If performance climbs back up above threshold, slowdowns are gradually removed. The following UOX.INI options have been added under [system] category to support this system:
APSPERFTHRESHOLD=50 // Performance threshold (measured in simulation cycles/sec) below which the APS kicks in
APSINTERVAL=100 // How often (in milliseconds) the APS checks performance and makes adjustments (if needed) to balance out shard performance
APSDELAYSTEP=50 // How much the delay timer is modified by (in milliseconds) each time APS makes adjustments
APSDELAYMAXCAP=2000 // Max amount of of delay APS can introduce for NPC AI/movement handling when attempting to restore shard performance
Added new JS Methods for Region objects:
.GetOrePrefs( oreType ) // Get ore preference data for ore type found in town region. Returned as an array containing the following data:
orePrefData -> [
oreName, // name of ore
color, // color of ore
minskill, // min skill to mine ore
ingotName, // name of ingot created from ore
makemenu, // makemenu entry for crafting something from ingot
oreChance, // default global chance of finding this ore type
scriptID // script attached to mined ore
],
orePrefChance // Chance of finding this ore type in given town region
.GetOreChance() // Get base chance of finding any ore in town region
Moved Mining skill plus gravedigging feature from hard code to scripts (js/skill/mining.js) to make it easier to maintain and/or customize, and removed hard coded variants
Gravedigging now relies on the ore resource system behind the scenes to restrict how often graves in a given area can be dug up
Fixed some incorrect references to .worldNumber Character property in misc scripts (should be .worldnumber)
Made some "hard-scripted" system messages in misc scripts use dictionary system instead
Added spawn region for banker NPCs in Serpent's Hold (dfndata/spawn/felucca/spawn_town_serpents_hold.dfn, dfndata/spawn/trammel/spawn_town_serpents_hold.dfn)
Fixed region definition of Ocllo to actually cover the entire town (dfndata/regions/regions.dfn)
Added numerous additional locations accessible with 'goplace # command, for key locations in Ilshenar, Malas, Tokuno Islands and Ter Mur (dfndata/location/location.dfn)
Revised travel-menu portion of GM menu (shortcut: 'travel) to include more travel options based on new locations (dfndata/items/travelmenu.dfn and travelmenu.bulk.dfn)
Fixed an issue where NPCs could attempt to attack targets in other worlds/instances
Added new JS Event that triggers for characters who are about to deal damage in combat. Complimentary to onDamage, which triggers for chars receiving damage:
onDamageDeal( dmgDealer, dmgReceiver, damageValue, damageType )
Added new JS Event that can trigger in global script upon creation of new player chars. Note that this will trigger in place of onCreateDFN event for player characters:
onCreatePlayer( pChar )
Added new JS Event that triggers for characters selecting a target with a spell. Complimentary to onSpellTarget, which triggers for targets selected with a spell:
onSpellTargetSelect( caster, target, spellNum )
Updated onCombatStart and onCombatEnd JS Events to also trigger for the other party in combat
Updated onPickup JS Event to include a third parameter - the potential container item was picked up from. Event now also triggers event in scripts attached to said containers:
onPickup( iPickedUp, pChar, iCont )
Added tracking of total playtime per individual character, and per account across all characters, and exposed these properties to JS engine:
.totalPlayTime // Account property, total playtime across all chars
.playTime // Character property, total playtime on given character only
Added new player-accessible command ('playtime) to spit out the playtime of current character/account as a whole (js/commands/playtime.js)
Implemented first version of Young Player System:
Replaced the UNUSED9 account flag with YOUNG, to be used by Young/New Player System
Added new JS Account property that gets/sets whether player account is considered Young:
.isYoung
Added new Char timers:
TIMER_YOUNGHEAL // Restricts how often Young players are healed by NPC healers
TIMER_YOUNGMESSAGE // Restricts how often Young players are warned about dangerous looking monsters in overworld
Updated GM commands 'get and 'set to get/set .isYoung property of player's account (js/commands/targeting/get.js and set.js)
Added new UOX.INI setting to enable/disable Young Player System (enabled by default):
YOUNGPLAYERSYSTEM=1
If Young Player System is enabled, all newly created player accounts are automatically marked with Young flag
Added new script to handle various restrictions and functions related to Young characters (js/player/young_player.js):
Young characters will have [Young] displayed over their head
Young characters will have their Young status checked and verified on every login + every stat/skill gain, to revoke the Young status if any of the following is true:
Account has a total playtime of more than 40 hours
Any character has more than 350 total (base) skill points
Any character has more than 70 skill points in a single skill
Any character has more than 150 total stat points
Any character has more than 80 stat points in a single stat
Young characters can renounce their Young status manually by saying the words "I renounce my young player status"
Young characters get two additional items upon creation:
a new player ticket (can be combined with any other players new player ticket for both players to get a reward)
a sextant (shows Young players directions to nearest moongate/bank regardless of where they are, as long as outdoors)
Young characters cannot target other player characters with hostile spells or skills
Young characters cannot be the target of other player characters' hostile spells or skills
Young characters can only cast beneficial spells upon themselves or other Young characters
Young characters can only be the target of beneficial spells from other Young players
Young players (and their pets/followers) cannot attack or harm any other players (or their pets/followers), nor themselves be attacked or harmed by any other players (or their pets/followers), whether from combat, spells, skills or items
Young players don't lose any of their items on death, and are teleported to the nearest healer upon death with all their items intact
Young players receive a warning upon entering dungeons that monsters there will be hostile
Updated global script (js/server/global.js) to:
Check/Verify Young status of players upon login
Attach young player script on login/creation for players on Young accounts
Give specific items to Young players upon creation
Added new script (js/item/corpse.js) that is assigned to all freshly created corpses. This script handles restrictions related to Young players interactions with corpses, and the interactions of other players with corpses of Young players
Updated JS Method .Carve() to return true/false depending on whether carving a corpse was successful
Updated Healer AI in code to heal nearby injured Young players
Updated Evil AI and Evil Caster AI in code to avoid selecting Young players as targets in combat outside of dungeons
Added a new section to UOX.INI - [young player starting locations] - that can be used to define starting locations for Young players. If only one such location is provided, all players start there. Otherwise, it matches the same starting location setup as the regular one - with one entry per town in Britannia.
Restricted transferring and/or friending of pets (code) and hirelings (hirelings.js) between Young and non-Young players
Restricted Young players from recalling or gating to Felucca facet
Young players can instantly logout from anywhere, at any time
2108 lines
74 KiB
C++
2108 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.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 )))
|
|
{
|
|
rValue = true;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( elev >= z && tile.altitude <= z && ( tile.CheckFlag( TF_BLOCKING ) && !tile.CheckFlag( TF_WET )))
|
|
{
|
|
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;
|
|
|
|
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
|
|
{
|
|
if( 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(( 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 ) -> std::int8_t
|
|
{
|
|
const auto &map = SeekMap( x, y, worldNumber );
|
|
// make sure nothing can move into black areas
|
|
if( 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 )
|
|
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::ifstream &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::ifstream &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( 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 );
|
|
}
|