Merge branch 'master' of https://github.com/EQEmu/Server into integration/multi-tenancy-expansions-repository

This commit is contained in:
Akkadius 2020-04-18 02:31:43 -05:00
commit a0f8bbb3b9
34 changed files with 513 additions and 177 deletions

1
.gitignore vendored
View File

@ -52,4 +52,5 @@ cmake-build-debug/
libs/
bin/
/Win32
/x64
/client_files/**/CMakeFiles/

View File

@ -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)

View File

@ -101,14 +101,20 @@ bool Database::CheckInstanceExpired(uint16 instance_id)
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();
@ -116,23 +122,28 @@ bool Database::CheckInstanceExpired(uint16 instance_id)
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<uint32> &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)

View File

@ -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

View File

@ -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;
}
}

View File

@ -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<ChatChannel*> iterator(ChatChannels);
@ -175,7 +180,7 @@ void ChatChannelList::RemoveChannel(ChatChannel *Channel) {
void ChatChannelList::RemoveAllChannels() {
LogInfo("RemoveAllChannels");
LogDebug("RemoveAllChannels");
LinkedListIterator<ChatChannel*> 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<Client*> 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<uint32>(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());
}
}

View File

@ -235,7 +235,7 @@ std::vector<std::string> 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<std::string> 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,17 +2366,16 @@ void Client::SendFriends() {
}
}
std::string Client::MailBoxName() {
if ((Characters.empty()) || (CurrentMailBox > (Characters.size() - 1)))
{
LogInfo("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]",
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());
return "";
return std::string();
}
LogInfo("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]",
LogDebug("MailBoxName() called with CurrentMailBox set to [{}] and Characters.size() is [{}]",
CurrentMailBox, Characters.size());
return Characters[CurrentMailBox].Name;

View File

@ -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);

View File

@ -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;

View File

@ -70,6 +70,7 @@ int main() {
// Check every minute for unused channels we can delete
//
Timer ChannelListProcessTimer(60000);
Timer ClientConnectionPruneTimer(60000);
Timer InterserverTimer(INTERSERVER_TIMER); // does auto-reconnect
@ -144,20 +145,27 @@ 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();
Sleep(5);
}
if (ClientConnectionPruneTimer.Check()) {
g_Clientlist->CheckForStaleConnectionsAll();
}
};
EQ::Timer process_timer(loop_fn);
process_timer.Start(32, true);
EQ::EventLoop::Get().Run();
ChannelList->RemoveAllChannels();
g_Clientlist->CloseAllConnections();

View File

@ -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)
{

View File

@ -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

View File

@ -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<EQ::Net::ServertalkServerConnection> 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();
}
Stream = inStream;
if (Stream) {
Stream->OnMessage(std::bind(&UCSConnection::ProcessPacket, this, std::placeholders::_1, std::placeholders::_2));
connection = inStream;
if (connection) {
connection->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);
}

View File

@ -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 <memory>
class UCSConnection
@ -13,11 +14,17 @@ public:
void SetConnection(std::shared_ptr<EQ::Net::ServertalkServerConnection> 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<EQ::Net::ServertalkServerConnection> Stream;
inline std::string GetIP() const { return (connection && connection->Handle()) ? connection->Handle()->RemoteIP() : 0; }
std::shared_ptr<EQ::Net::ServertalkServerConnection> connection;
/**
* Keepalive
*/
std::unique_ptr<EQ::Timer> m_keepalive;
void OnKeepAlive(EQ::Timer *t);
};
#endif /*UCS_H_*/

View File

@ -32,6 +32,7 @@
#endif
#include "map.h"
#include "water_map.h"
extern Zone* zone;
//#define LOSDEBUG 6
@ -238,6 +239,11 @@ bool Mob::CheckWillAggro(Mob *mob) {
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
*/

View File

@ -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();
@ -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;
@ -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);
}

View File

@ -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();
@ -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);

View File

@ -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 (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);
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);
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);
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);
}
Message(
Chat::Yellow, "%s (%u) will expire in in %u seconds.",
zone->GetLongName(), zone->GetInstanceID(), second
);
}
}
SendRewards();

View File

@ -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);

View File

@ -1984,17 +1984,26 @@ Raid *EntityList::GetRaidByID(uint32 id)
Raid *EntityList::GetRaidByClient(Client* client)
{
std::list<Raid *>::iterator iterator;
if (client->p_raid_instance) {
return client->p_raid_instance;
}
std::list<Raid *>::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();

View File

@ -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);

View File

@ -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)

View File

@ -39,6 +39,7 @@ public:
void Disconnect();
bool IsLD();
void WorldKick();
void SendToGuildHall();
bool GetAnon();
void Duck();
void Stand();

View File

@ -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),

View File

@ -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);
@ -712,6 +707,11 @@ bool NPC::Process()
return false;
}
if (IsStunned() && stunned_timer.Check()) {
Mob::UnStun();
this->spun_timer.Disable();
}
SpellProcess();
if (mob_scan_close.Check()) {

View File

@ -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;
@ -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, "$$");

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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:

View File

@ -584,7 +584,6 @@ void Zone::GetMerchantDataForZoneLoad() {
std::map<uint32, std::list<MerchantList> >::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;
}

View File

@ -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;