mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
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:
parent
a77f8b582e
commit
da067be2fa
@ -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",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
136
world/expedition.cpp
Normal 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
35
world/expedition.h
Normal 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
|
||||
@ -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()) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
301
zone/client.cpp
301
zone/client.cpp
@ -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());
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
1587
zone/expedition.cpp
Normal file
File diff suppressed because it is too large
Load Diff
188
zone/expedition.h
Normal file
188
zone/expedition.h
Normal 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
|
||||
608
zone/expedition_database.cpp
Normal file
608
zone/expedition_database.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
70
zone/expedition_database.h
Normal file
70
zone/expedition_database.h
Normal 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
|
||||
74
zone/expedition_lockout_timer.cpp
Normal file
74
zone/expedition_lockout_timer.cpp
Normal 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;
|
||||
}
|
||||
64
zone/expedition_lockout_timer.h
Normal file
64
zone/expedition_lockout_timer.h
Normal 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
370
zone/expedition_request.cpp
Normal 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
76
zone/expedition_request.h
Normal 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
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user