diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 2ca06319a..d28478a97 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -119,6 +119,7 @@ namespace Logs { ZonePoints, Loot, Expeditions, + DynamicZones, MaxCategoryID /* Don't Remove this */ }; @@ -197,6 +198,7 @@ namespace Logs { "ZonePoints", "Loot", "Expeditions", + "DynamicZones", }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index b10521eee..efe25dabe 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -616,6 +616,16 @@ OutF(LogSys, Logs::Detail, Logs::Expeditions, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogDynamicZones(message, ...) do {\ + if (LogSys.log_settings[Logs::DynamicZones].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::DynamicZones, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogDynamicZonesDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::DynamicZones].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::DynamicZones, __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__);\ @@ -976,6 +986,12 @@ #define LogExpeditionsDetail(message, ...) do {\ } while (0) +#define LogDynamicZones(message, ...) do {\ +} while (0) + +#define LogDynamicZonesDetail(message, ...) do {\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ } while (0) diff --git a/common/ruletypes.h b/common/ruletypes.h index f1ea792de..9fb3ba629 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -788,6 +788,12 @@ 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_BOOL(Expedition, EmptyDzShutdownEnabled, true, "Enable early instance shutdown after last member of expedition removed") +RULE_INT(Expedition, EmptyDzShutdownDelaySeconds, 900, "Seconds to set dynamic zone instance expiration if early shutdown enabled") +RULE_CATEGORY_END() + +RULE_CATEGORY(DynamicZone) +RULE_INT(DynamicZone, ClientRemovalDelayMS, 60000, "Delay (ms) until a client is teleported out of dynamic zone after being removed as member") RULE_CATEGORY_END() #undef RULE_CATEGORY diff --git a/common/servertalk.h b/common/servertalk.h index 6efb20eb8..6fbe67b96 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -151,6 +151,11 @@ #define ServerOP_ExpeditionGetOnlineMembers 0x0407 #define ServerOP_ExpeditionDzAddPlayer 0x0408 #define ServerOP_ExpeditionDzMakeLeader 0x0409 +#define ServerOP_ExpeditionDzCompass 0x040a +#define ServerOP_ExpeditionDzSafeReturn 0x040b +#define ServerOP_ExpeditionDzZoneIn 0x040c + +#define ServerOP_DzCharacterChange 0x0450 #define ServerOP_LSInfo 0x1000 #define ServerOP_LSStatus 0x1001 @@ -2053,6 +2058,25 @@ struct ServerDzCommand_Struct { char remove_name[64]; // used for swap command }; +struct ServerDzLocation_Struct { + uint32 owner_id; // system associated with the dz (expedition, shared task, etc) + uint16 dz_zone_id; + uint16 dz_instance_id; + uint32 sender_zone_id; + uint16 sender_instance_id; + uint32 zone_id; // compass or safereturn zone id + float y; + float x; + float z; + float heading; +}; + +struct ServerDzCharacter_Struct { + uint16 instance_id; + uint8 remove; // 0: added 1: removed + uint32 character_id; +}; + #pragma pack() #endif diff --git a/utils/sql/git/required/wip_dynamiczones.sql b/utils/sql/git/required/wip_dynamiczones.sql new file mode 100644 index 000000000..c38487e31 --- /dev/null +++ b/utils/sql/git/required/wip_dynamiczones.sql @@ -0,0 +1,25 @@ +CREATE TABLE `dynamic_zones` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `instance_id` INT(10) NOT NULL DEFAULT 0, + `type` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + `compass_zone_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `compass_x` FLOAT NOT NULL DEFAULT 0, + `compass_y` FLOAT NOT NULL DEFAULT 0, + `compass_z` FLOAT NOT NULL DEFAULT 0, + `safe_return_zone_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `safe_return_x` FLOAT NOT NULL DEFAULT 0, + `safe_return_y` FLOAT NOT NULL DEFAULT 0, + `safe_return_z` FLOAT NOT NULL DEFAULT 0, + `safe_return_heading` FLOAT NOT NULL DEFAULT 0, + `zone_in_x` FLOAT NOT NULL DEFAULT 0, + `zone_in_y` FLOAT NOT NULL DEFAULT 0, + `zone_in_z` FLOAT NOT NULL DEFAULT 0, + `zone_in_heading` FLOAT NOT NULL DEFAULT 0, + `has_zone_in` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE INDEX `instance_id` (`instance_id`), + CONSTRAINT `FK_dynamic_zones_instance_list` FOREIGN KEY (`instance_id`) REFERENCES `instance_list` (`id`) ON DELETE CASCADE +) +COLLATE='utf8mb4_general_ci' +ENGINE=InnoDB +; diff --git a/world/expedition.cpp b/world/expedition.cpp index e871ff25c..82ff28ba6 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -30,23 +30,27 @@ extern ClientList client_list; extern ZSList zoneserver_list; -void Expedition::PurgeEmptyExpeditions() +void Expedition::PurgeExpiredExpeditions() { std::string query = SQL( DELETE expedition FROM expedition_details expedition + LEFT JOIN instance_list ON expedition.instance_id = instance_list.id 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 + WHERE + expedition.instance_id IS NULL + OR expedition_members.member_count = 0 + OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); ); auto results = database.QueryDatabase(query); if (!results.Success()) { - LogExpeditions("Failed to purge empty expeditions"); + LogExpeditions("Failed to purge expired and empty expeditions"); } } diff --git a/world/expedition.h b/world/expedition.h index bfd1250ed..2d9346e78 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -25,7 +25,7 @@ class ServerPacket; namespace Expedition { - void PurgeEmptyExpeditions(); + void PurgeExpiredExpeditions(); void PurgeExpiredCharacterLockouts(); void AddPlayer(ServerPacket* pack); void MakeLeader(ServerPacket* pack); diff --git a/world/main.cpp b/world/main.cpp index d7ac2d91e..0f0f6fa65 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -431,7 +431,7 @@ int main(int argc, char** argv) { PurgeInstanceTimer.Start(450000); LogInfo("Purging expired expeditions"); - Expedition::PurgeEmptyExpeditions(); //database.PurgeExpiredExpeditions(); + Expedition::PurgeExpiredExpeditions(); Expedition::PurgeExpiredCharacterLockouts(); LogInfo("Loading char create info"); @@ -604,7 +604,7 @@ int main(int argc, char** argv) { if (PurgeInstanceTimer.Check()) { database.PurgeExpiredInstances(); database.PurgeAllDeletedDataBuckets(); - Expedition::PurgeEmptyExpeditions(); + Expedition::PurgeExpiredExpeditions(); Expedition::PurgeExpiredCharacterLockouts(); } diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index d39b3288d..f113b0b1e 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1375,6 +1375,9 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ExpeditionMemberChange: case ServerOP_ExpeditionMemberSwap: case ServerOP_ExpeditionMemberStatus: + case ServerOP_ExpeditionDzCompass: + case ServerOP_ExpeditionDzSafeReturn: + case ServerOP_ExpeditionDzZoneIn: { zoneserver_list.SendPacket(pack); break; @@ -1394,6 +1397,16 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { Expedition::MakeLeader(pack); break; } + case ServerOP_DzCharacterChange: + { + auto buf = reinterpret_cast(pack->pBuffer); + ZoneServer* instance_zs = zoneserver_list.FindByInstanceID(buf->instance_id); + if (instance_zs) + { + instance_zs->SendPacket(pack); + } + break; + } default: { LogInfo("Unknown ServerOPcode from zone {:#04x}, size [{}]", pack->opcode, pack->size); diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 7cf213e3e..4e85f25dc 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -22,6 +22,7 @@ SET(zone_sources corpse.cpp data_bucket.cpp doors.cpp + dynamiczone.cpp effects.cpp embparser.cpp embparser_api.cpp @@ -170,6 +171,7 @@ SET(zone_headers corpse.h data_bucket.h doors.h + dynamiczone.h embparser.h embperl.h embxs.h diff --git a/zone/client.cpp b/zone/client.cpp index 5f513f4bb..060979f1a 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -43,6 +43,7 @@ extern volatile bool RunLoops; #include "expedition.h" #include "expedition_database.h" #include "expedition_lockout_timer.h" +#include "expedition_request.h" #include "position.h" #include "worldserver.h" #include "zonedb.h" @@ -267,6 +268,7 @@ Client::Client(EQStreamInterface* ieqs) InitializeMercInfo(); SetMerc(0); if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) SetPVP(true, false); + dynamiczone_removal_timer.Disable(); //for good measure: memset(&m_pp, 0, sizeof(m_pp)); @@ -6155,24 +6157,19 @@ 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(outapp->pBuffer); - - outbuf->client_id = 0; - outbuf->count = count; - - if (count) { - 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; + if (count == 0) + { + m_quest_compass.zone_id = 0; + } + else + { + m_quest_compass.zone_id = zone ? zone->GetZoneID() : 0; + m_quest_compass.x = in_x; + m_quest_compass.y = in_y; + m_quest_compass.z = in_z; } - FastQueuePacket(&outapp); - safe_delete(outapp); + SendDzCompassUpdate(); } void Client::SendZonePoints() @@ -9564,6 +9561,8 @@ 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 + SendDzCompassUpdate(); + auto expedition = GetExpedition(); if (expedition) { @@ -9571,7 +9570,7 @@ void Client::UpdateExpeditionInfoAndLockouts() // 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()) + if (expedition->GetDynamicZone().IsCurrentZoneDzInstance()) { ExpeditionDatabase::AssignPendingLockouts(CharacterID(), expedition->GetName()); expedition->SetMemberStatus(this, ExpeditionMemberStatus::InDynamicZone); @@ -9581,13 +9580,17 @@ void Client::UpdateExpeditionInfoAndLockouts() expedition->SetMemberStatus(this, ExpeditionMemberStatus::Online); } } + Expedition::LoadAllClientLockouts(this); } Expedition* Client::CreateExpedition( - std::string name, uint32 min_players, uint32 max_players, bool has_replay_timer) + std::string zone_name, uint32 version, uint32 duration, + std::string expedition_name, uint32 min_players, uint32 max_players, bool has_replay_timer) { - return Expedition::TryCreate(this, name, min_players, max_players, has_replay_timer); + DynamicZone dz_instance{ zone_name, version, duration, DynamicZoneType::Expedition }; + ExpeditionRequest request{ expedition_name, min_players, max_players, has_replay_timer }; + return Expedition::TryCreate(this, dz_instance, request); } Expedition* Client::GetExpedition() const @@ -9616,30 +9619,6 @@ std::vector Client::GetExpeditionLockouts(const std::str 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 @@ -9744,3 +9723,201 @@ void Client::SendExpeditionLockoutTimers() } QueuePacket(outapp.get()); } + +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::SetDzRemovalTimer(bool enable_timer) +{ + uint32_t timer_ms = RuleI(DynamicZone, ClientRemovalDelayMS); + + LogDynamicZones( + "Character [{}] instance [{}] removal timer enabled: [{}] delay (ms): [{}]", + CharacterID(), zone ? zone->GetInstanceID() : 0, enable_timer, timer_ms + ); + + if (enable_timer) + { + dynamiczone_removal_timer.Start(timer_ms); + } + else + { + dynamiczone_removal_timer.Disable(); + } +} + +void Client::SendDzCompassUpdate() +{ + // a client may be associated with multiple dynamic zones with compasses + // in the same zone. any systems that use dynamic zones need checked here + std::vector compass_entries; + + Expedition* expedition = GetExpedition(); + if (expedition) + { + auto compass = expedition->GetDynamicZone().GetCompassLocation(); + if (zone && zone->GetZoneID() == compass.zone_id) + { + DynamicZoneCompassEntry_Struct entry; + entry.dz_zone_id = static_cast(expedition->GetDynamicZone().GetZoneID()); + entry.dz_instance_id = static_cast(expedition->GetDynamicZone().GetInstanceID()); + entry.dz_type = static_cast(expedition->GetDynamicZone().GetType()); + entry.x = compass.x; + entry.y = compass.y; + entry.z = compass.z; + + compass_entries.emplace_back(entry); + } + } + + // todo: shared tasks, missions, and quests with an associated dz + + // compass set via MarkSingleCompassLocation() + if (zone && zone->GetZoneID() == m_quest_compass.zone_id) + { + DynamicZoneCompassEntry_Struct entry; + entry.dz_zone_id = 0; + entry.dz_instance_id = 0; + entry.dz_type = 0; + entry.x = m_quest_compass.x; + entry.y = m_quest_compass.y; + entry.z = m_quest_compass.z; + + compass_entries.emplace_back(entry); + } + + uint32 count = static_cast(compass_entries.size()); + uint32 entries_size = sizeof(DynamicZoneCompassEntry_Struct) * count; + uint32 outsize = sizeof(DynamicZoneCompass_Struct) + entries_size; + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzCompass, outsize)); + auto outbuf = reinterpret_cast(outapp->pBuffer); + outbuf->client_id = 0; + outbuf->count = count; + memcpy(outbuf->entries, compass_entries.data(), entries_size); + + QueuePacket(outapp.get()); +} + +void Client::GoToDzSafeReturnOrBind(const DynamicZoneLocation& safereturn) +{ + LogDynamicZonesDetail( + "Sending character [{}] in zone [{}]:[{}] to safereturn [{}] at ([{}], [{}], [{}], [{}]) or bind", + CharacterID(), + zone ? zone->GetZoneID() : 0, + zone ? zone->GetInstanceID() : 0, + safereturn.zone_id, + safereturn.x, + safereturn.y, + safereturn.z, + safereturn.heading + ); + + if (safereturn.zone_id == 0) + { + GoToBind(); + } + else + { + MovePC(safereturn.zone_id, 0, safereturn.x, safereturn.y, safereturn.z, safereturn.heading); + } +} + +void Client::MovePCDynamicZone(uint32 zone_id) +{ + if (zone_id == 0) + { + return; + } + + // check client systems for any associated dynamic zones to the requested zone id + std::vector client_dzs; + DynamicZone single_dz; + + Expedition* expedition = GetExpedition(); + if (expedition && expedition->GetDynamicZone().GetZoneID() == zone_id) + { + single_dz = expedition->GetDynamicZone(); + + DynamicZoneChooseZoneEntry_Struct dz; + dz.dz_zone_id = expedition->GetDynamicZone().GetZoneID(); + dz.dz_instance_id = expedition->GetDynamicZone().GetInstanceID(); + dz.dz_type = static_cast(expedition->GetDynamicZone().GetType()); + //dz.unknown_id2 = expedition->GetDynamicZone().GetRealID(); + strn0cpy(dz.description, expedition->GetName().c_str(), sizeof(dz.description)); + strn0cpy(dz.leader_name, expedition->GetLeaderName().c_str(), sizeof(dz.leader_name)); + + client_dzs.emplace_back(dz); + } + + // todo: check for Missions (Shared Tasks), Quests, or Tasks that have associated dzs to zone_id + + if (client_dzs.empty()) + { + MessageString(Chat::Red, DYNAMICZONE_WAY_IS_BLOCKED); // unconfirmed message + } + else if (client_dzs.size() == 1) + { + if (single_dz.GetInstanceID() == 0) + { + LogDynamicZones("Character [{}] has dz for zone [{}] with no instance id", CharacterID(), zone_id); + } + else + { + DynamicZoneLocation zonein = single_dz.GetZoneInLocation(); + ZoneMode zone_mode = ZoneMode::ZoneToSafeCoords; + if (single_dz.HasZoneInLocation()) + { + zone_mode = ZoneMode::ZoneSolicited; + } + MovePC(zone_id, single_dz.GetInstanceID(), zonein.x, zonein.y, zonein.z, zonein.heading, 0, zone_mode); + } + } + else if (client_dzs.size() > 1) + { + LogDynamicZonesDetail( + "Sending DzSwitchListWnd to character [{}] associated with [{}] dynamic zone(s)", + CharacterID(), client_dzs.size() + ); + + // more than one dynamic zone to this zone, send out the switchlist window + // note that this will most likely crash clients if they've reloaded the ui + // this occurs on live as well so it may just be a long lasting client bug + uint32 count = static_cast(client_dzs.size()); + uint32 entries_size = sizeof(DynamicZoneChooseZoneEntry_Struct) * count; + uint32 outsize = sizeof(DynamicZoneChooseZone_Struct) + entries_size; + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzChooseZone, outsize)); + auto outbuf = reinterpret_cast(outapp->pBuffer); + outbuf->client_id = 0; + outbuf->count = count; + memcpy(outbuf->choices, client_dzs.data(), entries_size); + + QueuePacket(outapp.get()); + } +} + +void Client::MovePCDynamicZone(const std::string& zone_name) +{ + auto zone_id = ZoneID(zone_name.c_str()); + MovePCDynamicZone(zone_id); +} diff --git a/zone/client.h b/zone/client.h index b9e47b903..df65b6b16 100644 --- a/zone/client.h +++ b/zone/client.h @@ -54,6 +54,7 @@ namespace EQ #include "aggromanager.h" #include "common.h" +#include "dynamiczone.h" #include "merc.h" #include "mob.h" #include "qglobals.h" @@ -1116,10 +1117,13 @@ public: 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* CreateExpedition( + std::string zone_name, uint32 version, uint32 duration, std::string expedition_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 ExpeditionLockoutTimer* GetExpeditionLockout( + const std::string& expedition_name, const std::string& event_name, bool include_expired = false) const; const std::vector& GetExpeditionLockouts() const { return m_expedition_lockouts; }; std::vector GetExpeditionLockouts(const std::string& expedition_name); uint32 GetPendingExpeditionInviteID() const { return m_pending_expedition_invite_id; } @@ -1131,6 +1135,11 @@ public: void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; void UpdateExpeditionInfoAndLockouts(); void DzListTimers(); + void SetDzRemovalTimer(bool enable_timer); + void SendDzCompassUpdate(); + void GoToDzSafeReturnOrBind(const DynamicZoneLocation& safereturn); + void MovePCDynamicZone(uint32 zone_id); + void MovePCDynamicZone(const std::string& zone_name); void CalcItemScale(); bool CalcItemScale(uint32 slot_x, uint32 slot_y); // behavior change: 'slot_y' is now [RANGE]_END and not [RANGE]_END + 1 @@ -1585,6 +1594,7 @@ private: Timer hp_other_update_throttle_timer; /* This is to keep clients from DOSing the server with macros that change client targets constantly */ Timer position_update_timer; /* Timer used when client hasn't updated within a 10 second window */ Timer consent_throttle_timer; + Timer dynamiczone_removal_timer; glm::vec3 m_Proximity; glm::vec4 last_position_before_bulk_update; @@ -1688,8 +1698,8 @@ private: uint32 m_expedition_id = 0; uint32 m_pending_expedition_invite_id = 0; - Expedition* m_expedition = nullptr; std::vector m_expedition_lockouts; + DynamicZoneLocation m_quest_compass; #ifdef BOTS diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 798a4c552..704e3dc4b 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5637,9 +5637,30 @@ void Client::Handle_OP_DzAddPlayer(const EQApplicationPacket *app) void Client::Handle_OP_DzChooseZoneReply(const EQApplicationPacket *app) { - // todo: implement - LogExpeditionsModerate("Handle_OP_DzChooseZoneReply"); auto dzmsg = reinterpret_cast(app->pBuffer); + LogDynamicZones( + "Character [{}] chose DynamicZone [{}]:[{}] type: [{}] with system id: [{}]", + CharacterID(), dzmsg->dz_zone_id, dzmsg->dz_instance_id, dzmsg->dz_type, dzmsg->unknown_id2 + ); + + if (!dzmsg->dz_instance_id || !database.VerifyInstanceAlive(dzmsg->dz_instance_id, CharacterID())) + { + // live just no-ops this without a message + LogDynamicZones( + "Character [{}] chose invalid DynamicZone [{}]:[{}] or is no longer a member", + CharacterID(), dzmsg->dz_zone_id, dzmsg->dz_instance_id + ); + return; + } + + DynamicZone dz = DynamicZone::LoadDzFromDatabase(dzmsg->dz_instance_id); + DynamicZoneLocation loc = dz.GetZoneInLocation(); + ZoneMode zone_mode = ZoneMode::ZoneToSafeCoords; + if (dz.HasZoneInLocation()) + { + zone_mode = ZoneMode::ZoneSolicited; + } + MovePC(dzmsg->dz_zone_id, dzmsg->dz_instance_id, loc.x, loc.y, loc.z, loc.heading, 0, zone_mode); } void Client::Handle_OP_DzExpeditionInviteResponse(const EQApplicationPacket *app) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index c898c249f..353a02267 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -161,6 +161,16 @@ bool Client::Process() { if (TaskPeriodic_Timer.Check() && taskstate) taskstate->TaskPeriodicChecks(this); + if (dynamiczone_removal_timer.Check()) + { + dynamiczone_removal_timer.Disable(); + if (zone && zone->GetInstanceID() != 0) + { + DynamicZone dz = DynamicZone::LoadDzFromDatabase(zone->GetInstanceID()); + GoToDzSafeReturnOrBind(dz.GetSafeReturnLocation()); + } + } + if (linkdead_timer.Check()) { LeaveGroup(); Save(); diff --git a/zone/command.cpp b/zone/command.cpp index 8a67846e0..789528fdb 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6837,22 +6837,24 @@ void command_dz(Client* c, const Seperator* sep) { if (strcasecmp(sep->arg[2], "list") == 0) { - c->Message(Chat::White, "Total Active Expeditions: [%u]", static_cast(zone->expedition_cache.size())); + c->Message(Chat::White, fmt::format("Total Active Expeditions: [{}]", zone->expedition_cache.size()).c_str()); for (const auto& expedition : zone->expedition_cache) { - c->Message( - Chat::White, "Expedition id: [%u]: leader: [%s] instance id: [%u] members: [%u]", + c->Message(Chat::White, fmt::format( + "Expedition id: [{}]: leader: [{}] instance id: [{}] members: [{}]", expedition.second->GetID(), - expedition.second->GetLeaderName().c_str(), + expedition.second->GetLeaderName(), expedition.second->GetInstanceID(), expedition.second->GetMemberCount() - ); + ).c_str()); } } else if (strcasecmp(sep->arg[2], "reload") == 0) { Expedition::CacheAllFromDatabase(); - c->Message(Chat::White, "Reloaded [%u] expeditions to cache from database.", static_cast(zone->expedition_cache.size())); + c->Message(Chat::White, fmt::format( + "Reloaded [{}] expeditions to cache from database.", zone->expedition_cache.size() + ).c_str()); } } else if (strcasecmp(sep->arg[1], "destroy") == 0) @@ -6870,12 +6872,53 @@ void command_dz(Client* c, const Seperator* sep) } } } + else if (strcasecmp(sep->arg[1], "list") == 0) + { + std::string query = SQL( + SELECT + dynamic_zones.type, + instance_list.id, + instance_list.zone, + instance_list.version, + instance_list.start_time, + instance_list.duration, + COUNT(instance_list.id) member_count + FROM dynamic_zones + INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id + LEFT JOIN instance_list_player ON instance_list.id = instance_list_player.id + GROUP BY instance_list.id; + ); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + c->Message(Chat::White, fmt::format("Total Dynamic Zones: [{}]", results.RowCount()).c_str()); + for (auto row = results.begin(); row != results.end(); ++row) + { + auto start_time = strtoul(row[4], nullptr, 10); + auto duration = strtoul(row[5], nullptr, 10); + auto expire_time = std::chrono::system_clock::from_time_t(start_time + duration); + bool is_expired = std::chrono::system_clock::now() > expire_time; + + c->Message(Chat::White, fmt::format( + "type: [{}] instance: [{}] zone: [{}] version: [{}] members: [{}] expired: [{}]", + strtoul(row[0], nullptr, 10), + strtoul(row[1], nullptr, 10), + strtoul(row[2], nullptr, 10), + strtoul(row[3], nullptr, 10), + strtoul(row[6], nullptr, 10), + is_expired + ).c_str()); + } + } + } 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 - destroy expedition globally (must be in cache)"); + c->Message(Chat::White, "#dz list - list all dynamic zones with corresponding instance ids from database"); } } diff --git a/zone/doors.cpp b/zone/doors.cpp index ddaa5709e..ba3d36d21 100644 --- a/zone/doors.cpp +++ b/zone/doors.cpp @@ -207,6 +207,8 @@ void Doors::HandleClick(Client* sender, uint8 trigger) { } } + // todo: if IsDzDoor() call Client::MovePCDynamicZone(target_zone_id) (for systems that use dzs) + uint32 required_key_item = GetKeyItem(); uint8 disable_add_to_key_ring = GetNoKeyring(); uint32 player_has_key = 0; diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp new file mode 100644 index 000000000..0fcd2b8b2 --- /dev/null +++ b/zone/dynamiczone.cpp @@ -0,0 +1,507 @@ +/** + * 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 "dynamiczone.h" +#include "client.h" +#include "worldserver.h" +#include "zonedb.h" +#include "../common/eqemu_logsys.h" +#include + +extern WorldServer worldserver; + +DynamicZone::DynamicZone( + uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type +) : + m_zone_id(zone_id), + m_version(version), + m_duration(duration), + m_type(type) +{ +} + +DynamicZone::DynamicZone( + std::string zone_shortname, uint32_t version, uint32_t duration, DynamicZoneType type +) : + m_version(version), + m_duration(duration), + m_type(type) +{ + m_zone_id = ZoneID(zone_shortname.c_str()); + + if (!m_zone_id) + { + LogDynamicZones("Failed to get zone id for zone [{}]", zone_shortname); + } +} + +DynamicZone DynamicZone::LoadDzFromDatabase(uint32_t instance_id) +{ + DynamicZone dynamic_zone; + if (instance_id != 0) + { + dynamic_zone.LoadFromDatabase(instance_id); + } + return dynamic_zone; +} + +uint32_t DynamicZone::CreateInstance() +{ + if (m_instance_id) + { + LogDynamicZones("CreateInstance failed, instance id [{}] already created", m_instance_id); + return 0; + } + + if (!m_zone_id) + { + LogDynamicZones("CreateInstance failed, invalid zone id [{}]", m_zone_id); + return 0; + } + + uint16_t instance_id = 0; + if (!database.GetUnusedInstanceID(instance_id)) // todo: doesn't this race with insert? + { + LogDynamicZones("Failed to find unused instance id"); + return 0; + } + + auto start_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + + std::string query = fmt::format(SQL( + INSERT INTO instance_list + (id, zone, version, start_time, duration) + VALUES + ({}, {}, {}, {}, {}) + ), instance_id, m_zone_id, m_version, start_time, m_duration); + + auto results = database.QueryDatabase(query); + if (!results.Success()) + { + LogDynamicZones("Failed to create instance [{}] for Dynamic Zone [{}]", instance_id, m_zone_id); + return 0; + } + + m_instance_id = instance_id; + m_start_time = static_cast(start_time); + m_never_expires = false; + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + + return m_instance_id; +} + +void DynamicZone::LoadFromDatabase(uint32_t instance_id) +{ + if (instance_id == 0) + { + return; + } + + if (m_instance_id) + { + LogDynamicZones( + "Loading instance data for [{}] failed, instance id [{}] data already loaded", + instance_id, m_instance_id + ); + return; + } + + LogDynamicZonesDetail("Loading dz instance [{}] from database", instance_id); + + std::string query = fmt::format(SQL( + SELECT + instance_list.zone, + instance_list.version, + instance_list.start_time, + instance_list.duration, + instance_list.never_expires, + dynamic_zones.type, + dynamic_zones.compass_zone_id, + dynamic_zones.compass_x, + dynamic_zones.compass_y, + dynamic_zones.compass_z, + dynamic_zones.safe_return_zone_id, + dynamic_zones.safe_return_x, + dynamic_zones.safe_return_y, + dynamic_zones.safe_return_z, + dynamic_zones.safe_return_heading, + dynamic_zones.zone_in_x, + dynamic_zones.zone_in_y, + dynamic_zones.zone_in_z, + dynamic_zones.zone_in_heading, + dynamic_zones.has_zone_in + FROM dynamic_zones + INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id + WHERE dynamic_zones.instance_id = {}; + ), instance_id); + + auto results = database.QueryDatabase(query); + if (results.Success() && results.RowCount() > 0) + { + auto row = results.begin(); + + m_instance_id = instance_id; + m_zone_id = strtoul(row[0], nullptr, 10); + m_version = strtoul(row[1], nullptr, 10); + m_start_time = strtoul(row[2], nullptr, 10); + m_duration = strtoul(row[3], nullptr, 10); + m_never_expires = (strtoul(row[4], nullptr, 10) != 0); + m_type = static_cast(strtoul(row[5], nullptr, 10)); + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + m_compass.zone_id = strtoul(row[6], nullptr, 10); + m_compass.x = strtof(row[7], nullptr); + m_compass.y = strtof(row[8], nullptr); + m_compass.z = strtof(row[9], nullptr); + m_safereturn.zone_id = strtoul(row[10], nullptr, 10); + m_safereturn.x = strtof(row[11], nullptr); + m_safereturn.y = strtof(row[12], nullptr); + m_safereturn.z = strtof(row[13], nullptr); + m_safereturn.heading = strtof(row[14], nullptr); + m_zonein.x = strtof(row[15], nullptr); + m_zonein.y = strtof(row[16], nullptr); + m_zonein.z = strtof(row[17], nullptr); + m_zonein.heading = strtof(row[18], nullptr); + m_has_zonein = (strtoul(row[19], nullptr, 10) != 0); + } +} + +uint32_t DynamicZone::SaveToDatabase() +{ + LogDynamicZonesDetail("Saving dz instance [{}] to database", m_instance_id); + + if (m_instance_id != 0) + { + std::string query = fmt::format(SQL( + INSERT INTO dynamic_zones + ( + instance_id, + type, + compass_zone_id, + compass_x, + compass_y, + compass_z, + safe_return_zone_id, + safe_return_x, + safe_return_y, + safe_return_z, + safe_return_heading, + zone_in_x, + zone_in_y, + zone_in_z, + zone_in_heading, + has_zone_in + ) + VALUES + ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}); + ), + m_instance_id, + static_cast(m_type), + m_compass.zone_id, + m_compass.x, + m_compass.y, + m_compass.z, + m_safereturn.zone_id, + m_safereturn.x, + m_safereturn.y, + m_safereturn.z, + m_safereturn.heading, + m_zonein.x, + m_zonein.y, + m_zonein.z, + m_zonein.heading, + m_has_zonein + ); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + return results.LastInsertedID(); + } + } + return 0; +} + +void DynamicZone::SaveCompassToDatabase() +{ + LogDynamicZonesDetail( + "Instance [{}] saving compass zone: [{}] xyz: ([{}], [{}], [{}])", + m_instance_id, m_compass.zone_id, m_compass.x, m_compass.y, m_compass.z + ); + + if (m_instance_id != 0) + { + std::string query = fmt::format(SQL( + UPDATE dynamic_zones SET + compass_zone_id = {}, + compass_x = {}, + compass_y = {}, + compass_z = {} + WHERE instance_id = {}; + ), + m_compass.zone_id, + m_compass.x, + m_compass.y, + m_compass.z, + m_instance_id + ); + + database.QueryDatabase(query); + } +} + +void DynamicZone::SaveSafeReturnToDatabase() +{ + LogDynamicZonesDetail( + "Instance [{}] saving safereturn zone: [{}] xyzh: ([{}], [{}], [{}], [{}])", + m_instance_id, m_safereturn.zone_id, m_safereturn.x, m_safereturn.y, m_safereturn.z, m_safereturn.heading + ); + + if (m_instance_id != 0) + { + std::string query = fmt::format(SQL( + UPDATE dynamic_zones SET + safe_return_zone_id = {}, + safe_return_x = {}, + safe_return_y = {}, + safe_return_z = {}, + safe_return_heading = {} + WHERE instance_id = {}; + ), + m_safereturn.zone_id, + m_safereturn.x, + m_safereturn.y, + m_safereturn.z, + m_safereturn.heading, + m_instance_id + ); + + database.QueryDatabase(query); + } +} + +void DynamicZone::SaveZoneInLocationToDatabase() +{ + LogDynamicZonesDetail( + "Instance [{}] saving zonein zone: [{}] xyzh: ([{}], [{}], [{}], [{}]) has: [{}]", + m_instance_id, m_zone_id, m_zonein.x, m_zonein.y, m_zonein.z, m_zonein.heading, m_has_zonein + ); + + if (m_instance_id != 0) + { + std::string query = fmt::format(SQL( + UPDATE dynamic_zones SET + zone_in_x = {}, + zone_in_y = {}, + zone_in_z = {}, + zone_in_heading = {}, + has_zone_in = {} + WHERE instance_id = {}; + ), + m_zonein.x, + m_zonein.y, + m_zonein.z, + m_zonein.heading, + m_has_zonein, + m_instance_id + ); + + database.QueryDatabase(query); + } +} + + +void DynamicZone::DeleteFromDatabase() +{ + LogDynamicZonesDetail("Deleting dz instance [{}] from database", m_instance_id); + + if (m_instance_id != 0) + { + std::string query = fmt::format(SQL( + DELETE FROM dynamic_zones WHERE instance_id = {}; + ), m_instance_id); + + database.QueryDatabase(query); + } +} + +void DynamicZone::AddCharacter(uint32_t character_id) +{ + database.AddClientToInstance(m_instance_id, character_id); + SendInstanceCharacterChange(character_id, false); // stops client kick timer +} + +void DynamicZone::RemoveCharacter(uint32_t character_id) +{ + database.RemoveClientFromInstance(m_instance_id, character_id); + SendInstanceCharacterChange(character_id, true); // start client kick timer +} + +void DynamicZone::RemoveAllCharacters() +{ + // caller has to notify clients of instance change since we don't hold members here + if (m_instance_id != 0) + { + database.RemoveClientsFromInstance(m_instance_id); + } +} + +void DynamicZone::SaveInstanceMembersToDatabase(const std::unordered_set character_ids) +{ + std::string insert_values; + for (const auto& character_id : character_ids) + { + fmt::format_to(std::back_inserter(insert_values), "({}, {}),", m_instance_id, character_id); + } + + if (!insert_values.empty()) + { + insert_values.pop_back(); // trailing comma + + std::string query = fmt::format(SQL( + REPLACE INTO instance_list_player (id, charid) VALUES {}; + ), insert_values); + + auto results = database.QueryDatabase(query); + if (!results.Success()) + { + LogDynamicZones("Failed to save instance members to database"); + } + } +} + +void DynamicZone::SendInstanceCharacterChange(uint32_t character_id, bool removed) +{ + // if removing, sets removal timer on client inside the instance + if (IsCurrentZoneDzInstance()) + { + Client* client = entity_list.GetClientByCharID(character_id); + if (client) + { + client->SetDzRemovalTimer(removed); + } + } + else if (GetInstanceID() != 0) + { + uint32_t packsize = sizeof(ServerDzCharacter_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_DzCharacterChange, packsize)); + auto packbuf = reinterpret_cast(pack->pBuffer); + packbuf->instance_id = GetInstanceID(); + packbuf->remove = removed; + packbuf->character_id = character_id; + worldserver.SendPacket(pack.get()); + } +} + +void DynamicZone::UpdateExpireTime(uint32_t seconds) +{ + if (GetInstanceID() == 0) + { + return; + } + + m_duration = seconds; + m_expire_time = std::chrono::system_clock::now() + std::chrono::seconds(seconds); + + auto new_duration = std::chrono::system_clock::to_time_t(m_expire_time) - m_start_time; + + std::string query = fmt::format(SQL( + UPDATE instance_list SET duration = {} WHERE id = {}; + ), new_duration, GetInstanceID()); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + uint32_t packsize = sizeof(ServerInstanceUpdateTime_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_InstanceUpdateTime, packsize)); + auto packbuf = reinterpret_cast(pack->pBuffer); + packbuf->instance_id = GetInstanceID(); + packbuf->new_duration = seconds; + worldserver.SendPacket(pack.get()); + } +} + +void DynamicZone::SetCompass(const DynamicZoneLocation& location, bool update_db) +{ + m_compass = location; + + if (update_db) + { + SaveCompassToDatabase(); + } +} + +void DynamicZone::SetSafeReturn(const DynamicZoneLocation& location, bool update_db) +{ + m_safereturn = location; + + if (update_db) + { + SaveSafeReturnToDatabase(); + } +} + +void DynamicZone::SetZoneInLocation(const DynamicZoneLocation& location, bool update_db) +{ + m_zonein = location; + m_has_zonein = true; + + if (update_db) + { + SaveZoneInLocationToDatabase(); + } +} + +bool DynamicZone::IsCurrentZoneDzInstance() const +{ + return (zone && zone->GetInstanceID() != 0 && zone->GetInstanceID() == GetInstanceID()); +} + +bool DynamicZone::IsInstanceID(uint32_t instance_id) const +{ + return (GetInstanceID() != 0 && GetInstanceID() == instance_id); +} + +uint32_t DynamicZone::GetSecondsRemaining() const +{ + auto now = std::chrono::system_clock::now(); + if (m_expire_time > now) + { + auto remaining = m_expire_time - now; + return static_cast(std::chrono::duration_cast(remaining).count()); + } + return 0; +} + +void DynamicZone::HandleWorldMessage(ServerPacket* pack) +{ + switch (pack->opcode) + { + case ServerOP_DzCharacterChange: + { + auto buf = reinterpret_cast(pack->pBuffer); + Client* client = entity_list.GetClientByCharID(buf->character_id); + if (client) + { + client->SetDzRemovalTimer(buf->remove); // instance kick timer + } + break; + } + } +} diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h new file mode 100644 index 000000000..ceeef14ff --- /dev/null +++ b/zone/dynamiczone.h @@ -0,0 +1,117 @@ +/** + * 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 DYNAMICZONE_H +#define DYNAMICZONE_H + +#include +#include +#include +#include +#include + +class ServerPacket; + +enum class DynamicZoneType : uint8_t // DynamicZoneActiveType +{ + None = 0, + Expedition, + Tutorial, + Task, + Mission, // Shared Task + Quest +}; + +struct DynamicZoneLocation +{ + uint32_t zone_id = 0; + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float heading = 0.0f; + + DynamicZoneLocation() {} + DynamicZoneLocation(uint32_t zone_id_, float x_, float y_, float z_, float heading_) + : zone_id(zone_id_), x(x_), y(y_), z(z_), heading(heading_) {} +}; + +class DynamicZone +{ +public: + DynamicZone() = default; + DynamicZone(uint32_t zone_id, uint32_t version, uint32_t duration, DynamicZoneType type); + DynamicZone(std::string zone_shortname, uint32_t version, uint32_t duration, DynamicZoneType type); + DynamicZone(DynamicZoneType type) : m_type(type) { } + + static DynamicZone LoadDzFromDatabase(uint32_t instance_id); + static void HandleWorldMessage(ServerPacket* pack); + + DynamicZoneType GetType() const { return m_type; } + DynamicZoneLocation GetCompassLocation() const { return m_compass; } + DynamicZoneLocation GetSafeReturnLocation() const { return m_safereturn; } + DynamicZoneLocation GetZoneInLocation() const { return m_zonein; } + + uint32_t CreateInstance(); + void AddCharacter(uint32_t character_id); + void SaveInstanceMembersToDatabase(const std::unordered_set character_ids); + + uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(m_expire_time); } + uint16_t GetInstanceID() const { return static_cast(m_instance_id); }; + //uint32_t GetRealID() const { return (m_instance_id << 16) | (m_zone_id & 0xffff); } + uint32_t GetSecondsRemaining() const; + uint16_t GetZoneID() const { return static_cast(m_zone_id); }; + uint32_t GetZoneVersion() const { return m_version; }; + + bool HasZoneInLocation() const { return m_has_zonein; } + bool IsCurrentZoneDzInstance() const; + bool IsInstanceID(uint32_t instance_id) const; + bool IsValid() const { return m_instance_id != 0; } + void RemoveAllCharacters(); + void RemoveCharacter(uint32_t character_id); + void SendInstanceCharacterChange(uint32_t character_id, bool removed); + void SetCompass(const DynamicZoneLocation& location, bool update_db = false); + void SetSafeReturn(const DynamicZoneLocation& location, bool update_db = false); + void SetZoneInLocation(const DynamicZoneLocation& location, bool update_db = false); + void UpdateExpireTime(uint32_t seconds); + + void LoadFromDatabase(uint32_t instance_id); + uint32_t SaveToDatabase(); + +private: + void DeleteFromDatabase(); + void SaveCompassToDatabase(); + void SaveSafeReturnToDatabase(); + void SaveZoneInLocationToDatabase(); + + uint32_t m_zone_id = 0; + uint32_t m_instance_id = 0; + uint32_t m_version = 0; + uint32_t m_start_time = 0; + uint32_t m_duration = 0; + bool m_never_expires = false; + bool m_has_zonein = false; + DynamicZoneType m_type = DynamicZoneType::None; + DynamicZoneLocation m_compass; + DynamicZoneLocation m_safereturn; + DynamicZoneLocation m_zonein; + std::chrono::time_point m_expire_time; //uint64_t m_expire_time = 0; +}; + +#endif diff --git a/zone/entity.cpp b/zone/entity.cpp index 417bac493..e19ecdf73 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -5202,3 +5202,26 @@ std::unordered_map &EntityList::GetCloseMobList(Mob *mob, float d return mob_list; } +void EntityList::GateAllClientsToSafeReturn() +{ + DynamicZone dz; + if (zone) + { + dz = DynamicZone::LoadDzFromDatabase(zone->GetInstanceID()); + + LogDynamicZones( + "Sending all clients in zone: [{}] instance: [{}] to dz safereturn or bind", + zone->GetZoneID(), zone->GetInstanceID() + ); + } + + for (const auto& client_list_iter : client_list) + { + Client* client = client_list_iter.second; + if (client) + { + // falls back to gating clients to bind if dz invalid + client->GoToDzSafeReturnOrBind(dz.GetSafeReturnLocation()); + } + } +} diff --git a/zone/entity.h b/zone/entity.h index 3815f3c50..b7f90b6f9 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -49,6 +49,7 @@ class Raid; class Spawn2; class Trap; +struct DynamicZoneSafeReturn; struct GuildBankItemUpdate_Struct; struct NewSpawn_Struct; struct QGlobal; @@ -496,6 +497,8 @@ public: void UpdateFindableNPCState(NPC *n, bool Remove); void HideCorpses(Client *c, uint8 CurrentMode, uint8 NewMode); + void GateAllClientsToSafeReturn(); + uint16 GetClientCount(); void GetMobList(std::list &m_list); void GetNPCList(std::list &n_list); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 95d01577e..cc409da06 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -26,8 +26,8 @@ #include "groups.h" #include "raids.h" #include "string_ids.h" -#include "zonedb.h" #include "worldserver.h" +#include "zonedb.h" #include "../common/eqemu_logsys.h" extern WorldServer worldserver; @@ -46,10 +46,11 @@ const uint32_t Expedition::REPLAY_TIMER_ID = std::numeric_limits::max( const uint32_t Expedition::EVENT_TIMER_ID = 1; Expedition::Expedition( - uint32_t id, std::string expedition_name, const ExpeditionMember& leader, - uint32_t min_players, uint32_t max_players, bool replay_timer + uint32_t id, const DynamicZone& dynamic_zone, std::string expedition_name, + const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players, bool replay_timer ) : m_id(id), + m_dynamiczone(dynamic_zone), m_expedition_name(expedition_name), m_leader(leader), m_min_players(min_players), @@ -59,7 +60,7 @@ Expedition::Expedition( } Expedition* Expedition::TryCreate( - Client* requester, std::string name, uint32_t min_players, uint32_t max_players, bool replay_timer) + Client* requester, DynamicZone& dynamiczone, ExpeditionRequest& request) { if (!requester || !zone) { @@ -67,10 +68,25 @@ Expedition* Expedition::TryCreate( } // request parses leader, members list, and lockouts while validating - ExpeditionRequest request(requester, name, min_players, max_players, replay_timer); - if (!request.Validate()) + if (!request.Validate(requester)) { - LogExpeditionsModerate("Creation of [{}] by [{}] denied", name, requester->GetName()); + LogExpeditionsModerate( + "Creation of [{}] by [{}] denied", request.GetExpeditionName(), requester->GetName() + ); + return nullptr; + } + + if (dynamiczone.GetInstanceID() == 0) + { + dynamiczone.CreateInstance(); + } + + if (dynamiczone.GetInstanceID() == 0) + { + // live uses this message when trying to enter an instance that isn't ready + // we can use it as the client error message if instance creation fails + requester->MessageString(Chat::Red, DZ_PREVENT_ENTERING); + LogExpeditions("Failed to create a dynamic zone instance for expedition"); return nullptr; } @@ -78,19 +94,33 @@ Expedition* Expedition::TryCreate( // unique expedition ids are created from database via auto-increment column auto expedition_id = ExpeditionDatabase::InsertExpedition( - name, leader.char_id, min_players, max_players, replay_timer + dynamiczone.GetInstanceID(), + request.GetExpeditionName(), + leader.char_id, + request.GetMinPlayers(), + request.GetMaxPlayers(), + request.HasReplayTimer() ); if (expedition_id) { - auto expedition = std::make_unique( - expedition_id, name, leader, min_players, max_players, replay_timer - ); + dynamiczone.SaveToDatabase(); + + auto expedition = std::unique_ptr(new Expedition( + expedition_id, + dynamiczone, + request.GetExpeditionName(), + leader, + request.GetMinPlayers(), + request.GetMaxPlayers(), + request.HasReplayTimer() + )); LogExpeditions( - "Created [{}] ({}) leader: [{}] minplayers: [{}] maxplayers: [{}]", + "Created [{}] ({}) instance id: [{}] leader: [{}] minplayers: [{}] maxplayers: [{}]", expedition->GetID(), expedition->GetName(), + expedition->GetInstanceID(), expedition->GetLeaderName(), expedition->GetMinPlayers(), expedition->GetMaxPlayers() @@ -104,7 +134,7 @@ Expedition* Expedition::TryCreate( Client* leader_client = request.GetLeaderClient(); Client::SendCrossZoneMessageString( - leader_client, leader.name, Chat::Yellow, EXPEDITION_AVAILABLE, { name } + leader_client, leader.name, Chat::Yellow, EXPEDITION_AVAILABLE, { request.GetExpeditionName() } ); auto inserted = zone->expedition_cache.emplace(expedition_id, std::move(expedition)); @@ -129,15 +159,19 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) { auto leader_id = static_cast(strtoul(row[3], nullptr, 10)); ExpeditionMember leader{ leader_id, row[7] }; // id, name + auto instance_id = row[1] ? strtoul(row[1], nullptr, 10) : 0; // can be null from fk constraint - std::unique_ptr expedition = std::make_unique( + DynamicZone dynamic_zone = DynamicZone::LoadDzFromDatabase(instance_id); + + std::unique_ptr expedition = std::unique_ptr(new Expedition( expedition_id, + dynamic_zone, row[2], // expedition name leader, // expedition leader strtoul(row[4], nullptr, 10), // min_players strtoul(row[5], nullptr, 10), // max_players (strtoul(row[6], nullptr, 10) != 0) // has_replay_timer - ); + )); expedition->LoadMembers(); expedition->SendUpdatesToZoneMembers(); @@ -145,7 +179,6 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) // don't bother caching empty expeditions if (expedition->GetMemberCount() > 0) { - expedition->SendWorldGetOnlineMembers(); zone->expedition_cache.emplace(expedition_id, std::move(expedition)); } } @@ -214,7 +247,7 @@ bool Expedition::CacheAllFromDatabase() auto end = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast>(end - start); - LogExpeditions("Caching [{}] expeditions took {}s", zone->expedition_cache.size(), elapsed.count()); + LogExpeditions("Caching [{}] expedition(s) took {}s", zone->expedition_cache.size(), elapsed.count()); return true; } @@ -251,8 +284,9 @@ void Expedition::LoadMembers() { auto character_id = strtoul(row[0], nullptr, 10); bool is_current_member = strtoul(row[1], nullptr, 10); - AddInternalMember(row[2], character_id, is_current_member, true); + AddInternalMember(row[2], character_id, ExpeditionMemberStatus::Offline, is_current_member); } + SendWorldGetOnlineMembers(); } } @@ -270,6 +304,7 @@ void Expedition::SaveMembers(ExpeditionRequest& request) m_member_id_history.emplace(member.char_id); } ExpeditionDatabase::InsertMembers(m_id, m_members); + m_dynamiczone.SaveInstanceMembersToDatabase(m_member_id_history); // all are current members here } Expedition* Expedition::FindCachedExpeditionByCharacterID(uint32_t character_id) @@ -317,9 +352,13 @@ Expedition* Expedition::FindCachedExpeditionByID(uint32_t expedition_id) Expedition* Expedition::FindExpeditionByInstanceID(uint32_t instance_id) { - // ask database since it may have expired - auto expedition_id = ExpeditionDatabase::GetExpeditionIDFromInstanceID(instance_id); - return Expedition::FindCachedExpeditionByID(expedition_id); + if (instance_id) + { + // ask database since it may have expired + auto expedition_id = ExpeditionDatabase::GetExpeditionIDFromInstanceID(instance_id); + return Expedition::FindCachedExpeditionByID(expedition_id); + } + return nullptr; } bool Expedition::HasLockout(const std::string& event_name) @@ -329,7 +368,7 @@ bool Expedition::HasLockout(const std::string& event_name) bool Expedition::HasReplayLockout() { - return (m_lockouts.find(DZ_REPLAY_TIMER_NAME) != m_lockouts.end()); + return HasLockout(DZ_REPLAY_TIMER_NAME); } bool Expedition::HasMember(uint32_t character_id) @@ -419,7 +458,7 @@ void Expedition::AddInternalLockout(ExpeditionLockoutTimer&& lockout_timer) } void Expedition::AddInternalMember( - const std::string& char_name, uint32_t character_id, bool is_current_member, bool offline) + const std::string& char_name, uint32_t character_id, ExpeditionMemberStatus status, bool is_current_member) { if (is_current_member) { @@ -430,7 +469,6 @@ void Expedition::AddInternalMember( if (it == m_members.end()) { - auto status = offline ? ExpeditionMemberStatus::Offline : ExpeditionMemberStatus::Online; m_members.emplace_back(ExpeditionMember{character_id, char_name, status}); } } @@ -446,6 +484,7 @@ bool Expedition::AddMember(const std::string& add_char_name, uint32_t add_char_i } ExpeditionDatabase::InsertMember(m_id, add_char_id); + m_dynamiczone.AddCharacter(add_char_id); ProcessMemberAdded(add_char_name, add_char_id); SendWorldMemberChanged(add_char_name, add_char_id, false); @@ -453,8 +492,24 @@ bool Expedition::AddMember(const std::string& add_char_name, uint32_t add_char_i return true; } -void Expedition::RemoveAllMembers() +void Expedition::RemoveAllMembers(bool enable_removal_timers, bool update_dz_expire_time) { + m_dynamiczone.RemoveAllCharacters(); + + if (enable_removal_timers) + { + // expedition holds member list (not dz) so inform dz members to start kick timers + for (const auto& member : m_members) + { + m_dynamiczone.SendInstanceCharacterChange(member.char_id, true); + } + } + + if (update_dz_expire_time && RuleB(Expedition, EmptyDzShutdownEnabled)) + { + m_dynamiczone.UpdateExpireTime(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); + } + ExpeditionDatabase::DeleteAllMembers(m_id); ExpeditionDatabase::DeleteExpedition(m_id); @@ -471,6 +526,7 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) } ExpeditionDatabase::UpdateMemberRemoved(m_id, member.char_id); + m_dynamiczone.RemoveCharacter(member.char_id); ProcessMemberRemoved(member.name, member.char_id); SendWorldMemberChanged(member.name, member.char_id, true); @@ -480,11 +536,14 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) { ChooseNewLeader(); } - - if (m_members.empty()) + else if (m_members.empty()) { // cache removal will occur in world message handler ExpeditionDatabase::DeleteExpedition(m_id); + if (RuleB(Expedition, EmptyDzShutdownEnabled)) + { + m_dynamiczone.UpdateExpireTime(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); + } } return true; @@ -506,6 +565,8 @@ void Expedition::SwapMember(Client* add_client, const std::string& remove_char_n // make remove and add atomic to avoid racing with separate world messages ExpeditionDatabase::UpdateMemberRemoved(m_id, member.char_id); ExpeditionDatabase::InsertMember(m_id, add_client->CharacterID()); + m_dynamiczone.RemoveCharacter(member.char_id); + m_dynamiczone.AddCharacter(add_client->CharacterID()); ProcessMemberRemoved(member.name, member.char_id); ProcessMemberAdded(add_client->GetName(), add_client->CharacterID()); @@ -710,44 +771,36 @@ void Expedition::DzInviteResponse( add_client->GetName(), accepted, has_swap_name, swap_remove_name ); - // if client accepts the invite we need to re-confirm there's no conflicts + // a null leader_client is handled by SendLeaderMessage fallbacks // note current leader receives invite reply messages (if leader changed) - bool was_swap_invite = (has_swap_name && !swap_remove_name.empty()); - - // null leader_client is handled by SendLeaderMessage fallbacks Client* leader_client = entity_list.GetClientByCharID(m_leader.char_id); - bool has_conflicts = false; - if (accepted) + if (!accepted) { - has_conflicts = ProcessAddConflicts(leader_client, add_client, was_swap_invite); + SendLeaderMessage(leader_client, Chat::Red, EXPEDITION_INVITE_DECLINED, { add_client->GetName() }); + return; } + bool was_swap_invite = (has_swap_name && !swap_remove_name.empty()); + bool has_conflicts = ProcessAddConflicts(leader_client, add_client, was_swap_invite); + // error if swapping and character was already removed before the accept - if (accepted && was_swap_invite && !HasMember(swap_remove_name)) + if (was_swap_invite && !HasMember(swap_remove_name)) { has_conflicts = true; } - if (accepted && !has_conflicts) - { - SendLeaderMessage(leader_client, Chat::Yellow, EXPEDITION_INVITE_ACCEPTED, { add_client->GetName() }); - } - else if (accepted) + if (has_conflicts) { SendLeaderMessage(leader_client, Chat::Red, EXPEDITION_INVITE_ERROR, { add_client->GetName() }); } else { - SendLeaderMessage(leader_client, Chat::Red, EXPEDITION_INVITE_DECLINED, { add_client->GetName() }); - } + SendLeaderMessage(leader_client, Chat::Yellow, EXPEDITION_INVITE_ACCEPTED, { add_client->GetName() }); - if (accepted && !has_conflicts) - { // insert pending lockouts client will receive when entering dynamic zone. - // only lockouts missing from the client when they join are added. all - // missing lockouts are not applied on entering instance because client may - // have a lockout that expires after joining and shouldn't receive it again. + // only lockouts missing from client when they join are added. client may + // have a lockout that expires after joining and shouldn't receive it again ExpeditionDatabase::DeletePendingLockouts(add_client->CharacterID()); std::vector pending_lockouts; @@ -771,7 +824,10 @@ void Expedition::DzInviteResponse( } } - ExpeditionDatabase::InsertCharacterLockouts(add_client->CharacterID(), pending_lockouts, false, true); + bool add_immediately = m_dynamiczone.IsCurrentZoneDzInstance(); + + ExpeditionDatabase::InsertCharacterLockouts( + add_client->CharacterID(), pending_lockouts, false, !add_immediately); if (was_swap_invite) { @@ -781,6 +837,11 @@ void Expedition::DzInviteResponse( { AddMember(add_client->GetName(), add_client->CharacterID()); } + + if (m_dynamiczone.IsCurrentZoneDzInstance()) + { + SetMemberStatus(add_client, ExpeditionMemberStatus::InDynamicZone); + } } } @@ -1083,11 +1144,12 @@ void Expedition::ProcessMemberAdded(std::string char_name, uint32_t added_char_i if (member_client) { member_client->SetExpeditionID(GetID()); + member_client->SendDzCompassUpdate(); SendClientExpeditionInfo(member_client); member_client->MessageString(Chat::Yellow, EXPEDITION_MEMBER_ADDED, char_name.c_str(), m_expedition_name.c_str()); } - AddInternalMember(char_name, added_char_id); + AddInternalMember(char_name, added_char_id, ExpeditionMemberStatus::Online); SendUpdatesToZoneMembers(); // live sends full update when member added } @@ -1116,6 +1178,7 @@ void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t re { ExpeditionDatabase::DeletePendingLockouts(member_client->CharacterID()); member_client->SetExpeditionID(0); + member_client->SendDzCompassUpdate(); member_client->QueuePacket(CreateInfoPacket(true).get()); member_client->MessageString( Chat::Yellow, EXPEDITION_REMOVED, it->name.c_str(), m_expedition_name.c_str() @@ -1163,7 +1226,6 @@ void Expedition::SendUpdatesToZoneMembers(bool clear) { if (!m_members.empty()) { - //auto outapp_compass = CreateCompassPacket(); auto outapp_info = CreateInfoPacket(clear); auto outapp_members = CreateMemberListPacket(clear); @@ -1173,6 +1235,7 @@ void Expedition::SendUpdatesToZoneMembers(bool clear) if (member_client) { member_client->SetExpeditionID(clear ? 0 : GetID()); + member_client->SendDzCompassUpdate(); member_client->QueuePacket(outapp_info.get()); member_client->QueuePacket(outapp_members.get()); member_client->SendExpeditionLockoutTimers(); @@ -1222,8 +1285,8 @@ std::unique_ptr Expedition::CreateInvitePacket( strn0cpy(outbuf->expedition_name, m_expedition_name.c_str(), sizeof(outbuf->expedition_name)); strn0cpy(outbuf->swap_name, swap_remove_name.c_str(), sizeof(outbuf->swap_name)); outbuf->swapping = !swap_remove_name.empty(); - //outbuf->dz_zone_id = m_dynamiczone.GetZoneID(); - //outbuf->dz_instance_id = m_dynamiczone.GetInstanceID(); + outbuf->dz_zone_id = m_dynamiczone.GetZoneID(); + outbuf->dz_instance_id = m_dynamiczone.GetInstanceID(); return outapp; } @@ -1385,6 +1448,24 @@ void Expedition::SendWorldMemberStatus(uint32_t character_id, ExpeditionMemberSt worldserver.SendPacket(pack.get()); } +void Expedition::SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location) +{ + uint32_t pack_size = sizeof(ServerDzLocation_Struct); + auto pack = std::unique_ptr(new ServerPacket(server_opcode, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->owner_id = GetID(); + buf->dz_zone_id = m_dynamiczone.GetZoneID(); + buf->dz_instance_id = m_dynamiczone.GetInstanceID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->zone_id = location.zone_id; + buf->x = location.x; + buf->y = location.y; + buf->z = location.z; + buf->heading = location.heading; + worldserver.SendPacket(pack.get()); +} + void Expedition::SendWorldMemberSwapped( const std::string& remove_char_name, uint32_t remove_char_id, const std::string& add_char_name, uint32_t add_char_id) { @@ -1439,13 +1520,16 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) case ServerOP_ExpeditionDeleted: { auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (zone && expedition) { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) + if (!zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) { expedition->SendUpdatesToZoneMembers(true); } + + // remove even from sender zone + zone->expedition_cache.erase(buf->expedition_id); } break; } @@ -1538,14 +1622,14 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) { for (uint32_t i = 0; i < buf->count; ++i) { - auto entry = reinterpret_cast(&buf->entries[i]); - auto is_online = entry->character_online; + auto member = reinterpret_cast(&buf->entries[i]); + auto is_online = member->character_online; auto status = is_online ? ExpeditionMemberStatus::Online : ExpeditionMemberStatus::Offline; - if (is_online && expedition->GetInstanceID() == entry->character_instance_id) + if (is_online && expedition->GetDynamicZone().IsInstanceID(member->character_instance_id)) { status = ExpeditionMemberStatus::InDynamicZone; } - expedition->UpdateMemberStatus(entry->character_id, status); + expedition->UpdateMemberStatus(member->character_id, status); } } break; @@ -1583,5 +1667,87 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_ExpeditionDzCompass: + case ServerOP_ExpeditionDzSafeReturn: + case ServerOP_ExpeditionDzZoneIn: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + auto expedition = Expedition::FindCachedExpeditionByID(buf->owner_id); + if (expedition) + { + if (pack->opcode == ServerOP_ExpeditionDzCompass) + { + expedition->SetDzCompass(buf->zone_id, buf->x, buf->y, buf->z, false); + } + else if (pack->opcode == ServerOP_ExpeditionDzSafeReturn) + { + expedition->SetDzSafeReturn(buf->zone_id, buf->x, buf->y, buf->z, buf->heading, false); + } + else if (pack->opcode == ServerOP_ExpeditionDzZoneIn) + { + expedition->SetDzZoneInLocation(buf->x, buf->y, buf->z, buf->heading, false); + } + } + } + break; + } + } +} + +void Expedition::SetDzCompass(uint32_t zone_id, float x, float y, float z, bool update_db) +{ + DynamicZoneLocation location{ zone_id, x, y, z, 0.0f }; + m_dynamiczone.SetCompass(location, update_db); + + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.char_id); + if (member_client) + { + member_client->SendDzCompassUpdate(); + } + } + + if (update_db) + { + SendWorldDzLocationUpdate(ServerOP_ExpeditionDzCompass, location); + } +} + +void Expedition::SetDzCompass(const std::string& zone_name, float x, float y, float z, bool update_db) +{ + auto zone_id = ZoneID(zone_name.c_str()); + SetDzCompass(zone_id, x, y, z, update_db); +} + +void Expedition::SetDzSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db) +{ + DynamicZoneLocation location{ zone_id, x, y, z, heading }; + + m_dynamiczone.SetSafeReturn(location, update_db); + + if (update_db) + { + SendWorldDzLocationUpdate(ServerOP_ExpeditionDzSafeReturn, location); + } +} + +void Expedition::SetDzSafeReturn(const std::string& zone_name, float x, float y, float z, float heading, bool update_db) +{ + auto zone_id = ZoneID(zone_name.c_str()); + SetDzSafeReturn(zone_id, x, y, z, heading, update_db); +} + +void Expedition::SetDzZoneInLocation(float x, float y, float z, float heading, bool update_db) +{ + DynamicZoneLocation location{ 0, x, y, z, heading }; + + m_dynamiczone.SetZoneInLocation(location, update_db); + + if (update_db) + { + SendWorldDzLocationUpdate(ServerOP_ExpeditionDzZoneIn, location); } } diff --git a/zone/expedition.h b/zone/expedition.h index cebe46e7f..671a4e9b9 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -21,6 +21,7 @@ #ifndef EXPEDITION_H #define EXPEDITION_H +#include "dynamiczone.h" #include "expedition_lockout_timer.h" #include #include @@ -38,16 +39,6 @@ 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, @@ -73,11 +64,12 @@ class Expedition { public: Expedition() = delete; - Expedition(uint32_t id, std::string expedition_name, const ExpeditionMember& leader, + Expedition( + uint32_t id, const DynamicZone& dz, 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 Expedition* TryCreate(Client* requester, DynamicZone& dynamiczone, ExpeditionRequest& request); + static void CacheFromDatabase(uint32_t expedition_id); static bool CacheAllFromDatabase(); static void CacheExpeditions(MySQLRequestResult& results); @@ -89,10 +81,12 @@ public: static void HandleWorldMessage(ServerPacket* pack); uint32_t GetID() const { return m_id; } + uint16_t GetInstanceID() const { return m_dynamiczone.GetInstanceID(); } 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(m_members.size()); } + const DynamicZone& GetDynamicZone() const { return m_dynamiczone; } const std::string& GetName() const { return m_expedition_name; } const std::string& GetLeaderName() const { return m_leader.name; } const std::unordered_map& GetLockouts() const { return m_lockouts; } @@ -101,7 +95,7 @@ public: 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(); + void RemoveAllMembers(bool enable_removal_timers = true, bool update_dz_expire_time = true); 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); @@ -125,19 +119,18 @@ public: 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 + void SetDzCompass(uint32_t zone_id, float x, float y, float z, bool update_db = false); + void SetDzCompass(const std::string& zone_name, float x, float y, float z, bool update_db = false); + void SetDzSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db = false); + void SetDzSafeReturn(const std::string& zone_name, float x, float y, float z, float heading, bool update_db = false); + void SetDzZoneInLocation(float x, float y, float z, float heading, bool update_db = false); 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); + void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status, bool is_current_member = true); bool ChooseNewLeader(); bool ConfirmLeaderCommand(Client* requester); void LoadMembers(); @@ -152,6 +145,7 @@ private: 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& parameters = {}); void SendUpdatesToZoneMembers(bool clear = false); + void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); 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); @@ -174,11 +168,11 @@ private: std::unique_ptr 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; + DynamicZone m_dynamiczone { DynamicZoneType::Expedition }; ExpeditionMember m_leader; std::vector m_members; // current members std::unordered_set m_member_id_history; // track past members to allow invites for replay timer bypass diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index e8ef8848e..3888e1692 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -26,17 +26,17 @@ #include uint32_t ExpeditionDatabase::InsertExpedition( - const std::string& expedition_name, uint32_t leader_id, + uint32_t instance_id, 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) + (instance_id, expedition_name, leader_id, min_players, max_players, has_replay_timer) VALUES - ('{}', {}, {}, {}, {}); - ), expedition_name, leader_id, min_players, max_players, has_replay_lockout); + ({}, '{}', {}, {}, {}, {}); + ), instance_id, expedition_name, leader_id, min_players, max_players, has_replay_lockout); auto results = database.QueryDatabase(query); if (!results.Success()) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index e16a94e1e..38f758258 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -36,7 +36,7 @@ class MySQLRequestResult; namespace ExpeditionDatabase { uint32_t InsertExpedition( - const std::string& expedition_name, uint32_t leader_id, + uint32_t instance_id, 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(); diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 5cf049f60..4ec6a4846 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -38,10 +38,8 @@ struct ExpeditionRequestConflict }; ExpeditionRequest::ExpeditionRequest( - Client* requester, std::string expedition_name, uint32_t min_players, - uint32_t max_players, bool has_replay_timer + 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), @@ -49,8 +47,9 @@ ExpeditionRequest::ExpeditionRequest( { } -bool ExpeditionRequest::Validate() +bool ExpeditionRequest::Validate(Client* requester) { + m_requester = requester; if (!m_requester) { return false; @@ -353,7 +352,7 @@ bool ExpeditionRequest::IsPlayerCountValidated(uint32_t member_count) bool requirements_met = true; auto bypass_status = RuleI(Expedition, MinStatusToBypassPlayerCountRequirements); - auto gm_bypass = (m_requester->GetGM() && m_requester->Admin() >= bypass_status); + auto gm_bypass = (m_requester && m_requester->GetGM() && m_requester->Admin() >= bypass_status); if (!gm_bypass && (member_count < m_min_players || member_count > m_max_players)) { diff --git a/zone/expedition_request.h b/zone/expedition_request.h index 9c57bbb9e..5970e69e5 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -37,14 +37,17 @@ struct ExpeditionMember; class ExpeditionRequest { public: - ExpeditionRequest(Client* requester, std::string expedition_name, - uint32_t min_players, uint32_t max_players, bool has_replay_timer); + ExpeditionRequest(std::string expedition_name, uint32_t min_players, uint32_t max_players, bool has_replay_timer); - bool Validate(); + bool Validate(Client* requester); + const std::string& GetExpeditionName() const { return m_expedition_name; } Client* GetLeaderClient() const { return m_leader; } uint32_t GetLeaderID() const { return m_leader_id; } const std::string& GetLeaderName() const { return m_leader_name; } + uint32_t GetMinPlayers() const { return m_min_players; } + uint32_t GetMaxPlayers() const { return m_max_players; } + bool HasReplayTimer() const { return m_has_replay_timer; } std::vector TakeMembers() && { return std::move(m_members); } std::unordered_map TakeLockouts() && { return std::move(m_lockouts); } diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index e29caef70..7ad1b9bec 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1646,14 +1646,14 @@ int Lua_Client::GetClientMaxLevel() { return self->GetClientMaxLevel(); } -Lua_Expedition Lua_Client::CreateExpedition(std::string name, uint32 min_players, uint32 max_players) { +Lua_Expedition Lua_Client::CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players) { Lua_Safe_Call_Class(Lua_Expedition); - return self->CreateExpedition(name, min_players, max_players); + return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players); } -Lua_Expedition Lua_Client::CreateExpedition(std::string name, uint32 min_players, uint32 max_players, bool has_replay_timer) { +Lua_Expedition Lua_Client::CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool has_replay_timer) { Lua_Safe_Call_Class(Lua_Expedition); - return self->CreateExpedition(name, min_players, max_players, has_replay_timer); + return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players, has_replay_timer); } Lua_Expedition Lua_Client::GetExpedition() { @@ -1717,6 +1717,16 @@ bool Lua_Client::HasExpeditionLockout(std::string expedition_name, std::string e return self->HasExpeditionLockout(expedition_name, event_name); } +void Lua_Client::MovePCDynamicZone(uint32 zone_id) { + Lua_Safe_Call_Void(); + return self->MovePCDynamicZone(zone_id); +} + +void Lua_Client::MovePCDynamicZone(std::string zone_name) { + Lua_Safe_Call_Void(); + return self->MovePCDynamicZone(zone_name); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -2024,14 +2034,16 @@ luabind::scope lua_register_client() { .def("DisableAreaRegens", &Lua_Client::DisableAreaRegens) .def("SetClientMaxLevel", (void(Lua_Client::*)(int))&Lua_Client::SetClientMaxLevel) .def("GetClientMaxLevel", (int(Lua_Client::*)(void))&Lua_Client::GetClientMaxLevel) - .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32))&Lua_Client::CreateExpedition) - .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, bool))&Lua_Client::CreateExpedition) + .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32))&Lua_Client::CreateExpedition) + .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32, bool))&Lua_Client::CreateExpedition) .def("GetExpedition", (Lua_Expedition(Lua_Client::*)(void))&Lua_Client::GetExpedition) .def("GetExpeditionLockouts", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetExpeditionLockouts) .def("GetExpeditionLockouts", (luabind::object(Lua_Client::*)(lua_State* L, std::string))&Lua_Client::GetExpeditionLockouts) .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32))&Lua_Client::AddExpeditionLockout) .def("RemoveExpeditionLockout", (void(Lua_Client::*)(std::string, std::string))&Lua_Client::RemoveExpeditionLockout) - .def("HasExpeditionLockout", (bool(Lua_Client::*)(std::string, std::string))&Lua_Client::HasExpeditionLockout); + .def("HasExpeditionLockout", (bool(Lua_Client::*)(std::string, std::string))&Lua_Client::HasExpeditionLockout) + .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string))&Lua_Client::MovePCDynamicZone); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index 8f60aaacb..fec1f60fc 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -339,14 +339,16 @@ public: void SetClientMaxLevel(int value); int GetClientMaxLevel(); - Lua_Expedition CreateExpedition(std::string name, uint32 min_players, uint32 max_players); - Lua_Expedition CreateExpedition(std::string name, uint32 min_players, uint32 max_players, bool has_replay_timer); + Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players); + Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool has_replay_timer); Lua_Expedition GetExpedition(); luabind::object GetExpeditionLockouts(lua_State* L); luabind::object GetExpeditionLockouts(lua_State* L, std::string expedition_name); void AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds); void RemoveExpeditionLockout(std::string expedition_name, std::string event_name); bool HasExpeditionLockout(std::string expedition_name, std::string event_name); + void MovePCDynamicZone(uint32 zone_id); + void MovePCDynamicZone(std::string zone_name); }; #endif diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 5d4b05c04..4a9054d27 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -41,6 +41,11 @@ uint32_t Lua_Expedition::GetID() { return self->GetID(); } +int Lua_Expedition::GetInstanceID() { + Lua_Safe_Call_Int(); + return self->GetDynamicZone().GetInstanceID(); +} + std::string Lua_Expedition::GetLeaderName() { Lua_Safe_Call_String(); return self->GetLeaderName(); @@ -85,9 +90,14 @@ std::string Lua_Expedition::GetName() { return self->GetName(); } -int Lua_Expedition::GetType() { +int Lua_Expedition::GetSecondsRemaining() { Lua_Safe_Call_Int(); - return static_cast(self->GetType()); + return self->GetDynamicZone().GetSecondsRemaining(); +} + +int Lua_Expedition::GetZoneID() { + Lua_Safe_Call_Int(); + return self->GetDynamicZone().GetZoneID(); } bool Lua_Expedition::HasLockout(std::string event_name) { @@ -105,6 +115,31 @@ void Lua_Expedition::RemoveLockout(std::string event_name) { self->RemoveLockout(event_name); } +void Lua_Expedition::SetCompass(uint32_t zone_id, float x, float y, float z) { + Lua_Safe_Call_Void(); + return self->SetDzCompass(zone_id, x, y, z, true); +} + +void Lua_Expedition::SetCompass(std::string zone_name, float x, float y, float z) { + Lua_Safe_Call_Void(); + return self->SetDzCompass(zone_name, x, y, z, true); +} + +void Lua_Expedition::SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading) { + Lua_Safe_Call_Void(); + return self->SetDzSafeReturn(zone_id, x, y, z, heading, true); +} + +void Lua_Expedition::SetSafeReturn(std::string zone_name, float x, float y, float z, float heading) { + Lua_Safe_Call_Void(); + return self->SetDzSafeReturn(zone_name, x, y, z, heading, true); +} + +void Lua_Expedition::SetZoneInLocation(float x, float y, float z, float heading) { + Lua_Safe_Call_Void(); + return self->SetDzZoneInLocation(x, y, z, heading, true); +} + luabind::scope lua_register_expedition() { return luabind::class_("Expedition") .def(luabind::constructor<>()) @@ -113,15 +148,22 @@ luabind::scope lua_register_expedition() { .def("AddLockout", (void(Lua_Expedition::*)(std::string, uint32_t))&Lua_Expedition::AddLockout) .def("AddReplayLockout", (void(Lua_Expedition::*)(uint32_t))&Lua_Expedition::AddReplayLockout) .def("GetID", (uint32_t(Lua_Expedition::*)(void))&Lua_Expedition::GetID) + .def("GetInstanceID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetInstanceID) .def("GetLeaderName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetLeaderName) .def("GetLockouts", &Lua_Expedition::GetLockouts) .def("GetMemberCount", (uint32_t(Lua_Expedition::*)(void))&Lua_Expedition::GetMemberCount) .def("GetMembers", &Lua_Expedition::GetMembers) .def("GetName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetName) - .def("GetType", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetType) + .def("GetSecondsRemaining", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetSecondsRemaining) + .def("GetZoneID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetZoneID) .def("HasLockout", (bool(Lua_Expedition::*)(std::string))&Lua_Expedition::HasLockout) .def("HasReplayLockout", (bool(Lua_Expedition::*)())&Lua_Expedition::HasReplayLockout) - .def("RemoveLockout", (void(Lua_Expedition::*)(std::string))&Lua_Expedition::RemoveLockout); + .def("RemoveLockout", (void(Lua_Expedition::*)(std::string))&Lua_Expedition::RemoveLockout) + .def("SetCompass", (void(Lua_Expedition::*)(uint32_t, float, float, float))&Lua_Expedition::SetCompass) + .def("SetCompass", (void(Lua_Expedition::*)(std::string, float, float, float))&Lua_Expedition::SetCompass) + .def("SetSafeReturn", (void(Lua_Expedition::*)(uint32_t, float, float, float, float))&Lua_Expedition::SetSafeReturn) + .def("SetSafeReturn", (void(Lua_Expedition::*)(std::string, float, float, float, float))&Lua_Expedition::SetSafeReturn) + .def("SetZoneInLocation", (void(Lua_Expedition::*)(float, float, float, float))&Lua_Expedition::SetZoneInLocation); } luabind::scope lua_register_expedition_member_status() { @@ -136,17 +178,4 @@ luabind::scope lua_register_expedition_member_status() { ]; } -luabind::scope lua_register_dynamiczone_types() { - return luabind::class_("DynamicZoneType") - .enum_("constants") - [ - luabind::value("None", static_cast(DynamicZoneType::None)), - luabind::value("Expedition", static_cast(DynamicZoneType::Expedition)), - luabind::value("Tutorial", static_cast(DynamicZoneType::Tutorial)), - luabind::value("Task", static_cast(DynamicZoneType::Task)), - luabind::value("Mission", static_cast(DynamicZoneType::Mission)), - luabind::value("Quest", static_cast(DynamicZoneType::Quest)) - ]; -} - #endif // LUA_EQEMU diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 155c5bc09..83ee0b046 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -38,7 +38,6 @@ namespace luabind { using adl::object; } -luabind::scope lua_register_dynamiczone_types(); luabind::scope lua_register_expedition(); luabind::scope lua_register_expedition_member_status(); @@ -57,15 +56,22 @@ public: void AddLockout(std::string event_name, uint32_t seconds); void AddReplayLockout(uint32_t seconds); uint32_t GetID(); + int GetInstanceID(); std::string GetLeaderName(); uint32_t GetMemberCount(); luabind::object GetMembers(lua_State* L); std::string GetName(); - int GetType(); + int GetSecondsRemaining(); + int GetZoneID(); luabind::object GetLockouts(lua_State* L); bool HasLockout(std::string event_name); bool HasReplayLockout(); void RemoveLockout(std::string event_name); + void SetCompass(uint32 zone_id, float x, float y, float z); + void SetCompass(std::string zone_name, float x, float y, float z); + void SetSafeReturn(uint32 zone_id, float x, float y, float z, float heading); + void SetSafeReturn(std::string zone_name, float x, float y, float z, float heading); + void SetZoneInLocation(float x, float y, float z, float heading); }; #endif // LUA_EQEMU diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 8ac469359..2b5ed0ad6 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -1110,7 +1110,6 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_ruleb(), lua_register_journal_speakmode(), lua_register_journal_mode(), - lua_register_dynamiczone_types(), lua_register_expedition(), lua_register_expedition_member_status() ]; diff --git a/zone/string_ids.h b/zone/string_ids.h index 4a2263cda..5f1b89a02 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -299,6 +299,7 @@ #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 DZ_PREVENT_ENTERING 3510 //A strange magical presence prevents you from entering. It's too dangerous to enter at the moment. #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. @@ -314,6 +315,7 @@ #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 DYNAMICZONE_WAY_IS_BLOCKED 3528 //The way is blocked to you. Perhaps you would be able to enter if there was a reason to come here. #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 diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 2088cef05..b6b3461ca 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2909,10 +2909,18 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionGetOnlineMembers: case ServerOP_ExpeditionDzAddPlayer: case ServerOP_ExpeditionDzMakeLeader: + case ServerOP_ExpeditionDzCompass: + case ServerOP_ExpeditionDzSafeReturn: + case ServerOP_ExpeditionDzZoneIn: { Expedition::HandleWorldMessage(pack); break; } + case ServerOP_DzCharacterChange: + { + DynamicZone::HandleWorldMessage(pack); + break; + } default: { std::cout << " Unknown ZSopcode:" << (int)pack->opcode; std::cout << " size:" << pack->size << std::endl; diff --git a/zone/zone.cpp b/zone/zone.cpp index a8a2e039e..a415e45f0 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1491,7 +1491,14 @@ bool Zone::Process() { { if(Instance_Timer->Check()) { - entity_list.GateAllClients(); + // if this is a dynamic zone instance notify system associated with it + Expedition* expedition = Expedition::FindExpeditionByInstanceID(GetInstanceID()); + if (expedition) + { + expedition->RemoveAllMembers(false, false); // entity list will teleport clients out immediately + } + // todo: move corpses to non-instanced version of dz at same coords (if no graveyard) + entity_list.GateAllClientsToSafeReturn(); database.DeleteInstance(GetInstanceID()); Instance_Shutdown_Timer = new Timer(20000); //20 seconds } diff --git a/zone/zone.h b/zone/zone.h index f8d0fbbbb..32a98722f 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -406,4 +406,3 @@ private: }; #endif -