diff --git a/.gitignore b/.gitignore index b8ec6881f..1db525804 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,5 @@ cmake-build-debug/ libs/ bin/ /Win32 +/x64 /client_files/**/CMakeFiles/ diff --git a/README.md b/README.md index 31f88be38..1b08d54be 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ forum, although pull requests will be much quicker and easier on all parties. ## Resources - [EQEmulator Forums](http://www.eqemulator.org/forums) -- [EQEmulator Wiki](https://github.com/EQEmu/Server/wiki) +- [EQEmulator Wiki](https://eqemu.gitbook.io/) ## Related Repositories * [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) diff --git a/common/database.h b/common/database.h index d94401299..99775180d 100644 --- a/common/database.h +++ b/common/database.h @@ -195,19 +195,19 @@ public: void GetAccountFromID(uint32 id, char* oAccountName, int16* oStatus); void SetAgreementFlag(uint32 acctid); - + int GetIPExemption(std::string account_ip); int GetInstanceID(uint32 char_id, uint32 zone_id); /* Groups */ - + char* GetGroupLeaderForLogin(const char* name,char* leaderbuf); char* GetGroupLeadershipInfo(uint32 gid, char* leaderbuf, char* maintank = nullptr, char* assist = nullptr, char* puller = nullptr, char *marknpc = nullptr, char *mentoree = nullptr, int *mentor_percent = nullptr, GroupLeadershipAA_Struct* GLAA = nullptr); - + uint32 GetGroupID(const char* name); - + void ClearGroup(uint32 gid = 0); void ClearGroupLeader(uint32 gid = 0); void SetGroupID(const char* name, uint32 id, uint32 charid, uint32 ismerc = false); diff --git a/common/database_instances.cpp b/common/database_instances.cpp index 68496b61d..064cf6db4 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -97,42 +97,53 @@ bool Database::CheckInstanceExists(uint16 instance_id) { bool Database::CheckInstanceExpired(uint16 instance_id) { - int32 start_time = 0; - int32 duration = 0; + int32 start_time = 0; + int32 duration = 0; uint32 never_expires = 0; - std::string query = StringFormat("SELECT start_time, duration, never_expires FROM instance_list WHERE id=%u", instance_id); + std::string query = StringFormat( + "SELECT start_time, duration, never_expires FROM instance_list WHERE id=%u", + instance_id + ); + auto results = QueryDatabase(query); - if (!results.Success()) + if (!results.Success()) { return true; + } - if (results.RowCount() == 0) + if (results.RowCount() == 0) { return true; + } auto row = results.begin(); - start_time = atoi(row[0]); - duration = atoi(row[1]); + start_time = atoi(row[0]); + duration = atoi(row[1]); never_expires = atoi(row[2]); - if (never_expires == 1) + if (never_expires == 1) { return false; + } - timeval tv; + timeval tv{}; gettimeofday(&tv, nullptr); - if ((start_time + duration) <= tv.tv_sec) - return true; + return (start_time + duration) <= tv.tv_sec; - return false; } bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration) { - std::string query = StringFormat("INSERT INTO instance_list (id, zone, version, start_time, duration)" - " values(%lu, %lu, %lu, UNIX_TIMESTAMP(), %lu)", - (unsigned long)instance_id, (unsigned long)zone_id, (unsigned long)version, (unsigned long)duration); + std::string query = StringFormat( + "INSERT INTO instance_list (id, zone, version, start_time, duration)" + " values (%u, %u, %u, UNIX_TIMESTAMP(), %u)", + instance_id, + zone_id, + version, + duration + ); + auto results = QueryDatabase(query); return results.Success(); @@ -140,66 +151,84 @@ bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version bool Database::GetUnusedInstanceID(uint16 &instance_id) { - uint32 count = RuleI(Zone, ReservedInstances); - uint32 max = 65535; + uint32 max_reserved_instance_id = RuleI(Instances, ReservedInstances); + uint32 max = 32000; + + std::string query = StringFormat( + "SELECT IFNULL(MAX(id),%u)+1 FROM instance_list WHERE id > %u", + max_reserved_instance_id, + max_reserved_instance_id + ); + + if (RuleB(Instances, RecycleInstanceIds)) { + query = ( + SQL( + SELECT i.id + 1 AS next_available + FROM instance_list i + LEFT JOIN instance_list i2 ON i2.id = i.id + 1 + WHERE i2.id IS NULL + ORDER BY i.id + LIMIT 0, 1; + + ) + ); + } - std::string query = StringFormat("SELECT IFNULL(MAX(id),%u)+1 FROM instance_list WHERE id > %u", count, count); auto results = QueryDatabase(query); - if (!results.Success()) - { + if (!results.Success()) { instance_id = 0; return false; } - if (results.RowCount() == 0) - { - instance_id = 0; - return false; + if (results.RowCount() == 0) { + instance_id = max_reserved_instance_id; + return true; } auto row = results.begin(); - if (atoi(row[0]) <= max) - { + if (atoi(row[0]) <= max) { instance_id = atoi(row[0]); + return true; } - query = StringFormat("SELECT id FROM instance_list where id > %u ORDER BY id", count); + if (instance_id < max_reserved_instance_id) { + instance_id = max_reserved_instance_id; + return true; + } + + query = StringFormat("SELECT id FROM instance_list where id > %u ORDER BY id", max_reserved_instance_id); results = QueryDatabase(query); - if (!results.Success()) - { + if (!results.Success()) { instance_id = 0; return false; } - if (results.RowCount() == 0) - { + if (results.RowCount() == 0) { instance_id = 0; return false; } - count++; - for (auto row = results.begin(); row != results.end(); ++row) - { - if (count < atoi(row[0])) - { - instance_id = count; + max_reserved_instance_id++; + for (auto row = results.begin(); row != results.end(); ++row) { + if (max_reserved_instance_id < atoi(row[0])) { + instance_id = max_reserved_instance_id; return true; } - if (count > max) - { + if (max_reserved_instance_id > max) { instance_id = 0; return false; } - count++; + max_reserved_instance_id++; } - instance_id = count; + instance_id = max_reserved_instance_id; + return true; } @@ -544,17 +573,36 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list &ch void Database::PurgeExpiredInstances() { - std::string query("SELECT id FROM instance_list where (start_time+duration) <= UNIX_TIMESTAMP() and never_expires = 0"); + + /** + * Delay purging by a day so that we can continue using adjacent free instance id's + * from the table without risking the chance we immediately re-allocate a zone that freshly expired but + * has not been fully de-allocated + */ + std::string query = + SQL( + SELECT + id + FROM + instance_list + where + (start_time + duration) <= (UNIX_TIMESTAMP() - 86400) + and never_expires = 0 + ); + auto results = QueryDatabase(query); - if (!results.Success()) + if (!results.Success()) { return; + } - if (results.RowCount() == 0) + if (results.RowCount() == 0) { return; + } - for (auto row = results.begin(); row != results.end(); ++row) + for (auto row = results.begin(); row != results.end(); ++row) { DeleteInstance(atoi(row[0])); + } } void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) @@ -562,4 +610,4 @@ void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) std::string query = StringFormat("UPDATE `instance_list` SET start_time=UNIX_TIMESTAMP(), " "duration=%u WHERE id=%u", new_duration, instance_id); auto results = QueryDatabase(query); -} \ No newline at end of file +} diff --git a/common/ruletypes.h b/common/ruletypes.h index f3650d156..45c306e02 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -270,7 +270,6 @@ RULE_INT(Zone, PEQZoneDebuff1, 4454, "First debuff casted by #peqzone Default is RULE_INT(Zone, PEQZoneDebuff2, 2209, "Second debuff casted by #peqzone Default is Tendrils of Apathy") RULE_BOOL(Zone, UsePEQZoneDebuffs, true, "Will determine if #peqzone will debuff players or not when used") RULE_REAL(Zone, HotZoneBonus, 0.75, "") -RULE_INT(Zone, ReservedInstances, 30, "Will reserve this many instance ids for globals... probably not a good idea to change this while a server is running") RULE_INT(Zone, EbonCrystalItemID, 40902, "") RULE_INT(Zone, RadiantCrystalItemID, 40903, "") RULE_BOOL(Zone, LevelBasedEXPMods, false, "Allows you to use the level_exp_mods table in consideration to your players EXP hits") @@ -779,6 +778,12 @@ RULE_CATEGORY(Expansion) RULE_INT(Expansion, CurrentExpansion, -1, "The current expansion enabled for the server [-1 = ALL, 0 = Classic, 1 = Kunark etc.]") RULE_CATEGORY_END() +RULE_CATEGORY(Instances) +RULE_INT(Instances, ReservedInstances, 30, "Will reserve this many instance ids for globals... probably not a good idea to change this while a server is running") +RULE_BOOL(Instances, RecycleInstanceIds, true, "Will recycle free instance ids instead of gradually running out at 32k") +RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires") +RULE_CATEGORY_END() + #undef RULE_CATEGORY #undef RULE_INT #undef RULE_REAL diff --git a/common/timer.cpp b/common/timer.cpp index 120e3dea7..f4d931764 100644 --- a/common/timer.cpp +++ b/common/timer.cpp @@ -129,13 +129,17 @@ void Timer::SetTimer(uint32 set_timer_time) { } } -uint32 Timer::GetRemainingTime() const { +uint32 Timer::GetRemainingTime() const +{ if (enabled) { - if (current_time - start_time > timer_time) + if (current_time - start_time > timer_time) { return 0; - else + } + else { return (start_time + timer_time) - current_time; - } else { + } + } + else { return 0xFFFFFFFF; } } diff --git a/ucs/chatchannel.cpp b/ucs/chatchannel.cpp index 001f7b1e7..efac898d5 100644 --- a/ucs/chatchannel.cpp +++ b/ucs/chatchannel.cpp @@ -47,8 +47,13 @@ ChatChannel::ChatChannel(std::string inName, std::string inOwner, std::string in Moderated = false; - LogInfo("New ChatChannel created: Name: [[{}]], Owner: [[{}]], Password: [[{}]], MinStatus: [{}]", - Name.c_str(), Owner.c_str(), Password.c_str(), MinimumStatus); + LogDebug( + "New ChatChannel created: Name: [[{}]], Owner: [[{}]], Password: [[{}]], MinStatus: [{}]", + Name.c_str(), + Owner.c_str(), + Password.c_str(), + MinimumStatus + ); } @@ -154,7 +159,7 @@ void ChatChannelList::SendAllChannels(Client *c) { void ChatChannelList::RemoveChannel(ChatChannel *Channel) { - LogInfo("RemoveChannel([{}])", Channel->GetName().c_str()); + LogDebug("RemoveChannel ([{}])", Channel->GetName().c_str()); LinkedListIterator iterator(ChatChannels); @@ -175,7 +180,7 @@ void ChatChannelList::RemoveChannel(ChatChannel *Channel) { void ChatChannelList::RemoveAllChannels() { - LogInfo("RemoveAllChannels"); + LogDebug("RemoveAllChannels"); LinkedListIterator iterator(ChatChannels); @@ -242,7 +247,7 @@ void ChatChannel::AddClient(Client *c) { int AccountStatus = c->GetAccountStatus(); - LogInfo("Adding [{}] to channel [{}]", c->GetName().c_str(), Name.c_str()); + LogDebug("Adding [{}] to channel [{}]", c->GetName().c_str(), Name.c_str()); LinkedListIterator iterator(ClientsInChannel); @@ -267,7 +272,7 @@ bool ChatChannel::RemoveClient(Client *c) { if(!c) return false; - LogInfo("RemoveClient [{}] from channel [{}]", c->GetName().c_str(), GetName().c_str()); + LogDebug("RemoveClient [{}] from channel [{}]", c->GetName().c_str(), GetName().c_str()); bool HideMe = c->GetHideMe(); @@ -304,7 +309,7 @@ bool ChatChannel::RemoveClient(Client *c) { if((Password.length() == 0) || (RuleI(Channels, DeleteTimer) == 0)) return false; - LogInfo("Starting delete timer for empty password protected channel [{}]", Name.c_str()); + LogDebug("Starting delete timer for empty password protected channel [{}]", Name.c_str()); DeleteTimer.Start(RuleI(Channels, DeleteTimer) * 60000); } @@ -402,7 +407,7 @@ void ChatChannel::SendMessageToChannel(std::string Message, Client* Sender) { if(ChannelClient) { - LogInfo("Sending message to [{}] from [{}]", + LogDebug("Sending message to [{}] from [{}]", ChannelClient->GetName().c_str(), Sender->GetName().c_str()); if (cv_messages[static_cast(ChannelClient->GetClientVersion())].length() == 0) { @@ -505,7 +510,7 @@ ChatChannel *ChatChannelList::AddClientToChannel(std::string ChannelName, Client return nullptr; } - LogInfo("AddClient to channel [[{}]] with password [[{}]]", NormalisedName.c_str(), Password.c_str()); + LogDebug("AddClient to channel [[{}]] with password [[{}]]", NormalisedName.c_str(), Password.c_str()); ChatChannel *RequiredChannel = FindChannel(NormalisedName); @@ -581,7 +586,7 @@ void ChatChannelList::Process() { if(CurrentChannel && CurrentChannel->ReadyToDelete()) { - LogInfo("Empty temporary password protected channel [{}] being destroyed", + LogDebug("Empty temporary password protected channel [{}] being destroyed", CurrentChannel->GetName().c_str()); RemoveChannel(CurrentChannel); @@ -597,7 +602,7 @@ void ChatChannel::AddInvitee(const std::string &Invitee) if (!IsInvitee(Invitee)) { Invitees.push_back(Invitee); - LogInfo("Added [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); + LogDebug("Added [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); } } @@ -608,7 +613,7 @@ void ChatChannel::RemoveInvitee(std::string Invitee) if(it != std::end(Invitees)) { Invitees.erase(it); - LogInfo("Removed [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); + LogDebug("Removed [{}] as invitee to channel [{}]", Invitee.c_str(), Name.c_str()); } } diff --git a/ucs/clientlist.cpp b/ucs/clientlist.cpp index 429f28fbf..0b1d43262 100644 --- a/ucs/clientlist.cpp +++ b/ucs/clientlist.cpp @@ -235,7 +235,7 @@ std::vector ParseRecipients(std::string RecipientString) { static void ProcessMailTo(Client *c, std::string MailMessage) { - LogInfo("MAILTO: From [{}], [{}]", c->MailBoxName().c_str(), MailMessage.c_str()); + LogDebug("MAILTO: From [{}], [{}]", c->MailBoxName().c_str(), MailMessage.c_str()); std::vector Recipients; @@ -304,7 +304,7 @@ static void ProcessMailTo(Client *c, std::string MailMessage) { if (!database.SendMail(Recipient, c->MailBoxName(), Subject, Body, RecipientsString)) { - LogInfo("Failed in SendMail([{}], [{}], [{}], [{}])", Recipient.c_str(), + LogError("Failed in SendMail([{}], [{}], [{}], [{}])", Recipient.c_str(), c->MailBoxName().c_str(), Subject.c_str(), RecipientsString.c_str()); int PacketLength = 10 + Recipient.length() + Subject.length(); @@ -556,6 +556,17 @@ void Client::CloseConnection() { ClientStream->ReleaseFromUse(); } +void Clientlist::CheckForStaleConnectionsAll() +{ + LogDebug("Checking for stale connections"); + + auto it = ClientChatConnections.begin(); + while (it != ClientChatConnections.end()) { + (*it)->SendKeepAlive(); + ++it; + } +} + void Clientlist::CheckForStaleConnections(Client *c) { if (!c) return; @@ -634,10 +645,12 @@ void Clientlist::Process() // std::string::size_type LastPeriod = MailBoxString.find_last_of("."); - if (LastPeriod == std::string::npos) + if (LastPeriod == std::string::npos) { CharacterName = MailBoxString; - else + } + else { CharacterName = MailBoxString.substr(LastPeriod + 1); + } LogInfo("Received login for user [{}] with key [{}]", MailBox, Key); @@ -652,8 +665,9 @@ void Clientlist::Process() database.GetAccountStatus((*it)); - if ((*it)->GetConnectionType() == ConnectionTypeCombined) + if ((*it)->GetConnectionType() == ConnectionTypeCombined) { (*it)->SendFriends(); + } (*it)->SendMailBoxes(); @@ -865,7 +879,9 @@ void Clientlist::CloseAllConnections() { void Client::AddCharacter(int CharID, const char *CharacterName, int Level) { if (!CharacterName) return; - LogInfo("Adding character [{}] with ID [{}] for [{}]", CharacterName, CharID, GetName().c_str()); + + LogDebug("Adding character [{}] with ID [{}] for [{}]", CharacterName, CharID, GetName().c_str()); + CharacterEntry NewCharacter; NewCharacter.CharID = CharID; NewCharacter.Name = CharacterName; @@ -874,6 +890,10 @@ void Client::AddCharacter(int CharID, const char *CharacterName, int Level) { Characters.push_back(NewCharacter); } +void Client::SendKeepAlive() { + QueuePacket(new EQApplicationPacket(OP_SessionReady, 0)); +} + void Client::SendMailBoxes() { int Count = Characters.size(); @@ -930,7 +950,7 @@ void Client::AddToChannelList(ChatChannel *JoinedChannel) { for (int i = 0; i < MAX_JOINED_CHANNELS; i++) if (JoinedChannels[i] == nullptr) { JoinedChannels[i] = JoinedChannel; - LogInfo("Added Channel [{}] to slot [{}] for [{}]", JoinedChannel->GetName().c_str(), i + 1, GetName().c_str()); + LogDebug("Added Channel [{}] to slot [{}] for [{}]", JoinedChannel->GetName().c_str(), i + 1, GetName().c_str()); return; } } @@ -2346,18 +2366,17 @@ void Client::SendFriends() { } } -std::string Client::MailBoxName() { +std::string Client::MailBoxName() +{ + if ((Characters.empty()) || (CurrentMailBox > (Characters.size() - 1))) { + LogDebug("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]", + CurrentMailBox, Characters.size()); - if ((Characters.empty()) || (CurrentMailBox > (Characters.size() - 1))) - { - LogInfo("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]", - CurrentMailBox, Characters.size()); - - return ""; + return std::string(); } - LogInfo("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]", - CurrentMailBox, Characters.size()); + LogDebug("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]", + CurrentMailBox, Characters.size()); return Characters[CurrentMailBox].Name; diff --git a/ucs/clientlist.h b/ucs/clientlist.h index 6021c5c0e..548fb170c 100644 --- a/ucs/clientlist.h +++ b/ucs/clientlist.h @@ -143,7 +143,7 @@ public: void SetConnectionType(char c); ConnectionType GetConnectionType() { return TypeOfConnection; } EQEmu::versions::ClientVersion GetClientVersion() { return ClientVersion_; } - + inline bool IsMailConnection() { return (TypeOfConnection == ConnectionTypeMail) || (TypeOfConnection == ConnectionTypeCombined); } void SendNotification(int MailBoxNumber, std::string From, std::string Subject, int MessageID); void ChangeMailBox(int NewMailBox); @@ -151,6 +151,7 @@ public: void SendFriends(); int GetCharID(); void SendUptime(); + void SendKeepAlive(); private: unsigned int CurrentMailBox; @@ -183,6 +184,7 @@ public: void Process(); void CloseAllConnections(); Client *FindCharacter(std::string CharacterName); + void CheckForStaleConnectionsAll(); void CheckForStaleConnections(Client *c); Client *IsCharacterOnline(std::string CharacterName); void ProcessOPMailCommand(Client *c, std::string CommandString); diff --git a/ucs/database.cpp b/ucs/database.cpp index d756f1ff4..108b17871 100644 --- a/ucs/database.cpp +++ b/ucs/database.cpp @@ -108,7 +108,7 @@ void Database::GetAccountStatus(Client *client) { std::string query = StringFormat( - "SELECT `status`, `hideme`, `karma`, `revoked` FROM `account` WHERE `id` = '%i' LIMIT 1", + "SELECT `status`, `hideme`, `karma`, `revoked` FROM `account` WHERE `id` = %i LIMIT 1", client->GetAccountID() ); @@ -173,7 +173,7 @@ int Database::FindAccount(const char *characterName, Client *client) query = StringFormat( "SELECT `id`, `name`, `level` FROM `character_data` " - "WHERE `account_id` = %i AND `name` != '%s'", + "WHERE `account_id` = %i AND `name` != '%s' AND deleted_at is NULL", accountID, characterName ); @@ -320,7 +320,7 @@ void Database::SendHeaders(Client *client) int unknownField3 = 1; int characterID = FindCharacter(client->MailBoxName().c_str()); - LogInfo("Sendheaders for [{}], CharID is [{}]", client->MailBoxName().c_str(), characterID); + LogDebug("Sendheaders for [{}], CharID is [{}]", client->MailBoxName().c_str(), characterID); if (characterID <= 0) { return; diff --git a/ucs/ucs.cpp b/ucs/ucs.cpp index 0f396252b..46bda530c 100644 --- a/ucs/ucs.cpp +++ b/ucs/ucs.cpp @@ -70,17 +70,18 @@ int main() { // Check every minute for unused channels we can delete // Timer ChannelListProcessTimer(60000); + Timer ClientConnectionPruneTimer(60000); Timer InterserverTimer(INTERSERVER_TIMER); // does auto-reconnect LogInfo("Starting EQEmu Universal Chat Server"); - if (!ucsconfig::LoadConfig()) { - LogInfo("Loading server configuration failed"); + if (!ucsconfig::LoadConfig()) { + LogInfo("Loading server configuration failed"); return 1; } - Config = ucsconfig::get(); + Config = ucsconfig::get(); WorldShortName = Config->ShortName; @@ -144,19 +145,26 @@ int main() { worldserver = new WorldServer; - while(RunLoops) { + auto loop_fn = [&](EQ::Timer* t) { Timer::SetCurrentTime(); g_Clientlist->Process(); - if(ChannelListProcessTimer.Check()) + if (ChannelListProcessTimer.Check()) { ChannelList->Process(); + } - EQ::EventLoop::Get().Process(); + if (ClientConnectionPruneTimer.Check()) { + g_Clientlist->CheckForStaleConnectionsAll(); + } - Sleep(5); - } + }; + + EQ::Timer process_timer(loop_fn); + process_timer.Start(32, true); + + EQ::EventLoop::Get().Run(); ChannelList->RemoveAllChannels(); diff --git a/ucs/worldserver.cpp b/ucs/worldserver.cpp index 72865e416..107624a0d 100644 --- a/ucs/worldserver.cpp +++ b/ucs/worldserver.cpp @@ -61,7 +61,7 @@ void WorldServer::ProcessMessage(uint16 opcode, EQ::Net::Packet &p) ServerPacket tpack(opcode, p); ServerPacket *pack = &tpack; - LogInfo("Received Opcode: {:#04x}", opcode); + LogNetcode("Received Opcode: {:#04x}", opcode); switch (opcode) { diff --git a/utils/sql/peq-dump/peq-dump.sh b/utils/sql/peq-dump/peq-dump.sh index ccb7b8949..7eaffdc6a 100755 --- a/utils/sql/peq-dump/peq-dump.sh +++ b/utils/sql/peq-dump/peq-dump.sh @@ -44,6 +44,7 @@ echo "Generating [create_*] table exports..." bash -c "${world_bin} database:dump --login-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_login.sql" bash -c "${world_bin} database:dump --player-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_player.sql" bash -c "${world_bin} database:dump --state-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_state.sql" +echo 'REPLACE INTO `instance_list` VALUES (1,25,1,1,0,0,1),(2,25,2,1,0,0,1),(3,151,1,1,0,0,1),(4,114,1,1,0,0,1),(5,344,1,1,0,0,1),(6,202,0,1,0,0,1);' >> "${dump_path}create_tables_state.sql" bash -c "${world_bin} database:dump --query-serv-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_queryserv.sql" # with content @@ -67,4 +68,4 @@ bash -c "cd /tmp/ && rm -rf peq-latest.zip && zip peq-latest.zip peq-dump/* && m echo "Cleaning up..." rm -rf ${dump_path} -echo "Dump located [/tmp/peq-latest.zip]" \ No newline at end of file +echo "Dump located [/tmp/peq-latest.zip]" diff --git a/world/ucs.cpp b/world/ucs.cpp index f5910da80..cde94b8d9 100644 --- a/world/ucs.cpp +++ b/world/ucs.cpp @@ -6,29 +6,38 @@ #include "../common/misc_functions.h" #include "../common/md5.h" #include "../common/packet_dump.h" +#include "../common/event/timer.h" UCSConnection::UCSConnection() { - Stream = 0; + connection = 0; } void UCSConnection::SetConnection(std::shared_ptr inStream) { - if (Stream && Stream->Handle()) - { + if (inStream && connection && connection->Handle()) { LogInfo("Incoming UCS Connection while we were already connected to a UCS"); - Stream->Handle()->Disconnect(); + connection->Handle()->Disconnect(); + } + + connection = inStream; + if (connection) { + connection->OnMessage( + std::bind( + &UCSConnection::ProcessPacket, + this, + std::placeholders::_1, + std::placeholders::_2 + ) + ); } - Stream = inStream; - if (Stream) { - Stream->OnMessage(std::bind(&UCSConnection::ProcessPacket, this, std::placeholders::_1, std::placeholders::_2)); - } + m_keepalive.reset(new EQ::Timer(5000, true, std::bind(&UCSConnection::OnKeepAlive, this, std::placeholders::_1))); } void UCSConnection::ProcessPacket(uint16 opcode, EQ::Net::Packet &p) { - if (!Stream) + if (!connection) return; ServerPacket tpack(opcode, p); @@ -60,10 +69,10 @@ void UCSConnection::ProcessPacket(uint16 opcode, EQ::Net::Packet &p) void UCSConnection::SendPacket(ServerPacket* pack) { - if (!Stream) + if (!connection) return; - Stream->SendPacket(pack); + connection->SendPacket(pack); } void UCSConnection::SendMessage(const char *From, const char *Message) @@ -78,3 +87,13 @@ void UCSConnection::SendMessage(const char *From, const char *Message) SendPacket(pack); safe_delete(pack); } + +void UCSConnection::OnKeepAlive(EQ::Timer *t) +{ + if (!connection) { + return; + } + + ServerPacket pack(ServerOP_KeepAlive, 0); + connection->SendPacket(&pack); +} diff --git a/world/ucs.h b/world/ucs.h index d2051c0be..c32872ccb 100644 --- a/world/ucs.h +++ b/world/ucs.h @@ -4,6 +4,7 @@ #include "../common/types.h" #include "../common/net/servertalk_server_connection.h" #include "../common/servertalk.h" +#include "../common/event/timer.h" #include class UCSConnection @@ -13,11 +14,17 @@ public: void SetConnection(std::shared_ptr connection); void ProcessPacket(uint16 opcode, EQ::Net::Packet &p); void SendPacket(ServerPacket* pack); - void Disconnect() { if(Stream && Stream->Handle()) Stream->Handle()->Disconnect(); } + void Disconnect() { if(connection && connection->Handle()) connection->Handle()->Disconnect(); } void SendMessage(const char *From, const char *Message); private: - inline std::string GetIP() const { return (Stream && Stream->Handle()) ? Stream->Handle()->RemoteIP() : 0; } - std::shared_ptr Stream; + inline std::string GetIP() const { return (connection && connection->Handle()) ? connection->Handle()->RemoteIP() : 0; } + std::shared_ptr connection; + + /** + * Keepalive + */ + std::unique_ptr m_keepalive; + void OnKeepAlive(EQ::Timer *t); }; #endif /*UCS_H_*/ diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 24dd90e7d..0cc2d7691 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -32,6 +32,7 @@ #endif #include "map.h" +#include "water_map.h" extern Zone* zone; //#define LOSDEBUG 6 @@ -237,6 +238,11 @@ bool Mob::CheckWillAggro(Mob *mob) { if (!mob->CastToClient()->ClientFinishedLoading() || mob->CastToClient()->IsHoveringForRespawn() || mob->CastToClient()->bZoning) return false; } + + // We don't want to aggro clients outside of water if we're water only. + if (mob->IsClient() && mob->CastToClient()->GetLastRegion() != RegionTypeWater && IsUnderwaterOnly()) { + return false; + } /** * Pets shouldn't scan for aggro diff --git a/zone/client.cpp b/zone/client.cpp index 1c0226c40..822ea7e4f 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -256,6 +256,7 @@ Client::Client(EQStreamInterface* ieqs) TotalSecondsPlayed = 0; keyring.clear(); bind_sight_target = nullptr; + p_raid_instance = nullptr; mercid = 0; mercSlot = 0; InitializeMercInfo(); @@ -1918,7 +1919,7 @@ void Client::CheckManaEndUpdate() { else if (group) { group->SendEndurancePacketFrom(this); } - + auto endurance_packet = new EQApplicationPacket(OP_EnduranceUpdate, sizeof(EnduranceUpdate_Struct)); EnduranceUpdate_Struct* endurance_update = (EnduranceUpdate_Struct*)endurance_packet->pBuffer; endurance_update->cur_end = GetEndurance(); @@ -8758,6 +8759,11 @@ void Client::CheckRegionTypeChanges() if (last_region_type == new_region) return; + // If we got out of water clear any water aggro for water only npcs + if (last_region_type == RegionTypeWater) { + entity_list.ClearWaterAggro(this); + } + // region type changed last_region_type = new_region; @@ -9199,7 +9205,7 @@ void Client::SetSecondaryWeaponOrnamentation(uint32 model_id) secondary_item->SetOrnamentationIDFile(model_id); SendItemPacket(EQEmu::invslot::slotSecondary, secondary_item, ItemPacketTrade); WearChange(EQEmu::textures::weaponSecondary, static_cast(model_id), 0); - + Message(Chat::Yellow, "Your secondary weapon appearance has been modified"); } } @@ -9288,3 +9294,41 @@ void Client::SetBotOption(BotOwnerOption boo, bool flag) { } #endif + +void Client::SendToGuildHall() +{ + std::string zone_short_name = "guildhall"; + uint32 zone_id = database.GetZoneID(zone_short_name.c_str()); + if (zone_id == 0) { + return; + } + + uint32 expiration_time = (RuleI(Instances, GuildHallExpirationDays) * 86400); + uint16 instance_id = 0; + std::string guild_hall_instance_key = fmt::format("guild-hall-instance-{}", GuildID()); + std::string instance_data = DataBucket::GetData(guild_hall_instance_key); + if (!instance_data.empty() && std::stoi(instance_data) > 0) { + instance_id = std::stoi(instance_data); + } + + if (instance_id <= 0) { + if (!database.GetUnusedInstanceID(instance_id)) { + Message(Chat::Red, "Server was unable to find a free instance id."); + return; + } + + if (!database.CreateInstance(instance_id, zone_id, 1, expiration_time)) { + Message(Chat::Red, "Server was unable to create a new instance."); + return; + } + + DataBucket::SetData( + guild_hall_instance_key, + std::to_string(instance_id), + std::to_string(expiration_time) + ); + } + + AssignToInstance(instance_id); + MovePC(345, instance_id, -1.00, -1.00, 3.34, 0, 1); +} diff --git a/zone/client.h b/zone/client.h index fa863fae7..960656175 100644 --- a/zone/client.h +++ b/zone/client.h @@ -633,6 +633,7 @@ public: void MovePC(uint32 zoneID, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); void MovePC(float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); void MovePC(uint32 zoneID, uint32 instanceID, float x, float y, float z, float heading, uint8 ignorerestrictions = 0, ZoneMode zm = ZoneSolicited); + void SendToGuildHall(); void AssignToInstance(uint16 instance_id); void RemoveFromInstance(uint16 instance_id); void WhoAll(); @@ -691,7 +692,7 @@ public: int GetClientMaxLevel() const { return client_max_level; } void SetClientMaxLevel(int max_level) { client_max_level = max_level; } - + void CheckManaEndUpdate(); void SendManaUpdate(); void SendEnduranceUpdate(); @@ -1294,6 +1295,8 @@ public: void CheckRegionTypeChanges(); + WaterRegionType GetLastRegion() { return last_region_type; } + int32 CalcATK(); uint32 trapid; //ID of trap player has triggered. This is cleared when the player leaves the trap's radius, or it despawns. @@ -1301,6 +1304,8 @@ public: void SetLastPositionBeforeBulkUpdate(glm::vec4 in_last_position_before_bulk_update); glm::vec4 &GetLastPositionBeforeBulkUpdate(); + Raid *p_raid_instance; + protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); @@ -1340,6 +1345,7 @@ protected: char *adv_data; private: + eqFilterMode ClientFilters[_FilterCount]; int32 HandlePacket(const EQApplicationPacket *app); void OPTGB(const EQApplicationPacket *app); @@ -1633,9 +1639,9 @@ private: bool InterrogateInventory_error(int16 head, int16 index, const EQEmu::ItemInstance* inst, const EQEmu::ItemInstance* parent, int depth); int client_max_level; - + #ifdef BOTS - + public: enum BotOwnerOption : size_t { booDeathMarquee, @@ -1652,7 +1658,7 @@ public: bool GetBotOption(BotOwnerOption boo) const; void SetBotOption(BotOwnerOption boo, bool flag = true); - + bool GetBotPulling() { return m_bot_pulling; } void SetBotPulling(bool flag = true) { m_bot_pulling = flag; } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index aa5c11c72..05a51f8a5 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -819,35 +819,45 @@ void Client::CompleteConnect() database.QueryDatabase( StringFormat( "UPDATE `character_data` SET `last_login` = UNIX_TIMESTAMP() WHERE id = %u", - this->CharacterID() + CharacterID() ) ); } - if (zone) { - if (zone->GetInstanceTimer()) { - uint32 ttime = zone->GetInstanceTimer()->GetRemainingTime(); - uint32 day = (ttime / 86400000); - uint32 hour = (ttime / 3600000) % 24; - uint32 minute = (ttime / 60000) % 60; - uint32 second = (ttime / 1000) % 60; - if (day) { - Message(Chat::Yellow, "%s(%u) will expire in %u days, %u hours, %u minutes, and %u seconds.", - zone->GetLongName(), zone->GetInstanceID(), day, hour, minute, second); - } - else if (hour) { - Message(Chat::Yellow, "%s(%u) will expire in %u hours, %u minutes, and %u seconds.", - zone->GetLongName(), zone->GetInstanceID(), hour, minute, second); - } - else if (minute) { - Message(Chat::Yellow, "%s(%u) will expire in %u minutes, and %u seconds.", - zone->GetLongName(), zone->GetInstanceID(), minute, second); - } - else { - Message(Chat::Yellow, "%s(%u) will expire in in %u seconds.", - zone->GetLongName(), zone->GetInstanceID(), second); - } + if (zone && zone->GetInstanceTimer()) { + + bool is_permanent = false; + uint32 remaining_time_seconds = database.GetTimeRemainingInstance(zone->GetInstanceID(), is_permanent); + uint32 day = (remaining_time_seconds / 86400); + uint32 hour = (remaining_time_seconds / 3600) % 24; + uint32 minute = (remaining_time_seconds / 60) % 60; + uint32 second = (remaining_time_seconds / 1) % 60; + + if (day) { + Message( + Chat::Yellow, "%s (%u) will expire in %u days, %u hours, %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), day, hour, minute, second + ); } + else if (hour) { + Message( + Chat::Yellow, "%s (%u) will expire in %u hours, %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), hour, minute, second + ); + } + else if (minute) { + Message( + Chat::Yellow, "%s (%u) will expire in %u minutes, and %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), minute, second + ); + } + else { + Message( + Chat::Yellow, "%s (%u) will expire in in %u seconds.", + zone->GetLongName(), zone->GetInstanceID(), second + ); + } + } SendRewards(); @@ -4018,7 +4028,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) //Message(Chat::Red, "You cant cast right now, you arent in control of yourself!"); return; } - + // Hack for broken RoF2 which allows casting after a zoned IVU/IVA if (invisible_undead || invisible_animals) { BuffFadeByEffect(SE_InvisVsAnimals); diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 6d6f2e0cf..6b99d35d3 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -3314,6 +3314,43 @@ XS(XS__getcharidbyname) { XSRETURN(1); } +XS(XS__getclassname); +XS(XS__getclassname) { + dXSARGS; + if (items < 1 || items > 2) + Perl_croak(aTHX_ "Usage: quest::getclassname(uint8 class_id, [uint8 level = 0])"); + dXSTARG; + + std::string RETVAL; + uint8 class_id = (int) SvUV(ST(0)); + uint8 level = 0; + if (items > 1) + level = (int) SvUV(ST(1)); + + RETVAL = quest_manager.getclassname(class_id, level); + sv_setpv(TARG, RETVAL.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + +XS(XS__getcurrencyitemid); +XS(XS__getcurrencyitemid) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getcurrencyitemid(int currency_id)"); + dXSTARG; + + int RETVAL; + int currency_id = (int) SvUV(ST(0)); + + RETVAL = quest_manager.getcurrencyitemid(currency_id); + + XSprePUSH; + PUSHi((IV)RETVAL); + XSRETURN(1); +} + XS(XS__getcurrencyid); XS(XS__getcurrencyid) { dXSARGS; @@ -3322,12 +3359,11 @@ XS(XS__getcurrencyid) { dXSTARG; int RETVAL; - uint32 item_id = (int) SvUV(ST(0));; + uint32 item_id = (int) SvUV(ST(0)); RETVAL = quest_manager.getcurrencyid(item_id); XSprePUSH; PUSHi((IV)RETVAL); - XSRETURN(1); } @@ -4821,6 +4857,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "forcedoorclose"), XS__forcedoorclose, file); newXS(strcpy(buf, "forcedooropen"), XS__forcedooropen, file); newXS(strcpy(buf, "getcharidbyname"), XS__getcharidbyname, file); + newXS(strcpy(buf, "getclassname"), XS__getclassname, file); newXS(strcpy(buf, "getcurrencyid"), XS__getcurrencyid, file); newXS(strcpy(buf, "getinventoryslotid"), XS__getinventoryslotid, file); newXS(strcpy(buf, "getitemname"), XS__getitemname, file); @@ -4828,6 +4865,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "getnpcnamebyid"), XS__getnpcnamebyid, file); newXS(strcpy(buf, "get_spawn_condition"), XS__get_spawn_condition, file); newXS(strcpy(buf, "getcharnamebyid"), XS__getcharnamebyid, file); + newXS(strcpy(buf, "getcurrencyitemid"), XS__getcurrencyitemid, file); newXS(strcpy(buf, "getguildnamebyid"), XS__getguildnamebyid, file); newXS(strcpy(buf, "getguildidbycharid"), XS__getguildidbycharid, file); newXS(strcpy(buf, "getgroupidbycharid"), XS__getgroupidbycharid, file); diff --git a/zone/entity.cpp b/zone/entity.cpp index b13fb5649..23ff268fc 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -498,7 +498,7 @@ void EntityList::MobProcess() size_t sz = mob_list.size(); #ifdef IDLE_WHEN_EMPTY - if (numclients > 0 || + if (numclients > 0 || mob->GetWanderType() == 4 || mob->GetWanderType() == 6) { // Normal processing, or assuring that spawns that should // path and depop do that. Otherwise all of these type mobs @@ -931,12 +931,12 @@ bool EntityList::MakeDoorSpawnPacket(EQApplicationPacket *app, Client *client) memcpy(new_door.name, door->GetDoorName(), 32); auto position = door->GetPosition(); - + new_door.xPos = position.x; new_door.yPos = position.y; new_door.zPos = position.z; new_door.heading = position.w; - + new_door.incline = door->GetIncline(); new_door.size = door->GetSize(); new_door.doorId = door->GetDoorID(); @@ -1984,17 +1984,26 @@ Raid *EntityList::GetRaidByID(uint32 id) Raid *EntityList::GetRaidByClient(Client* client) { - std::list::iterator iterator; + if (client->p_raid_instance) { + return client->p_raid_instance; + } + std::list::iterator iterator; iterator = raid_list.begin(); while (iterator != raid_list.end()) { - for (int x = 0; x < MAX_RAID_MEMBERS; x++) - if ((*iterator)->members[x].member) - if((*iterator)->members[x].member == client) + for (auto &member : (*iterator)->members) { + if (member.member) { + if (member.member == client) { + client->p_raid_instance = *iterator; return *iterator; + } + } + } + ++iterator; } + return nullptr; } @@ -3277,13 +3286,15 @@ void EntityList::Evade(Mob *who) void EntityList::ClearAggro(Mob* targ) { Client *c = nullptr; - if (targ->IsClient()) + if (targ->IsClient()) { c = targ->CastToClient(); + } auto it = npc_list.begin(); while (it != npc_list.end()) { if (it->second->CheckAggro(targ)) { - if (c) + if (c) { c->RemoveXTarget(it->second, false); + } it->second->RemoveFromHateList(targ); } if (c && it->second->IsOnFeignMemory(c)) { @@ -3294,6 +3305,32 @@ void EntityList::ClearAggro(Mob* targ) } } +//removes "targ" from all hate lists of mobs that are water only. +void EntityList::ClearWaterAggro(Mob* targ) +{ + Client *c = nullptr; + if (targ->IsClient()) { + c = targ->CastToClient(); + } + auto it = npc_list.begin(); + while (it != npc_list.end()) { + if (it->second->IsUnderwaterOnly()) { + if (it->second->CheckAggro(targ)) { + if (c) { + c->RemoveXTarget(it->second, false); + } + it->second->RemoveFromHateList(targ); + } + if (c && it->second->IsOnFeignMemory(c)) { + it->second->RemoveFromFeignMemory(c); //just in case we feigned + c->RemoveXTarget(it->second, false); + } + } + ++it; + } +} + + void EntityList::ClearFeignAggro(Mob *targ) { auto it = npc_list.begin(); diff --git a/zone/entity.h b/zone/entity.h index 505f34963..3c182be29 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -447,6 +447,7 @@ public: void Process(); void ClearAggro(Mob* targ); + void ClearWaterAggro(Mob* targ); void ClearFeignAggro(Mob* targ); void ClearZoneFeignAggro(Client* targ); void AggroZone(Mob* who, uint32 hate = 0); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 9d0873360..955130922 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -90,6 +90,11 @@ void Lua_Client::SetPVP(bool v) { self->SetPVP(v); } +void Lua_Client::SendToGuildHall() { + Lua_Safe_Call_Void(); + self->SendToGuildHall(); +} + bool Lua_Client::GetPVP() { Lua_Safe_Call_Bool(); return self->GetPVP(); @@ -1584,6 +1589,7 @@ luabind::scope lua_register_client() { .def("Disconnect", (void(Lua_Client::*)(void))&Lua_Client::Disconnect) .def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD) .def("WorldKick", (void(Lua_Client::*)(void))&Lua_Client::WorldKick) + .def("SendToGuildHall", (void(Lua_Client::*)(void))&Lua_Client::SendToGuildHall) .def("GetAnon", (bool(Lua_Client::*)(void))&Lua_Client::GetAnon) .def("Duck", (void(Lua_Client::*)(void))&Lua_Client::Duck) .def("Stand", (void(Lua_Client::*)(void))&Lua_Client::Stand) diff --git a/zone/lua_client.h b/zone/lua_client.h index aef0378cb..b0eb719e8 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -39,6 +39,7 @@ public: void Disconnect(); bool IsLD(); void WorldKick(); + void SendToGuildHall(); bool GetAnon(); void Duck(); void Stand(); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 7bf5045af..18f3bf8e1 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -887,10 +887,22 @@ uint32 lua_get_char_id_by_name(const char* name) { return quest_manager.getcharidbyname(name); } +std::string lua_get_class_name(uint8 class_id) { + return quest_manager.getclassname(class_id); +} + +std::string lua_get_class_name(uint8 class_id, uint8 level) { + return quest_manager.getclassname(class_id, level); +} + int lua_get_currency_id(uint32 item_id) { return quest_manager.getcurrencyid(item_id); } +int lua_get_currency_item_id(int currency_id) { + return quest_manager.getcurrencyitemid(currency_id); +} + const char *lua_get_guild_name_by_id(uint32 guild_id) { return quest_manager.getguildnamebyid(guild_id); } @@ -2012,7 +2024,10 @@ luabind::scope lua_register_general() { luabind::def("delete_data", (bool(*)(std::string))&lua_delete_data), luabind::def("get_char_name_by_id", &lua_get_char_name_by_id), luabind::def("get_char_id_by_name", (uint32(*)(const char*))&lua_get_char_id_by_name), + luabind::def("get_class_name", (std::string(*)(uint8))&lua_get_class_name), + luabind::def("get_class_name", (std::string(*)(uint8,uint8))&lua_get_class_name), luabind::def("get_currency_id", &lua_get_currency_id), + luabind::def("get_currency_item_id", &lua_get_currency_item_id), luabind::def("get_guild_name_by_id", &lua_get_guild_name_by_id), luabind::def("get_guild_id_by_char_id", &lua_get_guild_id_by_char_id), luabind::def("get_group_id_by_char_id", &lua_get_group_id_by_char_id), diff --git a/zone/npc.cpp b/zone/npc.cpp index e65da4cfb..f65ebefa2 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -694,11 +694,6 @@ void NPC::RemoveCash() { bool NPC::Process() { - if (IsStunned() && stunned_timer.Check()) { - Mob::UnStun(); - this->spun_timer.Disable(); - } - if (p_depop) { Mob* owner = entity_list.GetMob(this->ownerid); @@ -711,6 +706,11 @@ bool NPC::Process() } return false; } + + if (IsStunned() && stunned_timer.Check()) { + Mob::UnStun(); + this->spun_timer.Disable(); + } SpellProcess(); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 2390dea3a..e46b5e8d4 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -245,6 +245,27 @@ XS(XS_Client_WorldKick) { XSRETURN_EMPTY; } +XS(XS_Client_SendToGuildHall); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_SendToGuildHall) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::SendToGuildHall(THIS)"); + { + Client *THIS; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV *) SvRV(ST(0))); + THIS = INT2PTR(Client *, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Client"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SendToGuildHall(); + } + XSRETURN_EMPTY; +} + XS(XS_Client_GetAnon); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_GetAnon) { dXSARGS; @@ -2439,7 +2460,7 @@ XS(XS_Client_MemmedCount) { RETVAL = THIS->MemmedCount(); XSprePUSH; - PUSHu((UV) RETVAL); + PUSHu((UV) RETVAL); } XSRETURN(1); } @@ -4786,7 +4807,7 @@ XS(XS_Client_AddLevelBasedExp) { if (items > 2) max_level = (uint8) SvUV(ST(2)); - + if (items > 3) ignore_mods = (bool) SvTRUE(ST(3)); @@ -6564,6 +6585,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "SendSound"), XS_Client_SendSound, file, "$"); newXSproto(strcpy(buf, "SendSpellAnim"), XS_Client_SendSpellAnim, file, "$$$"); newXSproto(strcpy(buf, "SendTargetCommand"), XS_Client_SendTargetCommand, file, "$$"); + newXSproto(strcpy(buf, "SendToGuildHall"), XS_Client_SendToGuildHall, file, "$"); newXSproto(strcpy(buf, "SendWebLink"), XS_Client_SendWebLink, file, "$:$"); newXSproto(strcpy(buf, "SendZoneFlagInfo"), XS_Client_SendZoneFlagInfo, file, "$$"); newXSproto(strcpy(buf, "SetAAPoints"), XS_Client_SetAAPoints, file, "$$"); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index de09b8193..6929e695d 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2962,6 +2962,10 @@ uint32 QuestManager::getcharidbyname(const char* name) { return database.GetCharacterID(name); } +std::string QuestManager::getclassname(uint8 class_id, uint8 level) { + return GetClassIDName(class_id, level); +} + int QuestManager::getcurrencyid(uint32 item_id) { auto iter = zone->AlternateCurrencies.begin(); while (iter != zone->AlternateCurrencies.end()) { @@ -2973,6 +2977,19 @@ int QuestManager::getcurrencyid(uint32 item_id) { return 0; } +int QuestManager::getcurrencyitemid(int currency_id) { + if (currency_id > 0) { + auto iter = zone->AlternateCurrencies.begin(); + while (iter != zone->AlternateCurrencies.end()) { + if (currency_id == (*iter).id) { + return (*iter).item_id; + } + ++iter; + } + } + return 0; +} + const char* QuestManager::getguildnamebyid(int guild_id) { if (guild_id > 0) return guild_mgr.GetGuildName(guild_id); diff --git a/zone/questmgr.h b/zone/questmgr.h index caed775cc..14ddea9c8 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -259,7 +259,9 @@ public: std::string saylink(char *saylink_text, bool silent, const char *link_name); const char* getcharnamebyid(uint32 char_id); uint32 getcharidbyname(const char* name); + std::string getclassname(uint8 class_id, uint8 level = 0); int getcurrencyid(uint32 item_id); + int getcurrencyitemid(int currency_id); const char* getguildnamebyid(int guild_id); int getguildidbycharid(uint32 char_id); int getgroupidbycharid(uint32 char_id); diff --git a/zone/raids.cpp b/zone/raids.cpp index a4e1535d3..428b17843 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -177,6 +177,7 @@ void Raid::RemoveMember(const char *characterName) if(client) { client->SetRaidGrouped(false); client->LeaveRaidXTargets(this); + client->p_raid_instance = nullptr; } auto pack = new ServerPacket(ServerOP_RaidRemove, sizeof(ServerRaidGeneralAction_Struct)); @@ -1078,8 +1079,9 @@ void Raid::SendRaidRemoveAll(const char *who) void Raid::SendRaidDisband(Client *to) { - if(!to) + if (!to) { return; + } auto outapp = new EQApplicationPacket(OP_RaidUpdate, sizeof(RaidGeneral_Struct)); RaidGeneral_Struct *rg = (RaidGeneral_Struct*)outapp->pBuffer; @@ -1614,7 +1616,7 @@ void Raid::SendHPManaEndPacketsFrom(Mob *mob) return; uint32 group_id = 0; - + if(mob->IsClient()) group_id = this->GetGroup(mob->CastToClient()); @@ -1622,7 +1624,7 @@ void Raid::SendHPManaEndPacketsFrom(Mob *mob) EQApplicationPacket outapp(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); mob->CreateHPPacket(&hpapp); - + for(int x = 0; x < MAX_RAID_MEMBERS; x++) { if(members[x].member) { if(!mob->IsClient() || ((members[x].member != mob->CastToClient()) && (members[x].GroupNumber == group_id))) { @@ -1633,7 +1635,7 @@ void Raid::SendHPManaEndPacketsFrom(Mob *mob) mana_update->spawn_id = mob->GetID(); mana_update->mana = mob->GetManaPercent(); members[x].member->QueuePacket(&outapp, false); - + outapp.SetOpcode(OP_MobEnduranceUpdate); MobEnduranceUpdate_Struct *endurance_update = (MobEnduranceUpdate_Struct *)outapp.pBuffer; endurance_update->endurance = mob->GetEndurancePercent(); diff --git a/zone/spells.cpp b/zone/spells.cpp index be1419caa..d9378a23c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3979,6 +3979,8 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r cd->hit_heading = action->hit_heading; cd->hit_pitch = action->hit_pitch; cd->damage = 0; + + auto spellOwner = GetOwnerOrSelf(); if(!IsEffectInSpell(spell_id, SE_BindAffinity) && !is_damage_or_lifetap_spell){ entity_list.QueueCloseClients( spelltar, /* Sender */ @@ -3989,14 +3991,8 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r true, /* Packet ACK */ (spelltar->IsClient() ? FilterPCSpells : FilterNPCSpells) /* Message Filter Type: (8 or 9) */ ); - } else if (is_damage_or_lifetap_spell && - (IsClient() || - (HasOwner() && - GetOwner()->IsClient() - ) - ) - ) { - (HasOwner() ? GetOwner() : this)->CastToClient()->QueuePacket( + } else if (is_damage_or_lifetap_spell && spellOwner->IsClient()) { + spellOwner->CastToClient()->QueuePacket( message_packet, true, Mob::CLIENT_CONNECTINGALL, diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 71041924f..e7c5ac85f 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -1881,9 +1881,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } case ServerOP_UCSServerStatusReply: { - auto ucsss = (UCSServerStatus_Struct*)pack->pBuffer; - if (zone) + auto ucsss = (UCSServerStatus_Struct *) pack->pBuffer; + if (zone) { zone->SetUCSServerAvailable((ucsss->available != 0), ucsss->timestamp); + LogInfo("UCS Server is now [{}]", (ucsss->available == 1 ? "online" : "offline")); + } break; } case ServerOP_CZSetEntityVariableByNPCTypeID: diff --git a/zone/zone.cpp b/zone/zone.cpp index 8498b865b..6c3b57950 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -584,7 +584,6 @@ void Zone::GetMerchantDataForZoneLoad() { std::map >::iterator merchant_list; uint32 npc_id = 0; - if (results.RowCount() == 0) { LogDebug("No Merchant Data found for [{}]", GetShortName()); return; @@ -809,7 +808,7 @@ void Zone::Shutdown(bool quiet) if (RuleB(Zone, KillProcessOnDynamicShutdown)) { LogInfo("[KillProcessOnDynamicShutdown] Shutting down"); - std::exit(EXIT_SUCCESS); + EQ::EventLoop::Get().Shutdown(); } } @@ -2562,3 +2561,13 @@ Timer Zone::GetInitgridsTimer() { return initgrids_timer; } + +uint32 Zone::GetInstanceTimeRemaining() const +{ + return instance_time_remaining; +} + +void Zone::SetInstanceTimeRemaining(uint32 instance_time_remaining) +{ + Zone::instance_time_remaining = instance_time_remaining; +} diff --git a/zone/zone.h b/zone/zone.h index bf986e6a1..728b19e3e 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -285,6 +285,8 @@ public: ZonePoint *GetClosestZonePointWithoutZone(float x, float y, float z, Client *client, float max_distance = 40000.0f); Timer GetInitgridsTimer(); + uint32 GetInstanceTimeRemaining() const; + void SetInstanceTimeRemaining(uint32 instance_time_remaining); /** * GMSay Callback for LogSys @@ -368,6 +370,7 @@ private: uint8 zone_type; uint16 instanceversion; uint32 instanceid; + uint32 instance_time_remaining; uint32 pgraveyard_id, pgraveyard_zoneid; uint32 pMaxClients; uint32 zoneid;