mirror of
https://github.com/polserver/polserver
synced 2026-08-13 08:23:08 -04:00
* origin/master: (93 commits) Add support for dictionary creation (#277) Add do-while statement (#276) Add foreach loops (#275) New compiler: array initialization (#274) Disambiguator: figure out if label is a case statement dispatch selector, or should apply to a statement within the case dispatch group's block. (#273) New compiler: Add support for case statements (#272) Change "logical" to "bitwise". (#258) Fix divide-by-zero errors, and mask FormatError if it were thrown in a destructor. (#271) Add break and continue statements. (#270) New compiler: assignment to local and global variables. (#269) Add support for while loops (#268) Optimize if statements, and emit module functions in legacy order (#267) Optimizations: binary operators (#266) Add binary operators (#265) Add support for const declarations (#264) Handle default parameter values, pass-by-name, and return statements in functions. (#263) Add limited support for user functions (#262) New compiler: add return and exit statements (#261) Add support for if-then-else statements (#259) OG compiler: set module to Mod_Basic when a token is an identifier (#257) ...
1423 lines
42 KiB
C++
1423 lines
42 KiB
C++
/** @file
|
|
*
|
|
* @par History
|
|
* - 2005/01/23 Shinigami: for_all_mobiles, write_items & write_multis - Tokuno MapDimension doesn't
|
|
* fit blocks of 64x64 (WGRID_SIZE)
|
|
* - 2007/06/17 Shinigami: added config.world_data_path
|
|
* - 2009/09/03 MuadDib: Relocation of account related cpp/h
|
|
* Relocation of multi related cpp/h
|
|
* - 2009/09/14 MuadDib: All UOX3 Import Code commented out. You can script this.
|
|
* - 2009/12/02 Turley: added config.max_tile_id - Tomi
|
|
* - 2011/11/28 MuadDib: Removed last of uox referencing code.
|
|
*/
|
|
|
|
#include "uimport.h"
|
|
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <future>
|
|
#include <string>
|
|
#include <time.h>
|
|
|
|
#include "../clib/Program/ProgramConfig.h"
|
|
#include "../clib/cfgelem.h"
|
|
#include "../clib/cfgfile.h"
|
|
#include "../clib/clib.h"
|
|
#include "../clib/clib_endian.h"
|
|
#include "../clib/esignal.h"
|
|
#include "../clib/fileutil.h"
|
|
#include "../clib/logfacility.h"
|
|
#include "../clib/passert.h"
|
|
#include "../clib/rawtypes.h"
|
|
#include "../clib/threadhelp.h"
|
|
#include "../clib/timer.h"
|
|
#include "../plib/poltype.h"
|
|
#include "../plib/systemstate.h"
|
|
#include "accounts/accounts.h"
|
|
#include "containr.h"
|
|
#include "fnsearch.h"
|
|
#include "gameclck.h"
|
|
#include "globals/network.h"
|
|
#include "globals/object_storage.h"
|
|
#include "globals/settings.h"
|
|
#include "globals/state.h"
|
|
#include "globals/uvars.h"
|
|
#include "item/item.h"
|
|
#include "item/itemdesc.h"
|
|
#include "loaddata.h"
|
|
#include "mobile/charactr.h"
|
|
#include "mobile/npc.h"
|
|
#include "multi/house.h"
|
|
#include "multi/multi.h"
|
|
#include "objecthash.h"
|
|
#include "polvar.h"
|
|
#include "regions/resource.h"
|
|
#include "savedata.h"
|
|
#include "servdesc.h"
|
|
#include "startloc.h"
|
|
#include "storage.h"
|
|
#include "ufunc.h"
|
|
#include "uobject.h"
|
|
#include "uworld.h"
|
|
|
|
namespace Pol
|
|
{
|
|
namespace Module
|
|
{
|
|
void commit_datastore();
|
|
void read_datastore_dat();
|
|
void write_datastore( Clib::StreamWriter& sw );
|
|
} // namespace Module
|
|
namespace Core
|
|
{
|
|
void read_party_dat();
|
|
void write_party( Clib::StreamWriter& sw );
|
|
void read_guilds_dat();
|
|
void write_guilds( Clib::StreamWriter& sw );
|
|
|
|
std::shared_future<bool> SaveContext::finished;
|
|
|
|
/****************** POL Native Files *******************/
|
|
// Dave changed 3/8/3 to use objecthash
|
|
void read_character( Clib::ConfigElem& elem )
|
|
{
|
|
// if this object is modified in a subsequent incremental save,
|
|
// don't load it now.
|
|
pol_serial_t serial = 0;
|
|
elem.get_prop( "SERIAL", &serial );
|
|
if ( get_save_index( serial ) > objStorageManager.current_incremental_save )
|
|
return;
|
|
|
|
CharacterRef chr( new Mobile::Character( elem.remove_ushort( "OBJTYPE" ) ) );
|
|
|
|
try
|
|
{
|
|
// note chr->logged_in is true..
|
|
chr->readProperties( elem );
|
|
|
|
if ( !chr->realm() ) // not guaranteed by loading
|
|
chr->setposition( Core::Pos4d( chr->pos().xyz(), gamestate.main_realm ) );
|
|
|
|
// Allows the realm to recognize this char as offline
|
|
chr->realm()->add_mobile( *chr, Realms::WorldChangeReason::PlayerLoad );
|
|
|
|
chr->clear_dirty();
|
|
|
|
// readProperties gets the serial, so we can't add to the objecthash until now.
|
|
objStorageManager.objecthash.Insert( chr.get() );
|
|
}
|
|
catch ( std::exception& )
|
|
{
|
|
if ( chr.get() != nullptr )
|
|
chr->destroy();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Dave changed 3/8/3 to use objecthash
|
|
void read_npc( Clib::ConfigElem& elem )
|
|
{
|
|
// if this object is modified in a subsequent incremental save,
|
|
// don't load it now.
|
|
pol_serial_t serial = 0;
|
|
elem.get_prop( "SERIAL", &serial );
|
|
if ( get_save_index( serial ) > objStorageManager.current_incremental_save )
|
|
return;
|
|
|
|
NpcRef npc( new Mobile::NPC( elem.remove_ushort( "OBJTYPE" ), elem ) );
|
|
|
|
try
|
|
{
|
|
npc->readProperties( elem );
|
|
|
|
if ( !npc->realm() ) // not guaranteed by loading
|
|
npc->setposition( Core::Pos4d( npc->pos().xyz(), gamestate.main_realm ) );
|
|
SetCharacterWorldPosition( npc.get(), Realms::WorldChangeReason::NpcLoad );
|
|
npc->clear_dirty();
|
|
|
|
////HASH
|
|
objStorageManager.objecthash.Insert( npc.get() );
|
|
////
|
|
}
|
|
catch ( std::exception& )
|
|
{
|
|
if ( npc.get() != nullptr )
|
|
npc->destroy();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// hmm, an interesting idea, what if there was an
|
|
// Item::create( ConfigElem& elem ) member,
|
|
// which would do this?
|
|
// Item would need a constructor (ConfigElem&) also.
|
|
// Polymorphism would then take
|
|
// care of it, and move the logic into the derived
|
|
// classes.
|
|
Items::Item* read_item( Clib::ConfigElem& elem )
|
|
{
|
|
u32 serial;
|
|
u32 objtype;
|
|
if ( elem.remove_prop( "SERIAL", &serial ) == false )
|
|
{
|
|
ERROR_PRINT << "Item element has no SERIAL property, omitting.\n";
|
|
return nullptr;
|
|
}
|
|
|
|
if ( !IsItem( serial ) )
|
|
{
|
|
elem.warn_with_line(
|
|
"Item element does not have an item serial\n(should be larger than 0x40000000)" );
|
|
if ( Plib::systemstate.config.check_integrity )
|
|
{
|
|
throw std::runtime_error( "Data integrity error" );
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
if ( Plib::systemstate.config.check_integrity )
|
|
{
|
|
if ( system_find_item( serial ) )
|
|
{
|
|
ERROR_PRINT.Format( "Duplicate item read from datafiles (Serial=0x{:X})\n" ) << serial;
|
|
throw std::runtime_error( "Data integrity error" );
|
|
}
|
|
}
|
|
if ( elem.remove_prop( "OBJTYPE", &objtype ) == false )
|
|
{
|
|
ERROR_PRINT.Format( "Item (Serial 0x{:X}) has no OBJTYPE property, omitting." ) << serial;
|
|
return nullptr;
|
|
}
|
|
if ( gamestate.old_objtype_conversions.count( objtype ) )
|
|
objtype = gamestate.old_objtype_conversions[objtype];
|
|
|
|
Items::Item* item = Items::Item::create( objtype, serial );
|
|
if ( item == nullptr )
|
|
{
|
|
ERROR_PRINT.Format( "Unable to create item: objtype=0x{:X}, serial=0x{:X}" )
|
|
<< objtype << serial;
|
|
if ( !Plib::systemstate.config.ignore_load_errors )
|
|
throw std::runtime_error( "Item::create failed!" );
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
item->readProperties( elem );
|
|
|
|
item->clear_dirty();
|
|
|
|
return item;
|
|
}
|
|
|
|
#define USE_PARENT_CONTS 1
|
|
|
|
typedef std::stack<UContainer*> ContStack;
|
|
static ContStack parent_conts;
|
|
|
|
void read_global_item( Clib::ConfigElem& elem, int /*sysfind_flags*/ )
|
|
{
|
|
// if this object is modified in a subsequent incremental save,
|
|
// don't load it now.
|
|
pol_serial_t serial = 0;
|
|
elem.get_prop( "SERIAL", &serial );
|
|
if ( get_save_index( serial ) > objStorageManager.current_incremental_save )
|
|
return;
|
|
|
|
|
|
u32 container_serial = 0; // defaults to item in the world's top-level
|
|
(void)elem.remove_prop( "CONTAINER",
|
|
&container_serial ); // therefore we don't need to check the return value
|
|
|
|
Items::Item* item = read_item( elem );
|
|
// dave added 1/15/3, protect against further crash if item is null. Should throw instead?
|
|
if ( item == nullptr )
|
|
{
|
|
elem.warn_with_line( "Error reading item SERIAL or OBJTYPE." );
|
|
return;
|
|
}
|
|
|
|
ItemRef itemref( item ); // dave 1/28/3 prevent item from being destroyed before function ends
|
|
if ( container_serial == 0 )
|
|
{
|
|
if ( !item->realm() ) // not guaranteed by loading
|
|
item->setposition( Core::Pos4d( item->pos().xyz(), gamestate.main_realm ) );
|
|
add_item_to_world( item );
|
|
if ( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
|
|
parent_conts.push( static_cast<UContainer*>( item ) );
|
|
}
|
|
else
|
|
{
|
|
// read position again without realm to not clip the coords
|
|
item->setposition( Core::Pos4d( elem.read_ushort( "X" ), elem.read_ushort( "Y" ),
|
|
static_cast<s8>( elem.read_int( "Z" ) ), nullptr ) );
|
|
|
|
if ( IsCharacter( container_serial ) ) // it's equipped on a character
|
|
{
|
|
Mobile::Character* chr = system_find_mobile( container_serial );
|
|
if ( chr != nullptr )
|
|
{
|
|
equip_loaded_item( chr, item );
|
|
}
|
|
else
|
|
{
|
|
defer_item_insertion( item, container_serial );
|
|
}
|
|
return;
|
|
}
|
|
Items::Item* cont_item = nullptr;
|
|
// bool new_parent_cont = false;
|
|
|
|
while ( !parent_conts.empty() )
|
|
{
|
|
UContainer* cont = parent_conts.top();
|
|
if ( cont->serial == container_serial )
|
|
{
|
|
cont_item = cont;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
parent_conts.pop();
|
|
}
|
|
}
|
|
|
|
if ( cont_item == nullptr )
|
|
{
|
|
cont_item = system_find_item( container_serial );
|
|
// new_parent_cont = true;
|
|
}
|
|
|
|
if ( cont_item )
|
|
{
|
|
add_loaded_item( cont_item, item );
|
|
}
|
|
else
|
|
{
|
|
defer_item_insertion( item, container_serial );
|
|
}
|
|
}
|
|
}
|
|
|
|
void read_system_vars( Clib::ConfigElem& elem )
|
|
{
|
|
settingsManager.polvar.DataWrittenBy = elem.remove_ushort( "CoreVersion" );
|
|
stateManager.stored_last_item_serial =
|
|
elem.remove_ulong( "LastItemSerialNumber", UINT_MAX ); // dave 3/9/3
|
|
stateManager.stored_last_char_serial =
|
|
elem.remove_ulong( "LastCharSerialNumber", UINT_MAX ); // dave 3/9/3
|
|
}
|
|
|
|
void read_shadow_realms( Clib::ConfigElem& elem )
|
|
{
|
|
std::string name = elem.remove_string( "Name" );
|
|
Realms::Realm* baserealm = find_realm( elem.remove_string( "BaseRealm" ) );
|
|
if ( !baserealm )
|
|
elem.warn_with_line( "BaseRealm not found." );
|
|
else if ( defined_realm( name ) )
|
|
elem.warn_with_line( "Realmname already defined" );
|
|
else
|
|
{
|
|
add_realm( name, baserealm );
|
|
INFO_PRINT << "\nShadowrealm " << name << "\n";
|
|
}
|
|
}
|
|
|
|
void read_multi( Clib::ConfigElem& elem )
|
|
{
|
|
// if this object is modified in a subsequent incremental save,
|
|
// don't load it now.
|
|
pol_serial_t serial = 0;
|
|
elem.get_prop( "SERIAL", &serial );
|
|
if ( get_save_index( serial ) > objStorageManager.current_incremental_save )
|
|
return;
|
|
|
|
u32 objtype;
|
|
if ( elem.remove_prop( "SERIAL", &serial ) == false )
|
|
{
|
|
ERROR_PRINT << "A Multi has no SERIAL property.\n";
|
|
throw std::runtime_error( "Config File error." );
|
|
}
|
|
if ( system_find_multi( serial ) || system_find_item( serial ) )
|
|
{
|
|
ERROR_PRINT.Format( "Duplicate item read from datafiles (Serial=0x{:X})\n" ) << serial;
|
|
throw std::runtime_error( "Data integrity error" );
|
|
}
|
|
if ( elem.remove_prop( "OBJTYPE", &objtype ) == false )
|
|
{
|
|
ERROR_PRINT.Format( "Multi (Serial 0x{:X}) has no OBJTYPE property, omitting." ) << serial;
|
|
return;
|
|
}
|
|
if ( gamestate.old_objtype_conversions.count( objtype ) )
|
|
objtype = gamestate.old_objtype_conversions[objtype];
|
|
|
|
Multi::UMulti* multi = Multi::UMulti::create( Items::find_itemdesc( objtype ), serial );
|
|
if ( multi == nullptr )
|
|
{
|
|
ERROR_PRINT.Format( "Unable to create multi: objtype=0x{:X}, serial=0x{:X}\n" )
|
|
<< objtype << serial;
|
|
throw std::runtime_error( "Multi::create failed!" );
|
|
}
|
|
multi->readProperties( elem );
|
|
|
|
if ( !multi->realm() ) // not guaranteed by loading
|
|
multi->setposition( Core::Pos4d( multi->pos().xyz(), gamestate.main_realm ) );
|
|
add_multi_to_world( multi );
|
|
}
|
|
|
|
std::string elapsed( clock_t start, clock_t end )
|
|
{
|
|
size_t ms = static_cast<size_t>( ( end - start ) * 1000.0 / CLOCKS_PER_SEC );
|
|
return Clib::tostring( ms ) + " ms";
|
|
}
|
|
|
|
void slurp( const char* filename, const char* tags, int sysfind_flags )
|
|
{
|
|
static int num_until_dot = 1000;
|
|
|
|
if ( Clib::FileExists( filename ) )
|
|
{
|
|
INFO_PRINT << " " << filename << ":";
|
|
Clib::ConfigFile cf( filename, tags );
|
|
Clib::ConfigElem elem;
|
|
|
|
Tools::Timer<> timer;
|
|
|
|
unsigned int nobjects = 0;
|
|
while ( cf.read( elem ) )
|
|
{
|
|
if ( --num_until_dot == 0 )
|
|
{
|
|
INFO_PRINT << ".";
|
|
num_until_dot = 1000;
|
|
}
|
|
try
|
|
{
|
|
if ( stricmp( elem.type(), "CHARACTER" ) == 0 )
|
|
read_character( elem );
|
|
else if ( stricmp( elem.type(), "NPC" ) == 0 )
|
|
read_npc( elem );
|
|
else if ( stricmp( elem.type(), "ITEM" ) == 0 )
|
|
read_global_item( elem, sysfind_flags );
|
|
else if ( stricmp( elem.type(), "GLOBALPROPERTIES" ) == 0 )
|
|
gamestate.global_properties->readProperties( elem );
|
|
else if ( elem.type_is( "SYSTEM" ) )
|
|
read_system_vars( elem );
|
|
else if ( elem.type_is( "MULTI" ) )
|
|
read_multi( elem );
|
|
else if ( elem.type_is( "STORAGEAREA" ) )
|
|
{
|
|
StorageArea* storage_area = gamestate.storage.create_area( elem );
|
|
// this will be followed by an item
|
|
if ( !cf.read( elem ) )
|
|
throw std::runtime_error( "Expected an item to exist after the storagearea." );
|
|
|
|
storage_area->load_item( elem );
|
|
}
|
|
else if ( elem.type_is( "REALM" ) )
|
|
read_shadow_realms( elem );
|
|
}
|
|
catch ( std::exception& )
|
|
{
|
|
if ( !Plib::systemstate.config.ignore_load_errors )
|
|
throw;
|
|
}
|
|
++nobjects;
|
|
}
|
|
|
|
timer.stop();
|
|
|
|
INFO_PRINT << " " << nobjects << " elements in " << timer.ellapsed() << " ms.\n";
|
|
}
|
|
}
|
|
|
|
void read_pol_dat()
|
|
{
|
|
std::string polfile = Plib::systemstate.config.world_data_path + "pol.txt";
|
|
|
|
slurp( polfile.c_str(), "GLOBALPROPERTIES SYSTEM REALM" );
|
|
|
|
if ( settingsManager.polvar.DataWrittenBy == 0 )
|
|
{
|
|
ERROR_PRINT
|
|
<< "CoreVersion not found in " << polfile << "\n\n"
|
|
<< polfile << " must contain a section similar to: \n"
|
|
<< "System\n"
|
|
<< "{\n"
|
|
<< "\tCoreVersion 99\n"
|
|
<< "}\n\n"
|
|
<< "Ensure that the CoreVersion matches the version that created your data files!\n";
|
|
throw std::runtime_error( "Data file error" );
|
|
}
|
|
}
|
|
|
|
void read_objects_dat()
|
|
{
|
|
slurp( ( Plib::systemstate.config.world_data_path + "objects.txt" ).c_str(),
|
|
"CHARACTER NPC ITEM GLOBALPROPERTIES" );
|
|
}
|
|
|
|
void read_pcs_dat()
|
|
{
|
|
slurp( ( Plib::systemstate.config.world_data_path + "pcs.txt" ).c_str(), "CHARACTER ITEM",
|
|
SYSFIND_SKIP_WORLD );
|
|
}
|
|
|
|
void read_pcequip_dat()
|
|
{
|
|
slurp( ( Plib::systemstate.config.world_data_path + "pcequip.txt" ).c_str(), "ITEM",
|
|
SYSFIND_SKIP_WORLD );
|
|
}
|
|
|
|
void read_npcs_dat()
|
|
{
|
|
slurp( ( Plib::systemstate.config.world_data_path + "npcs.txt" ).c_str(), "NPC ITEM",
|
|
SYSFIND_SKIP_WORLD );
|
|
}
|
|
|
|
void read_npcequip_dat()
|
|
{
|
|
slurp( ( Plib::systemstate.config.world_data_path + "npcequip.txt" ).c_str(), "ITEM",
|
|
SYSFIND_SKIP_WORLD );
|
|
}
|
|
|
|
void read_items_dat()
|
|
{
|
|
slurp( ( Plib::systemstate.config.world_data_path + "items.txt" ).c_str(), "ITEM" );
|
|
}
|
|
|
|
void read_multis_dat()
|
|
{
|
|
slurp( ( Plib::systemstate.config.world_data_path + "multis.txt" ).c_str(), "MULTI" );
|
|
}
|
|
|
|
void read_storage_dat()
|
|
{
|
|
std::string storagefile = Plib::systemstate.config.world_data_path + "storage.txt";
|
|
|
|
if ( Clib::FileExists( storagefile ) )
|
|
{
|
|
INFO_PRINT << " " << storagefile << ":";
|
|
Clib::ConfigFile cf2( storagefile );
|
|
gamestate.storage.read( cf2 );
|
|
}
|
|
}
|
|
|
|
Items::Item* find_existing_item( u32 objtype, Pos4d pos )
|
|
{
|
|
Pos2d p = zone_convert( pos );
|
|
for ( auto& item : pos.realm()->getzone( p ).items )
|
|
{
|
|
// FIXME won't find doors which have been perturbed
|
|
if ( item->objtype_ == objtype && item->pos() == pos )
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int import_count;
|
|
int dupe_count;
|
|
|
|
void import( Clib::ConfigElem& elem )
|
|
{
|
|
u32 objtype;
|
|
objtype = elem.remove_unsigned( "OBJTYPE" );
|
|
if ( objtype > Plib::systemstate.config.max_tile_id )
|
|
{
|
|
ERROR_PRINT.Format( "Importing file: 0x{:X} is out of range.\n" ) << objtype;
|
|
throw std::runtime_error( "Error while importing file." );
|
|
}
|
|
|
|
Items::Item* item = Items::Item::create( objtype, 0x40000000 ); // dummy serial
|
|
|
|
if ( item == nullptr )
|
|
{
|
|
ERROR_PRINT.Format( "Unable to import item: objtype=0x{:X}\n" ) << objtype;
|
|
throw std::runtime_error( "Item::create failed!" );
|
|
}
|
|
|
|
item->readProperties( elem );
|
|
|
|
if ( find_existing_item( item->objtype_, item->pos() ) )
|
|
{
|
|
item->destroy();
|
|
++dupe_count;
|
|
}
|
|
else
|
|
{
|
|
item->serial = GetNewItemSerialNumber();
|
|
|
|
item->serial_ext = ctBEu32( item->serial );
|
|
|
|
if ( !item->realm() ) // not guaranteed by loading
|
|
item->setposition( Core::Pos4d( item->pos().xyz(), gamestate.main_realm ) );
|
|
add_item_to_world( item );
|
|
register_with_supporting_multi( item );
|
|
++import_count;
|
|
}
|
|
}
|
|
|
|
void import_new_data()
|
|
{
|
|
std::string importfile = Plib::systemstate.config.world_data_path + "import.txt";
|
|
|
|
if ( Clib::FileExists( importfile ) )
|
|
{
|
|
Clib::ConfigFile cf( importfile, "ITEM" );
|
|
Clib::ConfigElem elem;
|
|
while ( cf.read( elem ) )
|
|
{
|
|
import( elem );
|
|
}
|
|
unlink( importfile.c_str() );
|
|
INFO_PRINT << "Import Results: " << import_count << " imported, " << dupe_count
|
|
<< " duplicates.\n";
|
|
}
|
|
}
|
|
|
|
void rndat( const std::string& basename )
|
|
{
|
|
std::string datname = Plib::systemstate.config.world_data_path + basename + ".dat";
|
|
std::string txtname = Plib::systemstate.config.world_data_path + basename + ".txt";
|
|
|
|
if ( Clib::FileExists( datname.c_str() ) )
|
|
{
|
|
rename( datname.c_str(), txtname.c_str() );
|
|
}
|
|
}
|
|
|
|
void rename_dat_files()
|
|
{
|
|
rndat( "pol" );
|
|
rndat( "objects" );
|
|
rndat( "pcs" );
|
|
rndat( "pcequip" );
|
|
rndat( "npcs" );
|
|
rndat( "npcequip" );
|
|
rndat( "items" );
|
|
rndat( "multis" );
|
|
rndat( "storage" );
|
|
rndat( "resource" );
|
|
rndat( "guilds" );
|
|
rndat( "parties" );
|
|
}
|
|
|
|
int read_data()
|
|
{
|
|
std::string objectsndtfile = Plib::systemstate.config.world_data_path + "objects.ndt";
|
|
std::string storagendtfile = Plib::systemstate.config.world_data_path + "storage.ndt";
|
|
|
|
stateManager.gflag_in_system_load = true;
|
|
if ( Clib::FileExists( objectsndtfile ) )
|
|
{
|
|
// Display reads "Reading data files..."
|
|
ERROR_PRINT << "Error!\n"
|
|
<< "'" << objectsndtfile << " exists. This probably means the system\n"
|
|
<< "exited while writing its state. To avoid loss of data,\n"
|
|
<< "forcing human intervention.\n";
|
|
throw std::runtime_error( "Human intervention required." );
|
|
}
|
|
if ( Clib::FileExists( storagendtfile ) )
|
|
{
|
|
ERROR_PRINT << "Error!\n"
|
|
<< "'" << storagendtfile << " exists. This probably means the system\n"
|
|
<< "exited while writing its state. To avoid loss of data,\n"
|
|
<< "forcing human intervention.\n";
|
|
throw std::runtime_error( "Human intervention required." );
|
|
}
|
|
|
|
rename_dat_files();
|
|
|
|
load_incremental_indexes();
|
|
|
|
read_pol_dat();
|
|
|
|
// POL clock should be paused at this point.
|
|
start_gameclock();
|
|
|
|
read_objects_dat();
|
|
read_pcs_dat();
|
|
read_pcequip_dat();
|
|
read_npcs_dat();
|
|
read_npcequip_dat();
|
|
read_items_dat();
|
|
read_multis_dat();
|
|
read_storage_dat();
|
|
read_resources_dat();
|
|
read_guilds_dat();
|
|
Module::read_datastore_dat();
|
|
read_party_dat();
|
|
|
|
read_incremental_saves();
|
|
insert_deferred_items();
|
|
|
|
register_deleted_serials();
|
|
clear_save_index();
|
|
|
|
import_new_data();
|
|
// import_wsc();
|
|
|
|
// dave 3/9/3
|
|
if ( stateManager.stored_last_item_serial < GetCurrentItemSerialNumber() )
|
|
SetCurrentItemSerialNumber( stateManager.stored_last_item_serial );
|
|
if ( stateManager.stored_last_char_serial < GetCurrentCharSerialNumber() )
|
|
SetCurrentCharSerialNumber( stateManager.stored_last_char_serial );
|
|
|
|
while ( !parent_conts.empty() )
|
|
parent_conts.pop();
|
|
|
|
for ( ObjectHash::hs::const_iterator citr = objStorageManager.objecthash.begin(),
|
|
citrend = objStorageManager.objecthash.end();
|
|
citr != citrend; ++citr )
|
|
{
|
|
UObject* obj = ( *citr ).second.get();
|
|
if ( obj->ismobile() )
|
|
{
|
|
Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
|
|
|
|
if ( chr->acct != nullptr )
|
|
chr->logged_in( false );
|
|
}
|
|
}
|
|
|
|
stateManager.gflag_in_system_load = false;
|
|
return 0;
|
|
}
|
|
|
|
|
|
SaveContext::SaveContext()
|
|
: _pol(),
|
|
_objects(),
|
|
_pcs(),
|
|
_pcequip(),
|
|
_npcs(),
|
|
_npcequip(),
|
|
_items(),
|
|
_multis(),
|
|
_storage(),
|
|
_resource(),
|
|
_guilds(),
|
|
_datastore(),
|
|
_party(),
|
|
pol( &_pol ),
|
|
objects( &_objects ),
|
|
pcs( &_pcs ),
|
|
pcequip( &_pcequip ),
|
|
npcs( &_npcs ),
|
|
npcequip( &_npcequip ),
|
|
items( &_items ),
|
|
multis( &_multis ),
|
|
storage( &_storage ),
|
|
resource( &_resource ),
|
|
guilds( &_guilds ),
|
|
datastore( &_datastore ),
|
|
party( &_party )
|
|
{
|
|
pol.init( Plib::systemstate.config.world_data_path + "pol.ndt" );
|
|
objects.init( Plib::systemstate.config.world_data_path + "objects.ndt" );
|
|
pcs.init( Plib::systemstate.config.world_data_path + "pcs.ndt" );
|
|
pcequip.init( Plib::systemstate.config.world_data_path + "pcequip.ndt" );
|
|
npcs.init( Plib::systemstate.config.world_data_path + "npcs.ndt" );
|
|
npcequip.init( Plib::systemstate.config.world_data_path + "npcequip.ndt" );
|
|
items.init( Plib::systemstate.config.world_data_path + "items.ndt" );
|
|
multis.init( Plib::systemstate.config.world_data_path + "multis.ndt" );
|
|
storage.init( Plib::systemstate.config.world_data_path + "storage.ndt" );
|
|
resource.init( Plib::systemstate.config.world_data_path + "resource.ndt" );
|
|
guilds.init( Plib::systemstate.config.world_data_path + "guilds.ndt" );
|
|
datastore.init( Plib::systemstate.config.world_data_path + "datastore.ndt" );
|
|
party.init( Plib::systemstate.config.world_data_path + "parties.ndt" );
|
|
|
|
pcs() << "#" << pf_endl << "# PCS.TXT: Player-Character Data" << pf_endl << "#" << pf_endl
|
|
<< "# In addition to PC data, this also contains hair, beards, death shrouds," << pf_endl
|
|
<< "# and backpacks, but not the contents of each backpack." << pf_endl << "#" << pf_endl
|
|
<< pf_endl;
|
|
|
|
pcequip() << "#" << pf_endl << "# PCEQUIP.TXT: Player-Character Equipment Data" << pf_endl << "#"
|
|
<< pf_endl
|
|
<< "# This file can be deleted to wipe all items held/equipped by characters"
|
|
<< pf_endl
|
|
<< "# Note that hair, beards, empty backpacks, and death shrouds are in PCS.TXT."
|
|
<< pf_endl << "#" << pf_endl << pf_endl;
|
|
|
|
npcs() << "#" << pf_endl << "# NPCS.TXT: Nonplayer-Character Data" << pf_endl << "#" << pf_endl
|
|
<< "# If you delete this file to perform an NPC wipe," << pf_endl
|
|
<< "# be sure to also delete NPCEQUIP.TXT" << pf_endl << "#" << pf_endl << pf_endl;
|
|
|
|
npcequip() << "#" << pf_endl << "# NPCEQUIP.TXT: Nonplayer-Character Equipment Data" << pf_endl
|
|
<< "#" << pf_endl << "# Delete this file along with NPCS.TXT to perform an NPC wipe"
|
|
<< pf_endl << "#" << pf_endl << pf_endl;
|
|
|
|
items() << "#" << pf_endl << "# ITEMS.TXT: Item data" << pf_endl << "#" << pf_endl
|
|
<< "# This file also contains ship and house components (doors, planks etc)" << pf_endl
|
|
<< "#" << pf_endl << pf_endl;
|
|
|
|
multis() << "#" << pf_endl << "# MULTIS.TXT: Ship and House data" << pf_endl << "#" << pf_endl
|
|
<< "# Deleting this file will not properly wipe houses and ships," << pf_endl
|
|
<< "# because doors, planks, and tillermen will be left in the world." << pf_endl << "#"
|
|
<< pf_endl << pf_endl;
|
|
|
|
storage() << "#" << pf_endl
|
|
<< "# STORAGE.TXT: Contains bank boxes, vendor inventories, and other data." << pf_endl
|
|
<< "#" << pf_endl
|
|
<< "# This file can safely be deleted to wipe bank boxes and vendor inventories."
|
|
<< pf_endl << "# Note that scripts may use this for other types of storage as well"
|
|
<< pf_endl << "#" << pf_endl << pf_endl;
|
|
|
|
resource() << "#" << pf_endl << "# RESOURCE.TXT: Resource System Data" << pf_endl << "#"
|
|
<< pf_endl << pf_endl;
|
|
|
|
guilds() << "#" << pf_endl << "# GUILDS.TXT: Guild Data" << pf_endl << "#" << pf_endl << pf_endl;
|
|
|
|
datastore() << "#" << pf_endl << "# DATASTORE.TXT: DataStore Data" << pf_endl << "#" << pf_endl
|
|
<< pf_endl;
|
|
party() << "#" << pf_endl << "# PARTIES.TXT: Party Data" << pf_endl << "#" << pf_endl << pf_endl;
|
|
}
|
|
|
|
SaveContext::~SaveContext()
|
|
{
|
|
pol.flush_file();
|
|
objects.flush_file();
|
|
pcs.flush_file();
|
|
pcequip.flush_file();
|
|
npcs.flush_file();
|
|
npcequip.flush_file();
|
|
items.flush_file();
|
|
multis.flush_file();
|
|
storage.flush_file();
|
|
resource.flush_file();
|
|
guilds.flush_file();
|
|
datastore.flush_file();
|
|
party.flush_file();
|
|
}
|
|
|
|
/// blocks till possible last commit finishes
|
|
void SaveContext::ready()
|
|
{
|
|
if ( SaveContext::finished.valid() )
|
|
{
|
|
// Tools::Timer<Tools::DebugT> t("future");
|
|
SaveContext::finished.wait();
|
|
}
|
|
}
|
|
|
|
|
|
void write_global_properties( Clib::StreamWriter& sw )
|
|
{
|
|
sw() << "GlobalProperties" << pf_endl << "{" << pf_endl;
|
|
gamestate.global_properties->printProperties( sw );
|
|
sw() << "}" << pf_endl << pf_endl;
|
|
// sw.flush();
|
|
}
|
|
|
|
void write_system_data( Clib::StreamWriter& sw )
|
|
{
|
|
sw() << "System" << pf_endl << "{" << pf_endl << "\tCoreVersion\t" << POL_VERSION << pf_endl
|
|
<< "\tCoreVersionString\t" << POL_VERSION_ID << pf_endl << "\tCompileDateTime\t"
|
|
<< Clib::ProgramConfig::build_datetime() << pf_endl << "\tLastItemSerialNumber\t"
|
|
<< GetCurrentItemSerialNumber() << pf_endl // dave 3/9/3
|
|
<< "\tLastCharSerialNumber\t" << GetCurrentCharSerialNumber() << pf_endl // dave 3/9/3
|
|
<< "}" << pf_endl << pf_endl;
|
|
// sw.flush();
|
|
}
|
|
|
|
void write_shadow_realms( Clib::StreamWriter& sw )
|
|
{
|
|
for ( const auto& realm : gamestate.Realms )
|
|
{
|
|
if ( realm->is_shadowrealm )
|
|
{
|
|
sw() << "Realm" << pf_endl << "{" << pf_endl << "\tName\t" << realm->shadowname << pf_endl
|
|
<< "\tBaseRealm\t" << realm->baserealm->name() << pf_endl << "}" << pf_endl << pf_endl;
|
|
}
|
|
}
|
|
// sw.flush();
|
|
}
|
|
|
|
// Austin (Oct. 17, 2006)
|
|
// Added to handle gotten item saving.
|
|
inline void WriteGottenItem( Mobile::Character* chr, Items::Item* item, Clib::StreamWriter& sw )
|
|
{
|
|
if ( item == nullptr || item->orphan() )
|
|
return;
|
|
// For now, it just saves the item in items.txt
|
|
|
|
Pos4d oldpos = item->pos();
|
|
item->setposition( chr->pos() );
|
|
item->printOn( sw );
|
|
item->setposition( oldpos );
|
|
}
|
|
|
|
void write_characters( Core::SaveContext& sc )
|
|
{
|
|
for ( const auto& objitr : objStorageManager.objecthash )
|
|
{
|
|
UObject* obj = objitr.second.get();
|
|
if ( obj->ismobile() && !obj->orphan() )
|
|
{
|
|
Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
|
|
if ( !chr->isa( UOBJ_CLASS::CLASS_NPC ) )
|
|
{
|
|
chr->printOn( sc.pcs );
|
|
chr->clear_dirty();
|
|
chr->printWornItems( sc.pcs, sc.pcequip );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void write_npcs( Core::SaveContext& sc )
|
|
{
|
|
for ( const auto& objitr : objStorageManager.objecthash )
|
|
{
|
|
UObject* obj = objitr.second.get();
|
|
if ( obj->ismobile() && !obj->orphan() )
|
|
{
|
|
Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
|
|
if ( chr->isa( UOBJ_CLASS::CLASS_NPC ) )
|
|
{
|
|
if ( chr->saveonexit() )
|
|
{
|
|
chr->printOn( sc.npcs );
|
|
chr->clear_dirty();
|
|
chr->printWornItems( sc.npcs, sc.npcequip );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void write_items( Clib::StreamWriter& sw_items )
|
|
{
|
|
for ( const auto& realm : gamestate.Realms )
|
|
{
|
|
Area2d area( Pos2d( 0, 0 ), Pos2d( realm->grid_width(), realm->grid_height() ) - Vec2d( 1, 1 ),
|
|
realm );
|
|
|
|
for ( const auto& p : area )
|
|
{
|
|
for ( const auto& item : realm->getzone( p ).items )
|
|
{
|
|
if ( item->itemdesc().save_on_exit && item->saveonexit() )
|
|
{
|
|
sw_items << *item;
|
|
item->clear_dirty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( const auto& objitr : objStorageManager.objecthash )
|
|
{
|
|
UObject* obj = objitr.second.get();
|
|
if ( obj->ismobile() && !obj->orphan() )
|
|
{
|
|
Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
|
|
if ( !chr->isa( UOBJ_CLASS::CLASS_NPC ) )
|
|
{
|
|
// Figure out where to save the 'gotten item' - Austin (Oct. 17, 2006)
|
|
if ( chr->has_gotten_item() )
|
|
WriteGottenItem( chr, chr->gotten_item(), sw_items );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void write_multis( Clib::StreamWriter& ofs )
|
|
{
|
|
for ( const auto& realm : gamestate.Realms )
|
|
{
|
|
Area2d area( Pos2d( 0, 0 ), Pos2d( realm->grid_width(), realm->grid_height() ) - Vec2d( 1, 1 ),
|
|
realm );
|
|
|
|
for ( const auto& p : area )
|
|
{
|
|
for ( auto& multi : realm->getzone( p ).multis )
|
|
{
|
|
if ( Clib::exit_signalled ) // drop waiting commit on shutdown
|
|
{
|
|
Multi::UHouse* house = multi->as_house();
|
|
if ( house != nullptr )
|
|
{
|
|
if ( house->IsCustom() )
|
|
{
|
|
if ( house->IsWaitingForAccept() )
|
|
house->AcceptHouseCommit( nullptr, false );
|
|
}
|
|
}
|
|
}
|
|
ofs << *multi;
|
|
multi->clear_dirty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool commit( const std::string& basename )
|
|
{
|
|
std::string bakfile = Plib::systemstate.config.world_data_path + basename + ".bak";
|
|
std::string datfile = Plib::systemstate.config.world_data_path + basename + ".txt";
|
|
std::string ndtfile = Plib::systemstate.config.world_data_path + basename + ".ndt";
|
|
const char* bakfile_c = bakfile.c_str();
|
|
const char* datfile_c = datfile.c_str();
|
|
const char* ndtfile_c = ndtfile.c_str();
|
|
|
|
bool any = false;
|
|
|
|
if ( Clib::FileExists( bakfile_c ) )
|
|
{
|
|
any = true;
|
|
if ( unlink( bakfile_c ) )
|
|
{
|
|
int err = errno;
|
|
POLLOG_ERROR.Format( "Unable to remove {}: {} ({})\n" )
|
|
<< bakfile_c << strerror( err ) << err;
|
|
}
|
|
}
|
|
|
|
if ( Clib::FileExists( datfile_c ) )
|
|
{
|
|
any = true;
|
|
if ( rename( datfile_c, bakfile_c ) )
|
|
{
|
|
int err = errno;
|
|
POLLOG_ERROR.Format( "Unable to rename {} to {}: {} ({})\n" )
|
|
<< datfile_c << bakfile_c << strerror( err ) << err;
|
|
}
|
|
}
|
|
|
|
if ( Clib::FileExists( ndtfile_c ) )
|
|
{
|
|
any = true;
|
|
if ( rename( ndtfile_c, datfile_c ) )
|
|
{
|
|
int err = errno;
|
|
POLLOG_ERROR.Format( "Unable to rename {} to {}: {} ({})\n" )
|
|
<< ndtfile_c << datfile_c << strerror( err ) << err;
|
|
}
|
|
}
|
|
|
|
return any;
|
|
}
|
|
|
|
bool should_write_data()
|
|
{
|
|
if ( Plib::systemstate.config.inhibit_saves )
|
|
return false;
|
|
if ( Clib::passert_shutdown_due_to_assertion && Clib::passert_nosave )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
int write_data( unsigned int& dirty_writes, unsigned int& clean_writes, long long& elapsed_ms )
|
|
{
|
|
SaveContext::ready(); // allow only one active
|
|
if ( !should_write_data() )
|
|
{
|
|
dirty_writes = clean_writes = 0;
|
|
elapsed_ms = 0;
|
|
return -1;
|
|
}
|
|
|
|
UObject::dirty_writes = 0;
|
|
UObject::clean_writes = 0;
|
|
|
|
Tools::Timer<> timer;
|
|
// launch complete save as seperate thread
|
|
// but wait till the first critical part is finished
|
|
// which means all objects got written into a format object
|
|
// the remaining operations are only pure buffered i/o
|
|
auto critical_promise = std::make_shared<std::promise<bool>>();
|
|
auto critical_future = critical_promise->get_future();
|
|
SaveContext::finished = std::async( std::launch::async, [&, critical_promise]() -> bool {
|
|
std::atomic<bool> result( true );
|
|
try
|
|
{
|
|
SaveContext sc;
|
|
std::vector<std::future<bool>> critical_parts;
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
sc.pol() << "#" << pf_endl << "# Created by Version: " << POL_VERSION_ID << pf_endl
|
|
<< "# Mobiles: " << get_mobile_count() << pf_endl
|
|
<< "# Top-level Items: " << get_toplevel_item_count() << pf_endl << "#"
|
|
<< pf_endl << pf_endl;
|
|
|
|
write_system_data( sc.pol );
|
|
write_global_properties( sc.pol );
|
|
write_shadow_realms( sc.pol );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store pol datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
write_items( sc.items );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store items datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
write_characters( sc );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store character datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
write_npcs( sc );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store npcs datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
write_multis( sc.multis );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store multis datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
gamestate.storage.print( sc.storage );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store storage datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
write_resources_dat( sc.resource );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store resource datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
write_guilds( sc.guilds );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store guilds datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
Module::write_datastore( sc.datastore );
|
|
// Atomically (hopefully) perform the switch.
|
|
Module::commit_datastore();
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store datastore datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
critical_parts.push_back( gamestate.task_thread_pool.checked_push( [&]() {
|
|
try
|
|
{
|
|
write_party( sc.party );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to store party datafile!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
} ) );
|
|
for ( auto& task : critical_parts )
|
|
task.wait();
|
|
|
|
critical_promise->set_value( result ); // critical part end
|
|
// TODO: since promise can only be set one time move it into a method with a dedicated try
|
|
// block, now when in theory an upper part fails the promise gets never set
|
|
} // deconstructor of the SaveContext flushes and joins the queues
|
|
catch ( std::ios_base::failure& e )
|
|
{
|
|
POLLOG_ERROR << "failed to save datafiles! " << e.what() << ":" << std::strerror( errno )
|
|
<< "\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
catch ( ... )
|
|
{
|
|
POLLOG_ERROR << "failed to save datafiles!\n";
|
|
Clib::force_backtrace();
|
|
result = false;
|
|
}
|
|
if ( result )
|
|
{
|
|
commit( "pol" );
|
|
commit( "objects" );
|
|
commit( "pcs" );
|
|
commit( "pcequip" );
|
|
commit( "npcs" );
|
|
commit( "npcequip" );
|
|
commit( "items" );
|
|
commit( "multis" );
|
|
commit( "storage" );
|
|
commit( "resource" );
|
|
commit( "guilds" );
|
|
commit( "datastore" );
|
|
commit( "parties" );
|
|
}
|
|
return true;
|
|
} );
|
|
critical_future.wait(); // wait for end of critical part
|
|
|
|
if ( Plib::systemstate.accounts_txt_dirty ) // write accounts extra, since it uses extra thread
|
|
// for io operations would be to many threads working
|
|
{
|
|
Accounts::write_account_data();
|
|
}
|
|
|
|
commit_incremental_saves();
|
|
objStorageManager.incremental_save_count = 0;
|
|
timer.stop();
|
|
objStorageManager.objecthash.ClearDeleted();
|
|
// optimize_zones(); // shrink zone vectors TODO this takes way to much time!
|
|
|
|
// cout << "Clean: " << UObject::clean_writes << " Dirty: " <<
|
|
// UObject::dirty_writes << endl;
|
|
clean_writes = UObject::clean_writes;
|
|
dirty_writes = UObject::dirty_writes;
|
|
elapsed_ms = timer.ellapsed();
|
|
|
|
objStorageManager.incremental_saves_disabled = false;
|
|
return 0;
|
|
}
|
|
|
|
void read_starting_locations()
|
|
{
|
|
Clib::ConfigFile cf( "config/startloc.cfg" );
|
|
|
|
Clib::ConfigElem elem;
|
|
while ( cf.read( elem ) )
|
|
{
|
|
if ( stricmp( elem.type(), "StartingLocation" ) != 0 )
|
|
{
|
|
ERROR_PRINT << "Unknown element type in startloc.cfg: " << elem.type() << "\n";
|
|
throw std::runtime_error( "Error in configuration file." );
|
|
}
|
|
|
|
std::unique_ptr<StartingLocation> loc( new StartingLocation );
|
|
loc->city = elem.remove_string( "CITY" );
|
|
loc->desc = elem.remove_string( "DESCRIPTION" );
|
|
loc->mapid = elem.remove_ushort( "MAPID", 0 );
|
|
loc->cliloc_desc = elem.remove_unsigned( "CLILOC", 1075072 );
|
|
loc->realm = find_realm( elem.remove_string( "REALM", "britannia" ) );
|
|
|
|
std::string coord;
|
|
while ( elem.remove_prop( "Coordinate", &coord ) )
|
|
{
|
|
int x, y, z;
|
|
if ( sscanf( coord.c_str(), "%d,%d,%d", &x, &y, &z ) == 3 )
|
|
{
|
|
loc->coords.push_back(
|
|
Pos3d( static_cast<u16>( x ), static_cast<u16>( y ), static_cast<s8>( z ) ) );
|
|
}
|
|
else
|
|
{
|
|
ERROR_PRINT << "Poorly formed coordinate in startloc.cfg: '" << coord << "' for city "
|
|
<< loc->city << ", description " << loc->desc << "\n";
|
|
throw std::runtime_error( "Configuration file error in startloc.cfg." );
|
|
}
|
|
}
|
|
if ( loc->coords.size() == 0 )
|
|
{
|
|
ERROR_PRINT << "STARTLOC.CFG: StartingLocation (" << loc->city << "," << loc->desc
|
|
<< ") has no Coordinate properties."
|
|
<< "\n";
|
|
throw std::runtime_error( "Configuration file error." );
|
|
}
|
|
gamestate.startlocations.push_back( loc.release() );
|
|
}
|
|
|
|
if ( gamestate.startlocations.empty() )
|
|
throw std::runtime_error(
|
|
"STARTLOC.CFG: No starting locations found. Clients will crash on character creation." );
|
|
}
|
|
|
|
ServerDescription::ServerDescription() : name( "" ), port( 0 ), hostname( "" )
|
|
{
|
|
memset( ip, 0, sizeof ip );
|
|
}
|
|
|
|
size_t ServerDescription::estimateSize() const
|
|
{
|
|
size_t size = name.capacity() + 4 * sizeof( unsigned char ) /*ip*/
|
|
+ sizeof( unsigned short ) /*port*/
|
|
+ 3 * sizeof( unsigned int* ) + ip_match.capacity() * sizeof( unsigned int ) +
|
|
3 * sizeof( unsigned int* ) + ip_match_mask.capacity() * sizeof( unsigned int ) +
|
|
3 * sizeof( std::string* ) + hostname.capacity();
|
|
for ( const auto& s : acct_match )
|
|
size += s.capacity();
|
|
return size;
|
|
}
|
|
|
|
void read_gameservers()
|
|
{
|
|
std::string accttext;
|
|
|
|
Clib::ConfigFile cf( "config/servers.cfg" );
|
|
|
|
Clib::ConfigElem elem;
|
|
while ( cf.read( elem ) )
|
|
{
|
|
if ( !elem.type_is( "GameServer" ) )
|
|
continue;
|
|
|
|
std::unique_ptr<ServerDescription> svr( new ServerDescription );
|
|
|
|
svr->name = elem.remove_string( "NAME" );
|
|
|
|
std::string iptext;
|
|
int ip0, ip1, ip2, ip3;
|
|
iptext = elem.remove_string( "IP" );
|
|
if ( iptext == "--ip--" )
|
|
{
|
|
iptext = networkManager.ipaddr_str;
|
|
if ( iptext == "" )
|
|
{
|
|
INFO_PRINT << "Skipping server " << svr->name
|
|
<< " because there is no Internet IP address.\n";
|
|
continue;
|
|
}
|
|
}
|
|
else if ( iptext == "--lan--" )
|
|
{
|
|
iptext = networkManager.lanaddr_str;
|
|
if ( iptext == "" )
|
|
{
|
|
INFO_PRINT << "Skipping server " << svr->name << " because there is no LAN IP address.\n";
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( isdigit( iptext[0] ) )
|
|
{
|
|
if ( sscanf( iptext.c_str(), "%d.%d.%d.%d", &ip0, &ip1, &ip2, &ip3 ) != 4 )
|
|
{
|
|
ERROR_PRINT << "SERVERS.CFG: Poorly formed IP (" << iptext << ") for GameServer '"
|
|
<< svr->name << "'.\n";
|
|
throw std::runtime_error( "Configuration file error." );
|
|
}
|
|
svr->ip[0] = static_cast<unsigned char>( ip3 );
|
|
svr->ip[1] = static_cast<unsigned char>( ip2 );
|
|
svr->ip[2] = static_cast<unsigned char>( ip1 );
|
|
svr->ip[3] = static_cast<unsigned char>( ip0 );
|
|
}
|
|
else
|
|
{
|
|
svr->hostname = iptext;
|
|
|
|
#ifdef __linux__
|
|
/* try to look up */
|
|
struct hostent host_ret;
|
|
struct hostent* host_result = nullptr;
|
|
char tmp_buf[1024];
|
|
int my_h_errno;
|
|
int res = gethostbyname_r( svr->hostname.c_str(), &host_ret, tmp_buf, sizeof tmp_buf,
|
|
&host_result, &my_h_errno );
|
|
if ( res == 0 && host_result && host_result->h_addr_list[0] )
|
|
{
|
|
char* addr = host_result->h_addr_list[0];
|
|
svr->ip[0] = addr[3];
|
|
svr->ip[1] = addr[2];
|
|
svr->ip[2] = addr[1];
|
|
svr->ip[3] = addr[0];
|
|
/*
|
|
struct sockaddr_in saddr;
|
|
memcpy( &saddr.sin_addr, he->h_addr_list[0], he->h_length);
|
|
server->ip[0] = saddr.sin_addr.S_un.S_un_b.s_b1;
|
|
server->ip[1] = saddr.sin_addr.S_un.S_un_b.s_b2;
|
|
server->ip[2] = saddr.sin_addr.S_un.S_un_b.s_b3;
|
|
server->ip[3] = saddr.sin_addr.S_un.S_un_b.s_b4;
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
POLLOG_ERROR.Format( "Warning: gethostbyname_r failed for server {} ({}): {}\n" )
|
|
<< svr->name << svr->hostname << my_h_errno;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
svr->port = elem.remove_ushort( "PORT" );
|
|
|
|
while ( elem.remove_prop( "IPMATCH", &iptext ) )
|
|
{
|
|
auto delim = iptext.find_first_of( '/' );
|
|
if ( delim != std::string::npos )
|
|
{
|
|
std::string ipaddr_str = iptext.substr( 0, delim );
|
|
std::string ipmask_str = iptext.substr( delim + 1 );
|
|
unsigned int ipaddr = inet_addr( ipaddr_str.c_str() );
|
|
unsigned int ipmask = inet_addr( ipmask_str.c_str() );
|
|
svr->ip_match.push_back( ipaddr );
|
|
svr->ip_match_mask.push_back( ipmask );
|
|
}
|
|
else
|
|
{
|
|
unsigned int ipaddr = inet_addr( iptext.c_str() );
|
|
svr->ip_match.push_back( ipaddr );
|
|
svr->ip_match_mask.push_back( 0xFFffFFffLu );
|
|
}
|
|
}
|
|
|
|
while ( elem.remove_prop( "ACCTMATCH", &accttext ) )
|
|
{
|
|
svr->acct_match.push_back( accttext );
|
|
}
|
|
|
|
networkManager.servers.push_back( svr.release() );
|
|
}
|
|
if ( networkManager.servers.empty() )
|
|
throw std::runtime_error( "There must be at least one GameServer in SERVERS.CFG." );
|
|
}
|
|
} // namespace Core
|
|
} // namespace Pol
|