Implement initial expedition system

Add Expeditions logging category

Add handlers for all Dynamic Zone/Expedition related opcodes

Add FormatName string_util function to format character names

Add Zone::IsZone helper method

Add cross zone MessageString support with variable parameters

Add static Client method helpers for cross zone messaging

Add #dz gm command to debug expedition cache for current zone
This commit is contained in:
hg 2020-04-14 17:18:54 -04:00
parent a77f8b582e
commit da067be2fa
31 changed files with 4011 additions and 12 deletions

View File

@ -118,6 +118,7 @@ namespace Logs {
Merchants,
ZonePoints,
Loot,
Expeditions,
MaxCategoryID /* Don't Remove this */
};
@ -194,7 +195,8 @@ namespace Logs {
"HotReload",
"Merchants",
"ZonePoints",
"Loot"
"Loot",
"Expeditions",
};
}

View File

@ -601,6 +601,21 @@
OutF(LogSys, Logs::Detail, Logs::Loot, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogExpeditions(message, ...) do {\
if (LogSys.log_settings[Logs::Expeditions].is_category_enabled == 1)\
OutF(LogSys, Logs::General, Logs::Expeditions, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogExpeditionsModerate(message, ...) do {\
if (LogSys.log_settings[Logs::Expeditions].is_category_enabled == 1)\
OutF(LogSys, Logs::Moderate, Logs::Expeditions, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogExpeditionsDetail(message, ...) do {\
if (LogSys.log_settings[Logs::Expeditions].is_category_enabled == 1)\
OutF(LogSys, Logs::Detail, Logs::Expeditions, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.log_settings[log_category].is_category_enabled == 1)\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
@ -952,6 +967,15 @@
#define LogZonePointsDetail(message, ...) do {\
} while (0)
#define LogExpeditions(message, ...) do {\
} while (0)
#define LogExpeditionsModerate(message, ...) do {\
} while (0)
#define LogExpeditionsDetail(message, ...) do {\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
} while (0)

View File

@ -785,6 +785,11 @@ RULE_BOOL(Instances, RecycleInstanceIds, true, "Setting whether free instance ID
RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires")
RULE_CATEGORY_END()
RULE_CATEGORY(Expedition)
RULE_INT(Expedition, MinStatusToBypassPlayerCountRequirements, 80, "Minimum GM status to bypass minimum player requirements for Expedition creation")
RULE_BOOL(Expedition, UseDatabaseToVerifyLeaderCommands, false, "Use database instead of zone cache to verify Expedition leader for commands")
RULE_CATEGORY_END()
#undef RULE_CATEGORY
#undef RULE_INT
#undef RULE_REAL

View File

@ -140,6 +140,18 @@
#define ServerOP_LFPUpdate 0x0213
#define ServerOP_LFPMatches 0x0214
#define ServerOP_ClientVersionSummary 0x0215
#define ServerOP_ExpeditionCreate 0x0400
#define ServerOP_ExpeditionDeleted 0x0401
#define ServerOP_ExpeditionLeaderChanged 0x0402
#define ServerOP_ExpeditionLockout 0x0403
#define ServerOP_ExpeditionMemberChange 0x0404
#define ServerOP_ExpeditionMemberSwap 0x0405
#define ServerOP_ExpeditionMemberStatus 0x0406
#define ServerOP_ExpeditionGetOnlineMembers 0x0407
#define ServerOP_ExpeditionDzAddPlayer 0x0408
#define ServerOP_ExpeditionDzMakeLeader 0x0409
#define ServerOP_LSInfo 0x1000
#define ServerOP_LSStatus 0x1001
#define ServerOP_LSClientAuthLeg 0x1002
@ -257,6 +269,8 @@
#define ServerOP_CZTaskRemoveGroup 0x4560
#define ServerOP_CZTaskRemoveRaid 0x4561
#define ServerOP_CZTaskRemoveGuild 0x4562
#define ServerOP_CZClientMessage 0x4563
#define ServerOP_CZClientMessageString 0x4564
#define ServerOP_WWAssignTask 0x4750
#define ServerOP_WWCastSpell 0x4751
@ -1958,6 +1972,87 @@ struct UCSServerStatus_Struct {
};
};
struct ServerCZClientMessage_Struct {
uint16 chat_type;
char character_name[64];
uint32 message_size;
char message[1];
};
struct ServerCZClientMessageString_Struct {
uint32 string_id;
uint16 chat_type;
char character_name[64];
uint32 string_params_size;
char string_params[1]; // null delimited
};
struct ServerExpeditionID_Struct {
uint32 expedition_id;
uint32 sender_zone_id;
uint32 sender_instance_id;
};
struct ServerExpeditionMemberChange_Struct {
uint32 expedition_id;
uint32 sender_zone_id;
uint16 sender_instance_id;
uint8 removed; // 0: added, 1: removed
uint32 char_id;
char char_name[64];
};
struct ServerExpeditionMemberSwap_Struct {
uint32 expedition_id;
uint32 sender_zone_id;
uint16 sender_instance_id;
uint32 add_char_id;
uint32 remove_char_id;
char add_char_name[64];
char remove_char_name[64];
};
struct ServerExpeditionMemberStatus_Struct {
uint32 expedition_id;
uint32 sender_zone_id;
uint16 sender_instance_id;
uint8 status; // 0: unknown 1: Online 2: Offline 3: In Dynamic Zone 4: Link Dead
uint32 character_id;
};
struct ServerExpeditionCharacterEntry_Struct {
uint32 character_id;
uint32 character_zone_id;
uint16 character_instance_id;
uint8 character_online; // 0: offline 1: online
};
struct ServerExpeditionCharacters_Struct {
uint32 expedition_id;
uint32 sender_zone_id;
uint16 sender_instance_id;
uint32 count;
ServerExpeditionCharacterEntry_Struct entries[0];
};
struct ServerExpeditionLockout_Struct {
uint32 expedition_id;
uint64 expire_time;
uint32 duration;
uint32 sender_zone_id;
uint16 sender_instance_id;
uint8 remove;
char event_name[256];
};
struct ServerDzCommand_Struct {
uint32 expedition_id;
uint8 is_char_online; // 0: target name is offline, 1: online
char requester_name[64];
char target_name[64];
char remove_name[64]; // used for swap command
};
#pragma pack()
#endif

View File

@ -592,3 +592,14 @@ std::string numberToWords(unsigned long long int n)
return res;
}
std::string FormatName(const std::string& char_name)
{
std::string formatted(char_name);
if (!formatted.empty())
{
std::transform(formatted.begin(), formatted.end(), formatted.begin(), ::tolower);
formatted[0] = ::toupper(formatted[0]);
}
return formatted;
}

View File

@ -206,6 +206,6 @@ void MakeLowerString(const char *source, char *target);
void RemoveApostrophes(std::string &s);
std::string convert2digit(int n, std::string suffix);
std::string numberToWords(unsigned long long int n);
std::string FormatName(const std::string& char_name);
#endif

View File

@ -9,6 +9,7 @@ SET(world_sources
console.cpp
eql_config.cpp
eqemu_api_world_data_service.cpp
expedition.cpp
launcher_link.cpp
launcher_list.cpp
lfplist.cpp
@ -39,6 +40,7 @@ SET(world_headers
console.h
eql_config.h
eqemu_api_world_data_service.h
expedition.h
launcher_link.h
launcher_list.h
lfplist.h

136
world/expedition.cpp Normal file
View File

@ -0,0 +1,136 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "expedition.h"
#include "clientlist.h"
#include "cliententry.h"
#include "zonelist.h"
#include "zoneserver.h"
#include "worlddb.h"
#include "../common/servertalk.h"
#include "../common/string_util.h"
extern ClientList client_list;
extern ZSList zoneserver_list;
void Expedition::PurgeEmptyExpeditions()
{
std::string query = SQL(
DELETE expedition FROM expedition_details expedition
LEFT JOIN (
SELECT expedition_id, COUNT(IF(is_current_member = TRUE, 1, NULL)) member_count
FROM expedition_members
GROUP BY expedition_id
) AS expedition_members
ON expedition_members.expedition_id = expedition.id
WHERE expedition_members.expedition_id IS NULL OR expedition_members.member_count <= 0
);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to purge empty expeditions");
}
}
void Expedition::PurgeExpiredCharacterLockouts()
{
std::string query = SQL(
DELETE FROM expedition_character_lockouts
WHERE expire_time <= NOW();
);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to purge expired lockouts");
}
}
void Expedition::AddPlayer(ServerPacket* pack)
{
auto buf = reinterpret_cast<ServerDzCommand_Struct*>(pack->pBuffer);
ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name);
if (invited_cle && invited_cle->Server())
{
// continue in the add target's zone
buf->is_char_online = true;
invited_cle->Server()->SendPacket(pack);
}
else
{
// add target not online, return to inviter
ClientListEntry* inviter_cle = client_list.FindCharacter(buf->requester_name);
if (inviter_cle && inviter_cle->Server())
{
inviter_cle->Server()->SendPacket(pack);
}
}
}
void Expedition::MakeLeader(ServerPacket* pack)
{
auto buf = reinterpret_cast<ServerDzCommand_Struct*>(pack->pBuffer);
// notify requester (old leader) and new leader of the result
ZoneServer* new_leader_zs = nullptr;
ClientListEntry* new_leader_cle = client_list.FindCharacter(buf->target_name);
if (new_leader_cle && new_leader_cle->Server())
{
buf->is_char_online = true;
new_leader_zs = new_leader_cle->Server();
new_leader_zs->SendPacket(pack);
}
// if old and new leader are in the same zone only send one message
ClientListEntry* requester_cle = client_list.FindCharacter(buf->requester_name);
if (requester_cle && requester_cle->Server() && requester_cle->Server() != new_leader_zs)
{
requester_cle->Server()->SendPacket(pack);
}
}
void Expedition::GetOnlineMembers(ServerPacket* pack)
{
auto buf = reinterpret_cast<ServerExpeditionCharacters_Struct*>(pack->pBuffer);
// not efficient but only requested during caching
char zone_name[64] = {0};
std::vector<ClientListEntry*> all_clients;
all_clients.reserve(client_list.GetClientCount());
client_list.GetClients(zone_name, all_clients);
for (uint32_t i = 0; i < buf->count; ++i)
{
for (const auto& cle : all_clients)
{
if (cle && cle->CharID() == buf->entries[i].character_id)
{
buf->entries[i].character_zone_id = cle->zone();
buf->entries[i].character_instance_id = cle->instance();
buf->entries[i].character_online = true;
break;
}
}
}
zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack);
}

35
world/expedition.h Normal file
View File

@ -0,0 +1,35 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef WORLD_EXPEDITION_H
#define WORLD_EXPEDITION_H
class ServerPacket;
namespace Expedition
{
void PurgeEmptyExpeditions();
void PurgeExpiredCharacterLockouts();
void AddPlayer(ServerPacket* pack);
void MakeLeader(ServerPacket* pack);
void GetOnlineMembers(ServerPacket* pack);
};
#endif

View File

@ -88,6 +88,7 @@ union semun {
#include "queryserv.h"
#include "web_interface.h"
#include "console.h"
#include "expedition.h"
#include "../common/net/servertalk_server.h"
#include "../zone/data_bucket.h"
@ -429,6 +430,10 @@ int main(int argc, char** argv) {
Timer PurgeInstanceTimer(450000);
PurgeInstanceTimer.Start(450000);
LogInfo("Purging expired expeditions");
Expedition::PurgeEmptyExpeditions(); //database.PurgeExpiredExpeditions();
Expedition::PurgeExpiredCharacterLockouts();
LogInfo("Loading char create info");
content_db.LoadCharacterCreateAllocations();
content_db.LoadCharacterCreateCombos();
@ -599,6 +604,8 @@ int main(int argc, char** argv) {
if (PurgeInstanceTimer.Check()) {
database.PurgeExpiredInstances();
database.PurgeAllDeletedDataBuckets();
Expedition::PurgeEmptyExpeditions();
Expedition::PurgeExpiredCharacterLockouts();
}
if (EQTimeTimer.Check()) {

View File

@ -36,6 +36,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "ucs.h"
#include "queryserv.h"
#include "world_store.h"
#include "expedition.h"
extern ClientList client_list;
extern GroupLFPList LFPGroupList;
@ -1355,6 +1356,44 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
cle->ProcessTellQueue();
break;
}
case ServerOP_CZClientMessage:
{
auto buf = reinterpret_cast<ServerCZClientMessage_Struct*>(pack->pBuffer);
client_list.SendPacket(buf->character_name, pack);
break;
}
case ServerOP_CZClientMessageString:
{
auto buf = reinterpret_cast<ServerCZClientMessageString_Struct*>(pack->pBuffer);
client_list.SendPacket(buf->character_name, pack);
break;
}
case ServerOP_ExpeditionCreate:
case ServerOP_ExpeditionDeleted:
case ServerOP_ExpeditionLeaderChanged:
case ServerOP_ExpeditionLockout:
case ServerOP_ExpeditionMemberChange:
case ServerOP_ExpeditionMemberSwap:
case ServerOP_ExpeditionMemberStatus:
{
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_ExpeditionGetOnlineMembers:
{
Expedition::GetOnlineMembers(pack);
break;
}
case ServerOP_ExpeditionDzAddPlayer:
{
Expedition::AddPlayer(pack);
break;
}
case ServerOP_ExpeditionDzMakeLeader:
{
Expedition::MakeLeader(pack);
break;
}
default:
{
LogInfo("Unknown ServerOPcode from zone {:#04x}, size [{}]", pack->opcode, pack->size);

View File

@ -30,6 +30,10 @@ SET(zone_sources
encounter.cpp
entity.cpp
exp.cpp
expedition.cpp
expedition_database.cpp
expedition_lockout_timer.cpp
expedition_request.cpp
fastmath.cpp
fearpath.cpp
forage.cpp
@ -172,6 +176,10 @@ SET(zone_headers
entity.h
errmsg.h
event_codes.h
expedition.h
expedition_database.h
expedition_lockout_timer.h
expedition_request.h
fastmath.h
forage.h
global_loot_manager.h

View File

@ -40,6 +40,9 @@ extern volatile bool RunLoops;
#include "../common/data_verification.h"
#include "../common/profanity_manager.h"
#include "data_bucket.h"
#include "expedition.h"
#include "expedition_database.h"
#include "expedition_lockout_timer.h"
#include "position.h"
#include "worldserver.h"
#include "zonedb.h"
@ -3201,6 +3204,27 @@ void Client::MessageString(uint32 type, uint32 string_id, const char* message1,
safe_delete(outapp);
}
void Client::MessageString(const ServerCZClientMessageString_Struct* msg)
{
if (msg)
{
if (msg->string_params_size == 0)
{
MessageString(msg->chat_type, msg->string_id);
}
else
{
uint32_t outsize = sizeof(FormattedMessage_Struct) + msg->string_params_size;
auto outapp = std::unique_ptr<EQApplicationPacket>(new EQApplicationPacket(OP_FormattedMessage, outsize));
auto outbuf = reinterpret_cast<FormattedMessage_Struct*>(outapp->pBuffer);
outbuf->string_id = msg->string_id;
outbuf->type = msg->chat_type;
memcpy(outbuf->message, msg->string_params, msg->string_params_size);
QueuePacket(outapp.get());
}
}
}
// helper function, returns true if we should see the message
bool Client::FilteredMessageCheck(Mob *sender, eqFilterType filter)
{
@ -3397,6 +3421,13 @@ void Client::LinkDead()
if(raid){
raid->MemberZoned(this);
}
Expedition* expedition = GetExpedition();
if (expedition)
{
expedition->SetMemberStatus(this, ExpeditionMemberStatus::LinkDead);
}
// save_timer.Start(2500);
linkdead_timer.Start(RuleI(Zone,ClientLinkdeadMS));
SendAppearancePacket(AT_Linkdead, 1);
@ -6124,17 +6155,20 @@ void Client::CheckEmoteHail(Mob *target, const char* message)
void Client::MarkSingleCompassLoc(float in_x, float in_y, float in_z, uint8 count)
{
uint32 entry_size = sizeof(DynamicZoneCompassEntry_Struct) * count;
auto outapp = new EQApplicationPacket(OP_DzCompass, sizeof(DynamicZoneCompass_Struct) + entry_size);
auto outbuf = reinterpret_cast<DynamicZoneCompass_Struct*>(outapp->pBuffer);
auto outapp = new EQApplicationPacket(OP_DzCompass, sizeof(ExpeditionInfo_Struct) +
sizeof(DynamicZoneCompassEntry_Struct) * count);
DynamicZoneCompass_Struct *ecs = (DynamicZoneCompass_Struct*)outapp->pBuffer;
//ecs->clientid = GetID();
ecs->count = count;
outbuf->client_id = 0;
outbuf->count = count;
if (count) {
ecs->entries[0].x = in_x;
ecs->entries[0].y = in_y;
ecs->entries[0].z = in_z;
outbuf->entries[0].dz_zone_id = 0;
outbuf->entries[0].dz_instance_id = 0;
outbuf->entries[0].dz_type = 0;
outbuf->entries[0].x = in_x;
outbuf->entries[0].y = in_y;
outbuf->entries[0].z = in_z;
}
FastQueuePacket(&outapp);
@ -9459,3 +9493,254 @@ void Client::ShowDevToolsMenu()
void Client::SendChatLineBreak(uint16 color) {
Message(color, "------------------------------------------------");
}
void Client::SendCrossZoneMessage(
Client* client, const std::string& character_name, uint16_t chat_type, const std::string& message)
{
// if client is null, falls back to sending a cross zone message by name
if (!client)
{
client = entity_list.GetClientByName(character_name.c_str());
}
if (client)
{
client->Message(chat_type, message.c_str());
}
else if (message.size() > 0)
{
uint32_t msg_size = static_cast<uint32_t>(message.size()) + 1;
uint32_t pack_size = sizeof(ServerCZClientMessage_Struct) + msg_size;
auto pack = std::unique_ptr<ServerPacket>(new ServerPacket(ServerOP_CZClientMessage, pack_size));
auto buf = reinterpret_cast<ServerCZClientMessage_Struct*>(pack->pBuffer);
buf->chat_type = chat_type;
strn0cpy(buf->character_name, character_name.c_str(), sizeof(buf->character_name));
buf->message_size = msg_size;
strn0cpy(buf->message, message.c_str(), buf->message_size);
worldserver.SendPacket(pack.get());
}
}
void Client::SendCrossZoneMessageString(
Client* client, const std::string& character_name, uint16_t chat_type,
uint32_t string_id, const std::initializer_list<std::string>& parameters)
{
// if client is null, falls back to sending a cross zone message by name
SerializeBuffer parameter_buffer;
for (const auto& parameter : parameters)
{
parameter_buffer.WriteString(parameter);
}
uint32_t pack_size = sizeof(ServerCZClientMessageString_Struct) + static_cast<uint32_t>(parameter_buffer.size());
auto pack = std::unique_ptr<ServerPacket>(new ServerPacket(ServerOP_CZClientMessageString, pack_size));
auto buf = reinterpret_cast<ServerCZClientMessageString_Struct*>(pack->pBuffer);
buf->string_id = string_id;
buf->chat_type = chat_type;
strn0cpy(buf->character_name, character_name.c_str(), sizeof(buf->character_name));
buf->string_params_size = static_cast<uint32_t>(parameter_buffer.size());
buf->string_params[0] = '\0';
if (parameter_buffer.size()) {
memcpy(buf->string_params, parameter_buffer.buffer(), parameter_buffer.size());
}
if (!client) // double check client isn't in this zone
{
client = entity_list.GetClientByName(character_name.c_str());
}
if (client)
{
client->MessageString(buf);
}
else
{
worldserver.SendPacket(pack.get());
}
}
void Client::UpdateExpeditionInfoAndLockouts()
{
// this is processed by client after entering a zone
// todo: live re-invites if client zoned with a pending invite window open
auto expedition = GetExpedition();
if (expedition)
{
expedition->SendClientExpeditionInfo(this);
// live only adds lockouts obtained during the active expedition to new
// members once they zone into the expedition's dynamic zone instance
if (zone && /*zone->GetInstanceID() && zone->GetInstanceID()*/zone->GetZoneID() == expedition->GetInstanceID())
{
ExpeditionDatabase::AssignPendingLockouts(CharacterID(), expedition->GetName());
expedition->SetMemberStatus(this, ExpeditionMemberStatus::InDynamicZone);
}
else
{
expedition->SetMemberStatus(this, ExpeditionMemberStatus::Online);
}
}
Expedition::LoadAllClientLockouts(this);
}
Expedition* Client::CreateExpedition(
std::string name, uint32 min_players, uint32 max_players, bool has_replay_timer)
{
return Expedition::TryCreate(this, name, min_players, max_players, has_replay_timer);
}
Expedition* Client::GetExpedition() const
{
if (zone && m_expedition_id)
{
auto expedition_cache_iter = zone->expedition_cache.find(m_expedition_id);
if (expedition_cache_iter != zone->expedition_cache.end())
{
return expedition_cache_iter->second.get();
}
}
return nullptr;
}
std::vector<ExpeditionLockoutTimer> Client::GetExpeditionLockouts(const std::string& expedition_name)
{
std::vector<ExpeditionLockoutTimer> lockouts;
for (const auto& lockout : m_expedition_lockouts)
{
if (lockout.GetExpeditionName() == expedition_name)
{
lockouts.emplace_back(lockout);
}
}
return lockouts;
}
void Client::DzListTimers()
{
// only lists player's current replay timer lockouts, not all event lockouts
bool found = false;
for (const auto& lockout : m_expedition_lockouts)
{
if (lockout.IsReplayTimer())
{
found = true;
auto time_remaining = lockout.GetDaysHoursMinutesRemaining();
MessageString(
Chat::Yellow, DZLIST_REPLAY_TIMER,
time_remaining.days.c_str(), time_remaining.hours.c_str(), time_remaining.mins.c_str(),
lockout.GetExpeditionName().c_str()
);
}
}
if (!found)
{
MessageString(Chat::Yellow, EXPEDITION_NO_TIMERS);
}
}
void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db)
{
// todo: support for account based lockouts like live AoC expeditions
auto it = std::find_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(),
[&](const ExpeditionLockoutTimer& existing_lockout) {
return existing_lockout.IsSameLockout(lockout);
});
if (it != m_expedition_lockouts.end())
{
it->SetExpireTime(lockout.GetExpireTime());
}
else
{
m_expedition_lockouts.emplace_back(lockout);
}
if (update_db) { // for quest api
ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { lockout }, true);
}
}
void Client::AddNewExpeditionLockout(
const std::string& expedition_name, const std::string& event_name, uint32_t seconds)
{
auto expire_at = std::chrono::system_clock::now() + std::chrono::seconds(seconds);
auto expire_time = static_cast<uint64_t>(std::chrono::system_clock::to_time_t(expire_at));
ExpeditionLockoutTimer lockout{ expedition_name, event_name, expire_time, seconds };
AddExpeditionLockout(lockout, true);
SendExpeditionLockoutTimers();
}
void Client::RemoveExpeditionLockout(
const std::string& expedition_name, const std::string& event_name, bool update_db)
{
m_expedition_lockouts.erase(std::remove_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(),
[&](const ExpeditionLockoutTimer& lockout) {
return lockout.IsSameLockout(expedition_name, event_name);
}
), m_expedition_lockouts.end());
if (update_db) { // for quest api
ExpeditionDatabase::DeleteCharacterLockout(CharacterID(), expedition_name, event_name);
}
}
const ExpeditionLockoutTimer* Client::GetExpeditionLockout(
const std::string& expedition_name, const std::string& event_name, bool include_expired) const
{
for (const auto& expedition_lockout : m_expedition_lockouts)
{
if ((include_expired || expedition_lockout.GetSecondsRemaining() > 0) &&
expedition_lockout.IsSameLockout(expedition_name, event_name))
{
return &expedition_lockout;
}
}
return nullptr;
}
bool Client::HasExpeditionLockout(
const std::string& expedition_name, const std::string& event_name, bool include_expired)
{
return (GetExpeditionLockout(expedition_name, event_name, include_expired) != nullptr);
}
void Client::SendExpeditionLockoutTimers()
{
std::vector<ExpeditionLockoutTimerEntry_Struct> lockout_entries;
// erases expired lockouts while building lockout timer list
for (auto it = m_expedition_lockouts.begin(); it != m_expedition_lockouts.end();)
{
auto seconds_remaining = it->GetSecondsRemaining();
if (seconds_remaining <= 0)
{
it = m_expedition_lockouts.erase(it);
}
else
{
ExpeditionLockoutTimerEntry_Struct lockout;
strn0cpy(lockout.expedition_name, it->GetExpeditionName().c_str(), sizeof(lockout.expedition_name));
lockout.seconds_remaining = seconds_remaining;
lockout.event_type = it->IsReplayTimer() ? Expedition::REPLAY_TIMER_ID : Expedition::EVENT_TIMER_ID;
strn0cpy(lockout.event_name, it->GetEventName().c_str(), sizeof(lockout.event_name));
lockout_entries.emplace_back(lockout);
++it;
}
}
uint32_t lockout_count = static_cast<uint32_t>(lockout_entries.size());
uint32_t lockout_entries_size = sizeof(ExpeditionLockoutTimerEntry_Struct) * lockout_count;
uint32_t outsize = sizeof(ExpeditionLockoutTimers_Struct) + lockout_entries_size;
auto outapp = std::unique_ptr<EQApplicationPacket>(new EQApplicationPacket(OP_DzExpeditionLockoutTimers, outsize));
auto outbuf = reinterpret_cast<ExpeditionLockoutTimers_Struct*>(outapp->pBuffer);
outbuf->client_id = 0;
outbuf->count = lockout_count;
if (!lockout_entries.empty())
{
memcpy(outbuf->timers, lockout_entries.data(), lockout_entries_size);
}
QueuePacket(outapp.get());
}

View File

@ -21,6 +21,8 @@
class Client;
class EQApplicationPacket;
class EQStream;
class Expedition;
class ExpeditionLockoutTimer;
class Group;
class NPC;
class Object;
@ -283,6 +285,7 @@ public:
uint8 SlotConvert(uint8 slot,bool bracer=false);
void MessageString(uint32 type, uint32 string_id, uint32 distance = 0);
void MessageString(uint32 type, uint32 string_id, const char* message,const char* message2=0,const char* message3=0,const char* message4=0,const char* message5=0,const char* message6=0,const char* message7=0,const char* message8=0,const char* message9=0, uint32 distance = 0);
void MessageString(const ServerCZClientMessageString_Struct* msg);
bool FilteredMessageCheck(Mob *sender, eqFilterType filter);
void FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id);
void FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter,
@ -1104,6 +1107,31 @@ public:
void MarkSingleCompassLoc(float in_x, float in_y, float in_z, uint8 count=1);
// cross zone client messaging helpers (null client argument will fallback to messaging by name)
static void SendCrossZoneMessage(
Client* client, const std::string& client_name, uint16_t chat_type, const std::string& message);
static void SendCrossZoneMessageString(
Client* client, const std::string& client_name, uint16_t chat_type,
uint32_t string_id, const std::initializer_list<std::string>& parameters = {});
void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false);
void AddNewExpeditionLockout(const std::string& expedition_name, const std::string& event_name, uint32_t duration);
Expedition* CreateExpedition(std::string name, uint32 min_players, uint32 max_players, bool has_replay_timer = false);
Expedition* GetExpedition() const;
uint32 GetExpeditionID() const { return m_expedition_id; }
const ExpeditionLockoutTimer* GetExpeditionLockout(const std::string& expedition_name, const std::string& event_name, bool include_expired = false) const;
const std::vector<ExpeditionLockoutTimer>& GetExpeditionLockouts() const { return m_expedition_lockouts; };
std::vector<ExpeditionLockoutTimer> GetExpeditionLockouts(const std::string& expedition_name);
uint32 GetPendingExpeditionInviteID() const { return m_pending_expedition_invite_id; }
bool HasExpeditionLockout(const std::string& expedition_name, const std::string& event_name, bool include_expired = false);
bool IsInExpedition() const { return m_expedition_id != 0; }
void RemoveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, bool update_db = false);
void SetPendingExpeditionInvite(uint32 id) { m_pending_expedition_invite_id = id; }
void SendExpeditionLockoutTimers();
void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; };
void UpdateExpeditionInfoAndLockouts();
void DzListTimers();
void CalcItemScale();
bool CalcItemScale(uint32 slot_x, uint32 slot_y); // behavior change: 'slot_y' is now [RANGE]_END and not [RANGE]_END + 1
void DoItemEnterZone();
@ -1658,6 +1686,11 @@ private:
int client_max_level;
uint32 m_expedition_id = 0;
uint32 m_pending_expedition_invite_id = 0;
Expedition* m_expedition = nullptr;
std::vector<ExpeditionLockoutTimer> m_expedition_lockouts;
#ifdef BOTS
public:

View File

@ -49,6 +49,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/zone_numbers.h"
#include "data_bucket.h"
#include "event_codes.h"
#include "expedition.h"
#include "expedition_database.h"
#include "guild_mgr.h"
#include "merc.h"
#include "petitions.h"
@ -191,6 +193,15 @@ void MapOpcodes()
ConnectedOpcodes[OP_DuelResponse2] = &Client::Handle_OP_DuelResponse2;
ConnectedOpcodes[OP_DumpName] = &Client::Handle_OP_DumpName;
ConnectedOpcodes[OP_Dye] = &Client::Handle_OP_Dye;
ConnectedOpcodes[OP_DzAddPlayer] = &Client::Handle_OP_DzAddPlayer;
ConnectedOpcodes[OP_DzChooseZoneReply] = &Client::Handle_OP_DzChooseZoneReply;
ConnectedOpcodes[OP_DzExpeditionInviteResponse] = &Client::Handle_OP_DzExpeditionInviteResponse;
ConnectedOpcodes[OP_DzListTimers] = &Client::Handle_OP_DzListTimers;
ConnectedOpcodes[OP_DzMakeLeader] = &Client::Handle_OP_DzMakeLeader;
ConnectedOpcodes[OP_DzPlayerList] = &Client::Handle_OP_DzPlayerList;
ConnectedOpcodes[OP_DzRemovePlayer] = &Client::Handle_OP_DzRemovePlayer;
ConnectedOpcodes[OP_DzSwapPlayer] = &Client::Handle_OP_DzSwapPlayer;
ConnectedOpcodes[OP_DzQuit] = &Client::Handle_OP_DzQuit;
ConnectedOpcodes[OP_Emote] = &Client::Handle_OP_Emote;
ConnectedOpcodes[OP_EndLootRequest] = &Client::Handle_OP_EndLootRequest;
ConnectedOpcodes[OP_EnvDamage] = &Client::Handle_OP_EnvDamage;
@ -266,6 +277,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_ItemViewUnknown] = &Client::Handle_OP_Ignore;
ConnectedOpcodes[OP_Jump] = &Client::Handle_OP_Jump;
ConnectedOpcodes[OP_KeyRing] = &Client::Handle_OP_KeyRing;
ConnectedOpcodes[OP_KickPlayers] = &Client::Handle_OP_KickPlayers;
ConnectedOpcodes[OP_LDoNButton] = &Client::Handle_OP_LDoNButton;
ConnectedOpcodes[OP_LDoNDisarmTraps] = &Client::Handle_OP_LDoNDisarmTraps;
ConnectedOpcodes[OP_LDoNInspect] = &Client::Handle_OP_LDoNInspect;
@ -885,6 +897,8 @@ void Client::CompleteConnect()
guild_mgr.RequestOnlineGuildMembers(this->CharacterID(), this->GuildID());
}
UpdateExpeditionInfoAndLockouts();
/** Request adventure info **/
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
strcpy((char*)pack->pBuffer, GetName());
@ -1701,6 +1715,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
/* Task Packets */
LoadClientTaskState();
m_expedition_id = ExpeditionDatabase::GetExpeditionIDFromCharacterID(CharacterID());
/**
* DevTools Load Settings
*/
@ -5604,6 +5620,91 @@ void Client::Handle_OP_Dye(const EQApplicationPacket *app)
return;
}
void Client::Handle_OP_DzAddPlayer(const EQApplicationPacket *app)
{
auto expedition = GetExpedition();
if (expedition)
{
auto dzcmd = reinterpret_cast<ExpeditionCommand_Struct*>(app->pBuffer);
expedition->DzAddPlayer(this, dzcmd->name);
}
else
{
// the only /dz command that sends an error message if no active expedition
Message(Chat::System, DZ_YOU_NOT_ASSIGNED);
}
}
void Client::Handle_OP_DzChooseZoneReply(const EQApplicationPacket *app)
{
// todo: implement
LogExpeditionsModerate("Handle_OP_DzChooseZoneReply");
auto dzmsg = reinterpret_cast<DynamicZoneChooseZoneReply_Struct*>(app->pBuffer);
}
void Client::Handle_OP_DzExpeditionInviteResponse(const EQApplicationPacket *app)
{
auto expedition = Expedition::FindCachedExpeditionByID(m_pending_expedition_invite_id);
m_pending_expedition_invite_id = 0;
if (expedition)
{
auto dzmsg = reinterpret_cast<ExpeditionInviteResponse_Struct*>(app->pBuffer);
expedition->DzInviteResponse(this, dzmsg->accepted, dzmsg->swapping, dzmsg->swap_name);
}
}
void Client::Handle_OP_DzListTimers(const EQApplicationPacket *app)
{
DzListTimers();
}
void Client::Handle_OP_DzMakeLeader(const EQApplicationPacket *app)
{
auto expedition = GetExpedition();
if (expedition)
{
auto dzcmd = reinterpret_cast<ExpeditionCommand_Struct*>(app->pBuffer);
expedition->DzMakeLeader(this, dzcmd->name);
}
}
void Client::Handle_OP_DzPlayerList(const EQApplicationPacket *app)
{
auto expedition = GetExpedition();
if (expedition) {
expedition->DzPlayerList(this);
}
}
void Client::Handle_OP_DzRemovePlayer(const EQApplicationPacket *app)
{
auto expedition = GetExpedition();
if (expedition)
{
auto dzcmd = reinterpret_cast<ExpeditionCommand_Struct*>(app->pBuffer);
expedition->DzRemovePlayer(this, dzcmd->name);
}
}
void Client::Handle_OP_DzSwapPlayer(const EQApplicationPacket *app)
{
auto expedition = GetExpedition();
if (expedition)
{
auto dzcmd = reinterpret_cast<ExpeditionCommandSwap_Struct*>(app->pBuffer);
expedition->DzSwapPlayer(this, dzcmd->rem_player_name, dzcmd->add_player_name);
}
}
void Client::Handle_OP_DzQuit(const EQApplicationPacket *app)
{
auto expedition = GetExpedition();
if (expedition) {
expedition->DzQuit(this);
}
}
void Client::Handle_OP_Emote(const EQApplicationPacket *app)
{
if (app->size != sizeof(Emote_Struct)) {
@ -8874,6 +8975,23 @@ void Client::Handle_OP_KeyRing(const EQApplicationPacket *app)
KeyRingList();
}
void Client::Handle_OP_KickPlayers(const EQApplicationPacket *app)
{
auto buf = reinterpret_cast<KickPlayers_Struct*>(app->pBuffer);
if (buf->kick_expedition)
{
auto expedition = GetExpedition();
if (expedition)
{
expedition->DzKickPlayers(this);
}
}
else if (buf->kick_task)
{
// todo: shared tasks
}
}
void Client::Handle_OP_LDoNButton(const EQApplicationPacket *app)
{
if (app->size < sizeof(bool))

View File

@ -101,6 +101,15 @@
void Handle_OP_DuelResponse2(const EQApplicationPacket *app);
void Handle_OP_DumpName(const EQApplicationPacket *app);
void Handle_OP_Dye(const EQApplicationPacket *app);
void Handle_OP_DzAddPlayer(const EQApplicationPacket *app);
void Handle_OP_DzChooseZoneReply(const EQApplicationPacket *app);
void Handle_OP_DzExpeditionInviteResponse(const EQApplicationPacket *app);
void Handle_OP_DzListTimers(const EQApplicationPacket *app);
void Handle_OP_DzMakeLeader(const EQApplicationPacket *app);
void Handle_OP_DzPlayerList(const EQApplicationPacket *app);
void Handle_OP_DzRemovePlayer(const EQApplicationPacket *app);
void Handle_OP_DzSwapPlayer(const EQApplicationPacket *app);
void Handle_OP_DzQuit(const EQApplicationPacket *app);
void Handle_OP_Emote(const EQApplicationPacket *app);
void Handle_OP_EndLootRequest(const EQApplicationPacket *app);
void Handle_OP_EnvDamage(const EQApplicationPacket *app);
@ -174,6 +183,7 @@
void Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app);
void Handle_OP_Jump(const EQApplicationPacket *app);
void Handle_OP_KeyRing(const EQApplicationPacket *app);
void Handle_OP_KickPlayers(const EQApplicationPacket *app);
void Handle_OP_LDoNButton(const EQApplicationPacket *app);
void Handle_OP_LDoNDisarmTraps(const EQApplicationPacket *app);
void Handle_OP_LDoNInspect(const EQApplicationPacket *app);

View File

@ -44,6 +44,7 @@
#include "../common/spdat.h"
#include "../common/string_util.h"
#include "event_codes.h"
#include "expedition.h"
#include "guild_mgr.h"
#include "map.h"
#include "petitions.h"
@ -560,6 +561,12 @@ bool Client::Process() {
client_state = CLIENT_LINKDEAD;
AI_Start(CLIENT_LD_TIMEOUT);
SendAppearancePacket(AT_Linkdead, 1);
Expedition* expedition = GetExpedition();
if (expedition)
{
expedition->SetMemberStatus(this, ExpeditionMemberStatus::LinkDead);
}
}
}
@ -641,6 +648,11 @@ bool Client::Process() {
myraid->MemberZoned(this);
}
}
Expedition* expedition = GetExpedition();
if (expedition && !bZoning)
{
expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline);
}
OnDisconnect(false);
return false;
}
@ -682,6 +694,12 @@ void Client::OnDisconnect(bool hard_disconnect) {
if (MyRaid)
MyRaid->MemberZoned(this);
Expedition* expedition = GetExpedition();
if (expedition)
{
expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline);
}
parse->EventPlayer(EVENT_DISCONNECT, this, "", 0);
/* QS: PlayerLogConnectDisconnect */

View File

@ -60,6 +60,7 @@
#include "data_bucket.h"
#include "command.h"
#include "expedition.h"
#include "guild_mgr.h"
#include "map.h"
#include "qglobals.h"
@ -198,6 +199,7 @@ int command_init(void)
command_add("disarmtrap", "Analog for ldon disarm trap for the newer clients since we still don't have it working.", 80, command_disarmtrap) ||
command_add("distance", "- Reports the distance between you and your target.", 80, command_distance) ||
command_add("doanim", "[animnum] [type] - Send an EmoteAnim for you or your target", 50, command_doanim) ||
command_add("dz", "Manage expeditions and dynamic zone instances", 80, command_dz) ||
command_add("editmassrespawn", "[name-search] [second-value] - Mass (Zone wide) NPC respawn timer editing command", 100, command_editmassrespawn) ||
command_add("emote", "['name'/'world'/'zone'] [type] [message] - Send an emote message", 80, command_emote) ||
command_add("emotesearch", "Searches NPC Emotes", 80, command_emotesearch) ||
@ -6825,6 +6827,58 @@ void command_doanim(Client *c, const Seperator *sep)
c->DoAnim(atoi(sep->arg[1]),atoi(sep->arg[2]));
}
void command_dz(Client* c, const Seperator* sep)
{
if (!c || !zone) {
return;
}
if (strcasecmp(sep->arg[1], "cache") == 0)
{
if (strcasecmp(sep->arg[2], "list") == 0)
{
c->Message(Chat::White, "Total Active Expeditions: [%u]", static_cast<uint32>(zone->expedition_cache.size()));
for (const auto& expedition : zone->expedition_cache)
{
c->Message(
Chat::White, "Expedition id: [%u]: leader: [%s] instance id: [%u] members: [%u]",
expedition.second->GetID(),
expedition.second->GetLeaderName().c_str(),
expedition.second->GetInstanceID(),
expedition.second->GetMemberCount()
);
}
}
else if (strcasecmp(sep->arg[2], "reload") == 0)
{
Expedition::CacheAllFromDatabase();
c->Message(Chat::White, "Reloaded [%u] expeditions to cache from database.", static_cast<uint32>(zone->expedition_cache.size()));
}
}
else if (strcasecmp(sep->arg[1], "destroy") == 0)
{
if (sep->IsNumber(2))
{
auto expedition_id = std::strtoul(sep->arg[2], nullptr, 10);
if (expedition_id)
{
auto expedition = Expedition::FindCachedExpeditionByID(expedition_id);
if (expedition)
{
expedition->RemoveAllMembers();
}
}
}
}
else
{
c->Message(Chat::White, "#dz usage:");
c->Message(Chat::White, "#dz cache list - list expeditions in current zone cache");
c->Message(Chat::White, "#dz cache reload - reload zone cache from database");
c->Message(Chat::White, "#dz destroy <expedition_id> - destroy expedition globally (must be in cache)");
}
}
void command_editmassrespawn(Client* c, const Seperator* sep)
{
if (strcasecmp(sep->arg[1], "usage") == 0) {

View File

@ -92,6 +92,7 @@ void command_disablerecipe(Client *c, const Seperator *sep);
void command_disarmtrap(Client *c, const Seperator *sep);
void command_distance(Client *c, const Seperator *sep);
void command_doanim(Client *c, const Seperator *sep);
void command_dz(Client *c, const Seperator *sep);
void command_editmassrespawn(Client* c, const Seperator* sep);
void command_emote(Client *c, const Seperator *sep);
void command_emotesearch(Client* c, const Seperator *sep);

1587
zone/expedition.cpp Normal file

File diff suppressed because it is too large Load Diff

188
zone/expedition.h Normal file
View File

@ -0,0 +1,188 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef EXPEDITION_H
#define EXPEDITION_H
#include "expedition_lockout_timer.h"
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
class Client;
class EQApplicationPacket;
class ExpeditionRequest;
class MySQLRequestResult;
class ServerPacket;
extern const char* const DZ_YOU_NOT_ASSIGNED;
extern const char* const EXPEDITION_OTHER_BELONGS;
enum class DynamicZoneType : uint8_t // DynamicZoneActiveType
{
None = 0,
Expedition,
Tutorial,
Task,
Mission,
Quest
};
enum class ExpeditionMemberStatus : uint8_t
{
Unknown = 0,
Online,
Offline,
InDynamicZone,
LinkDead
};
struct ExpeditionMember
{
uint32_t char_id = 0;
std::string name;
ExpeditionMemberStatus status = ExpeditionMemberStatus::Online;
ExpeditionMember() {}
ExpeditionMember(uint32_t char_id_, const std::string& name_) : char_id(char_id_), name(name_) {}
ExpeditionMember(uint32_t char_id_, const std::string& name_, ExpeditionMemberStatus status_)
: char_id(char_id_), name(name_), status(status_) {}
};
class Expedition
{
public:
Expedition() = delete;
Expedition(uint32_t id, std::string expedition_name, const ExpeditionMember& leader,
uint32_t min_players, uint32_t max_players, bool replay_timer);
static Expedition* TryCreate(
Client* requester, std::string name, uint32_t min_players, uint32_t max_players, bool replay_timer);
static void CacheFromDatabase(uint32_t expedition_id);
static bool CacheAllFromDatabase();
static void CacheExpeditions(MySQLRequestResult& results);
static void LoadAllClientLockouts(Client* client);
static Expedition* FindCachedExpeditionByCharacterID(uint32_t character_id);
static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name);
static Expedition* FindCachedExpeditionByID(uint32_t expedition_id);
static Expedition* FindExpeditionByInstanceID(uint32_t instance_id);
static void HandleWorldMessage(ServerPacket* pack);
uint32_t GetID() const { return m_id; }
uint32_t GetLeaderID() const { return m_leader.char_id; }
uint32_t GetMinPlayers() const { return m_min_players; }
uint32_t GetMaxPlayers() const { return m_max_players; }
uint32_t GetMemberCount() const { return static_cast<uint32_t>(m_members.size()); }
const std::string& GetName() const { return m_expedition_name; }
const std::string& GetLeaderName() const { return m_leader.name; }
const std::unordered_map<std::string, ExpeditionLockoutTimer>& GetLockouts() const { return m_lockouts; }
const std::vector<ExpeditionMember>& GetMembers() const { return m_members; }
bool AddMember(const std::string& add_char_name, uint32_t add_char_id);
bool HasMember(const std::string& name);
bool HasMember(uint32_t character_id);
void RemoveAllMembers();
bool RemoveMember(const std::string& remove_char_name);
void SetMemberStatus(Client* client, ExpeditionMemberStatus status);
void SetNewLeader(uint32_t new_leader_id, const std::string& new_leader_name);
void SwapMember(Client* add_client, const std::string& remove_char_name);
void AddLockout(const std::string& event_name, uint32_t seconds);
void AddReplayLockout(uint32_t seconds);
bool HasLockout(const std::string& event_name);
bool HasReplayLockout();
void RemoveLockout(const std::string& event_name);
void SendClientExpeditionInfo(Client* client);
void DzAddPlayer(Client* requester, std::string add_char_name, std::string swap_remove_name = {});
void DzAddPlayerContinue(std::string leader_name, std::string add_char_name, std::string swap_remove_name = {});
void DzInviteResponse(Client* add_client, bool accepted, bool has_swap_name, std::string swap_remove_name);
void DzMakeLeader(Client* requester, std::string new_leader_name);
void DzPlayerList(Client* requester);
void DzRemovePlayer(Client* requester, std::string remove_char_name);
void DzSwapPlayer(Client* requester, std::string remove_char_name, std::string add_char_name);
void DzQuit(Client* requester);
void DzKickPlayers(Client* requester);
#if 0
bool AssignInstance(uint32_t instance_id, bool update_db = true);
uint32_t CreateInstance(std::string zone, uint32_t version, uint32_t duration); // m_dynamiczone
#endif
uint32_t GetInstanceID() const { return 77; /*return m_instance_id;*/ } // todo: GetDynamicZoneID()
DynamicZoneType GetType() const { return DynamicZoneType::Expedition; } // m_dynamiczone
static const uint32_t REPLAY_TIMER_ID;
static const uint32_t EVENT_TIMER_ID;
private:
void AddInternalLockout(ExpeditionLockoutTimer&& lockout_timer);
void AddInternalMember(const std::string& char_name, uint32_t char_id, bool is_current_member = true, bool offline = false);
bool ChooseNewLeader();
bool ConfirmLeaderCommand(Client* requester);
void LoadMembers();
bool ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping);
void ProcessLeaderChanged(uint32_t new_leader_id, const std::string& new_leader_name);
void ProcessLockoutUpdate(const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove);
void ProcessMakeLeader(Client* old_leader, Client* new_leader, const std::string& new_leader_name, bool is_online);
void ProcessMemberAdded(std::string added_char_name, uint32_t added_char_id);
void ProcessMemberRemoved(std::string removed_char_name, uint32_t removed_char_id);
void SaveLockouts(ExpeditionRequest& request);
void SaveMembers(ExpeditionRequest& request);
void SendClientExpeditionInvite(Client* client, const std::string& inviter_name, const std::string& swap_remove_name);
void SendLeaderMessage(Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list<std::string>& parameters = {});
void SendUpdatesToZoneMembers(bool clear = false);
void SendWorldExpeditionUpdate(bool destroyed = false);
void SendWorldGetOnlineMembers();
void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name);
void SendWorldLeaderChanged();
void SendWorldLockoutUpdate(const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove = false);
void SendWorldMakeLeaderRequest(const std::string& requester_name, const std::string& new_leader_name);
void SendWorldMemberChanged(const std::string& char_name, uint32_t char_id, bool remove);
void SendWorldMemberStatus(uint32_t character_id, ExpeditionMemberStatus status);
void SendWorldMemberSwapped(const std::string& remove_char_name, uint32_t remove_char_id, const std::string& add_char_name, uint32_t add_char_id);
void TryAddClient(Client* add_client, std::string inviter_name, std::string orig_add_name, std::string swap_remove_name, Client* leader_client = nullptr);
void UpdateMemberStatus(uint32_t update_character_id, ExpeditionMemberStatus status);
ExpeditionMember GetMemberData(uint32_t character_id);
ExpeditionMember GetMemberData(const std::string& character_name);
std::unique_ptr<EQApplicationPacket> CreateInfoPacket(bool clear = false);
std::unique_ptr<EQApplicationPacket> CreateInvitePacket(const std::string& inviter_name, const std::string& swap_remove_name);
std::unique_ptr<EQApplicationPacket> CreateMemberListPacket(bool clear = false);
std::unique_ptr<EQApplicationPacket> CreateMemberListNamePacket(const std::string& name, bool remove_name);
std::unique_ptr<EQApplicationPacket> CreateMemberListStatusPacket(const std::string& name, ExpeditionMemberStatus status);
std::unique_ptr<EQApplicationPacket> CreateLeaderNamePacket();
uint32_t m_id = 0;
//uint32_t m_instance_id = 0; // todo: DynamicZone m_dynamiczone
uint32_t m_min_players = 0;
uint32_t m_max_players = 0;
bool m_has_replay_timer = false;
std::string m_expedition_name;
ExpeditionMember m_leader;
std::vector<ExpeditionMember> m_members; // current members
std::unordered_set<uint32_t> m_member_id_history; // track past members to allow invites for replay timer bypass
std::unordered_map<std::string, ExpeditionLockoutTimer> m_lockouts;
};
#endif

View File

@ -0,0 +1,608 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "expedition_database.h"
#include "expedition.h"
#include "expedition_lockout_timer.h"
#include "zonedb.h"
#include "../common/database.h"
#include <fmt/core.h>
uint32_t ExpeditionDatabase::InsertExpedition(
const std::string& expedition_name, uint32_t leader_id,
uint32_t min_players, uint32_t max_players, bool has_replay_lockout)
{
LogExpeditionsDetail("Inserting new expedition [{}] leader [{}]", expedition_name, leader_id);
std::string query = fmt::format(SQL(
INSERT INTO expedition_details
(expedition_name, leader_id, min_players, max_players, has_replay_timer)
VALUES
('{}', {}, {}, {}, {});
), expedition_name, leader_id, min_players, max_players, has_replay_lockout);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to obtain an expedition id for [{}]", expedition_name);
return 0;
}
return results.LastInsertedID();
}
MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id)
{
LogExpeditionsDetail("Loading expedition [{}]", expedition_id);
// no point caching expedition if no members, inner join instead of left
std::string query = fmt::format(SQL(
SELECT
expedition_details.id,
expedition_details.instance_id,
expedition_details.expedition_name,
expedition_details.leader_id,
expedition_details.min_players,
expedition_details.max_players,
expedition_details.has_replay_timer,
character_data.name leader_name,
expedition_lockouts.event_name,
UNIX_TIMESTAMP(expedition_lockouts.expire_time),
expedition_lockouts.duration,
expedition_lockouts.is_inherited
FROM expedition_details
INNER JOIN character_data ON expedition_details.leader_id = character_data.id
LEFT JOIN expedition_lockouts
ON expedition_details.id = expedition_lockouts.expedition_id
AND expedition_lockouts.expire_time > NOW()
WHERE expedition_details.id = {};
), expedition_id);
auto results = database.QueryDatabase(query);
return results;
}
MySQLRequestResult ExpeditionDatabase::LoadAllExpeditions()
{
LogExpeditionsDetail("Loading all expeditions from database");
// load all active expeditions and members to current zone cache
std::string query = SQL(
SELECT
expedition_details.id,
expedition_details.instance_id,
expedition_details.expedition_name,
expedition_details.leader_id,
expedition_details.min_players,
expedition_details.max_players,
expedition_details.has_replay_timer,
character_data.name leader_name,
expedition_lockouts.event_name,
UNIX_TIMESTAMP(expedition_lockouts.expire_time),
expedition_lockouts.duration,
expedition_lockouts.is_inherited
FROM expedition_details
INNER JOIN character_data ON expedition_details.leader_id = character_data.id
LEFT JOIN expedition_lockouts
ON expedition_details.id = expedition_lockouts.expedition_id
AND expedition_lockouts.expire_time > NOW()
ORDER BY expedition_details.id;
);
auto results = database.QueryDatabase(query);
return results;
}
MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts(uint32_t character_id)
{
LogExpeditionsDetail("Loading character [{}] lockouts", character_id);
auto query = fmt::format(SQL(
SELECT
UNIX_TIMESTAMP(expire_time),
duration,
expedition_name,
event_name
FROM expedition_character_lockouts
WHERE character_id = {} AND is_pending = FALSE AND expire_time > NOW();
), character_id);
return database.QueryDatabase(query);
}
MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts(
uint32_t character_id, const std::string& expedition_name)
{
LogExpeditionsDetail("Loading character [{}] lockouts for [{}]", character_id, expedition_name);
auto query = fmt::format(SQL(
SELECT
UNIX_TIMESTAMP(expire_time),
duration,
event_name
FROM expedition_character_lockouts
WHERE
character_id = {}
AND is_pending = FALSE
AND expire_time > NOW()
AND expedition_name = '{}';
), character_id, expedition_name);
return database.QueryDatabase(query);
}
MySQLRequestResult ExpeditionDatabase::LoadExpeditionMembers(uint32_t expedition_id)
{
LogExpeditionsDetail("Loading all members for expedition [{}]", expedition_id);
std::string query = fmt::format(SQL(
SELECT
expedition_members.character_id,
expedition_members.is_current_member,
character_data.name
FROM expedition_members
INNER JOIN character_data ON expedition_members.character_id = character_data.id
WHERE expedition_id = {};
), expedition_id);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to load expedition [{}] members from db", expedition_id);
}
return results;
}
MySQLRequestResult ExpeditionDatabase::LoadValidationData(
const std::string& character_names, const std::string& expedition_name)
{
LogExpeditionsDetail("Loading multiple characters data for [{}] request validation", expedition_name);
// for create validation, loads each character's lockouts and possible current expedition
auto query = fmt::format(SQL(
SELECT
character_data.id,
character_data.name,
member.expedition_id,
UNIX_TIMESTAMP(lockout.expire_time),
lockout.duration,
lockout.event_name
FROM character_data
LEFT JOIN expedition_character_lockouts lockout
ON character_data.id = lockout.character_id
AND lockout.is_pending = FALSE
AND lockout.expire_time > NOW()
AND lockout.expedition_name = '{}'
LEFT JOIN expedition_members member
ON character_data.id = member.character_id
AND member.is_current_member = TRUE
WHERE character_data.name IN ({})
ORDER BY character_data.id;
), expedition_name, character_names);
auto results = database.QueryDatabase(query);
return results;
}
void ExpeditionDatabase::DeleteCharacterLockout(
uint32_t character_id, const std::string& expedition_name, const std::string& event_name)
{
LogExpeditionsDetail("Deleting character [{}] lockout: [{}]:[{}]", character_id, expedition_name, event_name);
auto query = fmt::format(SQL(
DELETE FROM expedition_character_lockouts
WHERE
character_id = {}
AND is_pending = FALSE
AND expedition_name = '{}'
AND event_name = '{}';
), character_id, expedition_name, event_name);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions(
"Failed to delete [{}] event [{}] lockout from character [{}]",
expedition_name, event_name, character_id
);
}
}
void ExpeditionDatabase::DeleteMembersLockout(
const std::vector<ExpeditionMember>& members,
const std::string& expedition_name, const std::string& event_name)
{
LogExpeditionsDetail("Deleting members lockout: [{}]:[{}]", expedition_name, event_name);
std::string query_character_ids;
for (const auto& member : members)
{
fmt::format_to(std::back_inserter(query_character_ids), "{},", member.char_id);
}
if (!query_character_ids.empty())
{
query_character_ids.pop_back(); // trailing comma
auto query = fmt::format(SQL(
DELETE FROM expedition_character_lockouts
WHERE character_id
IN ({})
AND is_pending = FALSE
AND expedition_name = '{}'
AND event_name = '{}';
), query_character_ids, expedition_name, event_name);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to delete [{}] event [{}] lockouts", expedition_name, event_name);
}
}
}
void ExpeditionDatabase::AssignPendingLockouts(uint32_t character_id, const std::string& expedition_name)
{
LogExpeditionsDetail("Assigning character [{}] pending lockouts [{}]", character_id, expedition_name);
auto query = fmt::format(SQL(
UPDATE expedition_character_lockouts
SET is_pending = FALSE
WHERE
character_id = {}
AND is_pending = TRUE
AND expedition_name = '{}';
), character_id, expedition_name);
database.QueryDatabase(query);
}
void ExpeditionDatabase::DeletePendingLockouts(uint32_t character_id)
{
LogExpeditionsDetail("Deleting character [{}] pending lockouts", character_id);
auto query = fmt::format(SQL(
DELETE FROM expedition_character_lockouts
WHERE character_id = {} AND is_pending = TRUE;
), character_id);
database.QueryDatabase(query);
}
void ExpeditionDatabase::DeleteExpedition(uint32_t expedition_id)
{
LogExpeditionsDetail("Deleting expedition [{}]", expedition_id);
auto query = fmt::format("DELETE FROM expedition_details WHERE id = {}", expedition_id);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to delete expedition [{}]", expedition_id);
}
}
void ExpeditionDatabase::DeleteLockout(uint32_t expedition_id, const std::string& event_name)
{
LogExpeditionsDetail("Deleting expedition [{}] lockout event [{}]", expedition_id, event_name);
auto query = fmt::format(SQL(
DELETE FROM expedition_lockouts
WHERE expedition_id = {} AND event_name = '{}';
), expedition_id, event_name);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to delete expedition [{}] lockout [{}]", expedition_id, event_name);
}
}
void ExpeditionDatabase::DeleteAllMembers(uint32_t expedition_id)
{
LogExpeditionsDetail("Deleting all members of expedition [{}]", expedition_id);
auto query = fmt::format(SQL(
DELETE FROM expedition_members WHERE expedition_id = {};
), expedition_id);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to delete all members of expedition [{}]", expedition_id);
}
}
uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_id)
{
LogExpeditionsDetail("Getting expedition id for character [{}]", character_id);
uint32_t expedition_id = 0;
auto query = fmt::format(SQL(
SELECT expedition_id FROM expedition_members
WHERE character_id = {} AND is_current_member = TRUE;
), character_id);
auto results = database.QueryDatabase(query);
if (results.Success() && results.RowCount() > 0)
{
auto row = results.begin();
expedition_id = strtoul(row[0], nullptr, 10);
}
return expedition_id;
}
uint32_t ExpeditionDatabase::GetExpeditionIDFromInstanceID(uint32_t instance_id)
{
LogExpeditionsDetail("Getting expedition id for instance [{}]", instance_id);
uint32_t expedition_id = 0;
auto query = fmt::format(
"SELECT id FROM expedition_details WHERE instance_id = {};", instance_id
);
auto results = database.QueryDatabase(query);
if (results.Success() && results.RowCount() > 0)
{
auto row = results.begin();
expedition_id = std::strtoul(row[0], nullptr, 10);
}
return expedition_id;
}
ExpeditionMember ExpeditionDatabase::GetExpeditionLeader(uint32_t expedition_id)
{
LogExpeditionsDetail("Getting expedition leader for expedition [{}]", expedition_id);
auto query = fmt::format(SQL(
SELECT expedition_details.leader_id, character_data.name
FROM expedition_details
INNER JOIN character_data ON expedition_details.leader_id = character_data.id
WHERE expedition_id = {}
), expedition_id);
ExpeditionMember leader;
auto results = database.QueryDatabase(query);
if (results.Success() && results.RowCount() > 0)
{
auto row = results.begin();
leader.char_id = strtoul(row[0], nullptr, 10);
leader.name = row[1];
}
return leader;
}
void ExpeditionDatabase::InsertCharacterLockouts(
uint32_t character_id, const std::vector<ExpeditionLockoutTimer>& lockouts,
bool update_expire_times, bool is_pending)
{
LogExpeditionsDetail("Inserting character [{}] lockouts", character_id);
std::string insert_values;
for (const auto& lockout : lockouts)
{
fmt::format_to(std::back_inserter(insert_values),
"({}, FROM_UNIXTIME({}), {}, '{}', '{}', {}),",
character_id,
lockout.GetExpireTime(),
lockout.GetDuration(),
lockout.GetExpeditionName(),
lockout.GetEventName(),
is_pending
);
}
if (!insert_values.empty())
{
insert_values.pop_back(); // trailing comma
std::string on_duplicate;
if (update_expire_times) {
on_duplicate = "expire_time = VALUES(expire_time)";
} else {
on_duplicate = "character_id = VALUES(character_id)";
}
auto query = fmt::format(SQL(
INSERT INTO expedition_character_lockouts
(character_id, expire_time, duration, expedition_name, event_name, is_pending)
VALUES {}
ON DUPLICATE KEY UPDATE {};
), insert_values, on_duplicate);
database.QueryDatabase(query);
}
}
void ExpeditionDatabase::InsertMembersLockout(
const std::vector<ExpeditionMember>& members, const ExpeditionLockoutTimer& lockout)
{
LogExpeditionsDetail(
"Inserting members lockout [{}]:[{}] with expire time [{}]",
lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetExpireTime()
);
std::string insert_values;
for (const auto& member : members)
{
fmt::format_to(std::back_inserter(insert_values),
"({}, FROM_UNIXTIME({}), {}, '{}', '{}'),",
member.char_id,
lockout.GetExpireTime(),
lockout.GetDuration(),
lockout.GetExpeditionName(),
lockout.GetEventName()
);
}
if (!insert_values.empty())
{
insert_values.pop_back(); // trailing comma
auto query = fmt::format(SQL(
INSERT INTO expedition_character_lockouts
(character_id, expire_time, duration, expedition_name, event_name)
VALUES {}
ON DUPLICATE KEY UPDATE expire_time = VALUES(expire_time);
), insert_values);
database.QueryDatabase(query);
}
}
void ExpeditionDatabase::InsertLockout(
uint32_t expedition_id, const ExpeditionLockoutTimer& lockout)
{
LogExpeditionsDetail(
"Inserting expedition [{}] lockout: [{}]:[{}] expire time: [{}]",
expedition_id, lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetExpireTime()
);
auto query = fmt::format(SQL(
INSERT INTO expedition_lockouts
(expedition_id, event_name, expire_time, duration, is_inherited)
VALUES
({}, '{}', FROM_UNIXTIME({}), {}, FALSE)
ON DUPLICATE KEY UPDATE expire_time = VALUES(expire_time);
), expedition_id, lockout.GetEventName(), lockout.GetExpireTime(), lockout.GetDuration());
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to insert expedition lockouts");
}
}
void ExpeditionDatabase::InsertLockouts(
uint32_t expedition_id, const std::unordered_map<std::string, ExpeditionLockoutTimer>& lockouts)
{
LogExpeditionsDetail("Inserting expedition [{}] lockouts", expedition_id);
std::string insert_values;
for (const auto& lockout : lockouts)
{
fmt::format_to(std::back_inserter(insert_values),
"({}, '{}', FROM_UNIXTIME({}), {}, {}),",
expedition_id,
lockout.second.GetEventName(),
lockout.second.GetExpireTime(),
lockout.second.GetDuration(),
lockout.second.IsInherited()
);
}
if (!insert_values.empty())
{
insert_values.pop_back(); // trailing comma
auto query = fmt::format(SQL(
INSERT INTO expedition_lockouts
(expedition_id, event_name, expire_time, duration, is_inherited)
VALUES {}
ON DUPLICATE KEY UPDATE expire_time = VALUES(expire_time);
), insert_values);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to insert expedition lockouts");
}
}
}
void ExpeditionDatabase::InsertMember(uint32_t expedition_id, uint32_t character_id)
{
LogExpeditionsDetail("Inserting character [{}] into expedition [{}]", character_id, expedition_id);
auto query = fmt::format(SQL(
INSERT INTO expedition_members
(expedition_id, character_id, is_current_member)
VALUES
({}, {}, TRUE)
ON DUPLICATE KEY UPDATE is_current_member = TRUE;
), expedition_id, character_id);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to insert [{}] to expedition [{}]", character_id, expedition_id);
}
}
void ExpeditionDatabase::InsertMembers(
uint32_t expedition_id, const std::vector<ExpeditionMember>& members)
{
LogExpeditionsDetail("Inserting characters into expedition [{}]", expedition_id);
std::string insert_values;
for (const auto& member : members)
{
fmt::format_to(std::back_inserter(insert_values),
"({}, {}, TRUE),",
expedition_id, member.char_id
);
}
if (!insert_values.empty())
{
insert_values.pop_back(); // trailing comma
auto query = fmt::format(SQL(
INSERT INTO expedition_members (expedition_id, character_id, is_current_member)
VALUES {};
), insert_values);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to save expedition members to database");
}
}
}
void ExpeditionDatabase::UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id)
{
LogExpeditionsDetail("Updating leader [{}] for expedition [{}]", leader_id, expedition_id);
auto query = fmt::format(SQL(
UPDATE expedition_details SET leader_id = {} WHERE id = {}
), leader_id, expedition_id);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to update expedition [{}] leader", expedition_id);
}
}
void ExpeditionDatabase::UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id)
{
LogExpeditionsDetail("Removing member [{}] from expedition [{}]", character_id, expedition_id);
auto query = fmt::format(SQL(
UPDATE expedition_members SET is_current_member = FALSE
WHERE expedition_id = {} AND character_id = {};
), expedition_id, character_id);
auto results = database.QueryDatabase(query);
if (!results.Success())
{
LogExpeditions("Failed to remove [{}] from expedition [{}]", character_id, expedition_id);
}
}

View File

@ -0,0 +1,70 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef EXPEDITION_DATABASE_H
#define EXPEDITION_DATABASE_H
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
class Expedition;
class ExpeditionLockoutTimer;
struct ExpeditionMember;
class MySQLRequestResult;
namespace ExpeditionDatabase
{
uint32_t InsertExpedition(
const std::string& expedition_name, uint32_t leader_id,
uint32_t min_players, uint32_t max_players, bool has_replay_lockout);
MySQLRequestResult LoadExpedition(uint32_t expedition_id);
MySQLRequestResult LoadAllExpeditions();
MySQLRequestResult LoadCharacterLockouts(uint32_t character_id);
MySQLRequestResult LoadCharacterLockouts(uint32_t character_id, const std::string& expedition_name);
MySQLRequestResult LoadExpeditionMembers(uint32_t expedition_id);
MySQLRequestResult LoadValidationData(const std::string& character_names_query, const std::string& expedition_name);
void DeleteAllMembers(uint32_t expedition_id);
void DeleteCharacterLockout(uint32_t character_id, const std::string& expedition_name, const std::string& event_name);
void DeleteExpedition(uint32_t expedition_id);
void DeleteLockout(uint32_t expedition_id, const std::string& event_name);
void DeleteMembersLockout(
const std::vector<ExpeditionMember>& members, const std::string& expedition_name, const std::string& event_name);
void AssignPendingLockouts(uint32_t character_id, const std::string& expedition_name);
void DeletePendingLockouts(uint32_t character_id);
uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id);
uint32_t GetExpeditionIDFromInstanceID(uint32_t instance_id);
ExpeditionMember GetExpeditionLeader(uint32_t expedition_id);
void InsertCharacterLockouts(
uint32_t character_id, const std::vector<ExpeditionLockoutTimer>& lockouts,
bool update_expire_times, bool is_pending = false);
void InsertMembersLockout(const std::vector<ExpeditionMember>& members, const ExpeditionLockoutTimer& lockout);
void InsertLockout(uint32_t expedition_id, const ExpeditionLockoutTimer& lockout);
void InsertLockouts(uint32_t expedition_id, const std::unordered_map<std::string, ExpeditionLockoutTimer>& lockouts);
void InsertMember(uint32_t expedition_id, uint32_t character_id);
void InsertMembers(uint32_t expedition_id, const std::vector<ExpeditionMember>& members);
void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id);
void UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id);
};
#endif

View File

@ -0,0 +1,74 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "expedition_lockout_timer.h"
#include "../common/string_util.h"
#include <fmt/format.h>
#include <chrono>
const char* const DZ_REPLAY_TIMER_NAME = "Replay Timer"; // see December 14, 2016 patch notes
ExpeditionLockoutTimer::ExpeditionLockoutTimer(
std::string expedition_name, std::string event_name, uint64_t expire_time, uint32_t duration, bool inherited
) :
m_expedition_name(expedition_name),
m_event_name(event_name),
m_expire_time(expire_time),
m_duration(duration),
m_is_inherited(inherited)
{
if (event_name == DZ_REPLAY_TIMER_NAME)
{
m_is_replay_timer = true;
}
}
uint32_t ExpeditionLockoutTimer::GetSecondsRemaining() const
{
auto now = std::chrono::system_clock::now();
auto expire_time = std::chrono::system_clock::from_time_t(m_expire_time);
if (expire_time > now)
{
auto time_remaining = std::chrono::duration_cast<std::chrono::seconds>(expire_time - now).count();
return static_cast<uint32_t>(time_remaining);
}
return 0;
}
ExpeditionLockoutTimer::DaysHoursMinutes ExpeditionLockoutTimer::GetDaysHoursMinutesRemaining() const
{
auto seconds = GetSecondsRemaining();
return ExpeditionLockoutTimer::DaysHoursMinutes{
fmt::format_int(seconds / 86400).str(), // days
fmt::format_int((seconds / 3600) % 24).str(), // hours
fmt::format_int((seconds / 60) % 60).str() // minutes
};
}
bool ExpeditionLockoutTimer::IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const
{
return compare_lockout.IsSameLockout(GetExpeditionName(), GetEventName());
}
bool ExpeditionLockoutTimer::IsSameLockout(
const std::string& expedition_name, const std::string& event_name) const
{
return GetExpeditionName() == expedition_name && GetEventName() == event_name;
}

View File

@ -0,0 +1,64 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef EXPEDITION_LOCKOUT_TIMER_H
#define EXPEDITION_LOCKOUT_TIMER_H
#include <string>
extern const char* const DZ_REPLAY_TIMER_NAME;
// DynamicZoneEventTimer and DynamicZoneReplayTimer in client
class ExpeditionLockoutTimer
{
public:
ExpeditionLockoutTimer() {}
ExpeditionLockoutTimer(std::string expedition_name, std::string event_name, uint64_t expire_time, uint32_t duration, bool inherited = false);
struct DaysHoursMinutes
{
std::string days;
std::string hours;
std::string mins;
};
uint32_t GetDuration() const { return m_duration; }
uint64_t GetExpireTime() const { return m_expire_time; }
uint32_t GetSecondsRemaining() const;
DaysHoursMinutes GetDaysHoursMinutesRemaining() const;
const std::string& GetExpeditionName() const { return m_expedition_name; }
const std::string& GetEventName() const { return m_event_name; }
void SetExpireTime(uint64_t expire_time) { m_expire_time = expire_time; }
void SetInherited(bool is_inherited) { m_is_inherited = is_inherited; }
bool IsInherited() const { return m_is_inherited; }
bool IsReplayTimer() const { return m_is_replay_timer; }
bool IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const;
bool IsSameLockout(const std::string& expedition_name, const std::string& event_name) const;
private:
std::string m_expedition_name;
std::string m_event_name;
uint64_t m_expire_time = 0;
uint32_t m_duration = 0;
bool m_is_inherited = false; // inherited from expedition leader
bool m_is_replay_timer = false;
};
#endif

370
zone/expedition_request.cpp Normal file
View File

@ -0,0 +1,370 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "expedition_request.h"
#include "client.h"
#include "expedition.h"
#include "expedition_database.h"
#include "expedition_lockout_timer.h"
#include "groups.h"
#include "raids.h"
#include "string_ids.h"
#include "worldserver.h"
#include <fmt/format.h>
extern WorldServer worldserver;
struct ExpeditionRequestConflict
{
std::string character_name;
ExpeditionLockoutTimer lockout;
};
ExpeditionRequest::ExpeditionRequest(
Client* requester, std::string expedition_name, uint32_t min_players,
uint32_t max_players, bool has_replay_timer
) :
m_requester(requester),
m_expedition_name(expedition_name),
m_min_players(min_players),
m_max_players(max_players),
m_has_replay_timer(has_replay_timer)
{
}
bool ExpeditionRequest::Validate()
{
if (!m_requester)
{
return false;
}
// a message is sent to leader for every member that fails a requirement
auto start = std::chrono::steady_clock::now();
bool requirements_met = false;
Raid* raid = m_requester->GetRaid();
Group* group = m_requester->GetGroup();
if (raid)
{
requirements_met = CanRaidRequest(raid);
}
else if (group)
{
requirements_met = CanGroupRequest(group);
}
else // solo request
{
m_leader = m_requester;
m_leader_id = m_requester->CharacterID();
m_leader_name = m_requester->GetName();
requirements_met = ValidateMembers(fmt::format("'{}'", m_leader_name), 1);
}
auto end = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::duration<float>>(end - start);
LogExpeditions("Create validation for [{}] members took {}s", m_members.size(), elapsed.count());
return requirements_met;
}
bool ExpeditionRequest::CanRaidRequest(Raid* raid)
{
m_leader = raid->GetLeader();
m_leader_name = raid->leadername;
m_leader_id = m_leader ? m_leader->CharacterID() : database.GetCharacterID(raid->leadername);
uint32_t count = 0;
std::string query_member_names;
for (int i = 0; i < MAX_RAID_MEMBERS; ++i)
{
if (raid->members[i].membername[0])
{
fmt::format_to(std::back_inserter(query_member_names), "'{}',", raid->members[i].membername);
++count;
}
}
if (!query_member_names.empty())
{
query_member_names.pop_back(); // trailing comma
}
return ValidateMembers(query_member_names, count);
}
bool ExpeditionRequest::CanGroupRequest(Group* group)
{
m_leader = nullptr;
if (group->GetLeader() && group->GetLeader()->IsClient())
{
m_leader = group->GetLeader()->CastToClient();
}
m_leader_name = group->GetLeaderName();
m_leader_id = m_leader ? m_leader->CharacterID() : database.GetCharacterID(m_leader_name.c_str());
uint32_t count = 0;
std::string query_member_names;
for (int i = 0; i < MAX_GROUP_MEMBERS; ++i)
{
if (group->membername[i][0])
{
fmt::format_to(std::back_inserter(query_member_names), "'{}',", group->membername[i]);
++count;
}
}
if (!query_member_names.empty())
{
query_member_names.pop_back(); // trailing comma
}
return ValidateMembers(query_member_names, count);
}
bool ExpeditionRequest::ValidateMembers(const std::string& query_member_names, uint32_t member_count)
{
if (query_member_names.empty() || !LoadLeaderLockouts())
{
return false;
}
// get character ids for all members through database since some may be out
// of zone. also gets each member's existing expeditions and/or lockouts
auto results = ExpeditionDatabase::LoadValidationData(query_member_names, m_expedition_name);
if (!results.Success())
{
LogExpeditions("Failed to load data to verify members for expedition request");
return false;
}
bool requirements_met = true;
bool is_solo = (member_count == 1);
bool has_conflicts = CheckMembersForConflicts(results, is_solo);
if (has_conflicts)
{
requirements_met = false;
}
// live only checks player count requirement after other expensive checks pass (?)
// maybe it's done intentionally as a way to preview lockout conflicts
if (requirements_met)
{
requirements_met = IsPlayerCountValidated(member_count);
}
return requirements_met;
}
bool ExpeditionRequest::LoadLeaderLockouts()
{
// leader's lockouts are used to check member conflicts and later stored in expedition
auto results = ExpeditionDatabase::LoadCharacterLockouts(m_leader_id, m_expedition_name);
if (!results.Success())
{
LogExpeditions("Failed to load leader id [{}] lockouts ([{}])", m_leader_id, m_leader_name);
return false;
}
for (auto row = results.begin(); row != results.end(); ++row)
{
uint64_t expire_time = strtoull(row[0], nullptr, 10);
uint32_t duration = strtoul(row[1], nullptr, 10);
m_lockouts.emplace(row[2], ExpeditionLockoutTimer{
m_expedition_name, row[2], expire_time, duration, true
});
// on live if leader has a replay lockout it never bothers checking for event conflicts
if (m_check_event_lockouts && m_has_replay_timer && strcmp(row[2], DZ_REPLAY_TIMER_NAME) == 0)
{
m_check_event_lockouts = false;
}
}
return true;
}
bool ExpeditionRequest::CheckMembersForConflicts(MySQLRequestResult& results, bool is_solo)
{
// leader lockouts were pre-loaded to compare with members below
bool has_conflicts = false;
std::vector<ExpeditionRequestConflict> member_lockout_conflicts;
bool leader_processed = false;
uint32_t last_character_id = 0;
for (auto row = results.begin(); row != results.end(); ++row)
{
auto character_id = static_cast<uint32_t>(std::strtoul(row[0], nullptr, 10));
std::string character_name(row[1]);
if (character_id != last_character_id)
{
// defaults to online status, if offline group members implemented this needs to change
m_members.emplace_back(ExpeditionMember{character_id, character_name});
// process event lockout conflict messages from the previous character
for (const auto& member_lockout : member_lockout_conflicts)
{
SendLeaderMemberEventLockout(member_lockout.character_name, member_lockout.lockout);
}
member_lockout_conflicts.clear();
// current character existing expedition check
if (row[2])
{
has_conflicts = true;
SendLeaderMemberInExpedition(character_name, is_solo);
// solo requests break out early if requester in an expedition
if (is_solo)
{
return has_conflicts;
}
}
}
last_character_id = character_id;
// compare member lockouts with leader lockouts
if (row[3] && row[4] && row[5])
{
auto expire_time = strtoull(row[3], nullptr, 10);
auto original_duration = strtoul(row[4], nullptr, 10);
std::string event_name(row[5]);
ExpeditionLockoutTimer lockout(m_expedition_name, event_name, expire_time, original_duration);
// replay timer conflict messages always show up before event conflicts
if (/*m_has_replay_timer && */event_name == DZ_REPLAY_TIMER_NAME)
{
has_conflicts = true;
SendLeaderMemberReplayLockout(character_name, lockout, is_solo);
// replay timers no longer also show up as event conflicts
//SendLeaderMemberEventLockout(character_name, lockout);
}
else if (m_check_event_lockouts && character_id != m_leader_id)
{
if (m_lockouts.find(event_name) == m_lockouts.end())
{
// leader doesn't have this lockout
// queue instead of messaging now so they come after any replay lockout messages
has_conflicts = true;
member_lockout_conflicts.emplace_back(ExpeditionRequestConflict{character_name, lockout});
}
}
}
}
// event lockout messages for last processed character
for (const auto& member_lockout : member_lockout_conflicts)
{
SendLeaderMemberEventLockout(member_lockout.character_name, member_lockout.lockout);
}
return has_conflicts;
}
void ExpeditionRequest::SendLeaderMessage(
uint16_t chat_type, uint32_t string_id, const std::initializer_list<std::string>& parameters)
{
Client::SendCrossZoneMessageString(m_leader, m_leader_name, chat_type, string_id, parameters);
}
void ExpeditionRequest::SendLeaderMemberInExpedition(const std::string& member_name, bool is_solo)
{
if (is_solo)
{
SendLeaderMessage(Chat::Red, EXPEDITION_YOU_BELONG);
}
else if (m_requester)
{
std::string message = fmt::format(EXPEDITION_OTHER_BELONGS, m_requester->GetName(), member_name);
Client::SendCrossZoneMessage(m_leader, m_leader_name, Chat::Red, message);
}
}
void ExpeditionRequest::SendLeaderMemberReplayLockout(
const std::string& member_name, const ExpeditionLockoutTimer& lockout, bool is_solo)
{
if (lockout.GetSecondsRemaining() <= 0)
{
return;
}
auto time_remaining = lockout.GetDaysHoursMinutesRemaining();
if (is_solo)
{
SendLeaderMessage(Chat::Red, EXPEDITION_YOU_PLAYED_HERE, {
time_remaining.days, time_remaining.hours, time_remaining.mins
});
}
else
{
SendLeaderMessage(Chat::Red, EXPEDITION_REPLAY_TIMER, {
member_name, time_remaining.days, time_remaining.hours, time_remaining.mins
});
}
}
void ExpeditionRequest::SendLeaderMemberEventLockout(
const std::string& member_name, const ExpeditionLockoutTimer& lockout)
{
if (lockout.GetSecondsRemaining() <= 0)
{
return;
}
auto time_remaining = lockout.GetDaysHoursMinutesRemaining();
SendLeaderMessage(Chat::Red, EXPEDITION_EVENT_TIMER, {
member_name,
lockout.GetEventName(),
time_remaining.days,
time_remaining.hours,
time_remaining.mins,
lockout.GetEventName()
});
}
bool ExpeditionRequest::IsPlayerCountValidated(uint32_t member_count)
{
// note: offline group members count towards requirement but not added to expedition
bool requirements_met = true;
auto bypass_status = RuleI(Expedition, MinStatusToBypassPlayerCountRequirements);
auto gm_bypass = (m_requester->GetGM() && m_requester->Admin() >= bypass_status);
if (!gm_bypass && (member_count < m_min_players || member_count > m_max_players))
{
requirements_met = false;
SendLeaderMessage(Chat::Red, REQUIRED_PLAYER_COUNT, {
fmt::format_int(member_count).str(),
fmt::format_int(m_min_players).str(),
fmt::format_int(m_max_players).str()
});
}
return requirements_met;
}

76
zone/expedition_request.h Normal file
View File

@ -0,0 +1,76 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* 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; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef EXPEDITION_REQUEST_H
#define EXPEDITION_REQUEST_H
#include "expedition_lockout_timer.h"
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
class Client;
class Group;
class MySQLRequestResult;
class Raid;
class ServerPacket;
struct ExpeditionMember;
class ExpeditionRequest
{
public:
ExpeditionRequest(Client* requester, std::string expedition_name,
uint32_t min_players, uint32_t max_players, bool has_replay_timer);
bool Validate();
Client* GetLeaderClient() const { return m_leader; }
uint32_t GetLeaderID() const { return m_leader_id; }
const std::string& GetLeaderName() const { return m_leader_name; }
std::vector<ExpeditionMember> TakeMembers() && { return std::move(m_members); }
std::unordered_map<std::string, ExpeditionLockoutTimer> TakeLockouts() && { return std::move(m_lockouts); }
private:
bool ValidateMembers(const std::string& query_member_names, uint32_t member_count);
bool CanRaidRequest(Raid* raid);
bool CanGroupRequest(Group* group);
bool CheckMembersForConflicts(MySQLRequestResult& results, bool is_solo);
bool IsPlayerCountValidated(uint32_t member_count);
bool LoadLeaderLockouts();
void SendLeaderMemberInExpedition(const std::string& member_name, bool is_solo);
void SendLeaderMemberReplayLockout(const std::string& member_name, const ExpeditionLockoutTimer& lockout, bool is_solo);
void SendLeaderMemberEventLockout(const std::string& member_name, const ExpeditionLockoutTimer& lockout);
void SendLeaderMessage(uint16_t chat_type, uint32_t string_id, const std::initializer_list<std::string>& parameters = {});
Client* m_requester = nullptr;
Client* m_leader = nullptr;
uint32_t m_leader_id = 0;
uint32_t m_min_players = 0;
uint32_t m_max_players = 0;
bool m_check_event_lockouts = true;
bool m_has_replay_timer = false;
std::string m_expedition_name;
std::string m_leader_name;
std::vector<ExpeditionMember> m_members;
std::unordered_map<std::string, ExpeditionLockoutTimer> m_lockouts;
};
#endif

View File

@ -293,8 +293,43 @@
#define TRADESKILL_MISSING_ITEM 3455 //You are missing a %1.
#define TRADESKILL_MISSING_COMPONENTS 3456 //Sorry, but you don't have everything you need for this recipe in your general inventory.
#define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1!
#define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end.
#define EXPEDITION_YOU_BELONG 3500 //You cannot create this expedition since you already belong to another.
#define EXPEDITION_YOU_PLAYED_HERE 3501 //You cannot create this expedition for another %1d:%2h:%3m since you have recently played here.
#define REQUIRED_PLAYER_COUNT 3503 //You do not meet the player count requirement. You have %1 players. You must have at least %2 and no more than %3.
#define EXPEDITION_REPLAY_TIMER 3504 //%1 cannot be added to this expedition for another %2D:%3H:%4M since they have recently played in this area.
#define EXPEDITION_AVAILABLE 3507 //%1 is now available to you.
#define DZADD_INVITE 3508 //Sending an invitation to: %1.
#define DZADD_INVITE_FAIL 3511 //%1 could not be invited to join you.
#define UNABLE_RETRIEVE_LEADER 3512 //Unable to retrieve information on the leader to check permissions.
#define EXPEDITION_NOT_LEADER 3513 //You are not the expedition leader, only %1 can issue this command.
#define EXPEDITION_NOT_MEMBER 3514 //%1 is not a member of this expedition.
#define EXPEDITION_REMOVED 3516 //%1 has been removed from %2.
#define DZSWAP_INVITE 3517 //Sending an invitation to: %1. They must accept in order to swap party members.
#define DZMAKELEADER_NOT_ONLINE 3518 //%1 is not currently online. You can only transfer leadership to an online member of the expedition you are in.
#define DZLIST_REPLAY_TIMER 3519 //You have %1d:%2h:%3m remaining until you may enter %4.
#define DZMAKELEADER_NAME 3520 //%1 has been made the leader for this expedition.
#define DZMAKELEADER_YOU 3521 //You have been made the leader of this expedition.
#define EXPEDITION_INVITE_ACCEPTED 3522 //%1 has accepted your offer to join your expedition.
#define EXPEDITION_MEMBER_ADDED 3523 //%1 has been added to %2.
#define EXPEDITION_INVITE_ERROR 3524 //%1 accepted your offer to join your expedition but could not due to error(s).
#define EXPEDITION_INVITE_DECLINED 3525 //%1 has declined your offer to join your expedition.
#define EXPEDITION_ASKED_TO_JOIN 3527 //%1 has asked you to join the expedition: %2. Would you like to join?
#define EXPEDITION_NO_TIMERS 3529 //You have no outstanding timers.
#define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end.
#define EXPEDITION_LEADER 3552 //Expedition Leader: %1
#define EXPEDITION_MEMBERS 3553 //Expedition Members: %1
#define EXPEDITION_EVENT_TIMER 3561 //%1 cannot be added to this expedition since they have recently experienced %2. They must wait another %3D:%4H:%5M until they can experience it again. They may be added to the expedition later, once %2 has been completed.
#define LOOT_NOT_ALLOWED 3562 //You are not allowed to loot the item: %1.
#define DZ_UNABLE_RETRIEVE_LEADER 3583 //Unable to retrieve dynamic zone leader to check permissions.
#define DZADD_NOT_ONLINE 3586 //%1 is not currently online. A player needs to be online to be added to a Dynamic Zone
#define DZADD_EXCEED_MAX 3587 //You can not add another player since you currently have the maximum number of players allowed (%1) in this zone.
#define DZADD_ALREADY_PART 3588 //You can not add %1 since they are already part of this zone.
#define DZADD_ALREADY_ASSIGNED 3590 //%1 can not be added to this dynamic zone since they are already assigned to another dynamic zone.
#define DZADD_REPLAY_TIMER 3591 //%1 can not be added to this dynamic zone for another %2D:%3H:%4M since they have recently played this zone.
#define DZADD_EVENT_TIMER 3592 //%1 can not be added to this dynamic zone since they have recently experienced %2. They must wait for another %3D:%4H:%5M, or until event %2 has occurred.
#define DZADD_PENDING 3593 //%1 currently has an outstanding invitation to join this Dynamic Zone.
#define DZADD_PENDING_OTHER 3594 //%1 currently has an outstanding invitation to join another Dynamic Zone. Players may only have one invitation outstanding.
#define DZSWAP_CANNOT_REMOVE 3595 //%1 can not be removed from this dynamic zone since they are not assigned to it.
#define NOT_YOUR_TRAP 3671 //You cannot remove this, you are only allowed to remove traps you have set.
#define NO_CAST_ON_PET 4045 //You cannot cast this spell on your pet.
#define REWIND_WAIT 4059 //You must wait a bit longer before using the rewind command again.

View File

@ -42,6 +42,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "client.h"
#include "corpse.h"
#include "entity.h"
#include "expedition.h"
#include "quest_parser_collection.h"
#include "guild_mgr.h"
#include "mob.h"
@ -2846,7 +2847,6 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
break;
}
case ServerOP_ChangeSharedMem:
{
std::string hotfix_name = std::string((char*)pack->pBuffer);
@ -2881,6 +2881,38 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
}
break;
}
case ServerOP_CZClientMessage:
{
auto buf = reinterpret_cast<ServerCZClientMessage_Struct*>(pack->pBuffer);
Client* client = entity_list.GetClientByName(buf->character_name);
if (client) {
client->Message(buf->chat_type, buf->message);
}
break;
}
case ServerOP_CZClientMessageString:
{
auto buf = reinterpret_cast<ServerCZClientMessageString_Struct*>(pack->pBuffer);
Client* client = entity_list.GetClientByName(buf->character_name);
if (client) {
client->MessageString(buf);
}
break;
}
case ServerOP_ExpeditionCreate:
case ServerOP_ExpeditionDeleted:
case ServerOP_ExpeditionLeaderChanged:
case ServerOP_ExpeditionLockout:
case ServerOP_ExpeditionMemberChange:
case ServerOP_ExpeditionMemberSwap:
case ServerOP_ExpeditionMemberStatus:
case ServerOP_ExpeditionGetOnlineMembers:
case ServerOP_ExpeditionDzAddPlayer:
case ServerOP_ExpeditionDzMakeLeader:
{
Expedition::HandleWorldMessage(pack);
break;
}
default: {
std::cout << " Unknown ZSopcode:" << (int)pack->opcode;
std::cout << " size:" << pack->size << std::endl;

View File

@ -37,6 +37,7 @@
#include "../common/string_util.h"
#include "../common/eqemu_logsys.h"
#include "expedition.h"
#include "guild_mgr.h"
#include "map.h"
#include "npc.h"
@ -1183,6 +1184,9 @@ bool Zone::Init(bool iStaticZone) {
petition_list.ClearPetitions();
petition_list.ReadDatabase();
LogInfo("Loading active Expeditions");
Expedition::CacheAllFromDatabase();
LogInfo("Loading timezone data");
zone->zone_time.setEQTimeZone(content_db.GetZoneTZ(zoneid, GetInstanceVersion()));
@ -2699,3 +2703,7 @@ void Zone::SetInstanceTimeRemaining(uint32 instance_time_remaining)
Zone::instance_time_remaining = instance_time_remaining;
}
bool Zone::IsZone(uint32 zone_id, uint16 instance_id) const
{
return (zoneid == zone_id && instanceid == instance_id);
}

View File

@ -81,6 +81,7 @@ struct item_tick_struct {
};
class Client;
class Expedition;
class Map;
class Mob;
class WaterMap;
@ -129,6 +130,7 @@ public:
bool IsPVPZone() { return pvpzone; }
bool IsSpellBlocked(uint32 spell_id, const glm::vec3 &location);
bool IsUCSServerAvailable() { return m_ucss_available; }
bool IsZone(uint32 zone_id, uint16 instance_id) const;
bool LoadGroundSpawns();
bool LoadZoneCFG(const char *filename, uint16 instance_id);
bool LoadZoneObjects();
@ -217,6 +219,8 @@ public:
std::vector<GridRepository::Grid> zone_grids;
std::vector<GridEntriesRepository::GridEntry> zone_grid_entries;
std::unordered_map<uint32, std::unique_ptr<Expedition>> expedition_cache;
time_t weather_timer;
Timer spawn2_timer;
Timer hot_reload_timer;