uox3/source/uox3.cpp
Xoduz 94233bfd52 Fix for help menu script - UOX3 0.99.5b-7
Fixed a missing definition for GetStartTime JS function which would cause an error in the help menu script
Tweaked some messages output to the UOX3 console during startup for improved clarity
2022-08-26 23:15:13 +08:00

3422 lines
129 KiB
C++

//==========================================================================
/*
Ultima Offline eXperiment III (UOX3)
UO Server Emulation Program
Copyright 1998 - 2021 by UOX3 contributors
Copyright 1997, 98 by Marcus Rating (Cironian)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* In addition to that license, if you are running this program or modified *
* versions of it on a public system you HAVE TO make the complete source of *
* the version used by you available or provide people with a location to *
* download it. *
You can contact the author by sending email to <cironian@stratics.com>.
*/
//==========================================================================
#include <chrono>
#include <random>
#include <thread>
#include <cstdlib>
#include <filesystem>
#include <optional>
#include "uox3.h"
#include "weight.h"
#include "books.h"
#include "cGuild.h"
#include "combat.h"
#include "msgboard.h"
#include "townregion.h"
#include "cWeather.hpp"
#include "movement.h"
#include "cRaces.h"
#include "cServerDefinitions.h"
#include "skills.h"
#include "commands.h"
#include "cSpawnRegion.h"
#include "wholist.h"
#include "cMagic.h"
#include "PageVector.h"
#include "speech.h"
#include "cVersionClass.h"
#include "ssection.h"
#include "cHTMLSystem.h"
#include "gump.h"
#include "CJSMapping.h"
#include "cScript.h"
#include "cEffects.h"
#include "teffect.h"
#include "CPacketSend.h"
#include "classes.h"
#include "cThreadQueue.h"
#include "regions.h"
#include "magic.h"
#include "jail.h"
#include "Dictionary.h"
#include "ObjectFactory.h"
#include "PartySystem.h"
#include "CJSEngine.h"
#include "StringUtility.hpp"
#include "EventTimer.hpp"
#if PLATFORM == WINDOWS
#include <process.h>
#include <conio.h>
#endif
//==========================================================================
// Global variables
std::thread cons;
std::thread netw;
std::chrono::time_point<std::chrono::system_clock> current;
std::mt19937 generator;
std::random_device rd; //Will be used to obtain a seed for the random number engine
using namespace std::string_literals ;
//==========================================================================
// These should be atomic, for another day
bool isWorldSaving = false;
bool conthreadcloseok = false;
bool netpollthreadclose = false;
auto saveOnShutdown = false ;
//================================================================================================
// Classes we will use
CConsole Console; // non pointer class, has initialize
// Non depdendent class
auto aWorld = CWorldMain();
auto aDictionary = CDictionaryContainer(); // no startup
auto aCombat = CHandleCombat(); // No dependency,startup
auto aItems = cItem(); // No startup, no dependency
auto aNpcs = cCharStuff(); // nodependency, no startup
auto aSkills = cSkills(); // no ddependency, no startup
auto aWeight = CWeight(); // no dependency, no startup
auto aMagic = cMagic(); // No dependent, no startup
auto aRaces = cRaces(); // no dependent, no startup
auto aWeather = cWeatherAb(); // no dependent, no startup
auto aMovement = cMovement(); // No dependent, no startup
auto aWhoList = cWhoList(); // no dependent, no startup
auto aOffList = cWhoList(false); // no dependent, no startup
auto aBooks = cBooks(); // no dependent, no startup
auto aGMQueue = PageVector("GM Queue"); // no dependent, no startup
auto aCounselorQueue = PageVector("Counselor Queue"); // no dependent, no startup
auto aJSMapping = CJSMapping(); // nodepend, no startup
auto aEffects = cEffects(); // No dependnt, no startup
auto aHTMLTemplates = cHTMLTemplates(); // no depend, no startup
auto aGuildSys = CGuildCollection(); // no depend,no startup
auto aJailSys = JailSystem(); // no depend, no startup
// Dependent or have startup()
auto aSpeechSys = CSpeechQueue() ; // has startup
auto aJSEngine = CJSEngine(); // has startup
auto aFileLookup = CServerDefinitions() ; // has startup
auto aCommands = cCommands() ; // Restart resets commands, maybe no dependency
auto aMap = CMulHandler(); // replaced
auto aNetwork = cNetworkStuff(); // Maybe dependent, has startup
auto aMapRegion = CMapHandler(); // Dependent (Map->) , has startup
auto aAccounts = cAccountClass(); // no dpend, use SetPath
//o-----------------------------------------------------------------------------------------------o
// FileIO Pre-Declarations
//o-----------------------------------------------------------------------------------------------o
void LoadCustomTitle( void );
void LoadSkills( void );
void LoadSpawnRegions( void );
void LoadRegions( void );
void LoadTeleportLocations( void );
void LoadCreatures( void );
void LoadPlaces( void );
//o-----------------------------------------------------------------------------------------------o
// Misc Pre-Declarations
//o-----------------------------------------------------------------------------------------------o
void restockNPC( CChar& i, bool stockAll );
void clearTrades( void );
void sysBroadcast( const std::string& txt );
void MoveBoat( UI08 dir, CBoatObj *boat );
bool DecayItem( CItem& toDecay, const UI32 nextDecayItems, const UI32 nextDecayItemsInHouses );
void CheckAI( CChar& mChar );
//=================================================================================================
// Internal Pre-Declares
//=================================================================================================
#if PLATFORM == WINDOWS
BOOL WINAPI exit_handler( DWORD dwCtrlType );
#else
void app_stopped(int sig);
#endif
auto endmessage(SI32 x) ->void;
auto InitClasses() ->void ;
auto InitMultis() ->void ;
auto DisplayBanner()->void ;
auto CheckConsoleKeyThread() ->void;
auto DoMessageLoop() -> void;
auto startInitialize(CServerData &server_data) ->void;
auto initOperatingSystem() ->std::optional<std::string>;
//=================================================================================================
//o-----------------------------------------------------------------------------------------------o
//| Function - int main( SI32 argc, char *argv[] )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Main UOX startup
//o-----------------------------------------------------------------------------------------------o
auto main( SI32 argc, char *argv[] ) ->int {
UI32 tempSecs, tempMilli, tempTime;
UI32 loopSecs, loopMilli;
TIMERVAL uiNextCheckConn = 0;
//===============================================================================================
// We are going to do some fundmental checks, that if fail, we will bail out before
// setting up
//==============================================================================================
auto config_file = std::string("uox.ini");
if (argc>1){
config_file = argv[1] ;
}
auto status = initOperatingSystem() ;
if (status.has_value()){
std::cerr <<status.value() << std::endl;
return EXIT_FAILURE;
}
// Ok, we probably want the Console now
Console.initialize();
Console.Start( oldstrutil::format( "%s v%s.%s (%s)", CVersionClass::GetProductName().c_str(), CVersionClass::GetVersion().c_str(), CVersionClass::GetBuild().c_str(), OS_STR ));
Console.PrintSectionBegin();
Console << "UOX Server start up!" << myendl << "Welcome to " << CVersionClass::GetProductName() << " v" << CVersionClass::GetVersion() << "." << CVersionClass::GetBuild() << " (" << OS_STR << ")" << myendl;
Console.PrintSectionBegin();
// We are going to load some of our basic data, if that goes ok, we then initialize classes, data, network
// and the classes
Console << "Processing INI Settings ";
if( !std::filesystem::exists( std::filesystem::path( config_file )))
{
Console.error( config_file.empty() ? "Cannot find UOX3 ini file." : oldstrutil::format( "Cannot find UOX3 ini file: %s", config_file.c_str() ));
return EXIT_FAILURE;
}
auto serverdata = CServerData() ;
if( !serverdata.Load( config_file ))
{
Console.error( config_file.empty() ? "Error loading UOX3 ini file." : oldstrutil::format( "Error loading UOX3 ini file: %s", config_file.c_str() ));
return EXIT_FAILURE;
}
Console.PrintDone();
//=================================================================================================
// Start/Initalize classes,data,network
//=================================================================================================
startInitialize(serverdata) ;
//=================================================================================================
// Main Loop
//=================================================================================================
Console.PrintSectionBegin();
EVENT_TIMER(stopwatch,EVENT_TIMER_OFF);
while( cwmWorldState->GetKeepRun() ){
std::this_thread::sleep_for(std::chrono::milliseconds(( cwmWorldState->GetPlayersOnline() ? 5 : 90 )));
if( cwmWorldState->ServerProfile()->LoopTimeCount() >= 1000 ){
cwmWorldState->ServerProfile()->LoopTimeCount( 0 );
cwmWorldState->ServerProfile()->LoopTime( 0 );
}
cwmWorldState->ServerProfile()->IncLoopTimeCount();
StartMilliTimer( loopSecs, loopMilli );
if( cwmWorldState->ServerProfile()->NetworkTimeCount() >= 1000 ){
cwmWorldState->ServerProfile()->NetworkTimeCount( 0 );
cwmWorldState->ServerProfile()->NetworkTime( 0 );
}
StartMilliTimer( tempSecs, tempMilli );
EVENT_TIMER_RESET(stopwatch);
if( uiNextCheckConn <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
// Cut lag on CheckConn by not doing it EVERY loop.
Network->CheckConnections();
uiNextCheckConn = BuildTimeValue( 1.0f );
}
Network->CheckMessages();
EVENT_TIMER_NOW(stopwatch, Complete net checkmessages,EVENT_TIMER_KEEP);
tempTime = CheckMilliTimer( tempSecs, tempMilli );
cwmWorldState->ServerProfile()->IncNetworkTime( tempTime );
cwmWorldState->ServerProfile()->IncNetworkTimeCount();
if( cwmWorldState->ServerProfile()->TimerTimeCount() >= 1000 ) {
cwmWorldState->ServerProfile()->TimerTimeCount( 0 );
cwmWorldState->ServerProfile()->TimerTime( 0 );
}
StartMilliTimer( tempSecs, tempMilli );
cwmWorldState->CheckTimers();
//stopwatch.output("Delta for CheckTimers");
cwmWorldState->SetUICurrentTime( getclock() );
tempTime = CheckMilliTimer( tempSecs, tempMilli );
cwmWorldState->ServerProfile()->IncTimerTime( tempTime );
cwmWorldState->ServerProfile()->IncTimerTimeCount();
if( cwmWorldState->ServerProfile()->AutoTimeCount() >= 1000 ) {
cwmWorldState->ServerProfile()->AutoTimeCount( 0 );
cwmWorldState->ServerProfile()->AutoTime( 0 );
}
StartMilliTimer( tempSecs, tempMilli );
if( !cwmWorldState->GetReloadingScripts() ){
//auto stopauto = EventTimer() ;
EVENT_TIMER(stopauto,EVENT_TIMER_OFF);
cwmWorldState->CheckAutoTimers();
EVENT_TIMER_NOW(stopauto,CheckAutoTimers only,EVENT_TIMER_CLEAR);
}
tempTime = CheckMilliTimer( tempSecs, tempMilli );
cwmWorldState->ServerProfile()->IncAutoTime( tempTime );
cwmWorldState->ServerProfile()->IncAutoTimeCount();
StartMilliTimer( tempSecs, tempMilli );
EVENT_TIMER_RESET(stopwatch);
Network->ClearBuffers();
EVENT_TIMER_NOW(stopwatch,Delta for ClearBuffers,EVENT_TIMER_CLEAR);
tempTime = CheckMilliTimer( tempSecs, tempMilli );
cwmWorldState->ServerProfile()->IncNetworkTime( tempTime );
tempTime = CheckMilliTimer( loopSecs, loopMilli );
cwmWorldState->ServerProfile()->IncLoopTime( tempTime );
EVENT_TIMER_RESET(stopwatch);
DoMessageLoop();
EVENT_TIMER_NOW(stopwatch,Delta for DoMessageLoop,EVENT_TIMER_CLEAR);
}
//=================================================================================================
// Shutdown/Cleanup
//=================================================================================================
sysBroadcast( "The server is shutting down." );
Console << "Closing sockets...";
netpollthreadclose = true;
///HERE
Network->SockClose();
Console.PrintDone();
#if PLATFORM == WINDOWS
SetConsoleCtrlHandler( exit_handler, true );
#endif
if( cwmWorldState->GetWorldSaveProgress() != SS_SAVING ) {
isWorldSaving = true;
do {
cwmWorldState->SaveNewWorld( true );
} while( cwmWorldState->GetWorldSaveProgress() == SS_SAVING );
isWorldSaving = false;
}
cwmWorldState->ServerData()->save();
#if PLATFORM == WINDOWS
SetConsoleCtrlHandler( exit_handler, false );
#endif
Console.log( "Server Shutdown!\n=======================================================================\n" , "server.log" );
conthreadcloseok = true; // This will signal the console thread to close
Shutdown( 0 );
// Will never reach this, as Shutdown "exits"
return EXIT_SUCCESS;
}
//=====================================================================================
// Initialize the network
//=====================================================================================
auto initOperatingSystem() ->std::optional<std::string> {
//=================================================================================================
// Startup Winsock2(windows) or signal handers (unix)
//=================================================================================================
#if PLATFORM == WINDOWS
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD( 2, 2 );
SI32 err = WSAStartup( wVersionRequested, &wsaData );
if( err ) {
return "Winsock 2.2 not found on your system!"s;
}
#else
// Protection from server-shutdown during mid-worldsave
signal(SIGINT, app_stopped);
signal( SIGPIPE, SIG_IGN ); // This appears when we try to write to a broken network connection
#endif
return {};
}
//=====================================================================================
// Startup and Initialization
//=====================================================================================
auto startInitialize(CServerData &serverdata) ->void {
saveOnShutdown = false ;
// Let's measure startup time
auto startupStartTime = std::chrono::high_resolution_clock::now();
cwmWorldState = &aWorld;
cwmWorldState->setServerData( serverdata );
Console << "Initializing and creating class pointers... " << myendl;
InitClasses();
cwmWorldState->SetUICurrentTime( getclock() );
Console.PrintSectionBegin();
cwmWorldState->ServerData()->LoadTime();
Console << "Loading skill advancement ";
LoadSkills();
Console.PrintDone();
// Moved BulkStartup here, dunno why that function was there...
Console << "Loading dictionaries... " << myendl;
Console.PrintBasedOnVal( Dictionary->LoadDictionary( cwmWorldState->ServerData()->Directory( CSDDP_DICTIONARIES )) >= 0 );
Console << "Loading teleport ";
LoadTeleportLocations();
Console.PrintDone();
Console << "Loading GoPlaces ";
LoadPlaces();
Console.PrintDone();
generator = std::mt19937( rd() ); //Standard mersenne_twister_engine seeded with rd()
auto packetSection = JSMapping->GetSection( SCPT_PACKET );
for( const auto &[id, ourScript] : packetSection->collection() )
{
if( ourScript )
{
ourScript->ScriptRegistration( "Packet" );
}
}
// moved all the map loading into CMulHandler
//Map->load();
Skills->Load();
Console << "Loading Spawn Regions ";
LoadSpawnRegions();
Console.PrintDone();
Console << "Loading Regions ";
LoadRegions();
Console.PrintDone();
Magic->LoadScript();
Console << "Loading Races ";
Races->load();
Console.PrintDone();
Console << "Loading Weather ";
Weather->Load();
Weather->NewDay();
Weather->NewHour();
Console.PrintDone();
Console << "Loading Commands " << myendl;
Commands->Load();
Console.PrintDone();
// Rework that...
Console << "Loading World now ";
MapRegion->Load();
Console << "Loading Guilds ";
GuildSys->Load();
Console.PrintDone();
Console.PrintSectionBegin();
Console << "Clearing all trades ";
clearTrades();
Console.PrintDone();
InitMultis();
cwmWorldState->SetStartTime( cwmWorldState->GetUICurrentTime() );
cwmWorldState->SetEndTime( 0 );
cwmWorldState->SetLClock( 0 );
// no longer Que, because that's taken care of by PageVector
Console << "Initializing Jail system ";
JailSys->ReadSetup();
JailSys->ReadData();
Console.PrintDone();
Console << "Initializing Status system ";
HTMLTemplates->Load();
Console.PrintDone();
Console << "Loading custom titles ";
LoadCustomTitle();
Console.PrintDone();
Console << "Loading temporary Effects ";
Effects->LoadEffects();
Console.PrintDone();
Console << "Loading creatures ";
LoadCreatures();
Console.PrintDone();
Console << "Starting World Timers ";
cwmWorldState->SetTimer( tWORLD_LIGHTTIME, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_WEATHER ));
cwmWorldState->SetTimer( tWORLD_NEXTNPCAI, BuildTimeValue( (R32)cwmWorldState->ServerData()->CheckNpcAISpeed() ));
cwmWorldState->SetTimer( tWORLD_NEXTFIELDEFFECT, BuildTimeValue( 0.5f ));
cwmWorldState->SetTimer( tWORLD_SHOPRESTOCK, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_SHOPSPAWN ));
cwmWorldState->SetTimer( tWORLD_PETOFFLINECHECK, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_PETOFFLINECHECK ));
Console.PrintDone();
DisplayBanner();
Console << "Loading Accounts ";
Accounts->Load();
Console.PrintDone();
Console.log( "-=Server Startup=-\n=======================================================================", "server.log" );
Console << "Creating and Initializing Console Thread ";
cons = std::thread( &CheckConsoleKeyThread );
Console.PrintDone();
// Shows information about IPs and ports being listened on
Console.TurnYellow();
auto externalIP = cwmWorldState->ServerData()->ExternalIP();
if( externalIP != "" && externalIP != "localhost" && externalIP != "127.0.0.1" )
{
Console << "UOX: listening for incoming connections on External/WAN IP: " << externalIP.c_str() << myendl;
}
auto deviceIPs = ip4list_t::available();
for( auto &entry : deviceIPs.ips() )
{
switch( entry.type() )
{
case ip4addr_t::ip4type_t::lan:
Console << "UOX: listening for incoming connections on LAN IP: " << entry.description() << myendl;
break;
case ip4addr_t::ip4type_t::local:
Console << "UOX: listening for incoming connections on Local IP: " << entry.description() << myendl;
break;
case ip4addr_t::ip4type_t::wan:
Console << "UOX: listening for incoming connections on WAN IP: " << entry.description() << myendl;
break;
default:
Console << "UOX: listening for incoming connections on IP: " << entry.description() << myendl;
break;
}
}
Console.TurnNormal();
// we've really finished loading here
cwmWorldState->SetLoaded( true );
// Get a second timestamp for startup time
auto startupEndTime = std::chrono::high_resolution_clock::now();
// Calculate startup time in milliseconds
auto startupDuration = std::chrono::duration_cast<std::chrono::milliseconds>( startupEndTime - startupStartTime ).count();
Console.TurnGreen();
Console << "UOX: Startup Completed in " << (R32)startupDuration/1000 << " seconds." << myendl;
Console.TurnNormal();
}
//=================================================================================================
// Most things after this point, should be in different files, not in uox3.cpp.
//=================================================================================================
// Signal and exit handers
//=================================================================================================
#if PLATFORM == WINDOWS
//o-----------------------------------------------------------------------------------------------o
//| Function - BOOL WINAPI exit_handler(DWORD dwCtrlType)
//| void app_stopped(int sig)
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Prevent closing of console via CTRL+C/or CTRL+BREAK keys during worldsaves
//o-----------------------------------------------------------------------------------------------o
BOOL WINAPI exit_handler( DWORD dwCtrlType ) {
switch( dwCtrlType ) {
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
std::cout << std::endl << "World save in progress - closing UOX3 before it completes may result in corrupted save data!" << std::endl;
// Shutdown of the application will only be halted for as long as the exit_handler is doing something,
// so do some non-work while isWorldSaving is true to prevent shutdown during save
while( isWorldSaving == true ) {
Sleep(0);
}
return true;
default:
return false;
}
}
#else
//=================================================================================================
// These first two, should be removed. We should fix the error
//=================================================================================================
//=================================================================================================
auto illinst( SI32 x = 0 ) ->void {
sysBroadcast( "Fatal Server Error! Bailing out - Have a nice day!" );
Console.error( "Illegal Instruction Signal caught - attempting shutdown" );
endmessage( x );
}
//=================================================================================================
auto aus( SI32 signal ) ->void {
Console.error( "Server crash averted! Floating point exception caught." );
}
//=================================================================================================
void app_stopped(int sig)
{
// function called when signal is received.
if( isWorldSaving == false ) {
cwmWorldState->SetKeepRun( false );
}
}
#endif
//=================================================================================================
// Spawn related
//=================================================================================================
//o-----------------------------------------------------------------------------------------------o
//| Function - void UnloadSpawnRegions( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Unload spawn regions on server shutdown or when reloading spawn regions
//o-----------------------------------------------------------------------------------------------o
auto UnloadSpawnRegions() ->void {
for (auto &[regionnum,spawnregion]:cwmWorldState->spawnRegions) {
if( spawnregion ) {
// Iterate over list of spawned characters and delete them if no player has tamed them/hired them
std::vector<CChar *> toDelete ;
auto spawnedCharsList = spawnregion->GetSpawnedCharsList();
for (const auto &cCheck : spawnedCharsList->collection()){
if( ValidateObject( cCheck ) ){
if( !ValidateObject( cCheck->GetOwnerObj() )) {
toDelete.push_back(cCheck);
}
}
}
std::for_each(toDelete.begin(), toDelete.end(), [](CChar *entry){
entry->Delete();
});
// Iterate over list of spawned items and delete them if no player has picked them up
std::vector<CItem *> toIDelete ;
auto spawnedItemsList = spawnregion->GetSpawnedItemsList();
for (const auto &iCheck : spawnedItemsList->collection()){
if( ValidateObject( iCheck ) ){
if( iCheck->GetContSerial() != INVALIDSERIAL || !ValidateObject( iCheck->GetOwnerObj() )) {
toIDelete.push_back(iCheck);
}
}
}
std::for_each(toIDelete.begin(), toIDelete.end(), [](CItem *entry){
entry->Delete();
});
delete spawnregion;
}
}
cwmWorldState->spawnRegions.clear();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void UnloadRegions( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Unload town regions on server shutdown or when reloading town regions
//o-----------------------------------------------------------------------------------------------o
auto UnloadRegions() ->void {
std::for_each(cwmWorldState->townRegions.begin(), cwmWorldState->townRegions.end(), [](const std::pair<std::uint16_t,CTownRegion *> &entry){
if (entry.second) {
delete entry.second ;
}
});
cwmWorldState->townRegions.clear();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void DoMessageLoop( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Watch for messages thrown by UOX
//o-----------------------------------------------------------------------------------------------o
auto DoMessageLoop()->void {
// Grab all the data in the queue
auto messages = messageLoop.bulkData();
while( !messages.empty() ) {
auto tVal = messages.front();
messages.pop() ;
switch( tVal.actualMessage ) {
case MSG_SHUTDOWN: cwmWorldState->SetKeepRun(false); break;
case MSG_COUNT: break;
case MSG_WORLDSAVE: cwmWorldState->SetOldTime(0); break;
case MSG_PRINT: Console << tVal.data << myendl; break;
case MSG_RELOADJS: JSEngine->Reload();
JSMapping->Reload();
Console.PrintDone();
Commands->Load(); break;
case MSG_CONSOLEBCAST: sysBroadcast( tVal.data ); break;
case MSG_PRINTDONE: Console.PrintDone(); break;
case MSG_PRINTFAILED: Console.PrintFailed(); break;
case MSG_SECTIONBEGIN: Console.PrintSectionBegin(); break;
case MSG_RELOAD:
if( !cwmWorldState->GetReloadingScripts() ) {
cwmWorldState->SetReloadingScripts( true );
switch( tVal.data[0] ) {
case '0': cwmWorldState->ServerData()->Load(); break; // Reload INI file
case '1': Accounts->Load(); break; // Reload accounts
case '2': UnloadRegions();
LoadRegions();
LoadTeleportLocations(); break; // Reload regions/TeleportLocations
case '3': UnloadSpawnRegions(); LoadSpawnRegions(); break; // Reload spawn regions
case '4': Magic->LoadScript(); break; // Reload spells
case '5': JSMapping->Reload( SCPT_COMMAND );
Commands->Load(); break; // Reload commands
case '6': FileLookup->Reload();
LoadCreatures();
LoadCustomTitle();
LoadSkills();
LoadPlaces();
Skills->Load(); break; // Reload definition files
case '7': JSEngine->Reload();
JSMapping->Reload();
Console.PrintDone();
Commands->Load();
Skills->Load(); break; // Reload JS
case '8': HTMLTemplates->Unload();
HTMLTemplates->Load(); break; // Reload HTML
}
cwmWorldState->SetReloadingScripts( false );
}
break;
case MSG_UNKNOWN:
default: Console.error( "Unknown message type" ); break;
}
}
}
//=================================================================================================
// Threads
//=================================================================================================
//o-----------------------------------------------------------------------------------------------o
//| Function - void NetworkPollConnectionThread( void *params )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Watch for new connections
//o-----------------------------------------------------------------------------------------------o
auto NetworkPollConnectionThread() ->void {
messageLoop << "Thread: NetworkPollConnection has started";
netpollthreadclose = false;
while( !netpollthreadclose ) {
Network->CheckConnections();
Network->CheckLoginMessage();
std::this_thread::sleep_for(std::chrono::milliseconds( 20 ));
}
messageLoop << "Thread: NetworkPollConnection has Closed";
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void CheckConsoleKeyThread( void *params )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Listen for key inputs in server console
//o-----------------------------------------------------------------------------------------------o
auto CheckConsoleKeyThread() ->void {
messageLoop << "Thread: CheckConsoleThread has started";
Console.Registration();
conthreadcloseok = false;
while( !conthreadcloseok ) {
Console.Poll();
std::this_thread::sleep_for(std::chrono::milliseconds( 500 ));
}
messageLoop << "Thread: CheckConsoleKeyThread Closed";
}
//=================================================================================================
//
//=================================================================================================
//o-----------------------------------------------------------------------------------------------o
//| Function - bool isOnline( CChar& mChar )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check if the socket owning character c is still connected
//o-----------------------------------------------------------------------------------------------o
auto isOnline( CChar& mChar ) -> bool {
auto rvalue = false ;
if( !mChar.IsNpc() ){
CAccountBlock& actbTemp = mChar.GetAccount();
if( actbTemp.wAccountIndex != AB_INVALID_ID ) {
if( actbTemp.dwInGame == mChar.GetSerial() ){
rvalue = true ;
}
}
if (!rvalue){
for (auto &tSock : Network->connClients) {
if( tSock->CurrcharObj() == &mChar ){
rvalue = true ;
break;
}
}
}
}
return rvalue ;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void updateStats( CBaseObject *mObj, UI08 x )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Updates object's stats
//o-----------------------------------------------------------------------------------------------o
auto updateStats( CBaseObject *mObj, UI08 x ) ->void {
for (auto &tSock :FindNearbyPlayers( mObj ) ) {
if( tSock->LoginComplete() ){
// Normalize stats if we're updating our stats for other players
auto normalizeStats = true;
if( tSock->CurrcharObj()->GetSerial() == mObj->GetSerial() ){
tSock->statwindow( mObj );
normalizeStats = false;
}
// Prepare the stat update packet
CPUpdateStat toSend( (*mObj), x, normalizeStats );
// Send the stat update packet
tSock->Send( &toSend );
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void CollectGarbage( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Deletes objects in the Deletion Queue
//o-----------------------------------------------------------------------------------------------o
auto CollectGarbage() ->void {
Console << "Performing Garbage Collection...";
auto objectsDeleted = std::uint32_t(0);
std::for_each(cwmWorldState->deletionQueue.begin(), cwmWorldState->deletionQueue.end(),[&objectsDeleted](std::pair<CBaseObject*,std::uint32_t> entry){
if (entry.first) {
if (entry.first->isFree() && entry.first->isDeleted()){
Console.warning( "Invalid object found in Deletion Queue" );
}
else {
ObjectFactory::getSingleton().DestroyObject( entry.first );
++objectsDeleted;
}
}
});
cwmWorldState->deletionQueue.clear();
Console << " Removed " << objectsDeleted << " objects";
JSEngine->CollectGarbage();
Console.PrintDone();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void MountCreature( CSocket *sockPtr, CChar *s, CChar *x )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Mount a ridable creature
//|
//| Changes - 09/22/2002 - Unhide players when mounting horses etc.
//o-----------------------------------------------------------------------------------------------o
auto MountCreature( CSocket *sockPtr, CChar *s, CChar *x ) ->void {
if( !s->IsOnHorse() && objInRange( s, x, DIST_NEXTTILE )){
//No mounting horses for gargoyles!
if( s->GetID() == 0x029A || s->GetID() == 0x029B ){
sockPtr->sysmessage( 1798 );
}
else if( x->GetOwnerObj() == s || Npcs->checkPetFriend( s, x ) || s->IsGM() )
{
if( !cwmWorldState->ServerData()->CharHideWhileMounted() ){
s->ExposeToView();
}
s->SetOnHorse( true );
auto c = Items->CreateItem( nullptr, s, 0x0915, 1, x->GetSkin(), OT_ITEM );
auto xName = getNpcDictName( x, sockPtr );
c->SetName( xName );
c->SetDecayable( false );
c->SetLayer( IL_MOUNT );
if( cwmWorldState->creatures[x->GetID()].MountID() != 0 ){
c->SetID( cwmWorldState->creatures[x->GetID()].MountID() );
}
else{
c->SetID( 0x3E00 );
}
if( !c->SetCont( s ) ){
s->SetOnHorse( false ); // let's get off our horse again
c->Delete();
return;
}
else {
for (auto &tSock : FindNearbyPlayers( s )){
s->SendWornItems( tSock );
}
if( x->GetTarg()) {// zero out target, under all circumstances
x->SetTarg( nullptr );
if( x->IsAtWar() ){
x->ToggleCombat();
}
}
if( ValidateObject( x->GetAttacker() ) ){
x->GetAttacker()->SetTarg( nullptr );
}
x->SetFrozen( true );
x->SetMounted( true );
x->SetInvulnerable( true );
x->SetLocation( 7000, 7000, 0 );
c->SetTempVar( CITV_MOREX, x->GetSerial() );
if( x->GetTimer( tNPC_SUMMONTIME ) != 0 ){
c->SetDecayTime( x->GetTimer( tNPC_SUMMONTIME ) );
}
}
}
else {
sockPtr->sysmessage( 1214 );
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void DismountCreature( CChar *s )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Dismount a ridable creature
//o-----------------------------------------------------------------------------------------------o
auto DismountCreature( CChar *s ) ->void {
if( ValidateObject(s)){
auto ci = s->GetItemAtLayer( IL_MOUNT );
if( ValidateObject( ci ) && !ci->isFree() ){
s->SetOnHorse( false );
auto tMount = calcCharObjFromSer( ci->GetTempVar( CITV_MOREX ) );
if( ValidateObject( tMount ) ) {
tMount->SetLocation( s );
tMount->SetFrozen( false );
tMount->SetMounted( false );
tMount->SetInvulnerable( false );
if( ci->GetDecayTime() != 0 ){
tMount->SetTimer( tNPC_SUMMONTIME, ci->GetDecayTime() );
}
tMount->SetDir( s->GetDir() );
tMount->SetVisible( VT_VISIBLE );
}
ci->Delete();
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void endmessage( SI32 x )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Global message players with shutdown message
//o-----------------------------------------------------------------------------------------------o
auto endmessage(SI32 x) ->void {
x = 0; // Really, then why take a parameter?
const UI32 igetclock = cwmWorldState->GetUICurrentTime();
if( cwmWorldState->GetEndTime() < igetclock ){
cwmWorldState->SetEndTime( igetclock );
}
sysBroadcast( oldstrutil::format( Dictionary->GetEntry( 1209 ), ((cwmWorldState->GetEndTime()-igetclock)/ 1000 ) / 60 ) );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void callguards( CChar *mChar )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Used when a character calls "Guards" Will look for a criminal
//| first checking for anyone attacking him. If no one is attacking
//| him it will look for any people nearby who are criminal or
//| murderers
//o-----------------------------------------------------------------------------------------------o
auto callGuards( CChar *mChar ) -> void {
if( ValidateObject( mChar ) && mChar->GetRegion()->IsGuarded() && cwmWorldState->ServerData()->GuardsStatus() ){
auto attacker = mChar->GetAttacker();
if( ValidateObject( attacker ) ) {
if( !attacker->IsDead() && ( attacker->IsCriminal() || attacker->IsMurderer() ) ){
if( charInRange( mChar, attacker ) ){
Combat->SpawnGuard( mChar, attacker, attacker->GetX(), attacker->GetY(), attacker->GetZ() );
return;
}
}
}
auto toCheck = MapRegion->GetMapRegion( mChar );
if( toCheck ){
auto regChars = toCheck->GetCharList();
for (const auto &tempChar : regChars->collection()){
if( ValidateObject( tempChar ) ){
if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) ){
if( charInRange( tempChar, mChar ) ){
Combat->SpawnGuard( mChar, tempChar, tempChar->GetX(), tempChar->GetY(), tempChar->GetZ() );
break;
}
}
}
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void callguards( CChar *mChar, CChar *targChar )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Used when a character calls guards on another character, will
//| ensure that character is not dead and is either a criminal or
//| murderer, and that he is in visual range of the victim, will
//| then spawn a guard to take care of the criminal.
//o-----------------------------------------------------------------------------------------------o
auto callGuards( CChar *mChar, CChar *targChar )->void {
if( ValidateObject( mChar ) && ValidateObject( targChar ) ) {
if( mChar->GetRegion()->IsGuarded() && cwmWorldState->ServerData()->GuardsStatus() ){
if( !targChar->IsDead() && ( targChar->IsCriminal() || targChar->IsMurderer() ) ) {
if( charInRange( mChar, targChar ) ){
Combat->SpawnGuard( mChar, targChar, targChar->GetX(), targChar->GetY(), targChar->GetZ() );
}
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool genericCheck( CSocket *mSock, CChar& mChar, bool checkFieldEffects, bool doWeather )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check characters status. Returns true if character was killed
//o-----------------------------------------------------------------------------------------------o
auto genericCheck( CSocket *mSock, CChar& mChar, bool checkFieldEffects, bool doWeather ) ->bool {
UI16 c;
if( !mChar.IsDead() ){
const auto maxHP = mChar.GetMaxHP();
const auto maxStam = mChar.GetMaxStam();
const auto maxMana = mChar.GetMaxMana();
if( mChar.GetHP() > maxHP ){
mChar.SetHP( maxHP );
}
if( mChar.GetStamina() > maxStam ){
mChar.SetStamina( maxStam );
}
if( mChar.GetMana() > maxMana ){
mChar.SetMana( maxMana );
}
if( mChar.GetRegen( 0 ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
if( mChar.GetHP() < maxHP ){
if( !cwmWorldState->ServerData()->HungerSystemEnabled() || (mChar.GetHunger() > 0)
|| ( !Races->DoesHunger( mChar.GetRace() ) && (( cwmWorldState->ServerData()->SystemTimer( tSERVER_HUNGERRATE ) == 0) || mChar.IsNpc() ) ) ){
for(auto c = 0; c <= maxHP ; ++c ){
if( mChar.GetHP() <= maxHP && ( mChar.GetRegen( 0 ) + ( c * cwmWorldState->ServerData()->SystemTimer( tSERVER_HITPOINTREGEN ) * 1000 ) ) <= cwmWorldState->GetUICurrentTime() ){
if( mChar.GetSkill( HEALING ) < 500 ){
mChar.IncHP( 1 );
}
else if( mChar.GetSkill( HEALING ) < 800 ){
mChar.IncHP( 2 );
}
else{
mChar.IncHP( 3 );
}
if( mChar.GetHP() >= maxHP ){
mChar.SetHP( maxHP );
break;
}
}
else { // either we're all healed up, or all time periods have passed
break;
}
}
}
}
mChar.SetRegen( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_HITPOINTREGEN ), 0 );
}
if( mChar.GetRegen( 1 ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
auto mStamina = mChar.GetStamina();
if( mStamina < maxStam ){
// Continue with stamina regen if character is not yet fully parched, or if character is parched but has less than 25% stamina, or if char belongs to race that does not thirst
if( !cwmWorldState->ServerData()->ThirstSystemEnabled() || (mChar.GetThirst() > 0 )|| ( (mChar.GetThirst() == 0) && (mStamina < static_cast<SI16>( maxStam * 0.25 )) )
|| ( !Races->DoesThirst( mChar.GetRace() ) && ( cwmWorldState->ServerData()->SystemTimer( tSERVER_THIRSTRATE ) == 0 || mChar.IsNpc() ) ) ){
for( auto c = 0; c <= maxStam ; ++c ){
if( ( mChar.GetRegen( 1 ) + ( c * cwmWorldState->ServerData()->SystemTimer( tSERVER_STAMINAREGEN ) * 1000 ) ) <= cwmWorldState->GetUICurrentTime() && mChar.GetStamina() <= maxStam ){
mChar.IncStamina( 1 );
if( mChar.GetStamina() >= maxStam ){
mChar.SetStamina( maxStam );
break;
}
}
else{
break;
}
}
}
}
mChar.SetRegen( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_STAMINAREGEN ), 1 );
}
// CUSTOM START - SPUD:MANA REGENERATION:Rewrite of passive and active meditation code
if( mChar.GetRegen( 2 ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
if( mChar.GetMana() < maxMana ){
for( c = 0; c < maxMana + 1; ++c ){
if( mChar.GetRegen( 2 ) + ( c * cwmWorldState->ServerData()->SystemTimer( tSERVER_MANAREGEN ) * 1000 ) <= cwmWorldState->GetUICurrentTime() && mChar.GetMana() <= maxMana ){
Skills->CheckSkill( ( &mChar ), MEDITATION, 0, 1000 ); // Check Meditation for skill gain ala OSI
mChar.IncMana( 1 ); // Gain a mana point
if( mChar.GetMana() == maxMana ){
if( mChar.IsMeditating() ){
if( mSock){
mSock->sysmessage( 969 );
}
mChar.SetMeditating( false );
}
break;
}
}
}
}
const R32 MeditationBonus = ( .00075f * mChar.GetSkill( MEDITATION ) ); // Bonus for Meditation
SI32 NextManaRegen = static_cast<SI32>( cwmWorldState->ServerData()->SystemTimer( tSERVER_MANAREGEN ) * ( 1 - MeditationBonus ) * 1000 );
if( cwmWorldState->ServerData()->ArmorAffectManaRegen() ){ // If armor effects mana regeneration...
R32 ArmorPenalty = Combat->calcDef( ( &mChar ), 0, false ); // Penalty taken due to high def
if( ArmorPenalty > 100 ){ // For def higher then 100, penalty is the same...just in case
ArmorPenalty = 100;
}
ArmorPenalty = 1 + ( ArmorPenalty / 25 );
NextManaRegen = static_cast<SI32>( NextManaRegen * ArmorPenalty );
}
if( mChar.IsMeditating() ){ // If player is meditation...
mChar.SetRegen( ( cwmWorldState->GetUICurrentTime() + ( NextManaRegen / 2 ) ), 2 );
}
else{
mChar.SetRegen( ( cwmWorldState->GetUICurrentTime() + NextManaRegen ), 2 );
}
}
}
// CUSTOM END
if( mChar.GetVisible() == VT_INVISIBLE && ( mChar.GetTimer( tCHAR_INVIS ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ) ){
mChar.ExposeToView();
}
// Take NPC out of EvadeState
if( mChar.IsNpc() && mChar.IsEvading() && mChar.GetTimer( tNPC_EVADETIME ) <= cwmWorldState->GetUICurrentTime() ){
mChar.SetEvadeState( false );
#if defined( UOX_DEBUG_MODE ) && defined( DEBUG_COMBAT )
std::string mCharName = getNpcDictName( &mChar );
Console.print( oldstrutil::format( "DEBUG: EvadeTimer ended for NPC (%s, 0x%X, at %i, %i, %i, %i).\n", mCharName.c_str(), mChar.GetSerial(), mChar.GetX(), mChar.GetY(), mChar.GetZ(), mChar.WorldNumber() ));
#endif
}
if( !mChar.IsDead()) {
// Hunger/Thirst Code
mChar.DoHunger( mSock );
mChar.DoThirst( mSock );
// Loyalty update for pets
mChar.DoLoyaltyUpdate();
if( !mChar.IsInvulnerable() && mChar.GetPoisoned() > 0 ){
if( mChar.GetTimer( tCHAR_POISONTIME ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
if( mChar.GetTimer( tCHAR_POISONWEAROFF ) > cwmWorldState->GetUICurrentTime() ){
std::string mCharName = getNpcDictName( &mChar );
switch( mChar.GetPoisoned() ){
case 1: { // Lesser Poison
mChar.SetTimer( tCHAR_POISONTIME, BuildTimeValue( 2 ) );
if( mChar.GetTimer( tCHAR_POISONTEXT ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
mChar.SetTimer( tCHAR_POISONTEXT, BuildTimeValue( 6 ) );
mChar.TextMessage( nullptr, 1240, EMOTE, 1, mCharName.c_str() ); // * %s looks a bit nauseous *
}
SI16 poisonDmgPercent = RandomNum( 3, 6 ); // 3% to 6% of current health per tick
SI16 poisonDmg = static_cast<SI16>((mChar.GetHP() * poisonDmgPercent) / 100);
[[maybe_unused]] bool retVal = mChar.Damage( std::max(static_cast<SI16>(3), poisonDmg), POISON ); // Minimum 3 damage per tick
break;
}
case 2: {// Normal Poison
mChar.SetTimer( tCHAR_POISONTIME, BuildTimeValue( 3 ) );
if( mChar.GetTimer( tCHAR_POISONTEXT ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
mChar.SetTimer( tCHAR_POISONTEXT, BuildTimeValue( 10 ) );
mChar.TextMessage( nullptr, 1241, EMOTE, 1, mCharName.c_str() ); // * %s looks disoriented and nauseous! *
}
SI16 poisonDmgPercent = RandomNum( 4, 8 ); // 4% to 8% of current health per tick
SI16 poisonDmg = static_cast<SI16>((mChar.GetHP() * poisonDmgPercent) / 100);
[[maybe_unused]] bool retVal = mChar.Damage( std::max(static_cast<SI16>(5), poisonDmg), POISON ); // Minimum 5 damage per tick
break;
}
case 3: // Greater Poison
{
mChar.SetTimer( tCHAR_POISONTIME, BuildTimeValue( 4 ) );
if( mChar.GetTimer( tCHAR_POISONTEXT ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
mChar.SetTimer( tCHAR_POISONTEXT, BuildTimeValue( 10 ) );
mChar.TextMessage( nullptr, 1242, EMOTE, 1, mCharName.c_str() ); // * %s is in severe pain! *
}
SI16 poisonDmgPercent = RandomNum( 8, 12 ); // 8% to 12% of current health per tick
SI16 poisonDmg = static_cast<SI16>((mChar.GetHP() * poisonDmgPercent) / 100);
[[maybe_unused]] bool retVal = mChar.Damage( std::max(static_cast<SI16>(8), poisonDmg), POISON ); // Minimum 8 damage per tick
break;
}
case 4: // Deadly Poison
{
mChar.SetTimer( tCHAR_POISONTIME, BuildTimeValue( 5 ) );
if( mChar.GetTimer( tCHAR_POISONTEXT ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
mChar.SetTimer( tCHAR_POISONTEXT, BuildTimeValue( 10 ) );
mChar.TextMessage( nullptr, 1243, EMOTE, 1, mCharName.c_str() ); // * %s looks extremely weak and is wrecked in pain! *
}
SI16 poisonDmgPercent = RandomNum( 12, 25 ); // 12% to 25% of current health per tick
SI16 poisonDmg = static_cast<SI16>((mChar.GetHP() * poisonDmgPercent) / 100);
[[maybe_unused]] bool retVal = mChar.Damage( std::max(static_cast<SI16>(14), poisonDmg), POISON ); // Minimum 14 damage per tick
break;
}
case 5: // Lethal Poison - Used by monsters only
{
mChar.SetTimer( tCHAR_POISONTIME, BuildTimeValue( 5 ) );
if( mChar.GetTimer( tCHAR_POISONTEXT ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ){
mChar.SetTimer( tCHAR_POISONTEXT, BuildTimeValue( 10 ) );
mChar.TextMessage( nullptr, 1243, EMOTE, 1, mCharName.c_str() ); // * %s looks extremely weak and is wrecked in pain! *
}
SI16 poisonDmgPercent = RandomNum( 25, 50 ); // 25% to 50% of current health per tick
SI16 poisonDmg = static_cast<SI16>((mChar.GetHP() * poisonDmgPercent) / 100);
[[maybe_unused]] bool retVal = mChar.Damage( std::max(static_cast<SI16>(17), poisonDmg), POISON ); // Minimum 14 damage per tick
break;
}
default:
Console.error( " Fallout of switch statement without default. uox3.cpp, genericCheck(), mChar.GetPoisoned() not within valid range." );
mChar.SetPoisoned( 0 );
break;
}
if( mChar.GetHP() < 1 && !mChar.IsDead() ){
std::vector<UI16> scriptTriggers = mChar.GetScriptTriggers();
for( auto &i : scriptTriggers ){
cScript *toExecute = JSMapping->GetScript( i );
if( toExecute ){
SI08 retStatus = toExecute->OnDeathBlow( &mChar, mChar.GetAttacker() );
// -1 == script doesn't exist, or returned -1
// 0 == script returned false, 0, or nothing - don't execute hard code
// 1 == script returned true or 1
if( retStatus == 0 )
return false;
}
}
HandleDeath( ( &mChar ), nullptr );
if( mSock ){
mSock->sysmessage( 1244 );
}
}
}
}
}
if( mChar.GetTimer( tCHAR_POISONWEAROFF ) <= cwmWorldState->GetUICurrentTime() ){
if( mChar.GetPoisoned() > 0 ){
mChar.SetPoisoned( 0 );
if( mSock != nullptr ){
mSock->sysmessage( 1245 );
}
}
}
}
if( !mChar.GetCanAttack() && mChar.GetTimer( tCHAR_PEACETIMER ) <= cwmWorldState->GetUICurrentTime() ){
mChar.SetCanAttack( true );
if( mSock != nullptr ){
mSock->sysmessage( 1779 );
}
}
if( mChar.IsCriminal() && mChar.GetTimer( tCHAR_CRIMFLAG ) && ( mChar.GetTimer( tCHAR_CRIMFLAG ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ) ){
if( mSock != nullptr )
mSock->sysmessage( 1238 );
mChar.SetTimer( tCHAR_CRIMFLAG, 0 );
UpdateFlag( &mChar );
}
if( mChar.GetKills() && ( mChar.GetTimer( tCHAR_MURDERRATE ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ) ){
mChar.SetKills( static_cast<SI16>( mChar.GetKills() - 1 ) );
if( mChar.GetKills() ){
mChar.SetTimer( tCHAR_MURDERRATE, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_MURDERDECAY ) );
}
else{
mChar.SetTimer( tCHAR_MURDERRATE, 0 );
}
if( mSock != nullptr && mChar.GetKills() == cwmWorldState->ServerData()->RepMaxKills() ){
mSock->sysmessage( 1239 );
}
UpdateFlag( &mChar );
}
if( !mChar.IsDead() ){
if( doWeather ){
const UI08 curLevel = cwmWorldState->ServerData()->WorldLightCurrentLevel();
LIGHTLEVEL toShow;
if( Races->VisLevel( mChar.GetRace() ) > curLevel ){
toShow = 0;
}
else{
toShow = static_cast<UI08>( curLevel - Races->VisLevel( mChar.GetRace() ) );
}
if( mChar.IsNpc() ){
doLight( &mChar, toShow );
}
else{
doLight( mSock, toShow );
}
}
Weather->doLightEffect( mSock, mChar );
Weather->doWeatherEffect( mSock, mChar, RAIN );
Weather->doWeatherEffect( mSock, mChar, SNOW );
Weather->doWeatherEffect( mSock, mChar, HEAT );
Weather->doWeatherEffect( mSock, mChar, COLD );
Weather->doWeatherEffect( mSock, mChar, STORM );
if( checkFieldEffects ){
Magic->CheckFieldEffects( mChar );
}
mChar.UpdateDamageTrack();
}
if( mChar.IsDead() ){
return true;
}
else if( mChar.GetHP() <= 0 ){
std::vector<UI16> scriptTriggers = mChar.GetScriptTriggers();
for( auto i : scriptTriggers ){
auto toExecute = JSMapping->GetScript( i );
if( toExecute){
auto retStatus = toExecute->OnDeathBlow( &mChar, mChar.GetAttacker() );
// -1 == script doesn't exist, or returned -1
// 0 == script returned false, 0, or nothing - don't execute hard code
// 1 == script returned true or 1
if( retStatus == 0 ){
return false;
}
}
}
HandleDeath( (&mChar), nullptr );
return true;
}
return false;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void checkPC( CSocket *mSock, CChar& mChar )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check a PC's status
//o-----------------------------------------------------------------------------------------------o
auto checkPC( CSocket *mSock, CChar& mChar ) ->void {
Combat->CombatLoop( mSock, mChar );
if( mChar.GetSquelched() == 2 ){
if( mSock->GetTimer( tPC_MUTETIME ) != 0 && ( mSock->GetTimer( tPC_MUTETIME ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ) ){
mChar.SetSquelched( 0 );
mSock->SetTimer( tPC_MUTETIME, 0 );
mSock->sysmessage( 1237 );
}
}
if( mChar.IsCasting() && !mChar.IsJSCasting() && mChar.GetSpellCast() != -1 ){
// Casting a spell
auto spellNum = mChar.GetSpellCast();
mChar.SetNextAct( mChar.GetNextAct() - 1 );
if( mChar.GetTimer( tCHAR_SPELLTIME ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() )//Spell is complete target it.
{
// Set the recovery time before another spell can be cast
mChar.SetTimer( tCHAR_SPELLRECOVERYTIME, BuildTimeValue( static_cast<R32>( Magic->spells[spellNum].RecoveryDelay()) ));
if( Magic->spells[spellNum].RequireTarget() ){
mChar.SetCasting( false );
mChar.SetFrozen( false );
UI08 cursorType = 0;
if( Magic->spells[spellNum].AggressiveSpell() ){
cursorType = 1;
}
else if( (spellNum == 4) || (spellNum == 6) || (spellNum == 7) || (spellNum == 9) || (spellNum == 10) || (spellNum == 11) || (spellNum == 15) || (spellNum == 16) || (spellNum == 17) || (spellNum == 25) || (spellNum == 26) || (spellNum == 29) || (spellNum == 44) || (spellNum == 59) ){
cursorType = 2;
}
mSock->target( 0, TARGET_CASTSPELL, Magic->spells[spellNum].StringToSay().c_str(), cursorType );
}
else{
mChar.SetCasting( false );
Magic->CastSpell( mSock, &mChar );
mChar.SetTimer( tCHAR_SPELLTIME, 0 );
mChar.SetFrozen( false );
}
}
else if( mChar.GetNextAct() <= 0 ){
//redo the spell action
mChar.SetNextAct( 75 );
if( !mChar.IsOnHorse() && !mChar.IsFlying() ){
// Consider Gargoyle flying as mounted here
Effects->PlaySpellCastingAnimation( &mChar, Magic->spells[mChar.GetSpellCast()].Action() );
}
}
}
if( cwmWorldState->ServerData()->WorldAmbientSounds() >= 1 ) {
if( cwmWorldState->ServerData()->WorldAmbientSounds() > 10 ){
cwmWorldState->ServerData()->WorldAmbientSounds( 10 );
}
const SI16 soundTimer = static_cast<SI16>(cwmWorldState->ServerData()->WorldAmbientSounds() * 100);
if( !mChar.IsDead() && ( RandomNum( 0, soundTimer - 1 ) ) == ( soundTimer / 2 ) ){
Effects->PlayBGSound( (*mSock), mChar );
}
}
if( mSock->GetTimer( tPC_SPIRITSPEAK ) > 0 && mSock->GetTimer( tPC_SPIRITSPEAK) < cwmWorldState->GetUICurrentTime() ){
mSock->SetTimer( tPC_SPIRITSPEAK, 0 );
}
if( mSock->GetTimer( tPC_TRACKING ) > cwmWorldState->GetUICurrentTime() ){
if( mSock->GetTimer( tPC_TRACKINGDISPLAY ) <= cwmWorldState->GetUICurrentTime() ){
mSock->SetTimer( tPC_TRACKINGDISPLAY, BuildTimeValue( (R32)cwmWorldState->ServerData()->TrackingRedisplayTime() ) );
Skills->Track( &mChar );
}
}
else {
if( mSock->GetTimer( tPC_TRACKING ) > ( cwmWorldState->GetUICurrentTime() / 10 ) ) {
// dont send arrow-away packet all the time
mSock->SetTimer( tPC_TRACKING, 0 );
if( ValidateObject( mChar.GetTrackingTarget() ) ) {
CPTrackingArrow tSend = (*mChar.GetTrackingTarget());
tSend.Active( 0 );
if( mSock->ClientType() >= CV_HS2D ){
tSend.AddSerial( mChar.GetTrackingTarget()->GetSerial() );
}
mSock->Send( &tSend );
}
}
}
if( mChar.IsOnHorse() ) {
CItem *horseItem = mChar.GetItemAtLayer( IL_MOUNT );
if( !ValidateObject( horseItem ) ){
mChar.SetOnHorse( false ); // turn it off, we aren't on one because there's no item!
}
else if( horseItem->GetDecayTime() != 0 && ( horseItem->GetDecayTime() <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ) ) {
mChar.SetOnHorse( false );
horseItem->Delete();
}
}
if( mChar.GetTimer( tCHAR_FLYINGTOGGLE ) > 0 && mChar.GetTimer( tCHAR_FLYINGTOGGLE ) < cwmWorldState->GetUICurrentTime() ) {
mChar.SetTimer( tCHAR_FLYINGTOGGLE, 0 );
mChar.SetFrozen( false );
mChar.Teleport();
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void checkNPC( CChar& mChar, bool checkAI, bool doRestock, bool doPetOfflineCheck )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check NPC's status
//o-----------------------------------------------------------------------------------------------o
auto checkNPC( CChar& mChar, bool checkAI, bool doRestock, bool doPetOfflineCheck )->void{
// okay, this will only ever trigger after we check an npc... Question is:
// should we remove the time delay on the AI check as well? Just stick with AI/movement
// AI can never be faster than how often we check npcs
bool doAICheck = true;
std::vector<UI16> scriptTriggers = mChar.GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute){
if( toExecute->OnAISliver( &mChar ) == 1 ) {
// Script returned true or 1, don't do hard-coded AI check
doAICheck = false;
}
}
}
if( doAICheck && checkAI ){
CheckAI( mChar );
}
Movement->NpcMovement( mChar );
if( doRestock ){
restockNPC( mChar, false );
}
if( doPetOfflineCheck ) {
mChar.checkPetOfflineTimeout();
}
if( mChar.GetTimer( tNPC_SUMMONTIME ) ) {
if( (mChar.GetTimer( tNPC_SUMMONTIME ) <= cwmWorldState->GetUICurrentTime()) || cwmWorldState->GetOverflow() ) {
// Added Dec 20, 1999
// QUEST expire check - after an Escort quest is created a timer is set
// so that the NPC will be deleted and removed from the game if it hangs around
// too long without every having its quest accepted by a player so we have to remove
// its posting from the messageboard before icing the NPC
// Only need to remove the post if the NPC does not have a follow target set
if( mChar.GetQuestType() == QT_ESCORTQUEST && !ValidateObject( mChar.GetFTarg() ) ) {
MsgBoardQuestEscortRemovePost( &mChar );
mChar.Delete();
return;
}
if( mChar.GetNPCAiType() == AI_GUARD && mChar.IsAtWar() ) {
mChar.SetTimer( tNPC_SUMMONTIME, BuildTimeValue( 25 ) );
return;
}
Effects->PlaySound( &mChar, 0x01FE );
mChar.SetDead( true );
mChar.Delete();
return;
}
}
if( mChar.GetFleeAt() == 0 ){
mChar.SetFleeAt( cwmWorldState->ServerData()->CombatNPCBaseFleeAt() );
}
if( mChar.GetReattackAt() == 0 ){
mChar.SetReattackAt( cwmWorldState->ServerData()->CombatNPCBaseReattackAt() );
}
if( (mChar.GetNpcWander() != WT_FLEE) && (mChar.GetNpcWander() != WT_FROZEN) && ( mChar.GetHP() < mChar.GetMaxHP() * mChar.GetFleeAt() / 100 ) ) {
CChar *mTarget = mChar.GetTarg();
if( ValidateObject( mTarget ) && !mTarget->IsDead() && objInRange( &mChar, mTarget, DIST_SAMESCREEN )) {
mChar.SetOldNpcWander( mChar.GetNpcWander() );
mChar.SetNpcWander( WT_FLEE );
if( mChar.GetMounted() ){
mChar.SetTimer( tNPC_MOVETIME, BuildTimeValue( mChar.GetMountedFleeingSpeed() ) );
}
else{
mChar.SetTimer( tNPC_MOVETIME, BuildTimeValue( mChar.GetFleeingSpeed() ) );
}
}
}
else if( (mChar.GetNpcWander() == WT_FLEE ) && (mChar.GetHP() > mChar.GetMaxHP() * mChar.GetReattackAt() / 100)) {
mChar.SetNpcWander( mChar.GetOldNpcWander() );
if( mChar.GetMounted() ){
mChar.SetTimer( tNPC_MOVETIME, BuildTimeValue( mChar.GetMountedWalkingSpeed() ) );
}
else{
mChar.SetTimer( tNPC_MOVETIME, BuildTimeValue( mChar.GetWalkingSpeed() ) );
}
mChar.SetOldNpcWander( WT_NONE ); // so it won't save this at the wsc file
}
Combat->CombatLoop( nullptr, mChar );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void checkItem( CMapRegion *toCheck, bool checkItems, UI32 nextDecayItems, UI32 nextDecayItemsInHouses )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check item decay, spawn timers and boat movement in a given region
//o-----------------------------------------------------------------------------------------------o
auto checkItem( CMapRegion *toCheck, bool checkItems, UI32 nextDecayItems, UI32 nextDecayItemsInHouses, bool doWeather ){
auto regItems = toCheck->GetItemList();
auto collection = regItems->collection() ;
for (const auto &itemCheck : collection){
if( ValidateObject( itemCheck ) ){
if( checkItems ) {
if( itemCheck->isDecayable() && (itemCheck->GetCont() == nullptr) ) {
if( itemCheck->GetType() == IT_HOUSESIGN && itemCheck->GetTempVar( CITV_MORE ) > 0 ) {
// Don't decay signs that belong to houses
itemCheck->SetDecayable( false );
}
if( (itemCheck->GetDecayTime() <= cwmWorldState->GetUICurrentTime()) || cwmWorldState->GetOverflow() ) {
auto scriptTriggers = itemCheck->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute) {
if( toExecute->OnDecay( itemCheck ) == 0 ){
// if it exists and we don't want hard code, return
return;
}
}
}
// Check global script! Maybe there's another event there
auto toExecute = JSMapping->GetScript( static_cast<UI16>(0) );
if( toExecute ) {
if( toExecute->OnDecay( itemCheck ) == 0 ){
// if it exists and we don't want hard code, return
return;
}
}
if( DecayItem( (*itemCheck), nextDecayItems, nextDecayItemsInHouses ) ){
continue;
}
}
}
switch( itemCheck->GetType() ){
case IT_ITEMSPAWNER:
case IT_NPCSPAWNER:
case IT_SPAWNCONT:
case IT_LOCKEDSPAWNCONT:
case IT_UNLOCKABLESPAWNCONT:
case IT_AREASPAWNER:
case IT_ESCORTNPCSPAWNER:
case IT_PLANK:
{
if(( itemCheck->GetTempTimer() <= cwmWorldState->GetUICurrentTime() ) || cwmWorldState->GetOverflow() )
{
if( itemCheck->GetObjType() == OT_SPAWNER )
{
CSpawnItem *spawnItem = static_cast<CSpawnItem *>(itemCheck);
if( spawnItem->DoRespawn() )
{
continue;
}
spawnItem->SetTempTimer( BuildTimeValue( static_cast<R32>(RandomNum( spawnItem->GetInterval( 0 ) * 60, spawnItem->GetInterval( 1 ) * 60 ))));
}
else if( itemCheck->GetObjType() == OT_ITEM && itemCheck->GetType() == IT_PLANK)
{
// Automatically close the plank if it's still open, and still locked
auto plankStatus = itemCheck->GetTag( "plankLocked" );
if( plankStatus.m_IntValue == 1 )
{
switch( itemCheck->GetID() )
{
case 0x3E84: itemCheck->SetID( 0x3EE9 ); itemCheck->SetTempTimer( 0 ); break;
case 0x3ED5: itemCheck->SetID( 0x3EB1 ); itemCheck->SetTempTimer( 0 ); break;
case 0x3ED4: itemCheck->SetID( 0x3EB2 ); itemCheck->SetTempTimer( 0 ); break;
case 0x3E89: itemCheck->SetID( 0x3E8A ); itemCheck->SetTempTimer( 0 ); break;
}
}
}
else
{
itemCheck->SetType( IT_NOTYPE );
Console.warning( "Invalid spawner object detected; item type reverted to 0. All spawner objects have to be added using 'ADD SPAWNER # command." );
}
}
break;
}
case IT_SOUNDOBJECT:
if( itemCheck->GetTempVar( CITV_MOREY ) < 25 ) {
if( RandomNum( 1, 100 ) <= (SI32)itemCheck->GetTempVar( CITV_MOREZ ) ) {
for (auto &tSock : FindNearbyPlayers( itemCheck, static_cast<UI16>(itemCheck->GetTempVar( CITV_MOREY )) )) {
Effects->PlaySound( tSock, static_cast<UI16>(itemCheck->GetTempVar( CITV_MOREX )), false );
}
}
}
break;
default:
break;
}
}
if( itemCheck->CanBeObjType( OT_BOAT ) ) {
CBoatObj *mBoat = static_cast<CBoatObj *>(itemCheck);
SI08 boatMoveType = mBoat->GetMoveType();
if( ValidateObject( mBoat ) && boatMoveType && ( (mBoat->GetMoveTime() <= cwmWorldState->GetUICurrentTime()) || cwmWorldState->GetOverflow() ) ) {
if( boatMoveType != BOAT_ANCHORED ) {
switch( boatMoveType ) {
//case BOAT_ANCHORED:
//case BOAT_STOP:
case BOAT_FORWARD:
case BOAT_SLOWFORWARD:
case BOAT_ONEFORWARD:
MoveBoat( itemCheck->GetDir(), mBoat );
break;
case BOAT_BACKWARD:
case BOAT_SLOWBACKWARD:
case BOAT_ONEBACKWARD: {
UI08 dir = static_cast<UI08>( itemCheck->GetDir() + 4 );
if( dir > 7 )
dir %= 8;
MoveBoat( dir, mBoat );
break;
}
case BOAT_LEFT:
case BOAT_SLOWLEFT:
case BOAT_ONELEFT: {
UI08 dir = static_cast<UI08>( itemCheck->GetDir() - 2 );
dir %= 8;
MoveBoat( dir, mBoat );
break;
}
case BOAT_RIGHT:
case BOAT_SLOWRIGHT:
case BOAT_ONERIGHT: {
// Right / One Right
UI08 dir = static_cast<UI08>( itemCheck->GetDir() + 2 );
dir %= 8;
MoveBoat( dir, mBoat );
break;
}
case BOAT_FORWARDLEFT:
case BOAT_SLOWFORWARDLEFT:
case BOAT_ONEFORWARDLEFT: {
UI08 dir = static_cast<UI08>( itemCheck->GetDir() - 1 );
dir %= 8;
MoveBoat( dir, mBoat );
break;
}
case BOAT_FORWARDRIGHT:
case BOAT_SLOWFORWARDRIGHT:
case BOAT_ONEFORWARDRIGHT: {
UI08 dir = static_cast<UI08>( itemCheck->GetDir() + 1 );
dir %= 8;
MoveBoat( dir, mBoat );
break;
}
case BOAT_BACKWARDLEFT:
case BOAT_SLOWBACKWARDLEFT:
case BOAT_ONEBACKWARDLEFT: {
UI08 dir = static_cast<UI08>( itemCheck->GetDir() + 5 );
if( dir > 7 )
dir %= 8;
MoveBoat( dir, mBoat );
break;
}
case BOAT_BACKWARDRIGHT:
case BOAT_SLOWBACKWARDRIGHT:
case BOAT_ONEBACKWARDRIGHT: {
UI08 dir = static_cast<UI08>( itemCheck->GetDir() + 3 );
if( dir > 7 ){
dir %= 8;
}
MoveBoat( dir, mBoat );
break;
}
default:
break;
}
// One-step boat commands, so reset move type to 0 after the initial move
if( boatMoveType == BOAT_LEFT || boatMoveType == BOAT_RIGHT ) {
// Move 50% slower left/right than forward/back
mBoat->SetMoveTime( BuildTimeValue( (R32)cwmWorldState->ServerData()->CheckBoatSpeed() * 1.5 ) );
}
else if( boatMoveType >= BOAT_ONELEFT && boatMoveType <= BOAT_ONEBACKWARDRIGHT ) {
mBoat->SetMoveType( 0 );
// Set timer to restrict movement to normal boat speed if player spams command
mBoat->SetMoveTime( BuildTimeValue( (R32)cwmWorldState->ServerData()->CheckBoatSpeed() * 1.5 ) );
}
else if( boatMoveType >= BOAT_SLOWLEFT && boatMoveType <= BOAT_SLOWBACKWARDLEFT ) {
// Set timer to slowly move the boat forward
mBoat->SetMoveTime( BuildTimeValue( (R32)cwmWorldState->ServerData()->CheckBoatSpeed() * 2.0 ) );
}
else {
// Set timer to move the boat forward at normal speed
mBoat->SetMoveTime( BuildTimeValue( (R32)cwmWorldState->ServerData()->CheckBoatSpeed() ) );
}
}
}
}
// Do JS Weather for item
if( doWeather ) {
doLight( itemCheck, cwmWorldState->ServerData()->WorldLightCurrentLevel() );
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void CheckAutoTimers( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check automatic and timer controlled functions
//o-----------------------------------------------------------------------------------------------o
auto CWorldMain::CheckAutoTimers() ->void {
static UI32 nextCheckSpawnRegions = 0;
static UI32 nextCheckTownRegions = 0;
static UI32 nextCheckItems = 0;
static UI32 nextDecayItems = 0;
static UI32 nextDecayItemsInHouses = 0;
static UI32 nextSetNPCFlagTime = 0;
static UI32 accountFlush = 0;
bool doWeather = false;
bool doPetOfflineCheck = false;
CServerData *serverData = ServerData();
// modify this stuff to take into account more variables
if( (accountFlush <= GetUICurrentTime()) || GetOverflow() ) {
bool reallyOn = false;
// time to flush our account status!
MAPUSERNAMEID_ITERATOR I;
for( I = Accounts->begin(); I != Accounts->end(); ++I ) {
CAccountBlock& actbTemp = I->second;
if( actbTemp.wAccountIndex == AB_INVALID_ID){
continue;
}
if( actbTemp.wFlags.test( AB_FLAGS_ONLINE ) ) {
reallyOn = false; // to start with, there's no one really on
{
for (auto &tSock :Network->connClients){
CChar *tChar = tSock->CurrcharObj();
if( !ValidateObject( tChar ) ){
continue;
}
if( tChar->GetAccount().wAccountIndex == actbTemp.wAccountIndex ){
reallyOn = true;
}
}
}
if( !reallyOn ){
// no one's really on, let's set that
actbTemp.wFlags.reset( AB_FLAGS_ONLINE );
}
}
}
accountFlush = BuildTimeValue( (R32)serverData->AccountFlushTimer() );
}
//Network->On(); //<<<<<< WHAT the HECK, this is why you dont bury mutex locking
// PushConn and PopConn lock and unlock as well (yes, bad)
// But now we are doing recursive lock here
if( GetWorldSaveProgress() == SS_NOTSAVING ){
{
for (auto &tSock: Network->connClients) {
if( (tSock->IdleTimeout() != -1) && (UI32)tSock->IdleTimeout() <= GetUICurrentTime() ) {
CChar *tChar = tSock->CurrcharObj();
if( !ValidateObject( tChar ) ){
continue;
}
if( !tChar->IsGM() ) {
tSock->IdleTimeout( -1 );
tSock->sysmessage( 1246 );
Network->Disconnect( tSock );
}
}
else if( ( ( (UI32)( tSock->IdleTimeout() + 300 * 1000 ) <= GetUICurrentTime() && (UI32)( tSock->IdleTimeout()+200*1000) >= GetUICurrentTime() ) || GetOverflow() ) && !tSock->WasIdleWarned() ){
//is their idle time between 3 and 5 minutes, and they haven't been warned already?
CPIdleWarning warn( 0x07 );
tSock->Send( &warn );
tSock->WasIdleWarned( true );
}
if( serverData->KickOnAssistantSilence() ) {
if( !tSock->NegotiatedWithAssistant() && tSock->NegotiateTimeout() != -1 && static_cast<UI32>(tSock->NegotiateTimeout()) <= GetUICurrentTime() ) {
const CChar *tChar = tSock->CurrcharObj();
if( !ValidateObject( tChar ) ){
continue;
}
if( !tChar->IsGM() ) {
tSock->IdleTimeout( -1 );
tSock->sysmessage( 9047 ); // Failed to negotiate features with assistant tool. Disconnecting client...
Network->Disconnect( tSock );
}
}
}
// Check player's network traffic usage versus caps set in ini
if( tSock->LoginComplete() && (tSock->AcctNo() != 0) && tSock->GetTimer( tPC_TRAFFICWARDEN ) <= GetUICurrentTime() ) {
if( !ValidateObject( tSock->CurrcharObj() ) || tSock->CurrcharObj()->IsGM() ){
continue;
}
bool tempTimeBan = false;
if( tSock->BytesReceived() > serverData->MaxClientBytesIn() ) {
// Player has exceeded the cap! Send one warning - next time kick player
tSock->sysmessage( Dictionary->GetEntry( 9082, tSock->Language() )); // Excessive data usage detected! Sending too many requests to the server in a short amount of time will get you banned.
tSock->BytesReceivedWarning( tSock->BytesReceivedWarning() + 1 );
if( tSock->BytesReceivedWarning() > 2 ) {
// If it happens 3 times in the same session, give player a temporary ban
tempTimeBan = true;
}
}
if( tSock->BytesSent() > serverData->MaxClientBytesOut() ) {
// This is data sent from server, so should be more lenient before a kick (though could still be initiated by player somehow)
tSock->sysmessage( Dictionary->GetEntry( 9082, tSock->Language() )); // Excessive data usage detected! Sending too many requests to the server in a short amount of time will get you banned.
tSock->BytesSentWarning( tSock->BytesSentWarning() + 1 );
if( tSock->BytesSentWarning() > 2 ) {
// If it happens 3 times or more in the same session, give player a temporary ban
tempTimeBan = true;
}
}
if( tempTimeBan ) {
// Give player a 30 minute temp ban
CAccountBlock& myAccount = Accounts->GetAccountByID( tSock->GetAccount().wAccountIndex );
myAccount.wFlags.set( AB_FLAGS_BANNED, true );
myAccount.wTimeBan = GetMinutesSinceEpoch() + serverData->NetTrafficTimeban();
Network->Disconnect( tSock );
continue;
}
// Reset amount of bytes received and sent, and restart timer
tSock->BytesReceived( 0 );
tSock->BytesSent( 0 );
tSock->SetTimer( tPC_TRAFFICWARDEN, BuildTimeValue( static_cast<R32>( 10 )) );
}
}
}
}
else if( GetWorldSaveProgress() == SS_JUSTSAVED ){
// if we've JUST saved, do NOT kick anyone off (due to a possibly really long save), but reset any offending players to 60 seconds to go before being kicked off
{
for (auto &wsSocket : Network->connClients) {
if( wsSocket ){
if( (UI32)wsSocket->IdleTimeout() < GetUICurrentTime() ){
wsSocket->IdleTimeout( BuildTimeValue( 60.0F ) );
wsSocket->WasIdleWarned( true );//don't give them the message if they only have 60s
}
if( cwmWorldState->ServerData()->KickOnAssistantSilence() ){
if( !wsSocket->NegotiatedWithAssistant() && static_cast<UI32>(wsSocket->NegotiateTimeout()) < GetUICurrentTime() ){
wsSocket->NegotiateTimeout( BuildTimeValue( 60.0F ) );
}
}
}
}
}
SetWorldSaveProgress( SS_NOTSAVING );
}
if( (nextCheckTownRegions <= GetUICurrentTime()) || GetOverflow() ) {
std::for_each(cwmWorldState->townRegions.begin(), cwmWorldState->townRegions.end(),[]( std::pair<const std::uint16_t,CTownRegion*> &town) {
if (town.second != nullptr){
town.second->PeriodicCheck();
}
});
nextCheckTownRegions = BuildTimeValue( 10 ); // do checks every 10 seconds or so, rather than every single time
JailSys->PeriodicCheck();
}
if( (nextCheckSpawnRegions <= GetUICurrentTime()) && (serverData->CheckSpawnRegionSpeed() != -1) ) {
//Regionspawns
UI32 itemsSpawned = 0;
UI32 npcsSpawned = 0;
UI32 totalItemsSpawned = 0;
UI32 totalNpcsSpawned = 0;
UI32 maxItemsSpawned = 0;
UI32 maxNpcsSpawned = 0;
for (auto &[regnum,spawnReg]:cwmWorldState->spawnRegions ){
if( spawnReg ){
if( spawnReg->GetNextTime() <= GetUICurrentTime() ){
spawnReg->doRegionSpawn( itemsSpawned, npcsSpawned );
}
// Grab some info from the spawn region anyway, even if it's not time to spawn
totalItemsSpawned += static_cast<UI32>(spawnReg->GetCurrentItemAmt());
totalNpcsSpawned += static_cast<UI32>(spawnReg->GetCurrentCharAmt());
maxItemsSpawned += static_cast<UI32>(spawnReg->GetMaxItemSpawn());
maxNpcsSpawned += static_cast<UI32>(spawnReg->GetMaxCharSpawn());
}
}
// Adaptive spawn region check timer. The closer spawn regions as a whole are to being at their defined max capacity,
// the less frequently UOX3 will check spawn regions again. Similarly, the more room there is to spawn additional
// stuff, the more frequently UOX3 will check spawn regions
auto checkSpawnRegionSpeed = static_cast<R32>(serverData->CheckSpawnRegionSpeed());
UI16 itemSpawnCompletionRatio = ( maxItemsSpawned > 0 ? (( totalItemsSpawned * 100.0 ) / maxItemsSpawned ) : 100 );
UI16 npcSpawnCompletionRatio = ( maxNpcsSpawned > 0 ? (( totalNpcsSpawned * 100.0 ) / maxNpcsSpawned ) : 100 );
SI32 avgCompletionRatio = ( itemSpawnCompletionRatio + npcSpawnCompletionRatio ) / 2;
if( avgCompletionRatio == 100 ){
checkSpawnRegionSpeed *= 3;
}
else if( avgCompletionRatio >= 90 ){
checkSpawnRegionSpeed *= 2;
}
else if( avgCompletionRatio >= 75 ){
checkSpawnRegionSpeed *= 1.5;
}
else if( avgCompletionRatio >= 50 ){
checkSpawnRegionSpeed *= 1.25;
}
nextCheckSpawnRegions = BuildTimeValue( checkSpawnRegionSpeed );//Don't check them TOO often (Keep down the lag)
}
HTMLTemplates->Poll( ETT_ALLTEMPLATES );
const UI32 saveinterval = serverData->ServerSavesTimerStatus();
if( saveinterval != 0 ){
time_t oldTime = GetOldTime();
if( !GetAutoSaved() ){
SetAutoSaved( true );
time(&oldTime);
SetOldTime( static_cast<UI32>(oldTime) );
}
time_t newTime = GetNewTime();
time(&newTime);
SetNewTime( static_cast<UI32>(newTime) );
if( difftime( GetNewTime(), GetOldTime() ) >= saveinterval ) {
// Added Dec 20, 1999
// After an automatic world save occurs, lets check to see if
// anyone is online (clients connected). If nobody is connected
// Lets do some maintenance on the bulletin boards.
if( !GetPlayersOnline() && (GetWorldSaveProgress() != SS_SAVING )){
Console << "No players currently online. Starting bulletin board maintenance" << myendl;
Console.log( "Bulletin Board Maintenance routine running (AUTO)", "server.log" );
MsgBoardMaintenance();
}
SetAutoSaved( false );
#if PLATFORM == WINDOWS
SetConsoleCtrlHandler( exit_handler, true );
#endif
isWorldSaving = true;
SaveNewWorld( false );
isWorldSaving = false;
#if PLATFORM == WINDOWS
SetConsoleCtrlHandler( exit_handler, false );
#endif
}
}
/*time_t oldIPTime = GetOldIPTime();
if( !GetIPUpdated() )
{
SetIPUpdated( true );
time(&oldIPTime);
SetOldIPTime( static_cast<UI32>(oldIPTime) );
}
time_t newIPTime = GetNewIPTime();
time(&newIPTime);
SetNewIPTime( static_cast<UI32>(newIPTime) );
if( difftime( GetNewIPTime(), GetOldIPTime() ) >= 120 )
{
ServerData()->RefreshIPs();
SetIPUpdated( false );
}*/
//Time functions
if( (GetUOTickCount() <= GetUICurrentTime()) || ( GetOverflow() ) ) {
UI08 oldHour = serverData->ServerTimeHours();
if( serverData->incMinute() ){
Weather->NewDay();
}
if( oldHour != serverData->ServerTimeHours() ){
Weather->NewHour();
}
SetUOTickCount( BuildTimeValue( serverData->ServerSecondsPerUOMinute() ) );
}
if( (GetTimer( tWORLD_LIGHTTIME ) <= GetUICurrentTime()) || GetOverflow() ) {
doWorldLight(); //Changes lighting, if it is currently time to.
Weather->DoStuff(); // updates the weather types
SetTimer( tWORLD_LIGHTTIME, serverData->BuildSystemTimeValue( tSERVER_WEATHER ) );
doWeather = true;
}
if( (GetTimer( tWORLD_PETOFFLINECHECK ) <= GetUICurrentTime()) || GetOverflow() ){
SetTimer( tWORLD_PETOFFLINECHECK, serverData->BuildSystemTimeValue( tSERVER_PETOFFLINECHECK ) );
doPetOfflineCheck = true;
}
bool checkFieldEffects = false;
if( (GetTimer( tWORLD_NEXTFIELDEFFECT ) <= GetUICurrentTime() )|| GetOverflow() ){
checkFieldEffects = true;
SetTimer( tWORLD_NEXTFIELDEFFECT, BuildTimeValue( 0.5f ) );
}
std::set< CMapRegion * > regionList;
{
for (auto &iSock : Network->connClients){
if( iSock){
CChar *mChar = iSock->CurrcharObj();
if( !ValidateObject( mChar ) ){
continue;
}
UI08 worldNumber = mChar->WorldNumber();
if( mChar->GetAccount().wAccountIndex == iSock->AcctNo() && mChar->GetAccount().dwInGame == mChar->GetSerial() ){
genericCheck( iSock, (*mChar), checkFieldEffects, doWeather );
checkPC( iSock, (*mChar) );
SI16 xOffset = MapRegion->GetGridX( mChar->GetX() );
SI16 yOffset = MapRegion->GetGridY( mChar->GetY() );
// Restrict the amount of active regions based on how far player is from the border
// to the next one. This reduces active regions around a player from always 9 to
// varying between 3 to 6. Only regions on yOffset are considered, because the xOffset
// ones are too narrow
auto yOffsetUnrounded = static_cast<R32>(mChar->GetY()) / static_cast<R32>(MapRowSize);
SI08 counter2Start = 0, counter2End = 0;
if( yOffsetUnrounded < yOffset + 0.25 ){
counter2Start = -1;
counter2End = 0;
}
else if( yOffsetUnrounded > yOffset + 0.75 ){
counter2Start = 0;
counter2End = 1;
}
for( SI08 counter = -1; counter <= 1; ++counter ) {
// Check 3 x colums
for( SI08 ctr2 = counter2Start; ctr2 <= counter2End; ++ctr2 ){
// Check variable y colums
auto tC = MapRegion->GetMapRegion( xOffset + counter, yOffset + ctr2, worldNumber );
if( tC) {
regionList.insert( tC );
}
}
}
}
}
}
}
// Reduce some lag checking these timers constantly in the loop
bool setNPCFlags = false, checkItems = false, checkAI = false, doRestock = false;
if( (nextSetNPCFlagTime <= GetUICurrentTime()) || GetOverflow() ) {
nextSetNPCFlagTime = serverData->BuildSystemTimeValue( tSERVER_NPCFLAGUPDATETIMER ); // Slow down lag "needed" for setting flags, they are set often enough ;-)
setNPCFlags = true;
}
if( (nextCheckItems <= GetUICurrentTime() )|| GetOverflow() ){
nextCheckItems = BuildTimeValue( static_cast<R32>(serverData->CheckItemsSpeed()) );
nextDecayItems = serverData->BuildSystemTimeValue( tSERVER_DECAY );
nextDecayItemsInHouses = serverData->BuildSystemTimeValue( tSERVER_DECAYINHOUSE );
checkItems = true;
}
if( (GetTimer( tWORLD_NEXTNPCAI ) <= GetUICurrentTime()) || GetOverflow() ){
SetTimer( tWORLD_NEXTNPCAI, BuildTimeValue( (R32)serverData->CheckNpcAISpeed() ) );
checkAI = true;
}
if( (GetTimer( tWORLD_SHOPRESTOCK ) <= GetUICurrentTime()) || GetOverflow() ){
SetTimer( tWORLD_SHOPRESTOCK, serverData->BuildSystemTimeValue( tSERVER_SHOPSPAWN ) );
doRestock = true;
}
bool allowAwakeNPCs = cwmWorldState->ServerData()->AllowAwakeNPCs();
for (auto &toCheck : regionList){
auto regChars = toCheck->GetCharList();
auto collection = regChars->collection();
for (const auto &charCheck:collection){
if( ValidateObject( charCheck ) ){
if( charCheck->IsNpc() ) {
if( !charCheck->IsAwake() || !allowAwakeNPCs ){
// Only perform these checks on NPCs that are not permanently awake
if( !genericCheck( nullptr, (*charCheck), checkFieldEffects, doWeather ) ) {
if( setNPCFlags ){
UpdateFlag( charCheck ); // only set flag on npcs every 60 seconds (save a little extra lag)
}
checkNPC( (*charCheck), checkAI, doRestock, doPetOfflineCheck );
}
}
}
else if( charCheck->GetTimer( tPC_LOGOUT ) ) {
CAccountBlock& actbTemp = charCheck->GetAccount();
if( actbTemp.wAccountIndex != AB_INVALID_ID ) {
SERIAL oaiw = actbTemp.dwInGame;
if( oaiw == INVALIDSERIAL ) {
charCheck->SetTimer( tPC_LOGOUT, 0 );
charCheck->Update();
}
else if( (oaiw == charCheck->GetSerial()) && ( (charCheck->GetTimer( tPC_LOGOUT ) <= GetUICurrentTime()) || GetOverflow() ) ) {
actbTemp.dwInGame = INVALIDSERIAL;
charCheck->SetTimer( tPC_LOGOUT, 0 );
charCheck->Update();
charCheck->Teleport();
}
}
}
}
}
checkItem( toCheck, checkItems, nextDecayItems, nextDecayItemsInHouses, doWeather );
}
// Check NPCs marked as always active, regardless of whether their region is "awake"
if( allowAwakeNPCs ) {
auto alwaysAwakeChars = Npcs->GetAlwaysAwakeNPCs();
std::vector<CChar*> toRemove ;
for (const auto &charCheck : alwaysAwakeChars->collection()){
if( ValidateObject( charCheck ) && !charCheck->isFree() && charCheck->IsNpc() ) {
if( !genericCheck( nullptr, (*charCheck), checkFieldEffects, doWeather ) ) {
if( setNPCFlags ){
UpdateFlag( charCheck ); // only set flag on npcs every 60 seconds (save a little extra lag)
}
checkNPC( (*charCheck), checkAI, doRestock, doPetOfflineCheck );
}
}
else {
toRemove.push_back(charCheck);
}
}
std::for_each(toRemove.begin(), toRemove.end(), [&alwaysAwakeChars](CChar *character){
alwaysAwakeChars->Remove(character);
});
toRemove.clear() ;
}
Effects->checktempeffects();
SpeechSys->Poll();
// Implement RefreshItem() / statwindow() queue here
std::for_each(cwmWorldState->refreshQueue.begin(),cwmWorldState->refreshQueue.end(),[](std::pair<CBaseObject*,std::uint32_t> entry){
if (ValidateObject(entry.first)){
if( entry.first->CanBeObjType( OT_CHAR ) ) {
auto uChar = static_cast<CChar *>(entry.first);
if( uChar->GetUpdate( UT_HITPOINTS ) ){
updateStats( entry.first, 0 );
}
if( uChar->GetUpdate( UT_STAMINA ) ){
updateStats( entry.first, 1 );
}
if( uChar->GetUpdate( UT_MANA ) ) {
updateStats( entry.first, 2 );
}
if( uChar->GetUpdate( UT_LOCATION ) ){
uChar->Teleport();
}
else if( uChar->GetUpdate( UT_HIDE ) ) {
uChar->ClearUpdate();
if( uChar->GetVisible() != VT_VISIBLE ){
uChar->RemoveFromSight();
}
uChar->Update( nullptr, false );
}
else if( uChar->GetUpdate( UT_UPDATE ) ){
uChar->Update();
}
else if( uChar->GetUpdate( UT_STATWINDOW ) ) {
CSocket *uSock = uChar->GetSocket();
if( uSock){
uSock->statwindow( uChar );
}
}
uChar->ClearUpdate();
}
else{
entry.first->Update();
}
}
});
cwmWorldState->refreshQueue.clear();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void InitClasses( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Initialize UOX classes
//o-----------------------------------------------------------------------------------------------o
auto InitClasses()->void {
cwmWorldState->ClassesInitialized( true );
JSEngine = &aJSEngine ;
JSMapping = &aJSMapping ;
Effects = &aEffects;
Commands = &aCommands ;
Combat = &aCombat ;
Items = &aItems ;
Map = &aMap ;
Npcs = &aNpcs ;
Skills = &aSkills ;
Weight = &aWeight ;
JailSys = &aJailSys ;
Network = &aNetwork;
Magic = &aMagic ;
Races = &aRaces ;
Weather = &aWeather ;
Movement = &aMovement ;
GuildSys = &aGuildSys ;
WhoList = &aWhoList ;
OffList = &aOffList ;
Books = &aBooks ;
GMQueue = &aGMQueue ;
Dictionary = &aDictionary ;
Accounts = &aAccounts ;
MapRegion = &aMapRegion ;
SpeechSys = &aSpeechSys ;
CounselorQueue = &aCounselorQueue ;
HTMLTemplates = &aHTMLTemplates ;
FileLookup = &aFileLookup ;
aJSEngine.startup() ;
aFileLookup.startup() ;
aCommands.startup() ;
aSpeechSys.startup() ;
// Need to do map
aNetwork.startup() ;
aMap.load() ;
JSMapping->ResetDefaults();
JSMapping->GetEnvokeByID()->Parse();
JSMapping->GetEnvokeByType()->Parse();
aMapRegion.startup() ;
aAccounts.SetPath(cwmWorldState->ServerData()->Directory(CSDDP_ACCOUNTS));
}
auto findNearbyObjects( SI16 x, SI16 y, UI08 worldNumber, UI16 instanceID, UI16 distance )->std::vector< CBaseObject* > ;
auto inMulti( SI16 x, SI16 y, SI08 z, CMultiObj *m )->bool;
//o-----------------------------------------------------------------------------------------------o
//| Function - bool FindMultiFunctor( CBaseObject *a, UI32 &b, void *extraData )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Looks for a multi at object's location and assigns any multi found to object
//o-----------------------------------------------------------------------------------------------o
auto FindMultiFunctor( CBaseObject *a, UI32 &b, void *extraData )->bool {
if( ValidateObject( a ) ) {
if( a->CanBeObjType( OT_MULTI )) {
auto aMulti = static_cast<CMultiObj *>(a);
for (auto &objToCheck : findNearbyObjects( aMulti->GetX(), aMulti->GetY(), aMulti->WorldNumber(), aMulti->GetInstanceID(), 20 )){
if( inMulti( objToCheck->GetX(), objToCheck->GetY(), objToCheck->GetZ(), aMulti )){
objToCheck->SetMulti( aMulti );
}
else if( (objToCheck->GetObjType() == OT_ITEM)
&& ( ( (objToCheck->GetID() >= 0x0b95) && (objToCheck->GetID() <= 0x0c0e )) || (objToCheck->GetID() == 0x1f28) || (objToCheck->GetID() == 0x1f29) ) ) {
// Reunite house signs with their multis
SERIAL houseSerial = static_cast<CItem *>( objToCheck )->GetTempVar( CITV_MORE );
CMultiObj *multi = calcMultiFromSer( houseSerial );
if( ValidateObject( multi ) ) {
objToCheck->SetMulti( multi );
}
}
else {
// No other multi found where item is, safe to set item's multi to INVALIDSERIAL
if( findMulti( objToCheck ) == nullptr ){
objToCheck->SetMulti( INVALIDSERIAL );
}
}
}
}
}
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void InitMultis( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Initialize Multis
//o-----------------------------------------------------------------------------------------------o
auto InitMultis() ->void {
Console << "Initializing multis ";
UI32 b = 0;
ObjectFactory::getSingleton().IterateOver( OT_MULTI, b, nullptr, &FindMultiFunctor );
Console.PrintDone();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void DisplayBanner( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Display some information at the end of UOX startup
//o-----------------------------------------------------------------------------------------------o
auto DisplayBanner() ->void{
Console.PrintSectionBegin();
//auto idName = oldstrutil::format( "%s v%s(%s) [%s]\n| Compiled by %s\n| Programmed by: %s", CVersionClass::GetProductName().c_str(), CVersionClass::GetVersion().c_str(), CVersionClass::GetBuild().c_str(), OS_STR, CVersionClass::GetName().c_str(), CVersionClass::GetProgrammers().c_str() );
Console.TurnYellow();
Console << "Compiled on ";
Console.TurnNormal();
Console << __DATE__ << " (" << __TIME__ << ")" << myendl;
Console.TurnYellow();
Console << "Compiled by ";
Console.TurnNormal();
Console << CVersionClass::GetName() << myendl;
Console.TurnYellow();
Console << "Contact: ";
Console.TurnNormal();
Console << CVersionClass::GetEmail() << myendl;
Console.PrintSectionBegin();
saveOnShutdown = true ;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void Shutdown( SI32 retCode )
//| Date - Oct. 09, 1999
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handled deleting / free() ing of pointers as neccessary
//| as well as closing open file handles to avoid file file corruption.
//| Exits with proper error code.
//o-----------------------------------------------------------------------------------------------o
auto Shutdown( SI32 retCode )->void {
Console.PrintSectionBegin();
Console << "Beginning UOX final shut down sequence..." << myendl;
if (retCode && saveOnShutdown) {
//they want us to save, there has been an error, we have loaded the world, and WorldState is a valid pointer.
#if PLATFORM == WINDOWS
SetConsoleCtrlHandler( exit_handler, true );
#endif
isWorldSaving = true;
do {
cwmWorldState->SaveNewWorld( true );
} while( cwmWorldState->GetWorldSaveProgress() == SS_SAVING );
isWorldSaving = false;
#if PLATFORM == WINDOWS
SetConsoleCtrlHandler( exit_handler, false );
#endif
}
if( cwmWorldState && cwmWorldState->ClassesInitialized() ) {
if( HTMLTemplates ) {
Console << "HTMLTemplates object detected. Writing Offline HTML Now...";
HTMLTemplates->Poll( ETT_OFFLINE );
Console.PrintDone();
}
else{
Console << "HTMLTemplates object not found." << myendl;
}
Console << "Cleaning up item and character memory... ";
ObjectFactory::getSingleton().GarbageCollect();
Console.PrintDone();
Console << "Destroying class objects and pointers... ";
// delete any objects that were created (delete takes care of nullptr check =)
UnloadSpawnRegions();
UnloadRegions();
Console.PrintDone();
}
//Lets wait for console thread to quit here
if( !retCode ){
cons.join();
}
// don't leave file pointers open, could lead to file corruption
Console.PrintSectionBegin();
Console.TurnGreen();
Console << "Server shutdown complete!" << myendl;
Console << "Thank you for supporting " << CVersionClass::GetName() << myendl;
Console.TurnNormal();
Console.PrintSectionBegin();
// dispay what error code we had
// don't report errorlevel for no errors, this is confusing ppl
if( retCode ) {
Console.TurnRed();
Console << "Exiting UOX with errorlevel " << retCode << myendl;
Console.TurnNormal();
#if PLATFORM == WINDOWS
Console << "Press Return to exit " << myendl;
std::string throwAway;
std::getline(std::cin, throwAway);
#endif
}
else {
Console.TurnGreen();
Console << "Exiting UOX with no errors..." << myendl;
Console.TurnNormal();
}
Console.PrintSectionBegin();
exit(retCode);
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void advanceObj( CChar *applyTo, UI16 advObj, bool multiUse )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handle advancement objects (stat / skill gates)
//o-----------------------------------------------------------------------------------------------o
auto advanceObj( CChar *applyTo, UI16 advObj, bool multiUse ) ->void {
if( (applyTo->GetAdvObj() == 0 )|| multiUse ) {
Effects->PlayStaticAnimation( applyTo, 0x373A, 0, 15);
Effects->PlaySound( applyTo, 0x01E9 );
applyTo->SetAdvObj( advObj );
auto sect = "ADVANCEMENT "s + oldstrutil::number( advObj );
sect = oldstrutil::trim( oldstrutil::removeTrailing( sect, "//" ));
auto Advancement = FileLookup->FindEntry( sect, advance_def );
if( Advancement == nullptr ) {
Console << "ADVANCEMENT OBJECT: Script section not found, Aborting" << myendl;
applyTo->SetAdvObj( 0 );
return;
}
CItem *retItem = nullptr;
auto hairobject = applyTo->GetItemAtLayer( IL_HAIR );
auto beardobject = applyTo->GetItemAtLayer( IL_FACIALHAIR );
DFNTAGS tag = DFNTAG_COUNTOFTAGS;
std::string cdata;
SI32 ndata = -1, odata = -1;
UI08 skillToSet = 0;
for (const auto &sec:Advancement->collection2()){
tag = sec->tag;
cdata = sec->cdata ;
ndata = sec->ndata ;
odata = sec->odata ;
switch( tag ) {
case DFNTAG_ALCHEMY: skillToSet = ALCHEMY; break;
case DFNTAG_ANATOMY: skillToSet = ANATOMY; break;
case DFNTAG_ANIMALLORE: skillToSet = ANIMALLORE; break;
case DFNTAG_ARMSLORE: skillToSet = ARMSLORE; break;
case DFNTAG_ARCHERY: skillToSet = ARCHERY; break;
case DFNTAG_ADVOBJ: applyTo->SetAdvObj( static_cast<UI16>(ndata) ); break;
case DFNTAG_BEGGING: skillToSet = BEGGING; break;
case DFNTAG_BLACKSMITHING: skillToSet = BLACKSMITHING; break;
case DFNTAG_BOWCRAFT: skillToSet = BOWCRAFT; break;
case DFNTAG_BUSHIDO: skillToSet = BUSHIDO; break;
case DFNTAG_CAMPING: skillToSet = CAMPING; break;
case DFNTAG_CARPENTRY: skillToSet = CARPENTRY; break;
case DFNTAG_CARTOGRAPHY: skillToSet = CARTOGRAPHY; break;
case DFNTAG_CHIVALRY: skillToSet = CHIVALRY; break;
case DFNTAG_COOKING: skillToSet = COOKING; break;
case DFNTAG_DEX: applyTo->SetDexterity( static_cast<SI16>(RandomNum( ndata, odata )) ); break;
case DFNTAG_DETECTINGHIDDEN: skillToSet = DETECTINGHIDDEN; break;
case DFNTAG_DYEHAIR:
if( ValidateObject( hairobject ) )
hairobject->SetColour( static_cast<UI16>(ndata) );
break;
case DFNTAG_DYEBEARD:
if( ValidateObject( beardobject ) )
beardobject->SetColour( static_cast<UI16>(ndata) );
break;
case DFNTAG_ENTICEMENT: skillToSet = ENTICEMENT; break;
case DFNTAG_EVALUATINGINTEL: skillToSet = EVALUATINGINTEL; break;
case DFNTAG_EQUIPITEM:
retItem = Items->CreateBaseScriptItem( nullptr, cdata, applyTo->WorldNumber(), 1 );
if( retItem ) {
if( !retItem->SetCont( applyTo ) ) {
retItem->SetCont( applyTo->GetPackItem() );
retItem->PlaceInPack();
}
}
break;
case DFNTAG_FAME: applyTo->SetFame( static_cast<SI16>(ndata) ); break;
case DFNTAG_FENCING: skillToSet = FENCING; break;
case DFNTAG_FISHING: skillToSet = FISHING; break;
case DFNTAG_FOCUS: skillToSet = FOCUS; break;
case DFNTAG_FORENSICS: skillToSet = FORENSICS; break;
case DFNTAG_HEALING: skillToSet = HEALING; break;
case DFNTAG_HERDING: skillToSet = HERDING; break;
case DFNTAG_HIDING: skillToSet = HIDING; break;
case DFNTAG_IMBUING: skillToSet = IMBUING; break;
case DFNTAG_INTELLIGENCE: applyTo->SetIntelligence( static_cast<SI16>(RandomNum( ndata, odata )) ); break;
case DFNTAG_ITEMID: skillToSet = ITEMID; break;
case DFNTAG_INSCRIPTION: skillToSet = INSCRIPTION; break;
case DFNTAG_KARMA: applyTo->SetKarma( static_cast<SI16>(ndata) ); break;
case DFNTAG_KILLHAIR:
retItem = applyTo->GetItemAtLayer( IL_HAIR );
if( ValidateObject( retItem ) ){
retItem->Delete();
}
break;
case DFNTAG_KILLBEARD:
retItem = applyTo->GetItemAtLayer( IL_FACIALHAIR );
if( ValidateObject( retItem ) ){
retItem->Delete();
}
break;
case DFNTAG_KILLPACK:
retItem = applyTo->GetItemAtLayer( IL_PACKITEM );
if( ValidateObject( retItem ) ){
retItem->Delete();
}
break;
case DFNTAG_LOCKPICKING: skillToSet = LOCKPICKING; break;
case DFNTAG_LUMBERJACKING: skillToSet = LUMBERJACKING; break;
case DFNTAG_MAGERY: skillToSet = MAGERY; break;
case DFNTAG_MAGICRESISTANCE: skillToSet = MAGICRESISTANCE; break;
case DFNTAG_MACEFIGHTING: skillToSet = MACEFIGHTING; break;
case DFNTAG_MEDITATION: skillToSet = MEDITATION; break;
case DFNTAG_MINING: skillToSet = MINING; break;
case DFNTAG_MUSICIANSHIP: skillToSet = MUSICIANSHIP; break;
case DFNTAG_MYSTICISM: skillToSet = MYSTICISM; break;
case DFNTAG_NECROMANCY: skillToSet = NECROMANCY; break;
case DFNTAG_NINJITSU: skillToSet = NINJITSU; break;
case DFNTAG_PARRYING: skillToSet = PARRYING; break;
case DFNTAG_PEACEMAKING: skillToSet = PEACEMAKING; break;
case DFNTAG_POISONING: skillToSet = POISONING; break;
case DFNTAG_PROVOCATION: skillToSet = PROVOCATION; break;
case DFNTAG_POLY: applyTo->SetID( static_cast<UI16>(ndata) ); break;
case DFNTAG_PACKITEM:
if( ValidateObject( applyTo->GetPackItem() ) ) {
auto csecs = oldstrutil::sections( cdata, "," );
if( !cdata.empty() ) {
if( csecs.size() > 1 ) {
retItem = Items->CreateScriptItem( nullptr, applyTo, oldstrutil::trim(oldstrutil::removeTrailing( csecs[0],"//") ), oldstrutil::value<std::uint16_t>( oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" ))), OT_ITEM, true );
}
else {
retItem = Items->CreateScriptItem( nullptr, applyTo, cdata, 1, OT_ITEM, true );
}
}
}
else{
Console << "Warning: Bad NPC Script with problem no backpack for packitem" << myendl;
}
break;
case DFNTAG_REMOVETRAP: skillToSet = REMOVETRAP; break;
case DFNTAG_STRENGTH: applyTo->SetStrength( static_cast<SI16>(RandomNum( ndata, odata )) ); break;
case DFNTAG_SKILL: applyTo->SetBaseSkill( static_cast<UI16>(odata), static_cast<UI08>(ndata) ); break;
case DFNTAG_SKIN: applyTo->SetSkin( static_cast<UI16>(std::stoul(cdata, nullptr, 0) )); break;
case DFNTAG_SNOOPING: skillToSet = SNOOPING; break;
case DFNTAG_SPELLWEAVING: skillToSet = SPELLWEAVING; break;
case DFNTAG_SPIRITSPEAK: skillToSet = SPIRITSPEAK; break;
case DFNTAG_STEALING: skillToSet = STEALING; break;
case DFNTAG_STEALTH: skillToSet = STEALTH; break;
case DFNTAG_SWORDSMANSHIP: skillToSet = SWORDSMANSHIP; break;
case DFNTAG_TACTICS: skillToSet = TACTICS; break;
case DFNTAG_TAILORING: skillToSet = TAILORING; break;
case DFNTAG_TAMING: skillToSet = TAMING; break;
case DFNTAG_TASTEID: skillToSet = TASTEID; break;
case DFNTAG_THROWING: skillToSet = THROWING; break;
case DFNTAG_TINKERING: skillToSet = TINKERING; break;
case DFNTAG_TRACKING: skillToSet = TRACKING; break;
case DFNTAG_VETERINARY: skillToSet = VETERINARY; break;
case DFNTAG_WRESTLING: skillToSet = WRESTLING; break;
default: Console << "Unknown tag in advanceObj(): " << (SI32)tag << myendl; break;
}
if( skillToSet > 0 ) {
applyTo->SetBaseSkill( static_cast<UI16>(RandomNum( ndata, odata )), skillToSet );
skillToSet = 0; // reset for next time through
}
}
applyTo->Teleport();
}
else {
auto sock = applyTo->GetSocket();
if( sock )
sock->sysmessage( 1366 );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - UI32 getclock( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Return CPU time used, Emulates clock()
//o-----------------------------------------------------------------------------------------------o
auto getclock()->std::uint32_t {
auto now = std::chrono::system_clock::now();
return static_cast<std::uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now-current).count());
}
//o-----------------------------------------------------------------------------------------------o
//| Function - R32 roundNumber( R32 toRound )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - rounds a number up or down depending on it's value
//o-----------------------------------------------------------------------------------------------o
auto roundNumber( R32 toRound)->R32{
R32 flVal = floor( toRound );
if( flVal < floor( toRound + 0.5 ) ){
return ceil( toRound );
}
return flVal;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool isNumber( const std::string& str )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns true if string is a number, false if not
//o-----------------------------------------------------------------------------------------------o
auto isNumber( const std::string& str )->bool {
return str.find_first_not_of( "0123456789" ) == std::string::npos;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void doLight( CSocket *s, UI08 level )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Sets light level for player and applies relevant effects
//o-----------------------------------------------------------------------------------------------o
auto doLight( CSocket *s, UI08 level ) ->void {
if(s){
auto mChar = s->CurrcharObj();
CPLightLevel toSend( level );
if( (Races->Affect( mChar->GetRace(), LIGHT )) && mChar->GetWeathDamage( LIGHT ) == 0 ){
mChar->SetWeathDamage( static_cast<UI32>(BuildTimeValue( static_cast<R32>(Races->Secs( mChar->GetRace(), LIGHT )) )), LIGHT );
}
if( mChar->GetFixedLight() != 255 ) {
toSend.Level( mChar->GetFixedLight() );
s->Send( &toSend );
Weather->DoPlayerStuff( s, mChar );
return;
}
auto curRegion = mChar->GetRegion();
auto wSys = Weather->Weather( curRegion->GetWeather() );
auto toShow = cwmWorldState->ServerData()->WorldLightCurrentLevel();
auto dunLevel = cwmWorldState->ServerData()->DungeonLightLevel();
// we have a valid weather system
if( wSys ){
const R32 lightMin = wSys->LightMin();
const R32 lightMax = wSys->LightMax();
if( lightMin < 300 && lightMax < 300 ) {
R32 i = wSys->CurrentLight();
if( Races->VisLevel( mChar->GetRace() ) > i ){
toShow = 0;
}
else{
toShow = static_cast<LIGHTLEVEL>(roundNumber( i - Races->VisLevel( mChar->GetRace() )));
}
toSend.Level( toShow );
}
else{
toSend.Level( level );
}
}
else {
if( mChar->inDungeon() ) {
if( Races->VisLevel( mChar->GetRace() ) > dunLevel ){
toShow = 0;
}
else {
toShow = static_cast<LIGHTLEVEL>(roundNumber( dunLevel - Races->VisLevel( mChar->GetRace() )));
}
toSend.Level( toShow );
}
}
s->Send( &toSend );
auto eventFound = false;
auto scriptTriggers = mChar->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
auto toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute) {
if( toExecute->OnLightChange( mChar, toShow ) == 1 ) {
// A script with the event returned true; prevent other scripts from running
eventFound = true;
break;
}
}
}
if( !eventFound ) {
// Check global script! Maybe there's another event there
auto toExecute = JSMapping->GetScript( static_cast<UI16>(0) );
if( toExecute ){
toExecute->OnLightChange( mChar, toShow );
}
}
Weather->DoPlayerStuff( s, mChar );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void doLight( CChar *mChar, UI08 level )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Sets light level for character and applies relevant effects
//o-----------------------------------------------------------------------------------------------o
auto doLight( CChar *mChar, UI08 level )->void {
if( (Races->Affect( mChar->GetRace(), LIGHT )) && (mChar->GetWeathDamage( LIGHT ) == 0) ){
mChar->SetWeathDamage( static_cast<UI32>(BuildTimeValue( static_cast<R32>(Races->Secs( mChar->GetRace(), LIGHT )) )), LIGHT );
}
auto curRegion = mChar->GetRegion();
auto wSys = Weather->Weather( curRegion->GetWeather() );
LIGHTLEVEL toShow = level;
LIGHTLEVEL dunLevel = cwmWorldState->ServerData()->DungeonLightLevel();
// we have a valid weather system
if( wSys) {
const R32 lightMin = wSys->LightMin();
const R32 lightMax = wSys->LightMax();
if( lightMin < 300 && lightMax < 300 ) {
R32 i = wSys->CurrentLight();
if( Races->VisLevel( mChar->GetRace() ) > i ){
toShow = 0;
}
else{
toShow = static_cast<LIGHTLEVEL>(roundNumber( i - Races->VisLevel( mChar->GetRace() )));
}
}
}
else {
if( mChar->inDungeon() ) {
if( Races->VisLevel( mChar->GetRace() ) > dunLevel ){
toShow = 0;
}
else{
toShow = static_cast<LIGHTLEVEL>(roundNumber( dunLevel - Races->VisLevel( mChar->GetRace() )));
}
}
}
bool eventFound = false;
auto scriptTriggers = mChar->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
auto toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute ){
if( toExecute->OnLightChange( mChar, toShow ) == 1 ) {
// A script with the event returned true; prevent other scripts from running
eventFound = true;
break;
}
}
}
if( !eventFound ) {
// Check global script! Maybe there's another event there
auto toExecute = JSMapping->GetScript( static_cast<UI16>(0) );
if( toExecute ){
toExecute->OnLightChange( mChar, toShow );
}
}
Weather->DoNPCStuff( mChar );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void doLight( CItem *mItem, UI08 level )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Sets light level for items and applies relevant effects
//o-----------------------------------------------------------------------------------------------o
auto doLight( CItem *mItem, UI08 level )->void {
auto curRegion = mItem->GetRegion();
auto wSys = Weather->Weather( curRegion->GetWeather() );
LIGHTLEVEL toShow = level;
LIGHTLEVEL dunLevel = cwmWorldState->ServerData()->DungeonLightLevel();
// we have a valid weather system
if(wSys){
const R32 lightMin = wSys->LightMin();
const R32 lightMax = wSys->LightMax();
if( (lightMin < 300) && (lightMax < 300) ) {
toShow = static_cast<LIGHTLEVEL>(wSys->CurrentLight());
}
}
else{
if( mItem->inDungeon() ){
toShow = dunLevel;
}
}
auto eventFound = false;
auto scriptTriggers = mItem->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
auto toExecute = JSMapping->GetScript( scriptTrig );
if(toExecute) {
if( toExecute->OnLightChange( mItem, toShow ) == 1 ) {
// A script with the event returned true; prevent other scripts from running
eventFound = true;
break;
}
}
}
if( !eventFound ) {
// Check global script! Maybe there's another event there
auto toExecute = JSMapping->GetScript( static_cast<UI16>(0) );
if( toExecute ){
toExecute->OnLightChange( mItem, toShow );
}
}
Weather->DoItemStuff( mItem );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - TIMERVAL getPoisonDuration( UI08 poisonStrength )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Calculates the duration of poison based on its strength
//o-----------------------------------------------------------------------------------------------o
auto getPoisonDuration( UI08 poisonStrength ) ->TIMERVAL{
// Calculate duration of poison, based on the strength of the poison
auto poisonDuration = TIMERVAL(0);
switch( poisonStrength ){
case 1: // Lesser poison - 9 to 13 pulses, 2 second frequency
poisonDuration = RandomNum( 9, 13 ) * 2;
break;
case 2: // Normal poison - 10 to 14 pulses, 3 second frequency
poisonDuration = RandomNum( 10, 14 ) * 3;
break;
case 3: // Greater poison - 11 to 15 pulses, 4 second frequency
poisonDuration = RandomNum( 11, 15 ) * 4;
break;
case 4: // Deadly poison - 12 to 16 pulses, 5 second frequency
poisonDuration = RandomNum( 12, 16 ) * 5;
break;
case 5: // Lethal poison - 13 to 17 pulses, 5 second frequency
poisonDuration = RandomNum( 13, 17 ) * 5;
break;
}
return poisonDuration;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - TIMERVAL getPoisonTickTime( UI08 poisonStrength )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Calculates the time between each tick of a poison, based on its strength
//o-----------------------------------------------------------------------------------------------o
auto getPoisonTickTime( UI08 poisonStrength )->TIMERVAL{
// Calculate duration of poison, based on the strength of the poison
auto poisonTickTime = TIMERVAL(0);
switch( poisonStrength ){
case 1: // Lesser poison - 2 second frequency
poisonTickTime = 2;
break;
case 2: // Normal poison - 3 second frequency
poisonTickTime = 3;
break;
case 3: // Greater poison - 4 second frequency
poisonTickTime = 4;
break;
case 4: // Deadly poison - 5 second frequency
poisonTickTime = 5;
break;
case 5: // Lethal poison - 5 second frequency
poisonTickTime = 5;
break;
}
return poisonTickTime;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - size_t getTileName( CItem& mItem, std::string& itemname )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns the length of an items name from tiledata.mul and
//| sets itemname to the name.
//| The format it accepts is same as UO style - %plural/single% or %plural%
//| arrow%s%
//| loa%ves/f% of bread
//o-----------------------------------------------------------------------------------------------o
auto getTileName( CItem& mItem, std::string& itemname )->size_t {
std::string temp = mItem.GetName();
temp = oldstrutil::trim( oldstrutil::removeTrailing( temp, "//" ));
const UI16 getAmount = mItem.GetAmount();
CTile& tile = Map->SeekTile( mItem.GetID() );
if( temp.substr( 0, 1 ) == "#" ) {
temp = tile.Name();
}
if( getAmount == 1 ) {
if( tile.CheckFlag( TF_DISPLAYAN ) ){
temp = "an " + temp;
}
else if( tile.CheckFlag( TF_DISPLAYA ) ){
temp = "a " + temp;
}
}
auto psecs = oldstrutil::sections( temp, "%" );
// Find out if the name has a % in it
if( psecs.size() > 2 ) {
std::string single;
const std::string first = psecs[0];
std::string plural = psecs[1];
const std::string rest = psecs[2];
auto fssecs = oldstrutil::sections( plural, "/" );
if( fssecs.size() > 1 ) {
single = fssecs[1];
plural = fssecs[0];
}
if( getAmount < 2 ){
temp = first + single + rest;
}
else{
temp = first + plural + rest;
}
}
itemname = oldstrutil::simplify( temp );
return itemname.size() + 1;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - std::string getNpcDictName( CChar *mChar, CSocket *tSock )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns the dictionary name for a given NPC, if their name equals # or a dictionary ID
//o-----------------------------------------------------------------------------------------------o
auto getNpcDictName( CChar *mChar, CSocket *tSock ) ->std::string {
CChar *tChar = nullptr;
if(tSock){
tChar = tSock->CurrcharObj();
}
std::string dictName = mChar->GetNameRequest( tChar );
SI32 dictEntryID = 0;
if( dictName == "#" ) {
// If character name is #, get dictionary entry based on base dictionary entry for creature names (3000) plus character's ID
dictEntryID = static_cast<SI32>( 3000 + mChar->GetID() );
if(tSock){
dictName = Dictionary->GetEntry( dictEntryID, tSock->Language() );
}
else{
dictName = Dictionary->GetEntry( dictEntryID );
}
}
else if( isNumber( dictName )){
// If name is a number, assume it's a direct dictionary entry reference, and use that
dictEntryID = static_cast<SI32>( oldstrutil::value<SI32>( dictName ));
if(tSock){
dictName = Dictionary->GetEntry( dictEntryID, tSock->Language() );
}
else{
dictName = Dictionary->GetEntry( dictEntryID );
}
}
return dictName;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - std::string getNpcDictTitle( CChar *mChar, CSocket *tSock )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns the dictionary string for the title of a given NPC, if their title
//| equals a dictionary ID
//o-----------------------------------------------------------------------------------------------o
auto getNpcDictTitle( CChar *mChar, CSocket *tSock ) ->std::string{
std::string dictTitle = mChar->GetTitle();
SI32 dictEntryID = 0;
if( !dictTitle.empty() && isNumber( dictTitle ))
{
// If title is a number, assume it's a direct dictionary entry reference, and use that
dictEntryID = static_cast<SI32>( oldstrutil::value<SI32>( dictTitle ) );
if( tSock )
{
dictTitle = Dictionary->GetEntry( dictEntryID, tSock->Language() );
}
else
{
dictTitle = Dictionary->GetEntry( dictEntryID );
}
}
return dictTitle;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void checkRegion( CSocket *mSock, CChar& mChar, bool forceUpdateLight)
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check what region a character is in, updating it if necesarry.
//o-----------------------------------------------------------------------------------------------o
auto checkRegion( CSocket *mSock, CChar& mChar, bool forceUpdateLight)->void{
// Get character's old/previous region
auto iRegion = mChar.GetRegion();
auto oldSubRegionNum = mChar.GetSubRegion();
// Calculate character's current region
auto calcReg = calcRegionFromXY( mChar.GetX(), mChar.GetY(), mChar.WorldNumber(), mChar.GetInstanceID(), &mChar );
if( (iRegion == nullptr) && (calcReg != nullptr) ){
mChar.SetRegion( calcReg->GetRegionNum() );
}
else if( calcReg != iRegion ) {
if(mSock){
if( iRegion != nullptr && calcReg != nullptr ){
// Don't display left/entered region messages if name of region is identical
if( iRegion->GetName() != calcReg->GetName() ) {
if( !iRegion->GetName().empty() ){
mSock->sysmessage( 1358, iRegion->GetName().c_str() ); // You have left %s.
}
if( !calcReg->GetName().empty() ){
mSock->sysmessage( 1359, calcReg->GetName().c_str() ); // You have entered %s.
}
}
if( calcReg->IsGuarded() || iRegion->IsGuarded() ) {
if( calcReg->IsGuarded() ) {
// Don't display change of guard message if guardowner is identical
if( !iRegion->IsGuarded() || ( iRegion->IsGuarded() && calcReg->GetOwner() != iRegion->GetOwner() ) ) {
if( calcReg->GetOwner().empty() ){
mSock->sysmessage( 1360 ); // You are now under the protection of the guards.
}
else{
mSock->sysmessage( 1361, calcReg->GetOwner().c_str() ); // You are now under the protection of %s guards.
}
}
}
else {
if( iRegion->GetOwner().empty() ){
mSock->sysmessage( 1362 ); // You are no longer under the protection of the guards.
}
else{
mSock->sysmessage( 1363, iRegion->GetOwner().c_str() ); // You are no longer under the protection of %s guards.
}
}
UpdateFlag( &mChar );
}
if( calcReg->GetAppearance() != iRegion->GetAppearance() ){
// if the regions look different
CPWorldChange wrldChange( calcReg->GetAppearance(), 1 );
mSock->Send( &wrldChange );
}
if( calcReg == cwmWorldState->townRegions[mChar.GetTown()] ){
// enter our home town
mSock->sysmessage( 1364 ); // You feel loved and cherished under the protection of your home town.
CItem *packItem = mChar.GetPackItem();
if( ValidateObject( packItem ) ) {
auto piCont = packItem->GetContainsList();
for (const auto &toScan : piCont->collection()){
if( ValidateObject( toScan ) ) {
if( toScan->GetType() == IT_TOWNSTONE ) {
CTownRegion *targRegion = cwmWorldState->townRegions[static_cast<UI16>(toScan->GetTempVar( CITV_MOREX ))];
mSock->sysmessage( 1365, targRegion->GetName().c_str() ); // You have successfully returned the townstone of %s to your home town.
targRegion->DoDamage( targRegion->GetHealth() ); // finish it off
targRegion->Possess( calcReg );
mChar.SetFame( (SI16)( mChar.GetFame() + mChar.GetFame() / 5 ) ); // 20% fame boost
break;
}
}
}
}
}
}
}
if( iRegion != nullptr && calcReg != nullptr ) {
// Run onLeaveRegion/onEnterRegion for character
auto scriptTriggers = mChar.GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
auto toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute) {
toExecute->OnLeaveRegion( &mChar, iRegion->GetRegionNum() );
toExecute->OnEnterRegion( &mChar, calcReg->GetRegionNum() );
}
}
// Run onLeaveRegion event for region being left
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = iRegion->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
auto toExecute = JSMapping->GetScript( scriptTrig );
if(toExecute) {
toExecute->OnLeaveRegion( &mChar, iRegion->GetRegionNum() );
}
}
// Run onEnterRegion event for region being entered
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = calcReg->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
auto toExecute = JSMapping->GetScript( scriptTrig );
if(toExecute ){
toExecute->OnEnterRegion( &mChar, calcReg->GetRegionNum() );
}
}
}
if(calcReg){
mChar.SetRegion( calcReg->GetRegionNum() );
}
if(mSock){
Effects->doSocketMusic( mSock );
doLight( mSock, cwmWorldState->ServerData()->WorldLightCurrentLevel() );
}
}
else {
// Main region didn't change, but subregion did! Update music
if( oldSubRegionNum != mChar.GetSubRegion() ) {
Effects->doSocketMusic( mSock );
}
// Update lighting
if( forceUpdateLight && mSock != nullptr ) {
doLight( mSock, cwmWorldState->ServerData()->WorldLightCurrentLevel() );
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void CheckCharInsideBuilding( CChar *c, CSocket *mSock, bool doWeatherStuff )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Checks if a character is inside a building before applying weather effects
//o-----------------------------------------------------------------------------------------------o
auto CheckCharInsideBuilding( CChar *c, CSocket *mSock, bool doWeatherStuff ) ->void {
if( !c->GetMounted()&& !c->GetStabled() ){
auto wasInBuilding = c->inBuilding();
bool isInBuilding = Map->inBuilding( c->GetX(), c->GetY(), c->GetZ(), c->WorldNumber(), c->GetInstanceID() );
if( wasInBuilding != isInBuilding ) {
c->SetInBuilding( isInBuilding );
if( doWeatherStuff ){
if( c->IsNpc() ) {
Weather->DoNPCStuff( c );
}
else {
Weather->DoPlayerStuff( mSock, c );
}
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool WillResultInCriminal( CChar *mChar, CChar *targ )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check flagging, race, and guild info to find if character
//| should be flagged criminal (returns true if so)
//o-----------------------------------------------------------------------------------------------o
auto WillResultInCriminal( CChar *mChar, CChar *targ )->bool {
auto tOwner = targ->GetOwnerObj();
auto mOwner = mChar->GetOwnerObj();
auto mCharParty = PartyFactory::getSingleton().Get( mChar );
auto rvalue = false ;
if (ValidateObject(mChar) && ValidateObject(targ) && mChar != targ){
if ((Races->Compare( mChar, targ ) > RACE_ENEMY) && GuildSys->ResultInCriminal( mChar, targ ) ) {
if (!mCharParty || mCharParty->HasMember(targ) ){
if (!targ->DidAttackFirst() || (targ->GetTarg() == mChar)){
if (!ValidateObject(tOwner)){
if (!ValidateObject(mOwner)){
if (targ->IsInnocent()){
rvalue = true ;
}
}
}
}
}
}
}
return rvalue ;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void criminal( CChar *c )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Make character a criminal
//o-----------------------------------------------------------------------------------------------o
auto criminal( CChar *c )->void {
c->SetTimer( tCHAR_CRIMFLAG, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_CRIMINAL ) );
if( !c->IsCriminal() && !c->IsMurderer() ) {
auto cSock = c->GetSocket();
if(cSock){
cSock->sysmessage( 1379 );
}
UpdateFlag( c );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void UpdateFlag( CChar *mChar )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Updates character flags
//o-----------------------------------------------------------------------------------------------o
auto UpdateFlag( CChar *mChar )->void {
if( ValidateObject(mChar) ){
UI08 oldFlag = mChar->GetFlag();
if( mChar->IsTamed() ){
CChar *i = mChar->GetOwnerObj();
if( ValidateObject( i ) ){
mChar->SetFlag( i->GetFlag() );
}
else {
mChar->SetFlagBlue();
Console.warning( oldstrutil::format("Tamed Creature has an invalid owner, Serial: 0x%X", mChar->GetSerial()) );
}
}
else {
if( mChar->GetKills() > cwmWorldState->ServerData()->RepMaxKills() ) {
mChar->SetFlagRed();
}
else if( (mChar->GetTimer(tCHAR_CRIMFLAG) != 0) && (mChar->GetNPCFlag() != fNPC_EVIL) ){
mChar->SetFlagGray();
}
else {
if( mChar->IsNpc() ) {
auto doSwitch = true;
if( cwmWorldState->creatures[mChar->GetID()].IsAnimal() && (mChar->GetNPCAiType() != AI_EVIL) ) {
if( cwmWorldState->ServerData()->CombatAnimalsGuarded() && mChar->GetRegion()->IsGuarded() ) {
mChar->SetFlagBlue();
doSwitch = false;
}
}
if( doSwitch ) {
switch( mChar->GetNPCFlag() ) {
case fNPC_NEUTRAL:
default:
mChar->SetFlagNeutral();
break;
case fNPC_INNOCENT:
mChar->SetFlagBlue();
break;
case fNPC_EVIL:
mChar->SetFlagRed();
break;
}
}
}
else {
mChar->SetFlagBlue();
}
}
}
UI08 newFlag = mChar->GetFlag();
if( oldFlag != newFlag ) {
auto scriptTriggers = mChar->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers ) {
auto toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute) {
if( toExecute->OnFlagChange( mChar, newFlag, oldFlag ) == 1 ) {
break;
}
}
}
mChar->Dirty( UT_UPDATE );
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void SendMapChange( UI08 worldNumber, CSocket *sock, bool initialLogin )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Send mapchange packet to client to teleport player to new world/map
//o-----------------------------------------------------------------------------------------------o
void SendMapChange( UI08 worldNumber, CSocket *sock, bool initialLogin )
{
if( sock){
CPMapChange mapChange( worldNumber );
/*
if( !initialLogin && worldNumber > 1 ) {
switch( sock->ClientType() ) {
case CV_UO3D:
case CV_KRRIOS:
break;
default:
break;
}
}
*/
sock->Send( &mapChange );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void SocketMapChange( CSocket *sock, CChar *charMoving, CItem *gate )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check if conditions are right to send a map change packet to the client
//o-----------------------------------------------------------------------------------------------o
auto SocketMapChange( CSocket *sock, CChar *charMoving, CItem *gate ) ->void {
if( sock){
if( ValidateObject( gate ) && ValidateObject( charMoving ) ){
UI08 tWorldNum = (UI08)gate->GetTempVar( CITV_MORE );
UI16 tInstanceID = gate->GetInstanceID();
if( Map->MapExists( tWorldNum ) ){
CChar *toMove = nullptr;
if( ValidateObject( charMoving ) ){
toMove = charMoving;
}
else {
toMove = sock->CurrcharObj();
}
if( ValidateObject( toMove ) ){
// Teleport pets to new location too!
auto myPets = toMove->GetPetList();
for( CChar *myPet = myPets->First(); !myPets->Finished(); myPet = myPets->Next() ) {
if( ValidateObject( myPet ) ){
if( !myPet->GetMounted() && myPet->IsNpc() && myPet->GetOwnerObj() == toMove ) {
if( objInOldRange( toMove, myPet, DIST_CMDRANGE ) ){
myPet->SetLocation( (SI16)gate->GetTempVar( CITV_MOREX ), (SI16)gate->GetTempVar( CITV_MOREY ), (SI08)gate->GetTempVar(CITV_MOREZ ), tWorldNum, tInstanceID );
}
}
}
}
switch( sock->ClientType() ) {
case CV_UO3D:
case CV_KRRIOS:
toMove->SetLocation( (SI16)gate->GetTempVar( CITV_MOREX ), (SI16)gate->GetTempVar( CITV_MOREY ), (SI08)gate->GetTempVar( CITV_MOREZ ), tWorldNum, tInstanceID );
break;
default:
toMove->SetLocation( (SI16)gate->GetTempVar( CITV_MOREX ), (SI16)gate->GetTempVar( CITV_MOREY ), (SI08)gate->GetTempVar( CITV_MOREZ ), tWorldNum, tInstanceID );
break;
}
SendMapChange( tWorldNum, sock );
}
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void DoorMacro( CSocket *s )
//| Date - 11th October, 1999
//| Changes - (support CSocket *s and door blocking)
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Door use macro support.
//o-----------------------------------------------------------------------------------------------o
void DoorMacro( CSocket *s )
{
CChar *mChar = s->CurrcharObj();
SI16 xc = mChar->GetX(), yc = mChar->GetY();
switch( mChar->GetDir() )
{
case 0 : --yc; break;
case 1 : { ++xc; --yc; } break;
case 2 : ++xc; break;
case 3 : { ++xc; ++yc; } break;
case 4 : ++yc; break;
case 5 : { --xc; ++yc; } break;
case 6 : --xc; break;
case 7 : { --xc; --yc; } break;
}
for (auto &toCheck : MapRegion->PopulateList( mChar )){
if(toCheck){
auto regItems = toCheck->GetItemList();
for (const auto &itemCheck : regItems->collection()){
if( ValidateObject( itemCheck ) && (itemCheck->GetInstanceID() == mChar->GetInstanceID()) ){
SI16 distZ = abs( itemCheck->GetZ() - mChar->GetZ() );
if( itemCheck->GetX() == xc && itemCheck->GetY() == yc && distZ < 7 ) {
if( itemCheck->GetType() == IT_DOOR || itemCheck->GetType() == IT_LOCKEDDOOR ){
// only open doors
if( JSMapping->GetEnvokeByType()->Check( static_cast<UI16>(itemCheck->GetType()) ) ) {
UI16 envTrig = JSMapping->GetEnvokeByType()->GetScript( static_cast<UI16>(itemCheck->GetType()) );
auto envExecute = JSMapping->GetScript( envTrig );
if(envExecute)
[[maybe_unused]] SI08 retVal = envExecute->OnUseChecked( mChar, itemCheck );
return;
}
}
}
}
}
}
}
}