From 32f4722c0fd19e00adf17d75a547c25af893e76a Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 20 Sep 2020 13:55:45 -0400 Subject: [PATCH 001/196] Only UF and earlier have the CORPSE_ITEM_LOST string Let's not confuse players with missing messages I guess --- zone/exp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/exp.cpp b/zone/exp.cpp index 8f722b1f3..b0eb13790 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -718,7 +718,8 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { else Message(Chat::Yellow, "Welcome to level %i!", check_level); - if (check_level == RuleI(Character, DeathItemLossLevel)) + if (check_level == RuleI(Character, DeathItemLossLevel) && + m_ClientVersionBit & EQ::versions::maskUFAndEarlier) MessageString(Chat::Yellow, CORPSE_ITEM_LOST); if (check_level == RuleI(Character, DeathExpLossLevel)) From d507222d21ece298e3394e205dbd12fee4e9e46b Mon Sep 17 00:00:00 2001 From: Noudess Date: Mon, 21 Sep 2020 10:36:00 -0400 Subject: [PATCH 002/196] Allow amphibious creatures to swim to next node of pathing. --- zone/mob_movement_manager.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/zone/mob_movement_manager.cpp b/zone/mob_movement_manager.cpp index 904bc5246..0ebed1188 100644 --- a/zone/mob_movement_manager.cpp +++ b/zone/mob_movement_manager.cpp @@ -1088,6 +1088,17 @@ void MobMovementManager::UpdatePath(Mob *who, float x, float y, float z, MobMove PushFlyTo(ent.second, x, y, z, mob_movement_mode); PushStopMoving(ent.second); } + // Below for npcs that can traverse land or water so they don't sink + else if (who->GetFlyMode() == GravityBehavior::Water && + zone->watermap->InLiquid(who->GetPosition()) && + zone->watermap->InLiquid(glm::vec3(x, y, z)) && + zone->zonemap->CheckLoS(who->GetPosition(), glm::vec3(x, y, z))) { + auto iter = _impl->Entries.find(who); + auto &ent = (*iter); + + PushSwimTo(ent.second, x, y, z, mob_movement_mode); + PushStopMoving(ent.second); + } else { UpdatePathGround(who, x, y, z, mob_movement_mode); } From 1d4bea21c18d0f4ad8b5c5de6cb4207bc62115ad Mon Sep 17 00:00:00 2001 From: Snail Date: Sun, 4 Oct 2020 18:17:45 -0400 Subject: [PATCH 003/196] compile fix on linux (glm is using c++14 code), update CXX_STANDARD --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24816a323..0efa7f511 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE) ENDIF(NOT CMAKE_BUILD_TYPE) -SET(CMAKE_CXX_STANDARD 11) +SET(CMAKE_CXX_STANDARD 14) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_CXX_EXTENSIONS OFF) From f03669b8f471c9a79255deaf5e595db34c4da82b Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 10 Oct 2020 21:13:13 -0400 Subject: [PATCH 004/196] Fix for command '#editmassrespawn' --- zone/command.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 9b42adacd..3a077b562 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6879,7 +6879,7 @@ void command_editmassrespawn(Client* c, const Seperator* sep) int results_count = 0; - auto results = database.QueryDatabase(query); + auto results = content_db.QueryDatabase(query); if (results.Success() && results.RowCount()) { results_count = results.RowCount(); @@ -6906,7 +6906,7 @@ void command_editmassrespawn(Client* c, const Seperator* sep) if (change_apply) { - results = database.QueryDatabase( + results = content_db.QueryDatabase( fmt::format( SQL( UPDATE spawn2 From e9d312fa8664b736d2052bac1e76797485aca21d Mon Sep 17 00:00:00 2001 From: Ali Al-Khalifa Date: Sun, 25 Oct 2020 03:49:43 +0300 Subject: [PATCH 005/196] Spell Fizzle for < LDON expansions via lua_mod (#1118) * [expansions] Create a Lua mod hook into Client::CheckFizzle() * Added expansions_combat.lua mod stub * Spell Fizzle as per TAKP formula --- utils/mods/expansions_combat.lua | 173 +++++++++++++++++++++++++++++++ zone/lua_client.cpp | 6 ++ zone/lua_client.h | 1 + zone/lua_mod.cpp | 55 ++++++++++ zone/lua_mod.h | 2 + zone/lua_parser.cpp | 9 ++ zone/lua_parser.h | 2 + zone/spells.cpp | 11 ++ 8 files changed, 259 insertions(+) create mode 100644 utils/mods/expansions_combat.lua diff --git a/utils/mods/expansions_combat.lua b/utils/mods/expansions_combat.lua new file mode 100644 index 000000000..8f2ded4b9 --- /dev/null +++ b/utils/mods/expansions_combat.lua @@ -0,0 +1,173 @@ +-- Source Function: Client::CheckFizzle() +function CheckFizzle(e) + if eq.is_lost_dungeons_of_norrath_enabled() then + e.IgnoreDefault = false; + return e; + + else -- Classic Fizzle -- Based on TAKP's fizzle formula + e.IgnoreDefault = true; + local client = e.self; + local spell = e.spell; + if client:GetGM() == true then -- GMs never fizzle + e.ReturnValue = true; + return e; + end + + local caster_class = client:GetClass(); + local spell_level = spell:Classes(caster_class - 1); + local aa_bonuses = client:GetAABonuses(); + local item_bonuses = client:GetItemBonuses(); + local spell_bonuses = client:GetSpellBonuses(); + + local no_fizzle_level = math.max(unpack({ + aa_bonuses:MasteryofPast(), + item_bonuses:MasteryofPast(), + spell_bonuses:MasteryofPast() + })); + local no_fizzle_level = 0; + + if spell_level < no_fizzle_level then + e.ReturnValue = true; + return e; + end + + local fizzle_adjustment = spell:BaseDiff(); + local spell_casting_skill_effects = aa_bonuses:adjusted_casting_skill() + + item_bonuses:adjusted_casting_skill() + + spell_bonuses:adjusted_casting_skill(); + + local capped_chance = 95; + local random_penalty = 0; + local effective_spell_casting_skill = 0; + local spell_level_adjustment = 0; + local prime_stat_bonus = 0; + + if fizzle_adjustment ~= 0 or spell_casting_skill_effects < 0 then + -- If Superior Healing not cast by cleric + if spell:ID() == 9 and caster_class ~= Class.CLERIC then + fizzle_adjustment = 0 + end + + if spell_level > 55 then + fizzle_adjustment = 0; + end + + if (caster_class == Class.PALADIN or + caster_class == Class.RANGER or + caster_class == Class.SHADOWKNIGHT) and + spell_level > 40 then + fizzle_adjustment = 0; + end + + if caster_class == Class.BARD and fizzle_adjustment > 15 then + fizzle_adjustment = 15; + end + + local prime_stat = 0; + if caster_class == Class.BARD then + prime_stat = (client:GetCHA() + client:GetDEX()) / 2; + elseif caster_class == Class.CLERIC or + caster_class == Class.PALADIN or + caster_class == Class.RANGER or + caster_class == Class.DRUID or + caster_class == Class.SHAMAN or + caster_class == Class.BEASTLORD then + prime_stat = client:GetWIS(); + elseif caster_class == Class.SHADOWKNIGHT or + caster_class == Class.NECROMANCER or + caster_class == Class.WIZARD or + caster_class == Class.MAGICIAN or + caster_class == Class.ENCHANTER then + prime_stat = client:GetINT(); + end + + prime_stat_bonus = math.floor(prime_stat / 10); + + local effective_spell_level = spell_level - 1; + if effective_spell_level > 50 then + effective_spell_level = 50 + end; + + local spell_casting_skill = client:GetSkill(spell:Skill()); + + effective_spell_casting_skill = spell_casting_skill + spell_casting_skill_effects; + if effective_spell_casting_skill < 0 then + effective_spell_casting_skill = 0; + end + + random_penalty = Random.Int(0, 10); + + spell_level_adjustment = 5 * (18 - effective_spell_level); + local chance = 0 + + effective_spell_casting_skill + + (spell_level_adjustment + prime_stat_bonus) - + random_penalty - + fizzle_adjustment; + + capped_chance = chance; + if caster_class == Class.BARD then + if capped_chance < 1 then + capped_chance = 1; + elseif capped_chance > 95 then + capped_chance = 95; + end + elseif caster_class <= 16 then + if capped_chance < 5 then + capped_chance = 5; + elseif capped_chance > 95 then + capped_chance = 95; + end + else -- Unknown Class + capped_chance = 0; + end + end + + local specialize_skill = client:GetSpecializeSkillValue(spell:ID()); + local specialize_adjustment = 0; + local spell_casting_mastery_adjustment = 0; + + if specialize_skill > 0 then + specialize_adjustment = math.floor(specialize_skill / 10) + 1; + + local spell_casting_mastery_level = client:GetAA(83); -- aaSpellCastingMastery + if spell_casting_mastery_level == 1 then + spell_casting_mastery_adjustment = 2; + elseif spell_casting_mastery_level == 2 then + spell_casting_mastery_adjustment = 5; + elseif spell_casting_mastery_level == 3 then + spell_casting_mastery_adjustment = 10; + end + + capped_chance = capped_chance + specialize_adjustment + spell_casting_mastery_adjustment; + if capped_chance > 98 then + capped_chance = 98; + end + end + + local roll_100 = Random.Int(1, 100); + + if client:IsSilenced() then + roll_100 = capped_chance + 1; + end + + --client:Message(15, "CheckFizzle(LUA:expansions_combat): " .. + -- "spell_id = " .. spell:ID() .. + -- ", roll_100 = " .. roll_100 .. + -- ", capped_chance (" .. capped_chance .. ") = " .. + -- "effective_spell_casting_skill (" .. effective_spell_casting_skill .. ") + " .. + -- "spell_level_adjustment (" .. spell_level_adjustment .. ") + " .. + -- "prime_stat_bonus (" .. prime_stat_bonus .. ") - " .. + -- "random_penalty (" .. random_penalty .. ") - " .. + -- "fizzle_adjustment (" .. fizzle_adjustment .. ") + " .. + -- "specialize_adjustment (" .. specialize_adjustment .. ") + " .. + -- "spell_casting_mastery_adjustment (" .. spell_casting_mastery_adjustment .. ")"); + + if capped_chance >= roll_100 then + e.ReturnValue = true; + return e; + end + + e.ReturnValue = false; + return e; + end +end diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index a6145000d..e32957823 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -485,6 +485,11 @@ int Lua_Client::GetRawSkill(int skill_id) { return self->GetRawSkill(static_cast(skill_id)); } +int Lua_Client::GetSkill(int skill_id) { + Lua_Safe_Call_Int(); + return self->GetSkill(static_cast(skill_id)); +} + bool Lua_Client::HasSkill(int skill_id) { Lua_Safe_Call_Bool(); return self->HasSkill(static_cast(skill_id)); @@ -1730,6 +1735,7 @@ luabind::scope lua_register_client() { .def("IncreaseLanguageSkill", (void(Lua_Client::*)(int))&Lua_Client::IncreaseLanguageSkill) .def("IncreaseLanguageSkill", (void(Lua_Client::*)(int,int))&Lua_Client::IncreaseLanguageSkill) .def("GetRawSkill", (int(Lua_Client::*)(int))&Lua_Client::GetRawSkill) + .def("GetSkill", (int(Lua_Client::*)(int))&Lua_Client::GetSkill) .def("HasSkill", (bool(Lua_Client::*)(int))&Lua_Client::HasSkill) .def("CanHaveSkill", (bool(Lua_Client::*)(int))&Lua_Client::CanHaveSkill) .def("SetSkill", (void(Lua_Client::*)(int,int))&Lua_Client::SetSkill) diff --git a/zone/lua_client.h b/zone/lua_client.h index 5900930f0..8d300c048 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -122,6 +122,7 @@ public: void IncreaseLanguageSkill(int skill_id); void IncreaseLanguageSkill(int skill_id, int value); int GetRawSkill(int skill_id); + int GetSkill(int skill_id); bool HasSkill(int skill_id); bool CanHaveSkill(int skill_id); void SetSkill(int skill_id, int value); diff --git a/zone/lua_mod.cpp b/zone/lua_mod.cpp index 382f8adba..955187a02 100644 --- a/zone/lua_mod.cpp +++ b/zone/lua_mod.cpp @@ -632,4 +632,59 @@ void LuaMod::GetExperienceForKill(Client *self, Mob *against, uint32 &returnValu } } +void LuaMod::CheckFizzle(Client *self, uint16 &spell_id, SPDat_Spell_Struct spell_struct, bool &returnValue, bool &ignoreDefault) { + int start = lua_gettop(L); + + try { + if (!m_has_check_fizzle) { + return; + } + + lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); + lua_getfield(L, -1, "CheckFizzle"); + + Lua_Client l_self(self); + luabind::adl::object e = luabind::newtable(L); + e["self"] = l_self; + e["spell_id"] = spell_id; + + e.push(L); + + Lua_Spell l_spell(&spell_struct); + auto l_spell_o = luabind::adl::object(L, l_spell); + + l_spell_o.push(L); + lua_setfield(L, -2, "spell"); + + if (lua_pcall(L, 1, 1, 0)) { + std::string error = lua_tostring(L, -1); + parser_->AddError(error); + lua_pop(L, 1); + return; + } + + if (lua_type(L, -1) == LUA_TTABLE) { + luabind::adl::object ret(luabind::from_stack(L, -1)); + auto IgnoreDefaultObj = ret["IgnoreDefault"]; + if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { + ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); + } + + auto returnValueObj = ret["ReturnValue"]; + if (luabind::type(returnValueObj) == LUA_TBOOLEAN) { + returnValue = returnValue || luabind::object_cast(returnValueObj); + } + } + } + catch (std::exception& ex) { + parser_->AddError(ex.what()); + } + + int end = lua_gettop(L); + int n = end - start; + if (n > 0) { + lua_pop(L, n); + } +} + #endif diff --git a/zone/lua_mod.h b/zone/lua_mod.h index defc5edab..d21062e6e 100644 --- a/zone/lua_mod.h +++ b/zone/lua_mod.h @@ -26,6 +26,7 @@ public: void GetRequiredAAExperience(Client *self, uint32 &returnValue, bool &ignoreDefault); void GetEXPForLevel(Client *self, uint16 level, uint32 &returnValue, bool &ignoreDefault); void GetExperienceForKill(Client *self, Mob *against, uint32 &returnValue, bool &ignoreDefault); + void CheckFizzle(Client *self, uint16 &spell_id, SPDat_Spell_Struct spell_struct, bool &returnValue, bool &ignoreDefault); private: LuaParser *parser_; lua_State *L; @@ -40,4 +41,5 @@ private: bool m_has_get_required_aa_experience; bool m_has_get_exp_for_level; bool m_has_get_experience_for_kill; + bool m_has_check_fizzle; }; diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index ebe88f32a..1e69a7076 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -1377,4 +1377,13 @@ uint32 LuaParser::GetExperienceForKill(Client *self, Mob *against, bool &ignoreD return retval; } +bool LuaParser::CheckFizzle(Client *self, uint16 &spell_id, SPDat_Spell_Struct spell_struct, bool &ignoreDefault) +{ + bool retValue = false; + for (auto &mod : mods_) { + mod.CheckFizzle(self, spell_id, spell_struct, retValue, ignoreDefault); + } + return retValue; +} + #endif diff --git a/zone/lua_parser.h b/zone/lua_parser.h index 8265a191f..929f3d300 100644 --- a/zone/lua_parser.h +++ b/zone/lua_parser.h @@ -99,6 +99,8 @@ public: uint32 GetRequiredAAExperience(Client *self, bool &ignoreDefault); uint32 GetEXPForLevel(Client *self, uint16 level, bool &ignoreDefault); uint32 GetExperienceForKill(Client *self, Mob *against, bool &ignoreDefault); + bool CheckFizzle(Client *self, uint16 &spell_id, SPDat_Spell_Struct spell_struct, bool &ignoreDefault); + private: LuaParser(); diff --git a/zone/spells.cpp b/zone/spells.cpp index ea925ecda..74cd0db8c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -82,6 +82,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include "string_ids.h" #include "worldserver.h" #include "fastmath.h" +#include "lua_parser.h" #include #include @@ -734,6 +735,16 @@ bool Mob::CheckFizzle(uint16 spell_id) bool Client::CheckFizzle(uint16 spell_id) { +#ifdef LUA_EQEMU + bool ignoreDefault = false; + bool fizzle = LuaParser::Instance()->CheckFizzle(this, spell_id, spells[spell_id], ignoreDefault); + + if (!fizzle) { + return false; + } else if (ignoreDefault) { + return true; + } +#endif // GMs don't fizzle if (GetGM()) return(true); From 62efae2e006276c8f8d17bf0b0614278141f9ab3 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sun, 25 Oct 2020 21:48:29 -0500 Subject: [PATCH 006/196] SendIllusion Update Internal Values (#1130) * Fix scenarios where quest calls to SendIllusion also update internal values so that new clients that zone in see the correct appearance * Typo [skip ci] --- zone/entity.cpp | 4 +- zone/mob.cpp | 147 ++++++++++++++++++++++++++---------------------- 2 files changed, 83 insertions(+), 68 deletions(-) diff --git a/zone/entity.cpp b/zone/entity.cpp index e02495e86..b6d897ccb 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1322,8 +1322,8 @@ void EntityList::SendZoneSpawnsBulk(Client *client) const glm::vec4 &client_position = client->GetPosition(); const float distance_max = (600.0 * 600.0); - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - spawn = it->second; + for (auto & it : mob_list) { + spawn = it.second; if (spawn && spawn->GetID() > 0 && spawn->Spawned()) { if (!spawn->ShouldISpawnFor(client)) { continue; diff --git a/zone/mob.cpp b/zone/mob.cpp index 63eb351cb..175f9a94f 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1206,15 +1206,22 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName)); //for (i = 0; i < _MaterialCount; i++) - for (i = 0; i < 9; i++) - { + for (i = 0; i < 9; i++) { // Only Player Races Wear Armor - if (Mob::IsPlayerRace(race) || i > 6) - { - ns->spawn.equipment.Slot[i].Material = GetEquipmentMaterial(i); - ns->spawn.equipment.Slot[i].EliteModel = IsEliteMaterialItem(i); + if (Mob::IsPlayerRace(race) || i > 6) { + ns->spawn.equipment.Slot[i].Material = GetEquipmentMaterial(i); + ns->spawn.equipment.Slot[i].EliteModel = IsEliteMaterialItem(i); ns->spawn.equipment.Slot[i].HerosForgeModel = GetHerosForgeModel(i); - ns->spawn.equipment_tint.Slot[i].Color = GetEquipmentColor(i); + ns->spawn.equipment_tint.Slot[i].Color = GetEquipmentColor(i); + } + } + + if (texture > 0) { + for (i = 0; i < 9; i++) { + if (i == EQ::textures::weaponPrimary || i == EQ::textures::weaponSecondary || texture == 255) { + continue; + } + ns->spawn.equipment.Slot[i].Material = texture; } } @@ -1757,78 +1764,69 @@ void Mob::SendIllusionPacket( float in_size ) { - uint8 new_texture = in_texture; - uint8 new_helmtexture = in_helmtexture; - uint8 new_haircolor; - uint8 new_beardcolor; - uint8 new_eyecolor1; - uint8 new_eyecolor2; - uint8 new_hairstyle; - uint8 new_luclinface; - uint8 new_beard; - uint8 new_aa_title; + uint8 new_texture = in_texture; + uint8 new_helmtexture = in_helmtexture; + uint8 new_haircolor; + uint8 new_beardcolor; + uint8 new_eyecolor1; + uint8 new_eyecolor2; + uint8 new_hairstyle; + uint8 new_luclinface; + uint8 new_beard; + uint8 new_aa_title; uint32 new_drakkin_heritage; uint32 new_drakkin_tattoo; uint32 new_drakkin_details; race = in_race; - if (race == 0) - { + if (race == 0) { race = (use_model) ? use_model : GetBaseRace(); - } + } - if (in_gender != 0xFF) - { + if (in_gender != 0xFF) { gender = in_gender; - } - else - { + } + else { gender = (in_race) ? GetDefaultGender(race, gender) : GetBaseGender(); - } + } - if (in_texture == 0xFF && !IsPlayerRace(in_race)) - { + if (in_texture == 0xFF && !IsPlayerRace(in_race)) { new_texture = GetTexture(); - } + } - if (in_helmtexture == 0xFF && !IsPlayerRace(in_race)) - { + if (in_helmtexture == 0xFF && !IsPlayerRace(in_race)) { new_helmtexture = GetHelmTexture(); - } + } - new_haircolor = (in_haircolor == 0xFF) ? GetHairColor() : in_haircolor; - new_beardcolor = (in_beardcolor == 0xFF) ? GetBeardColor() : in_beardcolor; - new_eyecolor1 = (in_eyecolor1 == 0xFF) ? GetEyeColor1() : in_eyecolor1; - new_eyecolor2 = (in_eyecolor2 == 0xFF) ? GetEyeColor2() : in_eyecolor2; - new_hairstyle = (in_hairstyle == 0xFF) ? GetHairStyle() : in_hairstyle; - new_luclinface = (in_luclinface == 0xFF) ? GetLuclinFace() : in_luclinface; - new_beard = (in_beard == 0xFF) ? GetBeard() : in_beard; - new_drakkin_heritage = - (in_drakkin_heritage == 0xFFFFFFFF) ? GetDrakkinHeritage() : in_drakkin_heritage; - new_drakkin_tattoo = - (in_drakkin_tattoo == 0xFFFFFFFF) ? GetDrakkinTattoo() : in_drakkin_tattoo; - new_drakkin_details = - (in_drakkin_details == 0xFFFFFFFF) ? GetDrakkinDetails() : in_drakkin_details; - new_aa_title = in_aa_title; - size = (in_size <= 0.0f) ? GetSize() : in_size; + new_haircolor = (in_haircolor == 0xFF) ? GetHairColor() : in_haircolor; + new_beardcolor = (in_beardcolor == 0xFF) ? GetBeardColor() : in_beardcolor; + new_eyecolor1 = (in_eyecolor1 == 0xFF) ? GetEyeColor1() : in_eyecolor1; + new_eyecolor2 = (in_eyecolor2 == 0xFF) ? GetEyeColor2() : in_eyecolor2; + new_hairstyle = (in_hairstyle == 0xFF) ? GetHairStyle() : in_hairstyle; + new_luclinface = (in_luclinface == 0xFF) ? GetLuclinFace() : in_luclinface; + new_beard = (in_beard == 0xFF) ? GetBeard() : in_beard; + new_drakkin_heritage = (in_drakkin_heritage == 0xFFFFFFFF) ? GetDrakkinHeritage() : in_drakkin_heritage; + new_drakkin_tattoo = (in_drakkin_tattoo == 0xFFFFFFFF) ? GetDrakkinTattoo() : in_drakkin_tattoo; + new_drakkin_details = (in_drakkin_details == 0xFFFFFFFF) ? GetDrakkinDetails() : in_drakkin_details; + new_aa_title = in_aa_title; // Reset features to Base from the Player Profile if (IsClient() && in_race == 0) { - race = CastToClient()->GetBaseRace(); - gender = CastToClient()->GetBaseGender(); - new_texture = texture = 0xFF; - new_helmtexture = helmtexture = 0xFF; - new_haircolor = haircolor = CastToClient()->GetBaseHairColor(); - new_beardcolor = beardcolor = CastToClient()->GetBaseBeardColor(); - new_eyecolor1 = eyecolor1 = CastToClient()->GetBaseEyeColor(); - new_eyecolor2 = eyecolor2 = CastToClient()->GetBaseEyeColor(); - new_hairstyle = hairstyle = CastToClient()->GetBaseHairStyle(); - new_luclinface = luclinface = CastToClient()->GetBaseFace(); - new_beard = beard = CastToClient()->GetBaseBeard(); - new_aa_title = aa_title = 0xFF; - new_drakkin_heritage = drakkin_heritage = CastToClient()->GetBaseHeritage(); - new_drakkin_tattoo = drakkin_tattoo = CastToClient()->GetBaseTattoo(); - new_drakkin_details = drakkin_details = CastToClient()->GetBaseDetails(); + race = CastToClient()->GetBaseRace(); + gender = CastToClient()->GetBaseGender(); + new_texture = texture = 0xFF; + new_helmtexture = helmtexture = 0xFF; + new_haircolor = haircolor = CastToClient()->GetBaseHairColor(); + new_beardcolor = beardcolor = CastToClient()->GetBaseBeardColor(); + new_eyecolor1 = eyecolor1 = CastToClient()->GetBaseEyeColor(); + new_eyecolor2 = eyecolor2 = CastToClient()->GetBaseEyeColor(); + new_hairstyle = hairstyle = CastToClient()->GetBaseHairStyle(); + new_luclinface = luclinface = CastToClient()->GetBaseFace(); + new_beard = beard = CastToClient()->GetBaseBeard(); + new_aa_title = aa_title = 0xFF; + new_drakkin_heritage = drakkin_heritage = CastToClient()->GetBaseHeritage(); + new_drakkin_tattoo = drakkin_tattoo = CastToClient()->GetBaseTattoo(); + new_drakkin_details = drakkin_details = CastToClient()->GetBaseDetails(); switch (race) { case OGRE: size = 9; @@ -1859,6 +1857,21 @@ void Mob::SendIllusionPacket( } } + // update internal values for mob + size = (in_size <= 0.0f) ? GetSize() : in_size; + texture = new_texture; + helmtexture = new_helmtexture; + haircolor = new_haircolor; + beardcolor = new_beardcolor; + eyecolor1 = new_eyecolor1; + eyecolor2 = new_eyecolor2; + hairstyle = new_hairstyle; + luclinface = new_luclinface; + beard = new_beard; + drakkin_heritage = new_drakkin_heritage; + drakkin_tattoo = new_drakkin_tattoo; + drakkin_details = new_drakkin_details; + auto outapp = new EQApplicationPacket(OP_Illusion, sizeof(Illusion_Struct)); Illusion_Struct *is = (Illusion_Struct *) outapp->pBuffer; is->spawnid = GetID(); @@ -1883,9 +1896,10 @@ void Mob::SendIllusionPacket( safe_delete(outapp); /* Refresh armor and tints after send illusion packet */ - this->SendArmorAppearance(); + SendArmorAppearance(); - LogSpells("Illusion: Race = [{}], Gender = [{}], Texture = [{}], HelmTexture = [{}], HairColor = [{}], BeardColor = [{}], EyeColor1 = [{}], EyeColor2 = [{}], HairStyle = [{}], Face = [{}], DrakkinHeritage = [{}], DrakkinTattoo = [{}], DrakkinDetails = [{}], Size = [{}]", + LogSpells( + "Illusion: Race [{}] Gender [{}] Texture [{}] HelmTexture [{}] HairColor [{}] BeardColor [{}] EyeColor1 [{}] EyeColor2 [{}] HairStyle [{}] Face [{}] DrakkinHeritage [{}] DrakkinTattoo [{}] DrakkinDetails [{}] Size [{}]", race, gender, new_texture, @@ -1899,7 +1913,8 @@ void Mob::SendIllusionPacket( new_drakkin_heritage, new_drakkin_tattoo, new_drakkin_details, - size); + size + ); } bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) @@ -2405,7 +2420,7 @@ void Mob::ChangeSize(float in_size = 0, bool bNoRestriction) { if (in_size > 255.0) in_size = 255.0; //End of Size Code - this->size = in_size; + size = in_size; SendAppearancePacket(AT_Size, (uint32) in_size); } From 80ce499f672bcab67bb35418ce4f59a2c75c1137 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sun, 25 Oct 2020 23:01:30 -0500 Subject: [PATCH 007/196] Scanning Optimizations (#1133) * Scanning optimizations this more properly applies idle / moving scanning algorithms and applies update_others when a client is moving * Fix bots * Perform a self and other scan when npc's pop --- common/ruletypes.h | 3 ++- zone/bot.cpp | 4 +-- zone/client.cpp | 2 +- zone/client_packet.cpp | 60 ++++++++++++++++++++++++++++++++++------- zone/client_process.cpp | 2 +- zone/entity.cpp | 2 ++ zone/mob.cpp | 4 +-- zone/mob.h | 4 +-- zone/npc.cpp | 31 ++++++++++++--------- 9 files changed, 80 insertions(+), 32 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 016fefc7d..0636f2959 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -547,7 +547,8 @@ RULE_INT(Aggro, IntAggroThreshold, 75, "Int lesser or equal the value will aggro RULE_BOOL(Aggro, AllowTickPulling, false, "tick pulling is an exploit in an NPC's call for help fixed sometime in 2006 on live") RULE_INT(Aggro, MinAggroLevel, 18, "Minimum level for use with UseLevelAggro") RULE_BOOL(Aggro, UseLevelAggro, true, "MinAggroLevel rule value+ and Undead will aggro regardless of level difference. This will disabled Rule:IntAggroThreshold if set to true") -RULE_INT(Aggro, ClientAggroCheckInterval, 6, "Interval in which clients actually check for aggro - in seconds") +RULE_INT(Aggro, ClientAggroCheckMovingInterval, 1000, "Interval in which clients actually check for aggro while moving - in milliseconds - this should be lower than ClientAggroCheckIdleInterval") +RULE_INT(Aggro, ClientAggroCheckIdleInterval, 6000, "Interval in which clients actually check for aggro while idle - in milliseconds - this should be higher than ClientAggroCheckMovingInterval") RULE_REAL(Aggro, PetAttackRange, 40000.0, "Maximum squared range /pet attack works at default is 200") RULE_BOOL(Aggro, NPCAggroMaxDistanceEnabled, true, "If enabled, NPC's will drop aggro beyond 600 units or what is defined at the zone level") RULE_CATEGORY_END() diff --git a/zone/bot.cpp b/zone/bot.cpp index 48e468a05..a45e575d9 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2211,12 +2211,12 @@ bool Bot::Process() return false; } - if (mob_scan_close.Check()) { + if (mob_close_scan_timer.Check()) { LogAIScanClose( "is_moving [{}] bot [{}] timer [{}]", moving ? "true" : "false", GetCleanName(), - mob_scan_close.GetDuration() + mob_close_scan_timer.GetDuration() ); entity_list.ScanCloseClientMobs(close_mobs, this); diff --git a/zone/client.cpp b/zone/client.cpp index fdc75cfe6..562e6de58 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -139,7 +139,7 @@ Client::Client(EQStreamInterface* ieqs) endupkeep_timer(1000), forget_timer(0), autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), - client_scan_npc_aggro_timer(RuleI(Aggro, ClientAggroCheckInterval) * 1000), + client_scan_npc_aggro_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)), client_zone_wide_full_position_update_timer(5 * 60 * 1000), tribute_timer(Tribute_duration), proximity_timer(ClientProximity_interval), diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 316c08cd9..511a94b7e 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -4533,23 +4533,63 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) { rewind_timer.Start(30000, true); } - /* Handle client aggro scanning timers NPCs */ - is_client_moving = (cy == m_Position.y && cx == m_Position.x) ? false : true; + + is_client_moving = !(cy == m_Position.y && cx == m_Position.x); + + + /** + * Client aggro scanning + */ + const uint16 client_scan_npc_aggro_timer_idle = RuleI(Aggro, ClientAggroCheckIdleInterval); + const uint16 client_scan_npc_aggro_timer_moving = RuleI(Aggro, ClientAggroCheckMovingInterval); + + LogAggroDetail( + "ClientUpdate [{}] {}moving, scan timer [{}]", + GetCleanName(), + is_client_moving ? "" : "NOT ", + client_scan_npc_aggro_timer.GetRemainingTime() + ); if (is_client_moving) { - LogDebug("ClientUpdate: Client is moving - scan timer is: [{}]", client_scan_npc_aggro_timer.GetDuration()); - if (client_scan_npc_aggro_timer.GetDuration() > 1000) { + if (client_scan_npc_aggro_timer.GetRemainingTime() > client_scan_npc_aggro_timer_moving) { + LogAggroDetail("Client [{}] Restarting with moving timer", GetCleanName()); client_scan_npc_aggro_timer.Disable(); - client_scan_npc_aggro_timer.Start(500); + client_scan_npc_aggro_timer.Start(client_scan_npc_aggro_timer_moving); + client_scan_npc_aggro_timer.Trigger(); } } - else { - LogDebug("ClientUpdate: Client is NOT moving - scan timer is: [{}]", client_scan_npc_aggro_timer.GetDuration()); - if (client_scan_npc_aggro_timer.GetDuration() < 1000) { - client_scan_npc_aggro_timer.Disable(); - client_scan_npc_aggro_timer.Start(3000); + else if (client_scan_npc_aggro_timer.GetDuration() == client_scan_npc_aggro_timer_moving) { + LogAggroDetail("Client [{}] Restarting with idle timer", GetCleanName()); + client_scan_npc_aggro_timer.Disable(); + client_scan_npc_aggro_timer.Start(client_scan_npc_aggro_timer_idle); + } + + /** + * Client mob close list cache scan timer + */ + const uint16 client_mob_close_scan_timer_moving = 6000; + const uint16 client_mob_close_scan_timer_idle = 60000; + + LogAIScanCloseDetail( + "Client [{}] {}moving, scan timer [{}]", + GetCleanName(), + is_client_moving ? "" : "NOT ", + mob_close_scan_timer.GetRemainingTime() + ); + + if (is_client_moving) { + if (mob_close_scan_timer.GetRemainingTime() > client_mob_close_scan_timer_moving) { + LogAIScanCloseDetail("Client [{}] Restarting with moving timer", GetCleanName()); + mob_close_scan_timer.Disable(); + mob_close_scan_timer.Start(client_mob_close_scan_timer_moving); + mob_close_scan_timer.Trigger(); } } + else if (mob_close_scan_timer.GetDuration() == client_mob_close_scan_timer_moving) { + LogAIScanCloseDetail("Client [{}] Restarting with idle timer", GetCleanName()); + mob_close_scan_timer.Disable(); + mob_close_scan_timer.Start(client_mob_close_scan_timer_idle); + } /** * On a normal basis we limit mob movement updates based on distance diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 18ee9979f..ba62d470d 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -257,7 +257,7 @@ bool Client::Process() { * Used in aggro checks */ if (mob_close_scan_timer.Check()) { - entity_list.ScanCloseMobs(close_mobs, this, true); + entity_list.ScanCloseMobs(close_mobs, this, is_client_moving); } bool may_use_attacks = false; diff --git a/zone/entity.cpp b/zone/entity.cpp index b6d897ccb..417bac493 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -712,6 +712,8 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) npc_list.insert(std::pair(npc->GetID(), npc)); mob_list.insert(std::pair(npc->GetID(), npc)); + entity_list.ScanCloseMobs(npc->close_mobs, npc, true); + /* Zone controller process EVENT_SPAWN_ZONE */ if (RuleB(Zone, UseZoneController)) { if (entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID) && npc->GetNPCTypeID() != ZONE_CONTROLLER_NPC_ID){ diff --git a/zone/mob.cpp b/zone/mob.cpp index 175f9a94f..08c4625c0 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -118,7 +118,7 @@ Mob::Mob( attack_anim_timer(500), position_update_melee_push_timer(500), hate_list_cleanup_timer(6000), - mob_scan_close(6000), + mob_close_scan_timer(6000), mob_check_moving_timer(1000) { mMovementManager = &MobMovementManager::Get(); @@ -463,7 +463,7 @@ Mob::Mob( m_manual_follow = false; #endif - mob_scan_close.Trigger(); + mob_close_scan_timer.Trigger(); } Mob::~Mob() diff --git a/zone/mob.h b/zone/mob.h index 6fd040175..1f8705a30 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -172,8 +172,8 @@ public: void DisplayInfo(Mob *mob); std::unordered_map close_mobs; - Timer mob_scan_close; - Timer mob_check_moving_timer; + Timer mob_close_scan_timer; + Timer mob_check_moving_timer; //Somewhat sorted: needs documenting! diff --git a/zone/npc.cpp b/zone/npc.cpp index 2cd5cd095..a5ccfac76 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -723,22 +723,27 @@ bool NPC::Process() SpellProcess(); - if (mob_scan_close.Check()) { - + if (mob_close_scan_timer.Check()) { entity_list.ScanCloseMobs(close_mobs, this); - - if (moving) { - mob_scan_close.Disable(); - mob_scan_close.Start(RandomTimer(3000, 6000)); - } - else { - mob_scan_close.Disable(); - mob_scan_close.Start(RandomTimer(6000, 60000)); - } } - if (mob_check_moving_timer.Check() && moving) { - mob_scan_close.Trigger(); + const uint16 npc_mob_close_scan_timer_moving = 6000; + const uint16 npc_mob_close_scan_timer_idle = 60000; + + if (mob_check_moving_timer.Check()) { + if (moving) { + if (mob_close_scan_timer.GetRemainingTime() > npc_mob_close_scan_timer_moving) { + LogAIScanCloseDetail("NPC [{}] Restarting with moving timer", GetCleanName()); + mob_close_scan_timer.Disable(); + mob_close_scan_timer.Start(npc_mob_close_scan_timer_moving); + mob_close_scan_timer.Trigger(); + } + } + else if (mob_close_scan_timer.GetDuration() == npc_mob_close_scan_timer_moving) { + LogAIScanCloseDetail("NPC [{}] Restarting with idle timer", GetCleanName()); + mob_close_scan_timer.Disable(); + mob_close_scan_timer.Start(npc_mob_close_scan_timer_idle); + } } if (tic_timer.Check()) { From ee7ef9750689ac33528e2b0c18cc363ccbe40a20 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 25 Oct 2020 23:48:57 -0500 Subject: [PATCH 008/196] Revert "Spell Fizzle for < LDON expansions via lua_mod (#1118)" This reverts commit e9d312fa8664b736d2052bac1e76797485aca21d. --- utils/mods/expansions_combat.lua | 173 ------------------------------- zone/lua_client.cpp | 6 -- zone/lua_client.h | 1 - zone/lua_mod.cpp | 55 ---------- zone/lua_mod.h | 2 - zone/lua_parser.cpp | 9 -- zone/lua_parser.h | 2 - zone/spells.cpp | 11 -- 8 files changed, 259 deletions(-) delete mode 100644 utils/mods/expansions_combat.lua diff --git a/utils/mods/expansions_combat.lua b/utils/mods/expansions_combat.lua deleted file mode 100644 index 8f2ded4b9..000000000 --- a/utils/mods/expansions_combat.lua +++ /dev/null @@ -1,173 +0,0 @@ --- Source Function: Client::CheckFizzle() -function CheckFizzle(e) - if eq.is_lost_dungeons_of_norrath_enabled() then - e.IgnoreDefault = false; - return e; - - else -- Classic Fizzle -- Based on TAKP's fizzle formula - e.IgnoreDefault = true; - local client = e.self; - local spell = e.spell; - if client:GetGM() == true then -- GMs never fizzle - e.ReturnValue = true; - return e; - end - - local caster_class = client:GetClass(); - local spell_level = spell:Classes(caster_class - 1); - local aa_bonuses = client:GetAABonuses(); - local item_bonuses = client:GetItemBonuses(); - local spell_bonuses = client:GetSpellBonuses(); - - local no_fizzle_level = math.max(unpack({ - aa_bonuses:MasteryofPast(), - item_bonuses:MasteryofPast(), - spell_bonuses:MasteryofPast() - })); - local no_fizzle_level = 0; - - if spell_level < no_fizzle_level then - e.ReturnValue = true; - return e; - end - - local fizzle_adjustment = spell:BaseDiff(); - local spell_casting_skill_effects = aa_bonuses:adjusted_casting_skill() + - item_bonuses:adjusted_casting_skill() + - spell_bonuses:adjusted_casting_skill(); - - local capped_chance = 95; - local random_penalty = 0; - local effective_spell_casting_skill = 0; - local spell_level_adjustment = 0; - local prime_stat_bonus = 0; - - if fizzle_adjustment ~= 0 or spell_casting_skill_effects < 0 then - -- If Superior Healing not cast by cleric - if spell:ID() == 9 and caster_class ~= Class.CLERIC then - fizzle_adjustment = 0 - end - - if spell_level > 55 then - fizzle_adjustment = 0; - end - - if (caster_class == Class.PALADIN or - caster_class == Class.RANGER or - caster_class == Class.SHADOWKNIGHT) and - spell_level > 40 then - fizzle_adjustment = 0; - end - - if caster_class == Class.BARD and fizzle_adjustment > 15 then - fizzle_adjustment = 15; - end - - local prime_stat = 0; - if caster_class == Class.BARD then - prime_stat = (client:GetCHA() + client:GetDEX()) / 2; - elseif caster_class == Class.CLERIC or - caster_class == Class.PALADIN or - caster_class == Class.RANGER or - caster_class == Class.DRUID or - caster_class == Class.SHAMAN or - caster_class == Class.BEASTLORD then - prime_stat = client:GetWIS(); - elseif caster_class == Class.SHADOWKNIGHT or - caster_class == Class.NECROMANCER or - caster_class == Class.WIZARD or - caster_class == Class.MAGICIAN or - caster_class == Class.ENCHANTER then - prime_stat = client:GetINT(); - end - - prime_stat_bonus = math.floor(prime_stat / 10); - - local effective_spell_level = spell_level - 1; - if effective_spell_level > 50 then - effective_spell_level = 50 - end; - - local spell_casting_skill = client:GetSkill(spell:Skill()); - - effective_spell_casting_skill = spell_casting_skill + spell_casting_skill_effects; - if effective_spell_casting_skill < 0 then - effective_spell_casting_skill = 0; - end - - random_penalty = Random.Int(0, 10); - - spell_level_adjustment = 5 * (18 - effective_spell_level); - local chance = 0 + - effective_spell_casting_skill + - (spell_level_adjustment + prime_stat_bonus) - - random_penalty - - fizzle_adjustment; - - capped_chance = chance; - if caster_class == Class.BARD then - if capped_chance < 1 then - capped_chance = 1; - elseif capped_chance > 95 then - capped_chance = 95; - end - elseif caster_class <= 16 then - if capped_chance < 5 then - capped_chance = 5; - elseif capped_chance > 95 then - capped_chance = 95; - end - else -- Unknown Class - capped_chance = 0; - end - end - - local specialize_skill = client:GetSpecializeSkillValue(spell:ID()); - local specialize_adjustment = 0; - local spell_casting_mastery_adjustment = 0; - - if specialize_skill > 0 then - specialize_adjustment = math.floor(specialize_skill / 10) + 1; - - local spell_casting_mastery_level = client:GetAA(83); -- aaSpellCastingMastery - if spell_casting_mastery_level == 1 then - spell_casting_mastery_adjustment = 2; - elseif spell_casting_mastery_level == 2 then - spell_casting_mastery_adjustment = 5; - elseif spell_casting_mastery_level == 3 then - spell_casting_mastery_adjustment = 10; - end - - capped_chance = capped_chance + specialize_adjustment + spell_casting_mastery_adjustment; - if capped_chance > 98 then - capped_chance = 98; - end - end - - local roll_100 = Random.Int(1, 100); - - if client:IsSilenced() then - roll_100 = capped_chance + 1; - end - - --client:Message(15, "CheckFizzle(LUA:expansions_combat): " .. - -- "spell_id = " .. spell:ID() .. - -- ", roll_100 = " .. roll_100 .. - -- ", capped_chance (" .. capped_chance .. ") = " .. - -- "effective_spell_casting_skill (" .. effective_spell_casting_skill .. ") + " .. - -- "spell_level_adjustment (" .. spell_level_adjustment .. ") + " .. - -- "prime_stat_bonus (" .. prime_stat_bonus .. ") - " .. - -- "random_penalty (" .. random_penalty .. ") - " .. - -- "fizzle_adjustment (" .. fizzle_adjustment .. ") + " .. - -- "specialize_adjustment (" .. specialize_adjustment .. ") + " .. - -- "spell_casting_mastery_adjustment (" .. spell_casting_mastery_adjustment .. ")"); - - if capped_chance >= roll_100 then - e.ReturnValue = true; - return e; - end - - e.ReturnValue = false; - return e; - end -end diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index e32957823..a6145000d 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -485,11 +485,6 @@ int Lua_Client::GetRawSkill(int skill_id) { return self->GetRawSkill(static_cast(skill_id)); } -int Lua_Client::GetSkill(int skill_id) { - Lua_Safe_Call_Int(); - return self->GetSkill(static_cast(skill_id)); -} - bool Lua_Client::HasSkill(int skill_id) { Lua_Safe_Call_Bool(); return self->HasSkill(static_cast(skill_id)); @@ -1735,7 +1730,6 @@ luabind::scope lua_register_client() { .def("IncreaseLanguageSkill", (void(Lua_Client::*)(int))&Lua_Client::IncreaseLanguageSkill) .def("IncreaseLanguageSkill", (void(Lua_Client::*)(int,int))&Lua_Client::IncreaseLanguageSkill) .def("GetRawSkill", (int(Lua_Client::*)(int))&Lua_Client::GetRawSkill) - .def("GetSkill", (int(Lua_Client::*)(int))&Lua_Client::GetSkill) .def("HasSkill", (bool(Lua_Client::*)(int))&Lua_Client::HasSkill) .def("CanHaveSkill", (bool(Lua_Client::*)(int))&Lua_Client::CanHaveSkill) .def("SetSkill", (void(Lua_Client::*)(int,int))&Lua_Client::SetSkill) diff --git a/zone/lua_client.h b/zone/lua_client.h index 8d300c048..5900930f0 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -122,7 +122,6 @@ public: void IncreaseLanguageSkill(int skill_id); void IncreaseLanguageSkill(int skill_id, int value); int GetRawSkill(int skill_id); - int GetSkill(int skill_id); bool HasSkill(int skill_id); bool CanHaveSkill(int skill_id); void SetSkill(int skill_id, int value); diff --git a/zone/lua_mod.cpp b/zone/lua_mod.cpp index 955187a02..382f8adba 100644 --- a/zone/lua_mod.cpp +++ b/zone/lua_mod.cpp @@ -632,59 +632,4 @@ void LuaMod::GetExperienceForKill(Client *self, Mob *against, uint32 &returnValu } } -void LuaMod::CheckFizzle(Client *self, uint16 &spell_id, SPDat_Spell_Struct spell_struct, bool &returnValue, bool &ignoreDefault) { - int start = lua_gettop(L); - - try { - if (!m_has_check_fizzle) { - return; - } - - lua_getfield(L, LUA_REGISTRYINDEX, package_name_.c_str()); - lua_getfield(L, -1, "CheckFizzle"); - - Lua_Client l_self(self); - luabind::adl::object e = luabind::newtable(L); - e["self"] = l_self; - e["spell_id"] = spell_id; - - e.push(L); - - Lua_Spell l_spell(&spell_struct); - auto l_spell_o = luabind::adl::object(L, l_spell); - - l_spell_o.push(L); - lua_setfield(L, -2, "spell"); - - if (lua_pcall(L, 1, 1, 0)) { - std::string error = lua_tostring(L, -1); - parser_->AddError(error); - lua_pop(L, 1); - return; - } - - if (lua_type(L, -1) == LUA_TTABLE) { - luabind::adl::object ret(luabind::from_stack(L, -1)); - auto IgnoreDefaultObj = ret["IgnoreDefault"]; - if (luabind::type(IgnoreDefaultObj) == LUA_TBOOLEAN) { - ignoreDefault = ignoreDefault || luabind::object_cast(IgnoreDefaultObj); - } - - auto returnValueObj = ret["ReturnValue"]; - if (luabind::type(returnValueObj) == LUA_TBOOLEAN) { - returnValue = returnValue || luabind::object_cast(returnValueObj); - } - } - } - catch (std::exception& ex) { - parser_->AddError(ex.what()); - } - - int end = lua_gettop(L); - int n = end - start; - if (n > 0) { - lua_pop(L, n); - } -} - #endif diff --git a/zone/lua_mod.h b/zone/lua_mod.h index d21062e6e..defc5edab 100644 --- a/zone/lua_mod.h +++ b/zone/lua_mod.h @@ -26,7 +26,6 @@ public: void GetRequiredAAExperience(Client *self, uint32 &returnValue, bool &ignoreDefault); void GetEXPForLevel(Client *self, uint16 level, uint32 &returnValue, bool &ignoreDefault); void GetExperienceForKill(Client *self, Mob *against, uint32 &returnValue, bool &ignoreDefault); - void CheckFizzle(Client *self, uint16 &spell_id, SPDat_Spell_Struct spell_struct, bool &returnValue, bool &ignoreDefault); private: LuaParser *parser_; lua_State *L; @@ -41,5 +40,4 @@ private: bool m_has_get_required_aa_experience; bool m_has_get_exp_for_level; bool m_has_get_experience_for_kill; - bool m_has_check_fizzle; }; diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 1e69a7076..ebe88f32a 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -1377,13 +1377,4 @@ uint32 LuaParser::GetExperienceForKill(Client *self, Mob *against, bool &ignoreD return retval; } -bool LuaParser::CheckFizzle(Client *self, uint16 &spell_id, SPDat_Spell_Struct spell_struct, bool &ignoreDefault) -{ - bool retValue = false; - for (auto &mod : mods_) { - mod.CheckFizzle(self, spell_id, spell_struct, retValue, ignoreDefault); - } - return retValue; -} - #endif diff --git a/zone/lua_parser.h b/zone/lua_parser.h index 929f3d300..8265a191f 100644 --- a/zone/lua_parser.h +++ b/zone/lua_parser.h @@ -99,8 +99,6 @@ public: uint32 GetRequiredAAExperience(Client *self, bool &ignoreDefault); uint32 GetEXPForLevel(Client *self, uint16 level, bool &ignoreDefault); uint32 GetExperienceForKill(Client *self, Mob *against, bool &ignoreDefault); - bool CheckFizzle(Client *self, uint16 &spell_id, SPDat_Spell_Struct spell_struct, bool &ignoreDefault); - private: LuaParser(); diff --git a/zone/spells.cpp b/zone/spells.cpp index 74cd0db8c..ea925ecda 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -82,7 +82,6 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include "string_ids.h" #include "worldserver.h" #include "fastmath.h" -#include "lua_parser.h" #include #include @@ -735,16 +734,6 @@ bool Mob::CheckFizzle(uint16 spell_id) bool Client::CheckFizzle(uint16 spell_id) { -#ifdef LUA_EQEMU - bool ignoreDefault = false; - bool fizzle = LuaParser::Instance()->CheckFizzle(this, spell_id, spells[spell_id], ignoreDefault); - - if (!fizzle) { - return false; - } else if (ignoreDefault) { - return true; - } -#endif // GMs don't fizzle if (GetGM()) return(true); From be12cad7bdac94330ea8f5767f230a584f63c139 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Sat, 31 Oct 2020 18:47:43 -0500 Subject: [PATCH 009/196] Fix for quest::GetZoneLongName(zone_short_name) garbled output (#1134) --- zone/embparser_api.cpp | 103 +++++++++++++++++++++-------------------- zone/questmgr.cpp | 14 +++--- zone/questmgr.h | 2 +- 3 files changed, 59 insertions(+), 60 deletions(-) diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 9977f9ca3..2ad425f3a 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -3733,10 +3733,11 @@ XS(XS__GetZoneLongName) { if (items != 1) Perl_croak(aTHX_ "Usage: quest::GetZoneLongName(string zone)"); dXSTARG; - char *zone = (char *) SvPV_nolen(ST(0)); - Const_char *RETVAL = quest_manager.GetZoneLongName(zone); - sv_setpv(TARG, RETVAL); + std::string zone = (std::string) SvPV_nolen(ST(0)); + std::string RETVAL = quest_manager.GetZoneLongName(zone); + + sv_setpv(TARG, RETVAL.c_str()); XSprePUSH; PUSHTARG; XSRETURN(1); @@ -3767,7 +3768,7 @@ XS(XS__crosszoneassigntaskbycharid) { if (items == 3) { enforce_level_requirement = (bool) SvTRUE(ST(2)); - } + } quest_manager.CrossZoneAssignTaskByCharID(character_id, task_id, enforce_level_requirement); } @@ -3801,13 +3802,13 @@ XS(XS__crosszoneassigntaskbyraidid) { int raid_id = (int) SvIV(ST(0)); uint32 task_id = (uint32) SvIV(ST(1)); bool enforce_level_requirement = false; - + if (items == 3) { enforce_level_requirement = (bool) SvTRUE(ST(2)); } quest_manager.CrossZoneAssignTaskByRaidID(raid_id, task_id, enforce_level_requirement); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneassigntaskbyguildid); @@ -3825,7 +3826,7 @@ XS(XS__crosszoneassigntaskbyguildid) { } quest_manager.CrossZoneAssignTaskByGuildID(guild_id, task_id, enforce_level_requirement); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonecastspellbycharid); @@ -3838,7 +3839,7 @@ XS(XS__crosszonecastspellbycharid) { uint32 spell_id = (uint32) SvIV(ST(1)); quest_manager.CrossZoneCastSpellByCharID(character_id, spell_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonecastspellbygroupid); @@ -3851,7 +3852,7 @@ XS(XS__crosszonecastspellbygroupid) { uint32 spell_id = (uint32) SvIV(ST(1)); quest_manager.CrossZoneCastSpellByGroupID(group_id, spell_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonecastspellbyraidid); @@ -3864,7 +3865,7 @@ XS(XS__crosszonecastspellbyraidid) { uint32 spell_id = (uint32) SvIV(ST(1)); quest_manager.CrossZoneCastSpellByRaidID(raid_id, spell_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonecastspellbyguildid); @@ -3877,7 +3878,7 @@ XS(XS__crosszonecastspellbyguildid) { uint32 spell_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneCastSpellByGuildID(guild_id, spell_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonedisabletaskbycharid); @@ -3890,7 +3891,7 @@ XS(XS__crosszonedisabletaskbycharid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneDisableTaskByCharID(char_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonedisabletaskbygroupid); @@ -3903,7 +3904,7 @@ XS(XS__crosszonedisabletaskbygroupid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneDisableTaskByGroupID(group_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonedisabletaskbyraidid); @@ -3916,7 +3917,7 @@ XS(XS__crosszonedisabletaskbyraidid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneDisableTaskByRaidID(raid_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonedisabletaskbyguildid); @@ -3929,7 +3930,7 @@ XS(XS__crosszonedisabletaskbyguildid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneDisableTaskByGuildID(guild_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneenabletaskbycharid); @@ -3942,7 +3943,7 @@ XS(XS__crosszoneenabletaskbycharid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneEnableTaskByCharID(char_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneenabletaskbygroupid); @@ -3955,7 +3956,7 @@ XS(XS__crosszoneenabletaskbygroupid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneEnableTaskByGroupID(group_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneenabletaskbyraidid); @@ -3968,7 +3969,7 @@ XS(XS__crosszoneenabletaskbyraidid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneEnableTaskByRaidID(raid_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneenabletaskbyguildid); @@ -3981,7 +3982,7 @@ XS(XS__crosszoneenabletaskbyguildid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneEnableTaskByGuildID(guild_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonefailtaskbycharid); @@ -3994,7 +3995,7 @@ XS(XS__crosszonefailtaskbycharid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneFailTaskByCharID(char_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonefailtaskbygroupid); @@ -4007,7 +4008,7 @@ XS(XS__crosszonefailtaskbygroupid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneFailTaskByGroupID(group_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonefailtaskbyraidid); @@ -4020,7 +4021,7 @@ XS(XS__crosszonefailtaskbyraidid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneFailTaskByRaidID(raid_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonefailtaskbyguildid); @@ -4033,7 +4034,7 @@ XS(XS__crosszonefailtaskbyguildid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneFailTaskByGuildID(guild_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonemarqueebycharid); @@ -4390,7 +4391,7 @@ XS(XS__crosszoneremovetaskbycharid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneRemoveTaskByCharID(char_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneremovetaskbygroupid); @@ -4403,7 +4404,7 @@ XS(XS__crosszoneremovetaskbygroupid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneRemoveTaskByGroupID(group_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneremovetaskbyraidid); @@ -4416,7 +4417,7 @@ XS(XS__crosszoneremovetaskbyraidid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneRemoveTaskByRaidID(raid_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneremovetaskbyguildid); @@ -4429,7 +4430,7 @@ XS(XS__crosszoneremovetaskbyguildid) { uint32 task_id = (uint32) SvUV(ST(1)); quest_manager.CrossZoneRemoveTaskByGuildID(guild_id, task_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneresetactivitybycharid); @@ -4443,7 +4444,7 @@ XS(XS__crosszoneresetactivitybycharid) { int activity_id = (int) SvIV(ST(2)); quest_manager.CrossZoneResetActivityByCharID(char_id, task_id, activity_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneresetactivitybygroupid); @@ -4457,7 +4458,7 @@ XS(XS__crosszoneresetactivitybygroupid) { int activity_id = (int) SvIV(ST(2)); quest_manager.CrossZoneResetActivityByGroupID(group_id, task_id, activity_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneresetactivitybyraidid); @@ -4471,7 +4472,7 @@ XS(XS__crosszoneresetactivitybyraidid) { int activity_id = (int) SvIV(ST(2)); quest_manager.CrossZoneResetActivityByRaidID(raid_id, task_id, activity_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneresetactivitybyguildid); @@ -4485,7 +4486,7 @@ XS(XS__crosszoneresetactivitybyguildid) { int activity_id = (int) SvIV(ST(2)); quest_manager.CrossZoneResetActivityByGuildID(guild_id, task_id, activity_id); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszonesetentityvariablebynpctypeid); @@ -4684,7 +4685,7 @@ XS(XS__crosszoneupdateactivitybycharid) { } quest_manager.CrossZoneUpdateActivityByCharID(char_id, task_id, activity_id, activity_count); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneupdateactivitybygroupid); @@ -4702,7 +4703,7 @@ XS(XS__crosszoneupdateactivitybygroupid) { } quest_manager.CrossZoneUpdateActivityByGroupID(group_id, task_id, activity_id, activity_count); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneupdateactivitybyraidid); @@ -4720,7 +4721,7 @@ XS(XS__crosszoneupdateactivitybyraidid) { } quest_manager.CrossZoneUpdateActivityByRaidID(raid_id, task_id, activity_id, activity_count); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__crosszoneupdateactivitybyguildid); @@ -4738,7 +4739,7 @@ XS(XS__crosszoneupdateactivitybyguildid) { } quest_manager.CrossZoneUpdateActivityByGuildID(guild_id, task_id, activity_id, activity_count); } - XSRETURN_EMPTY; + XSRETURN_EMPTY; } XS(XS__worldwideassigntask); @@ -4753,7 +4754,7 @@ XS(XS__worldwideassigntask) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -4775,7 +4776,7 @@ XS(XS__worldwidecastspell) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -4797,7 +4798,7 @@ XS(XS__worldwidedisabletask) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -4819,7 +4820,7 @@ XS(XS__worldwideenabletask) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -4841,7 +4842,7 @@ XS(XS__worldwidefailtask) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -4868,7 +4869,7 @@ XS(XS__worldwidemarquee) { if (items == 7) { min_status = (uint8) SvUV(ST(6)); } - + if (items == 8) { max_status = (uint8) SvUV(ST(7)); } @@ -4891,7 +4892,7 @@ XS(XS__worldwidemessage) { if (items == 3) { min_status = (uint8) SvUV(ST(2)); } - + if (items == 4) { max_status = (uint8) SvUV(ST(3)); } @@ -4914,7 +4915,7 @@ XS(XS__worldwidemove) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -4936,7 +4937,7 @@ XS(XS__worldwidemoveinstance) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -4958,7 +4959,7 @@ XS(XS__worldwideremovespell) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -4980,7 +4981,7 @@ XS(XS__worldwideremovetask) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(2)); } @@ -5003,7 +5004,7 @@ XS(XS__worldwideresetactivity) { if (items == 3) { min_status = (uint8) SvUV(ST(2)); } - + if (items == 4) { max_status = (uint8) SvUV(ST(3)); } @@ -5026,7 +5027,7 @@ XS(XS__worldwidesetentityvariableclient) { if (items == 3) { min_status = (uint8) SvUV(ST(2)); } - + if (items == 4) { max_status = (uint8) SvUV(ST(3)); } @@ -5075,7 +5076,7 @@ XS(XS__worldwidesignalclient) { if (items == 2) { min_status = (uint8) SvUV(ST(1)); } - + if (items == 3) { max_status = (uint8) SvUV(ST(1)); } @@ -5102,7 +5103,7 @@ XS(XS__worldwideupdateactivity) { if (items == 4) { min_status = (uint8) SvUV(ST(3)); } - + if (items == 5) { max_status = (uint8) SvUV(ST(4)); } @@ -6171,7 +6172,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "crosszonemoveinstancebycharid"), XS__crosszonemoveinstancebycharid, file); newXS(strcpy(buf, "crosszonemoveinstancebygroupid"), XS__crosszonemoveinstancebygroupid, file); newXS(strcpy(buf, "crosszonemoveinstancebyraidid"), XS__crosszonemoveinstancebyraidid, file); - newXS(strcpy(buf, "crosszonemoveinstancebyguildid"), XS__crosszonemoveinstancebyguildid, file); + newXS(strcpy(buf, "crosszonemoveinstancebyguildid"), XS__crosszonemoveinstancebyguildid, file); newXS(strcpy(buf, "crosszoneremovespellbycharid"), XS__crosszoneremovespellbycharid, file); newXS(strcpy(buf, "crosszoneremovespellbygroupid"), XS__crosszoneremovespellbygroupid, file); newXS(strcpy(buf, "crosszoneremovespellbyraidid"), XS__crosszoneremovespellbyraidid, file); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 82fae08ee..15651b1be 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -3272,13 +3272,11 @@ int32 QuestManager::GetZoneID(const char *zone) { return static_cast(ZoneID(zone)); } -const char* QuestManager::GetZoneLongName(const char *zone) { - char *long_name; - content_db.GetZoneLongName(zone, &long_name); - std::string ln = long_name; - safe_delete_array(long_name); - - return ln.c_str(); +std::string QuestManager::GetZoneLongName(std::string zone_short_name) +{ + return zone_store.GetZoneLongName( + zone_store.GetZoneID(zone_short_name) + ); } void QuestManager::CrossZoneAssignTaskByCharID(int character_id, uint32 task_id, bool enforce_level_requirement) { @@ -3958,7 +3956,7 @@ void QuestManager::CrossZoneUpdateActivityByGuildID(int guild_id, uint32 task_id } } -void QuestManager::WorldWideAssignTask(uint32 task_id, bool enforce_level_requirement, uint8 min_status, uint8 max_status) { +void QuestManager::WorldWideAssignTask(uint32 task_id, bool enforce_level_requirement, uint8 min_status, uint8 max_status) { QuestManagerCurrentQuestVars(); if (initiator && owner) { auto pack = new ServerPacket(ServerOP_WWAssignTask, sizeof(WWAssignTask_Struct)); diff --git a/zone/questmgr.h b/zone/questmgr.h index 9ea34e1e1..18c20907f 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -280,7 +280,7 @@ public: void SendMail(const char *to, const char *from, const char *subject, const char *message); uint16 CreateDoor( const char* model, float x, float y, float z, float heading, uint8 opentype, uint16 size); int32 GetZoneID(const char *zone); - const char *GetZoneLongName(const char *zone); + static std::string GetZoneLongName(std::string zone_short_name); void CrossZoneAssignTaskByCharID(int character_id, uint32 task_id, bool enforce_level_requirement = false); void CrossZoneAssignTaskByGroupID(int group_id, uint32 task_id, bool enforce_level_requirement = false); void CrossZoneAssignTaskByRaidID(int raid_id, uint32 task_id, bool enforce_level_requirement = false); From 1fc4073a056836be185a790a57a3557a648287ae Mon Sep 17 00:00:00 2001 From: Akkadius Date: Tue, 3 Nov 2020 19:46:47 -0500 Subject: [PATCH 010/196] Garbage commit to push a build --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e999b7050..fea2c23a4 100644 --- a/README.md +++ b/README.md @@ -80,3 +80,4 @@ forum, although pull requests will be much quicker and easier on all parties. + From 60254105f15618c3f689b0f08546c35d428ece28 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 5 Nov 2020 19:40:04 -0500 Subject: [PATCH 011/196] Update the SummonItem call to default charges for QuestRewards This should summon the item at max charges, if it has charges --- zone/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client.cpp b/zone/client.cpp index 562e6de58..182fb1b60 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -8617,7 +8617,7 @@ void Client::QuestReward(Mob* target, uint32 copper, uint32 silver, uint32 gold, AddMoneyToPP(copper, silver, gold, platinum, false); if (itemid > 0) - SummonItem(itemid, 0, 0, 0, 0, 0, 0, false, EQ::invslot::slotCursor); + SummonItem(itemid, -1, 0, 0, 0, 0, 0, false, EQ::invslot::slotCursor); if (faction) { From eb0d12f22038353342fccbd655e6d5c253df0ab3 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 17 Nov 2020 15:47:12 -0500 Subject: [PATCH 012/196] Save the rest_timer sooner to prevent issues We need to save the remaining time sooner to prevent circumstances described here: http://www.projecteq.net/forums/index.php?threads/raid-out-of-combat-regen-timers-resetting-on-non-raid-mobs.16114/ --- zone/client.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 562e6de58..087aeb1ec 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -4725,6 +4725,12 @@ void Client::IncrementAggroCount(bool raid_target) uint32 newtimer = raid_target ? RuleI(Character, RestRegenRaidTimeToActivate) : RuleI(Character, RestRegenTimeToActivate); + // When our aggro count is 1 here, we are exiting rest state. We need to pause our current timer, if we have time remaining + // We should not actually have to do anything to the Timer object since the AggroCount counter blocks it from being checked + // and will have it's timer changed when we exit combat so let's not do any extra work + if (AggroCount == 1 && rest_timer.GetRemainingTime()) // the Client::rest_timer is never disabled, so don't need to check + m_pp.RestTimer = std::max(1u, rest_timer.GetRemainingTime() / 1000); // I guess round up? + // save the new timer if it's higher m_pp.RestTimer = std::max(m_pp.RestTimer, newtimer); @@ -4734,10 +4740,6 @@ void Client::IncrementAggroCount(bool raid_target) if(AggroCount > 1) return; - // Pause the rest timer, it's possible the new timer is a non-raid timer we're currently ticking down on a raid timer - if (AggroCount == 1) - m_pp.RestTimer = std::max(m_pp.RestTimer, rest_timer.GetRemainingTime() / 1000); - if (ClientVersion() >= EQ::versions::ClientVersion::SoF) { auto outapp = new EQApplicationPacket(OP_RestState, 1); char *Buffer = (char *)outapp->pBuffer; From f26b3195c91a179aaeb0018852c7c32dc89eba5c Mon Sep 17 00:00:00 2001 From: E Spause Date: Sat, 5 Dec 2020 16:15:53 -0500 Subject: [PATCH 013/196] Add safeguard for issue with mobs pathing to 0,0,0 on teleport nodes with values 0,0,0 (#1140) * IP limit and account session limit refactor - uses responses built into loginserver * Fix an issue where teleport nodes with 0.0f as a value were added to routes when pathing. --- zone/mob_movement_manager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/mob_movement_manager.cpp b/zone/mob_movement_manager.cpp index 0ebed1188..90bc075d4 100644 --- a/zone/mob_movement_manager.cpp +++ b/zone/mob_movement_manager.cpp @@ -1225,7 +1225,7 @@ void MobMovementManager::UpdatePathGround(Mob *who, float x, float y, float z, M ) ); } - else { + else if(!next_node.teleport) { if (zone->watermap->InLiquid(previous_pos)) { PushSwimTo(ent.second, next_node.pos.x, next_node.pos.y, next_node.pos.z, mode); } @@ -1345,7 +1345,7 @@ void MobMovementManager::UpdatePathUnderwater(Mob *who, float x, float y, float next_node.pos.y )); } - else { + else if(!next_node.teleport) { PushSwimTo(ent.second, next_node.pos.x, next_node.pos.y, next_node.pos.z, movement_mode); } } From 8bc60acd77c691015718e3569ca972b2c34ab822 Mon Sep 17 00:00:00 2001 From: Noudess Date: Mon, 7 Dec 2020 11:10:59 -0500 Subject: [PATCH 014/196] Fix so roamboxes allow water boxes again. --- zone/mob_ai.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 994724f0d..7acebb219 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1605,6 +1605,7 @@ void NPC::AI_DoMovement() { * if the roam box was sloppily configured */ if (!this->GetWasSpawnedInWater()) { + roambox_destination_z = GetGroundZ(roambox_destination_x, roambox_destination_y); if (zone->HasMap() && zone->HasWaterMap()) { auto position = glm::vec3( roambox_destination_x, @@ -1629,6 +1630,17 @@ void NPC::AI_DoMovement() { } } } + else { // Mob was in water, make sure new spot is in water also + roambox_destination_z = m_Position.z; + auto position = glm::vec3( roambox_destination_x, + roambox_destination_y, + m_Position.z + 15); + if (!zone->watermap->InLiquid(position)) { + roambox_destination_x = m_SpawnPoint.x; + roambox_destination_y = m_SpawnPoint.y; + roambox_destination_z = m_SpawnPoint.z; + } + } PathfinderOptions opts; opts.smooth_path = true; @@ -1643,7 +1655,7 @@ void NPC::AI_DoMovement() { glm::vec3( roambox_destination_x, roambox_destination_y, - GetGroundZ(roambox_destination_x, roambox_destination_y) + roambox_destination_z ), partial, stuck, @@ -1659,8 +1671,6 @@ void NPC::AI_DoMovement() { return; } - roambox_destination_z = 0; - Log( Logs::General, Logs::NPCRoamBox, From 5a4c651d6b9c0d87be83fb1853c61990c66acfd0 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Mon, 7 Dec 2020 22:36:59 -0600 Subject: [PATCH 015/196] Drone Build Pipeline (#1144) * Create drone config * Tweak config * Update .drone.yml * Garbage commit to run another build * Bring cores down * Fix formatting from https://github.com/EQEmu/Server/pull/1142 * Add tests line * Change directory to build before running the unit tests * Remove tests dir * Avoid double build on PR's * Testing build settings --- .drone.yml | 16 ++++++++++++++++ zone/mob_ai.cpp | 16 +++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 000000000..749ab62c5 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,16 @@ +--- +kind: pipeline +type: docker +name: EQEmulator Server Linux CI + +# Limits how many of these builds can run on the drone runner at a time, this isn't about cores +concurrency: + limit: 1 + +steps: + - name: server-build + # Source build script https://github.com/Akkadius/akk-stack/blob/master/containers/eqemu-server/Dockerfile#L20 + image: akkadius/eqemu-server:latest + commands: + - sudo chown eqemu:eqemu /drone/src/ * -R + - git submodule init && git submodule update && mkdir -p build && cd build && cmake -DEQEMU_BUILD_LOGIN=ON -DEQEMU_BUILD_BOTS=ON -DEQEMU_BUILD_LUA=ON -G 'Unix Makefiles' .. && make -j$((`nproc`-4)) \ No newline at end of file diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 7acebb219..34d2daba5 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1632,14 +1632,16 @@ void NPC::AI_DoMovement() { } else { // Mob was in water, make sure new spot is in water also roambox_destination_z = m_Position.z; - auto position = glm::vec3( roambox_destination_x, - roambox_destination_y, - m_Position.z + 15); + auto position = glm::vec3( + roambox_destination_x, + roambox_destination_y, + m_Position.z + 15 + ); if (!zone->watermap->InLiquid(position)) { - roambox_destination_x = m_SpawnPoint.x; - roambox_destination_y = m_SpawnPoint.y; - roambox_destination_z = m_SpawnPoint.z; - } + roambox_destination_x = m_SpawnPoint.x; + roambox_destination_y = m_SpawnPoint.y; + roambox_destination_z = m_SpawnPoint.z; + } } PathfinderOptions opts; From fff53fc465444664a056cd666c2f06b1118759d8 Mon Sep 17 00:00:00 2001 From: Noudess Date: Sun, 13 Dec 2020 10:17:04 -0500 Subject: [PATCH 016/196] Destination check needed to be actual ground z at loc. -15 wasnt enough --- zone/mob_ai.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 7acebb219..e856a0a40 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1610,7 +1610,7 @@ void NPC::AI_DoMovement() { auto position = glm::vec3( roambox_destination_x, roambox_destination_y, - (m_Position.z - 15) + roambox_destination_z ); /** From a1cc68d2144802f7712ff5f93920e8003d7c3d86 Mon Sep 17 00:00:00 2001 From: neckkola <65987027+neckkola@users.noreply.github.com> Date: Mon, 21 Dec 2020 19:06:48 -0400 Subject: [PATCH 017/196] Added new Perl/LUA GetSpellIDByBookSlot (#1151) Added a new questAPI GetSpellIDByBookSlot to allow for sorting spellbooks by various attributes (level, type, etc). Allows to determine which spell is in what book slot. --- zone/client.h | 1 + zone/lua_client.cpp | 6 ++++++ zone/lua_client.h | 1 + zone/perl_client.cpp | 28 ++++++++++++++++++++++++++++ zone/spells.cpp | 7 +++++++ 5 files changed, 43 insertions(+) diff --git a/zone/client.h b/zone/client.h index d2778d55a..f3aa6cf01 100644 --- a/zone/client.h +++ b/zone/client.h @@ -988,6 +988,7 @@ public: void ProcessInspectRequest(Client* requestee, Client* requester); bool ClientFinishedLoading() { return (conn_state == ClientConnectFinished); } int FindSpellBookSlotBySpellID(uint16 spellid); + uint32 GetSpellIDByBookSlot(int book_slot); int GetNextAvailableSpellBookSlot(int starting_slot = 0); inline uint32 GetSpellByBookSlot(int book_slot) { return m_pp.spell_book[book_slot]; } inline bool HasSpellScribed(int spellid) { return (FindSpellBookSlotBySpellID(spellid) != -1 ? true : false); } diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index a6145000d..086c90269 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1160,6 +1160,11 @@ int Lua_Client::GetNextAvailableSpellBookSlot() { return self->GetNextAvailableSpellBookSlot(); } +uint32 Lua_Client::GetSpellIDByBookSlot(int slot_id) { + Lua_Safe_Call_Int(); + return self->GetSpellIDByBookSlot(slot_id); +} + int Lua_Client::GetNextAvailableSpellBookSlot(int start) { Lua_Safe_Call_Int(); return self->GetNextAvailableSpellBookSlot(start); @@ -1866,6 +1871,7 @@ luabind::scope lua_register_client() { .def("ClearCompassMark",(void(Lua_Client::*)(void))&Lua_Client::ClearCompassMark) .def("GetNextAvailableSpellBookSlot", (int(Lua_Client::*)(void))&Lua_Client::GetNextAvailableSpellBookSlot) .def("GetNextAvailableSpellBookSlot", (int(Lua_Client::*)(int))&Lua_Client::GetNextAvailableSpellBookSlot) + .def("GetSpellIDByBookSlot", (uint32(Lua_Client::*)(int))& Lua_Client::GetSpellIDByBookSlot) .def("FindSpellBookSlotBySpellID", (int(Lua_Client::*)(int))&Lua_Client::FindSpellBookSlotBySpellID) .def("UpdateTaskActivity", (void(Lua_Client::*)(int,int,int))&Lua_Client::UpdateTaskActivity) .def("AssignTask", (void(Lua_Client::*)(int,int))&Lua_Client::AssignTask) diff --git a/zone/lua_client.h b/zone/lua_client.h index 5900930f0..96b1b1c79 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -260,6 +260,7 @@ public: void ClearCompassMark(); int GetNextAvailableSpellBookSlot(); int GetNextAvailableSpellBookSlot(int start); + uint32 GetSpellIDByBookSlot(int book_slot); int FindSpellBookSlotBySpellID(int spell_id); void UpdateTaskActivity(int task, int activity, int count); void AssignTask(int task, int npc_id); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index aabeaa77d..ba3043fc5 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -5297,6 +5297,33 @@ XS(XS_Client_GetSpellBookSlotBySpellID) { XSRETURN(1); } +XS(XS_Client_GetSpellIDByBookSlot); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_GetSpellIDByBookSlot) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::GetSpellIDByBookSlot(THIS, int slot_id)"); + { + Client* THIS; + int RETVAL; + int slot_id = SvUV(ST(1)); + dXSTARG; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client*, tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + RETVAL = THIS->GetSpellIDByBookSlot(slot_id); + XSprePUSH; + PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + XS(XS_Client_UpdateTaskActivity); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_UpdateTaskActivity) { dXSARGS; @@ -6821,6 +6848,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetRawSkill"), XS_Client_GetRawSkill, file, "$$"); newXSproto(strcpy(buf, "GetSkillPoints"), XS_Client_GetSkillPoints, file, "$"); newXSproto(strcpy(buf, "GetSpellBookSlotBySpellID"), XS_Client_GetSpellBookSlotBySpellID, file, "$$"); + newXSproto(strcpy(buf, "GetSpellIDByBookSlot"), XS_Client_GetSpellIDByBookSlot, file, "$$"); newXSproto(strcpy(buf, "GetSpentAA"), XS_Client_GetSpentAA, file, "$$"); newXSproto(strcpy(buf, "GetStartZone"), XS_Client_GetStartZone, file, "$"); newXSproto(strcpy(buf, "GetTargetRingX"), XS_Client_GetTargetRingX, file, "$$"); diff --git a/zone/spells.cpp b/zone/spells.cpp index ea925ecda..742878bbc 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5090,6 +5090,13 @@ void Client::UnmemSpellAll(bool update_client) UnmemSpell(i, update_client); } +uint32 Client::GetSpellIDByBookSlot(int book_slot) { + if (book_slot <= EQ::spells::SPELLBOOK_SIZE) { + return GetSpellByBookSlot(book_slot); + } + return -1; //default +} + uint16 Client::FindMemmedSpellBySlot(int slot) { if (m_pp.mem_spells[slot] != 0xFFFFFFFF) return m_pp.mem_spells[slot]; From 1eb5e4a0c1398bb725b470197dfdee8010781469 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Mon, 21 Dec 2020 18:07:15 -0500 Subject: [PATCH 018/196] Remove the extra erroneous message (DoT landing message) at mob death if mob dies of DoT. (#1138) * Removed the erroneous message of a DoT "landing" when a mob dies from that DoT * Set spell=-1 on Death struct for NPCs. It was causing extra DoT msg. * Remove whitespace Co-authored-by: Noudess --- zone/attack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 1d780a27e..2531024ee 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2247,7 +2247,7 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQ::skills::SkillTy d->spawn_id = GetID(); d->killer_id = killer_mob ? killer_mob->GetID() : 0; d->bindzoneid = 0; - d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell; + d->spell_id = 0xffffffff; // Sending spell was causing extra DoT land msg d->attack_skill = SkillDamageTypes[attack_skill]; d->damage = damage; app->priority = 6; From f87662f67636a737cab894c0613ac56a2be5eddb Mon Sep 17 00:00:00 2001 From: Thalic Date: Tue, 22 Dec 2020 00:07:45 +0100 Subject: [PATCH 019/196] Minor ortographic corrections of ruletypes.h (#1147) * Update ruletypes.h * Update ruletypes.h --- common/ruletypes.h | 62 +++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 0636f2959..dc79a35de 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -36,7 +36,7 @@ RULE_CATEGORY(Character) -RULE_INT(Character, MaxLevel, 65, "Sets the highest level attainable by experience for players") +RULE_INT(Character, MaxLevel, 65, "Sets the highest level for players that can be reached through experience") RULE_BOOL(Character, PerCharacterQglobalMaxLevel, false, "Check for qglobal 'CharMaxLevel' character qglobal (Type 5, \"\"), if player tries to level beyond that point, it will not go beyond that level") RULE_BOOL(Character, PerCharacterBucketMaxLevel, false, "Check for data bucket 'CharMaxLevel', if player tries to level beyond that point, it will not go beyond that level") RULE_INT(Character, MaxExpLevel, 0, "Defines the maximum level that can be reached through experience") @@ -57,8 +57,8 @@ RULE_REAL(Character, ExpMultiplier, 0.5, "If greater than 0, the experience gain RULE_REAL(Character, AAExpMultiplier, 0.5, "If greater than 0, the AA experience gained is multiplied by this value. ") RULE_REAL(Character, GroupExpMultiplier, 0.5, "The experience in a group is multiplied by this value in addition to the group multiplier. The group multiplier is: 2 members=x 1.2, 3=x1.4, 4=x1.6, 5=x1.8, 6=x2.16") RULE_REAL(Character, RaidExpMultiplier, 0.2, "The experience gained in raids is multiplied by (1-RaidExpMultiplier) ") -RULE_BOOL(Character, UseXPConScaling, true, "When activated, the experience is modified depending on the difference between player level and NPC level. The settings Green/LightBlue/Blue//White/Yellow and RedModifier are used") -RULE_INT(Character, ShowExpValues, 0, "Show expirience values. 0=normal, 1=show raw experience values, 2=show raw experience values and percent") +RULE_BOOL(Character, UseXPConScaling, true, "When activated, the experience is modified depending on the difference between player level and NPC level. The values from the rules GreenModifier to RedModifier are used") +RULE_INT(Character, ShowExpValues, 0, "Show experience values. 0=normal, 1=show raw experience values, 2=show raw experience values and percent") RULE_INT(Character, GreenModifier, 20, "The experience obtained for green con mobs is multiplied by value/100") RULE_INT(Character, LightBlueModifier, 40, "The experience obtained for light-blue con mobs is multiplied by value/100") RULE_INT(Character, BlueModifier, 90, "The experience obtained for blue con mobs is multiplied by value/100") @@ -80,7 +80,7 @@ RULE_INT(Character, ItemAccuracyCap, 150, "Limit on accuracy granted by items") RULE_INT(Character, ItemAvoidanceCap, 100, "Limit on avoidance granted by items") RULE_INT(Character, ItemCombatEffectsCap, 100, "Limit on combat effects granted by items") RULE_INT(Character, ItemShieldingCap, 35, "Limit on shielding granted by items") -RULE_INT(Character, ItemSpellShieldingCap, 35, "Limit on spell shieldung granted by items") +RULE_INT(Character, ItemSpellShieldingCap, 35, "Limit on spell shielding granted by items") RULE_INT(Character, ItemDoTShieldingCap, 35, "Limit on DoT shielding granted by items") RULE_INT(Character, ItemStunResistCap, 35, "Limit on resistance granted by items") RULE_INT(Character, ItemStrikethroughCap, 35, "Limit on strikethrough granted by items") @@ -91,7 +91,7 @@ RULE_INT(Character, ItemClairvoyanceCap, 250, "Limit on clairvoyance granted by RULE_INT(Character, ItemDSMitigationCap, 50, "Limit on damageshield mitigation granted by items") RULE_INT(Character, ItemEnduranceRegenCap, 15, "Limit on endurance regeneration granted by items") RULE_INT(Character, ItemExtraDmgCap, 150, "Cap for bonuses to melee skills like Bash, Frenzy, etc.") -RULE_INT(Character, HasteCap, 100, "Haste cap for non-v3(overhaste) haste") +RULE_INT(Character, HasteCap, 100, "Haste cap for non-v3(over haste) haste") RULE_INT(Character, SkillUpModifier, 100, "The probability for a skill-up is multiplied by value/100") RULE_BOOL(Character, SharedBankPlat, false, "Shared bank platinum. Off by default to prevent duplication") RULE_BOOL(Character, BindAnywhere, false, "Allows players to bind their soul anywhere in the world") @@ -112,14 +112,14 @@ RULE_INT(Character, RespawnFromHoverTimer, 300, "Respawn Window countdown timer, RULE_BOOL(Character, UseNewStatsWindow, true, "Setting whether the new Stats window, which displays all information, should be used") RULE_BOOL(Character, ItemCastsUseFocus, false, "If true, this allows item clickies to use focuses that have limited maximum levels on them") RULE_INT(Character, MinStatusForNoDropExemptions, 80, "This allows status x and higher to trade no drop items") -RULE_INT(Character, SkillCapMaxLevel, 75, "Sets the Maximum Level used for Skill Caps (from skill_caps table). -1 makes it use MaxLevel rule value. It is set to 75 because PEQ only has skillcaps up to that level, and grabbing the players' skill past 75 will return 0, breaking all skills past that level. This helps servers with obsurd level caps (75+ level cap) function without any modifications") +RULE_INT(Character, SkillCapMaxLevel, 75, "Sets the Maximum Level used for Skill Caps (from skill_caps table). -1 makes it use MaxLevel rule value. It is set to 75 because PEQ only has skill caps up to that level, and grabbing the players' skill past 75 will return 0, breaking all skills past that level. This helps servers with obsurd level caps (75+ level cap) function without any modifications") RULE_INT(Character, StatCap, 0, "If StatCap > 0 then this value is used. If it is 0, the value of the following code is used: If Level < 61: 255. If Level >= 61 and the client SoF or newer: 255 + 5 x (level -60). If the client is older than SoF and the level < 71 then: 255 + x (level-60). In all other cases: 330.") RULE_BOOL(Character, CheckCursorEmptyWhenLooting, true, "If true, a player cannot loot a corpse (player or NPC) with an item on their cursor") RULE_BOOL(Character, MaintainIntoxicationAcrossZones, true, "If true, alcohol effects are maintained across zoning and logging out/in") RULE_BOOL(Character, EnableDiscoveredItems, true, "If enabled, it enables EVENT_DISCOVER_ITEM and also saves character names and timestamps for the first time an item is discovered") RULE_BOOL(Character, EnableXTargetting, true, "Enable Extended Targeting Window, for users with UF and later clients") RULE_BOOL(Character, EnableAggroMeter, true, "Enable Aggro Meter, for users with RoF and later clients") -RULE_BOOL(Character, KeepLevelOverMax, false, "Don't delevel a character that has somehow gone over the level cap") +RULE_BOOL(Character, KeepLevelOverMax, false, "Don't de-level a character that has somehow gone over the level cap") RULE_INT(Character, FoodLossPerUpdate, 32, "How much food/water you lose per stamina update") RULE_BOOL(Character, EnableHungerPenalties, false, "Being hungry/thirsty has negative effects -- it does appear normal live servers do not have penalties") RULE_BOOL(Character, EnableFoodRequirement, true, "If disabled, food is no longer required") @@ -129,16 +129,16 @@ RULE_INT(Character, BaseRunSpeedCap, 158, "Base Run Speed Cap, on live it's 158% RULE_INT(Character, OrnamentationAugmentType, 20, "Ornamentation Augment Type") RULE_REAL(Character, EnvironmentDamageMulipliter, 1, "Multiplier for environmental damage like fall damage.") RULE_BOOL(Character, UnmemSpellsOnDeath, true, "Setting whether at death all memorized Spells are forgotten") -RULE_INT(Character, TradeskillUpAlchemy, 2, "Alchemy skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpBaking, 2, "Baking skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpBlacksmithing, 2, "Blacksmithing skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpBrewing, 3, "Brewing skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpFletching, 2, "Fletching skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpJewelcrafting, 2, "Jewelcrafting skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpMakePoison, 2, "Make Poison skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpPottery, 4, "Pottery skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpResearch, 1, "Research skillup rate adjust. Lower is faster") -RULE_INT(Character, TradeskillUpTinkering, 2, "Tinkering skillup rate adjust. Lower is faster") +RULE_INT(Character, TradeskillUpAlchemy, 2, "Alchemy skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpBaking, 2, "Baking skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpBlacksmithing, 2, "Blacksmithing skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpBrewing, 3, "Brewing skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpFletching, 2, "Fletching skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpJewelcrafting, 2, "Jewelcrafting skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpMakePoison, 2, "Make Poison skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpPottery, 4, "Pottery skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpResearch, 1, "Research skillup rate adjustment. Lower is faster") +RULE_INT(Character, TradeskillUpTinkering, 2, "Tinkering skillup rate adjustment. Lower is faster") RULE_BOOL(Character, MarqueeHPUpdates, false, "Will show health percentage in center of screen if health lesser than 100%") RULE_INT(Character, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars") RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres") @@ -147,7 +147,7 @@ RULE_BOOL(Character, ActiveInvSnapshots, false, "Takes a periodic snapshot of in RULE_INT(Character, InvSnapshotMinIntervalM, 180, "Minimum time between inventory snapshots (minutes)") RULE_INT(Character, InvSnapshotMinRetryM, 30, "Time to re-attempt an inventory snapshot after a failure (minutes)") RULE_INT(Character, InvSnapshotHistoryD, 30, "Time to keep snapshot entries (days)") -RULE_BOOL(Character, RestrictSpellScribing, false, "Setting whether to testricts spell scribing to allowable races/classes of spell scroll") +RULE_BOOL(Character, RestrictSpellScribing, false, "Setting whether to restrict spell scribing to allowable races/classes of spell scroll") RULE_BOOL(Character, UseStackablePickPocketing, true, "Allows stackable pickpocketed items to stack instead of only being allowed in empty inventory slots") RULE_BOOL(Character, EnableAvoidanceCap, false, "Setting whether the avoidance cap should be activated") RULE_INT(Character, AvoidanceCap, 750, "750 Is a pretty good value, seen people dodge all attacks beyond 1,000 Avoidance") @@ -197,7 +197,7 @@ RULE_INT(Skills, MaxTrainSpecializations, 50, "Maximum level a GM trainer will t RULE_INT(Skills, SwimmingStartValue, 100, "Start value of swimming skill") RULE_BOOL(Skills, TrainSenseHeading, false, "Switch whether SenseHeading is trained by use") RULE_INT(Skills, SenseHeadingStartValue, 200, "Start value of sense heading skill") -RULE_BOOL(Skills, SelfLanguageLearning, true, "Enabling self learning of languages") +RULE_BOOL(Skills, SelfLanguageLearning, true, "Enabling self-learning of languages") RULE_BOOL(Skills, RequireTomeHandin, false, "Disable click-to-learn and force hand in to Guild Master") RULE_CATEGORY_END() @@ -339,7 +339,7 @@ RULE_BOOL(Spells, ReflectMessagesClose, true, "True (Live functionality) is for RULE_INT(Spells, VirusSpreadDistance, 30, "The distance a viral spell will jump to its next victim") RULE_BOOL(Spells, LiveLikeFocusEffects, true, "Determines whether specific healing, dmg and mana reduction focuses are randomized") RULE_INT(Spells, BaseImmunityLevel, 55, "The level that targets start to be immune to stun, fear and mez spells with a maximum level of 0") -RULE_BOOL(Spells, NPCIgnoreBaseImmunity, true, "Whether or not NPCget to ignore the BaseImmunityLevel for their spells") +RULE_BOOL(Spells, NPCIgnoreBaseImmunity, true, "Whether or not NPC get to ignore the BaseImmunityLevel for their spells") RULE_REAL(Spells, AvgSpellProcsPerMinute, 6.0, "Adjust rate for sympathetic spell procs") RULE_INT(Spells, ResistFalloff, 67, "Maximum that level that will adjust our resist chance based on level modifiers") RULE_INT(Spells, CharismaEffectiveness, 10, "Determines how much resist modification charisma applies to charm/pacify checks. Default 10 CHA = -1 resist mod") @@ -397,7 +397,7 @@ RULE_INT(Combat, PetBaseCritChance, 0, "Pet base crit chance") RULE_INT(Combat, NPCBashKickLevel, 6, "The level that NPCcan KICK/BASH") RULE_INT(Combat, NPCBashKickStunChance, 15, "Percent chance that a bash/kick will stun") RULE_INT(Combat, MeleeCritDifficulty, 8900, "Value against which is rolled to check if a melee crit is triggered. Lower is easier") -RULE_INT(Combat, ArcheryCritDifficulty, 3400, "Value against which is rolled to check if a archery crit is triggered. Lower is easier") +RULE_INT(Combat, ArcheryCritDifficulty, 3400, "Value against which is rolled to check if an archery crit is triggered. Lower is easier") RULE_INT(Combat, ThrowingCritDifficulty, 1100, "Value against which is rolled to check if a throwing crit is triggered. Lower is easier") RULE_BOOL(Combat, NPCCanCrit, false, "Setting whether an NPC can land critical hits") RULE_BOOL(Combat, UseIntervalAC, true, "Switch whether bonuses, armour class, multipliers, classes and caps should be considered in the calculation of damage values") @@ -416,7 +416,7 @@ RULE_REAL(Combat, BaseProcChance, 0.035, "Base chance for procs") RULE_REAL(Combat, ProcDexDivideBy, 11000, "Divisor for the probability of a proc increased by dexterity") RULE_BOOL(Combat, AdjustSpecialProcPerMinute, true, "Set PPM for special abilities like HeadShot (Live does this as of 4-14)") RULE_REAL(Combat, AvgSpecialProcsPerMinute, 2.0, "Unclear what best value is atm") -RULE_REAL(Combat, BaseHitChance, 69.0, "Base chance to hiz") +RULE_REAL(Combat, BaseHitChance, 69.0, "Base chance to hit") RULE_REAL(Combat, NPCBonusHitChance, 26.0, "Bonus chance to hit for NPC") RULE_REAL(Combat, HitFalloffMinor, 5.0, "Hit will fall off up to value over the initial level range (percent)") RULE_REAL(Combat, HitFalloffModerate, 7.0, "Hit will fall off up to value over the three levels after the initial level range (percent)") @@ -490,14 +490,14 @@ RULE_BOOL(Combat, ProjectileDmgOnImpact, true, "If enabled, projectiles (ie arro RULE_BOOL(Combat, MeleePush, true, "Eenable melee push") RULE_INT(Combat, MeleePushChance, 50, "NPC chance the target will be pushed. Made up, 100 actually isn't that bad") RULE_BOOL(Combat, UseLiveCombatRounds, true, "Turn this false if you don't want to worry about fixing up combat rounds for NPCs") -RULE_INT(Combat, NPCAssistCap, 5, "Maxiumium number of NPCthat will assist another NPC at once") +RULE_INT(Combat, NPCAssistCap, 5, "Maxiumium number of NPC that will assist another NPC at once") RULE_INT(Combat, NPCAssistCapTimer, 6000, "Time a NPC will take to clear assist aggro cap space (milliseconds)") RULE_BOOL(Combat, UseRevampHandToHand, false, "Use h2h revamped dmg/delays I believe this was implemented during SoF") -RULE_BOOL(Combat, ClassicMasterWu, false, "Classic master wu uses a random special, modern doesn't") +RULE_BOOL(Combat, ClassicMasterWu, false, "Classic Master Wu uses a random special, modern doesn't") RULE_REAL(Combat, HitBoxMod, 1.00, "Added to test hit boxes.") RULE_INT(Combat, LevelToStopDamageCaps, 0, "Level to stop damage caps. 1 will effectively disable them, 20 should give basically same results as old incorrect system") RULE_INT(Combat, LevelToStopACTwinkControl, 50, "Level to stop armorclass twink control. 1 will effectively disable it, 50 should give basically same results as current system") -RULE_BOOL(Combat, ClassicNPCBackstab, false, "True disables npc facestab - NPCget normal attack if not behind") +RULE_BOOL(Combat, ClassicNPCBackstab, false, "True disables NPC facestab - NPC get normal attack if not behind") RULE_BOOL(Combat, UseNPCDamageClassLevelMods, true, "Uses GetClassLevelDamageMod calc in npc_scale_manager") RULE_BOOL(Combat, UseExtendedPoisonProcs, false, "Allow old school poisons to last until characrer zones, at a lower proc rate") RULE_CATEGORY_END() @@ -508,7 +508,7 @@ RULE_INT(NPC, MajorNPCCorpseDecayTimeMS, 1500000, "NPC corpse decay time, if NPC RULE_INT(NPC, CorpseUnlockTimer, 150000, "Time after which corpses are unlocked for everyone to loot (milliseconds)") RULE_INT(NPC, EmptyNPCCorpseDecayTimeMS, 0, "NPC corpse decay time, if no items are left on the corpse (milliseconds)") RULE_BOOL(NPC, UseItemBonusesForNonPets, true, "Switch whether item bonuses should be used for NPCs who are not pets") -RULE_BOOL(NPC, UseBaneDamage, false, "If NPCs can't inheriently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs") +RULE_BOOL(NPC, UseBaneDamage, false, "If NPCs can't inherently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs") RULE_INT(NPC, SayPauseTimeInSec, 5, "Time span in which an NPC pauses his movement after a Say event without aggro (seconds)") RULE_INT(NPC, OOCRegen, 0, "Enable out-of-combat regeneration for NPC") RULE_BOOL(NPC, BuffFriends, false, "Setting whether NPC should buff other NPC") @@ -516,27 +516,27 @@ RULE_BOOL(NPC, EnableNPCQuestJournal, false, "Setting whether the NPC Quest Jour RULE_INT(NPC, LastFightingDelayMovingMin, 10000, "Minimum time before mob goes home after all aggro loss (milliseconds)") RULE_INT(NPC, LastFightingDelayMovingMax, 20000, "Maximum time before mob goes home after all aggro loss (milliseconds)") RULE_BOOL(NPC, SmartLastFightingDelayMoving, true, "When true, mobs that started going home previously will do so again immediately if still on FD hate list") -RULE_BOOL(NPC, ReturnNonQuestNoDropItems, false, "Returns NO DROP items on NPCthat don't have an EVENT_TRADE sub in their script") +RULE_BOOL(NPC, ReturnNonQuestNoDropItems, false, "Returns NO DROP items on NPC that don't have an EVENT_TRADE sub in their script") RULE_INT(NPC, StartEnrageValue, 9, " Percentage HP that an NPC will begin to enrage") RULE_BOOL(NPC, LiveLikeEnrage, false, "If set to true then only player controlled pets will enrage") RULE_BOOL(NPC, EnableMeritBasedFaction, false, "If set to true, faction will given in the same way as experience (solo/group/raid)") RULE_INT(NPC, NPCToNPCAggroTimerMin, 500, "Minimum time span after which one NPC aggro another NPC (milliseconds)") RULE_INT(NPC, NPCToNPCAggroTimerMax, 6000, "Maximum time span after which one NPC aggro another NPC (milliseconds)") -RULE_BOOL(NPC, UseClassAsLastName, true, "Uses class archetype as LastName for NPCwith none") +RULE_BOOL(NPC, UseClassAsLastName, true, "Uses class archetype as LastName for NPC with none") RULE_BOOL(NPC, NewLevelScaling, true, "Better level scaling, use old if new formulas would break your server") RULE_INT(NPC, NPCGatePercent, 20, " Percentage at which the NPC Will attempt to gate at") RULE_BOOL(NPC, NPCGateNearBind, false, "Will NPC attempt to gate when near bind location?") RULE_INT(NPC, NPCGateDistanceBind, 75, "Distance from bind before NPC will attempt to gate") RULE_BOOL(NPC, NPCHealOnGate, true, "Will the NPC Heal on Gate") RULE_BOOL(NPC, UseMeditateBasedManaRegen, false, "Based NPC ooc regen on Meditate skill") -RULE_REAL(NPC, NPCHealOnGateAmount, 25, "How much the npc will heal on gate if enabled") +RULE_REAL(NPC, NPCHealOnGateAmount, 25, "How much the NPC will heal on gate if enabled") RULE_CATEGORY_END() RULE_CATEGORY(Aggro) RULE_BOOL(Aggro, SmartAggroList, true, "Smart aggro list attempts to choose targets in a much smarter fashion, prefering players to pets, sitting and critically injured players to normal players, and players in melee range to players not") RULE_INT(Aggro, SittingAggroMod, 35, "Aggro increase against sitting targets. 35=35%") RULE_INT(Aggro, MeleeRangeAggroMod, 10, "Aggro increase against targets in melee range. 10=10%") -RULE_INT(Aggro, CurrentTargetAggroMod, 0, "Aggro increase against current target. 0% = prefer the current target to any other. Makes it harder for our NPCto switch targets") +RULE_INT(Aggro, CurrentTargetAggroMod, 0, "Aggro increase against current target. 0% = prefer the current target to any other. Makes it harder for our NPC to switch targets") RULE_INT(Aggro, CriticallyWoundedAggroMod, 100, "Aggro increase against critical wounded targets") RULE_INT(Aggro, SpellAggroMod, 100, "Aggro increase for spells") RULE_INT(Aggro, SongAggroMod, 33, "Aggro increase for songs") @@ -620,7 +620,7 @@ RULE_BOOL(Chat, EnableVoiceMacros, true, "Enable voice macros") RULE_BOOL(Chat, EnableMailKeyIPVerification, true, "Setting whether the authenticity of the client should be verified via its IP address when accessing the InGame mailbox") RULE_BOOL(Chat, EnableAntiSpam, true, "Enable anti-spam system for chat") RULE_BOOL(Chat, SuppressCommandErrors, false, "Do not suppress command errors by default") -RULE_INT(Chat, MinStatusToBypassAntiSpam, 100, "Minimum status to bypass the anti spam system") +RULE_INT(Chat, MinStatusToBypassAntiSpam, 100, "Minimum status to bypass the anti-spam system") RULE_INT(Chat, MinimumMessagesPerInterval, 4, "Minimum number of chat messages allowed per interval. The karma value is added to this value") RULE_INT(Chat, MaximumMessagesPerInterval, 12, "Maximum value of chat messages allowed per interval") RULE_INT(Chat, MaxMessagesBeforeKick, 20, "If an attempt is made to send more than the maximum allowed number of chat messages per interval, the client will be disconnected after this absolute number of messages") From 69a8507908380d0e8f28b7756bd7e27d71b5a1f4 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Mon, 21 Dec 2020 18:08:04 -0500 Subject: [PATCH 020/196] Feature/underworld (#1146) * Update NewZone_Structs * Update packet translators for NewZone_Struct * Add OP_UnderWorld OPcodes These aren't implemented yet, but I thought it would be good to document them. This sends up just entity ID and location when you fall underworld. This could possibly be used to check for someone screwing with zone data locally to warp or something I guess. * Add database fields --- common/eq_packet_structs.h | 5 +- common/patches/rof.cpp | 1 + common/patches/rof2.cpp | 1 + common/patches/rof2_structs.h | 2 +- common/patches/rof_structs.h | 6 +- common/patches/sod.cpp | 1 + common/patches/sod_structs.h | 6 +- common/patches/sof.cpp | 1 + common/patches/sof_structs.h | 6 +- common/patches/uf.cpp | 1 + common/patches/uf_structs.h | 6 +- common/version.h | 2 +- utils/patches/patch_RoF.conf | 1 + utils/patches/patch_RoF2.conf | 1 + utils/patches/patch_SoD.conf | 1 + utils/patches/patch_SoF.conf | 1 + utils/patches/patch_Titanium.conf | 1 + utils/patches/patch_UF.conf | 1 + utils/sql/db_update_manifest.txt | 1 + .../git/required/2020_12_09_underworld.sql | 6 + zone/zonedb.cpp | 126 +++++++++--------- 21 files changed, 108 insertions(+), 69 deletions(-) create mode 100644 utils/sql/git/required/2020_12_09_underworld.sql diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index d6e8b0c97..c22069196 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -375,13 +375,16 @@ struct NewZone_Struct { /*0686*/ uint16 zone_instance; /*0688*/ uint32 unknown688; /*0692*/ uint8 unknown692[8]; +// Titanium doesn't have a translator, but we can still safely add stuff under here without issues since client memcpy's only what it knows +// Just wastes some bandwidth sending to tit clients /shrug /*0700*/ float fog_density; /*0704*/ uint32 SuspendBuffs; /*0708*/ uint32 FastRegenHP; /*0712*/ uint32 FastRegenMana; /*0716*/ uint32 FastRegenEndurance; /*0720*/ uint32 NPCAggroMaxDist; -/*0724*/ +/*0724*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, if this value is 0, it prevents you from running off edges that would end up underworld +/*0728*/ }; /* diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index c9e80a0f1..8811c8746 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1827,6 +1827,7 @@ namespace RoF OUT(FastRegenHP); OUT(FastRegenMana); OUT(FastRegenEndurance); + OUT(underworld_teleport_index); eq->FogDensity = emu->fog_density; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 1b3827984..09a2d502d 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1876,6 +1876,7 @@ namespace RoF2 OUT(FastRegenHP); OUT(FastRegenMana); OUT(FastRegenEndurance); + OUT(underworld_teleport_index); eq->FogDensity = emu->fog_density; diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 67947d57f..96e74187d 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -628,7 +628,7 @@ struct NewZone_Struct { /*0856*/ uint32 scriptNPCReceivedanItem; /*0860*/ uint32 bCheck; // padded bool /*0864*/ uint32 scriptIDSomething; - /*0868*/ uint32 scriptIDSomething2; + /*0868*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions /*0872*/ uint32 scriptIDSomething3; /*0876*/ uint32 SuspendBuffs; // padded bool /*0880*/ uint32 LavaDamage; // LavaDamage value diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 76324d59b..c22278aac 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -575,7 +575,11 @@ struct NewZone_Struct { /*0848*/ int32 unknown848; /*0852*/ uint16 zone_id; /*0854*/ uint16 zone_instance; -/*0856*/ char unknown856[20]; +/*0856*/ uint32 scriptNPCReceivedanItem; +/*0860*/ uint32 bCheck; // padded bool +/*0864*/ uint32 scriptIDSomething; +/*0868*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions +/*0872*/ uint32 scriptIDSomething3; /*0876*/ uint32 SuspendBuffs; /*0880*/ uint32 unknown880; // Seen 50 /*0884*/ uint32 unknown884; // Seen 10 diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 6e3f274e7..45f2fe20e 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1353,6 +1353,7 @@ namespace SoD OUT(FastRegenHP); OUT(FastRegenMana); OUT(FastRegenEndurance); + OUT(underworld_teleport_index); /*fill in some unknowns with observed values, hopefully it will help */ eq->unknown800 = -1; diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 2a8b0cec8..7a2ae5710 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -444,7 +444,11 @@ struct NewZone_Struct { /*0848*/ int32 unknown848; /*0852*/ uint16 zone_id; /*0854*/ uint16 zone_instance; -/*0856*/ char unknown856[20]; +/*0856*/ uint32 scriptNPCReceivedanItem; +/*0860*/ uint32 bCheck; // padded bool +/*0864*/ uint32 scriptIDSomething; +/*0868*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions +/*0872*/ uint32 scriptIDSomething3; /*0876*/ uint32 SuspendBuffs; /*0880*/ uint32 unknown880; //seen 50 /*0884*/ uint32 unknown884; //seen 10 diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 94e94f946..f8ccf0274 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -1030,6 +1030,7 @@ namespace SoF OUT(FastRegenHP); OUT(FastRegenMana); OUT(FastRegenEndurance); + OUT(underworld_teleport_index); /*fill in some unknowns with observed values, hopefully it will help */ eq->unknown796 = -1; diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index c76ef0b96..b9716d016 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -448,7 +448,11 @@ struct NewZone_Struct { /*0844*/ int32 unknown844; /*0848*/ uint16 zone_id; /*0850*/ uint16 zone_instance; -/*0852*/ char unknown852[20]; +/*0852*/ uint32 scriptNPCReceivedanItem; +/*0856*/ uint32 bCheck; // padded bool +/*0860*/ uint32 scriptIDSomething; +/*0864*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions +/*0868*/ uint32 scriptIDSomething3; /*0872*/ uint32 SuspendBuffs; /*0876*/ uint32 unknown876; //seen 50 /*0880*/ uint32 unknown880; //seen 10 diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 49c13833d..9dfc653b4 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -1577,6 +1577,7 @@ namespace UF OUT(FastRegenHP); OUT(FastRegenMana); OUT(FastRegenEndurance); + OUT(underworld_teleport_index); eq->FogDensity = emu->fog_density; diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index a87ab7a07..895881a7a 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -444,7 +444,11 @@ struct NewZone_Struct { /*0848*/ int32 unknown848; /*0852*/ uint16 zone_id; /*0854*/ uint16 zone_instance; -/*0856*/ char unknown856[20]; +/*0856*/ uint32 scriptNPCReceivedanItem; +/*0860*/ uint32 bCheck; // padded bool +/*0864*/ uint32 scriptIDSomething; +/*0868*/ uint32 underworld_teleport_index; // > 0 teleports w/ zone point index, invalid succors, -1 affects some collisions +/*0872*/ uint32 scriptIDSomething3; /*0876*/ uint32 SuspendBuffs; /*0880*/ uint32 unknown880; //seen 50 /*0884*/ uint32 unknown884; //seen 10 diff --git a/common/version.h b/common/version.h index e524afb01..82fe629b6 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9157 +#define CURRENT_BINARY_DATABASE_VERSION 9158 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9027 diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 31b091ff8..6e638ba1a 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -366,6 +366,7 @@ OP_CancelSneakHide=0x265f OP_AggroMeterLockTarget=0x70b7 OP_AggroMeterTargetInfo=0x18fe OP_AggroMeterUpdate=0x75aa +OP_UnderWorld=0x44f9 # clients sends up when they detect an underworld issue, might be useful for cheat detection OP_DzQuit=0x5fc8 OP_DzListTimers=0x67b9 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index a39fde999..f62b90dd4 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -367,6 +367,7 @@ OP_CancelSneakHide=0x0927 OP_AggroMeterLockTarget=0x1643 OP_AggroMeterTargetInfo=0x16bc OP_AggroMeterUpdate=0x1781 +OP_UnderWorld=0x2eb3 # clients sends up when they detect an underworld issue, might be useful for cheat detection # Expeditions OP_DzAddPlayer=0x4701 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 619f986c1..05e4a335f 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -357,6 +357,7 @@ OP_OpenContainer=0x3278 OP_Marquee=0x7dc9 OP_Fling=0x2b88 OP_CancelSneakHide=0x7705 +OP_UnderWorld=0x51ae # clients sends up when they detect an underworld issue, might be useful for cheat detection # Expedition OP_DzQuit=0x054e diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index 6a5d41fb4..c6d754ba3 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -338,6 +338,7 @@ OP_OpenContainer=0x10e3 OP_Marquee=0x2f75 OP_Untargetable=0x3e36 OP_CancelSneakHide=0x5335 +OP_UnderWorld=0x7580 # clients sends up when they detect an underworld issue, might be useful for cheat detection #expedition OP_DzQuit=0x20d6 diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index 9a7e1e800..ade2576aa 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -543,6 +543,7 @@ OP_PlayerStateRemove=0x381d OP_VoiceMacroIn=0x2866 # Client to Server OP_VoiceMacroOut=0x2ec6 # Server to Client OP_CameraEffect=0x0937 # Correct +OP_UnderWorld=0x7186 # clients sends up when they detect an underworld issue, might be useful for cheat detection #named unknowns, to make looking for real unknown easier OP_AnnoyingZoneUnknown=0x729c diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 69d6bb1e5..101fafde1 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -368,6 +368,7 @@ OP_OpenContainer=0x041a OP_Marquee=0x3675 OP_Fling=0x51b1 OP_CancelSneakHide=0x7686 +OP_UnderWorld=0x2d9d # clients sends up when they detect an underworld issue, might be useful for cheat detection OP_DzQuit=0x1539 OP_DzListTimers=0x21e9 diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 63af98bbd..9f005365b 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -411,6 +411,7 @@ 9155|2020_08_15_lootdrop_level_filtering.sql|SHOW COLUMNS from `lootdrop_entries` LIKE 'trivial_min_level'|empty| 9156|2020_08_16_virtual_zonepoints.sql|SHOW COLUMNS from `zone_points` LIKE 'is_virtual'|empty| 9157|2020_09_02_pet_taunting.sql|SHOW COLUMNS from `character_pet_info` LIKE 'taunting'|empty| +9158|2020_12_09_underworld.sql|SHOW COLUMNS from `zone` LIKE 'underworld_teleport_index'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2020_12_09_underworld.sql b/utils/sql/git/required/2020_12_09_underworld.sql new file mode 100644 index 000000000..f2f2ba032 --- /dev/null +++ b/utils/sql/git/required/2020_12_09_underworld.sql @@ -0,0 +1,6 @@ +ALTER TABLE `zone` ADD COLUMN `underworld_teleport_index` INT(4) NOT NULL DEFAULT '0'; +UPDATE `zone` SET `underworld` = '-2030' WHERE `zoneidnumber` = '71'; +UPDATE `zone` SET `underworld_teleport_index` = '11' WHERE `zoneidnumber` = '71'; +UPDATE `zone` SET `underworld_teleport_index` = '-1' WHERE `zoneidnumber` = '75'; +UPDATE `zone` SET `underworld_teleport_index` = '-1' WHERE `zoneidnumber` = '150'; + diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index bc1b542dd..5b8a08d77 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -105,68 +105,69 @@ bool ZoneDatabase::GetZoneCFG( std::string query = StringFormat( "SELECT " - "ztype, " // 0 - "fog_red, " // 1 - "fog_green, " // 2 - "fog_blue, " // 3 - "fog_minclip, " // 4 - "fog_maxclip, " // 5 - "fog_red2, " // 6 - "fog_green2, " // 7 - "fog_blue2, " // 8 - "fog_minclip2, " // 9 - "fog_maxclip2, " // 10 - "fog_red3, " // 11 - "fog_green3, " // 12 - "fog_blue3, " // 13 - "fog_minclip3, " // 14 - "fog_maxclip3, " // 15 - "fog_red4, " // 16 - "fog_green4, " // 17 - "fog_blue4, " // 18 - "fog_minclip4, " // 19 - "fog_maxclip4, " // 20 - "fog_density, " // 21 - "sky, " // 22 - "zone_exp_multiplier, " // 23 - "safe_x, " // 24 - "safe_y, " // 25 - "safe_z, " // 26 - "underworld, " // 27 - "minclip, " // 28 - "maxclip, " // 29 - "time_type, " // 30 - "canbind, " // 31 - "cancombat, " // 32 - "canlevitate, " // 33 - "castoutdoor, " // 34 - "hotzone, " // 35 - "ruleset, " // 36 - "suspendbuffs, " // 37 - "map_file_name, " // 38 - "short_name, " // 39 - "rain_chance1, " // 40 - "rain_chance2, " // 41 - "rain_chance3, " // 42 - "rain_chance4, " // 43 - "rain_duration1, " // 44 - "rain_duration2, " // 45 - "rain_duration3, " // 46 - "rain_duration4, " // 47 - "snow_chance1, " // 48 - "snow_chance2, " // 49 - "snow_chance3, " // 50 - "snow_chance4, " // 51 - "snow_duration1, " // 52 - "snow_duration2, " // 53 - "snow_duration3, " // 54 - "snow_duration4, " // 55 - "gravity, " // 56 - "fast_regen_hp, " // 57 - "fast_regen_mana, " // 58 - "fast_regen_endurance, " // 59 - "npc_max_aggro_dist, " // 60 - "max_movement_update_range " // 61 + "ztype, " // 0 + "fog_red, " // 1 + "fog_green, " // 2 + "fog_blue, " // 3 + "fog_minclip, " // 4 + "fog_maxclip, " // 5 + "fog_red2, " // 6 + "fog_green2, " // 7 + "fog_blue2, " // 8 + "fog_minclip2, " // 9 + "fog_maxclip2, " // 10 + "fog_red3, " // 11 + "fog_green3, " // 12 + "fog_blue3, " // 13 + "fog_minclip3, " // 14 + "fog_maxclip3, " // 15 + "fog_red4, " // 16 + "fog_green4, " // 17 + "fog_blue4, " // 18 + "fog_minclip4, " // 19 + "fog_maxclip4, " // 20 + "fog_density, " // 21 + "sky, " // 22 + "zone_exp_multiplier, " // 23 + "safe_x, " // 24 + "safe_y, " // 25 + "safe_z, " // 26 + "underworld, " // 27 + "minclip, " // 28 + "maxclip, " // 29 + "time_type, " // 30 + "canbind, " // 31 + "cancombat, " // 32 + "canlevitate, " // 33 + "castoutdoor, " // 34 + "hotzone, " // 35 + "ruleset, " // 36 + "suspendbuffs, " // 37 + "map_file_name, " // 38 + "short_name, " // 39 + "rain_chance1, " // 40 + "rain_chance2, " // 41 + "rain_chance3, " // 42 + "rain_chance4, " // 43 + "rain_duration1, " // 44 + "rain_duration2, " // 45 + "rain_duration3, " // 46 + "rain_duration4, " // 47 + "snow_chance1, " // 48 + "snow_chance2, " // 49 + "snow_chance3, " // 50 + "snow_chance4, " // 51 + "snow_duration1, " // 52 + "snow_duration2, " // 53 + "snow_duration3, " // 54 + "snow_duration4, " // 55 + "gravity, " // 56 + "fast_regen_hp, " // 57 + "fast_regen_mana, " // 58 + "fast_regen_endurance, " // 59 + "npc_max_aggro_dist, " // 60 + "max_movement_update_range, " // 61 + "underwold_teleport_index " // 62 "FROM zone WHERE zoneidnumber = %i AND version = %i %s", zoneid, instance_id, @@ -218,6 +219,7 @@ bool ZoneDatabase::GetZoneCFG( zone_data->FastRegenMana = atoi(row[58]); zone_data->FastRegenEndurance = atoi(row[59]); zone_data->NPCAggroMaxDist = atoi(row[60]); + zone_data->underworld_teleport_index = atoi(row[62]); int bindable = 0; bindable = atoi(row[31]); From cc344ac98a38a69ec6313f606e08924531737c44 Mon Sep 17 00:00:00 2001 From: thalix1337 Date: Tue, 22 Dec 2020 18:06:26 +0100 Subject: [PATCH 021/196] Fix for Feature/underworld, typo in SQL --- zone/zonedb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 5b8a08d77..b157aeba2 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -167,7 +167,7 @@ bool ZoneDatabase::GetZoneCFG( "fast_regen_endurance, " // 59 "npc_max_aggro_dist, " // 60 "max_movement_update_range, " // 61 - "underwold_teleport_index " // 62 + "underworld_teleport_index " // 62 "FROM zone WHERE zoneidnumber = %i AND version = %i %s", zoneid, instance_id, From 8a0f242fc6bc9683413fb82a51dcce89fe976e80 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Tue, 22 Dec 2020 15:45:17 -0500 Subject: [PATCH 022/196] Added RACE_BOAT_533 to IsBoat() (#1154) Co-authored-by: Noudess --- zone/mob.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index 08c4625c0..e48ca6f26 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4887,7 +4887,8 @@ bool Mob::IsBoat() const { race == RACE_SHIP_404 || race == RACE_MERCHANT_SHIP_550 || race == RACE_PIRATE_SHIP_551 || - race == RACE_GHOST_SHIP_552 + race == RACE_GHOST_SHIP_552 || + race == RACE_BOAT_533 ); } From 21a1a7bed7ab2ed4ced17577739ab7bb7b14e978 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Tue, 22 Dec 2020 19:05:02 -0500 Subject: [PATCH 023/196] Client tracks pet sit/stand - force new pets to stand rather than use client (#1155) Co-authored-by: Noudess --- zone/spell_effects.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 8c3599aca..b6c2f9cf8 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -774,6 +774,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove caster->SetPet(this); SetOwnerID(caster->GetID()); SetPetOrder(SPO_Follow); + SetAppearance(eaStanding); + // Client has saved previous pet sit/stand - make all new pets + // stand on charm. + if (caster->IsClient()) { + caster->CastToClient()->SetPetCommandState(PET_BUTTON_SIT,0); + } + SetPetType(petCharmed); if(caster->IsClient()){ From a920d449ff4a35b12624d242fb8f5eb64ab667c8 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 29 Dec 2020 19:21:47 -0500 Subject: [PATCH 024/196] Add removeitem(item_id, quantity) to Perl/Lua. (#1156) - Perl: quest::removeitem(item_id, quantity); - Lua: eq.remove_item(item_id, quantity); --- zone/embparser_api.cpp | 17 +++++++++++++++++ zone/lua_general.cpp | 10 ++++++++++ zone/questmgr.cpp | 37 +++++++++++++++++++++++++++++++++++++ zone/questmgr.h | 1 + 4 files changed, 65 insertions(+) diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 2ad425f3a..1ae4c22c1 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -2926,6 +2926,22 @@ XS(XS__countitem) { XSRETURN_IV(quantity); } +XS(XS__removeitem); +XS(XS__removeitem) { + dXSARGS; + if (items < 1 || items > 2) + Perl_croak(aTHX_ "Usage: quest::removeitem(int item_id, [int quantity = 1])"); + + uint32 item_id = (int) SvIV(ST(0)); + uint32 quantity = 1; + if (items > 1) + quantity = (int) SvIV(ST(1)); + + quest_manager.removeitem(item_id, quantity); + + XSRETURN_EMPTY; +} + XS(XS__getitemname); XS(XS__getitemname) { dXSARGS; @@ -6312,6 +6328,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "rain"), XS__rain, file); newXS(strcpy(buf, "rebind"), XS__rebind, file); newXS(strcpy(buf, "reloadzonestaticdata"), XS__reloadzonestaticdata, file); + newXS(strcpy(buf, "removeitem"), XS__removeitem, file); newXS(strcpy(buf, "removetitle"), XS__removetitle, file); newXS(strcpy(buf, "repopzone"), XS__repopzone, file); newXS(strcpy(buf, "resettaskactivity"), XS__resettaskactivity, file); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 1fb257150..ecb072369 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -806,6 +806,14 @@ int lua_count_item(uint32 item_id) { return quest_manager.countitem(item_id); } +void lua_remove_item(uint32 item_id) { + quest_manager.removeitem(item_id); +} + +void lua_remove_item(uint32 item_id, uint32 quantity) { + quest_manager.removeitem(item_id, quantity); +} + void lua_update_spawn_timer(uint32 id, uint32 new_time) { quest_manager.UpdateSpawnTimer(id, new_time); } @@ -2487,6 +2495,8 @@ luabind::scope lua_register_general() { luabind::def("modify_npc_stat", &lua_modify_npc_stat), luabind::def("collect_items", &lua_collect_items), luabind::def("count_item", &lua_count_item), + luabind::def("remove_item", (void(*)(uint32))&lua_remove_item), + luabind::def("remove_item", (void(*)(uint32,uint32))&lua_remove_item), luabind::def("update_spawn_timer", &lua_update_spawn_timer), luabind::def("merchant_set_item", (void(*)(uint32,uint32))&lua_merchant_set_item), luabind::def("merchant_set_item", (void(*)(uint32,uint32,uint32))&lua_merchant_set_item), diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 15651b1be..631bb1218 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2729,6 +2729,43 @@ int QuestManager::countitem(uint32 item_id) { return quantity; } +void QuestManager::removeitem(uint32 item_id, uint32 quantity) { + QuestManagerCurrentQuestVars(); + EQ::ItemInstance *item = nullptr; + static const int16 slots[][2] = { + { EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END }, + { EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END }, + { EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END}, + { EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END }, + { EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END }, + { EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END }, + { EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END }, + }; + int removed_count = 0; + const size_t size = sizeof(slots) / sizeof(slots[0]); + for (int slot_index = 0; slot_index < size; ++slot_index) { + for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) { + if (removed_count == quantity) + break; + + item = initiator->GetInv().GetItem(slot_id); + if (item && item->GetID() == item_id) { + int stack_size = item->IsStackable() ? item->GetCharges() : 1; + if ((removed_count + stack_size) <= quantity) { + removed_count += stack_size; + initiator->DeleteItemInInventory(slot_id, stack_size, true); + } else { + int amount_left = (quantity - removed_count); + if (amount_left > 0 && stack_size >= amount_left) { + removed_count += amount_left; + initiator->DeleteItemInInventory(slot_id, amount_left, true); + } + } + } + } + } +} + void QuestManager::UpdateSpawnTimer(uint32 id, uint32 newTime) { bool found = false; diff --git a/zone/questmgr.h b/zone/questmgr.h index 18c20907f..f3f656e51 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -227,6 +227,7 @@ public: int collectitems(uint32 item_id, bool remove); int collectitems_processSlot(int16 slot_id, uint32 item_id, bool remove); int countitem(uint32 item_id); + void removeitem(uint32 item_id, uint32 quantity = 1); std::string getitemname(uint32 item_id); void enabletitle(int titleset); bool checktitle(int titlecheck); From c1d7a82307de1fc11f08ed704e2c10f7ef257b23 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Wed, 30 Dec 2020 14:43:33 -0600 Subject: [PATCH 025/196] [DevTools] Improve DevTools Toggling Options (#1161) * Improve devtools toggling * Cleanup a few more references --- common/ruletypes.h | 1 + zone/client.cpp | 27 ++++++++++++--------------- zone/client.h | 6 +++--- zone/client_packet.cpp | 10 +++++----- zone/command.cpp | 14 +++++++------- zone/mob_info.cpp | 2 +- 6 files changed, 29 insertions(+), 31 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index dc79a35de..e46439184 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -246,6 +246,7 @@ RULE_BOOL(World, MaxClientsSimplifiedLogic, false, "New logic that only uses Exe RULE_INT (World, TellQueueSize, 20, "Maximum tell queue size") RULE_BOOL(World, StartZoneSameAsBindOnCreation, true, "Should the start zone always be the same location as your bind?") RULE_BOOL(World, EnforceCharacterLimitAtLogin, false, "Enforce the limit for characters that are online at login") +RULE_BOOL(World, EnableDevTools, true, "Enable or Disable the Developer Tools globally (Most of the time you want this enabled)") RULE_CATEGORY_END() RULE_CATEGORY(Zone) diff --git a/zone/client.cpp b/zone/client.cpp index 6528bb57b..2ed8a0fd2 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -350,8 +350,8 @@ Client::Client(EQStreamInterface* ieqs) /** * GM */ - display_mob_info_window = true; - dev_tools_window_enabled = true; + SetDisplayMobInfoWindow(true); + SetDevToolsEnabled(true); #ifdef BOTS bot_owner_options[booDeathMarquee] = false; @@ -9171,17 +9171,14 @@ void Client::SetDisplayMobInfoWindow(bool display_mob_info_window) Client::display_mob_info_window = display_mob_info_window; } -bool Client::IsDevToolsWindowEnabled() const +bool Client::IsDevToolsEnabled() const { - return dev_tools_window_enabled; + return dev_tools_enabled && RuleB(World, EnableDevTools); } -/** - * @param in_dev_tools_window_enabled - */ -void Client::SetDevToolsWindowEnabled(bool in_dev_tools_window_enabled) +void Client::SetDevToolsEnabled(bool in_dev_tools_enabled) { - Client::dev_tools_window_enabled = in_dev_tools_window_enabled; + Client::dev_tools_enabled = in_dev_tools_enabled; } /** @@ -9390,7 +9387,7 @@ void Client::ShowDevToolsMenu() std::string menu_commands_search; std::string menu_commands_show; std::string reload_commands_show; - std::string window_toggle_command; + std::string devtools_toggle; /** * Search entity commands @@ -9425,9 +9422,9 @@ void Client::ShowDevToolsMenu() /** * Show window status */ - window_toggle_command = "Disabled [" + EQ::SayLinkEngine::GenerateQuestSaylink("#devtools enable_window", false, "Enable") + "] "; - if (IsDevToolsWindowEnabled()) { - window_toggle_command = "Enabled [" + EQ::SayLinkEngine::GenerateQuestSaylink("#devtools disable_window", false, "Disable") + "] "; + devtools_toggle = "Disabled [" + EQ::SayLinkEngine::GenerateQuestSaylink("#devtools enable", false, "Enable") + "] "; + if (IsDevToolsEnabled()) { + devtools_toggle = "Enabled [" + EQ::SayLinkEngine::GenerateQuestSaylink("#devtools disable", false, "Disable") + "] "; } /** @@ -9435,8 +9432,8 @@ void Client::ShowDevToolsMenu() */ SendChatLineBreak(); Message( - Chat::White, "| [Devtools] Window %s Show this menu with %s | Current expansion [%s]", - window_toggle_command.c_str(), + Chat::White, "| [Devtools] %s Show this menu with %s | Current expansion [%s]", + devtools_toggle.c_str(), EQ::SayLinkEngine::GenerateQuestSaylink("#dev", false, "#dev").c_str(), content_service.GetCurrentExpansionName().c_str() ); diff --git a/zone/client.h b/zone/client.h index f3aa6cf01..8c2e7510a 100644 --- a/zone/client.h +++ b/zone/client.h @@ -232,8 +232,8 @@ public: void SetDisplayMobInfoWindow(bool display_mob_info_window); bool GetDisplayMobInfoWindow() const; - bool IsDevToolsWindowEnabled() const; - void SetDevToolsWindowEnabled(bool dev_tools_window_enabled); + bool IsDevToolsEnabled() const; + void SetDevToolsEnabled(bool in_dev_tools_enabled); void SetPrimaryWeaponOrnamentation(uint32 model_id); void SetSecondaryWeaponOrnamentation(uint32 model_id); @@ -1477,7 +1477,7 @@ private: uint32 tmSitting; // time stamp started sitting, used for HP regen bonus added on MAY 5, 2004 bool display_mob_info_window; - bool dev_tools_window_enabled; + bool dev_tools_enabled; int32 max_end; int32 current_endurance; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 511a94b7e..49836d032 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -920,7 +920,7 @@ void Client::CompleteConnect() entity_list.ScanCloseMobs(close_mobs, this, true); - if (GetGM()) { + if (GetGM() && IsDevToolsEnabled()) { ShowDevToolsMenu(); } @@ -1705,9 +1705,9 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) * DevTools Load Settings */ if (Admin() >= EQ::DevTools::GM_ACCOUNT_STATUS_LEVEL) { - std::string dev_tools_window_key = StringFormat("%i-dev-tools-window-disabled", AccountID()); + std::string dev_tools_window_key = StringFormat("%i-dev-tools-disabled", AccountID()); if (DataBucket::GetData(dev_tools_window_key) == "true") { - dev_tools_window_enabled = false; + dev_tools_enabled = false; } } @@ -10912,8 +10912,8 @@ void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app) break; case EQ::popupresponse::MOB_INFO_DISMISS: - this->SetDisplayMobInfoWindow(false); - this->Message(Chat::Yellow, "[DevTools] Window snoozed in this zone..."); + SetDisplayMobInfoWindow(false); + Message(Chat::Yellow, "[DevTools] Window snoozed in this zone..."); break; default: break; diff --git a/zone/command.cpp b/zone/command.cpp index 3a077b562..08ca85ddc 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -5592,18 +5592,18 @@ void command_depopzone(Client *c, const Seperator *sep) void command_devtools(Client *c, const Seperator *sep) { - std::string dev_tools_window_key = StringFormat("%i-dev-tools-window-disabled", c->AccountID()); + std::string dev_tools_key = StringFormat("%i-dev-tools-disabled", c->AccountID()); /** * Handle window toggle */ - if (strcasecmp(sep->arg[1], "disable_window") == 0) { - DataBucket::SetData(dev_tools_window_key, "true"); - c->SetDevToolsWindowEnabled(false); + if (strcasecmp(sep->arg[1], "disable") == 0) { + DataBucket::SetData(dev_tools_key, "true"); + c->SetDevToolsEnabled(false); } - if (strcasecmp(sep->arg[1], "enable_window") == 0) { - DataBucket::DeleteData(dev_tools_window_key); - c->SetDevToolsWindowEnabled(true); + if (strcasecmp(sep->arg[1], "enable") == 0) { + DataBucket::DeleteData(dev_tools_key); + c->SetDevToolsEnabled(true); } c->ShowDevToolsMenu(); diff --git a/zone/mob_info.cpp b/zone/mob_info.cpp index 95f04edec..44d6a8c88 100644 --- a/zone/mob_info.cpp +++ b/zone/mob_info.cpp @@ -640,7 +640,7 @@ void Mob::DisplayInfo(Mob *mob) Client *client = this->CastToClient(); - if (!client->IsDevToolsWindowEnabled()) { + if (!client->IsDevToolsEnabled()) { return; } From c593ed6a05157a326685b26fa5807185c6dc618b Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 30 Dec 2020 15:46:09 -0500 Subject: [PATCH 026/196] Add SetRadiantCrystals() and SetEbonCrystals() to Perl/Lua. (#1159) - Add $client->SetRadiantCrystals(value) to Perl. - Add $client->SetEbonCrystals(value) to Perl. - Add client:SetRadiantCrystals(value) to Lua. - Add client:SetEbonCrystals(value) to Lua. Co-authored-by: Chris Miles --- zone/client.cpp | 12 +++++++++++ zone/client.h | 4 ++-- zone/client_packet.cpp | 2 -- zone/command.cpp | 4 ---- zone/lua_client.cpp | 12 +++++++++++ zone/lua_client.h | 2 ++ zone/perl_client.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 74 insertions(+), 8 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 2ed8a0fd2..6338e410f 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -5745,6 +5745,18 @@ void Client::AddCrystals(uint32 Radiant, uint32 Ebon) SendCrystalCounts(); } +void Client::SetEbonCrystals(uint32 value) { + m_pp.currentEbonCrystals = value; + SaveCurrency(); + SendCrystalCounts(); +} + +void Client::SetRadiantCrystals(uint32 value) { + m_pp.currentRadCrystals = value; + SaveCurrency(); + SendCrystalCounts(); +} + // Processes a client request to inspect a SoF+ client's equipment. void Client::ProcessInspectRequest(Client* requestee, Client* requester) { if(requestee && requester) { diff --git a/zone/client.h b/zone/client.h index 8c2e7510a..13f7b81b6 100644 --- a/zone/client.h +++ b/zone/client.h @@ -594,9 +594,9 @@ public: uint32 GetPVPPoints() { return m_pp.PVPCurrentPoints; } void AddPVPPoints(uint32 Points); uint32 GetRadiantCrystals() { return m_pp.currentRadCrystals; } - void SetRadiantCrystals(uint32 Crystals) { m_pp.currentRadCrystals = Crystals; } + void SetRadiantCrystals(uint32 value); uint32 GetEbonCrystals() { return m_pp.currentEbonCrystals; } - void SetEbonCrystals(uint32 Crystals) { m_pp.currentEbonCrystals = Crystals; } + void SetEbonCrystals(uint32 value); void AddCrystals(uint32 Radiant, uint32 Ebon); void SendCrystalCounts(); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 49836d032..ed1ec0bdb 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -2025,12 +2025,10 @@ void Client::Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app) else if (aps->Type == NorrathsKeepersMerchant) { SetRadiantCrystals(GetRadiantCrystals() - (int32)item->LDoNPrice); - SendCrystalCounts(); } else if (aps->Type == DarkReignMerchant) { SetEbonCrystals(GetEbonCrystals() - (int32)item->LDoNPrice); - SendCrystalCounts(); } int16 charges = 1; if (item->MaxCharges != 0) diff --git a/zone/command.cpp b/zone/command.cpp index 08ca85ddc..d56325725 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -7855,14 +7855,10 @@ void command_setcrystals(Client *c, const Seperator *sep) else if(!strcasecmp(sep->arg[1], "radiant")) { t->SetRadiantCrystals(atoi(sep->arg[2])); - t->SendCrystalCounts(); - t->SaveCurrency(); } else if(!strcasecmp(sep->arg[1], "ebon")) { t->SetEbonCrystals(atoi(sep->arg[2])); - t->SendCrystalCounts(); - t->SaveCurrency(); } else { diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 086c90269..9309c7cbb 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1040,6 +1040,16 @@ void Lua_Client::AddCrystals(uint32 radiant, uint32 ebon) { self->AddCrystals(radiant, ebon); } +void Lua_Client::SetEbonCrystals(uint32 value) { + Lua_Safe_Call_Void(); + self->SetEbonCrystals(value); +} + +void Lua_Client::SetRadiantCrystals(uint32 value) { + Lua_Safe_Call_Void(); + self->SetRadiantCrystals(value); +} + uint32 Lua_Client::GetPVPPoints() { Lua_Safe_Call_Int(); return self->GetPVPPoints(); @@ -1846,6 +1856,8 @@ luabind::scope lua_register_client() { .def("KeyRingCheck", (bool(Lua_Client::*)(uint32))&Lua_Client::KeyRingCheck) .def("AddPVPPoints", (void(Lua_Client::*)(uint32))&Lua_Client::AddPVPPoints) .def("AddCrystals", (void(Lua_Client::*)(uint32,uint32))&Lua_Client::AddCrystals) + .def("SetEbonCrystals", (void(Lua_Client::*)(uint32))&Lua_Client::SetEbonCrystals) + .def("SetRadiantCrystals", (void(Lua_Client::*)(uint32))&Lua_Client::SetRadiantCrystals) .def("GetPVPPoints", (uint32(Lua_Client::*)(void))&Lua_Client::GetPVPPoints) .def("GetRadiantCrystals", (uint32(Lua_Client::*)(void))&Lua_Client::GetRadiantCrystals) .def("GetEbonCrystals", (uint32(Lua_Client::*)(void))&Lua_Client::GetEbonCrystals) diff --git a/zone/lua_client.h b/zone/lua_client.h index 96b1b1c79..2f5de4a59 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -235,6 +235,8 @@ public: bool KeyRingCheck(uint32 item); void AddPVPPoints(uint32 points); void AddCrystals(uint32 radiant, uint32 ebon); + void SetEbonCrystals(uint32 value); + void SetRadiantCrystals(uint32 value); uint32 GetPVPPoints(); uint32 GetRadiantCrystals(); uint32 GetEbonCrystals(); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index ba3043fc5..82cf3b730 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -4707,6 +4707,50 @@ XS(XS_Client_AddCrystals) { XSRETURN_EMPTY; } +XS(XS_Client_SetEbonCrystals); +XS(XS_Client_SetEbonCrystals) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::SetEbonCrystals(THIS, uint32 value)"); + { + Client *THIS; + uint32 value = (uint32) SvUV(ST(1)); + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV *) SvRV(ST(0))); + THIS = INT2PTR(Client *, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Client"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetEbonCrystals(value); + } + XSRETURN_EMPTY; +} + +XS(XS_Client_SetRadiantCrystals); +XS(XS_Client_SetRadiantCrystals) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::SetRadiantCrystals(THIS, uint32 value)"); + { + Client *THIS; + uint32 value = (uint32) SvUV(ST(1)); + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV *) SvRV(ST(0))); + THIS = INT2PTR(Client *, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Client"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + THIS->SetRadiantCrystals(value); + } + XSRETURN_EMPTY; +} + XS(XS_Client_GetPVPPoints); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_GetPVPPoints) { dXSARGS; @@ -6942,6 +6986,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "SetDeity"), XS_Client_SetDeity, file, "$$"); newXSproto(strcpy(buf, "SetDueling"), XS_Client_SetDueling, file, "$$"); newXSproto(strcpy(buf, "SetDuelTarget"), XS_Client_SetDuelTarget, file, "$$"); + newXSproto(strcpy(buf, "SetEbonCrystals"), XS_Client_SetEbonCrystals, file, "$$"); newXSproto(strcpy(buf, "SetEndurance"), XS_Client_SetEndurance, file, "$$"); newXSproto(strcpy(buf, "SetEXP"), XS_Client_SetEXP, file, "$$$;$"); newXSproto(strcpy(buf, "SetFactionLevel"), XS_Client_SetFactionLevel, file, "$$$$$$"); @@ -6954,6 +6999,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "SetMaterial"), XS_Client_SetMaterial, file, "$$$"); newXSproto(strcpy(buf, "SetPrimaryWeaponOrnamentation"), XS_Client_SetPrimaryWeaponOrnamentation, file, "$$"); newXSproto(strcpy(buf, "SetPVP"), XS_Client_SetPVP, file, "$$"); + newXSproto(strcpy(buf, "SetRadiantCrystals"), XS_Client_SetRadiantCrystals, file, "$$"); newXSproto(strcpy(buf, "SetSecondaryWeaponOrnamentation"), XS_Client_SetSecondaryWeaponOrnamentation, file, "$$"); newXSproto(strcpy(buf, "SetSkill"), XS_Client_SetSkill, file, "$$$"); newXSproto(strcpy(buf, "SetSkillPoints"), XS_Client_SetSkillPoints, file, "$$"); From 51a74c23ef2e960df0adacf6a4056556edaebba8 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 30 Dec 2020 15:47:27 -0500 Subject: [PATCH 027/196] quest::getspellname() and quest::getclassname() fixes/additions. (#1158) - Update GetSpellName() to uint16. - Add more classes to GetClassIDName(). --- common/classes.cpp | 14 ++++++++++++++ common/classes.h | 1 + common/spdat.cpp | 2 +- common/spdat.h | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/common/classes.cpp b/common/classes.cpp index 3aaeeded3..b9445443f 100644 --- a/common/classes.cpp +++ b/common/classes.cpp @@ -378,6 +378,8 @@ const char *GetClassIDName(uint8 class_id, uint8 level) return "Berserker Guildmaster"; case MERCHANT: return "Merchant"; + case DISCORD_MERCHANT: + return "Discord Merchant"; case ADVENTURERECRUITER: return "Adventure Recruiter"; case ADVENTUREMERCHANT: @@ -388,6 +390,18 @@ const char *GetClassIDName(uint8 class_id, uint8 level) return "Tribute Master"; case GUILD_TRIBUTE_MASTER: return "Guild Tribute Master"; + case GUILD_BANKER: + return "Guild Banker"; + case NORRATHS_KEEPERS_MERCHANT: + return "Radiant Crystal Merchant"; + case DARK_REIGN_MERCHANT: + return "Ebon Crystal Merchant"; + case FELLOWSHIP_MASTER: + return "Fellowship Master"; + case ALT_CURRENCY_MERCHANT: + return "Alternate Currency Merchant"; + case MERCERNARY_MASTER: + return "Mercenary Liaison"; default: return "Unknown"; } diff --git a/common/classes.h b/common/classes.h index f63758937..a35014d94 100644 --- a/common/classes.h +++ b/common/classes.h @@ -61,6 +61,7 @@ #define CORPSE_CLASS 62 // only seen on Danvi's Corpse in Akheva so far.. #define TRIBUTE_MASTER 63 #define GUILD_TRIBUTE_MASTER 64 // not sure +#define GUILD_BANKER 66 #define NORRATHS_KEEPERS_MERCHANT 67 #define DARK_REIGN_MERCHANT 68 #define FELLOWSHIP_MASTER 69 diff --git a/common/spdat.cpp b/common/spdat.cpp index 3f0ae6e41..fe6e4db9c 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -1271,7 +1271,7 @@ bool IsSpellUsableThisZoneType(uint16 spell_id, uint8 zone_type) return false; } -const char* GetSpellName(int16 spell_id) +const char* GetSpellName(uint16 spell_id) { return spells[spell_id].name; } diff --git a/common/spdat.h b/common/spdat.h index 811825757..d6d6ef144 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -981,6 +981,6 @@ uint32 GetNimbusEffect(uint16 spell_id); int32 GetFuriousBash(uint16 spell_id); bool IsShortDurationBuff(uint16 spell_id); bool IsSpellUsableThisZoneType(uint16 spell_id, uint8 zone_type); -const char *GetSpellName(int16 spell_id); +const char *GetSpellName(uint16 spell_id); #endif From a77f8b582ea64076811340686c46642f377c6eac Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 21 Feb 2020 19:35:54 -0500 Subject: [PATCH 028/196] Update opcodes and packet structs for expeditions Add and rename some opcodes Add packet translations for expeditions Fix OP_DzExpeditionEndsWarning opcode for RoF2 Add RoF2 OP_KickPlayers --- common/emu_oplist.h | 13 +- common/eq_packet_structs.h | 164 ++++++++++++++++---- common/patches/rof.cpp | 247 ++++++++++++++++++++--------- common/patches/rof2.cpp | 247 ++++++++++++++++++++--------- common/patches/rof2_ops.h | 15 +- common/patches/rof2_structs.h | 171 ++++++++++++++++---- common/patches/rof_ops.h | 15 +- common/patches/rof_structs.h | 161 +++++++++++++++---- common/patches/sod.cpp | 248 +++++++++++++++++++++--------- common/patches/sod_ops.h | 15 +- common/patches/sod_structs.h | 162 +++++++++++++++---- common/patches/sof.cpp | 248 +++++++++++++++++++++--------- common/patches/sof_ops.h | 15 +- common/patches/sof_structs.h | 141 +++++++++++++++-- common/patches/titanium.cpp | 245 ++++++++++++++++++++--------- common/patches/titanium_ops.h | 15 +- common/patches/titanium_structs.h | 141 +++++++++++++++-- common/patches/uf.cpp | 248 +++++++++++++++++++++--------- common/patches/uf_ops.h | 15 +- common/patches/uf_structs.h | 162 +++++++++++++++---- utils/patches/patch_RoF.conf | 14 +- utils/patches/patch_RoF2.conf | 19 ++- utils/patches/patch_SoD.conf | 15 +- utils/patches/patch_SoF.conf | 14 +- utils/patches/patch_Titanium.conf | 15 +- utils/patches/patch_UF.conf | 14 +- zone/client.cpp | 4 +- zone/lua_packet.cpp | 12 +- 28 files changed, 2132 insertions(+), 663 deletions(-) diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 5078cca68..46fd564e6 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -136,20 +136,22 @@ N(OP_Dye), N(OP_DynamicWall), N(OP_DzAddPlayer), N(OP_DzChooseZone), +N(OP_DzChooseZoneReply), N(OP_DzCompass), N(OP_DzExpeditionEndsWarning), N(OP_DzExpeditionInfo), -N(OP_DzExpeditionList), -N(OP_DzJoinExpeditionConfirm), -N(OP_DzJoinExpeditionReply), -N(OP_DzLeaderStatus), +N(OP_DzExpeditionInvite), +N(OP_DzExpeditionInviteResponse), +N(OP_DzExpeditionLockoutTimers), N(OP_DzListTimers), N(OP_DzMakeLeader), N(OP_DzMemberList), -N(OP_DzMemberStatus), +N(OP_DzMemberListName), +N(OP_DzMemberListStatus), N(OP_DzPlayerList), N(OP_DzQuit), N(OP_DzRemovePlayer), +N(OP_DzSetLeaderName), N(OP_DzSwapPlayer), N(OP_Emote), N(OP_EndLootRequest), @@ -271,6 +273,7 @@ N(OP_ItemVerifyRequest), N(OP_ItemViewUnknown), N(OP_Jump), N(OP_KeyRing), +N(OP_KickPlayers), N(OP_KnowledgeBase), N(OP_LDoNButton), N(OP_LDoNDisarmTraps), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index c22069196..da589abbf 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -4835,17 +4835,98 @@ struct BuffIcon_Struct BuffIconEntry_Struct entries[0]; }; -struct ExpeditionInfo_Struct +struct ExpeditionInvite_Struct { -/*000*/ uint32 max_players; -/*004*/ char expedition_name[128]; -/*132*/ char leader_name[64]; +/*000*/ uint32 client_id; // unique character id +/*004*/ uint32 unknown004; // added after titanium +/*008*/ char inviter_name[64]; +/*072*/ char expedition_name[128]; +/*200*/ uint8 swapping; // 0: adding 1: swapping +/*201*/ char swap_name[64]; // if swapping, swap name being removed +/*265*/ uint8 padding[3]; +/*268*/ uint16 dz_zone_id; // dz_id zone/instance pair, sent back in reply +/*270*/ uint16 dz_instance_id; }; -struct ExpeditionJoinPrompt_Struct +struct ExpeditionInviteResponse_Struct { -/*000*/ char player_name[64]; -/*064*/ char expedition_name[64]; +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; // added after titanium +/*008*/ uint16 dz_zone_id; // dz_id pair sent in invite +/*010*/ uint16 dz_instance_id; +/*012*/ uint8 accepted; // 0: declined 1: accepted +/*013*/ uint8 swapping; // 0: adding 1: swapping (sent in invite) +/*014*/ char swap_name[64]; // swap name sent in invite +/*078*/ uint8 unknown078; // padding garbage? +/*079*/ uint8 unknown079; // padding garbage? +}; + +struct ExpeditionInfo_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; // added after titanium +/*008*/ uint32 assigned; // padded bool, 0: not in expedition (clear data), 1: in expedition +/*012*/ uint32 max_players; +/*016*/ char expedition_name[128]; +/*144*/ char leader_name[64]; +}; + +struct ExpeditionMemberEntry_Struct +{ +/*000*/ char name[64]; // variable length, null terminated, max 0x40 (64) +/*064*/ uint8 status; // 0: unknown, 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +}; + +struct ExpeditionMemberList_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +}; + +struct ExpeditionMemberListName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status +/*012*/ char name[64]; +}; + +struct ExpeditionLockoutTimerEntry_Struct +{ +/*000*/ char expedition_name[128]; // variable length, null terminated, max 0x80 (128) +/*000*/ uint32 seconds_remaining; +/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ char event_name[256]; // variable length, null terminated, max 0x100 (256) +}; + +struct ExpeditionLockoutTimers_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; +}; + +struct ExpeditionSetLeaderName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ char leader_name[64]; +}; + +struct ExpeditionCommand_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char name[64]; +}; + +struct ExpeditionCommandSwap_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char add_player_name[64]; // swap to (player must confirm) +/*072*/ char rem_player_name[64]; // swap from }; struct ExpeditionExpireWarning @@ -4853,48 +4934,67 @@ struct ExpeditionExpireWarning /*008*/ uint32 minutes_remaining; }; -struct ExpeditionCompassEntry_Struct +struct DynamicZoneCompassEntry_Struct { -/*000*/ uint32 enabled; //guess -/*004*/ float y; -/*008*/ float x; -/*012*/ float z; +/*000*/ uint16 dz_zone_id; // target dz id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 dz_type; // 1: Expedition, 2: Tutorial (purple), 3: Task, 4: Mission, 5: Quest (green) +/*008*/ uint32 unknown008; +/*012*/ float y; +/*016*/ float x; +/*020*/ float z; }; -struct ExpeditionCompass_Struct +struct DynamicZoneCompass_Struct { +/*000*/ uint32 client_id; /*000*/ uint32 count; -/*004*/ ExpeditionCompassEntry_Struct entries[0]; +/*004*/ DynamicZoneCompassEntry_Struct entries[0]; }; -struct ExpeditionMemberEntry_Struct +struct DynamicZoneChooseZoneEntry_Struct { - char name[64]; - char status; +/*000*/ uint16 dz_zone_id; // dz_id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 unknown_id1; // seen 28 00 00 00 (40), sent back in reply +/*008*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest -- sent back in reply +/*012*/ uint32 unknown_id2; // possibly an id based on dz type, for expeditions this was same as dz_id (zone|instance) but task dz was different +/*016*/ char description[128]; // variable length, null terminated +/*144*/ char leader_name[64]; // variable length, null terminated }; -struct ExpeditionMemberList_Struct +struct DynamicZoneChooseZone_Struct { -/*000*/ uint32 count; -/*004*/ ExpeditionMemberEntry_Struct entries[0]; +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ DynamicZoneChooseZoneEntry_Struct choices[0]; }; -struct ExpeditionLockoutEntry_Struct +struct DynamicZoneChooseZoneReply_Struct { -/*000*/ uint32 time_left; -/*004*/ char expedition[128]; -/*132*/ char expedition_event[128]; +/*000*/ uint32 unknown000; // ff ff ff ff +/*004*/ uint32 unknown004; // seen 69 00 00 00 +/*008*/ uint32 unknown008; // ff ff ff ff +/*012*/ uint32 unknown_id1; // from choose zone entry message +/*016*/ uint16 dz_zone_id; // dz_id pair +/*018*/ uint16 dz_instance_id; +/*020*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest +/*024*/ uint32 unknown_id2; // from choose zone entry message +/*028*/ uint32 unknown028; // 00 00 00 00 +/*032*/ uint32 unknown032; // always same as unknown044 +/*036*/ uint32 unknown036; +/*040*/ uint32 unknown040; +/*044*/ uint32 unknown044; // always same as unknown032 +/*048*/ uint32 unknown048; // seen 01 00 00 00 and 02 00 00 00 }; -struct ExpeditionLockoutList_Struct +struct KickPlayers_Struct { -/*000*/ uint32 count; -/*004*/ ExpeditionLockoutEntry_Struct entries[0]; -}; - -struct ExpeditionLeaderSet_Struct -{ -/*000*/ char leader_name[64]; +/*000*/ char char_name[64]; +/*064*/ uint32 unknown064; // always 0 +/*068*/ uint8 kick_expedition; // true if /kickplayers exp +/*069*/ uint8 kick_task; // true if /kickplayers task +/*070*/ uint8 padding[2]; }; struct CorpseDrag_Struct diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 8811c8746..6d7d0021b 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -710,15 +710,48 @@ namespace RoF FINISH_ENCODE(); } + ENCODE(OP_DzChooseZone) + { + SETUP_VAR_ENCODE(DynamicZoneChooseZone_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); + + for (uint32 i = 0; i < emu->count; ++i) + { + buf.WriteUInt16(emu->choices[i].dz_zone_id); + buf.WriteUInt16(emu->choices[i].dz_instance_id); + buf.WriteUInt32(emu->choices[i].unknown_id1); + buf.WriteUInt32(emu->choices[i].dz_type); + buf.WriteUInt32(emu->choices[i].unknown_id2); + buf.WriteString(emu->choices[i].description); + buf.WriteString(emu->choices[i].leader_name); + } + + __packet->size = buf.size(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); + + FINISH_ENCODE(); + } + ENCODE(OP_DzCompass) { - SETUP_VAR_ENCODE(ExpeditionCompass_Struct); - ALLOC_VAR_ENCODE(structs::ExpeditionCompass_Struct, sizeof(structs::ExpeditionInfo_Struct) + sizeof(structs::ExpeditionCompassEntry_Struct) * emu->count); + SETUP_VAR_ENCODE(DynamicZoneCompass_Struct); + ALLOC_VAR_ENCODE(structs::DynamicZoneCompass_Struct, + sizeof(structs::DynamicZoneCompass_Struct) + + sizeof(structs::DynamicZoneCompassEntry_Struct) * emu->count + ); + OUT(client_id); OUT(count); for (uint32 i = 0; i < emu->count; ++i) { + OUT(entries[i].dz_zone_id); + OUT(entries[i].dz_instance_id); + OUT(entries[i].dz_type); OUT(entries[i].x); OUT(entries[i].y); OUT(entries[i].z); @@ -742,81 +775,60 @@ namespace RoF ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + OUT(client_id); + OUT(assigned); OUT(max_players); - eq->unknown004 = 785316192; - eq->unknown008 = 435601; - strncpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); - strncpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } - ENCODE(OP_DzExpeditionList) + ENCODE(OP_DzExpeditionInvite) { - SETUP_VAR_ENCODE(ExpeditionLockoutList_Struct); + ENCODE_LENGTH_EXACT(ExpeditionInvite_Struct); + SETUP_DIRECT_ENCODE(ExpeditionInvite_Struct, structs::ExpeditionInvite_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + OUT(client_id); + strn0cpy(eq->inviter_name, emu->inviter_name, sizeof(eq->inviter_name)); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + OUT(swapping); + strn0cpy(eq->swap_name, emu->swap_name, sizeof(eq->swap_name)); + OUT(dz_zone_id); + OUT(dz_instance_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionLockoutTimers) + { + SETUP_VAR_ENCODE(ExpeditionLockoutTimers_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].expedition, strlen(emu->entries[i].expedition)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].time_left, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->entries[i].expedition_event, strlen(emu->entries[i].expedition_event)); - ss.write((const char*)&null_term, sizeof(char)); + buf.WriteString(emu->timers[i].expedition_name); + buf.WriteUInt32(emu->timers[i].seconds_remaining); + buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteString(emu->timers[i].event_name); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } - ENCODE(OP_DzJoinExpeditionConfirm) + ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionJoinPrompt_Struct); - SETUP_DIRECT_ENCODE(ExpeditionJoinPrompt_Struct, structs::ExpeditionJoinPrompt_Struct); + ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); - strncpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); - strncpy(eq->player_name, emu->player_name, sizeof(eq->player_name)); - - FINISH_ENCODE(); - } - - ENCODE(OP_DzLeaderStatus) - { - SETUP_VAR_ENCODE(ExpeditionLeaderSet_Struct); - - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->leader_name, strlen(emu->leader_name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//0xffffffff - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//1 - ss.write((const char*)&client_id, sizeof(uint32)); - - __packet->size = ss.str().length(); - __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + OUT(client_id); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } @@ -825,26 +837,43 @@ namespace RoF { SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].name, strlen(emu->entries[i].name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].status, sizeof(char)); + buf.WriteString(emu->members[i].name); + buf.WriteUInt8(emu->members[i].status); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } + ENCODE(OP_DzMemberListName) + { + ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + + OUT(client_id); + OUT(add_name); + strn0cpy(eq->name, emu->name, sizeof(eq->name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzMemberListStatus) + { + auto emu = reinterpret_cast((*p)->pBuffer); + if (emu->count == 1) + { + ENCODE_FORWARD(OP_DzMemberList); + } + } + ENCODE(OP_Emote) { EQApplicationPacket *in = *p; @@ -4388,6 +4417,84 @@ namespace RoF FINISH_DIRECT_DECODE(); } + DECODE(OP_DzAddPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzChooseZoneReply) + { + DECODE_LENGTH_EXACT(structs::DynamicZoneChooseZoneReply_Struct); + SETUP_DIRECT_DECODE(DynamicZoneChooseZoneReply_Struct, structs::DynamicZoneChooseZoneReply_Struct); + + IN(unknown000); + IN(unknown004); + IN(unknown008); + IN(unknown_id1); + IN(dz_zone_id); + IN(dz_instance_id); + IN(dz_type); + IN(unknown_id2); + IN(unknown028); + IN(unknown032); + IN(unknown036); + IN(unknown040); + IN(unknown044); + IN(unknown048); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzExpeditionInviteResponse) + { + DECODE_LENGTH_EXACT(structs::ExpeditionInviteResponse_Struct); + SETUP_DIRECT_DECODE(ExpeditionInviteResponse_Struct, structs::ExpeditionInviteResponse_Struct); + + IN(dz_zone_id); + IN(dz_instance_id); + IN(accepted); + IN(swapping); + strn0cpy(emu->swap_name, eq->swap_name, sizeof(emu->swap_name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzMakeLeader) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzRemovePlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzSwapPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommandSwap_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommandSwap_Struct, structs::ExpeditionCommandSwap_Struct); + + strn0cpy(emu->add_player_name, eq->add_player_name, sizeof(emu->add_player_name)); + strn0cpy(emu->rem_player_name, eq->rem_player_name, sizeof(emu->rem_player_name)); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Emote) { unsigned char *__eq_buffer = __packet->pBuffer; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 09a2d502d..671d539ed 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -759,15 +759,48 @@ namespace RoF2 FINISH_ENCODE(); } + ENCODE(OP_DzChooseZone) + { + SETUP_VAR_ENCODE(DynamicZoneChooseZone_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); + + for (uint32 i = 0; i < emu->count; ++i) + { + buf.WriteUInt16(emu->choices[i].dz_zone_id); + buf.WriteUInt16(emu->choices[i].dz_instance_id); + buf.WriteUInt32(emu->choices[i].unknown_id1); + buf.WriteUInt32(emu->choices[i].dz_type); + buf.WriteUInt32(emu->choices[i].unknown_id2); + buf.WriteString(emu->choices[i].description); + buf.WriteString(emu->choices[i].leader_name); + } + + __packet->size = buf.size(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); + + FINISH_ENCODE(); + } + ENCODE(OP_DzCompass) { - SETUP_VAR_ENCODE(ExpeditionCompass_Struct); - ALLOC_VAR_ENCODE(structs::ExpeditionCompass_Struct, sizeof(structs::ExpeditionInfo_Struct) + sizeof(structs::ExpeditionCompassEntry_Struct) * emu->count); + SETUP_VAR_ENCODE(DynamicZoneCompass_Struct); + ALLOC_VAR_ENCODE(structs::DynamicZoneCompass_Struct, + sizeof(structs::DynamicZoneCompass_Struct) + + sizeof(structs::DynamicZoneCompassEntry_Struct) * emu->count + ); + OUT(client_id); OUT(count); for (uint32 i = 0; i < emu->count; ++i) { + OUT(entries[i].dz_zone_id); + OUT(entries[i].dz_instance_id); + OUT(entries[i].dz_type); OUT(entries[i].x); OUT(entries[i].y); OUT(entries[i].z); @@ -791,81 +824,60 @@ namespace RoF2 ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + OUT(client_id); + OUT(assigned); OUT(max_players); - eq->unknown004 = 785316192; - eq->unknown008 = 435601; - strncpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); - strncpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } - ENCODE(OP_DzExpeditionList) + ENCODE(OP_DzExpeditionInvite) { - SETUP_VAR_ENCODE(ExpeditionLockoutList_Struct); + ENCODE_LENGTH_EXACT(ExpeditionInvite_Struct); + SETUP_DIRECT_ENCODE(ExpeditionInvite_Struct, structs::ExpeditionInvite_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + OUT(client_id); + strn0cpy(eq->inviter_name, emu->inviter_name, sizeof(eq->inviter_name)); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + OUT(swapping); + strn0cpy(eq->swap_name, emu->swap_name, sizeof(eq->swap_name)); + OUT(dz_zone_id); + OUT(dz_instance_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionLockoutTimers) + { + SETUP_VAR_ENCODE(ExpeditionLockoutTimers_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].expedition, strlen(emu->entries[i].expedition)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].time_left, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->entries[i].expedition_event, strlen(emu->entries[i].expedition_event)); - ss.write((const char*)&null_term, sizeof(char)); + buf.WriteString(emu->timers[i].expedition_name); + buf.WriteUInt32(emu->timers[i].seconds_remaining); + buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteString(emu->timers[i].event_name); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } - ENCODE(OP_DzJoinExpeditionConfirm) + ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionJoinPrompt_Struct); - SETUP_DIRECT_ENCODE(ExpeditionJoinPrompt_Struct, structs::ExpeditionJoinPrompt_Struct); + ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); - strncpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); - strncpy(eq->player_name, emu->player_name, sizeof(eq->player_name)); - - FINISH_ENCODE(); - } - - ENCODE(OP_DzLeaderStatus) - { - SETUP_VAR_ENCODE(ExpeditionLeaderSet_Struct); - - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->leader_name, strlen(emu->leader_name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//0xffffffff - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//1 - ss.write((const char*)&client_id, sizeof(uint32)); - - __packet->size = ss.str().length(); - __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + OUT(client_id); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } @@ -874,26 +886,43 @@ namespace RoF2 { SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].name, strlen(emu->entries[i].name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].status, sizeof(char)); + buf.WriteString(emu->members[i].name); + buf.WriteUInt8(emu->members[i].status); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } + ENCODE(OP_DzMemberListName) + { + ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + + OUT(client_id); + OUT(add_name); + strn0cpy(eq->name, emu->name, sizeof(eq->name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzMemberListStatus) + { + auto emu = reinterpret_cast((*p)->pBuffer); + if (emu->count == 1) + { + ENCODE_FORWARD(OP_DzMemberList); + } + } + ENCODE(OP_Emote) { EQApplicationPacket *in = *p; @@ -4585,6 +4614,84 @@ namespace RoF2 FINISH_DIRECT_DECODE(); } + DECODE(OP_DzAddPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzChooseZoneReply) + { + DECODE_LENGTH_EXACT(structs::DynamicZoneChooseZoneReply_Struct); + SETUP_DIRECT_DECODE(DynamicZoneChooseZoneReply_Struct, structs::DynamicZoneChooseZoneReply_Struct); + + IN(unknown000); + IN(unknown004); + IN(unknown008); + IN(unknown_id1); + IN(dz_zone_id); + IN(dz_instance_id); + IN(dz_type); + IN(unknown_id2); + IN(unknown028); + IN(unknown032); + IN(unknown036); + IN(unknown040); + IN(unknown044); + IN(unknown048); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzExpeditionInviteResponse) + { + DECODE_LENGTH_EXACT(structs::ExpeditionInviteResponse_Struct); + SETUP_DIRECT_DECODE(ExpeditionInviteResponse_Struct, structs::ExpeditionInviteResponse_Struct); + + IN(dz_zone_id); + IN(dz_instance_id); + IN(accepted); + IN(swapping); + strn0cpy(emu->swap_name, eq->swap_name, sizeof(emu->swap_name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzMakeLeader) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzRemovePlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzSwapPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommandSwap_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommandSwap_Struct, structs::ExpeditionCommandSwap_Struct); + + strn0cpy(emu->add_player_name, eq->add_player_name, sizeof(emu->add_player_name)); + strn0cpy(emu->rem_player_name, eq->rem_player_name, sizeof(emu->rem_player_name)); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Emote) { unsigned char *__eq_buffer = __packet->pBuffer; diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 32798fef5..22b6e6662 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -58,13 +58,16 @@ E(OP_DeleteCharge) E(OP_DeleteItem) E(OP_DeleteSpawn) E(OP_DisciplineUpdate) +E(OP_DzChooseZone) E(OP_DzCompass) E(OP_DzExpeditionEndsWarning) E(OP_DzExpeditionInfo) -E(OP_DzExpeditionList) -E(OP_DzJoinExpeditionConfirm) -E(OP_DzLeaderStatus) +E(OP_DzExpeditionInvite) +E(OP_DzExpeditionLockoutTimers) E(OP_DzMemberList) +E(OP_DzMemberListName) +E(OP_DzMemberListStatus) +E(OP_DzSetLeaderName) E(OP_Emote) E(OP_ExpansionInfo) E(OP_FormattedMessage) @@ -159,6 +162,12 @@ D(OP_ConsiderCorpse) D(OP_Consume) D(OP_Damage) D(OP_DeleteItem) +D(OP_DzAddPlayer) +D(OP_DzChooseZoneReply) +D(OP_DzExpeditionInviteResponse) +D(OP_DzMakeLeader) +D(OP_DzRemovePlayer) +D(OP_DzSwapPlayer) D(OP_Emote) D(OP_EnvDamage) D(OP_FaceChange) diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 96e74187d..5e2b99ba2 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -4882,52 +4882,169 @@ struct VeteranClaim /*076*/ uint32 action; }; -struct ExpeditionEntryHeader_Struct +struct ExpeditionInvite_Struct +{ +/*000*/ uint32 client_id; // unique character id +/*004*/ uint32 unknown004; +/*008*/ char inviter_name[64]; +/*072*/ char expedition_name[128]; +/*200*/ uint8 swapping; // 0: adding 1: swapping +/*201*/ char swap_name[64]; // if swapping, swap name being removed +/*265*/ uint8 padding[3]; +/*268*/ uint16 dz_zone_id; // dz_id zone/instance pair, sent back in reply +/*270*/ uint16 dz_instance_id; +}; + +struct ExpeditionInviteResponse_Struct { /*000*/ uint32 unknown000; -/*000*/ uint32 number_of_entries; -}; - -struct ExpeditionJoinPrompt_Struct -{ -/*000*/ uint32 clientid; /*004*/ uint32 unknown004; -/*008*/ char player_name[64]; -/*072*/ char expedition_name[64]; -}; - -struct ExpeditionExpireWarning -{ -/*000*/ uint32 clientid; -/*004*/ uint32 unknown004; -/*008*/ uint32 minutes_remaining; +/*008*/ uint16 dz_zone_id; // dz_id pair sent in invite +/*010*/ uint16 dz_instance_id; +/*012*/ uint8 accepted; // 0: declined 1: accepted +/*013*/ uint8 swapping; // 0: adding 1: swapping (sent in invite) +/*014*/ char swap_name[64]; // swap name sent in invite +/*078*/ uint8 unknown078; // padding garbage? +/*079*/ uint8 unknown079; // padding garbage? }; struct ExpeditionInfo_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 unknown004; -/*008*/ uint32 unknown008; +/*008*/ uint32 assigned; // padded bool, 0: not in expedition (clear data), 1: in expedition /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; -/*142*/ char leader_name[64]; +/*016*/ char expedition_name[128]; +/*144*/ char leader_name[64]; +//*208*/ uint32 unknown208; // live sends 01 00 00 00 here but client doesn't read it }; -struct ExpeditionCompassEntry_Struct +struct ExpeditionMemberEntry_Struct { -/*000*/ float unknown000; //seen *((uint32*)) = 1584791871 -/*004*/ uint32 enabled; //guess -/*008*/ uint32 unknown008; //seen 1019 +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +}; + +struct ExpeditionMemberList_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; // number of players in window +/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +}; + +struct ExpeditionMemberListName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status +/*012*/ char name[64]; +}; + +struct ExpeditionLockoutTimerEntry_Struct +{ +/*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ uint32 seconds_remaining; +/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) +}; + +struct ExpeditionLockoutTimers_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; +}; + +struct ExpeditionSetLeaderName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ char leader_name[64]; +}; + +struct ExpeditionCommand_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char name[64]; +}; + +struct ExpeditionCommandSwap_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char add_player_name[64]; // swap to (player must confirm) +/*072*/ char rem_player_name[64]; // swap from +}; + +struct ExpeditionExpireWarning +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 minutes_remaining; +}; + +struct DynamicZoneCompassEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // target dz id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 dz_type; // 1: Expedition, 2: Tutorial (purple), 3: Task, 4: Mission, 5: Quest (green) +/*008*/ uint32 unknown008; /*012*/ float y; /*016*/ float x; /*020*/ float z; }; -struct ExpeditionCompass_Struct +struct DynamicZoneCompass_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 count; -/*008*/ ExpeditionCompassEntry_Struct entries[0]; +/*008*/ DynamicZoneCompassEntry_Struct entries[0]; +}; + +struct DynamicZoneChooseZoneEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // dz_id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 unknown_id1; // seen 28 00 00 00 (40), sent back in reply +/*008*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest -- sent back in reply +/*012*/ uint32 unknown_id2; // possibly an id based on dz type, for expeditions this was same as dz_id (zone|instance) but task dz was different +/*016*/ char description[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ char leader_name[1]; // variable length, null terminated, max 0x40 (64) +}; + +struct DynamicZoneChooseZone_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ DynamicZoneChooseZoneEntry_Struct choices[0]; +}; + +struct DynamicZoneChooseZoneReply_Struct +{ +/*000*/ uint32 unknown000; // ff ff ff ff +/*004*/ uint32 unknown004; // seen 69 00 00 00 +/*008*/ uint32 unknown008; // ff ff ff ff +/*012*/ uint32 unknown_id1; // from choose zone entry message +/*016*/ uint16 dz_zone_id; // dz_id pair +/*018*/ uint16 dz_instance_id; +/*020*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest +/*024*/ uint32 unknown_id2; // from choose zone entry message +/*028*/ uint32 unknown028; // 00 00 00 00 +/*032*/ uint32 unknown032; // always same as unknown044 +/*036*/ uint32 unknown036; +/*040*/ uint32 unknown040; +/*044*/ uint32 unknown044; // always same as unknown032 +/*048*/ uint32 unknown048; // seen 01 00 00 00 and 02 00 00 00 +}; + +struct KickPlayers_Struct +{ +/*000*/ char char_name[64]; +/*064*/ uint32 unknown064; // always 0 +/*068*/ uint8 kick_expedition; // true if /kickplayers exp +/*069*/ uint8 kick_task; // true if /kickplayers task +/*070*/ uint8 padding[2]; }; struct MaxCharacters_Struct diff --git a/common/patches/rof_ops.h b/common/patches/rof_ops.h index 11fd83f51..f06834d21 100644 --- a/common/patches/rof_ops.h +++ b/common/patches/rof_ops.h @@ -44,13 +44,16 @@ E(OP_DeleteCharge) E(OP_DeleteItem) E(OP_DeleteSpawn) E(OP_DisciplineUpdate) +E(OP_DzChooseZone) E(OP_DzCompass) E(OP_DzExpeditionEndsWarning) E(OP_DzExpeditionInfo) -E(OP_DzExpeditionList) -E(OP_DzJoinExpeditionConfirm) -E(OP_DzLeaderStatus) +E(OP_DzExpeditionInvite) +E(OP_DzExpeditionLockoutTimers) E(OP_DzMemberList) +E(OP_DzMemberListName) +E(OP_DzMemberListStatus) +E(OP_DzSetLeaderName) E(OP_Emote) E(OP_ExpansionInfo) E(OP_FormattedMessage) @@ -145,6 +148,12 @@ D(OP_ConsiderCorpse) D(OP_Consume) D(OP_Damage) D(OP_DeleteItem) +D(OP_DzAddPlayer) +D(OP_DzChooseZoneReply) +D(OP_DzExpeditionInviteResponse) +D(OP_DzMakeLeader) +D(OP_DzRemovePlayer) +D(OP_DzSwapPlayer) D(OP_Emote) D(OP_EnvDamage) D(OP_FaceChange) diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index c22278aac..e99a6b96a 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -4815,52 +4815,159 @@ struct VeteranClaim /*076*/ uint32 action; }; -struct ExpeditionEntryHeader_Struct +struct ExpeditionInvite_Struct +{ +/*000*/ uint32 client_id; // unique character id +/*004*/ uint32 unknown004; +/*008*/ char inviter_name[64]; +/*072*/ char expedition_name[128]; +/*200*/ uint8 swapping; // 0: adding 1: swapping +/*201*/ char swap_name[64]; // if swapping, swap name being removed +/*265*/ uint8 padding[3]; +/*268*/ uint16 dz_zone_id; // dz_id zone/instance pair, sent back in reply +/*270*/ uint16 dz_instance_id; +}; + +struct ExpeditionInviteResponse_Struct { /*000*/ uint32 unknown000; -/*000*/ uint32 number_of_entries; -}; - -struct ExpeditionJoinPrompt_Struct -{ -/*000*/ uint32 clientid; /*004*/ uint32 unknown004; -/*008*/ char player_name[64]; -/*072*/ char expedition_name[64]; -}; - -struct ExpeditionExpireWarning -{ -/*000*/ uint32 clientid; -/*004*/ uint32 unknown004; -/*008*/ uint32 minutes_remaining; +/*008*/ uint16 dz_zone_id; // dz_id pair sent in invite +/*010*/ uint16 dz_instance_id; +/*012*/ uint8 accepted; // 0: declined 1: accepted +/*013*/ uint8 swapping; // 0: adding 1: swapping (sent in invite) +/*014*/ char swap_name[64]; // swap name sent in invite +/*078*/ uint8 unknown078; // padding garbage? +/*079*/ uint8 unknown079; // padding garbage? }; struct ExpeditionInfo_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 unknown004; -/*008*/ uint32 unknown008; +/*008*/ uint32 assigned; // padded bool /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; -/*142*/ char leader_name[64]; +/*016*/ char expedition_name[128]; +/*144*/ char leader_name[64]; }; -struct ExpeditionCompassEntry_Struct +struct ExpeditionMemberEntry_Struct { -/*000*/ float unknown000; //seen *((uint32*)) = 1584791871 -/*004*/ uint32 enabled; //guess -/*008*/ uint32 unknown008; //seen 1019 +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +}; + +struct ExpeditionMemberList_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; // number of players in window +/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +}; + +struct ExpeditionMemberListName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status +/*012*/ char name[64]; +}; + +struct ExpeditionLockoutTimerEntry_Struct +{ +/*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ uint32 seconds_remaining; +/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) +}; + +struct ExpeditionLockoutTimers_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; +}; + +struct ExpeditionSetLeaderName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ char leader_name[64]; +}; + +struct ExpeditionCommand_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char name[64]; +}; + +struct ExpeditionCommandSwap_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char add_player_name[64]; // swap to (player must confirm) +/*072*/ char rem_player_name[64]; // swap from +}; + +struct ExpeditionExpireWarning +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 minutes_remaining; +}; + +struct DynamicZoneCompassEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // target dz id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 dz_type; // 1: Expedition, 2: Tutorial (purple), 3: Task, 4: Mission, 5: Quest (green) +/*008*/ uint32 unknown008; /*012*/ float y; /*016*/ float x; /*020*/ float z; }; -struct ExpeditionCompass_Struct +struct DynamicZoneCompass_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 count; -/*008*/ ExpeditionCompassEntry_Struct entries[0]; +/*008*/ DynamicZoneCompassEntry_Struct entries[0]; +}; + +struct DynamicZoneChooseZoneEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // dz_id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 unknown_id1; // sent back in reply +/*008*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest -- sent back in reply +/*012*/ uint32 unknown_id2; // possibly an id based on dz type, for expeditions this was same as dz_id (zone|instance) but task dz was different +/*016*/ char description[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ char leader_name[1]; // variable length, null terminated, max 0x40 (64) +}; + +struct DynamicZoneChooseZone_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ DynamicZoneChooseZoneEntry_Struct choices[0]; +}; + +struct DynamicZoneChooseZoneReply_Struct +{ +/*000*/ uint32 unknown000; // ff ff ff ff +/*004*/ uint32 unknown004; // seen 69 00 00 00 +/*008*/ uint32 unknown008; // ff ff ff ff +/*012*/ uint32 unknown_id1; // from choose zone entry message +/*016*/ uint16 dz_zone_id; // dz_id pair +/*018*/ uint16 dz_instance_id; +/*020*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest +/*024*/ uint32 unknown_id2; // from choose zone entry message +/*028*/ uint32 unknown028; // 00 00 00 00 +/*032*/ uint32 unknown032; // always same as unknown044 +/*036*/ uint32 unknown036; +/*040*/ uint32 unknown040; +/*044*/ uint32 unknown044; // always same as unknown032 +/*048*/ uint32 unknown048; // seen 01 00 00 00 and 02 00 00 00 }; struct MaxCharacters_Struct diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 45f2fe20e..e84641919 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -483,15 +483,48 @@ namespace SoD FINISH_ENCODE(); } + ENCODE(OP_DzChooseZone) + { + SETUP_VAR_ENCODE(DynamicZoneChooseZone_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); + + for (uint32 i = 0; i < emu->count; ++i) + { + buf.WriteUInt16(emu->choices[i].dz_zone_id); + buf.WriteUInt16(emu->choices[i].dz_instance_id); + buf.WriteUInt32(emu->choices[i].unknown_id1); + buf.WriteUInt32(emu->choices[i].dz_type); + buf.WriteUInt32(emu->choices[i].unknown_id2); + buf.WriteString(emu->choices[i].description); + buf.WriteString(emu->choices[i].leader_name); + } + + __packet->size = buf.size(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); + + FINISH_ENCODE(); + } + ENCODE(OP_DzCompass) { - SETUP_VAR_ENCODE(ExpeditionCompass_Struct); - ALLOC_VAR_ENCODE(structs::ExpeditionCompass_Struct, sizeof(structs::ExpeditionInfo_Struct) + sizeof(structs::ExpeditionCompassEntry_Struct) * emu->count); + SETUP_VAR_ENCODE(DynamicZoneCompass_Struct); + ALLOC_VAR_ENCODE(structs::DynamicZoneCompass_Struct, + sizeof(structs::DynamicZoneCompass_Struct) + + sizeof(structs::DynamicZoneCompassEntry_Struct) * emu->count + ); + OUT(client_id); OUT(count); for (uint32 i = 0; i < emu->count; ++i) { + OUT(entries[i].dz_zone_id); + OUT(entries[i].dz_instance_id); + OUT(entries[i].dz_type); OUT(entries[i].x); OUT(entries[i].y); OUT(entries[i].z); @@ -515,81 +548,60 @@ namespace SoD ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + OUT(client_id); + OUT(assigned); OUT(max_players); - eq->unknown004 = 785316192; - eq->unknown008 = 435601; - strcpy(eq->expedition_name, emu->expedition_name); - strcpy(eq->leader_name, emu->leader_name); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } - ENCODE(OP_DzExpeditionList) + ENCODE(OP_DzExpeditionInvite) { - SETUP_VAR_ENCODE(ExpeditionLockoutList_Struct); + ENCODE_LENGTH_EXACT(ExpeditionInvite_Struct); + SETUP_DIRECT_ENCODE(ExpeditionInvite_Struct, structs::ExpeditionInvite_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + OUT(client_id); + strn0cpy(eq->inviter_name, emu->inviter_name, sizeof(eq->inviter_name)); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + OUT(swapping); + strn0cpy(eq->swap_name, emu->swap_name, sizeof(eq->swap_name)); + OUT(dz_zone_id); + OUT(dz_instance_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionLockoutTimers) + { + SETUP_VAR_ENCODE(ExpeditionLockoutTimers_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].expedition, strlen(emu->entries[i].expedition)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].time_left, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->entries[i].expedition_event, strlen(emu->entries[i].expedition_event)); - ss.write((const char*)&null_term, sizeof(char)); + buf.WriteString(emu->timers[i].expedition_name); + buf.WriteUInt32(emu->timers[i].seconds_remaining); + buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteString(emu->timers[i].event_name); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } - ENCODE(OP_DzJoinExpeditionConfirm) + ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionJoinPrompt_Struct); - SETUP_DIRECT_ENCODE(ExpeditionJoinPrompt_Struct, structs::ExpeditionJoinPrompt_Struct); + ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); - strcpy(eq->expedition_name, emu->expedition_name); - strcpy(eq->player_name, emu->player_name); - - FINISH_ENCODE(); - } - - ENCODE(OP_DzLeaderStatus) - { - SETUP_VAR_ENCODE(ExpeditionLeaderSet_Struct); - - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->leader_name, strlen(emu->leader_name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//0xffffffff - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//1 - ss.write((const char*)&client_id, sizeof(uint32)); - - __packet->size = ss.str().length(); - __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + OUT(client_id); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } @@ -598,26 +610,44 @@ namespace SoD { SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].name, strlen(emu->entries[i].name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].status, sizeof(char)); + buf.WriteString(emu->members[i].name); + buf.WriteUInt8(emu->members[i].status); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } + ENCODE(OP_DzMemberListName) + { + ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + + OUT(client_id); + OUT(add_name); + strn0cpy(eq->name, emu->name, sizeof(eq->name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzMemberListStatus) + { + auto emu = reinterpret_cast((*p)->pBuffer); + if (emu->count == 1) + { + ENCODE_FORWARD(OP_DzMemberList); + } + } + + ENCODE(OP_Emote) { EQApplicationPacket *in = *p; @@ -2974,6 +3004,84 @@ namespace SoD FINISH_DIRECT_DECODE(); } + DECODE(OP_DzAddPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzChooseZoneReply) + { + DECODE_LENGTH_EXACT(structs::DynamicZoneChooseZoneReply_Struct); + SETUP_DIRECT_DECODE(DynamicZoneChooseZoneReply_Struct, structs::DynamicZoneChooseZoneReply_Struct); + + IN(unknown000); + IN(unknown004); + IN(unknown008); + IN(unknown_id1); + IN(dz_zone_id); + IN(dz_instance_id); + IN(dz_type); + IN(unknown_id2); + IN(unknown028); + IN(unknown032); + IN(unknown036); + IN(unknown040); + IN(unknown044); + IN(unknown048); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzExpeditionInviteResponse) + { + DECODE_LENGTH_EXACT(structs::ExpeditionInviteResponse_Struct); + SETUP_DIRECT_DECODE(ExpeditionInviteResponse_Struct, structs::ExpeditionInviteResponse_Struct); + + IN(dz_zone_id); + IN(dz_instance_id); + IN(accepted); + IN(swapping); + strn0cpy(emu->swap_name, eq->swap_name, sizeof(emu->swap_name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzMakeLeader) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzRemovePlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzSwapPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommandSwap_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommandSwap_Struct, structs::ExpeditionCommandSwap_Struct); + + strn0cpy(emu->add_player_name, eq->add_player_name, sizeof(emu->add_player_name)); + strn0cpy(emu->rem_player_name, eq->rem_player_name, sizeof(emu->rem_player_name)); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Emote) { unsigned char *__eq_buffer = __packet->pBuffer; diff --git a/common/patches/sod_ops.h b/common/patches/sod_ops.h index 6909e1f28..5216a77ce 100644 --- a/common/patches/sod_ops.h +++ b/common/patches/sod_ops.h @@ -35,13 +35,16 @@ E(OP_Consider) E(OP_Damage) E(OP_DeleteCharge) E(OP_DeleteItem) +E(OP_DzChooseZone) E(OP_DzCompass) E(OP_DzExpeditionEndsWarning) E(OP_DzExpeditionInfo) -E(OP_DzExpeditionList) -E(OP_DzJoinExpeditionConfirm) -E(OP_DzLeaderStatus) +E(OP_DzExpeditionInvite) +E(OP_DzExpeditionLockoutTimers) E(OP_DzMemberList) +E(OP_DzMemberListName) +E(OP_DzMemberListStatus) +E(OP_DzSetLeaderName) E(OP_Emote) E(OP_ExpansionInfo) E(OP_FormattedMessage) @@ -111,6 +114,12 @@ D(OP_Consider) D(OP_ConsiderCorpse) D(OP_Consume) D(OP_DeleteItem) +D(OP_DzAddPlayer) +D(OP_DzChooseZoneReply) +D(OP_DzExpeditionInviteResponse) +D(OP_DzMakeLeader) +D(OP_DzRemovePlayer) +D(OP_DzSwapPlayer) D(OP_Emote) D(OP_FaceChange) D(OP_FindPersonRequest) diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 7a2ae5710..f31ee6792 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -4169,52 +4169,160 @@ struct VeteranReward /*012*/ VeteranRewardItem items[8]; }; -struct ExpeditionEntryHeader_Struct + +struct ExpeditionInvite_Struct +{ +/*000*/ uint32 client_id; // unique character id +/*004*/ uint32 unknown004; +/*008*/ char inviter_name[64]; +/*072*/ char expedition_name[128]; +/*200*/ uint8 swapping; // 0: adding 1: swapping +/*201*/ char swap_name[64]; // if swapping, swap name being removed +/*265*/ uint8 padding[3]; +/*268*/ uint16 dz_zone_id; // dz_id zone/instance pair, sent back in reply +/*270*/ uint16 dz_instance_id; +}; + +struct ExpeditionInviteResponse_Struct { /*000*/ uint32 unknown000; -/*000*/ uint32 number_of_entries; -}; - -struct ExpeditionJoinPrompt_Struct -{ -/*000*/ uint32 clientid; /*004*/ uint32 unknown004; -/*008*/ char player_name[64]; -/*072*/ char expedition_name[64]; -}; - -struct ExpeditionExpireWarning -{ -/*000*/ uint32 clientid; -/*004*/ uint32 unknown004; -/*008*/ uint32 minutes_remaining; +/*008*/ uint16 dz_zone_id; // dz_id pair sent in invite +/*010*/ uint16 dz_instance_id; +/*012*/ uint8 accepted; // 0: declined 1: accepted +/*013*/ uint8 swapping; // 0: adding 1: swapping (sent in invite) +/*014*/ char swap_name[64]; // swap name sent in invite +/*078*/ uint8 unknown078; // padding garbage? +/*079*/ uint8 unknown079; // padding garbage? }; struct ExpeditionInfo_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 unknown004; -/*008*/ uint32 unknown008; +/*008*/ uint32 assigned; // padded bool /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; -/*142*/ char leader_name[64]; +/*016*/ char expedition_name[128]; +/*144*/ char leader_name[64]; }; -struct ExpeditionCompassEntry_Struct +struct ExpeditionMemberEntry_Struct { -/*000*/ float unknown000; //seen *((uint32*)) = 1584791871 -/*004*/ uint32 enabled; //guess -/*008*/ uint32 unknown008; //seen 1019 +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +}; + +struct ExpeditionMemberList_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; // number of players in window +/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +}; + +struct ExpeditionMemberListName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status +/*012*/ char name[64]; +}; + +struct ExpeditionLockoutTimerEntry_Struct +{ +/*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ uint32 seconds_remaining; +/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) +}; + +struct ExpeditionLockoutTimers_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; +}; + +struct ExpeditionSetLeaderName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ char leader_name[64]; +}; + +struct ExpeditionCommand_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char name[64]; +}; + +struct ExpeditionCommandSwap_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char add_player_name[64]; // swap to (player must confirm) +/*072*/ char rem_player_name[64]; // swap from +}; + +struct ExpeditionExpireWarning +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 minutes_remaining; +}; + +struct DynamicZoneCompassEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // target dz id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 dz_type; // 1: Expedition, 2: Tutorial (purple), 3: Task, 4: Mission, 5: Quest (green) +/*008*/ uint32 unknown008; /*012*/ float y; /*016*/ float x; /*020*/ float z; }; -struct ExpeditionCompass_Struct +struct DynamicZoneCompass_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 count; -/*008*/ ExpeditionCompassEntry_Struct entries[0]; +/*008*/ DynamicZoneCompassEntry_Struct entries[0]; +}; + +struct DynamicZoneChooseZoneEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // dz_id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 unknown_id1; // sent back in reply +/*008*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest -- sent back in reply +/*012*/ uint32 unknown_id2; // possibly an id based on dz type, for expeditions this was same as dz_id (zone|instance) but task dz was different +/*016*/ char description[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ char leader_name[1]; // variable length, null terminated, max 0x40 (64) +}; + +struct DynamicZoneChooseZone_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ DynamicZoneChooseZoneEntry_Struct choices[0]; +}; + +struct DynamicZoneChooseZoneReply_Struct +{ +/*000*/ uint32 unknown000; // ff ff ff ff +/*004*/ uint32 unknown004; // seen 69 00 00 00 +/*008*/ uint32 unknown008; // ff ff ff ff +/*012*/ uint32 unknown_id1; // from choose zone entry message +/*016*/ uint16 dz_zone_id; // dz_id pair +/*018*/ uint16 dz_instance_id; +/*020*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest +/*024*/ uint32 unknown_id2; // from choose zone entry message +/*028*/ uint32 unknown028; // 00 00 00 00 +/*032*/ uint32 unknown032; // always same as unknown044 +/*036*/ uint32 unknown036; +/*040*/ uint32 unknown040; +/*044*/ uint32 unknown044; // always same as unknown032 +/*048*/ uint32 unknown048; // seen 01 00 00 00 and 02 00 00 00 }; struct AltCurrencySelectItem_Struct { diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index f8ccf0274..ccb1fd19e 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -471,14 +471,48 @@ namespace SoF FINISH_ENCODE(); } + ENCODE(OP_DzChooseZone) + { + SETUP_VAR_ENCODE(DynamicZoneChooseZone_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); + + for (uint32 i = 0; i < emu->count; ++i) + { + buf.WriteUInt16(emu->choices[i].dz_zone_id); + buf.WriteUInt16(emu->choices[i].dz_instance_id); + buf.WriteUInt32(emu->choices[i].unknown_id1); + buf.WriteUInt32(emu->choices[i].dz_type); + buf.WriteUInt32(emu->choices[i].unknown_id2); + buf.WriteString(emu->choices[i].description); + buf.WriteString(emu->choices[i].leader_name); + } + + __packet->size = buf.size(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); + + FINISH_ENCODE(); + } + ENCODE(OP_DzCompass) { - SETUP_VAR_ENCODE(ExpeditionCompass_Struct); - ALLOC_VAR_ENCODE(structs::ExpeditionCompass_Struct, sizeof(structs::ExpeditionInfo_Struct) + sizeof(structs::ExpeditionCompassEntry_Struct) * emu->count); + SETUP_VAR_ENCODE(DynamicZoneCompass_Struct); + ALLOC_VAR_ENCODE(structs::DynamicZoneCompass_Struct, + sizeof(structs::DynamicZoneCompass_Struct) + + sizeof(structs::DynamicZoneCompassEntry_Struct) * emu->count + ); + + OUT(client_id); OUT(count); for (uint32 i = 0; i < emu->count; ++i) { + OUT(entries[i].dz_zone_id); + OUT(entries[i].dz_instance_id); + OUT(entries[i].dz_type); OUT(entries[i].x); OUT(entries[i].y); OUT(entries[i].z); @@ -502,80 +536,60 @@ namespace SoF ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + OUT(client_id); + OUT(assigned); OUT(max_players); - eq->enabled_max = 1; - strcpy(eq->expedition_name, emu->expedition_name); - strcpy(eq->leader_name, emu->leader_name); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } - ENCODE(OP_DzExpeditionList) + ENCODE(OP_DzExpeditionInvite) { - SETUP_VAR_ENCODE(ExpeditionLockoutList_Struct); + ENCODE_LENGTH_EXACT(ExpeditionInvite_Struct); + SETUP_DIRECT_ENCODE(ExpeditionInvite_Struct, structs::ExpeditionInvite_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); - for (int i = 0; i < emu->count; ++i) + OUT(client_id); + strn0cpy(eq->inviter_name, emu->inviter_name, sizeof(eq->inviter_name)); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + OUT(swapping); + strn0cpy(eq->swap_name, emu->swap_name, sizeof(eq->swap_name)); + OUT(dz_zone_id); + OUT(dz_instance_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionLockoutTimers) + { + SETUP_VAR_ENCODE(ExpeditionLockoutTimers_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); + for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].expedition, strlen(emu->entries[i].expedition)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].time_left, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->entries[i].expedition_event, strlen(emu->entries[i].expedition_event)); - ss.write((const char*)&null_term, sizeof(char)); + buf.WriteString(emu->timers[i].expedition_name); + buf.WriteUInt32(emu->timers[i].seconds_remaining); + buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteString(emu->timers[i].event_name); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } - ENCODE(OP_DzJoinExpeditionConfirm) + ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionJoinPrompt_Struct); - SETUP_DIRECT_ENCODE(ExpeditionJoinPrompt_Struct, structs::ExpeditionJoinPrompt_Struct); + ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); - strcpy(eq->expedition_name, emu->expedition_name); - strcpy(eq->player_name, emu->player_name); - - FINISH_ENCODE(); - } - - ENCODE(OP_DzLeaderStatus) - { - SETUP_VAR_ENCODE(ExpeditionLeaderSet_Struct); - - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - - ss.write((const char*)&client_id, sizeof(uint32)); - //ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->leader_name, strlen(emu->leader_name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//0xffffffff - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//1 - ss.write((const char*)&client_id, sizeof(uint32)); - - __packet->size = ss.str().length(); - __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + OUT(client_id); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } @@ -584,26 +598,43 @@ namespace SoF { SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].name, strlen(emu->entries[i].name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].status, sizeof(char)); + buf.WriteString(emu->members[i].name); + buf.WriteUInt8(emu->members[i].status); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } + ENCODE(OP_DzMemberListName) + { + ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + + OUT(client_id); + OUT(add_name); + strn0cpy(eq->name, emu->name, sizeof(eq->name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzMemberListStatus) + { + auto emu = reinterpret_cast((*p)->pBuffer); + if (emu->count == 1) + { + ENCODE_FORWARD(OP_DzMemberList); + } + } + ENCODE(OP_Emote) { EQApplicationPacket *in = *p; @@ -2435,6 +2466,83 @@ namespace SoF FINISH_DIRECT_DECODE(); } + DECODE(OP_DzAddPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzChooseZoneReply) + { + DECODE_LENGTH_EXACT(structs::DynamicZoneChooseZoneReply_Struct); + SETUP_DIRECT_DECODE(DynamicZoneChooseZoneReply_Struct, structs::DynamicZoneChooseZoneReply_Struct); + + emu->unknown000 = eq->unknown000; + emu->unknown008 = eq->unknown004; + IN(unknown_id1); + IN(dz_zone_id); + IN(dz_instance_id); + IN(dz_type); + IN(unknown_id2); + emu->unknown028 = eq->unknown024; + emu->unknown032 = eq->unknown028; + emu->unknown036 = eq->unknown032; + emu->unknown040 = eq->unknown036; + emu->unknown044 = eq->unknown040; + emu->unknown048 = eq->unknown044; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzExpeditionInviteResponse) + { + DECODE_LENGTH_EXACT(structs::ExpeditionInviteResponse_Struct); + SETUP_DIRECT_DECODE(ExpeditionInviteResponse_Struct, structs::ExpeditionInviteResponse_Struct); + + IN(dz_zone_id); + IN(dz_instance_id); + IN(accepted); + IN(swapping); + strn0cpy(emu->swap_name, eq->swap_name, sizeof(emu->swap_name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzMakeLeader) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzRemovePlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzSwapPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommandSwap_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommandSwap_Struct, structs::ExpeditionCommandSwap_Struct); + + strn0cpy(emu->add_player_name, eq->add_player_name, sizeof(emu->add_player_name)); + strn0cpy(emu->rem_player_name, eq->rem_player_name, sizeof(emu->rem_player_name)); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Emote) { unsigned char *__eq_buffer = __packet->pBuffer; diff --git a/common/patches/sof_ops.h b/common/patches/sof_ops.h index 0ff2cfd2f..653bb5979 100644 --- a/common/patches/sof_ops.h +++ b/common/patches/sof_ops.h @@ -36,13 +36,16 @@ E(OP_Damage) E(OP_DeleteCharge) E(OP_DeleteItem) E(OP_DeleteSpawn) +E(OP_DzChooseZone) E(OP_DzCompass) E(OP_DzExpeditionEndsWarning) E(OP_DzExpeditionInfo) -E(OP_DzExpeditionList) -E(OP_DzJoinExpeditionConfirm) -E(OP_DzLeaderStatus) +E(OP_DzExpeditionInvite) +E(OP_DzExpeditionLockoutTimers) E(OP_DzMemberList) +E(OP_DzMemberListName) +E(OP_DzMemberListStatus) +E(OP_DzSetLeaderName) E(OP_Emote) E(OP_ExpansionInfo) E(OP_FormattedMessage) @@ -103,6 +106,12 @@ D(OP_Consider) D(OP_ConsiderCorpse) D(OP_Consume) D(OP_DeleteItem) +D(OP_DzAddPlayer) +D(OP_DzChooseZoneReply) +D(OP_DzExpeditionInviteResponse) +D(OP_DzMakeLeader) +D(OP_DzRemovePlayer) +D(OP_DzSwapPlayer) D(OP_Emote) D(OP_FaceChange) D(OP_FindPersonRequest) diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index b9716d016..fac58e554 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -4088,43 +4088,150 @@ struct VeteranReward /*012*/ VeteranRewardItem items[8]; }; -struct ExpeditionExpireWarning +struct ExpeditionInvite_Struct +{ +/*000*/ uint32 client_id; +/*004*/ char inviter_name[64]; +/*068*/ char expedition_name[128]; +/*196*/ uint8 swapping; // 0: adding 1: swapping +/*197*/ char swap_name[64]; // if swapping, swap name being removed +/*261*/ uint8 padding[3]; +/*264*/ uint16 dz_zone_id; // dz_id zone/instance pair, sent back in reply +/*268*/ uint16 dz_instance_id; +}; + +struct ExpeditionInviteResponse_Struct { /*000*/ uint32 unknown000; -/*004*/ uint32 minutes_remaining; +/*004*/ uint16 dz_zone_id; // dz_id pair sent in invite +/*006*/ uint16 dz_instance_id; +/*008*/ uint8 accepted; // 0: declined 1: accepted +/*009*/ uint8 swapping; // 0: adding 1: swapping (sent in invite) +/*010*/ char swap_name[64]; // swap name sent in invite +/*074*/ uint8 unknown078; // padding/garbage? +/*075*/ uint8 unknown079; // padding/garbage? }; struct ExpeditionInfo_Struct { -/*000*/ uint32 clientid; -/*004*/ uint32 enabled_max; +/*000*/ uint32 client_id; +/*004*/ uint32 assigned; // padded bool /*008*/ uint32 max_players; -/*012*/ char expedition_name[128]; -/*142*/ char leader_name[64]; +/*012*/ char expedition_name[128]; +/*140*/ char leader_name[64]; }; -struct ExpeditionCompassEntry_Struct +struct ExpeditionMemberEntry_Struct { -/*000*/ float unknown000; //seen *((uint32*)) = 1584791871 -/*004*/ uint32 enabled; //guess -/*008*/ uint32 unknown008; //seen 1019 +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +}; + +struct ExpeditionMemberList_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +}; + +struct ExpeditionMemberListName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status +/*008*/ char name[64]; +}; + +struct ExpeditionLockoutTimerEntry_Struct +{ +/*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ uint32 seconds_remaining; +/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) +}; + +struct ExpeditionLockoutTimers_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; +}; + +struct ExpeditionSetLeaderName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ char leader_name[64]; +}; + +struct ExpeditionCommand_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ char name[64]; +}; + +struct ExpeditionCommandSwap_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ char add_player_name[64]; // swap to (player must confirm) +/*068*/ char rem_player_name[64]; // swap from +}; + +struct ExpeditionExpireWarning +{ +/*000*/ uint32 client_id; +/*004*/ uint32 minutes_remaining; +}; + +struct DynamicZoneCompassEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // target dz id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 dz_type; // 1: Expedition, 2: Tutorial (purple), 3: Task, 4: Mission, 5: Quest (green) +/*008*/ uint32 unknown008; /*012*/ float y; /*016*/ float x; /*020*/ float z; }; -struct ExpeditionCompass_Struct +struct DynamicZoneCompass_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 count; -/*008*/ ExpeditionCompassEntry_Struct entries[0]; +/*008*/ DynamicZoneCompassEntry_Struct entries[0]; }; -struct ExpeditionJoinPrompt_Struct +struct DynamicZoneChooseZoneEntry_Struct { -/*000*/ uint32 clientid; -/*004*/ char player_name[64]; -/*068*/ char expedition_name[64]; +/*000*/ uint16 dz_zone_id; // dz_id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 unknown_id1; // sent back in reply +/*008*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest -- sent back in reply +/*012*/ uint32 unknown_id2; // possibly an id based on dz type, for expeditions this was same as dz_id (zone|instance) but task dz was different +/*016*/ char description[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ char leader_name[1]; // variable length, null terminated, max 0x40 (64) +}; + +struct DynamicZoneChooseZone_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ DynamicZoneChooseZoneEntry_Struct choices[0]; +}; + +struct DynamicZoneChooseZoneReply_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ uint32 unknown_id1; +/*012*/ uint16 dz_zone_id; +/*014*/ uint16 dz_instance_id; +/*016*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest +/*020*/ uint32 unknown_id2; +/*024*/ uint32 unknown024; +/*028*/ uint32 unknown028; // always same as unknown040 +/*032*/ uint32 unknown032; +/*036*/ uint32 unknown036; +/*040*/ uint32 unknown040; // always same as unknown028 +/*044*/ uint32 unknown044; }; struct AltCurrencySelectItem_Struct { diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 505bca10c..5b97f7110 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -414,15 +414,48 @@ namespace Titanium FINISH_ENCODE(); } + ENCODE(OP_DzChooseZone) + { + SETUP_VAR_ENCODE(DynamicZoneChooseZone_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); + + for (uint32 i = 0; i < emu->count; ++i) + { + buf.WriteUInt16(emu->choices[i].dz_zone_id); + buf.WriteUInt16(emu->choices[i].dz_instance_id); + buf.WriteUInt32(emu->choices[i].unknown_id1); + buf.WriteUInt32(emu->choices[i].dz_type); + buf.WriteUInt32(emu->choices[i].unknown_id2); + buf.WriteString(emu->choices[i].description); + buf.WriteString(emu->choices[i].leader_name); + } + + __packet->size = buf.size(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); + + FINISH_ENCODE(); + } + ENCODE(OP_DzCompass) { - SETUP_VAR_ENCODE(ExpeditionCompass_Struct); - ALLOC_VAR_ENCODE(structs::ExpeditionCompass_Struct, sizeof(structs::ExpeditionInfo_Struct) + sizeof(structs::ExpeditionCompassEntry_Struct) * emu->count); + SETUP_VAR_ENCODE(DynamicZoneCompass_Struct); + ALLOC_VAR_ENCODE(structs::DynamicZoneCompass_Struct, + sizeof(structs::DynamicZoneCompass_Struct) + + sizeof(structs::DynamicZoneCompassEntry_Struct) * emu->count + ); + OUT(client_id); OUT(count); for (uint32 i = 0; i < emu->count; ++i) { + OUT(entries[i].dz_zone_id); + OUT(entries[i].dz_instance_id); + OUT(entries[i].dz_type); OUT(entries[i].x); OUT(entries[i].y); OUT(entries[i].z); @@ -446,80 +479,60 @@ namespace Titanium ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + OUT(client_id); + OUT(assigned); OUT(max_players); - eq->enabled_max = 1; - strcpy(eq->expedition_name, emu->expedition_name); - strcpy(eq->leader_name, emu->leader_name); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } - ENCODE(OP_DzExpeditionList) + ENCODE(OP_DzExpeditionInvite) { - SETUP_VAR_ENCODE(ExpeditionLockoutList_Struct); + ENCODE_LENGTH_EXACT(ExpeditionInvite_Struct); + SETUP_DIRECT_ENCODE(ExpeditionInvite_Struct, structs::ExpeditionInvite_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + OUT(client_id); + strn0cpy(eq->inviter_name, emu->inviter_name, sizeof(eq->inviter_name)); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + OUT(swapping); + strn0cpy(eq->swap_name, emu->swap_name, sizeof(eq->swap_name)); + OUT(dz_zone_id); + OUT(dz_instance_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionLockoutTimers) + { + SETUP_VAR_ENCODE(ExpeditionLockoutTimers_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].expedition, strlen(emu->entries[i].expedition)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].time_left, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->entries[i].expedition_event, strlen(emu->entries[i].expedition_event)); - ss.write((const char*)&null_term, sizeof(char)); + buf.WriteString(emu->timers[i].expedition_name); + buf.WriteUInt32(emu->timers[i].seconds_remaining); + buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteString(emu->timers[i].event_name); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } - ENCODE(OP_DzJoinExpeditionConfirm) + ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionJoinPrompt_Struct); - SETUP_DIRECT_ENCODE(ExpeditionJoinPrompt_Struct, structs::ExpeditionJoinPrompt_Struct); + ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); - strcpy(eq->expedition_name, emu->expedition_name); - strcpy(eq->player_name, emu->player_name); - - FINISH_ENCODE(); - } - - ENCODE(OP_DzLeaderStatus) - { - SETUP_VAR_ENCODE(ExpeditionLeaderSet_Struct); - - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - - ss.write((const char*)&client_id, sizeof(uint32)); - //ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->leader_name, strlen(emu->leader_name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//0xffffffff - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//1 - ss.write((const char*)&client_id, sizeof(uint32)); - - __packet->size = ss.str().length(); - __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + OUT(client_id); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } @@ -528,26 +541,43 @@ namespace Titanium { SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].name, strlen(emu->entries[i].name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].status, sizeof(char)); + buf.WriteString(emu->members[i].name); + buf.WriteUInt8(emu->members[i].status); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } + ENCODE(OP_DzMemberListName) + { + ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + + OUT(client_id); + OUT(add_name); + strn0cpy(eq->name, emu->name, sizeof(eq->name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzMemberListStatus) + { + auto emu = reinterpret_cast((*p)->pBuffer); + if (emu->count == 1) + { + ENCODE_FORWARD(OP_DzMemberList); + } + } + ENCODE(OP_Emote) { EQApplicationPacket *in = *p; @@ -1943,6 +1973,83 @@ namespace Titanium FINISH_DIRECT_DECODE(); } + DECODE(OP_DzAddPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzChooseZoneReply) + { + DECODE_LENGTH_EXACT(structs::DynamicZoneChooseZoneReply_Struct); + SETUP_DIRECT_DECODE(DynamicZoneChooseZoneReply_Struct, structs::DynamicZoneChooseZoneReply_Struct); + + emu->unknown000 = eq->unknown000; + emu->unknown008 = eq->unknown004; + IN(unknown_id1); + IN(dz_zone_id); + IN(dz_instance_id); + IN(dz_type); + IN(unknown_id2); + emu->unknown028 = eq->unknown024; + emu->unknown032 = eq->unknown028; + emu->unknown036 = eq->unknown032; + emu->unknown040 = eq->unknown036; + emu->unknown044 = eq->unknown040; + emu->unknown048 = eq->unknown044; + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzExpeditionInviteResponse) + { + DECODE_LENGTH_EXACT(structs::ExpeditionInviteResponse_Struct); + SETUP_DIRECT_DECODE(ExpeditionInviteResponse_Struct, structs::ExpeditionInviteResponse_Struct); + + IN(dz_zone_id); + IN(dz_instance_id); + IN(accepted); + IN(swapping); + strn0cpy(emu->swap_name, eq->swap_name, sizeof(emu->swap_name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzMakeLeader) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzRemovePlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzSwapPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommandSwap_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommandSwap_Struct, structs::ExpeditionCommandSwap_Struct); + + strn0cpy(emu->add_player_name, eq->add_player_name, sizeof(emu->add_player_name)); + strn0cpy(emu->rem_player_name, eq->rem_player_name, sizeof(emu->rem_player_name)); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Emote) { unsigned char *__eq_buffer = __packet->pBuffer; diff --git a/common/patches/titanium_ops.h b/common/patches/titanium_ops.h index d3c8c1dde..961c850fe 100644 --- a/common/patches/titanium_ops.h +++ b/common/patches/titanium_ops.h @@ -32,13 +32,16 @@ E(OP_Damage) E(OP_DeleteCharge) E(OP_DeleteItem) E(OP_DeleteSpawn) +E(OP_DzChooseZone) E(OP_DzCompass) E(OP_DzExpeditionEndsWarning) E(OP_DzExpeditionInfo) -E(OP_DzExpeditionList) -E(OP_DzJoinExpeditionConfirm) -E(OP_DzLeaderStatus) +E(OP_DzExpeditionInvite) +E(OP_DzExpeditionLockoutTimers) E(OP_DzMemberList) +E(OP_DzMemberListName) +E(OP_DzMemberListStatus) +E(OP_DzSetLeaderName) E(OP_Emote) E(OP_FormattedMessage) E(OP_GroundSpawn) @@ -86,6 +89,12 @@ D(OP_CharacterCreate) D(OP_ClientUpdate) D(OP_Consume) D(OP_DeleteItem) +D(OP_DzAddPlayer) +D(OP_DzChooseZoneReply) +D(OP_DzExpeditionInviteResponse) +D(OP_DzMakeLeader) +D(OP_DzRemovePlayer) +D(OP_DzSwapPlayer) D(OP_Emote) D(OP_FaceChange) D(OP_InspectAnswer) diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index f620089d6..ba5bb6ab5 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -3299,43 +3299,150 @@ struct VeteranReward /*004*/ VeteranRewardItem item; }; -struct ExpeditionExpireWarning +struct ExpeditionInvite_Struct +{ +/*000*/ uint32 client_id; +/*004*/ char inviter_name[64]; +/*068*/ char expedition_name[128]; +/*196*/ uint8 swapping; // 0: adding 1: swapping +/*197*/ char swap_name[64]; // if swapping, swap name being removed +/*261*/ uint8 padding[3]; +/*264*/ uint16 dz_zone_id; // dz_id zone/instance pair, sent back in reply +/*268*/ uint16 dz_instance_id; +}; + +struct ExpeditionInviteResponse_Struct { /*000*/ uint32 unknown000; -/*004*/ uint32 minutes_remaining; +/*004*/ uint16 dz_zone_id; // dz_id pair sent in invite +/*006*/ uint16 dz_instance_id; +/*008*/ uint8 accepted; // 0: declined 1: accepted +/*009*/ uint8 swapping; // 0: adding 1: swapping (sent in invite) +/*010*/ char swap_name[64]; // swap name sent in invite +/*074*/ uint8 unknown078; // padding/garbage? +/*075*/ uint8 unknown079; // padding/garbage? }; struct ExpeditionInfo_Struct { -/*000*/ uint32 clientid; -/*004*/ uint32 enabled_max; +/*000*/ uint32 client_id; +/*004*/ uint32 assigned; // padded bool /*008*/ uint32 max_players; -/*012*/ char expedition_name[128]; -/*142*/ char leader_name[64]; +/*012*/ char expedition_name[128]; +/*140*/ char leader_name[64]; }; -struct ExpeditionCompassEntry_Struct +struct ExpeditionMemberEntry_Struct { -/*000*/ float unknown000; //seen *((uint32*)) = 1584791871 -/*004*/ uint32 enabled; //guess -/*008*/ uint32 unknown008; //seen 1019 +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +}; + +struct ExpeditionMemberList_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +}; + +struct ExpeditionMemberListName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status +/*008*/ char name[64]; +}; + +struct ExpeditionLockoutTimerEntry_Struct +{ +/*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ uint32 seconds_remaining; +/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) +}; + +struct ExpeditionLockoutTimers_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; +}; + +struct ExpeditionSetLeaderName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ char leader_name[64]; +}; + +struct ExpeditionCommand_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ char name[64]; +}; + +struct ExpeditionCommandSwap_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ char add_player_name[64]; // swap to (player must confirm) +/*068*/ char rem_player_name[64]; // swap from +}; + +struct ExpeditionExpireWarning +{ +/*000*/ uint32 client_id; +/*004*/ uint32 minutes_remaining; +}; + +struct DynamicZoneCompassEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // target dz id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 dz_type; // 1: Expedition, 2: Tutorial (purple), 3: Task, 4: Mission, 5: Quest (green) +/*008*/ uint32 unknown008; /*012*/ float y; /*016*/ float x; /*020*/ float z; }; -struct ExpeditionCompass_Struct +struct DynamicZoneCompass_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 count; -/*008*/ ExpeditionCompassEntry_Struct entries[0]; +/*008*/ DynamicZoneCompassEntry_Struct entries[0]; }; -struct ExpeditionJoinPrompt_Struct +struct DynamicZoneChooseZoneEntry_Struct { -/*000*/ uint32 clientid; -/*004*/ char player_name[64]; -/*068*/ char expedition_name[64]; +/*000*/ uint16 dz_zone_id; // dz_id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 unknown_id1; // sent back in reply +/*008*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest -- sent back in reply +/*012*/ uint32 unknown_id2; // possibly an id based on dz type, for expeditions this was same as dz_id (zone|instance) but task dz was different +/*016*/ char description[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ char leader_name[1]; // variable length, null terminated, max 0x40 (64) +}; + +struct DynamicZoneChooseZone_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ DynamicZoneChooseZoneEntry_Struct choices[0]; +}; + +struct DynamicZoneChooseZoneReply_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ uint32 unknown_id1; +/*012*/ uint16 dz_zone_id; +/*014*/ uint16 dz_instance_id; +/*016*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest +/*020*/ uint32 unknown_id2; +/*024*/ uint32 unknown024; +/*028*/ uint32 unknown028; // always same as unknown040 +/*032*/ uint32 unknown032; +/*036*/ uint32 unknown036; +/*040*/ uint32 unknown040; // always same as unknown028 +/*044*/ uint32 unknown044; }; struct LFGuild_SearchPlayer_Struct diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 9dfc653b4..fa3000346 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -613,14 +613,48 @@ namespace UF FINISH_ENCODE(); } + ENCODE(OP_DzChooseZone) + { + SETUP_VAR_ENCODE(DynamicZoneChooseZone_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); + + for (uint32 i = 0; i < emu->count; ++i) + { + buf.WriteUInt16(emu->choices[i].dz_zone_id); + buf.WriteUInt16(emu->choices[i].dz_instance_id); + buf.WriteUInt32(emu->choices[i].unknown_id1); + buf.WriteUInt32(emu->choices[i].dz_type); + buf.WriteUInt32(emu->choices[i].unknown_id2); + buf.WriteString(emu->choices[i].description); + buf.WriteString(emu->choices[i].leader_name); + } + + __packet->size = buf.size(); + __packet->pBuffer = new unsigned char[__packet->size]; + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); + + FINISH_ENCODE(); + } + ENCODE(OP_DzCompass) { - SETUP_VAR_ENCODE(ExpeditionCompass_Struct); - ALLOC_VAR_ENCODE(structs::ExpeditionCompass_Struct, sizeof(structs::ExpeditionInfo_Struct) + sizeof(structs::ExpeditionCompassEntry_Struct) * emu->count); + SETUP_VAR_ENCODE(DynamicZoneCompass_Struct); + ALLOC_VAR_ENCODE(structs::DynamicZoneCompass_Struct, + sizeof(structs::DynamicZoneCompass_Struct) + + sizeof(structs::DynamicZoneCompassEntry_Struct) * emu->count + ); + + OUT(client_id); OUT(count); for (uint32 i = 0; i < emu->count; ++i) { + OUT(entries[i].dz_zone_id); + OUT(entries[i].dz_instance_id); + OUT(entries[i].dz_type); OUT(entries[i].x); OUT(entries[i].y); OUT(entries[i].z); @@ -644,81 +678,60 @@ namespace UF ENCODE_LENGTH_EXACT(ExpeditionInfo_Struct); SETUP_DIRECT_ENCODE(ExpeditionInfo_Struct, structs::ExpeditionInfo_Struct); + OUT(client_id); + OUT(assigned); OUT(max_players); - eq->unknown004 = 785316192; - eq->unknown008 = 435601; - strcpy(eq->expedition_name, emu->expedition_name); - strcpy(eq->leader_name, emu->leader_name); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } - ENCODE(OP_DzExpeditionList) + ENCODE(OP_DzExpeditionInvite) { - SETUP_VAR_ENCODE(ExpeditionLockoutList_Struct); + ENCODE_LENGTH_EXACT(ExpeditionInvite_Struct); + SETUP_DIRECT_ENCODE(ExpeditionInvite_Struct, structs::ExpeditionInvite_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + OUT(client_id); + strn0cpy(eq->inviter_name, emu->inviter_name, sizeof(eq->inviter_name)); + strn0cpy(eq->expedition_name, emu->expedition_name, sizeof(eq->expedition_name)); + OUT(swapping); + strn0cpy(eq->swap_name, emu->swap_name, sizeof(eq->swap_name)); + OUT(dz_zone_id); + OUT(dz_instance_id); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzExpeditionLockoutTimers) + { + SETUP_VAR_ENCODE(ExpeditionLockoutTimers_Struct); + + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].expedition, strlen(emu->entries[i].expedition)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].time_left, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->entries[i].expedition_event, strlen(emu->entries[i].expedition_event)); - ss.write((const char*)&null_term, sizeof(char)); + buf.WriteString(emu->timers[i].expedition_name); + buf.WriteUInt32(emu->timers[i].seconds_remaining); + buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteString(emu->timers[i].event_name); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } - ENCODE(OP_DzJoinExpeditionConfirm) + ENCODE(OP_DzSetLeaderName) { - ENCODE_LENGTH_EXACT(ExpeditionJoinPrompt_Struct); - SETUP_DIRECT_ENCODE(ExpeditionJoinPrompt_Struct, structs::ExpeditionJoinPrompt_Struct); + ENCODE_LENGTH_EXACT(ExpeditionSetLeaderName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionSetLeaderName_Struct, structs::ExpeditionSetLeaderName_Struct); - strcpy(eq->expedition_name, emu->expedition_name); - strcpy(eq->player_name, emu->player_name); - - FINISH_ENCODE(); - } - - ENCODE(OP_DzLeaderStatus) - { - SETUP_VAR_ENCODE(ExpeditionLeaderSet_Struct); - - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - uint32 client_id = 0; - uint8 null_term = 0; - - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write(emu->leader_name, strlen(emu->leader_name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//0xffffffff - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&client_id, sizeof(uint32));//1 - ss.write((const char*)&client_id, sizeof(uint32)); - - __packet->size = ss.str().length(); - __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + OUT(client_id); + strn0cpy(eq->leader_name, emu->leader_name, sizeof(eq->leader_name)); FINISH_ENCODE(); } @@ -727,26 +740,43 @@ namespace UF { SETUP_VAR_ENCODE(ExpeditionMemberList_Struct); - std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); - - uint32 client_id = 0; - uint8 null_term = 0; - ss.write((const char*)&client_id, sizeof(uint32)); - ss.write((const char*)&emu->count, sizeof(uint32)); + SerializeBuffer buf; + buf.WriteUInt32(emu->client_id); + buf.WriteUInt32(emu->count); for (uint32 i = 0; i < emu->count; ++i) { - ss.write(emu->entries[i].name, strlen(emu->entries[i].name)); - ss.write((const char*)&null_term, sizeof(char)); - ss.write((const char*)&emu->entries[i].status, sizeof(char)); + buf.WriteString(emu->members[i].name); + buf.WriteUInt8(emu->members[i].status); } - __packet->size = ss.str().length(); + __packet->size = buf.size(); __packet->pBuffer = new unsigned char[__packet->size]; - memcpy(__packet->pBuffer, ss.str().c_str(), __packet->size); + memcpy(__packet->pBuffer, buf.buffer(), __packet->size); FINISH_ENCODE(); } + ENCODE(OP_DzMemberListName) + { + ENCODE_LENGTH_EXACT(ExpeditionMemberListName_Struct); + SETUP_DIRECT_ENCODE(ExpeditionMemberListName_Struct, structs::ExpeditionMemberListName_Struct); + + OUT(client_id); + OUT(add_name); + strn0cpy(eq->name, emu->name, sizeof(eq->name)); + + FINISH_ENCODE(); + } + + ENCODE(OP_DzMemberListStatus) + { + auto emu = reinterpret_cast((*p)->pBuffer); + if (emu->count == 1) + { + ENCODE_FORWARD(OP_DzMemberList); + } + } + ENCODE(OP_Emote) { EQApplicationPacket *in = *p; @@ -3315,6 +3345,84 @@ namespace UF FINISH_DIRECT_DECODE(); } + DECODE(OP_DzAddPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzChooseZoneReply) + { + DECODE_LENGTH_EXACT(structs::DynamicZoneChooseZoneReply_Struct); + SETUP_DIRECT_DECODE(DynamicZoneChooseZoneReply_Struct, structs::DynamicZoneChooseZoneReply_Struct); + + IN(unknown000); + IN(unknown004); + IN(unknown008); + IN(unknown_id1); + IN(dz_zone_id); + IN(dz_instance_id); + IN(dz_type); + IN(unknown_id2); + IN(unknown028); + IN(unknown032); + IN(unknown036); + IN(unknown040); + IN(unknown044); + IN(unknown048); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzExpeditionInviteResponse) + { + DECODE_LENGTH_EXACT(structs::ExpeditionInviteResponse_Struct); + SETUP_DIRECT_DECODE(ExpeditionInviteResponse_Struct, structs::ExpeditionInviteResponse_Struct); + + IN(dz_zone_id); + IN(dz_instance_id); + IN(accepted); + IN(swapping); + strn0cpy(emu->swap_name, eq->swap_name, sizeof(emu->swap_name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzMakeLeader) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzRemovePlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommand_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommand_Struct, structs::ExpeditionCommand_Struct); + + strn0cpy(emu->name, eq->name, sizeof(emu->name)); + + FINISH_DIRECT_DECODE(); + } + + DECODE(OP_DzSwapPlayer) + { + DECODE_LENGTH_EXACT(structs::ExpeditionCommandSwap_Struct); + SETUP_DIRECT_DECODE(ExpeditionCommandSwap_Struct, structs::ExpeditionCommandSwap_Struct); + + strn0cpy(emu->add_player_name, eq->add_player_name, sizeof(emu->add_player_name)); + strn0cpy(emu->rem_player_name, eq->rem_player_name, sizeof(emu->rem_player_name)); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Emote) { unsigned char *__eq_buffer = __packet->pBuffer; diff --git a/common/patches/uf_ops.h b/common/patches/uf_ops.h index 9d7742cf0..76b7e014b 100644 --- a/common/patches/uf_ops.h +++ b/common/patches/uf_ops.h @@ -38,13 +38,16 @@ E(OP_Damage) E(OP_DeleteCharge) E(OP_DeleteItem) E(OP_DisciplineUpdate) +E(OP_DzChooseZone) E(OP_DzCompass) E(OP_DzExpeditionEndsWarning) E(OP_DzExpeditionInfo) -E(OP_DzExpeditionList) -E(OP_DzJoinExpeditionConfirm) -E(OP_DzLeaderStatus) +E(OP_DzExpeditionInvite) +E(OP_DzExpeditionLockoutTimers) E(OP_DzMemberList) +E(OP_DzMemberListName) +E(OP_DzMemberListStatus) +E(OP_DzSetLeaderName) E(OP_Emote) E(OP_ExpansionInfo) E(OP_FormattedMessage) @@ -120,6 +123,12 @@ D(OP_ConsiderCorpse) D(OP_Consume) D(OP_Damage) D(OP_DeleteItem) +D(OP_DzAddPlayer) +D(OP_DzChooseZoneReply) +D(OP_DzExpeditionInviteResponse) +D(OP_DzMakeLeader) +D(OP_DzRemovePlayer) +D(OP_DzSwapPlayer) D(OP_Emote) D(OP_EnvDamage) D(OP_FaceChange) diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 895881a7a..7a8eab757 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -4250,52 +4250,160 @@ struct VeteranReward /*012*/ VeteranRewardItem items[8]; }; -struct ExpeditionEntryHeader_Struct + +struct ExpeditionInvite_Struct +{ +/*000*/ uint32 client_id; // unique character id +/*004*/ uint32 unknown004; +/*008*/ char inviter_name[64]; +/*072*/ char expedition_name[128]; +/*200*/ uint8 swapping; // 0: adding 1: swapping +/*201*/ char swap_name[64]; // if swapping, swap name being removed +/*265*/ uint8 padding[3]; +/*268*/ uint16 dz_zone_id; // dz_id zone/instance pair, sent back in reply +/*270*/ uint16 dz_instance_id; +}; + +struct ExpeditionInviteResponse_Struct { /*000*/ uint32 unknown000; -/*000*/ uint32 number_of_entries; -}; - -struct ExpeditionJoinPrompt_Struct -{ -/*000*/ uint32 clientid; /*004*/ uint32 unknown004; -/*008*/ char player_name[64]; -/*072*/ char expedition_name[64]; -}; - -struct ExpeditionExpireWarning -{ -/*000*/ uint32 clientid; -/*004*/ uint32 unknown004; -/*008*/ uint32 minutes_remaining; +/*008*/ uint16 dz_zone_id; // dz_id pair sent in invite +/*010*/ uint16 dz_instance_id; +/*012*/ uint8 accepted; // 0: declined 1: accepted +/*013*/ uint8 swapping; // 0: adding 1: swapping (sent in invite) +/*014*/ char swap_name[64]; // swap name sent in invite +/*078*/ uint8 unknown078; // padding garbage? +/*079*/ uint8 unknown079; // padding garbage? }; struct ExpeditionInfo_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 unknown004; -/*008*/ uint32 unknown008; +/*008*/ uint32 assigned; // padded bool /*012*/ uint32 max_players; -/*016*/ char expedition_name[128]; -/*142*/ char leader_name[64]; +/*016*/ char expedition_name[128]; +/*144*/ char leader_name[64]; }; -struct ExpeditionCompassEntry_Struct +struct ExpeditionMemberEntry_Struct { -/*000*/ float unknown000; //seen *((uint32*)) = 1584791871 -/*004*/ uint32 enabled; //guess -/*008*/ uint32 unknown008; //seen 1019 +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +}; + +struct ExpeditionMemberList_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; // number of players in window +/*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length +}; + +struct ExpeditionMemberListName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 add_name; // padded bool, 0: remove name, 1: add name with unknown status +/*012*/ char name[64]; +}; + +struct ExpeditionLockoutTimerEntry_Struct +{ +/*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ uint32 seconds_remaining; +/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) +}; + +struct ExpeditionLockoutTimers_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ ExpeditionLockoutTimerEntry_Struct timers[0]; +}; + +struct ExpeditionSetLeaderName_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ char leader_name[64]; +}; + +struct ExpeditionCommand_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char name[64]; +}; + +struct ExpeditionCommandSwap_Struct +{ +/*000*/ uint32 unknown000; +/*004*/ uint32 unknown004; +/*008*/ char add_player_name[64]; // swap to (player must confirm) +/*072*/ char rem_player_name[64]; // swap from +}; + +struct ExpeditionExpireWarning +{ +/*000*/ uint32 client_id; +/*004*/ uint32 unknown004; +/*008*/ uint32 minutes_remaining; +}; + +struct DynamicZoneCompassEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // target dz id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 dz_type; // 1: Expedition, 2: Tutorial (purple), 3: Task, 4: Mission, 5: Quest (green) +/*008*/ uint32 unknown008; /*012*/ float y; /*016*/ float x; /*020*/ float z; }; -struct ExpeditionCompass_Struct +struct DynamicZoneCompass_Struct { -/*000*/ uint32 clientid; +/*000*/ uint32 client_id; /*004*/ uint32 count; -/*008*/ ExpeditionCompassEntry_Struct entries[0]; +/*008*/ DynamicZoneCompassEntry_Struct entries[0]; +}; + +struct DynamicZoneChooseZoneEntry_Struct +{ +/*000*/ uint16 dz_zone_id; // dz_id pair +/*002*/ uint16 dz_instance_id; +/*004*/ uint32 unknown_id1; // sent back in reply +/*008*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest -- sent back in reply +/*012*/ uint32 unknown_id2; // possibly an id based on dz type, for expeditions this was same as dz_id (zone|instance) but task dz was different +/*016*/ char description[1]; // variable length, null terminated, max 0x80 (128) +/*000*/ char leader_name[1]; // variable length, null terminated, max 0x40 (64) +}; + +struct DynamicZoneChooseZone_Struct +{ +/*000*/ uint32 client_id; +/*004*/ uint32 count; +/*008*/ DynamicZoneChooseZoneEntry_Struct choices[0]; +}; + +struct DynamicZoneChooseZoneReply_Struct +{ +/*000*/ uint32 unknown000; // ff ff ff ff +/*004*/ uint32 unknown004; // seen 69 00 00 00 +/*008*/ uint32 unknown008; // ff ff ff ff +/*012*/ uint32 unknown_id1; // from choose zone entry message +/*016*/ uint16 dz_zone_id; // dz_id pair +/*018*/ uint16 dz_instance_id; +/*020*/ uint32 dz_type; // 1: Expedition, 2: Tutorial, 3: Task, 4: Mission, 5: Quest +/*024*/ uint32 unknown_id2; // from choose zone entry message +/*028*/ uint32 unknown028; // 00 00 00 00 +/*032*/ uint32 unknown032; // always same as unknown044 +/*036*/ uint32 unknown036; +/*040*/ uint32 unknown040; +/*044*/ uint32 unknown044; // always same as unknown032 +/*048*/ uint32 unknown048; // seen 01 00 00 00 and 02 00 00 00 }; struct AltCurrencySelectItem_Struct { diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 6e638ba1a..76178bf62 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -375,16 +375,18 @@ OP_DzRemovePlayer=0x0dc1 OP_DzSwapPlayer=0x4995 OP_DzMakeLeader=0x17b2 OP_DzPlayerList=0x1aff -OP_DzJoinExpeditionConfirm=0x30df -OP_DzJoinExpeditionReply=0x15d4 +OP_DzExpeditionInvite=0x30df +OP_DzExpeditionInviteResponse=0x15d4 OP_DzExpeditionInfo=0x3861 -OP_DzExpeditionList=0x0b3b -OP_DzMemberStatus=0x26c2 -OP_DzLeaderStatus=0x4021 -OP_DzExpeditionEndsWarning=0x32eb +OP_DzExpeditionLockoutTimers=0x0b3b OP_DzMemberList=0x348f +OP_DzMemberListName=0x26c2 +OP_DzMemberListStatus=0x0000 +OP_DzSetLeaderName=0x4021 +OP_DzExpeditionEndsWarning=0x32eb OP_DzCompass=0x0e01 # Was 0x4f09 OP_DzChooseZone=0x6e5e # Maybe 0x29d6 +OP_DzChooseZoneReply=0x0000 # New Opcodes OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ? diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index f62b90dd4..96f3258a8 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -368,25 +368,28 @@ OP_AggroMeterLockTarget=0x1643 OP_AggroMeterTargetInfo=0x16bc OP_AggroMeterUpdate=0x1781 OP_UnderWorld=0x2eb3 # clients sends up when they detect an underworld issue, might be useful for cheat detection +OP_KickPlayers=0x6770 # Expeditions +OP_DzQuit=0xb2e3 +OP_DzListTimers=0x7b68 OP_DzAddPlayer=0x4701 OP_DzRemovePlayer=0x1abc OP_DzSwapPlayer=0x405b OP_DzMakeLeader=0x543d OP_DzPlayerList=0x14c6 -OP_DzJoinExpeditionConfirm=0x7f4b -OP_DzJoinExpeditionReply=0x1950 -OP_DzListTimers=0x7b68 +OP_DzExpeditionInvite=0x7f4b +OP_DzExpeditionInviteResponse=0x1950 OP_DzExpeditionInfo=0x9119 -OP_DzExpeditionList=0x205f -OP_DzQuit=0xb2e3 -OP_DzMemberStatus=0x32f0 -OP_DzLeaderStatus=0x3de9 +OP_DzExpeditionLockoutTimers=0x205f OP_DzMemberList=0x5ae4 -OP_DzExpeditionEndsWarning=0x383c +OP_DzMemberListName=0x32f0 +OP_DzMemberListStatus=0x12F5 +OP_DzSetLeaderName=0x3de9 +OP_DzExpeditionEndsWarning=0x5189 OP_DzCompass=0x3e0e OP_DzChooseZone=0x0b7d +OP_DzChooseZoneReply=0x4de1 # New Opcodes OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ? diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 05e4a335f..1ef8a138a 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -367,17 +367,18 @@ OP_DzRemovePlayer=0xa682 OP_DzSwapPlayer=0x0d8d OP_DzMakeLeader=0x1caa OP_DzPlayerList=0x74ca -OP_DzJoinExpeditionConfirm=0x1772 -OP_DzJoinExpeditionReply=0x3c13 +OP_DzExpeditionInvite=0x1772 +OP_DzExpeditionInviteResponse=0x3c13 OP_DzExpeditionInfo=0x128e -OP_DzMemberStatus=0x4661 -OP_DzLeaderStatus=0x226f -OP_DzExpeditionEndsWarning=0x1879 -OP_DzExpeditionList=0x3657 +OP_DzExpeditionLockoutTimers=0x3657 OP_DzMemberList=0x74e4 +OP_DzMemberListName=0x4661 +OP_DzMemberListStatus=0x1d99 +OP_DzSetLeaderName=0x226f +OP_DzExpeditionEndsWarning=0x1879 OP_DzCompass=0x35d3 OP_DzChooseZone=0x0d8a -#0x1d99 was grouped with these too but I don't really know it's purpose. +OP_DzChooseZoneReply=0x5a67 # New Opcodes OP_SpawnPositionUpdate=0x4656 # diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index c6d754ba3..1c5fd3eda 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -348,16 +348,18 @@ OP_DzRemovePlayer=0x2ce8 OP_DzSwapPlayer=0x2c3e OP_DzMakeLeader=0x1a75 OP_DzPlayerList=0x5116 -OP_DzJoinExpeditionConfirm=0x1793 -OP_DzJoinExpeditionReply=0x7a6f +OP_DzExpeditionInvite=0x1793 +OP_DzExpeditionInviteResponse=0x7a6f OP_DzExpeditionInfo=0x60a6 -OP_DzMemberStatus=0x0516 -OP_DzLeaderStatus=0x79d3 -OP_DzExpeditionEndsWarning=0x5153 -OP_DzExpeditionList=0x02ac +OP_DzExpeditionLockoutTimers=0x02ac OP_DzMemberList=0x5e14 +OP_DzMemberListName=0x0516 +OP_DzMemberListStatus=0x0000 +OP_DzSetLeaderName=0x79d3 +OP_DzExpeditionEndsWarning=0x5153 OP_DzCompass=0x531d OP_DzChooseZone=0x3c5b +OP_DzChooseZoneReply=0x0000 #Looting OP_LootRequest=0x36E3 #Trevius 02/16/09 diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index ade2576aa..f984f88db 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -297,17 +297,18 @@ OP_DzRemovePlayer=0x540b OP_DzSwapPlayer=0x794a OP_DzMakeLeader=0x0ce9 OP_DzPlayerList=0xada0 -OP_DzJoinExpeditionConfirm=0x3817 -OP_DzJoinExpeditionReply=0x5da9 +OP_DzExpeditionInvite=0x3817 +OP_DzExpeditionInviteResponse=0x5da9 OP_DzExpeditionInfo=0x98e -OP_DzMemberStatus=0x1826 -OP_DzLeaderStatus=0x7abc -OP_DzExpeditionEndsWarning=0x1c3f -OP_DzExpeditionList=0x7c12 +OP_DzExpeditionLockoutTimers=0x7c12 OP_DzMemberList=0x9b6 +OP_DzMemberListName=0x1826 +OP_DzMemberListStatus=0x330d +OP_DzSetLeaderName=0x7abc +OP_DzExpeditionEndsWarning=0x1c3f OP_DzCompass=0x28aa OP_DzChooseZone=0x1022 -#0x330d is something but I'm not sure what yet. +OP_DzChooseZoneReply=0x20e7 #bazaar trader stuff stuff: #become and buy from diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 101fafde1..8efce91f6 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -377,16 +377,18 @@ OP_DzRemovePlayer=0x054e OP_DzSwapPlayer=0x4661 OP_DzMakeLeader=0x226f OP_DzPlayerList=0x74e4 -OP_DzJoinExpeditionConfirm=0x3c5e -OP_DzJoinExpeditionReply=0x1154 +OP_DzExpeditionInvite=0x3c5e +OP_DzExpeditionInviteResponse=0x1154 OP_DzExpeditionInfo=0x1150 -OP_DzMemberStatus=0x2d17 -OP_DzLeaderStatus=0x2caf -OP_DzExpeditionEndsWarning=0x6ac2 -OP_DzExpeditionList=0x70d8 +OP_DzExpeditionLockoutTimers=0x70d8 OP_DzMemberList=0x15c4 +OP_DzMemberListName=0x2d17 +OP_DzMemberListStatus=0x0d98 +OP_DzSetLeaderName=0x2caf +OP_DzExpeditionEndsWarning=0x6ac2 OP_DzCompass=0x01cb OP_DzChooseZone=0x65e1 +OP_DzChooseZoneReply=0xa682 #shroud OP_ShroudSelectionWindow=0x72ad diff --git a/zone/client.cpp b/zone/client.cpp index 6338e410f..821c14570 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -6126,8 +6126,8 @@ void Client::MarkSingleCompassLoc(float in_x, float in_y, float in_z, uint8 coun { auto outapp = new EQApplicationPacket(OP_DzCompass, sizeof(ExpeditionInfo_Struct) + - sizeof(ExpeditionCompassEntry_Struct) * count); - ExpeditionCompass_Struct *ecs = (ExpeditionCompass_Struct*)outapp->pBuffer; + sizeof(DynamicZoneCompassEntry_Struct) * count); + DynamicZoneCompass_Struct *ecs = (DynamicZoneCompass_Struct*)outapp->pBuffer; //ecs->clientid = GetID(); ecs->count = count; diff --git a/zone/lua_packet.cpp b/zone/lua_packet.cpp index 648d6fa87..4936259e6 100644 --- a/zone/lua_packet.cpp +++ b/zone/lua_packet.cpp @@ -788,16 +788,18 @@ luabind::scope lua_register_packet_opcodes() { luabind::value("DzRemovePlayer", static_cast(OP_DzRemovePlayer)), luabind::value("DzSwapPlayer", static_cast(OP_DzSwapPlayer)), luabind::value("DzMakeLeader", static_cast(OP_DzMakeLeader)), - luabind::value("DzJoinExpeditionConfirm", static_cast(OP_DzJoinExpeditionConfirm)), - luabind::value("DzJoinExpeditionReply", static_cast(OP_DzJoinExpeditionReply)), + luabind::value("DzExpeditionInvite", static_cast(OP_DzExpeditionInvite)), + luabind::value("DzExpeditionInviteResponse", static_cast(OP_DzExpeditionInviteResponse)), luabind::value("DzExpeditionInfo", static_cast(OP_DzExpeditionInfo)), - luabind::value("DzMemberStatus", static_cast(OP_DzMemberStatus)), - luabind::value("DzLeaderStatus", static_cast(OP_DzLeaderStatus)), + luabind::value("DzMemberListName", static_cast(OP_DzMemberListName)), + luabind::value("DzMemberListStatus", static_cast(OP_DzMemberListStatus)), + luabind::value("DzSetLeaderName", static_cast(OP_DzSetLeaderName)), luabind::value("DzExpeditionEndsWarning", static_cast(OP_DzExpeditionEndsWarning)), - luabind::value("DzExpeditionList", static_cast(OP_DzExpeditionList)), + luabind::value("DzExpeditionLockoutTimers", static_cast(OP_DzExpeditionLockoutTimers)), luabind::value("DzMemberList", static_cast(OP_DzMemberList)), luabind::value("DzCompass", static_cast(OP_DzCompass)), luabind::value("DzChooseZone", static_cast(OP_DzChooseZone)), + luabind::value("DzChooseZoneReply", static_cast(OP_DzChooseZoneReply)), luabind::value("BuffCreate", static_cast(OP_BuffCreate)), luabind::value("GuildStatus", static_cast(OP_GuildStatus)), luabind::value("BuffRemoveRequest", static_cast(OP_BuffRemoveRequest)), From da067be2fa6671d85df3f33eb36bd3cecf06946a Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 14 Apr 2020 17:18:54 -0400 Subject: [PATCH 029/196] 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 --- common/eqemu_logsys.h | 4 +- common/eqemu_logsys_log_aliases.h | 24 + common/ruletypes.h | 5 + common/servertalk.h | 95 ++ common/string_util.cpp | 11 + common/string_util.h | 2 +- world/CMakeLists.txt | 2 + world/expedition.cpp | 136 +++ world/expedition.h | 35 + world/main.cpp | 7 + world/zoneserver.cpp | 39 + zone/CMakeLists.txt | 8 + zone/client.cpp | 301 +++++- zone/client.h | 33 + zone/client_packet.cpp | 118 +++ zone/client_packet.h | 10 + zone/client_process.cpp | 18 + zone/command.cpp | 54 + zone/command.h | 1 + zone/expedition.cpp | 1587 +++++++++++++++++++++++++++++ zone/expedition.h | 188 ++++ zone/expedition_database.cpp | 608 +++++++++++ zone/expedition_database.h | 70 ++ zone/expedition_lockout_timer.cpp | 74 ++ zone/expedition_lockout_timer.h | 64 ++ zone/expedition_request.cpp | 370 +++++++ zone/expedition_request.h | 76 ++ zone/string_ids.h | 37 +- zone/worldserver.cpp | 34 +- zone/zone.cpp | 8 + zone/zone.h | 4 + 31 files changed, 4011 insertions(+), 12 deletions(-) create mode 100644 world/expedition.cpp create mode 100644 world/expedition.h create mode 100644 zone/expedition.cpp create mode 100644 zone/expedition.h create mode 100644 zone/expedition_database.cpp create mode 100644 zone/expedition_database.h create mode 100644 zone/expedition_lockout_timer.cpp create mode 100644 zone/expedition_lockout_timer.h create mode 100644 zone/expedition_request.cpp create mode 100644 zone/expedition_request.h diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 80cf9851f..2ca06319a 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -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", }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index 514f37e21..b10521eee 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -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) diff --git a/common/ruletypes.h b/common/ruletypes.h index e46439184..f1ea792de 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -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 diff --git a/common/servertalk.h b/common/servertalk.h index 5ed3b3306..6efb20eb8 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -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 diff --git a/common/string_util.cpp b/common/string_util.cpp index 680c2822b..9190207ee 100644 --- a/common/string_util.cpp +++ b/common/string_util.cpp @@ -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; +} diff --git a/common/string_util.h b/common/string_util.h index 0d602e395..226e21645 100644 --- a/common/string_util.h +++ b/common/string_util.h @@ -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 diff --git a/world/CMakeLists.txt b/world/CMakeLists.txt index ab14ed603..a0830c6a2 100644 --- a/world/CMakeLists.txt +++ b/world/CMakeLists.txt @@ -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 diff --git a/world/expedition.cpp b/world/expedition.cpp new file mode 100644 index 000000000..e871ff25c --- /dev/null +++ b/world/expedition.cpp @@ -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(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(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(pack->pBuffer); + + // not efficient but only requested during caching + char zone_name[64] = {0}; + std::vector 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); +} diff --git a/world/expedition.h b/world/expedition.h new file mode 100644 index 000000000..bfd1250ed --- /dev/null +++ b/world/expedition.h @@ -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 diff --git a/world/main.cpp b/world/main.cpp index b94ecea4c..d7ac2d91e 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -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()) { diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 5e2268755..d39b3288d 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -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(pack->pBuffer); + client_list.SendPacket(buf->character_name, pack); + break; + } + case ServerOP_CZClientMessageString: + { + auto buf = reinterpret_cast(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); diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index beacca565..81112445e 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -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 diff --git a/zone/client.cpp b/zone/client.cpp index 821c14570..5f513f4bb 100644 --- a/zone/client.cpp +++ b/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(new EQApplicationPacket(OP_FormattedMessage, outsize)); + auto outbuf = reinterpret_cast(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(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(message.size()) + 1; + uint32_t pack_size = sizeof(ServerCZClientMessage_Struct) + msg_size; + auto pack = std::unique_ptr(new ServerPacket(ServerOP_CZClientMessage, pack_size)); + auto buf = reinterpret_cast(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& 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(parameter_buffer.size()); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_CZClientMessageString, pack_size)); + auto buf = reinterpret_cast(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(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 Client::GetExpeditionLockouts(const std::string& expedition_name) +{ + std::vector 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(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 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(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(new EQApplicationPacket(OP_DzExpeditionLockoutTimers, outsize)); + auto outbuf = reinterpret_cast(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()); +} diff --git a/zone/client.h b/zone/client.h index 13f7b81b6..b9e47b903 100644 --- a/zone/client.h +++ b/zone/client.h @@ -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& 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& GetExpeditionLockouts() const { return m_expedition_lockouts; }; + std::vector 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 m_expedition_lockouts; + #ifdef BOTS public: diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index ed1ec0bdb..798a4c552 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -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(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(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(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(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(app->pBuffer); + expedition->DzRemovePlayer(this, dzcmd->name); + } +} + +void Client::Handle_OP_DzSwapPlayer(const EQApplicationPacket *app) +{ + auto expedition = GetExpedition(); + if (expedition) + { + auto dzcmd = reinterpret_cast(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(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)) diff --git a/zone/client_packet.h b/zone/client_packet.h index 9605dc8cf..3951a1761 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -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); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index ba62d470d..c898c249f 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -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 */ diff --git a/zone/command.cpp b/zone/command.cpp index d56325725..8a67846e0 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -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(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(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 - destroy expedition globally (must be in cache)"); + } +} + void command_editmassrespawn(Client* c, const Seperator* sep) { if (strcasecmp(sep->arg[1], "usage") == 0) { diff --git a/zone/command.h b/zone/command.h index 87e01ae49..aebe8a697 100644 --- a/zone/command.h +++ b/zone/command.h @@ -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); diff --git a/zone/expedition.cpp b/zone/expedition.cpp new file mode 100644 index 000000000..95d01577e --- /dev/null +++ b/zone/expedition.cpp @@ -0,0 +1,1587 @@ +/** + * 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 "expedition_database.h" +#include "expedition_lockout_timer.h" +#include "expedition_request.h" +#include "client.h" +#include "groups.h" +#include "raids.h" +#include "string_ids.h" +#include "zonedb.h" +#include "worldserver.h" +#include "../common/eqemu_logsys.h" + +extern WorldServer worldserver; +extern Zone* zone; + +// message string 8271 (not in emu clients) +const char* const DZ_YOU_NOT_ASSIGNED = "You could not use this command because you are not currently assigned to a dynamic zone."; +// message string 9265 (not in emu clients) +const char* const EXPEDITION_OTHER_BELONGS = "{} attempted to create an expedition but {} already belongs to one."; +// lockout warnings were added to live in March 11 2020 patch +const char* const DZADD_INVITE_WARNING = "Warning! You will be given replay timers for the following events if you enter %s:"; +const char* const DZADD_INVITE_WARNING_TIMER = "%s - %sD:%sH:%sM"; +const char* const KICKPLAYERS_EVERYONE = "Everyone"; + +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 +) : + m_id(id), + m_expedition_name(expedition_name), + m_leader(leader), + m_min_players(min_players), + m_max_players(max_players), + m_has_replay_timer(replay_timer) +{ +} + +Expedition* Expedition::TryCreate( + Client* requester, std::string name, uint32_t min_players, uint32_t max_players, bool replay_timer) +{ + if (!requester || !zone) + { + return nullptr; + } + + // request parses leader, members list, and lockouts while validating + ExpeditionRequest request(requester, name, min_players, max_players, replay_timer); + if (!request.Validate()) + { + LogExpeditionsModerate("Creation of [{}] by [{}] denied", name, requester->GetName()); + return nullptr; + } + + ExpeditionMember leader{ request.GetLeaderID(), request.GetLeaderName() }; + + // 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 + ); + + if (expedition_id) + { + auto expedition = std::make_unique( + expedition_id, name, leader, min_players, max_players, replay_timer + ); + + LogExpeditions( + "Created [{}] ({}) leader: [{}] minplayers: [{}] maxplayers: [{}]", + expedition->GetID(), + expedition->GetName(), + expedition->GetLeaderName(), + expedition->GetMinPlayers(), + expedition->GetMaxPlayers() + ); + + expedition->SaveMembers(request); + expedition->SaveLockouts(request); + expedition->SendUpdatesToZoneMembers(); + expedition->SendWorldExpeditionUpdate(); // cache in other zones + + Client* leader_client = request.GetLeaderClient(); + + Client::SendCrossZoneMessageString( + leader_client, leader.name, Chat::Yellow, EXPEDITION_AVAILABLE, { name } + ); + + auto inserted = zone->expedition_cache.emplace(expedition_id, std::move(expedition)); + return inserted.first->second.get(); + } + + return nullptr; +} + +void Expedition::CacheExpeditions(MySQLRequestResult& results) +{ + if (!results.Success() || !zone) + { + return; + } + + uint32_t last_expedition_id = 0; + for (auto row = results.begin(); row != results.end(); ++row) + { + auto expedition_id = strtoul(row[0], nullptr, 10); + if (expedition_id != last_expedition_id) + { + auto leader_id = static_cast(strtoul(row[3], nullptr, 10)); + ExpeditionMember leader{ leader_id, row[7] }; // id, name + + std::unique_ptr expedition = std::make_unique( + expedition_id, + 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(); + + // don't bother caching empty expeditions + if (expedition->GetMemberCount() > 0) + { + expedition->SendWorldGetOnlineMembers(); + zone->expedition_cache.emplace(expedition_id, std::move(expedition)); + } + } + + last_expedition_id = expedition_id; + + // optional lockouts from left join + if (row[8] && row[9] && row[10] && row[11]) + { + auto it = zone->expedition_cache.find(last_expedition_id); + if (it != zone->expedition_cache.end()) + { + it->second->AddInternalLockout(ExpeditionLockoutTimer{ + row[2], // expedition_name + row[8], // event_name + strtoull(row[9], nullptr, 10), // expire_time + static_cast(strtoul(row[10], nullptr, 10)), // original duration + (strtoul(row[11], nullptr, 10) != 0) // is_inherited + }); + } + } + } +} + +void Expedition::CacheFromDatabase(uint32_t expedition_id) +{ + if (zone) + { + auto start = std::chrono::steady_clock::now(); + + auto results = ExpeditionDatabase::LoadExpedition(expedition_id); + if (!results.Success()) + { + LogExpeditions("Failed to load Expedition [{}] for zone cache", expedition_id); + return; + } + + CacheExpeditions(results); + + auto end = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast>(end - start); + LogExpeditions("Caching new expedition [{}] took {}s", expedition_id, elapsed.count()); + } +} + +bool Expedition::CacheAllFromDatabase() +{ + if (!zone) + { + return false; + } + + auto start = std::chrono::steady_clock::now(); + + zone->expedition_cache.clear(); + + // load all active expeditions and members to current zone cache + auto results = ExpeditionDatabase::LoadAllExpeditions(); + if (!results.Success()) + { + LogExpeditions("Failed to load Expeditions for zone cache"); + return false; + } + + CacheExpeditions(results); + + 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()); + + return true; +} + +void Expedition::LoadAllClientLockouts(Client* client) +{ + if (!client) + { + return; + } + + auto results = ExpeditionDatabase::LoadCharacterLockouts(client->CharacterID()); + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + auto expire_time = strtoull(row[0], nullptr, 10); + auto original_duration = static_cast(strtoul(row[1], nullptr, 10)); + ExpeditionLockoutTimer lockout{ row[2], row[3], expire_time, original_duration }; + client->AddExpeditionLockout(lockout); + } + } + client->SendExpeditionLockoutTimers(); +} + +void Expedition::LoadMembers() +{ + m_members.clear(); + + auto results = ExpeditionDatabase::LoadExpeditionMembers(m_id); + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + 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); + } + } +} + +void Expedition::SaveLockouts(ExpeditionRequest& request) +{ + m_lockouts = std::move(request).TakeLockouts(); + ExpeditionDatabase::InsertLockouts(m_id, m_lockouts); +} + +void Expedition::SaveMembers(ExpeditionRequest& request) +{ + m_members = std::move(request).TakeMembers(); + for (const auto& member : m_members) + { + m_member_id_history.emplace(member.char_id); + } + ExpeditionDatabase::InsertMembers(m_id, m_members); +} + +Expedition* Expedition::FindCachedExpeditionByCharacterID(uint32_t character_id) +{ + if (zone) + { + for (const auto& expedition : zone->expedition_cache) + { + if (expedition.second->HasMember(character_id)) + { + return expedition.second.get(); + } + } + } + return nullptr; +} + +Expedition* Expedition::FindCachedExpeditionByCharacterName(const std::string& char_name) +{ + if (zone) + { + for (const auto& expedition : zone->expedition_cache) + { + if (expedition.second->HasMember(char_name)) + { + return expedition.second.get(); + } + } + } + return nullptr; +} + +Expedition* Expedition::FindCachedExpeditionByID(uint32_t expedition_id) +{ + if (zone && expedition_id) + { + auto expedition_cache_iter = zone->expedition_cache.find(expedition_id); + if (expedition_cache_iter != zone->expedition_cache.end()) + { + return expedition_cache_iter->second.get(); + } + } + return nullptr; +} + +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); +} + +bool Expedition::HasLockout(const std::string& event_name) +{ + return (m_lockouts.find(event_name) != m_lockouts.end()); +} + +bool Expedition::HasReplayLockout() +{ + return (m_lockouts.find(DZ_REPLAY_TIMER_NAME) != m_lockouts.end()); +} + +bool Expedition::HasMember(uint32_t character_id) +{ + for (const auto& member : m_members) + { + if (member.char_id == character_id) + { + return true; + } + } + return false; +} + +bool Expedition::HasMember(const std::string& name) +{ + for (const auto& member : m_members) + { + if (strcasecmp(member.name.c_str(), name.c_str()) == 0) + { + return true; + } + } + return false; +} + +ExpeditionMember Expedition::GetMemberData(uint32_t character_id) +{ + ExpeditionMember member_data; + for (const auto& member : m_members) + { + if (member.char_id == character_id) + { + member_data = member; + break; + } + } + return member_data; +} + +ExpeditionMember Expedition::GetMemberData(const std::string& character_name) +{ + ExpeditionMember member_data; + for (const auto& member : m_members) + { + if (strcasecmp(member.name.c_str(), character_name.c_str()) == 0) + { + member_data = member; + break; + } + } + return member_data; +} + +void Expedition::AddReplayLockout(uint32_t seconds) +{ + AddLockout(DZ_REPLAY_TIMER_NAME, seconds); +} + +void Expedition::AddLockout(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(std::chrono::system_clock::to_time_t(expire_at)); + + // both expedition and current members get the lockout data, expirations updated on duplicates + ExpeditionLockoutTimer lockout{ m_expedition_name, event_name, expire_time, seconds }; + + ExpeditionDatabase::InsertLockout(m_id, lockout); + ExpeditionDatabase::InsertMembersLockout(m_members, lockout); + + ProcessLockoutUpdate(event_name, expire_time, seconds, false); + SendWorldLockoutUpdate(event_name, expire_time, seconds); +} + +void Expedition::RemoveLockout(const std::string& event_name) +{ + ExpeditionDatabase::DeleteLockout(m_id, event_name); + ExpeditionDatabase::DeleteMembersLockout(m_members, m_expedition_name, event_name); + + ProcessLockoutUpdate(event_name, 0, 0, true); + SendWorldLockoutUpdate(event_name, 0, 0, true); +} + +void Expedition::AddInternalLockout(ExpeditionLockoutTimer&& lockout_timer) +{ + m_lockouts.emplace(lockout_timer.GetEventName(), std::move(lockout_timer)); +} + +void Expedition::AddInternalMember( + const std::string& char_name, uint32_t character_id, bool is_current_member, bool offline) +{ + if (is_current_member) + { + auto it = std::find_if(m_members.begin(), m_members.end(), + [character_id](const ExpeditionMember& member) { + return member.char_id == character_id; + }); + + if (it == m_members.end()) + { + auto status = offline ? ExpeditionMemberStatus::Offline : ExpeditionMemberStatus::Online; + m_members.emplace_back(ExpeditionMember{character_id, char_name, status}); + } + } + + m_member_id_history.emplace(character_id); +} + +bool Expedition::AddMember(const std::string& add_char_name, uint32_t add_char_id) +{ + if (HasMember(add_char_id)) + { + return false; + } + + ExpeditionDatabase::InsertMember(m_id, add_char_id); + + ProcessMemberAdded(add_char_name, add_char_id); + SendWorldMemberChanged(add_char_name, add_char_id, false); + + return true; +} + +void Expedition::RemoveAllMembers() +{ + ExpeditionDatabase::DeleteAllMembers(m_id); + ExpeditionDatabase::DeleteExpedition(m_id); + + SendUpdatesToZoneMembers(true); + SendWorldExpeditionUpdate(true); +} + +bool Expedition::RemoveMember(const std::string& remove_char_name) +{ + auto member = GetMemberData(remove_char_name); + if (member.char_id == 0 || member.name.empty()) + { + return false; + } + + ExpeditionDatabase::UpdateMemberRemoved(m_id, member.char_id); + + ProcessMemberRemoved(member.name, member.char_id); + SendWorldMemberChanged(member.name, member.char_id, true); + + // live always sends a leader update but we can send only if leader changes + if (!m_members.empty() && member.char_id == m_leader.char_id) + { + ChooseNewLeader(); + } + + if (m_members.empty()) + { + // cache removal will occur in world message handler + ExpeditionDatabase::DeleteExpedition(m_id); + } + + return true; +} + +void Expedition::SwapMember(Client* add_client, const std::string& remove_char_name) +{ + if (!add_client || remove_char_name.empty()) + { + return; + } + + auto member = GetMemberData(remove_char_name); + if (member.char_id == 0 || member.name.empty()) + { + return; + } + + // 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()); + + ProcessMemberRemoved(member.name, member.char_id); + ProcessMemberAdded(add_client->GetName(), add_client->CharacterID()); + SendWorldMemberSwapped(member.name, member.char_id, add_client->GetName(), add_client->CharacterID()); + + if (!m_members.empty() && member.char_id == m_leader.char_id) + { + ChooseNewLeader(); + } +} + +void Expedition::SetMemberStatus(Client* client, ExpeditionMemberStatus status) +{ + if (client) + { + UpdateMemberStatus(client->CharacterID(), status); + SendWorldMemberStatus(client->CharacterID(), status); + } +} + +void Expedition::UpdateMemberStatus(uint32_t update_member_id, ExpeditionMemberStatus status) +{ + auto member_data = GetMemberData(update_member_id); + if (member_data.char_id == 0 || member_data.name.empty()) + { + return; + } + + auto outapp_member_status = CreateMemberListStatusPacket(member_data.name, status); + + for (auto& member : m_members) + { + if (member.char_id == update_member_id) + { + member.status = status; + } + + Client* member_client = entity_list.GetClientByCharID(member.char_id); + if (member_client) + { + member_client->QueuePacket(outapp_member_status.get()); + } + } +} + +bool Expedition::ChooseNewLeader() +{ + for (const auto& member : m_members) + { + if (member.char_id != m_leader.char_id) + { + LogExpeditionsModerate("Replacing leader [{}] with [{}]", m_leader.name, member.name); + SetNewLeader(member.char_id, member.name); + return true; + } + } + return false; +} + +void Expedition::SendClientExpeditionInvite( + Client* client, const std::string& inviter_name, const std::string& swap_remove_name) +{ + if (!client) + { + return; + } + + LogExpeditionsModerate( + "Sending expedition [{}] invite to player [{}] inviter [{}] swap name [{}]", + m_id, client->GetName(), inviter_name, swap_remove_name + ); + + client->SetPendingExpeditionInvite(m_id); + + client->MessageString( + Chat::System, EXPEDITION_ASKED_TO_JOIN, m_leader.name.c_str(), m_expedition_name.c_str() + ); + + // live (as of March 11 2020 patch) sends warnings for lockouts added + // during current expedition that client would receive on entering dz + bool warned = false; + for (const auto& lockout_iter : m_lockouts) + { + // live doesn't issue a warning for the dz's replay timer + const ExpeditionLockoutTimer& lockout = lockout_iter.second; + if (!lockout.IsInherited() && !lockout.IsReplayTimer() && + !client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) + { + if (!warned) + { + client->Message(Chat::System, DZADD_INVITE_WARNING, m_expedition_name.c_str()); + warned = true; + } + + auto time_remaining = lockout.GetDaysHoursMinutesRemaining(); + client->Message( + Chat::System, DZADD_INVITE_WARNING_TIMER, + lockout.GetEventName().c_str(), + time_remaining.days.c_str(), + time_remaining.hours.c_str(), + time_remaining.mins.c_str() + ); + } + } + + auto outapp = CreateInvitePacket(inviter_name, swap_remove_name); + client->QueuePacket(outapp.get()); +} + +void Expedition::SendLeaderMessage( + Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& parameters) +{ + Client::SendCrossZoneMessageString(leader_client, m_leader.name, chat_type, string_id, parameters); +} + +bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping) +{ + if (!add_client) // a null leader_client handled by SendLeaderMessage fallback + { + return true; + } + + bool has_conflict = false; + + auto expedition_id = add_client->GetExpeditionID(); + if (expedition_id) + { + auto string_id = (expedition_id == GetID()) ? DZADD_ALREADY_PART : DZADD_ALREADY_ASSIGNED; + SendLeaderMessage(leader_client, Chat::Red, string_id, { add_client->GetName() }); + has_conflict = true; + } + + // client with a replay lockout is allowed only if they were a previous member + auto member_iter = m_member_id_history.find(add_client->CharacterID()); + bool was_member = (member_iter != m_member_id_history.end()); + if (!was_member && m_has_replay_timer) + { + auto replay_lockout = add_client->GetExpeditionLockout(m_expedition_name, DZ_REPLAY_TIMER_NAME); + if (replay_lockout) + { + has_conflict = true; + + auto time_remaining = replay_lockout->GetDaysHoursMinutesRemaining(); + SendLeaderMessage(leader_client, Chat::Red, DZADD_REPLAY_TIMER, { + add_client->GetName(), + time_remaining.days, + time_remaining.hours, + time_remaining.mins + }); + } + } + + // check any extra event lockouts for this expedition that the client has and leader doesn't + auto client_lockouts = add_client->GetExpeditionLockouts(m_expedition_name); + for (const auto& client_lockout : client_lockouts) + { + bool is_missing_lockout = (m_lockouts.find(client_lockout.GetEventName()) == m_lockouts.end()); + if (!client_lockout.IsReplayTimer() && is_missing_lockout) + { + has_conflict = true; + + auto time_remaining = client_lockout.GetDaysHoursMinutesRemaining(); + SendLeaderMessage(leader_client, Chat::Red, DZADD_EVENT_TIMER, { + add_client->GetName(), + client_lockout.GetEventName(), + time_remaining.days, + time_remaining.hours, + time_remaining.mins, + client_lockout.GetEventName() + }); + } + } + + // swapping ignores the max player count check since it's a 1:1 change + if (!swapping && GetMemberCount() >= m_max_players) + { + SendLeaderMessage(leader_client, Chat::Red, DZADD_EXCEED_MAX, { fmt::format_int(m_max_players).str() }); + has_conflict = true; + } + + auto invite_id = add_client->GetPendingExpeditionInviteID(); + if (invite_id) + { + auto string_id = (invite_id == GetID()) ? DZADD_PENDING : DZADD_PENDING_OTHER; + SendLeaderMessage(leader_client, Chat::Red, string_id, { add_client->GetName() }); + has_conflict = true; + } + + return has_conflict; +} + +void Expedition::DzInviteResponse( + Client* add_client, bool accepted, bool has_swap_name, std::string swap_remove_name) +{ + if (!add_client) + { + return; + } + + LogExpeditionsModerate( + "Invite response by [{}] accepted [{}] swapping [{}] swap_name [{}]", + add_client->GetName(), accepted, has_swap_name, swap_remove_name + ); + + // if client accepts the invite we need to re-confirm there's no conflicts + // 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) + { + 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)) + { + has_conflicts = true; + } + + if (accepted && !has_conflicts) + { + SendLeaderMessage(leader_client, Chat::Yellow, EXPEDITION_INVITE_ACCEPTED, { add_client->GetName() }); + } + else if (accepted) + { + SendLeaderMessage(leader_client, Chat::Red, EXPEDITION_INVITE_ERROR, { add_client->GetName() }); + } + else + { + SendLeaderMessage(leader_client, Chat::Red, EXPEDITION_INVITE_DECLINED, { 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. + ExpeditionDatabase::DeletePendingLockouts(add_client->CharacterID()); + + std::vector pending_lockouts; + for (const auto& lockout_iter : m_lockouts) + { + const ExpeditionLockoutTimer& lockout = lockout_iter.second; + if (!lockout.IsInherited() && + !add_client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) + { + // replay timers are added to characters immediately on joining with + // a fresh expire time using the original duration + if (m_has_replay_timer && lockout.IsReplayTimer()) + { + add_client->AddNewExpeditionLockout( + lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetDuration()); + } + else + { + pending_lockouts.emplace_back(lockout); + } + } + } + + ExpeditionDatabase::InsertCharacterLockouts(add_client->CharacterID(), pending_lockouts, false, true); + + if (was_swap_invite) + { + SwapMember(add_client, swap_remove_name); + } + else + { + AddMember(add_client->GetName(), add_client->CharacterID()); + } + } +} + +bool Expedition::ConfirmLeaderCommand(Client* requester) +{ + if (!requester) + { + return false; + } + + ExpeditionMember leader; + if (RuleB(Expedition, UseDatabaseToVerifyLeaderCommands)) + { + leader = ExpeditionDatabase::GetExpeditionLeader(m_id); + } + else + { + leader = m_leader; + } + + if (leader.char_id == 0) + { + requester->MessageString(Chat::Red, UNABLE_RETRIEVE_LEADER); // unconfirmed message + return false; + } + + if (leader.char_id != requester->CharacterID()) + { + requester->MessageString(Chat::Red, EXPEDITION_NOT_LEADER, leader.name.c_str()); + return false; + } + + return true; +} + +void Expedition::TryAddClient( + Client* add_client, std::string inviter_name, std::string orig_add_name, + std::string swap_remove_name, Client* leader_client) +{ + if (!add_client) + { + return; + } + + LogExpeditionsModerate( + "Add player request for expedition [{}] by inviter [{}] add name [{}] swap name [{}]", + m_id, inviter_name, orig_add_name, swap_remove_name + ); + + // null leader client handled by ProcessAddConflicts/SendLeaderMessage fallbacks + if (!leader_client) + { + leader_client = entity_list.GetClientByName(inviter_name.c_str()); + } + + bool has_conflicts = ProcessAddConflicts(leader_client, add_client, !swap_remove_name.empty()); + if (!has_conflicts) + { + // live uses the original unsanitized input string in invite messages + uint32_t string_id = swap_remove_name.empty() ? DZADD_INVITE : DZSWAP_INVITE; + SendLeaderMessage(leader_client, Chat::Yellow, string_id, { orig_add_name.c_str() }); + SendClientExpeditionInvite(add_client, inviter_name.c_str(), swap_remove_name); + } + else if (swap_remove_name.empty()) // swap command doesn't result in this message + { + SendLeaderMessage(leader_client, Chat::Red, DZADD_INVITE_FAIL, { add_client->GetName() }); + } +} + +void Expedition::DzAddPlayer( + Client* requester, std::string add_char_name, std::string swap_remove_name) +{ + if (!requester || !ConfirmLeaderCommand(requester)) + { + return; + } + + if (add_char_name.empty()) + { + requester->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_char_name.c_str()); + return; + } + + // live prioritizes the "not online" message before the "already a member" + // message but we can avoid checking world if we trust member status accuracy + // live sanitizes input except for "sending invite" and "not online" msgs + auto member_data = GetMemberData(add_char_name); + if (member_data.char_id != 0 && member_data.status != ExpeditionMemberStatus::Offline) + { + requester->MessageString(Chat::Red, DZADD_ALREADY_PART, add_char_name.c_str()); + return; + } + + Client* add_client = entity_list.GetClientByName(add_char_name.c_str()); + if (add_client) + { + // client is online in this zone + TryAddClient(add_client, requester->GetName(), add_char_name, swap_remove_name, requester); + } + else + { + // forward to world to check if client is online and perform cross-zone invite + SendWorldAddPlayerInvite(requester->GetName(), swap_remove_name, add_char_name); + } +} + +void Expedition::DzAddPlayerContinue( + std::string inviter_name, std::string add_name, std::string swap_remove_name) +{ + // continuing expedition invite from leader in another zone + Client* add_client = entity_list.GetClientByName(add_name.c_str()); + if (add_client) + { + TryAddClient(add_client, inviter_name, add_name, swap_remove_name); + } +} + +void Expedition::DzMakeLeader(Client* requester, std::string new_leader_name) +{ + if (!requester || !ConfirmLeaderCommand(requester)) + { + return; + } + + // live uses sanitized input name for all /dzmakeleader messages + new_leader_name = FormatName(new_leader_name); + + if (new_leader_name.empty()) + { + requester->MessageString(Chat::Red, DZMAKELEADER_NOT_ONLINE, new_leader_name.c_str()); + return; + } + + auto new_leader_data = GetMemberData(new_leader_name); + if (new_leader_data.char_id == 0) + { + requester->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, new_leader_name.c_str()); + return; + } + + // database is not updated until new leader client validated + Client* new_leader_client = entity_list.GetClientByName(new_leader_name.c_str()); + if (new_leader_client) + { + ProcessMakeLeader(requester, new_leader_client, new_leader_name, true); + } + else + { + // new leader not in this zone, let world verify and pass to new leader's zone + SendWorldMakeLeaderRequest(requester->GetName(), FormatName(new_leader_name)); + } +} + +void Expedition::DzRemovePlayer(Client* requester, std::string char_name) +{ + if (!requester || !ConfirmLeaderCommand(requester)) + { + return; + } + + LogExpeditionsModerate( + "Remove player request for expedition [{}] by [{}] leader [{}] remove name [{}]", + m_id, requester->GetName(), m_leader.name, char_name + ); + + char_name = FormatName(char_name); + + // live only seems to enforce min_players for requesting expeditions, no need to check here + bool removed = RemoveMember(char_name); + if (!removed) + { + requester->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, char_name.c_str()); + } + else + { + requester->MessageString(Chat::Yellow, EXPEDITION_REMOVED, char_name.c_str(), m_expedition_name.c_str()); + } +} + +void Expedition::DzQuit(Client* requester) +{ + if (requester) + { + RemoveMember(requester->GetName()); + } +} + +void Expedition::DzSwapPlayer( + Client* requester, std::string remove_char_name, std::string add_char_name) +{ + if (!requester || !ConfirmLeaderCommand(requester)) + { + return; + } + + if (remove_char_name.empty() || !HasMember(remove_char_name)) + { + remove_char_name = FormatName(remove_char_name); + requester->MessageString(Chat::Red, DZSWAP_CANNOT_REMOVE, remove_char_name.c_str()); + return; + } + + DzAddPlayer(requester, add_char_name, remove_char_name); +} + +void Expedition::DzPlayerList(Client* requester) +{ + if (requester) + { + requester->MessageString(Chat::Yellow, EXPEDITION_LEADER, m_leader.name.c_str()); + + std::string member_names; + for (const auto& member : m_members) + { + fmt::format_to(std::back_inserter(member_names), "{}, ", member.name); + } + + if (member_names.size() > 1) + { + member_names.erase(member_names.length() - 2); // trailing comma and space + } + + requester->MessageString(Chat::Yellow, EXPEDITION_MEMBERS, member_names.c_str()); + } +} + +void Expedition::DzKickPlayers(Client* requester) +{ + if (!requester || !ConfirmLeaderCommand(requester)) + { + return; + } + + RemoveAllMembers(); + requester->MessageString(Chat::Red, EXPEDITION_REMOVED, KICKPLAYERS_EVERYONE, m_expedition_name.c_str()); +} + +void Expedition::SetNewLeader(uint32_t new_leader_id, const std::string& new_leader_name) +{ + ExpeditionDatabase::UpdateLeaderID(m_id, new_leader_id); + ProcessLeaderChanged(new_leader_id, new_leader_name); + SendWorldLeaderChanged(); +} + +void Expedition::ProcessLeaderChanged(uint32_t new_leader_id, const std::string& new_leader_name) +{ + m_leader.char_id = new_leader_id; + m_leader.name = new_leader_name; + + // update each client's expedition window in this zone + auto outapp_leader = CreateLeaderNamePacket(); + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.char_id); + if (member_client) + { + member_client->QueuePacket(outapp_leader.get()); + } + } +} + +void Expedition::ProcessMakeLeader( + Client* old_leader_client, Client* new_leader_client, const std::string& new_leader_name, bool is_online) +{ + if (old_leader_client) + { + // online flag is set by world to verify new leader is online or not + if (is_online) + { + old_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_NAME, new_leader_name.c_str()); + } + else + { + old_leader_client->MessageString(Chat::Red, DZMAKELEADER_NOT_ONLINE, new_leader_name.c_str()); + } + } + + if (!new_leader_client) + { + new_leader_client = entity_list.GetClientByName(new_leader_name.c_str()); + } + + if (new_leader_client) + { + new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + SetNewLeader(new_leader_client->CharacterID(), new_leader_client->GetName()); + } +} + +void Expedition::ProcessMemberAdded(std::string char_name, uint32_t added_char_id) +{ + // adds the member to this expedition and notifies both leader and new member + Client* leader_client = entity_list.GetClientByCharID(m_leader.char_id); + if (leader_client) + { + leader_client->MessageString(Chat::Yellow, EXPEDITION_MEMBER_ADDED, char_name.c_str(), m_expedition_name.c_str()); + } + + Client* member_client = entity_list.GetClientByCharID(added_char_id); + if (member_client) + { + member_client->SetExpeditionID(GetID()); + 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); + + SendUpdatesToZoneMembers(); // live sends full update when member added +} + +void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t removed_char_id) +{ + if (m_members.empty()) + { + return; + } + + // cache a re-usable packet for each member + auto outapp_member_name = CreateMemberListNamePacket(removed_char_name, true); + + for (auto it = m_members.begin(); it != m_members.end();) + { + bool is_removed = (it->name == removed_char_name); + + Client* member_client = entity_list.GetClientByCharID(it->char_id); + if (member_client) + { + // all members receive the removed player name packet + member_client->QueuePacket(outapp_member_name.get()); + + if (is_removed) + { + ExpeditionDatabase::DeletePendingLockouts(member_client->CharacterID()); + member_client->SetExpeditionID(0); + member_client->QueuePacket(CreateInfoPacket(true).get()); + member_client->MessageString( + Chat::Yellow, EXPEDITION_REMOVED, it->name.c_str(), m_expedition_name.c_str() + ); + } + } + + it = is_removed ? m_members.erase(it) : it + 1; + } +} + +void Expedition::ProcessLockoutUpdate( + const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove) +{ + ExpeditionLockoutTimer lockout{ m_expedition_name, event_name, expire_time, duration }; + + if (!remove) + { + m_lockouts.emplace(event_name, lockout); + } + else + { + m_lockouts.erase(event_name); + } + + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.char_id); + if (member_client) + { + if (!remove) + { + member_client->AddExpeditionLockout(lockout); + } + else + { + member_client->RemoveExpeditionLockout(m_expedition_name, event_name); + } + member_client->SendExpeditionLockoutTimers(); // full client lockout list update + } + } +} + +void Expedition::SendUpdatesToZoneMembers(bool clear) +{ + if (!m_members.empty()) + { + //auto outapp_compass = CreateCompassPacket(); + auto outapp_info = CreateInfoPacket(clear); + auto outapp_members = CreateMemberListPacket(clear); + + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.char_id); + if (member_client) + { + member_client->SetExpeditionID(clear ? 0 : GetID()); + member_client->QueuePacket(outapp_info.get()); + member_client->QueuePacket(outapp_members.get()); + member_client->SendExpeditionLockoutTimers(); + if (clear) + { + member_client->MessageString( + Chat::Yellow, EXPEDITION_REMOVED, member_client->GetName(), m_expedition_name.c_str() + ); + } + } + } + } +} + +void Expedition::SendClientExpeditionInfo(Client* client) +{ + if (client) + { + client->QueuePacket(CreateInfoPacket().get()); + client->QueuePacket(CreateMemberListPacket().get()); + } +} + +std::unique_ptr Expedition::CreateInfoPacket(bool clear) +{ + uint32_t outsize = sizeof(ExpeditionInfo_Struct); + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzExpeditionInfo, outsize)); + auto info = reinterpret_cast(outapp->pBuffer); + if (!clear) + { + info->client_id = 0; + info->assigned = true; + strn0cpy(info->expedition_name, m_expedition_name.c_str(), sizeof(info->expedition_name)); + strn0cpy(info->leader_name, m_leader.name.c_str(), sizeof(info->leader_name)); + info->max_players = m_max_players; + } + return outapp; +} + +std::unique_ptr Expedition::CreateInvitePacket( + const std::string& inviter_name, const std::string& swap_remove_name) +{ + uint32_t outsize = sizeof(ExpeditionInvite_Struct); + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzExpeditionInvite, outsize)); + auto outbuf = reinterpret_cast(outapp->pBuffer); + strn0cpy(outbuf->inviter_name, inviter_name.c_str(), sizeof(outbuf->inviter_name)); + 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(); + return outapp; +} + +std::unique_ptr Expedition::CreateMemberListPacket(bool clear) +{ + uint32_t member_count = clear ? 0 : static_cast(m_members.size()); + uint32_t member_entries_size = sizeof(ExpeditionMemberEntry_Struct) * member_count; + uint32_t outsize = sizeof(ExpeditionMemberList_Struct) + member_entries_size; + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzMemberList, outsize)); + auto buf = reinterpret_cast(outapp->pBuffer); + + buf->client_id = 0; + buf->count = member_count; + + if (!clear) + { + for (auto i = 0; i < m_members.size(); ++i) + { + strn0cpy(buf->members[i].name, m_members[i].name.c_str(), sizeof(buf->members[i].name)); + buf->members[i].status = static_cast(m_members[i].status); + } + } + + return outapp; +} + +std::unique_ptr Expedition::CreateMemberListNamePacket( + const std::string& name, bool remove_name) +{ + uint32_t outsize = sizeof(ExpeditionMemberListName_Struct); + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzMemberListName, outsize)); + auto buf = reinterpret_cast(outapp->pBuffer); + buf->client_id = 0; + buf->add_name = !remove_name; + strn0cpy(buf->name, name.c_str(), sizeof(buf->name)); + return outapp; +} + +std::unique_ptr Expedition::CreateMemberListStatusPacket( + const std::string& name, ExpeditionMemberStatus status) +{ + // member list status uses member list struct with a single entry + uint32_t outsize = sizeof(ExpeditionMemberList_Struct) + sizeof(ExpeditionMemberEntry_Struct); + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzMemberListStatus, outsize)); + auto buf = reinterpret_cast(outapp->pBuffer); + buf->client_id = 0; + buf->count = 1; + + auto entry = reinterpret_cast(buf->members); + strn0cpy(entry->name, name.c_str(), sizeof(entry->name)); + entry->status = static_cast(status); + + return outapp; +} + +std::unique_ptr Expedition::CreateLeaderNamePacket() +{ + uint32_t outsize = sizeof(ExpeditionSetLeaderName_Struct); + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzSetLeaderName, outsize)); + auto buf = reinterpret_cast(outapp->pBuffer); + buf->client_id = 0; + strn0cpy(buf->leader_name, m_leader.name.c_str(), sizeof(buf->leader_name)); + return outapp; +} + +void Expedition::SendWorldExpeditionUpdate(bool destroyed) +{ + uint16_t opcode = destroyed ? ServerOP_ExpeditionDeleted : ServerOP_ExpeditionCreate; + uint32_t pack_size = sizeof(ServerExpeditionID_Struct); + auto pack = std::unique_ptr(new ServerPacket(opcode, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + worldserver.SendPacket(pack.get()); +} + +void Expedition::SendWorldAddPlayerInvite( + const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name) +{ + uint32_t pack_size = sizeof(ServerDzCommand_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDzAddPlayer, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->is_char_online = false; + strn0cpy(buf->requester_name, inviter_name.c_str(), sizeof(buf->requester_name)); + strn0cpy(buf->target_name, add_name.c_str(), sizeof(buf->target_name)); + strn0cpy(buf->remove_name, swap_remove_name.c_str(), sizeof(buf->remove_name)); + worldserver.SendPacket(pack.get()); +} + +void Expedition::SendWorldLeaderChanged() +{ + uint32_t pack_size = sizeof(ServerExpeditionMemberChange_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionLeaderChanged, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->char_id = m_leader.char_id; + strn0cpy(buf->char_name, m_leader.name.c_str(), sizeof(buf->char_name)); + worldserver.SendPacket(pack.get()); +} + +void Expedition::SendWorldLockoutUpdate( + const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove) +{ + uint32_t pack_size = sizeof(ServerExpeditionLockout_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionLockout, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->expire_time = expire_time; + buf->duration = duration; + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->remove = remove; + strn0cpy(buf->event_name, event_name.c_str(), sizeof(buf->event_name)); + worldserver.SendPacket(pack.get()); +} + +void Expedition::SendWorldMakeLeaderRequest( + const std::string& requester_name, const std::string& new_leader_name) +{ + uint32_t pack_size = sizeof(ServerDzCommand_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDzMakeLeader, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->is_char_online = false; + strn0cpy(buf->requester_name, requester_name.c_str(), sizeof(buf->requester_name)); + strn0cpy(buf->target_name, new_leader_name.c_str(), sizeof(buf->target_name)); + worldserver.SendPacket(pack.get()); +} + +void Expedition::SendWorldMemberChanged(const std::string& char_name, uint32_t char_id, bool remove) +{ + // notify other zones of added or removed member + uint32_t pack_size = sizeof(ServerExpeditionMemberChange_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionMemberChange, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->removed = remove; + buf->char_id = char_id; + strn0cpy(buf->char_name, char_name.c_str(), sizeof(buf->char_name)); + worldserver.SendPacket(pack.get()); +} + +void Expedition::SendWorldMemberStatus(uint32_t character_id, ExpeditionMemberStatus status) +{ + uint32_t pack_size = sizeof(ServerExpeditionMemberStatus_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionMemberStatus, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->status = static_cast(status); + buf->character_id = character_id; + 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) +{ + uint32_t pack_size = sizeof(ServerExpeditionMemberSwap_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionMemberSwap, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->add_char_id = add_char_id; + buf->remove_char_id = remove_char_id; + strn0cpy(buf->add_char_name, add_char_name.c_str(), sizeof(buf->add_char_name)); + strn0cpy(buf->remove_char_name, remove_char_name.c_str(), sizeof(buf->remove_char_name)); + worldserver.SendPacket(pack.get()); +} + +void Expedition::SendWorldGetOnlineMembers() +{ + // request online status of all characters in our expedition tracked by world + uint32_t count = static_cast(m_members.size()); + uint32_t entries_size = sizeof(ServerExpeditionCharacterEntry_Struct) * count; + uint32_t pack_size = sizeof(ServerExpeditionCharacters_Struct) + entries_size; + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionGetOnlineMembers, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->count = count; + for (uint32_t i = 0; i < buf->count; ++i) + { + buf->entries[i].character_id = m_members[i].char_id; + buf->entries[i].character_zone_id = 0; + buf->entries[i].character_instance_id = 0; + buf->entries[i].character_online = false; + } + worldserver.SendPacket(pack.get()); +} + +void Expedition::HandleWorldMessage(ServerPacket* pack) +{ + switch (pack->opcode) + { + case ServerOP_ExpeditionCreate: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + Expedition::CacheFromDatabase(buf->expedition_id); + } + break; + } + 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 (expedition) + { + expedition->SendUpdatesToZoneMembers(true); + } + } + break; + } + case ServerOP_ExpeditionLeaderChanged: + { + 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 (expedition) + { + expedition->ProcessLeaderChanged(buf->char_id, buf->char_name); + } + } + break; + } + case ServerOP_ExpeditionLockout: + { + 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 (expedition) + { + expedition->ProcessLockoutUpdate(buf->event_name, buf->expire_time, buf->duration, buf->remove); + } + } + break; + } + case ServerOP_ExpeditionMemberChange: + { + auto buf = reinterpret_cast(pack->pBuffer); + + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition && zone) + { + if (!zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + { + if (buf->removed) + { + expedition->ProcessMemberRemoved(buf->char_name, buf->char_id); + } + else + { + expedition->ProcessMemberAdded(buf->char_name, buf->char_id); + } + } + + // remove this expedition from zone cache if last member was removed + if (buf->removed && expedition->GetMemberCount() == 0) + { + zone->expedition_cache.erase(buf->expedition_id); + } + } + break; + } + case ServerOP_ExpeditionMemberSwap: + { + 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 (expedition) + { + expedition->ProcessMemberRemoved(buf->remove_char_name, buf->remove_char_id); + expedition->ProcessMemberAdded(buf->add_char_name, buf->add_char_id); + } + } + break; + } + case ServerOP_ExpeditionMemberStatus: + { + 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 (expedition) + { + expedition->UpdateMemberStatus(buf->character_id, static_cast(buf->status)); + } + } + break; + } + case ServerOP_ExpeditionGetOnlineMembers: + { + // reply from world for online member statuses request + auto buf = reinterpret_cast(pack->pBuffer); + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition) + { + for (uint32_t i = 0; i < buf->count; ++i) + { + auto entry = reinterpret_cast(&buf->entries[i]); + auto is_online = entry->character_online; + auto status = is_online ? ExpeditionMemberStatus::Online : ExpeditionMemberStatus::Offline; + if (is_online && expedition->GetInstanceID() == entry->character_instance_id) + { + status = ExpeditionMemberStatus::InDynamicZone; + } + expedition->UpdateMemberStatus(entry->character_id, status); + } + } + break; + } + case ServerOP_ExpeditionDzAddPlayer: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (buf->is_char_online) + { + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition) + { + expedition->DzAddPlayerContinue(buf->requester_name, buf->target_name, buf->remove_name); + } + } + else + { + Client* leader = entity_list.GetClientByName(buf->requester_name); + if (leader) + { + leader->MessageString(Chat::Red, DZADD_NOT_ONLINE, FormatName(buf->target_name).c_str()); + } + } + break; + } + case ServerOP_ExpeditionDzMakeLeader: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition) + { + auto old_leader_client = entity_list.GetClientByName(buf->requester_name); + auto new_leader_client = entity_list.GetClientByName(buf->target_name); + expedition->ProcessMakeLeader(old_leader_client, new_leader_client, buf->target_name, buf->is_char_online); + } + break; + } + } +} diff --git a/zone/expedition.h b/zone/expedition.h new file mode 100644 index 000000000..cebe46e7f --- /dev/null +++ b/zone/expedition.h @@ -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 +#include +#include +#include +#include +#include + +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(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& GetLockouts() const { return m_lockouts; } + const std::vector& 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& 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 CreateInfoPacket(bool clear = false); + std::unique_ptr CreateInvitePacket(const std::string& inviter_name, const std::string& swap_remove_name); + std::unique_ptr CreateMemberListPacket(bool clear = false); + std::unique_ptr CreateMemberListNamePacket(const std::string& name, bool remove_name); + std::unique_ptr CreateMemberListStatusPacket(const std::string& name, ExpeditionMemberStatus status); + 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; + 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 + std::unordered_map m_lockouts; +}; + +#endif diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp new file mode 100644 index 000000000..e8ef8848e --- /dev/null +++ b/zone/expedition_database.cpp @@ -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 + +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& 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& 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& 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& 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& 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); + } +} diff --git a/zone/expedition_database.h b/zone/expedition_database.h new file mode 100644 index 000000000..e16a94e1e --- /dev/null +++ b/zone/expedition_database.h @@ -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 +#include +#include +#include +#include +#include + +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& 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& lockouts, + bool update_expire_times, bool is_pending = false); + void InsertMembersLockout(const std::vector& members, const ExpeditionLockoutTimer& lockout); + void InsertLockout(uint32_t expedition_id, const ExpeditionLockoutTimer& lockout); + void InsertLockouts(uint32_t expedition_id, const std::unordered_map& lockouts); + void InsertMember(uint32_t expedition_id, uint32_t character_id); + void InsertMembers(uint32_t expedition_id, const std::vector& members); + void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); + void UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id); +}; + +#endif diff --git a/zone/expedition_lockout_timer.cpp b/zone/expedition_lockout_timer.cpp new file mode 100644 index 000000000..e7eb8ea76 --- /dev/null +++ b/zone/expedition_lockout_timer.cpp @@ -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 +#include + +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(expire_time - now).count(); + return static_cast(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; +} diff --git a/zone/expedition_lockout_timer.h b/zone/expedition_lockout_timer.h new file mode 100644 index 000000000..ff70864c4 --- /dev/null +++ b/zone/expedition_lockout_timer.h @@ -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 + +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 diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp new file mode 100644 index 000000000..5cf049f60 --- /dev/null +++ b/zone/expedition_request.cpp @@ -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 + +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>(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 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(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& 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; +} diff --git a/zone/expedition_request.h b/zone/expedition_request.h new file mode 100644 index 000000000..9c57bbb9e --- /dev/null +++ b/zone/expedition_request.h @@ -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 +#include +#include +#include + +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 TakeMembers() && { return std::move(m_members); } + std::unordered_map 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& 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 m_members; + std::unordered_map m_lockouts; +}; + +#endif diff --git a/zone/string_ids.h b/zone/string_ids.h index b7ee5ddd4..4a2263cda 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -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. diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index d4fa5e572..2088cef05 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -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(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(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; diff --git a/zone/zone.cpp b/zone/zone.cpp index 66644edf1..a8a2e039e 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -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); +} diff --git a/zone/zone.h b/zone/zone.h index 824140700..f8d0fbbbb 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -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 zone_grids; std::vector zone_grid_entries; + std::unordered_map> expedition_cache; + time_t weather_timer; Timer spawn2_timer; Timer hot_reload_timer; From 779850464151334c99801c14ac48c125572e80ad Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 14 Apr 2020 17:30:36 -0400 Subject: [PATCH 030/196] Add expeditions sql schema file --- utils/sql/git/required/wip_expeditions.sql | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 utils/sql/git/required/wip_expeditions.sql diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql new file mode 100644 index 000000000..052a52a0d --- /dev/null +++ b/utils/sql/git/required/wip_expeditions.sql @@ -0,0 +1,59 @@ +CREATE TABLE `expedition_details` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `instance_id` INT(10) NULL DEFAULT NULL, + `expedition_name` VARCHAR(128) NOT NULL, + `leader_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `min_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + `max_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + `has_replay_timer` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE INDEX `instance_id` (`instance_id`), + CONSTRAINT `FK_expedition_details_instance_list` FOREIGN KEY (`instance_id`) REFERENCES `instance_list` (`id`) ON DELETE SET NULL +) +COLLATE='utf8mb4_general_ci' +ENGINE=InnoDB +; + +CREATE TABLE `expedition_lockouts` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `expedition_id` INT(10) UNSIGNED NOT NULL, + `event_name` VARCHAR(256) NOT NULL, + `expire_time` DATETIME NOT NULL DEFAULT current_timestamp(), + `duration` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `is_inherited` TINYINT(4) UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE INDEX `expedition_id_event_name` (`expedition_id`, `event_name`), + CONSTRAINT `FK_expedition_lockouts_expedition_details` FOREIGN KEY (`expedition_id`) REFERENCES `expedition_details` (`id`) ON DELETE CASCADE +) +COLLATE='utf8mb4_general_ci' +ENGINE=InnoDB +; + +CREATE TABLE `expedition_members` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `expedition_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `character_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `is_current_member` TINYINT(4) UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE INDEX `expedition_id_character_id` (`expedition_id`, `character_id`), + CONSTRAINT `FK_expedition_members_expedition_details` FOREIGN KEY (`expedition_id`) REFERENCES `expedition_details` (`id`) ON DELETE CASCADE +) +COLLATE='utf8mb4_general_ci' +ENGINE=InnoDB +; + +CREATE TABLE `expedition_character_lockouts` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `character_id` INT(10) UNSIGNED NOT NULL, + `expire_time` DATETIME NOT NULL DEFAULT current_timestamp(), + `duration` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `expedition_name` VARCHAR(128) NOT NULL, + `event_name` VARCHAR(256) NOT NULL, + `is_pending` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE INDEX `character_id_expedition_name_event_name` (`character_id`, `expedition_name`, `event_name`) +) +COLLATE='utf8mb4_general_ci' +ENGINE=InnoDB +ROW_FORMAT=DYNAMIC +; From f74605d3391132123ea936a2338b4d2f34f11de7 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 14 Apr 2020 17:22:01 -0400 Subject: [PATCH 031/196] Implement Lua quest api for expeditions --- zone/CMakeLists.txt | 2 + zone/lua_client.cpp | 81 ++++++++++++++++++++- zone/lua_client.h | 11 ++- zone/lua_expedition.cpp | 152 ++++++++++++++++++++++++++++++++++++++++ zone/lua_expedition.h | 72 +++++++++++++++++++ zone/lua_general.cpp | 19 ++++- zone/lua_parser.cpp | 6 +- zone/questmgr.cpp | 20 ++++++ zone/questmgr.h | 3 + 9 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 zone/lua_expedition.cpp create mode 100644 zone/lua_expedition.h diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 81112445e..7cf213e3e 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -52,6 +52,7 @@ SET(zone_sources lua_encounter.cpp lua_entity.cpp lua_entity_list.cpp + lua_expedition.cpp lua_general.cpp lua_group.cpp lua_hate_list.cpp @@ -195,6 +196,7 @@ SET(zone_headers lua_encounter.h lua_entity.h lua_entity_list.h + lua_expedition.h lua_general.h lua_group.h lua_hate_list.h diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 9309c7cbb..e29caef70 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -4,7 +4,9 @@ #include #include "client.h" +#include "expedition_lockout_timer.h" #include "lua_client.h" +#include "lua_expedition.h" #include "lua_npc.h" #include "lua_item.h" #include "lua_iteminst.h" @@ -1644,7 +1646,76 @@ int Lua_Client::GetClientMaxLevel() { return self->GetClientMaxLevel(); } +Lua_Expedition Lua_Client::CreateExpedition(std::string name, uint32 min_players, uint32 max_players) { + Lua_Safe_Call_Class(Lua_Expedition); + return self->CreateExpedition(name, min_players, max_players); +} +Lua_Expedition Lua_Client::CreateExpedition(std::string 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); +} + +Lua_Expedition Lua_Client::GetExpedition() { + Lua_Safe_Call_Class(Lua_Expedition); + return self->GetExpedition(); +} + +luabind::object Lua_Client::GetExpeditionLockouts(lua_State* L) +{ + auto lua_table = luabind::newtable(L); + if (d_) + { + auto self = reinterpret_cast(d_); + auto lockouts = self->GetExpeditionLockouts(); + + for (const auto& lockout : lockouts) + { + auto lockout_table = lua_table[lockout.GetExpeditionName()]; + if (luabind::type(lockout_table) != LUA_TTABLE) + { + lockout_table = luabind::newtable(L); + } + lockout_table[lockout.GetEventName()] = lockout.GetSecondsRemaining(); + } + } + return lua_table; +} + +luabind::object Lua_Client::GetExpeditionLockouts(lua_State* L, std::string expedition_name) +{ + auto lua_table = luabind::newtable(L); + if (d_) + { + auto self = reinterpret_cast(d_); + auto lockouts = self->GetExpeditionLockouts(); + + for (const auto& lockout : lockouts) + { + if (lockout.GetExpeditionName() == expedition_name) + { + lua_table[lockout.GetEventName()] = lockout.GetSecondsRemaining(); + } + } + } + return lua_table; +} + +void Lua_Client::AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds) { + Lua_Safe_Call_Void(); + self->AddNewExpeditionLockout(expedition_name, event_name, seconds); +} + +void Lua_Client::RemoveExpeditionLockout(std::string expedition_name, std::string event_name) { + Lua_Safe_Call_Void(); + self->RemoveExpeditionLockout(expedition_name, event_name, true); + self->SendExpeditionLockoutTimers(); +} + +bool Lua_Client::HasExpeditionLockout(std::string expedition_name, std::string event_name) { + Lua_Safe_Call_Bool(); + return self->HasExpeditionLockout(expedition_name, event_name); +} luabind::scope lua_register_client() { return luabind::class_("Client") @@ -1952,7 +2023,15 @@ luabind::scope lua_register_client() { .def("EnableAreaRegens", &Lua_Client::EnableAreaRegens) .def("DisableAreaRegens", &Lua_Client::DisableAreaRegens) .def("SetClientMaxLevel", (void(Lua_Client::*)(int))&Lua_Client::SetClientMaxLevel) - .def("GetClientMaxLevel", (int(Lua_Client::*)(void))&Lua_Client::GetClientMaxLevel); + .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("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); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index 2f5de4a59..8f60aaacb 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -5,6 +5,7 @@ #include "lua_mob.h" class Client; +class Lua_Expedition; class Lua_Group; class Lua_Raid; class Lua_Inventory; @@ -332,12 +333,20 @@ public: void EnableAreaRegens(int value); void DisableAreaRegens(); - void SetPrimaryWeaponOrnamentation(uint32 model_id); void SetSecondaryWeaponOrnamentation(uint32 model_id); 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 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); }; #endif diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp new file mode 100644 index 000000000..5d4b05c04 --- /dev/null +++ b/zone/lua_expedition.cpp @@ -0,0 +1,152 @@ +/** + * 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 + * + */ + +#ifdef LUA_EQEMU + +#include "lua_expedition.h" +#include "expedition.h" +#include "lua.hpp" +#include +#include + +void Lua_Expedition::AddLockout(std::string event_name, uint32_t seconds) { + Lua_Safe_Call_Void(); + self->AddLockout(event_name, seconds); +} + +void Lua_Expedition::AddReplayLockout(uint32_t seconds) { + Lua_Safe_Call_Void(); + self->AddReplayLockout(seconds); +} + +uint32_t Lua_Expedition::GetID() { + Lua_Safe_Call_Int(); + return self->GetID(); +} + +std::string Lua_Expedition::GetLeaderName() { + Lua_Safe_Call_String(); + return self->GetLeaderName(); +} + +luabind::object Lua_Expedition::GetLockouts(lua_State* L) { + luabind::object lua_table = luabind::newtable(L); + + if (d_) + { + auto self = reinterpret_cast(d_); + auto lockouts = self->GetLockouts(); + for (const auto& lockout : lockouts) + { + lua_table[lockout.first] = lockout.second.GetSecondsRemaining(); + } + } + return lua_table; +} + +uint32_t Lua_Expedition::GetMemberCount() { + Lua_Safe_Call_Int(); + return self->GetMemberCount(); +} + +luabind::object Lua_Expedition::GetMembers(lua_State* L) { + luabind::object lua_table = luabind::newtable(L); + + if (d_) + { + auto self = reinterpret_cast(d_); + for (const auto& member : self->GetMembers()) + { + lua_table[member.name] = member.char_id; + } + } + return lua_table; +} + +std::string Lua_Expedition::GetName() { + Lua_Safe_Call_String(); + return self->GetName(); +} + +int Lua_Expedition::GetType() { + Lua_Safe_Call_Int(); + return static_cast(self->GetType()); +} + +bool Lua_Expedition::HasLockout(std::string event_name) { + Lua_Safe_Call_Bool(); + return self->HasLockout(event_name); +} + +bool Lua_Expedition::HasReplayLockout() { + Lua_Safe_Call_Bool(); + return self->HasReplayLockout(); +} + +void Lua_Expedition::RemoveLockout(std::string event_name) { + Lua_Safe_Call_Void(); + self->RemoveLockout(event_name); +} + +luabind::scope lua_register_expedition() { + return luabind::class_("Expedition") + .def(luabind::constructor<>()) + .property("null", &Lua_Expedition::Null) + .property("valid", &Lua_Expedition::Valid) + .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("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("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); +} + +luabind::scope lua_register_expedition_member_status() { + return luabind::class_("ExpeditionMemberStatus") + .enum_("constants") + [ + luabind::value("Unknown", static_cast(ExpeditionMemberStatus::Unknown)), + luabind::value("Online", static_cast(ExpeditionMemberStatus::Online)), + luabind::value("Offline", static_cast(ExpeditionMemberStatus::Offline)), + luabind::value("InDynamicZone", static_cast(ExpeditionMemberStatus::InDynamicZone)), + luabind::value("LinkDead", static_cast(ExpeditionMemberStatus::LinkDead)) + ]; +} + +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 new file mode 100644 index 000000000..155c5bc09 --- /dev/null +++ b/zone/lua_expedition.h @@ -0,0 +1,72 @@ +/** + * 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 EQEMU_LUA_EXPEDITION_H +#define EQEMU_LUA_EXPEDITION_H +#ifdef LUA_EQEMU + +#include "lua_ptr.h" +#include "../common/types.h" +#include + +class Expedition; +class Lua_Client; +struct lua_State; + +namespace luabind { + struct scope; + namespace adl { + class object; + } + using adl::object; +} + +luabind::scope lua_register_dynamiczone_types(); +luabind::scope lua_register_expedition(); +luabind::scope lua_register_expedition_member_status(); + +class Lua_Expedition : public Lua_Ptr +{ + typedef Expedition NativeType; +public: + Lua_Expedition() : Lua_Ptr(nullptr) { } + Lua_Expedition(Expedition *d) : Lua_Ptr(d) { } + virtual ~Lua_Expedition() { } + + operator Expedition*() { + return reinterpret_cast(GetLuaPtrData()); + } + + void AddLockout(std::string event_name, uint32_t seconds); + void AddReplayLockout(uint32_t seconds); + uint32_t GetID(); + std::string GetLeaderName(); + uint32_t GetMemberCount(); + luabind::object GetMembers(lua_State* L); + std::string GetName(); + int GetType(); + luabind::object GetLockouts(lua_State* L); + bool HasLockout(std::string event_name); + bool HasReplayLockout(); + void RemoveLockout(std::string event_name); +}; + +#endif // LUA_EQEMU +#endif // EQEMU_LUA_EXPEDITION_H diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index ecb072369..5dbb8108c 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -18,6 +18,7 @@ #include "lua_client.h" #include "lua_npc.h" #include "lua_entity_list.h" +#include "lua_expedition.h" #include "quest_parser_collection.h" #include "questmgr.h" #include "qglobals.h" @@ -2178,6 +2179,18 @@ void lua_set_content_flag(std::string flag_name, bool enabled){ ZoneStore::SetContentFlag(flag_name, enabled); } +Lua_Expedition lua_get_expedition() { + return quest_manager.GetExpeditionForCurrentInstance(); +} + +Lua_Expedition lua_get_expedition_by_char_id(uint32 char_id) { + return quest_manager.GetExpeditionByCharID(char_id); +} + +Lua_Expedition lua_get_expedition_by_instance_id(uint32 instance_id) { + return quest_manager.GetExpeditionByInstanceID(instance_id); +} + #define LuaCreateNPCParse(name, c_type, default_value) do { \ cur = table[#name]; \ if(luabind::type(cur) != LUA_TNIL) { \ @@ -2775,7 +2788,11 @@ luabind::scope lua_register_general() { * Content flags */ luabind::def("is_content_flag_enabled", (bool(*)(std::string))&lua_is_content_flag_enabled), - luabind::def("set_content_flag", (void(*)(std::string, bool))&lua_set_content_flag) + luabind::def("set_content_flag", (void(*)(std::string, bool))&lua_set_content_flag), + + luabind::def("get_expedition", (Lua_Expedition(*)())&lua_get_expedition), + luabind::def("get_expedition_by_char_id", (Lua_Expedition(*)(uint32 char_id))&lua_get_expedition_by_char_id), + luabind::def("get_expedition_by_instance_id", (Lua_Expedition(*)(uint32 instance_id))&lua_get_expedition_by_instance_id) ]; } diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index ebe88f32a..8ac469359 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -19,6 +19,7 @@ #include "lua_parser.h" #include "lua_bit.h" #include "lua_entity.h" +#include "lua_expedition.h" #include "lua_item.h" #include "lua_iteminst.h" #include "lua_mob.h" @@ -1108,7 +1109,10 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_ruler(), lua_register_ruleb(), lua_register_journal_speakmode(), - lua_register_journal_mode() + lua_register_journal_mode(), + lua_register_dynamiczone_types(), + lua_register_expedition(), + lua_register_expedition_member_status() ]; } catch(std::exception &ex) { diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 631bb1218..a255d110f 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -26,6 +26,7 @@ #include "entity.h" #include "event_codes.h" +#include "expedition.h" #include "guild_mgr.h" #include "qglobals.h" #include "queryserv.h" @@ -4327,3 +4328,22 @@ void QuestManager::UpdateZoneHeader(std::string type, std::string value) { entity_list.QueueClients(0, outapp); safe_delete(outapp); } + +Expedition* QuestManager::GetExpeditionByCharID(uint32 char_id) +{ + return Expedition::FindCachedExpeditionByCharacterID(char_id); +} + +Expedition* QuestManager::GetExpeditionByInstanceID(uint32 instance_id) +{ + return Expedition::FindExpeditionByInstanceID(instance_id); +} + +Expedition* QuestManager::GetExpeditionForCurrentInstance() +{ + if (zone && zone->GetInstanceID() != 0) + { + return Expedition::FindExpeditionByInstanceID(zone->GetInstanceID()); + } + return nullptr; +} diff --git a/zone/questmgr.h b/zone/questmgr.h index f3f656e51..84dad5d02 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -366,6 +366,9 @@ public: bool DisableRecipe(uint32 recipe_id); void ClearNPCTypeCache(int npctype_id); void ReloadZoneStaticData(); + Expedition* GetExpeditionByCharID(uint32 char_id); + Expedition* GetExpeditionByInstanceID(uint32 instance_id); + Expedition* GetExpeditionForCurrentInstance(); Client *GetInitiator() const; NPC *GetNPC() const; From 8eef2ae089226309dae27bbfa6cfc7ac33c8bd57 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 18 Apr 2020 14:11:28 -0400 Subject: [PATCH 032/196] Add DynamicZone class for expedition instancing Add DynamicZone sql table schema Add DynamicZones logging category Modify CreateExpedition to take DynamicZone and ExpeditionRequest objects Implement DynamicZone compass, safereturn, and zone-in coordinates. Implement live-like DynamicZone instance kick timer for removed members Implement updating multiple client compasses (supports existing quest compass) fix: Send client compass update after entering zones to clear existing compass Implement Client::MovePCDynamicZone to invoke DynamicZoneSwitchListWnd when entering a zone where client has multiple dynamic zones assigned Implement OP_DzChooseZoneReply handling Add Lua api methods for expedition's associated dynamic zone Add #dz list gm command to list current DynamicZone instances from database --- common/eqemu_logsys.h | 2 + common/eqemu_logsys_log_aliases.h | 16 + common/ruletypes.h | 6 + common/servertalk.h | 24 + utils/sql/git/required/wip_dynamiczones.sql | 25 + world/expedition.cpp | 10 +- world/expedition.h | 2 +- world/main.cpp | 4 +- world/zoneserver.cpp | 13 + zone/CMakeLists.txt | 2 + zone/client.cpp | 263 ++++++++-- zone/client.h | 16 +- zone/client_packet.cpp | 25 +- zone/client_process.cpp | 10 + zone/command.cpp | 55 ++- zone/doors.cpp | 2 + zone/dynamiczone.cpp | 507 ++++++++++++++++++++ zone/dynamiczone.h | 117 +++++ zone/entity.cpp | 23 + zone/entity.h | 3 + zone/expedition.cpp | 284 ++++++++--- zone/expedition.h | 38 +- zone/expedition_database.cpp | 8 +- zone/expedition_database.h | 2 +- zone/expedition_request.cpp | 9 +- zone/expedition_request.h | 9 +- zone/lua_client.cpp | 26 +- zone/lua_client.h | 6 +- zone/lua_expedition.cpp | 63 ++- zone/lua_expedition.h | 10 +- zone/lua_parser.cpp | 1 - zone/string_ids.h | 2 + zone/worldserver.cpp | 8 + zone/zone.cpp | 9 +- zone/zone.h | 1 - 35 files changed, 1416 insertions(+), 185 deletions(-) create mode 100644 utils/sql/git/required/wip_dynamiczones.sql create mode 100644 zone/dynamiczone.cpp create mode 100644 zone/dynamiczone.h 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 - From 9102bb1478206a3ba68d8a263655b59e1100f9de Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 5 May 2020 23:27:35 -0400 Subject: [PATCH 033/196] Assign lockouts to all clients inside dz This is live like and prevents possible exploiting by dropping expedition before a lockout. Clients will continue receiving lockouts until they leave the zone or are kicked via timer --- zone/client_process.cpp | 9 +++------ zone/expedition.cpp | 43 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 353a02267..5a2f91875 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -161,14 +161,11 @@ bool Client::Process() { if (TaskPeriodic_Timer.Check() && taskstate) taskstate->TaskPeriodicChecks(this); - if (dynamiczone_removal_timer.Check()) + if (dynamiczone_removal_timer.Check() && zone && zone->GetInstanceID() != 0) { dynamiczone_removal_timer.Disable(); - if (zone && zone->GetInstanceID() != 0) - { - DynamicZone dz = DynamicZone::LoadDzFromDatabase(zone->GetInstanceID()); - GoToDzSafeReturnOrBind(dz.GetSafeReturnLocation()); - } + DynamicZone dz = DynamicZone::LoadDzFromDatabase(zone->GetInstanceID()); + GoToDzSafeReturnOrBind(dz.GetSafeReturnLocation()); } if (linkdead_timer.Check()) { diff --git a/zone/expedition.cpp b/zone/expedition.cpp index cc409da06..19c4de56d 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -532,11 +532,12 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) SendWorldMemberChanged(member.name, member.char_id, true); // live always sends a leader update but we can send only if leader changes - if (!m_members.empty() && member.char_id == m_leader.char_id) + if (member.char_id == m_leader.char_id) { ChooseNewLeader(); } - else if (m_members.empty()) + + if (m_members.empty()) { // cache removal will occur in world message handler ExpeditionDatabase::DeleteExpedition(m_id); @@ -1178,8 +1179,12 @@ 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()); + if (!m_dynamiczone.IsCurrentZoneDzInstance()) + { + // live doesn't clear expedition info on clients removed while inside dz + 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() ); @@ -1217,7 +1222,35 @@ void Expedition::ProcessLockoutUpdate( { member_client->RemoveExpeditionLockout(m_expedition_name, event_name); } - member_client->SendExpeditionLockoutTimers(); // full client lockout list update + member_client->SendExpeditionLockoutTimers(); + } + } + + // if this is the expedition's dz instance, all clients inside the zone need + // to receive added lockouts. this is done on live to avoid exploits where + // members leave the expedition but haven't been kicked from zone yet + if (m_dynamiczone.IsCurrentZoneDzInstance()) + { + std::vector non_members; + for (const auto& client_iter : entity_list.GetClientList()) + { + Client* client = client_iter.second; + if (client && client->GetExpeditionID() != GetID()) + { + non_members.emplace_back(ExpeditionMember{ client->CharacterID(), client->GetName() }); + + if (!remove) { + client->AddExpeditionLockout(lockout); + } else { + client->RemoveExpeditionLockout(m_expedition_name, event_name); + } + client->SendExpeditionLockoutTimers(); + } + } + + if (!remove && !non_members.empty()) // expedition members were already updated in db + { + ExpeditionDatabase::InsertMembersLockout(non_members, lockout); } } } From eccc79e4ce02c03b41c256ac75ab8bde89884cc8 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 7 May 2020 18:38:43 -0400 Subject: [PATCH 034/196] Let dz handle client removal timers Remove all clients inside a dz, not just those assigned to instance --- common/servertalk.h | 2 ++ world/zoneserver.cpp | 1 + zone/dynamiczone.cpp | 52 ++++++++++++++++++++++++++++++++++++++++---- zone/dynamiczone.h | 2 +- zone/expedition.cpp | 11 +--------- zone/worldserver.cpp | 1 + 6 files changed, 54 insertions(+), 15 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 6fbe67b96..c78bdf728 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -156,6 +156,7 @@ #define ServerOP_ExpeditionDzZoneIn 0x040c #define ServerOP_DzCharacterChange 0x0450 +#define ServerOP_DzRemoveAllCharacters 0x0451 #define ServerOP_LSInfo 0x1000 #define ServerOP_LSStatus 0x1001 @@ -2072,6 +2073,7 @@ struct ServerDzLocation_Struct { }; struct ServerDzCharacter_Struct { + uint16 zone_id; uint16 instance_id; uint8 remove; // 0: added 1: removed uint32 character_id; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index f113b0b1e..cd6a17e37 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1398,6 +1398,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } case ServerOP_DzCharacterChange: + case ServerOP_DzRemoveAllCharacters: { auto buf = reinterpret_cast(pack->pBuffer); ZoneServer* instance_zs = zoneserver_list.FindByInstanceID(buf->instance_id); diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 0fcd2b8b2..5e87e229c 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -353,13 +353,40 @@ void DynamicZone::RemoveCharacter(uint32_t character_id) SendInstanceCharacterChange(character_id, true); // start client kick timer } -void DynamicZone::RemoveAllCharacters() +void DynamicZone::RemoveAllCharacters(bool enable_removal_timers) { - // caller has to notify clients of instance change since we don't hold members here - if (m_instance_id != 0) + if (GetInstanceID() == 0) { - database.RemoveClientsFromInstance(m_instance_id); + return; } + + if (enable_removal_timers) + { + // just remove all clients in bulk instead of only characters assigned to the instance + if (IsCurrentZoneDzInstance()) + { + for (const auto& client_iter : entity_list.GetClientList()) + { + if (client_iter.second) + { + client_iter.second->SetDzRemovalTimer(true); + } + } + } + else if (GetInstanceID() != 0) + { + uint32_t packsize = sizeof(ServerDzCharacter_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_DzRemoveAllCharacters, packsize)); + auto packbuf = reinterpret_cast(pack->pBuffer); + packbuf->zone_id = GetZoneID(); + packbuf->instance_id = GetInstanceID(); + packbuf->remove = true; + packbuf->character_id = 0; + worldserver.SendPacket(pack.get()); + } + } + + database.RemoveClientsFromInstance(GetInstanceID()); } void DynamicZone::SaveInstanceMembersToDatabase(const std::unordered_set character_ids) @@ -402,6 +429,7 @@ void DynamicZone::SendInstanceCharacterChange(uint32_t character_id, bool remove uint32_t packsize = sizeof(ServerDzCharacter_Struct); auto pack = std::unique_ptr(new ServerPacket(ServerOP_DzCharacterChange, packsize)); auto packbuf = reinterpret_cast(pack->pBuffer); + packbuf->zone_id = GetZoneID(); packbuf->instance_id = GetInstanceID(); packbuf->remove = removed; packbuf->character_id = character_id; @@ -503,5 +531,21 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_DzRemoveAllCharacters: + { + auto buf = reinterpret_cast(pack->pBuffer); + if (buf->remove) + { + for (const auto& client_list_iter : entity_list.GetClientList()) + { + Client* client = client_list_iter.second; + if (client) + { + client->SetDzRemovalTimer(true); + } + } + } + break; + } } } diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index ceeef14ff..b2bf22b41 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -83,7 +83,7 @@ public: bool IsCurrentZoneDzInstance() const; bool IsInstanceID(uint32_t instance_id) const; bool IsValid() const { return m_instance_id != 0; } - void RemoveAllCharacters(); + void RemoveAllCharacters(bool enable_removal_timers = true); void RemoveCharacter(uint32_t character_id); void SendInstanceCharacterChange(uint32_t character_id, bool removed); void SetCompass(const DynamicZoneLocation& location, bool update_db = false); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 19c4de56d..7be333b0c 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -494,16 +494,7 @@ bool Expedition::AddMember(const std::string& add_char_name, uint32_t add_char_i 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); - } - } + m_dynamiczone.RemoveAllCharacters(enable_removal_timers); if (update_dz_expire_time && RuleB(Expedition, EmptyDzShutdownEnabled)) { diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index b6b3461ca..afdb9933b 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2917,6 +2917,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) break; } case ServerOP_DzCharacterChange: + case ServerOP_DzRemoveAllCharacters: { DynamicZone::HandleWorldMessage(pack); break; From 1819b7c23b3e7d1470d6306421582efe51a45a25 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 8 May 2020 11:19:24 -0400 Subject: [PATCH 035/196] Cache new expedition before sending client updates Compass updates get data from the expedition cache so it needs to be cached first. Currently this doesn't affect anything because compass isn't sent to CreateExpedition and has to be set post-creation. In the future this will make the order of client messages more live accurate though --- zone/expedition.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 7be333b0c..ade863363 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -128,8 +128,11 @@ Expedition* Expedition::TryCreate( expedition->SaveMembers(request); expedition->SaveLockouts(request); - expedition->SendUpdatesToZoneMembers(); - expedition->SendWorldExpeditionUpdate(); // cache in other zones + + auto inserted = zone->expedition_cache.emplace(expedition_id, std::move(expedition)); + + inserted.first->second->SendUpdatesToZoneMembers(); + inserted.first->second->SendWorldExpeditionUpdate(); // cache in other zones Client* leader_client = request.GetLeaderClient(); @@ -137,7 +140,6 @@ Expedition* Expedition::TryCreate( leader_client, leader.name, Chat::Yellow, EXPEDITION_AVAILABLE, { request.GetExpeditionName() } ); - auto inserted = zone->expedition_cache.emplace(expedition_id, std::move(expedition)); return inserted.first->second.get(); } From 78eb3be12712f946380cd86e8cc1df69399d55d3 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 8 May 2020 13:58:07 -0400 Subject: [PATCH 036/196] Add option to disable expedition conflict messages Add optional argument to CreateExpedition to disable conflict messages Some live expeditions like anguish use a timeout to prevent excessive leader conflict messages while still performing a creation request --- zone/client.cpp | 6 +++--- zone/client.h | 2 +- zone/expedition_request.cpp | 19 ++++++++++++++----- zone/expedition_request.h | 5 ++++- zone/lua_client.cpp | 6 ++++++ zone/lua_client.h | 1 + 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 060979f1a..30146d82f 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9585,11 +9585,11 @@ void Client::UpdateExpeditionInfoAndLockouts() } Expedition* Client::CreateExpedition( - std::string zone_name, uint32 version, uint32 duration, - std::string expedition_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, bool disable_messages) { DynamicZone dz_instance{ zone_name, version, duration, DynamicZoneType::Expedition }; - ExpeditionRequest request{ expedition_name, min_players, max_players, has_replay_timer }; + ExpeditionRequest request{ expedition_name, min_players, max_players, has_replay_timer, disable_messages }; return Expedition::TryCreate(this, dz_instance, request); } diff --git a/zone/client.h b/zone/client.h index df65b6b16..63c0a1d2b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1119,7 +1119,7 @@ public: void AddNewExpeditionLockout(const std::string& expedition_name, const std::string& event_name, uint32_t duration); 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); + uint32 min_players, uint32 max_players, bool has_replay_timer = false, bool disable_messages = false); Expedition* GetExpedition() const; uint32 GetExpeditionID() const { return m_expedition_id; } const ExpeditionLockoutTimer* GetExpeditionLockout( diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 4ec6a4846..e123611cb 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -38,12 +38,14 @@ struct ExpeditionRequestConflict }; ExpeditionRequest::ExpeditionRequest( - 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, bool disable_messages ) : m_expedition_name(expedition_name), m_min_players(min_players), m_max_players(max_players), - m_has_replay_timer(has_replay_timer) + m_has_replay_timer(has_replay_timer), + m_disable_messages(disable_messages) { } @@ -288,11 +290,18 @@ bool ExpeditionRequest::CheckMembersForConflicts(MySQLRequestResult& results, bo void ExpeditionRequest::SendLeaderMessage( uint16_t chat_type, uint32_t string_id, const std::initializer_list& parameters) { - Client::SendCrossZoneMessageString(m_leader, m_leader_name, chat_type, string_id, parameters); + if (!m_disable_messages) + { + Client::SendCrossZoneMessageString(m_leader, m_leader_name, chat_type, string_id, parameters); + } } void ExpeditionRequest::SendLeaderMemberInExpedition(const std::string& member_name, bool is_solo) { + if (m_disable_messages) { + return; + } + if (is_solo) { SendLeaderMessage(Chat::Red, EXPEDITION_YOU_BELONG); @@ -307,7 +316,7 @@ void ExpeditionRequest::SendLeaderMemberInExpedition(const std::string& member_n void ExpeditionRequest::SendLeaderMemberReplayLockout( const std::string& member_name, const ExpeditionLockoutTimer& lockout, bool is_solo) { - if (lockout.GetSecondsRemaining() <= 0) + if (m_disable_messages || lockout.GetSecondsRemaining() <= 0) { return; } @@ -330,7 +339,7 @@ void ExpeditionRequest::SendLeaderMemberReplayLockout( void ExpeditionRequest::SendLeaderMemberEventLockout( const std::string& member_name, const ExpeditionLockoutTimer& lockout) { - if (lockout.GetSecondsRemaining() <= 0) + if (m_disable_messages || lockout.GetSecondsRemaining() <= 0) { return; } diff --git a/zone/expedition_request.h b/zone/expedition_request.h index 5970e69e5..8cedf6392 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -37,7 +37,9 @@ struct ExpeditionMember; class ExpeditionRequest { public: - ExpeditionRequest(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 disable_messages = false); bool Validate(Client* requester); @@ -69,6 +71,7 @@ private: uint32_t m_min_players = 0; uint32_t m_max_players = 0; bool m_check_event_lockouts = true; + bool m_disable_messages = false; bool m_has_replay_timer = false; std::string m_expedition_name; std::string m_leader_name; diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 7ad1b9bec..24659a6ba 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1656,6 +1656,11 @@ Lua_Expedition Lua_Client::CreateExpedition(std::string zone_name, uint32 versio return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players, 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, bool disable_messages) { + Lua_Safe_Call_Class(Lua_Expedition); + return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players, has_replay_timer, disable_messages); +} + Lua_Expedition Lua_Client::GetExpedition() { Lua_Safe_Call_Class(Lua_Expedition); return self->GetExpedition(); @@ -2036,6 +2041,7 @@ luabind::scope lua_register_client() { .def("GetClientMaxLevel", (int(Lua_Client::*)(void))&Lua_Client::GetClientMaxLevel) .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("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32, bool, 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) diff --git a/zone/lua_client.h b/zone/lua_client.h index fec1f60fc..47a0ae355 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -341,6 +341,7 @@ public: 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 CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool has_replay_timer, bool disable_messages); Lua_Expedition GetExpedition(); luabind::object GetExpeditionLockouts(lua_State* L); luabind::object GetExpeditionLockouts(lua_State* L, std::string expedition_name); From b11673088585e325a834866ffeeb7f8f242870a0 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 8 May 2020 16:32:16 -0400 Subject: [PATCH 037/196] Fix wrong group leader name in expedition requests Get group leader name from Client if possible and ask database otherwise Group::GetLeaderName() is unreliable and broken for groups formed across zones. The correct leader name is needed here to avoid any possible exploits with an invalid leader bypassing lockout checks. --- zone/expedition_request.cpp | 10 +++++++++- zone/expedition_request.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index e123611cb..da6cce915 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -120,7 +120,8 @@ bool ExpeditionRequest::CanGroupRequest(Group* group) { m_leader = group->GetLeader()->CastToClient(); } - m_leader_name = group->GetLeaderName(); + // Group::GetLeaderName() is broken if group formed across zones, ask database instead + m_leader_name = m_leader ? m_leader->GetName() : GetGroupLeaderName(group->GetID()); // group->GetLeaderName(); m_leader_id = m_leader ? m_leader->CharacterID() : database.GetCharacterID(m_leader_name.c_str()); uint32_t count = 0; @@ -142,6 +143,13 @@ bool ExpeditionRequest::CanGroupRequest(Group* group) return ValidateMembers(query_member_names, count); } +std::string ExpeditionRequest::GetGroupLeaderName(uint32_t group_id) +{ + char leader_name_buffer[64] = { 0 }; + database.GetGroupLeadershipInfo(group_id, leader_name_buffer); + return std::string(leader_name_buffer); +} + bool ExpeditionRequest::ValidateMembers(const std::string& query_member_names, uint32_t member_count) { if (query_member_names.empty() || !LoadLeaderLockouts()) diff --git a/zone/expedition_request.h b/zone/expedition_request.h index 8cedf6392..864aa855a 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -58,6 +58,7 @@ private: bool CanRaidRequest(Raid* raid); bool CanGroupRequest(Group* group); bool CheckMembersForConflicts(MySQLRequestResult& results, bool is_solo); + std::string GetGroupLeaderName(uint32_t group_id); bool IsPlayerCountValidated(uint32_t member_count); bool LoadLeaderLockouts(); void SendLeaderMemberInExpedition(const std::string& member_name, bool is_solo); From ef77b28b3f587c8f1985174e5a25b27736a092fc Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 9 May 2020 19:17:00 -0400 Subject: [PATCH 038/196] Add #dz lockouts remove command This allows clearing a character's lockouts Adds client RemoveAllExpeditionLockouts methods and exposes to lua api --- common/servertalk.h | 36 +++++++++++++++++++++--------------- world/zoneserver.cpp | 6 ++++++ zone/client.cpp | 19 +++++++++++++++++++ zone/client.h | 1 + zone/command.cpp | 13 +++++++++++++ zone/expedition.cpp | 24 ++++++++++++++++++++++++ zone/expedition.h | 1 + zone/expedition_database.cpp | 31 +++++++++++++++++++++++++++++++ zone/expedition_database.h | 2 ++ zone/lua_client.cpp | 12 ++++++++++++ zone/lua_client.h | 2 ++ zone/worldserver.cpp | 1 + 12 files changed, 133 insertions(+), 15 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index c78bdf728..a50e5832a 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -141,22 +141,23 @@ #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_ExpeditionDzCompass 0x040a -#define ServerOP_ExpeditionDzSafeReturn 0x040b -#define ServerOP_ExpeditionDzZoneIn 0x040c +#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_ExpeditionDzCompass 0x040a +#define ServerOP_ExpeditionDzSafeReturn 0x040b +#define ServerOP_ExpeditionDzZoneIn 0x040c +#define ServerOP_ExpeditionRemoveCharLockouts 0x040d -#define ServerOP_DzCharacterChange 0x0450 -#define ServerOP_DzRemoveAllCharacters 0x0451 +#define ServerOP_DzCharacterChange 0x0450 +#define ServerOP_DzRemoveAllCharacters 0x0451 #define ServerOP_LSInfo 0x1000 #define ServerOP_LSStatus 0x1001 @@ -2051,6 +2052,11 @@ struct ServerExpeditionLockout_Struct { char event_name[256]; }; +struct ServerExpeditionCharacterName_Struct { + char character_name[64]; + char expedition_name[128]; +}; + struct ServerDzCommand_Struct { uint32 expedition_id; uint8 is_char_online; // 0: target name is offline, 1: online diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index cd6a17e37..35a6e27f0 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1397,6 +1397,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { Expedition::MakeLeader(pack); break; } + case ServerOP_ExpeditionRemoveCharLockouts: + { + auto buf = reinterpret_cast(pack->pBuffer); + client_list.SendPacket(buf->character_name, pack); + break; + } case ServerOP_DzCharacterChange: case ServerOP_DzRemoveAllCharacters: { diff --git a/zone/client.cpp b/zone/client.cpp index 30146d82f..ef1b639b1 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9665,6 +9665,25 @@ void Client::RemoveExpeditionLockout( } } +void Client::RemoveAllExpeditionLockouts(std::string expedition_name) +{ + if (expedition_name.empty()) + { + ExpeditionDatabase::DeleteAllCharacterLockouts(CharacterID()); + m_expedition_lockouts.clear(); + } + else + { + ExpeditionDatabase::DeleteAllCharacterLockouts(CharacterID(), expedition_name); + m_expedition_lockouts.erase(std::remove_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), + [&](const ExpeditionLockoutTimer& lockout) { + return lockout.GetExpeditionName() == expedition_name; + } + ), m_expedition_lockouts.end()); + } + SendExpeditionLockoutTimers(); +} + const ExpeditionLockoutTimer* Client::GetExpeditionLockout( const std::string& expedition_name, const std::string& event_name, bool include_expired) const { diff --git a/zone/client.h b/zone/client.h index 63c0a1d2b..6e4575afc 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1129,6 +1129,7 @@ public: 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 RemoveAllExpeditionLockouts(std::string expedition_name = {}); 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(); diff --git a/zone/command.cpp b/zone/command.cpp index 789528fdb..0d8386803 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6912,6 +6912,17 @@ void command_dz(Client* c, const Seperator* sep) } } } + else if (strcasecmp(sep->arg[1], "lockouts") == 0) + { + if (strcasecmp(sep->arg[2], "remove") == 0 && sep->arg[3][0] != '\0') + { + c->Message(Chat::White, fmt::format( + "Removing [{}] lockouts on [{}].", sep->arg[4][0] ? sep->arg[4] : "all", sep->arg[3] + ).c_str()); + + Expedition::RemoveAllCharacterLockouts(sep->arg[3], sep->arg[4]); + } + } else { c->Message(Chat::White, "#dz usage:"); @@ -6919,6 +6930,8 @@ void command_dz(Client* c, const Seperator* sep) 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"); + c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); + c->Message(Chat::White, "#dz lockouts remove \"\" - delete lockouts by expedition"); } } diff --git a/zone/expedition.cpp b/zone/expedition.cpp index ade863363..630e5631c 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1530,6 +1530,20 @@ void Expedition::SendWorldGetOnlineMembers() worldserver.SendPacket(pack.get()); } +void Expedition::RemoveAllCharacterLockouts(std::string character_name, std::string expedition_name) +{ + uint32_t pack_size = sizeof(ServerExpeditionCharacterName_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionRemoveCharLockouts, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + strn0cpy(buf->character_name, character_name.c_str(), sizeof(buf->character_name)); + buf->expedition_name[0] = '\0'; + if (!expedition_name.empty()) + { + strn0cpy(buf->expedition_name, expedition_name.c_str(), sizeof(buf->expedition_name)); + } + worldserver.SendPacket(pack.get()); +} + void Expedition::HandleWorldMessage(ServerPacket* pack) { switch (pack->opcode) @@ -1719,6 +1733,16 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_ExpeditionRemoveCharLockouts: + { + auto buf = reinterpret_cast(pack->pBuffer); + Client* client = entity_list.GetClientByName(buf->character_name); + if (client) + { + client->RemoveAllExpeditionLockouts(buf->expedition_name); + } + break; + } } } diff --git a/zone/expedition.h b/zone/expedition.h index 671a4e9b9..d4901fc38 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -78,6 +78,7 @@ public: static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name); static Expedition* FindCachedExpeditionByID(uint32_t expedition_id); static Expedition* FindExpeditionByInstanceID(uint32_t instance_id); + static void RemoveAllCharacterLockouts(std::string character_name, std::string expedition_name = {}); static void HandleWorldMessage(ServerPacket* pack); uint32_t GetID() const { return m_id; } diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 3888e1692..c3dcd3ef8 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -201,6 +201,37 @@ MySQLRequestResult ExpeditionDatabase::LoadValidationData( return results; } +void ExpeditionDatabase::DeleteAllCharacterLockouts(uint32_t character_id) +{ + LogExpeditionsDetail("Deleting all character [{}] lockouts", character_id); + + if (character_id != 0) + { + std::string query = fmt::format(SQL( + DELETE FROM expedition_character_lockouts + WHERE character_id = {}; + ), character_id); + + database.QueryDatabase(query); + } +} + +void ExpeditionDatabase::DeleteAllCharacterLockouts( + uint32_t character_id, const std::string& expedition_name) +{ + LogExpeditionsDetail("Deleting all character [{}] lockouts for [{}]", character_id, expedition_name); + + if (character_id != 0 && !expedition_name.empty()) + { + std::string query = fmt::format(SQL( + DELETE FROM expedition_character_lockouts + WHERE character_id = {} AND expedition_name = '{}'; + ), character_id, expedition_name); + + database.QueryDatabase(query); + } +} + void ExpeditionDatabase::DeleteCharacterLockout( uint32_t character_id, const std::string& expedition_name, const std::string& event_name) { diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 38f758258..bb8804d81 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -45,6 +45,8 @@ namespace ExpeditionDatabase 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 DeleteAllCharacterLockouts(uint32_t character_id); + void DeleteAllCharacterLockouts(uint32_t character_id, const std::string& expedition_name); 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); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 24659a6ba..c1e87cafc 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1711,6 +1711,16 @@ void Lua_Client::AddExpeditionLockout(std::string expedition_name, std::string e self->AddNewExpeditionLockout(expedition_name, event_name, seconds); } +void Lua_Client::RemoveAllExpeditionLockouts() { + Lua_Safe_Call_Void(); + self->RemoveAllExpeditionLockouts(); +} + +void Lua_Client::RemoveAllExpeditionLockouts(std::string expedition_name) { + Lua_Safe_Call_Void(); + self->RemoveAllExpeditionLockouts(expedition_name); +} + void Lua_Client::RemoveExpeditionLockout(std::string expedition_name, std::string event_name) { Lua_Safe_Call_Void(); self->RemoveExpeditionLockout(expedition_name, event_name, true); @@ -2046,6 +2056,8 @@ luabind::scope lua_register_client() { .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("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(void))&Lua_Client::RemoveAllExpeditionLockouts) + .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(std::string))&Lua_Client::RemoveAllExpeditionLockouts) .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("MovePCDynamicZone", (void(Lua_Client::*)(uint32))&Lua_Client::MovePCDynamicZone) diff --git a/zone/lua_client.h b/zone/lua_client.h index 47a0ae355..e339ead7a 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -346,6 +346,8 @@ public: 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 RemoveAllExpeditionLockouts(); + void RemoveAllExpeditionLockouts(std::string expedition_name); 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); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index afdb9933b..fa005ea6e 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2912,6 +2912,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionDzCompass: case ServerOP_ExpeditionDzSafeReturn: case ServerOP_ExpeditionDzZoneIn: + case ServerOP_ExpeditionRemoveCharLockouts: { Expedition::HandleWorldMessage(pack); break; From a9c65cd4b27db765a973073a1f88092e4fa3a501 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 9 May 2020 19:44:04 -0400 Subject: [PATCH 039/196] Filter out expired instances from #dz list Add 'all' argument to #dz list to optionally show expired instances --- zone/command.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 0d8386803..a85bbc868 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6900,15 +6900,18 @@ void command_dz(Client* c, const Seperator* sep) 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()); + if (!is_expired || strcasecmp(sep->arg[2], "all") == 0) + { + 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()); + } } } } @@ -6929,7 +6932,7 @@ void command_dz(Client* c, const Seperator* sep) 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"); + c->Message(Chat::White, "#dz list [all] - list dynamic zones from database -- 'all' includes expired"); c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); c->Message(Chat::White, "#dz lockouts remove \"\" - delete lockouts by expedition"); } From 158dad052c5b8b5538b1747e0fcd415c72a66061 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 12 May 2020 21:43:29 -0400 Subject: [PATCH 040/196] Clear client expedition info if removed inside dz Clears client expedition info immediately if removed inside dynamic zone Live clears expedition info from clients removed inside a dz on the same timer used for removals, even if the client zones before it triggers. This is problematic to mimic and not worth the effort --- zone/expedition.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 630e5631c..faf30519f 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1170,14 +1170,13 @@ void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t re if (is_removed) { + // live doesn't clear expedition info on clients removed while inside dz. + // it instead let's the dz kick timer do it even if character zones out + // before it triggers. for simplicity we'll always clear immediately ExpeditionDatabase::DeletePendingLockouts(member_client->CharacterID()); member_client->SetExpeditionID(0); - if (!m_dynamiczone.IsCurrentZoneDzInstance()) - { - // live doesn't clear expedition info on clients removed while inside dz - member_client->SendDzCompassUpdate(); - member_client->QueuePacket(CreateInfoPacket(true).get()); - } + 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() ); From af766dd32360b2bf88457ef0ce5235c652400a0a Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 13 May 2020 18:49:45 -0400 Subject: [PATCH 041/196] Move LoadAllClientLockouts back to a client method --- zone/client.cpp | 18 +++++++++++++++++- zone/client.h | 1 + zone/expedition.cpp | 21 --------------------- zone/expedition.h | 1 - 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index ef1b639b1..14f0dcc5c 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9581,7 +9581,7 @@ void Client::UpdateExpeditionInfoAndLockouts() } } - Expedition::LoadAllClientLockouts(this); + LoadAllExpeditionLockouts(); } Expedition* Client::CreateExpedition( @@ -9704,6 +9704,22 @@ bool Client::HasExpeditionLockout( return (GetExpeditionLockout(expedition_name, event_name, include_expired) != nullptr); } +void Client::LoadAllExpeditionLockouts() +{ + auto results = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + auto expire_time = strtoull(row[0], nullptr, 10); + auto original_duration = static_cast(strtoul(row[1], nullptr, 10)); + ExpeditionLockoutTimer lockout{ row[2], row[3], expire_time, original_duration }; + AddExpeditionLockout(lockout); + } + } + SendExpeditionLockoutTimers(); +} + void Client::SendExpeditionLockoutTimers() { std::vector lockout_entries; diff --git a/zone/client.h b/zone/client.h index 6e4575afc..353466687 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1134,6 +1134,7 @@ public: void SetPendingExpeditionInvite(uint32 id) { m_pending_expedition_invite_id = id; } void SendExpeditionLockoutTimers(); void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; + void LoadAllExpeditionLockouts(); void UpdateExpeditionInfoAndLockouts(); void DzListTimers(); void SetDzRemovalTimer(bool enable_timer); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index faf30519f..e70612b91 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -254,27 +254,6 @@ bool Expedition::CacheAllFromDatabase() return true; } -void Expedition::LoadAllClientLockouts(Client* client) -{ - if (!client) - { - return; - } - - auto results = ExpeditionDatabase::LoadCharacterLockouts(client->CharacterID()); - if (results.Success()) - { - for (auto row = results.begin(); row != results.end(); ++row) - { - auto expire_time = strtoull(row[0], nullptr, 10); - auto original_duration = static_cast(strtoul(row[1], nullptr, 10)); - ExpeditionLockoutTimer lockout{ row[2], row[3], expire_time, original_duration }; - client->AddExpeditionLockout(lockout); - } - } - client->SendExpeditionLockoutTimers(); -} - void Expedition::LoadMembers() { m_members.clear(); diff --git a/zone/expedition.h b/zone/expedition.h index d4901fc38..61b71829b 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -73,7 +73,6 @@ public: 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); From 11181190ee521393c35dd5e71208732f66fe5cff Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 14 May 2020 19:09:49 -0400 Subject: [PATCH 042/196] Add expired lockouts leeway rule Adds a rule for leeway with expired lockouts during creation requests The client removes lockout timers with under 60s remaining from the window. This allows a small leeway to compensate so players don't request an expedition that has visually hidden lockouts. --- common/ruletypes.h | 1 + zone/expedition_request.cpp | 57 +++++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 9fb3ba629..834e8bb6d 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -790,6 +790,7 @@ RULE_INT(Expedition, MinStatusToBypassPlayerCountRequirements, 80, "Minimum GM s 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_INT(Expedition, RequestExpiredLockoutLeewaySeconds, 60, "Seconds remaining on lockout to count as expired for creation requests (client hides timers under 60s remaining)") RULE_CATEGORY_END() RULE_CATEGORY(DynamicZone) diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index da6cce915..d72e73b70 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -199,15 +199,25 @@ bool ExpeditionRequest::LoadLeaderLockouts() { uint64_t expire_time = strtoull(row[0], nullptr, 10); uint32_t duration = strtoul(row[1], nullptr, 10); + ExpeditionLockoutTimer lockout{m_expedition_name, row[2], expire_time, duration, true}; - 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) + // client window hides timers with less than 60s remaining, optionally count them as expired + if (lockout.GetSecondsRemaining() <= RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)) { - m_check_event_lockouts = false; + LogExpeditionsModerate( + "Ignoring leader [{}] lockout [{}] with [{}] seconds remaining due to expired leeway rule", + m_leader_id, lockout.GetEventName(), lockout.GetSecondsRemaining() + ); + } + else + { + m_lockouts.emplace(row[2], lockout); + + // 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; + } } } @@ -265,22 +275,33 @@ bool ExpeditionRequest::CheckMembersForConflicts(MySQLRequestResult& results, bo 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) + // client window hides timers with less than 60s remaining, optionally count them as expired + if (lockout.GetSecondsRemaining() <= RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)) { - has_conflicts = true; - SendLeaderMemberReplayLockout(character_name, lockout, is_solo); - // replay timers no longer also show up as event conflicts - //SendLeaderMemberEventLockout(character_name, lockout); + LogExpeditionsModerate( + "Ignoring character [{}] lockout [{}] with [{}] seconds remaining due to expired leeway rule", + character_id, lockout.GetEventName(), lockout.GetSecondsRemaining() + ); } - else if (m_check_event_lockouts && character_id != m_leader_id) + else { - if (m_lockouts.find(event_name) == m_lockouts.end()) + // replay timer conflict messages always show up before event conflicts + if (/*m_has_replay_timer && */event_name == DZ_REPLAY_TIMER_NAME) { - // 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}); + 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}); + } } } } From 43963783db8adc7e9215c47f12db0b44a07c32c6 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 15 May 2020 17:46:07 -0400 Subject: [PATCH 043/196] Add instance time remaining to #dz list output --- zone/command.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index a85bbc868..e9474aa44 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6898,18 +6898,24 @@ void command_dz(Client* c, const Seperator* sep) 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; + auto now = std::chrono::system_clock::now(); + auto remaining = std::chrono::duration_cast(expire_time - now); + auto seconds = std::max(0, static_cast(remaining.count())); + + bool is_expired = now > expire_time; if (!is_expired || strcasecmp(sep->arg[2], "all") == 0) { c->Message(Chat::White, fmt::format( - "type: [{}] instance: [{}] zone: [{}] version: [{}] members: [{}] expired: [{}]", + "type: [{}] instance: [{}] zone: [{}] version: [{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", 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 + seconds / 3600, // hours + (seconds / 60) % 60, // minutes + seconds % 60 // seconds ).c_str()); } } From d92c0e330d2d2314c3fccc5f46edad7d9b9bada1 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 15 May 2020 17:58:07 -0400 Subject: [PATCH 044/196] Validate expedition invite response server side Stores expedition invite data on client --- zone/client.h | 9 ++++----- zone/client_packet.cpp | 7 ++++--- zone/expedition.cpp | 11 +++++------ zone/expedition.h | 9 ++++++++- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/zone/client.h b/zone/client.h index 353466687..58b0e8643 100644 --- a/zone/client.h +++ b/zone/client.h @@ -21,8 +21,6 @@ class Client; class EQApplicationPacket; class EQStream; -class Expedition; -class ExpeditionLockoutTimer; class Group; class NPC; class Object; @@ -54,6 +52,7 @@ namespace EQ #include "aggromanager.h" #include "common.h" +#include "expedition.h" #include "dynamiczone.h" #include "merc.h" #include "mob.h" @@ -1126,14 +1125,14 @@ public: 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; } + uint32 GetPendingExpeditionInviteID() const { return m_pending_expedition_invite.expedition_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 RemoveAllExpeditionLockouts(std::string expedition_name = {}); 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 SetPendingExpeditionInvite(ExpeditionInvite&& invite) { m_pending_expedition_invite = invite; } void LoadAllExpeditionLockouts(); void UpdateExpeditionInfoAndLockouts(); void DzListTimers(); @@ -1699,7 +1698,7 @@ private: int client_max_level; uint32 m_expedition_id = 0; - uint32 m_pending_expedition_invite_id = 0; + ExpeditionInvite m_pending_expedition_invite { 0 }; std::vector m_expedition_lockouts; DynamicZoneLocation m_quest_compass; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 704e3dc4b..824928c71 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5665,13 +5665,14 @@ void Client::Handle_OP_DzChooseZoneReply(const EQApplicationPacket *app) void Client::Handle_OP_DzExpeditionInviteResponse(const EQApplicationPacket *app) { - auto expedition = Expedition::FindCachedExpeditionByID(m_pending_expedition_invite_id); - m_pending_expedition_invite_id = 0; + auto expedition = Expedition::FindCachedExpeditionByID(m_pending_expedition_invite.expedition_id); + std::string swap_remove_name = m_pending_expedition_invite.swap_remove_name; + m_pending_expedition_invite = { 0 }; // clear before re-validating if (expedition) { auto dzmsg = reinterpret_cast(app->pBuffer); - expedition->DzInviteResponse(this, dzmsg->accepted, dzmsg->swapping, dzmsg->swap_name); + expedition->DzInviteResponse(this, dzmsg->accepted, swap_remove_name); } } diff --git a/zone/expedition.cpp b/zone/expedition.cpp index e70612b91..7a9ce5b00 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -612,7 +612,7 @@ void Expedition::SendClientExpeditionInvite( m_id, client->GetName(), inviter_name, swap_remove_name ); - client->SetPendingExpeditionInvite(m_id); + client->SetPendingExpeditionInvite(ExpeditionInvite{m_id, inviter_name, swap_remove_name}); client->MessageString( Chat::System, EXPEDITION_ASKED_TO_JOIN, m_leader.name.c_str(), m_expedition_name.c_str() @@ -731,8 +731,7 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, return has_conflict; } -void Expedition::DzInviteResponse( - Client* add_client, bool accepted, bool has_swap_name, std::string swap_remove_name) +void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std::string& swap_remove_name) { if (!add_client) { @@ -740,8 +739,8 @@ void Expedition::DzInviteResponse( } LogExpeditionsModerate( - "Invite response by [{}] accepted [{}] swapping [{}] swap_name [{}]", - add_client->GetName(), accepted, has_swap_name, swap_remove_name + "Invite response by [{}] accepted [{}] swap_name [{}]", + add_client->GetName(), accepted, swap_remove_name ); // a null leader_client is handled by SendLeaderMessage fallbacks @@ -754,7 +753,7 @@ void Expedition::DzInviteResponse( return; } - bool was_swap_invite = (has_swap_name && !swap_remove_name.empty()); + bool was_swap_invite = !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 diff --git a/zone/expedition.h b/zone/expedition.h index 61b71829b..cf6c9306d 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -60,6 +60,13 @@ struct ExpeditionMember : char_id(char_id_), name(name_), status(status_) {} }; +struct ExpeditionInvite +{ + uint32_t expedition_id; + std::string inviter_name; + std::string swap_remove_name; +}; + class Expedition { public: @@ -111,7 +118,7 @@ public: 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 DzInviteResponse(Client* add_client, bool accepted, const 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); From a1b5b210ddda12c182586e233d31b7629dc6b1ca Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 16 May 2020 08:41:17 -0400 Subject: [PATCH 045/196] Send client lockout update in lockout methods Add optional client update argument to client lockout methods This is better than requiring callers to manually send the update --- zone/client.cpp | 23 +++++++++++++++++------ zone/client.h | 4 ++-- zone/expedition.cpp | 2 -- zone/lua_client.cpp | 1 - 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 14f0dcc5c..5621d344a 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9619,7 +9619,7 @@ std::vector Client::GetExpeditionLockouts(const std::str return lockouts; } -void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db) +void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db, bool update_client) { // todo: support for account based lockouts like live AoC expeditions auto it = std::find_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), @@ -9636,9 +9636,15 @@ void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool up m_expedition_lockouts.emplace_back(lockout); } - if (update_db) { // for quest api + if (update_db) // for quest api + { ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { lockout }, true); } + + if (update_client) + { + SendExpeditionLockoutTimers(); + } } void Client::AddNewExpeditionLockout( @@ -9648,11 +9654,10 @@ void Client::AddNewExpeditionLockout( auto expire_time = static_cast(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) + const std::string& expedition_name, const std::string& event_name, bool update_db, bool update_client) { m_expedition_lockouts.erase(std::remove_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) { @@ -9660,9 +9665,15 @@ void Client::RemoveExpeditionLockout( } ), m_expedition_lockouts.end()); - if (update_db) { // for quest api + if (update_db) // for quest api + { ExpeditionDatabase::DeleteCharacterLockout(CharacterID(), expedition_name, event_name); } + + if (update_client) + { + SendExpeditionLockoutTimers(); + } } void Client::RemoveAllExpeditionLockouts(std::string expedition_name) @@ -9714,7 +9725,7 @@ void Client::LoadAllExpeditionLockouts() auto expire_time = strtoull(row[0], nullptr, 10); auto original_duration = static_cast(strtoul(row[1], nullptr, 10)); ExpeditionLockoutTimer lockout{ row[2], row[3], expire_time, original_duration }; - AddExpeditionLockout(lockout); + AddExpeditionLockout(lockout, false, false); } } SendExpeditionLockoutTimers(); diff --git a/zone/client.h b/zone/client.h index 58b0e8643..b08b21a83 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1114,7 +1114,7 @@ public: Client* client, const std::string& client_name, uint16_t chat_type, uint32_t string_id, const std::initializer_list& parameters = {}); - void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false); + void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false, bool update_client = true); void AddNewExpeditionLockout(const std::string& expedition_name, const std::string& event_name, uint32_t duration); Expedition* CreateExpedition( std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, @@ -1129,7 +1129,7 @@ public: 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 RemoveAllExpeditionLockouts(std::string expedition_name = {}); - void RemoveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, bool update_db = false); + void RemoveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, bool update_db = false, bool update_client = true); void SendExpeditionLockoutTimers(); void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; void SetPendingExpeditionInvite(ExpeditionInvite&& invite) { m_pending_expedition_invite = invite; } diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 7a9ce5b00..c800c8d78 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1192,7 +1192,6 @@ void Expedition::ProcessLockoutUpdate( { member_client->RemoveExpeditionLockout(m_expedition_name, event_name); } - member_client->SendExpeditionLockoutTimers(); } } @@ -1214,7 +1213,6 @@ void Expedition::ProcessLockoutUpdate( } else { client->RemoveExpeditionLockout(m_expedition_name, event_name); } - client->SendExpeditionLockoutTimers(); } } diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index c1e87cafc..e55658d24 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1724,7 +1724,6 @@ void Lua_Client::RemoveAllExpeditionLockouts(std::string expedition_name) { void Lua_Client::RemoveExpeditionLockout(std::string expedition_name, std::string event_name) { Lua_Safe_Call_Void(); self->RemoveExpeditionLockout(expedition_name, event_name, true); - self->SendExpeditionLockoutTimers(); } bool Lua_Client::HasExpeditionLockout(std::string expedition_name, std::string event_name) { From cc0c5afd00667007e16000142181cb0594375cb9 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 16 May 2020 10:44:12 -0400 Subject: [PATCH 046/196] Add alternative CreateExpedition api This allows expedition creation to be passed via Lua tables This also allows for compass, safereturn, and/or zone in location data of dynamic zones to be set on expedition creation from lua api Usage example: local instance_info = { "anguish", 0, 21600, compass = { 300, 1353.15, 1712.19, 109.001 }, safereturn = { 300, 1349.13, 1715.00, 123.81, 0 }, zonein = { -9, -2466, -79, 0 } } local expedition_info = { "Anguish, the Fallen Palace", 6, 54, true } local dz = e.other:CreateExpedition(instance_info, expedition_info) --- zone/client.cpp | 5 ++++ zone/client.h | 1 + zone/lua_client.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++ zone/lua_client.h | 1 + 4 files changed, 80 insertions(+) diff --git a/zone/client.cpp b/zone/client.cpp index 5621d344a..242f57fbe 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9584,6 +9584,11 @@ void Client::UpdateExpeditionInfoAndLockouts() LoadAllExpeditionLockouts(); } +Expedition* Client::CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request) +{ + return Expedition::TryCreate(this, dz_instance, request); +} + Expedition* Client::CreateExpedition( std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool has_replay_timer, bool disable_messages) diff --git a/zone/client.h b/zone/client.h index b08b21a83..b4d52d228 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1116,6 +1116,7 @@ public: void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false, bool update_client = true); void AddNewExpeditionLockout(const std::string& expedition_name, const std::string& event_name, uint32_t duration); + Expedition* CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request); 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, bool disable_messages = false); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index e55658d24..8ed8142ad 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -5,6 +5,7 @@ #include "client.h" #include "expedition_lockout_timer.h" +#include "expedition_request.h" #include "lua_client.h" #include "lua_expedition.h" #include "lua_npc.h" @@ -1646,6 +1647,77 @@ int Lua_Client::GetClientMaxLevel() { return self->GetClientMaxLevel(); } +Lua_Expedition Lua_Client::CreateExpedition(luabind::object dz_info, luabind::object expedition_info) { + Lua_Safe_Call_Class(Lua_Expedition); + + if (luabind::type(dz_info) != LUA_TTABLE || luabind::type(expedition_info) != LUA_TTABLE) + { + return nullptr; + } + + // luabind will catch thrown cast_failed exceptions for invalid args, we + // shouldn't need to validate here unless we want non-ambiguous quest errors + std::string zone_name = luabind::object_cast(dz_info[1]); + uint32_t zone_version = luabind::object_cast(dz_info[2]); + uint32_t zone_duration = luabind::object_cast(dz_info[3]); + + DynamicZone dz{ zone_name, zone_version, zone_duration, DynamicZoneType::Expedition }; + + // the dz_info table supports optional hash entries for 'compass', 'safereturn', and 'zonein' data + if (luabind::type(dz_info["compass"]) == LUA_TTABLE) + { + dz.SetCompass(DynamicZoneLocation{ + luabind::object_cast(dz_info["compass"][1]), + luabind::object_cast(dz_info["compass"][2]), + luabind::object_cast(dz_info["compass"][3]), + luabind::object_cast(dz_info["compass"][4]), + 0 + }); + } + + if (luabind::type(dz_info["safereturn"]) == LUA_TTABLE) + { + dz.SetSafeReturn(DynamicZoneLocation{ + luabind::object_cast(dz_info["safereturn"][1]), + luabind::object_cast(dz_info["safereturn"][2]), + luabind::object_cast(dz_info["safereturn"][3]), + luabind::object_cast(dz_info["safereturn"][4]), + luabind::object_cast(dz_info["safereturn"][5]) + }); + } + + if (luabind::type(dz_info["zonein"]) == LUA_TTABLE) + { + dz.SetZoneInLocation(DynamicZoneLocation{ + 0, + luabind::object_cast(dz_info["zonein"][1]), + luabind::object_cast(dz_info["zonein"][2]), + luabind::object_cast(dz_info["zonein"][3]), + luabind::object_cast(dz_info["zonein"][4]) + }); + } + + std::string expedition_name = luabind::object_cast(expedition_info[1]); + uint32_t min_players = luabind::object_cast(expedition_info[2]); + uint32_t max_players = luabind::object_cast(expedition_info[3]); + bool has_replay_timer = false; + bool disable_messages = false; + + if (luabind::type(expedition_info[4]) == LUA_TBOOLEAN) + { + has_replay_timer = luabind::object_cast(expedition_info[4]); + } + + if (luabind::type(expedition_info[5]) == LUA_TBOOLEAN) + { + disable_messages = luabind::object_cast(expedition_info[5]); + } + + ExpeditionRequest request{ expedition_name, min_players, max_players, has_replay_timer, disable_messages }; + + return self->CreateExpedition(dz, request); +} + 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(zone_name, version, duration, expedition_name, min_players, max_players); @@ -2048,6 +2120,7 @@ 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::*)(luabind::object, luabind::object))&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("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32, bool, bool))&Lua_Client::CreateExpedition) diff --git a/zone/lua_client.h b/zone/lua_client.h index e339ead7a..c2f80a6fa 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -339,6 +339,7 @@ public: void SetClientMaxLevel(int value); int GetClientMaxLevel(); + Lua_Expedition CreateExpedition(luabind::object dz_info, luabind::object expedition_info); 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 CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool has_replay_timer, bool disable_messages); From 50f9a499116df1166be01fa5f9c625d615ec836a Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 17 May 2020 00:07:52 -0400 Subject: [PATCH 047/196] Check for empty expedition via database not cache Checking the cache on member removal here isn't reliable due to race with cross zone message If a zone removes a member at the same time as another zone, neither zone can know if the expedition will be empty via cache unless it processes the world message from the other zone's member removal first. --- zone/expedition.cpp | 18 ++++++++++++++++-- zone/expedition_database.cpp | 19 +++++++++++++++++++ zone/expedition_database.h | 1 + 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index c800c8d78..98fcda966 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -509,9 +509,13 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) ChooseNewLeader(); } - if (m_members.empty()) + // we can't check for empty member count via cache because if other zones + // remove members at the same time then we race. cache member count won't + // be accurate until the world messages from other zones are processed + uint32_t member_count = ExpeditionDatabase::GetExpeditionMemberCount(m_id); + if (member_count == 0) { - // cache removal will occur in world message handler + // zone cache removal will occur in world message handler ExpeditionDatabase::DeleteExpedition(m_id); if (RuleB(Expedition, EmptyDzShutdownEnabled)) { @@ -1163,6 +1167,11 @@ void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t re it = is_removed ? m_members.erase(it) : it + 1; } + + LogExpeditionsDetail( + "Processed member [{}] ({}) removal, current zone cache member count: [{}]", + removed_char_name, removed_char_id, m_members.size() + ); } void Expedition::ProcessLockoutUpdate( @@ -1581,6 +1590,11 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition && zone) { + LogExpeditionsDetail( + "World member change message -- remove: [{}] name: [{}] zone: [{}]:[{}] sender: [{}]:[{}]", + buf->removed, buf->char_name, zone->GetZoneID(), zone->GetInstanceID(), buf->sender_zone_id, buf->sender_instance_id + ); + if (!zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) { if (buf->removed) diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index c3dcd3ef8..4ecda739d 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -419,6 +419,25 @@ ExpeditionMember ExpeditionDatabase::GetExpeditionLeader(uint32_t expedition_id) return leader; } +uint32_t ExpeditionDatabase::GetExpeditionMemberCount(uint32_t expedition_id) +{ + auto query = fmt::format(SQL( + SELECT COUNT(IF(is_current_member = TRUE, 1, NULL)) member_count + FROM expedition_members + WHERE expedition_id = {}; + ), expedition_id); + + auto results = database.QueryDatabase(query); + + uint32_t member_count = 0; + if (results.Success() && results.RowCount() > 0) + { + auto row = results.begin(); + member_count = static_cast(strtoul(row[0], nullptr, 10)); + } + return member_count; +} + void ExpeditionDatabase::InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, bool update_expire_times, bool is_pending) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index bb8804d81..d47ab09f3 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -57,6 +57,7 @@ namespace ExpeditionDatabase uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); uint32_t GetExpeditionIDFromInstanceID(uint32_t instance_id); ExpeditionMember GetExpeditionLeader(uint32_t expedition_id); + uint32_t GetExpeditionMemberCount(uint32_t expedition_id); void InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, bool update_expire_times, bool is_pending = false); From 89c6d1e258b48388a8b1df85b21a725e83ebd1eb Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 18 May 2020 23:26:00 -0400 Subject: [PATCH 048/196] Send expedition re-invite to clients that zone Moves expedition message handling in world to Expedition method for messages that need special handling --- common/servertalk.h | 6 ++++ world/cliententry.h | 5 ++++ world/expedition.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++ world/expedition.h | 3 ++ world/zoneserver.cpp | 17 ++---------- zone/client.cpp | 13 ++++++++- zone/client.h | 1 + zone/expedition.cpp | 15 ++++++++-- zone/expedition.h | 3 +- zone/zoning.cpp | 10 +++++++ 10 files changed, 121 insertions(+), 18 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index a50e5832a..031885132 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -155,6 +155,8 @@ #define ServerOP_ExpeditionDzSafeReturn 0x040b #define ServerOP_ExpeditionDzZoneIn 0x040c #define ServerOP_ExpeditionRemoveCharLockouts 0x040d +#define ServerOP_ExpeditionSaveInvite 0x040e +#define ServerOP_ExpeditionRequestInvite 0x040f #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 @@ -2057,6 +2059,10 @@ struct ServerExpeditionCharacterName_Struct { char expedition_name[128]; }; +struct ServerExpeditionCharacterID_Struct { + uint32_t character_id; +}; + struct ServerDzCommand_Struct { uint32 expedition_id; uint8 is_char_online; // 0: target name is offline, 1: online diff --git a/world/cliententry.h b/world/cliententry.h index b4d449c58..769700403 100644 --- a/world/cliententry.h +++ b/world/cliententry.h @@ -127,6 +127,9 @@ public: inline void PushToTellQueue(ServerChannelMessage_Struct *scm) { tell_queue.push_back(scm); } void ProcessTellQueue(); + void SetPendingExpeditionInvite(ServerPacket* pack) { p_pending_expedition_invite.reset(pack->Copy()); }; + std::unique_ptr GetPendingExpeditionInvite() { return std::move(p_pending_expedition_invite); } + private: void ClearVars(bool iAll = false); @@ -171,6 +174,8 @@ private: // Tell Queue -- really a vector :D std::vector tell_queue; + + std::unique_ptr p_pending_expedition_invite = nullptr; }; #endif /*CLIENTENTRY_H_*/ diff --git a/world/expedition.cpp b/world/expedition.cpp index 82ff28ba6..fb52a227b 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -68,6 +68,44 @@ void Expedition::PurgeExpiredCharacterLockouts() } } +void Expedition::HandleZoneMessage(ServerPacket* pack) +{ + switch (pack->opcode) + { + case ServerOP_ExpeditionGetOnlineMembers: + { + Expedition::GetOnlineMembers(pack); + break; + } + case ServerOP_ExpeditionDzAddPlayer: + { + Expedition::AddPlayer(pack); + break; + } + case ServerOP_ExpeditionDzMakeLeader: + { + Expedition::MakeLeader(pack); + break; + } + case ServerOP_ExpeditionRemoveCharLockouts: + { + auto buf = reinterpret_cast(pack->pBuffer); + client_list.SendPacket(buf->character_name, pack); + break; + } + case ServerOP_ExpeditionSaveInvite: + { + Expedition::SaveInvite(pack); + break; + } + case ServerOP_ExpeditionRequestInvite: + { + Expedition::RequestInvite(pack); + break; + } + } +} + void Expedition::AddPlayer(ServerPacket* pack) { auto buf = reinterpret_cast(pack->pBuffer); @@ -138,3 +176,31 @@ void Expedition::GetOnlineMembers(ServerPacket* pack) zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); } + +void Expedition::SaveInvite(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + + ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); + if (invited_cle) + { + // store packet on cle and re-send it when client requests it + buf->is_char_online = true; + pack->opcode = ServerOP_ExpeditionDzAddPlayer; + invited_cle->SetPendingExpeditionInvite(pack); + } +} + +void Expedition::RequestInvite(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + ClientListEntry* cle = client_list.FindCLEByCharacterID(buf->character_id); + if (cle) + { + auto invite_pack = cle->GetPendingExpeditionInvite(); + if (invite_pack && cle->Server()) + { + cle->Server()->SendPacket(invite_pack.get()); + } + } +} diff --git a/world/expedition.h b/world/expedition.h index 2d9346e78..f40cda834 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -27,9 +27,12 @@ namespace Expedition { void PurgeExpiredExpeditions(); void PurgeExpiredCharacterLockouts(); + void HandleZoneMessage(ServerPacket* pack); void AddPlayer(ServerPacket* pack); void MakeLeader(ServerPacket* pack); void GetOnlineMembers(ServerPacket* pack); + void SaveInvite(ServerPacket* pack); + void RequestInvite(ServerPacket* pack); }; #endif diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 35a6e27f0..6f3b1a9e1 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1383,24 +1383,13 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } case ServerOP_ExpeditionGetOnlineMembers: - { - Expedition::GetOnlineMembers(pack); - break; - } case ServerOP_ExpeditionDzAddPlayer: - { - Expedition::AddPlayer(pack); - break; - } case ServerOP_ExpeditionDzMakeLeader: - { - Expedition::MakeLeader(pack); - break; - } case ServerOP_ExpeditionRemoveCharLockouts: + case ServerOP_ExpeditionSaveInvite: + case ServerOP_ExpeditionRequestInvite: { - auto buf = reinterpret_cast(pack->pBuffer); - client_list.SendPacket(buf->character_name, pack); + Expedition::HandleZoneMessage(pack); break; } case ServerOP_DzCharacterChange: diff --git a/zone/client.cpp b/zone/client.cpp index 242f57fbe..979eb294b 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9560,7 +9560,6 @@ void Client::SendCrossZoneMessageString( 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(); @@ -9582,6 +9581,9 @@ void Client::UpdateExpeditionInfoAndLockouts() } LoadAllExpeditionLockouts(); + + // ask world for any pending invite we saved from a previous zone + RequestPendingExpeditionInvite(); } Expedition* Client::CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request) @@ -9775,6 +9777,15 @@ void Client::SendExpeditionLockoutTimers() QueuePacket(outapp.get()); } +void Client::RequestPendingExpeditionInvite() +{ + uint32_t packsize = sizeof(ServerExpeditionCharacterID_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionRequestInvite, packsize)); + auto packbuf = reinterpret_cast(pack->pBuffer); + packbuf->character_id = CharacterID(); + worldserver.SendPacket(pack.get()); +} + void Client::DzListTimers() { // only lists player's current replay timer lockouts, not all event lockouts diff --git a/zone/client.h b/zone/client.h index b4d52d228..17f25d70b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1131,6 +1131,7 @@ public: bool IsInExpedition() const { return m_expedition_id != 0; } void RemoveAllExpeditionLockouts(std::string expedition_name = {}); void RemoveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, bool update_db = false, bool update_client = true); + void RequestPendingExpeditionInvite(); void SendExpeditionLockoutTimers(); void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; void SetPendingExpeditionInvite(ExpeditionInvite&& invite) { m_pending_expedition_invite = invite; } diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 98fcda966..8adec8008 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1269,6 +1269,16 @@ void Expedition::SendClientExpeditionInfo(Client* client) } } +void Expedition::SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name) +{ + LogExpeditions( + "Character [{}] saving pending invite from [{}] to expedition [{}] in world", + add_name, invite.inviter_name, invite.expedition_id + ); + + SendWorldAddPlayerInvite(invite.inviter_name, invite.swap_remove_name, add_name, true); +} + std::unique_ptr Expedition::CreateInfoPacket(bool clear) { uint32_t outsize = sizeof(ExpeditionInfo_Struct); @@ -1375,10 +1385,11 @@ void Expedition::SendWorldExpeditionUpdate(bool destroyed) } void Expedition::SendWorldAddPlayerInvite( - const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name) + const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending) { + auto server_opcode = pending ? ServerOP_ExpeditionSaveInvite : ServerOP_ExpeditionDzAddPlayer; uint32_t pack_size = sizeof(ServerDzCommand_Struct); - auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDzAddPlayer, pack_size)); + auto pack = std::unique_ptr(new ServerPacket(server_opcode, pack_size)); auto buf = reinterpret_cast(pack->pBuffer); buf->expedition_id = GetID(); buf->is_char_online = false; diff --git a/zone/expedition.h b/zone/expedition.h index cf6c9306d..c28e2fa3d 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -115,6 +115,7 @@ public: void RemoveLockout(const std::string& event_name); void SendClientExpeditionInfo(Client* client); + void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); 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 = {}); @@ -155,7 +156,7 @@ private: 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); + void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); 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); diff --git a/zone/zoning.cpp b/zone/zoning.cpp index ac8732e82..35dec1ca1 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -404,6 +404,16 @@ void Client::DoZoneSuccess(ZoneChange_Struct *zc, uint16 zone_id, uint32 instanc if(this->GetPet()) entity_list.RemoveFromHateLists(this->GetPet()); + if (GetPendingExpeditionInviteID() != 0) + { + // live re-invites if client zoned with a pending invite, save pending invite info in world + auto expedition = Expedition::FindCachedExpeditionByID(GetPendingExpeditionInviteID()); + if (expedition) + { + expedition->SendWorldPendingInvite(m_pending_expedition_invite, GetName()); + } + } + LogInfo("Zoning [{}] to: [{}] ([{}]) - ([{}]) x [{}] y [{}] z [{}]", m_pp.name, ZoneName(zone_id), zone_id, instance_id, dest_x, dest_y, dest_z); //set the player's coordinates in the new zone so they have them From 528b74109e8af101ad3a9f76e2dc118c48901820 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 19 May 2020 23:03:36 -0400 Subject: [PATCH 049/196] Only update dz expire time if reducing Add optional UpdateExpireTime parameter This is currently only used when an expedition becomes empty to make dynamic zone instances shutdown earlier. For that it should only update if new time is less than remaining time --- zone/dynamiczone.cpp | 10 ++++------ zone/dynamiczone.h | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 5e87e229c..38377fefe 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -437,21 +437,19 @@ void DynamicZone::SendInstanceCharacterChange(uint32_t character_id, bool remove } } -void DynamicZone::UpdateExpireTime(uint32_t seconds) +void DynamicZone::UpdateExpireTime(uint32_t seconds, bool reduce_only) { - if (GetInstanceID() == 0) + if (GetInstanceID() == 0 || (reduce_only && GetSecondsRemaining() < seconds)) { 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; + m_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()); + ), m_duration, GetInstanceID()); auto results = database.QueryDatabase(query); if (results.Success()) diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index b2bf22b41..bb2bee16b 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -89,7 +89,7 @@ public: 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 UpdateExpireTime(uint32_t seconds, bool reduce_only = true); void LoadFromDatabase(uint32_t instance_id); uint32_t SaveToDatabase(); From c9504452e1e5da63d9cf74eaadb57ef3457c4e21 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 22 May 2020 21:33:04 -0400 Subject: [PATCH 050/196] Add #dzkickplayers command for pre-RoF clients Performs "/kickplayers exp" for older clients without the command --- zone/command.cpp | 13 +++++++++++++ zone/command.h | 1 + 2 files changed, 14 insertions(+) diff --git a/zone/command.cpp b/zone/command.cpp index e9474aa44..10aa6850c 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -200,6 +200,7 @@ int command_init(void) 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("dzkickplayers", "Removes all players from current expedition. (/kickplayers alternative for pre-RoF clients)", 0, command_dzkickplayers) || 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) || @@ -6944,6 +6945,18 @@ void command_dz(Client* c, const Seperator* sep) } } +void command_dzkickplayers(Client* c, const Seperator* sep) +{ + if (c) + { + auto expedition = c->GetExpedition(); + if (expedition) + { + expedition->DzKickPlayers(c); + } + } +} + void command_editmassrespawn(Client* c, const Seperator* sep) { if (strcasecmp(sep->arg[1], "usage") == 0) { diff --git a/zone/command.h b/zone/command.h index aebe8a697..a6f967d78 100644 --- a/zone/command.h +++ b/zone/command.h @@ -93,6 +93,7 @@ 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_dzkickplayers(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); From 8c1f556f29099e76a3ecfc3df9527d7159b56292 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 23 May 2020 00:05:01 -0400 Subject: [PATCH 051/196] Rename #dz cache to #dz expedition Add expedition's remaining dz time to expedition list Move #dz destroy to a #dz expedition subcommand Add success or failure messages to destroy command --- zone/command.cpp | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 10aa6850c..f7028cd96 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6834,19 +6834,24 @@ void command_dz(Client* c, const Seperator* sep) return; } - if (strcasecmp(sep->arg[1], "cache") == 0) + if (strcasecmp(sep->arg[1], "expedition") == 0) { if (strcasecmp(sep->arg[2], "list") == 0) { c->Message(Chat::White, fmt::format("Total Active Expeditions: [{}]", zone->expedition_cache.size()).c_str()); for (const auto& expedition : zone->expedition_cache) { + auto seconds = expedition.second->GetDynamicZone().GetSecondsRemaining(); + c->Message(Chat::White, fmt::format( - "Expedition id: [{}]: leader: [{}] instance id: [{}] members: [{}]", + "Expedition id: [{}]: leader: [{}] instance id: [{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", expedition.second->GetID(), expedition.second->GetLeaderName(), expedition.second->GetInstanceID(), - expedition.second->GetMemberCount() + expedition.second->GetMemberCount(), + seconds / 3600, // hours + (seconds / 60) % 60, // minutes + seconds % 60 // seconds ).c_str()); } } @@ -6857,19 +6862,20 @@ void command_dz(Client* c, const Seperator* sep) "Reloaded [{}] expeditions to cache from database.", zone->expedition_cache.size() ).c_str()); } - } - else if (strcasecmp(sep->arg[1], "destroy") == 0) - { - if (sep->IsNumber(2)) + else if (strcasecmp(sep->arg[2], "destroy") == 0 && sep->IsNumber(3)) { - auto expedition_id = std::strtoul(sep->arg[2], nullptr, 10); - if (expedition_id) + auto expedition_id = std::strtoul(sep->arg[3], nullptr, 10); + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) { - auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); - if (expedition) - { - expedition->RemoveAllMembers(); - } + c->Message(Chat::White, fmt::format( + "Destroying expedition [{}] ({})", expedition_id, expedition->GetName()).c_str() + ); + expedition->RemoveAllMembers(); + } + else + { + c->Message(Chat::Red, fmt::format("Failed to destroy expedition [{}]", sep->arg[3]).c_str()); } } } @@ -6936,10 +6942,10 @@ void command_dz(Client* c, const Seperator* sep) 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 [all] - list dynamic zones from database -- 'all' includes expired"); + c->Message(Chat::White, "#dz expedition list - list expeditions in current zone cache"); + c->Message(Chat::White, "#dz expedition reload - reload expedition zone cache from database"); + c->Message(Chat::White, "#dz expedition destroy - destroy expedition globally (must be in cache)"); + c->Message(Chat::White, "#dz list [all] - list dynamic zone instances from database -- 'all' includes expired"); c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); c->Message(Chat::White, "#dz lockouts remove \"\" - delete lockouts by expedition"); } From 780cf148fa2b8f4adeceac78eff496e78362d574 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 23 May 2020 17:04:53 -0400 Subject: [PATCH 052/196] Use built-in benchmarking for expedition caching --- zone/expedition.cpp | 14 ++++++-------- zone/expedition_request.cpp | 7 +++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 8adec8008..34101e19b 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -209,7 +209,7 @@ void Expedition::CacheFromDatabase(uint32_t expedition_id) { if (zone) { - auto start = std::chrono::steady_clock::now(); + BenchTimer benchmark; auto results = ExpeditionDatabase::LoadExpedition(expedition_id); if (!results.Success()) @@ -220,9 +220,8 @@ void Expedition::CacheFromDatabase(uint32_t expedition_id) CacheExpeditions(results); - auto end = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast>(end - start); - LogExpeditions("Caching new expedition [{}] took {}s", expedition_id, elapsed.count()); + auto elapsed = benchmark.elapsed(); + LogExpeditions("Caching new expedition [{}] took {}s", expedition_id, elapsed); } } @@ -233,7 +232,7 @@ bool Expedition::CacheAllFromDatabase() return false; } - auto start = std::chrono::steady_clock::now(); + BenchTimer benchmark; zone->expedition_cache.clear(); @@ -247,9 +246,8 @@ bool Expedition::CacheAllFromDatabase() CacheExpeditions(results); - auto end = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast>(end - start); - LogExpeditions("Caching [{}] expedition(s) took {}s", zone->expedition_cache.size(), elapsed.count()); + auto elapsed = benchmark.elapsed(); + LogExpeditions("Caching [{}] expedition(s) took {}s", zone->expedition_cache.size(), elapsed); return true; } diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index d72e73b70..77bdaa884 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -59,7 +59,7 @@ bool ExpeditionRequest::Validate(Client* requester) // a message is sent to leader for every member that fails a requirement - auto start = std::chrono::steady_clock::now(); + BenchTimer benchmark; bool requirements_met = false; @@ -81,9 +81,8 @@ bool ExpeditionRequest::Validate(Client* requester) requirements_met = ValidateMembers(fmt::format("'{}'", m_leader_name), 1); } - auto end = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast>(end - start); - LogExpeditions("Create validation for [{}] members took {}s", m_members.size(), elapsed.count()); + auto elapsed = benchmark.elapsed(); + LogExpeditions("Create validation for [{}] members took {}s", m_members.size(), elapsed); return requirements_met; } From 32cc2d66ddcc3c583411fa9e35b0a8a2940afa3a Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 23 May 2020 17:25:09 -0400 Subject: [PATCH 053/196] Use stl algorithms for expedition member searches --- zone/expedition.cpp | 48 ++++++++++++++++++--------------------------- zone/expedition.h | 2 +- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 34101e19b..cedca641b 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -352,52 +352,42 @@ bool Expedition::HasReplayLockout() bool Expedition::HasMember(uint32_t character_id) { - for (const auto& member : m_members) - { - if (member.char_id == character_id) - { - return true; - } - } - return false; + return std::any_of(m_members.begin(), m_members.end(), [&](const ExpeditionMember& member) { + return member.char_id == character_id; + }); } -bool Expedition::HasMember(const std::string& name) +bool Expedition::HasMember(const std::string& character_name) { - for (const auto& member : m_members) - { - if (strcasecmp(member.name.c_str(), name.c_str()) == 0) - { - return true; - } - } - return false; + return std::any_of(m_members.begin(), m_members.end(), [&](const ExpeditionMember& member) { + return (strcasecmp(member.name.c_str(), character_name.c_str()) == 0); + }); } ExpeditionMember Expedition::GetMemberData(uint32_t character_id) { + auto it = std::find_if(m_members.begin(), m_members.end(), [&](const ExpeditionMember& member) { + return member.char_id == character_id; + }); + ExpeditionMember member_data; - for (const auto& member : m_members) + if (it != m_members.end()) { - if (member.char_id == character_id) - { - member_data = member; - break; - } + member_data = *it; } return member_data; } ExpeditionMember Expedition::GetMemberData(const std::string& character_name) { + auto it = std::find_if(m_members.begin(), m_members.end(), [&](const ExpeditionMember& member) { + return (strcasecmp(member.name.c_str(), character_name.c_str()) == 0); + }); + ExpeditionMember member_data; - for (const auto& member : m_members) + if (it != m_members.end()) { - if (strcasecmp(member.name.c_str(), character_name.c_str()) == 0) - { - member_data = member; - break; - } + member_data = *it; } return member_data; } diff --git a/zone/expedition.h b/zone/expedition.h index c28e2fa3d..ce1e243f8 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -100,7 +100,7 @@ public: const std::vector& 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(const std::string& character_name); bool HasMember(uint32_t character_id); void RemoveAllMembers(bool enable_removal_timers = true, bool update_dz_expire_time = true); bool RemoveMember(const std::string& remove_char_name); From 39fad0c1a0f4aeb15dd5c0081a17b079d72fbfbc Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 23 May 2020 18:19:16 -0400 Subject: [PATCH 054/196] Remove unused ExpeditionMemberStatus Lua constants --- zone/lua_expedition.cpp | 12 ------------ zone/lua_expedition.h | 1 - zone/lua_parser.cpp | 3 +-- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 4a9054d27..d6dbccc5d 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -166,16 +166,4 @@ luabind::scope lua_register_expedition() { .def("SetZoneInLocation", (void(Lua_Expedition::*)(float, float, float, float))&Lua_Expedition::SetZoneInLocation); } -luabind::scope lua_register_expedition_member_status() { - return luabind::class_("ExpeditionMemberStatus") - .enum_("constants") - [ - luabind::value("Unknown", static_cast(ExpeditionMemberStatus::Unknown)), - luabind::value("Online", static_cast(ExpeditionMemberStatus::Online)), - luabind::value("Offline", static_cast(ExpeditionMemberStatus::Offline)), - luabind::value("InDynamicZone", static_cast(ExpeditionMemberStatus::InDynamicZone)), - luabind::value("LinkDead", static_cast(ExpeditionMemberStatus::LinkDead)) - ]; -} - #endif // LUA_EQEMU diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 83ee0b046..f7fa1f90a 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -39,7 +39,6 @@ namespace luabind { } luabind::scope lua_register_expedition(); -luabind::scope lua_register_expedition_member_status(); class Lua_Expedition : public Lua_Ptr { diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 2b5ed0ad6..063386713 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -1110,8 +1110,7 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_ruleb(), lua_register_journal_speakmode(), lua_register_journal_mode(), - lua_register_expedition(), - lua_register_expedition_member_status() + lua_register_expedition() ]; } catch(std::exception &ex) { From aee3e1084cc732c913eefabe0d5a3176372143f6 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 24 May 2020 14:55:27 -0400 Subject: [PATCH 055/196] Fix missing invite failure messages --- zone/expedition.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index cedca641b..7ba767386 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -883,19 +883,27 @@ void Expedition::DzAddPlayer( return; } + bool invite_failed = false; + if (add_char_name.empty()) { requester->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_char_name.c_str()); - return; + invite_failed = true; + } + else + { + // we can avoid checking online status in world if we trust member status accuracy + auto member_data = GetMemberData(add_char_name); + if (member_data.char_id != 0 && member_data.status != ExpeditionMemberStatus::Offline) + { + requester->MessageString(Chat::Red, DZADD_ALREADY_PART, add_char_name.c_str()); + invite_failed = true; + } } - // live prioritizes the "not online" message before the "already a member" - // message but we can avoid checking world if we trust member status accuracy - // live sanitizes input except for "sending invite" and "not online" msgs - auto member_data = GetMemberData(add_char_name); - if (member_data.char_id != 0 && member_data.status != ExpeditionMemberStatus::Offline) + if (invite_failed) { - requester->MessageString(Chat::Red, DZADD_ALREADY_PART, add_char_name.c_str()); + requester->MessageString(Chat::Red, DZADD_INVITE_FAIL, FormatName(add_char_name).c_str()); return; } @@ -1679,6 +1687,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) if (leader) { leader->MessageString(Chat::Red, DZADD_NOT_ONLINE, FormatName(buf->target_name).c_str()); + leader->MessageString(Chat::Red, DZADD_INVITE_FAIL, FormatName(buf->target_name).c_str()); } } break; From a7795eda5d39f347b683fbe8f884eacbdb0e8560 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 24 May 2020 21:17:21 -0400 Subject: [PATCH 056/196] Change expedition tables to latin1_swedish_ci Fixes insertion in MySQL older than 5.7.7 and MariaDB older than 10.2.2 that limit indexes to 767 bytes. This may be a temporary fix until future refactoring --- utils/sql/git/required/wip_expeditions.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 052a52a0d..4810b1ade 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -10,7 +10,7 @@ CREATE TABLE `expedition_details` ( UNIQUE INDEX `instance_id` (`instance_id`), CONSTRAINT `FK_expedition_details_instance_list` FOREIGN KEY (`instance_id`) REFERENCES `instance_list` (`id`) ON DELETE SET NULL ) -COLLATE='utf8mb4_general_ci' +COLLATE='latin1_swedish_ci' ENGINE=InnoDB ; @@ -25,7 +25,7 @@ CREATE TABLE `expedition_lockouts` ( UNIQUE INDEX `expedition_id_event_name` (`expedition_id`, `event_name`), CONSTRAINT `FK_expedition_lockouts_expedition_details` FOREIGN KEY (`expedition_id`) REFERENCES `expedition_details` (`id`) ON DELETE CASCADE ) -COLLATE='utf8mb4_general_ci' +COLLATE='latin1_swedish_ci' ENGINE=InnoDB ; @@ -53,7 +53,7 @@ CREATE TABLE `expedition_character_lockouts` ( PRIMARY KEY (`id`), UNIQUE INDEX `character_id_expedition_name_event_name` (`character_id`, `expedition_name`, `event_name`) ) -COLLATE='utf8mb4_general_ci' +COLLATE='latin1_swedish_ci' ENGINE=InnoDB ROW_FORMAT=DYNAMIC ; From 5ddb62e2754e29fa95455b6319e7075467787a94 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 23 May 2020 22:48:29 -0400 Subject: [PATCH 057/196] Make adding replay timers to new members optional Not all expeditions with a replay timer lockout add it to newly added members automatically This adds the Expedition::SetReplayLockoutOnMemberJoin(bool) method to the quest api so it can be disabled --- common/servertalk.h | 8 +++ utils/sql/git/required/wip_expeditions.sql | 1 + world/zoneserver.cpp | 1 + zone/expedition.cpp | 62 ++++++++++++++++++---- zone/expedition.h | 11 ++-- zone/expedition_database.cpp | 17 ++++++ zone/expedition_database.h | 1 + zone/lua_expedition.cpp | 6 +++ zone/lua_expedition.h | 1 + zone/worldserver.cpp | 1 + 10 files changed, 95 insertions(+), 14 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 031885132..7f73d1115 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -157,6 +157,7 @@ #define ServerOP_ExpeditionRemoveCharLockouts 0x040d #define ServerOP_ExpeditionSaveInvite 0x040e #define ServerOP_ExpeditionRequestInvite 0x040f +#define ServerOP_ExpeditionReplayOnJoin 0x0410 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 @@ -2054,6 +2055,13 @@ struct ServerExpeditionLockout_Struct { char event_name[256]; }; +struct ServerExpeditionSetting_Struct { + uint32 expedition_id; + uint32 sender_zone_id; + uint16 sender_instance_id; + uint8 enabled; +}; + struct ServerExpeditionCharacterName_Struct { char character_name[64]; char expedition_name[128]; diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 4810b1ade..f492ebe2c 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -6,6 +6,7 @@ CREATE TABLE `expedition_details` ( `min_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, `max_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, `has_replay_timer` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, + `add_replay_on_join` TINYINT(3) UNSIGNED NOT NULL DEFAULT 1, PRIMARY KEY (`id`), UNIQUE INDEX `instance_id` (`instance_id`), CONSTRAINT `FK_expedition_details_instance_list` FOREIGN KEY (`instance_id`) REFERENCES `instance_list` (`id`) ON DELETE SET NULL diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 6f3b1a9e1..df8ecb27a 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1375,6 +1375,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ExpeditionMemberChange: case ServerOP_ExpeditionMemberSwap: case ServerOP_ExpeditionMemberStatus: + case ServerOP_ExpeditionReplayOnJoin: case ServerOP_ExpeditionDzCompass: case ServerOP_ExpeditionDzSafeReturn: case ServerOP_ExpeditionDzZoneIn: diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 7ba767386..02e1e3057 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -160,7 +160,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) if (expedition_id != last_expedition_id) { auto leader_id = static_cast(strtoul(row[3], nullptr, 10)); - ExpeditionMember leader{ leader_id, row[7] }; // id, name + ExpeditionMember leader{ leader_id, row[8] }; // id, name auto instance_id = row[1] ? strtoul(row[1], nullptr, 10) : 0; // can be null from fk constraint DynamicZone dynamic_zone = DynamicZone::LoadDzFromDatabase(instance_id); @@ -175,6 +175,9 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) (strtoul(row[6], nullptr, 10) != 0) // has_replay_timer )); + bool add_replay_on_join = (strtoul(row[7], nullptr, 10) != 0); + + expedition->SetReplayLockoutOnMemberJoin(add_replay_on_join); expedition->LoadMembers(); expedition->SendUpdatesToZoneMembers(); @@ -188,17 +191,17 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) last_expedition_id = expedition_id; // optional lockouts from left join - if (row[8] && row[9] && row[10] && row[11]) + if (row[9] && row[10] && row[11] && row[12]) { auto it = zone->expedition_cache.find(last_expedition_id); if (it != zone->expedition_cache.end()) { it->second->AddInternalLockout(ExpeditionLockoutTimer{ row[2], // expedition_name - row[8], // event_name - strtoull(row[9], nullptr, 10), // expire_time - static_cast(strtoul(row[10], nullptr, 10)), // original duration - (strtoul(row[11], nullptr, 10) != 0) // is_inherited + row[9], // event_name + strtoull(row[10], nullptr, 10), // expire_time + static_cast(strtoul(row[11], nullptr, 10)), // original duration + (strtoul(row[12], nullptr, 10) != 0) // is_inherited }); } } @@ -392,6 +395,17 @@ ExpeditionMember Expedition::GetMemberData(const std::string& character_name) return member_data; } +void Expedition::SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db) +{ + m_add_replay_on_join = add_on_join; + + if (update_db) + { + ExpeditionDatabase::UpdateReplayLockoutOnJoin(m_id, add_on_join); + SendWorldSettingChanged(ServerOP_ExpeditionReplayOnJoin, m_add_replay_on_join); + } +} + void Expedition::AddReplayLockout(uint32_t seconds) { AddLockout(DZ_REPLAY_TIMER_NAME, seconds); @@ -774,12 +788,15 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: if (!lockout.IsInherited() && !add_client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) { - // replay timers are added to characters immediately on joining with - // a fresh expire time using the original duration + // replay timers are optionally added to new members immediately on + // join with a fresh expire time using the original duration. if (m_has_replay_timer && lockout.IsReplayTimer()) { - add_client->AddNewExpeditionLockout( - lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetDuration()); + if (m_add_replay_on_join) + { + add_client->AddNewExpeditionLockout( + lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetDuration()); + } } else { @@ -1499,6 +1516,18 @@ void Expedition::SendWorldMemberSwapped( worldserver.SendPacket(pack.get()); } +void Expedition::SendWorldSettingChanged(uint16_t server_opcode, bool setting_value) +{ + uint32_t pack_size = sizeof(ServerExpeditionSetting_Struct); + auto pack = std::unique_ptr(new ServerPacket(server_opcode, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->enabled = setting_value; + worldserver.SendPacket(pack.get()); +} + void Expedition::SendWorldGetOnlineMembers() { // request online status of all characters in our expedition tracked by world @@ -1649,6 +1678,19 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_ExpeditionReplayOnJoin: + { + 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 (expedition) + { + expedition->SetReplayLockoutOnMemberJoin(buf->enabled); + } + } + break; + } case ServerOP_ExpeditionGetOnlineMembers: { // reply from world for online member statuses request diff --git a/zone/expedition.h b/zone/expedition.h index ce1e243f8..811a4a3ad 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -113,6 +113,7 @@ public: bool HasLockout(const std::string& event_name); bool HasReplayLockout(); void RemoveLockout(const std::string& event_name); + void SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db = false); void SendClientExpeditionInfo(Client* client); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); @@ -163,6 +164,7 @@ private: 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 SendWorldSettingChanged(uint16_t server_opcode, bool setting_value); 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); @@ -175,10 +177,11 @@ private: std::unique_ptr CreateMemberListStatusPacket(const std::string& name, ExpeditionMemberStatus status); std::unique_ptr CreateLeaderNamePacket(); - uint32_t m_id = 0; - uint32_t m_min_players = 0; - uint32_t m_max_players = 0; - bool m_has_replay_timer = false; + uint32_t m_id = 0; + uint32_t m_min_players = 0; + uint32_t m_max_players = 0; + bool m_has_replay_timer = false; + bool m_add_replay_on_join = true; std::string m_expedition_name; DynamicZone m_dynamiczone { DynamicZoneType::Expedition }; ExpeditionMember m_leader; diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 4ecda739d..9b54df7ed 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -62,6 +62,7 @@ MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) expedition_details.min_players, expedition_details.max_players, expedition_details.has_replay_timer, + expedition_details.add_replay_on_join, character_data.name leader_name, expedition_lockouts.event_name, UNIX_TIMESTAMP(expedition_lockouts.expire_time), @@ -93,6 +94,7 @@ MySQLRequestResult ExpeditionDatabase::LoadAllExpeditions() expedition_details.min_players, expedition_details.max_players, expedition_details.has_replay_timer, + expedition_details.add_replay_on_join, character_data.name leader_name, expedition_lockouts.event_name, UNIX_TIMESTAMP(expedition_lockouts.expire_time), @@ -656,3 +658,18 @@ void ExpeditionDatabase::UpdateMemberRemoved(uint32_t expedition_id, uint32_t ch LogExpeditions("Failed to remove [{}] from expedition [{}]", character_id, expedition_id); } } + +void ExpeditionDatabase::UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join) +{ + LogExpeditionsDetail("Updating replay lockout on join [{}] for expedition [{}]", add_on_join, expedition_id); + + auto query = fmt::format(SQL( + UPDATE expedition_details SET add_replay_on_join = {} WHERE id = {}; + ), add_on_join, expedition_id); + + auto results = database.QueryDatabase(query); + if (!results.Success()) + { + LogExpeditions("Failed to update expedition [{}] replay timer setting", expedition_id); + } +} diff --git a/zone/expedition_database.h b/zone/expedition_database.h index d47ab09f3..ea1788574 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -68,6 +68,7 @@ namespace ExpeditionDatabase void InsertMembers(uint32_t expedition_id, const std::vector& members); void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); void UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id); + void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); }; #endif diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index d6dbccc5d..469021b7c 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -125,6 +125,11 @@ void Lua_Expedition::SetCompass(std::string zone_name, float x, float y, float z return self->SetDzCompass(zone_name, x, y, z, true); } +void Lua_Expedition::SetReplayLockoutOnMemberJoin(bool enable) { + Lua_Safe_Call_Void(); + self->SetReplayLockoutOnMemberJoin(enable, 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); @@ -161,6 +166,7 @@ luabind::scope lua_register_expedition() { .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("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) .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); diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index f7fa1f90a..c344b6d31 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -68,6 +68,7 @@ public: 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 SetReplayLockoutOnMemberJoin(bool enable); 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); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index fa005ea6e..7503ed037 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2906,6 +2906,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionMemberChange: case ServerOP_ExpeditionMemberSwap: case ServerOP_ExpeditionMemberStatus: + case ServerOP_ExpeditionReplayOnJoin: case ServerOP_ExpeditionGetOnlineMembers: case ServerOP_ExpeditionDzAddPlayer: case ServerOP_ExpeditionDzMakeLeader: From 3f4ea66ea176e0ebd8e3292d058bf7eaa8aac907 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 24 May 2020 16:22:14 -0400 Subject: [PATCH 058/196] Implement expedition locking Disables the ability to add new members Adds Expedition::SetLocked(bool) to quest api Adds is_locked column to expedition_details db table --- common/servertalk.h | 1 + utils/sql/git/required/wip_expeditions.sql | 1 + world/zoneserver.cpp | 1 + zone/expedition.cpp | 53 ++++++++++++++++++---- zone/expedition.h | 2 + zone/expedition_database.cpp | 20 +++++++- zone/expedition_database.h | 1 + zone/lua_expedition.cpp | 6 +++ zone/lua_expedition.h | 1 + zone/string_ids.h | 1 + zone/worldserver.cpp | 1 + 11 files changed, 77 insertions(+), 11 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 7f73d1115..8f1b64ac7 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -158,6 +158,7 @@ #define ServerOP_ExpeditionSaveInvite 0x040e #define ServerOP_ExpeditionRequestInvite 0x040f #define ServerOP_ExpeditionReplayOnJoin 0x0410 +#define ServerOP_ExpeditionLockState 0x0411 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index f492ebe2c..035330f79 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -7,6 +7,7 @@ CREATE TABLE `expedition_details` ( `max_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, `has_replay_timer` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, `add_replay_on_join` TINYINT(3) UNSIGNED NOT NULL DEFAULT 1, + `is_locked` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE INDEX `instance_id` (`instance_id`), CONSTRAINT `FK_expedition_details_instance_list` FOREIGN KEY (`instance_id`) REFERENCES `instance_list` (`id`) ON DELETE SET NULL diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index df8ecb27a..c0f9f7ed6 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1372,6 +1372,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ExpeditionDeleted: case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: + case ServerOP_ExpeditionLockState: case ServerOP_ExpeditionMemberChange: case ServerOP_ExpeditionMemberSwap: case ServerOP_ExpeditionMemberStatus: diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 02e1e3057..1b1513ea9 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -160,7 +160,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) if (expedition_id != last_expedition_id) { auto leader_id = static_cast(strtoul(row[3], nullptr, 10)); - ExpeditionMember leader{ leader_id, row[8] }; // id, name + ExpeditionMember leader{ leader_id, row[9] }; // id, name auto instance_id = row[1] ? strtoul(row[1], nullptr, 10) : 0; // can be null from fk constraint DynamicZone dynamic_zone = DynamicZone::LoadDzFromDatabase(instance_id); @@ -176,8 +176,10 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) )); bool add_replay_on_join = (strtoul(row[7], nullptr, 10) != 0); + bool is_locked = (strtoul(row[8], nullptr, 10) != 0); expedition->SetReplayLockoutOnMemberJoin(add_replay_on_join); + expedition->SetLocked(is_locked); expedition->LoadMembers(); expedition->SendUpdatesToZoneMembers(); @@ -191,17 +193,17 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) last_expedition_id = expedition_id; // optional lockouts from left join - if (row[9] && row[10] && row[11] && row[12]) + if (row[10] && row[11] && row[12] && row[13]) { auto it = zone->expedition_cache.find(last_expedition_id); if (it != zone->expedition_cache.end()) { it->second->AddInternalLockout(ExpeditionLockoutTimer{ row[2], // expedition_name - row[9], // event_name - strtoull(row[10], nullptr, 10), // expire_time - static_cast(strtoul(row[11], nullptr, 10)), // original duration - (strtoul(row[12], nullptr, 10) != 0) // is_inherited + row[10], // event_name + strtoull(row[11], nullptr, 10), // expire_time + static_cast(strtoul(row[12], nullptr, 10)), // original duration + (strtoul(row[13], nullptr, 10) != 0) // is_inherited }); } } @@ -760,7 +762,16 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: } bool was_swap_invite = !swap_remove_name.empty(); - bool has_conflicts = ProcessAddConflicts(leader_client, add_client, was_swap_invite); + bool has_conflicts = m_is_locked; + + if (m_is_locked) + { + SendLeaderMessage(leader_client, Chat::Red, DZADD_NOT_ALLOWING); + } + else + { + has_conflicts = ProcessAddConflicts(leader_client, add_client, was_swap_invite); + } // error if swapping and character was already removed before the accept if (was_swap_invite && !HasMember(swap_remove_name)) @@ -902,7 +913,12 @@ void Expedition::DzAddPlayer( bool invite_failed = false; - if (add_char_name.empty()) + if (m_is_locked) + { + requester->MessageString(Chat::Red, DZADD_NOT_ALLOWING); + invite_failed = true; + } + else if (add_char_name.empty()) { requester->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_char_name.c_str()); invite_failed = true; @@ -1068,6 +1084,17 @@ void Expedition::DzKickPlayers(Client* requester) requester->MessageString(Chat::Red, EXPEDITION_REMOVED, KICKPLAYERS_EVERYONE, m_expedition_name.c_str()); } +void Expedition::SetLocked(bool lock_expedition, bool update_db) +{ + m_is_locked = lock_expedition; + + if (update_db) + { + ExpeditionDatabase::UpdateLockState(m_id, lock_expedition); + SendWorldSettingChanged(ServerOP_ExpeditionLockState, m_is_locked); + } +} + void Expedition::SetNewLeader(uint32_t new_leader_id, const std::string& new_leader_name) { ExpeditionDatabase::UpdateLeaderID(m_id, new_leader_id); @@ -1678,6 +1705,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_ExpeditionLockState: case ServerOP_ExpeditionReplayOnJoin: { auto buf = reinterpret_cast(pack->pBuffer); @@ -1686,7 +1714,14 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition) { - expedition->SetReplayLockoutOnMemberJoin(buf->enabled); + if (pack->opcode == ServerOP_ExpeditionLockState) + { + expedition->SetLocked(buf->enabled); + } + else if (pack->opcode == ServerOP_ExpeditionReplayOnJoin) + { + expedition->SetReplayLockoutOnMemberJoin(buf->enabled); + } } } break; diff --git a/zone/expedition.h b/zone/expedition.h index 811a4a3ad..d6b30e376 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -107,6 +107,7 @@ public: 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 SetLocked(bool lock_expedition, bool update_db = false); void AddLockout(const std::string& event_name, uint32_t seconds); void AddReplayLockout(uint32_t seconds); @@ -180,6 +181,7 @@ private: uint32_t m_id = 0; uint32_t m_min_players = 0; uint32_t m_max_players = 0; + bool m_is_locked = false; bool m_has_replay_timer = false; bool m_add_replay_on_join = true; std::string m_expedition_name; diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 9b54df7ed..8dc5bb294 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -52,7 +52,6 @@ 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, @@ -63,6 +62,7 @@ MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) expedition_details.max_players, expedition_details.has_replay_timer, expedition_details.add_replay_on_join, + expedition_details.is_locked, character_data.name leader_name, expedition_lockouts.event_name, UNIX_TIMESTAMP(expedition_lockouts.expire_time), @@ -95,6 +95,7 @@ MySQLRequestResult ExpeditionDatabase::LoadAllExpeditions() expedition_details.max_players, expedition_details.has_replay_timer, expedition_details.add_replay_on_join, + expedition_details.is_locked, character_data.name leader_name, expedition_lockouts.event_name, UNIX_TIMESTAMP(expedition_lockouts.expire_time), @@ -633,7 +634,7 @@ void ExpeditionDatabase::UpdateLeaderID(uint32_t expedition_id, uint32_t leader_ LogExpeditionsDetail("Updating leader [{}] for expedition [{}]", leader_id, expedition_id); auto query = fmt::format(SQL( - UPDATE expedition_details SET leader_id = {} WHERE id = {} + UPDATE expedition_details SET leader_id = {} WHERE id = {}; ), leader_id, expedition_id); auto results = database.QueryDatabase(query); @@ -643,6 +644,21 @@ void ExpeditionDatabase::UpdateLeaderID(uint32_t expedition_id, uint32_t leader_ } } +void ExpeditionDatabase::UpdateLockState(uint32_t expedition_id, bool is_locked) +{ + LogExpeditionsDetail("Updating lock state [{}] for expedition [{}]", is_locked, expedition_id); + + auto query = fmt::format(SQL( + UPDATE expedition_details SET is_locked = {} WHERE id = {}; + ), is_locked, expedition_id); + + auto results = database.QueryDatabase(query); + if (!results.Success()) + { + LogExpeditions("Failed to update expedition [{}] lock state", expedition_id); + } +} + void ExpeditionDatabase::UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id) { LogExpeditionsDetail("Removing member [{}] from expedition [{}]", character_id, expedition_id); diff --git a/zone/expedition_database.h b/zone/expedition_database.h index ea1788574..2fe68efa9 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -67,6 +67,7 @@ namespace ExpeditionDatabase void InsertMember(uint32_t expedition_id, uint32_t character_id); void InsertMembers(uint32_t expedition_id, const std::vector& members); void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); + void UpdateLockState(uint32_t expedition_id, bool is_locked); void UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id); void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); }; diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 469021b7c..a27ed0efa 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -125,6 +125,11 @@ void Lua_Expedition::SetCompass(std::string zone_name, float x, float y, float z return self->SetDzCompass(zone_name, x, y, z, true); } +void Lua_Expedition::SetLocked(bool lock_expedition) { + Lua_Safe_Call_Void(); + self->SetLocked(lock_expedition, true); +} + void Lua_Expedition::SetReplayLockoutOnMemberJoin(bool enable) { Lua_Safe_Call_Void(); self->SetReplayLockoutOnMemberJoin(enable, true); @@ -166,6 +171,7 @@ luabind::scope lua_register_expedition() { .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("SetLocked", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetLocked) .def("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) .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) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index c344b6d31..904d52dfe 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -68,6 +68,7 @@ public: 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 SetLocked(bool lock_expedition); void SetReplayLockoutOnMemberJoin(bool enable); 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); diff --git a/zone/string_ids.h b/zone/string_ids.h index 5f1b89a02..40aa13057 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -323,6 +323,7 @@ #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_ALLOWING 3585 //The expedition is not allowing players to be added. #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. diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 7503ed037..d903db214 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2903,6 +2903,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionDeleted: case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: + case ServerOP_ExpeditionLockState: case ServerOP_ExpeditionMemberChange: case ServerOP_ExpeditionMemberSwap: case ServerOP_ExpeditionMemberStatus: From 33f23362445f4e00e82259767c03a517009c92cf Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 26 May 2020 20:50:15 -0400 Subject: [PATCH 059/196] Only draw dynamic zone compasses in non-instances This fixes compasses being drawn in instances that use the same zone as compass Also adds RemoveCompass method to quest api --- zone/client.cpp | 4 ++-- zone/lua_expedition.cpp | 18 ++++++++++++------ zone/lua_expedition.h | 1 + 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 979eb294b..3bf285d78 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9839,7 +9839,7 @@ void Client::SendDzCompassUpdate() if (expedition) { auto compass = expedition->GetDynamicZone().GetCompassLocation(); - if (zone && zone->GetZoneID() == compass.zone_id) + if (zone && zone->GetZoneID() == compass.zone_id && zone->GetInstanceID() == 0) { DynamicZoneCompassEntry_Struct entry; entry.dz_zone_id = static_cast(expedition->GetDynamicZone().GetZoneID()); @@ -9856,7 +9856,7 @@ void Client::SendDzCompassUpdate() // todo: shared tasks, missions, and quests with an associated dz // compass set via MarkSingleCompassLocation() - if (zone && zone->GetZoneID() == m_quest_compass.zone_id) + if (zone && zone->GetZoneID() == m_quest_compass.zone_id && zone->GetInstanceID() == 0) { DynamicZoneCompassEntry_Struct entry; entry.dz_zone_id = 0; diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index a27ed0efa..f33718568 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -110,6 +110,11 @@ bool Lua_Expedition::HasReplayLockout() { return self->HasReplayLockout(); } +void Lua_Expedition::RemoveCompass() { + Lua_Safe_Call_Void(); + self->SetDzCompass(0, 0, 0, 0, true); +} + void Lua_Expedition::RemoveLockout(std::string event_name) { Lua_Safe_Call_Void(); self->RemoveLockout(event_name); @@ -117,12 +122,12 @@ void Lua_Expedition::RemoveLockout(std::string 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); + 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); + self->SetDzCompass(zone_name, x, y, z, true); } void Lua_Expedition::SetLocked(bool lock_expedition) { @@ -137,17 +142,17 @@ void Lua_Expedition::SetReplayLockoutOnMemberJoin(bool enable) { 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); + 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); + 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); + self->SetDzZoneInLocation(x, y, z, heading, true); } luabind::scope lua_register_expedition() { @@ -167,7 +172,8 @@ luabind::scope lua_register_expedition() { .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("HasReplayLockout", (bool(Lua_Expedition::*)(void))&Lua_Expedition::HasReplayLockout) + .def("RemoveCompass", (void(Lua_Expedition::*)(void))&Lua_Expedition::RemoveCompass) .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) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 904d52dfe..3bc9893e6 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -65,6 +65,7 @@ public: luabind::object GetLockouts(lua_State* L); bool HasLockout(std::string event_name); bool HasReplayLockout(); + void RemoveCompass(); 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); From dcbcc5a1566e3f2ed65492e76cace0033ee0e114 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 25 May 2020 22:05:02 -0400 Subject: [PATCH 060/196] Implement world cache to monitor expeditions This implements a small cache in world to track expedition states. This fixes expired expeditions being left in zone caches unless the expedition's dz instance was running to detect it (or unless an expedition was deleted via a client using /kickplayers). This was also leaving clients in a ghost expedition that no longer actually existed --- common/ruletypes.h | 1 + common/servertalk.h | 1 + world/expedition.cpp | 241 ++++++++++++++++++++++++++++++++++++++++--- world/expedition.h | 51 ++++++++- world/main.cpp | 12 ++- world/zoneserver.cpp | 6 +- zone/expedition.cpp | 9 +- zone/expedition.h | 2 +- zone/worldserver.cpp | 1 + 9 files changed, 299 insertions(+), 25 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 834e8bb6d..34773f441 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -791,6 +791,7 @@ RULE_BOOL(Expedition, UseDatabaseToVerifyLeaderCommands, false, "Use database in 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_INT(Expedition, RequestExpiredLockoutLeewaySeconds, 60, "Seconds remaining on lockout to count as expired for creation requests (client hides timers under 60s remaining)") +RULE_INT(Expedition, WorldExpeditionProcessRateMS, 6000, "Timer interval (ms) that world checks expedition states") RULE_CATEGORY_END() RULE_CATEGORY(DynamicZone) diff --git a/common/servertalk.h b/common/servertalk.h index 8f1b64ac7..ae1ba691a 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -159,6 +159,7 @@ #define ServerOP_ExpeditionRequestInvite 0x040f #define ServerOP_ExpeditionReplayOnJoin 0x0410 #define ServerOP_ExpeditionLockState 0x0411 +#define ServerOP_ExpeditionExpired 0x0412 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 diff --git a/world/expedition.cpp b/world/expedition.cpp index fb52a227b..bb46c236c 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -27,10 +27,109 @@ #include "../common/servertalk.h" #include "../common/string_util.h" +ExpeditionCache expedition_cache; + extern ClientList client_list; extern ZSList zoneserver_list; -void Expedition::PurgeExpiredExpeditions() +Expedition::Expedition( + uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, + uint32_t start_time, uint32_t duration +) : + m_expedition_id(expedition_id), + m_dz_instance_id(instance_id), + m_dz_zone_id(dz_zone_id), + m_start_time(start_time), + m_duration(duration) +{ + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); +} + +void Expedition::SendZonesExpeditionExpired() +{ + uint32_t pack_size = sizeof(ServerExpeditionID_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionExpired, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + zoneserver_list.SendPacket(pack.get()); +} + +void ExpeditionCache::LoadActiveExpeditions() +{ + BenchTimer benchmark; + + m_expeditions = ExpeditionDatabase::LoadExpeditions(); + + auto elapsed = benchmark.elapsed(); + LogExpeditions("World caching [{}] expeditions took {}s", m_expeditions.size(), elapsed); +} + +void ExpeditionCache::AddExpedition(uint32_t expedition_id) +{ + if (expedition_id == 0) + { + return; + } + + auto expedition = ExpeditionDatabase::LoadExpedition(expedition_id); + + if (expedition.GetID() == expedition_id) + { + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it == m_expeditions.end()) + { + m_expeditions.emplace_back(expedition); + } + } +} + +void ExpeditionCache::RemoveExpedition(uint32_t expedition_id) +{ + m_expeditions.erase(std::remove_if(m_expeditions.begin(), m_expeditions.end(), + [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + } + ), m_expeditions.end()); +} + +void ExpeditionCache::Process() +{ + if (!m_process_throttle_timer.Check()) + { + return; + } + + std::vector expedition_ids; + + // check for expired expeditions (using the dz instance expiration time) + for (auto it = m_expeditions.begin(); it != m_expeditions.end();) + { + if (!it->IsExpired()) + { + ++it; + } + else + { + // we need to delete expired expeditions from the database now instead + // of waiting for purge timer so members can request new expeditions. + // the dz should process this on its own to kick any clients inside it + LogExpeditions("Expedition [{}] expired, notifying zones and deleting", it->GetID()); + expedition_ids.emplace_back(it->GetID()); + it->SendZonesExpeditionExpired(); + it = m_expeditions.erase(it); + } + } + + if (!expedition_ids.empty()) + { + ExpeditionDatabase::DeleteExpeditions(expedition_ids); + } +} + +void ExpeditionDatabase::PurgeExpiredExpeditions() { std::string query = SQL( DELETE expedition FROM expedition_details expedition @@ -54,7 +153,7 @@ void Expedition::PurgeExpiredExpeditions() } } -void Expedition::PurgeExpiredCharacterLockouts() +void ExpeditionDatabase::PurgeExpiredCharacterLockouts() { std::string query = SQL( DELETE FROM expedition_character_lockouts @@ -68,23 +167,139 @@ void Expedition::PurgeExpiredCharacterLockouts() } } -void Expedition::HandleZoneMessage(ServerPacket* pack) +std::vector ExpeditionDatabase::LoadExpeditions() +{ + std::vector expeditions; + + std::string query = SQL( + SELECT + expedition_details.id, + expedition_details.instance_id, + instance_list.zone, + instance_list.start_time, + instance_list.duration + FROM expedition_details + INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id + ORDER BY expedition_details.id; + ); + + auto results = database.QueryDatabase(query); + if (!results.Success()) + { + LogExpeditions("Failed to load expeditions for world cache"); + } + else + { + for (auto row = results.begin(); row != results.end(); ++row) + { + expeditions.emplace_back(Expedition{ + static_cast(strtoul(row[0], nullptr, 10)), // expedition_id + static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[3], nullptr, 10)), // start_time + static_cast(strtoul(row[4], nullptr, 10)) // duration + }); + } + } + + return expeditions; +} + +Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) +{ + std::string query = fmt::format(SQL( + SELECT + expedition_details.id, + expedition_details.instance_id, + instance_list.zone, + instance_list.start_time, + instance_list.duration + FROM expedition_details + INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id + WHERE expedition_details.id = {}; + ), expedition_id); + + auto results = database.QueryDatabase(query); + if (!results.Success()) + { + LogExpeditions("Failed to load expedition [{}] for world cache", expedition_id); + } + else if (results.RowCount() > 0) + { + auto row = results.begin(); + return Expedition{ + static_cast(strtoul(row[0], nullptr, 10)), // expedition_id + static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[3], nullptr, 10)), // start_time + static_cast(strtoul(row[4], nullptr, 10)) // duration + }; + } + + return Expedition{}; +} + +void ExpeditionDatabase::DeleteExpeditions(const std::vector& expedition_ids) +{ + std::string expedition_ids_query; + for (const auto& expedition_id : expedition_ids) + { + fmt::format_to(std::back_inserter(expedition_ids_query), "{},", expedition_id); + } + + if (!expedition_ids_query.empty()) + { + expedition_ids_query.pop_back(); // trailing comma + + std::string query = fmt::format( + "DELETE FROM expedition_details WHERE id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); + + // todo: if not using foreign key constraints + //query = fmt::format( + // "DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query + //); + //database.QueryDatabase(query); + + //query = fmt::format( + // "DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query + //); + //database.QueryDatabase(query); + } +} + +void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) { switch (pack->opcode) { + case ServerOP_ExpeditionCreate: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.AddExpedition(buf->expedition_id); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionDeleted: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.RemoveExpedition(buf->expedition_id); + zoneserver_list.SendPacket(pack); + break; + } case ServerOP_ExpeditionGetOnlineMembers: { - Expedition::GetOnlineMembers(pack); + ExpeditionMessage::GetOnlineMembers(pack); break; } case ServerOP_ExpeditionDzAddPlayer: { - Expedition::AddPlayer(pack); + ExpeditionMessage::AddPlayer(pack); break; } case ServerOP_ExpeditionDzMakeLeader: { - Expedition::MakeLeader(pack); + ExpeditionMessage::MakeLeader(pack); break; } case ServerOP_ExpeditionRemoveCharLockouts: @@ -95,18 +310,18 @@ void Expedition::HandleZoneMessage(ServerPacket* pack) } case ServerOP_ExpeditionSaveInvite: { - Expedition::SaveInvite(pack); + ExpeditionMessage::SaveInvite(pack); break; } case ServerOP_ExpeditionRequestInvite: { - Expedition::RequestInvite(pack); + ExpeditionMessage::RequestInvite(pack); break; } } } -void Expedition::AddPlayer(ServerPacket* pack) +void ExpeditionMessage::AddPlayer(ServerPacket* pack) { auto buf = reinterpret_cast(pack->pBuffer); @@ -128,7 +343,7 @@ void Expedition::AddPlayer(ServerPacket* pack) } } -void Expedition::MakeLeader(ServerPacket* pack) +void ExpeditionMessage::MakeLeader(ServerPacket* pack) { auto buf = reinterpret_cast(pack->pBuffer); @@ -150,7 +365,7 @@ void Expedition::MakeLeader(ServerPacket* pack) } } -void Expedition::GetOnlineMembers(ServerPacket* pack) +void ExpeditionMessage::GetOnlineMembers(ServerPacket* pack) { auto buf = reinterpret_cast(pack->pBuffer); @@ -177,7 +392,7 @@ void Expedition::GetOnlineMembers(ServerPacket* pack) zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); } -void Expedition::SaveInvite(ServerPacket* pack) +void ExpeditionMessage::SaveInvite(ServerPacket* pack) { auto buf = reinterpret_cast(pack->pBuffer); @@ -191,7 +406,7 @@ void Expedition::SaveInvite(ServerPacket* pack) } } -void Expedition::RequestInvite(ServerPacket* pack) +void ExpeditionMessage::RequestInvite(ServerPacket* pack) { auto buf = reinterpret_cast(pack->pBuffer); ClientListEntry* cle = client_list.FindCLEByCharacterID(buf->character_id); diff --git a/world/expedition.h b/world/expedition.h index f40cda834..bfa159647 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -21,12 +21,27 @@ #ifndef WORLD_EXPEDITION_H #define WORLD_EXPEDITION_H +#include "../common/rulesys.h" +#include "../common/timer.h" +#include +#include + +extern class ExpeditionCache expedition_cache; + +class Expedition; class ServerPacket; -namespace Expedition +namespace ExpeditionDatabase { void PurgeExpiredExpeditions(); void PurgeExpiredCharacterLockouts(); + std::vector LoadExpeditions(); + Expedition LoadExpedition(uint32_t expedition_id); + void DeleteExpeditions(const std::vector& expedition_ids); +}; + +namespace ExpeditionMessage +{ void HandleZoneMessage(ServerPacket* pack); void AddPlayer(ServerPacket* pack); void MakeLeader(ServerPacket* pack); @@ -35,4 +50,38 @@ namespace Expedition void RequestInvite(ServerPacket* pack); }; +class ExpeditionCache +{ +public: + void AddExpedition(uint32_t expedition_id); + void RemoveExpedition(uint32_t expedition_id); + void LoadActiveExpeditions(); + void Process(); + +private: + std::vector m_expeditions; + Timer m_process_throttle_timer{static_cast(RuleI(Expedition, WorldExpeditionProcessRateMS))}; +}; + +class Expedition +{ +public: + Expedition() = default; + Expedition( + uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, + uint32_t expire_time, uint32_t duration); + + uint32_t GetID() const { return m_expedition_id; } + bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } + void SendZonesExpeditionExpired(); + +private: + uint32_t m_expedition_id = 0; + uint32_t m_dz_instance_id = 0; + uint32_t m_dz_zone_id = 0; + uint32_t m_start_time = 0; + uint32_t m_duration = 0; + std::chrono::time_point m_expire_time; +}; + #endif diff --git a/world/main.cpp b/world/main.cpp index 0f0f6fa65..5df964004 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -431,8 +431,11 @@ int main(int argc, char** argv) { PurgeInstanceTimer.Start(450000); LogInfo("Purging expired expeditions"); - Expedition::PurgeExpiredExpeditions(); - Expedition::PurgeExpiredCharacterLockouts(); + ExpeditionDatabase::PurgeExpiredExpeditions(); + ExpeditionDatabase::PurgeExpiredCharacterLockouts(); + + LogInfo("Loading active expeditions"); + expedition_cache.LoadActiveExpeditions(); LogInfo("Loading char create info"); content_db.LoadCharacterCreateAllocations(); @@ -604,8 +607,8 @@ int main(int argc, char** argv) { if (PurgeInstanceTimer.Check()) { database.PurgeExpiredInstances(); database.PurgeAllDeletedDataBuckets(); - Expedition::PurgeExpiredExpeditions(); - Expedition::PurgeExpiredCharacterLockouts(); + ExpeditionDatabase::PurgeExpiredExpeditions(); + ExpeditionDatabase::PurgeExpiredCharacterLockouts(); } if (EQTimeTimer.Check()) { @@ -621,6 +624,7 @@ int main(int argc, char** argv) { launcher_list.Process(); LFPGroupList.Process(); adventure_manager.Process(); + expedition_cache.Process(); if (InterserverTimer.Check()) { InterserverTimer.Start(); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index c0f9f7ed6..93500117c 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1368,8 +1368,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { client_list.SendPacket(buf->character_name, pack); break; } - case ServerOP_ExpeditionCreate: - case ServerOP_ExpeditionDeleted: case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: case ServerOP_ExpeditionLockState: @@ -1384,6 +1382,8 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { zoneserver_list.SendPacket(pack); break; } + case ServerOP_ExpeditionCreate: + case ServerOP_ExpeditionDeleted: case ServerOP_ExpeditionGetOnlineMembers: case ServerOP_ExpeditionDzAddPlayer: case ServerOP_ExpeditionDzMakeLeader: @@ -1391,7 +1391,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ExpeditionSaveInvite: case ServerOP_ExpeditionRequestInvite: { - Expedition::HandleZoneMessage(pack); + ExpeditionMessage::HandleZoneMessage(pack); break; } case ServerOP_DzCharacterChange: diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 1b1513ea9..b58210ab9 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1272,7 +1272,7 @@ void Expedition::ProcessLockoutUpdate( } } -void Expedition::SendUpdatesToZoneMembers(bool clear) +void Expedition::SendUpdatesToZoneMembers(bool clear, bool message_on_clear) { if (!m_members.empty()) { @@ -1289,7 +1289,7 @@ void Expedition::SendUpdatesToZoneMembers(bool clear) member_client->QueuePacket(outapp_info.get()); member_client->QueuePacket(outapp_members.get()); member_client->SendExpeditionLockoutTimers(); - if (clear) + if (clear && message_on_clear) { member_client->MessageString( Chat::Yellow, EXPEDITION_REMOVED, member_client->GetName(), m_expedition_name.c_str() @@ -1605,6 +1605,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) break; } case ServerOP_ExpeditionDeleted: + case ServerOP_ExpeditionExpired: { auto buf = reinterpret_cast(pack->pBuffer); auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); @@ -1612,7 +1613,9 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) { if (!zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) { - expedition->SendUpdatesToZoneMembers(true); + // expired deletions should be silent + bool notify_members = (pack->opcode == ServerOP_ExpeditionDeleted); + expedition->SendUpdatesToZoneMembers(true, notify_members); } // remove even from sender zone diff --git a/zone/expedition.h b/zone/expedition.h index d6b30e376..1232d76ba 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -154,7 +154,7 @@ private: 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& parameters = {}); - void SendUpdatesToZoneMembers(bool clear = false); + void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); void SendWorldExpeditionUpdate(bool destroyed = false); void SendWorldGetOnlineMembers(); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index d903db214..3d46733f1 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2901,6 +2901,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } case ServerOP_ExpeditionCreate: case ServerOP_ExpeditionDeleted: + case ServerOP_ExpeditionExpired: case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: case ServerOP_ExpeditionLockState: From 148af3edfcf2dba041633aeca78143afe61d0d8c Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 26 May 2020 22:32:08 -0400 Subject: [PATCH 061/196] Only delete empty expeditions when the dz is empty Zones are no longer able to delete expeditions. World now tracks empty expeditions in cache and only deletes them when it detects an expedition's dynamic zone instance has no more clients inside. This fixes an exploit where lockouts couldn't be applied to expeditions after all members were removed because zones were deleting the expedition immediately. Clients still inside the dz were able to complete events before being kicked from the instance while not having an expedition. Expeditions are no longer purged from database in the world purge instance timer to avoid a possible race with this new system --- common/servertalk.h | 2 +- world/expedition.cpp | 152 ++++++++++++++++++++++++++--------- world/expedition.h | 12 ++- world/main.cpp | 1 - world/zoneserver.cpp | 6 +- zone/expedition.cpp | 49 +++++------ zone/expedition.h | 2 +- zone/expedition_database.cpp | 39 +++------ zone/expedition_database.h | 3 +- zone/worldserver.cpp | 2 +- 10 files changed, 167 insertions(+), 101 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index ae1ba691a..d56491299 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -159,7 +159,7 @@ #define ServerOP_ExpeditionRequestInvite 0x040f #define ServerOP_ExpeditionReplayOnJoin 0x0410 #define ServerOP_ExpeditionLockState 0x0411 -#define ServerOP_ExpeditionExpired 0x0412 +#define ServerOP_ExpeditionMembersRemoved 0x0412 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 diff --git a/world/expedition.cpp b/world/expedition.cpp index bb46c236c..a3e6e7207 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -45,10 +45,10 @@ Expedition::Expedition( m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); } -void Expedition::SendZonesExpeditionExpired() +void Expedition::SendZonesExpeditionDeleted() { uint32_t pack_size = sizeof(ServerExpeditionID_Struct); - auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionExpired, pack_size)); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDeleted, pack_size)); auto buf = reinterpret_cast(pack->pBuffer); buf->expedition_id = GetID(); zoneserver_list.SendPacket(pack.get()); @@ -95,6 +95,34 @@ void ExpeditionCache::RemoveExpedition(uint32_t expedition_id) ), m_expeditions.end()); } +void ExpeditionCache::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + if (remove) { + it->RemoveMember(character_id); + } else { + it->AddMember(character_id); + } + } +} + +void ExpeditionCache::RemoveAllMembers(uint32_t expedition_id) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + it->RemoveAllMembers(); + } +} + void ExpeditionCache::Process() { if (!m_process_throttle_timer.Check()) @@ -104,23 +132,28 @@ void ExpeditionCache::Process() std::vector expedition_ids; - // check for expired expeditions (using the dz instance expiration time) + // check cache for expired or empty expeditions to delete and notify zones. for (auto it = m_expeditions.begin(); it != m_expeditions.end();) { - if (!it->IsExpired()) + bool is_deleted = false; + + if (it->IsEmpty() || it->IsExpired()) { - ++it; - } - else - { - // we need to delete expired expeditions from the database now instead - // of waiting for purge timer so members can request new expeditions. - // the dz should process this on its own to kick any clients inside it - LogExpeditions("Expedition [{}] expired, notifying zones and deleting", it->GetID()); - expedition_ids.emplace_back(it->GetID()); - it->SendZonesExpeditionExpired(); - it = m_expeditions.erase(it); + // don't delete expedition until its dz instance is empty. this prevents + // an exploit where all members leave expedition and complete an event + // before being kicked from removal timer. the lockout could never be + // applied because the zone expedition cache was already invalidated. + auto dz_zoneserver = zoneserver_list.FindByInstanceID(it->GetInstanceID()); + if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) + { + LogExpeditions("Expedition [{}] expired or empty, notifying zones and deleting", it->GetID()); + expedition_ids.emplace_back(it->GetID()); + it->SendZonesExpeditionDeleted(); + is_deleted = true; + } } + + it = is_deleted ? m_expeditions.erase(it) : it + 1; } if (!expedition_ids.empty()) @@ -177,9 +210,13 @@ std::vector ExpeditionDatabase::LoadExpeditions() expedition_details.instance_id, instance_list.zone, instance_list.start_time, - instance_list.duration + instance_list.duration, + expedition_members.character_id FROM expedition_details INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id + INNER JOIN expedition_members + ON expedition_members.expedition_id = expedition_details.id + AND expedition_members.is_current_member = TRUE ORDER BY expedition_details.id; ); @@ -190,15 +227,27 @@ std::vector ExpeditionDatabase::LoadExpeditions() } else { + uint32_t last_expedition_id = 0; + for (auto row = results.begin(); row != results.end(); ++row) { - expeditions.emplace_back(Expedition{ - static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[3], nullptr, 10)), // start_time - static_cast(strtoul(row[4], nullptr, 10)) // duration - }); + uint32_t expedition_id = strtoul(row[0], nullptr, 10); + + if (last_expedition_id != expedition_id) + { + expeditions.emplace_back(Expedition{ + static_cast(strtoul(row[0], nullptr, 10)), // expedition_id + static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[3], nullptr, 10)), // start_time + static_cast(strtoul(row[4], nullptr, 10)) // duration + }); + } + + last_expedition_id = expedition_id; + + uint32_t member_id = static_cast(strtoul(row[5], nullptr, 10)); + expeditions.back().AddMember(member_id); } } @@ -207,15 +256,21 @@ std::vector ExpeditionDatabase::LoadExpeditions() Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) { + Expedition expedition; + std::string query = fmt::format(SQL( SELECT expedition_details.id, expedition_details.instance_id, instance_list.zone, instance_list.start_time, - instance_list.duration + instance_list.duration, + expedition_members.character_id FROM expedition_details INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id + INNER JOIN expedition_members + ON expedition_members.expedition_id = expedition_details.id + AND expedition_members.is_current_member = TRUE WHERE expedition_details.id = {}; ), expedition_id); @@ -224,19 +279,29 @@ Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) { LogExpeditions("Failed to load expedition [{}] for world cache", expedition_id); } - else if (results.RowCount() > 0) + else { - auto row = results.begin(); - return Expedition{ - static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[3], nullptr, 10)), // start_time - static_cast(strtoul(row[4], nullptr, 10)) // duration - }; + bool created = false; + for (auto row = results.begin(); row != results.end(); ++row) + { + if (!created) + { + expedition = Expedition{ + static_cast(strtoul(row[0], nullptr, 10)), // expedition_id + static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[3], nullptr, 10)), // start_time + static_cast(strtoul(row[4], nullptr, 10)) // duration + }; + created = true; + } + + auto member_id = static_cast(strtoul(row[5], nullptr, 10)); + expedition.AddMember(member_id); + } } - return Expedition{}; + return expedition; } void ExpeditionDatabase::DeleteExpeditions(const std::vector& expedition_ids) @@ -280,10 +345,25 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) zoneserver_list.SendPacket(pack); break; } - case ServerOP_ExpeditionDeleted: + case ServerOP_ExpeditionMemberChange: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.MemberChange(buf->expedition_id, buf->char_id, buf->removed); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionMemberSwap: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.MemberChange(buf->expedition_id, buf->remove_char_id, true); + expedition_cache.MemberChange(buf->expedition_id, buf->add_char_id, false); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionMembersRemoved: { auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.RemoveExpedition(buf->expedition_id); + expedition_cache.RemoveAllMembers(buf->expedition_id); zoneserver_list.SendPacket(pack); break; } diff --git a/world/expedition.h b/world/expedition.h index bfa159647..6c6c78a3a 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -24,6 +24,7 @@ #include "../common/rulesys.h" #include "../common/timer.h" #include +#include #include extern class ExpeditionCache expedition_cache; @@ -56,6 +57,8 @@ public: void AddExpedition(uint32_t expedition_id); void RemoveExpedition(uint32_t expedition_id); void LoadActiveExpeditions(); + void MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove); + void RemoveAllMembers(uint32_t expedition_id); void Process(); private: @@ -71,9 +74,15 @@ public: uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, uint32_t expire_time, uint32_t duration); + void AddMember(uint32_t character_id) { m_member_ids.emplace(character_id); } + void RemoveMember(uint32_t character_id) { m_member_ids.erase(character_id); } + void RemoveAllMembers() { m_member_ids.clear(); } uint32_t GetID() const { return m_expedition_id; } + uint16_t GetInstanceID() const { return static_cast(m_dz_instance_id); } + uint16_t GetZoneID() const { return static_cast(m_dz_zone_id); } + bool IsEmpty() const { return m_member_ids.empty(); } bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } - void SendZonesExpeditionExpired(); + void SendZonesExpeditionDeleted(); private: uint32_t m_expedition_id = 0; @@ -81,6 +90,7 @@ private: uint32_t m_dz_zone_id = 0; uint32_t m_start_time = 0; uint32_t m_duration = 0; + std::unordered_set m_member_ids; std::chrono::time_point m_expire_time; }; diff --git a/world/main.cpp b/world/main.cpp index 5df964004..a3ca55e48 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -607,7 +607,6 @@ int main(int argc, char** argv) { if (PurgeInstanceTimer.Check()) { database.PurgeExpiredInstances(); database.PurgeAllDeletedDataBuckets(); - ExpeditionDatabase::PurgeExpiredExpeditions(); ExpeditionDatabase::PurgeExpiredCharacterLockouts(); } diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 93500117c..71af4a1d1 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1371,8 +1371,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: case ServerOP_ExpeditionLockState: - case ServerOP_ExpeditionMemberChange: - case ServerOP_ExpeditionMemberSwap: case ServerOP_ExpeditionMemberStatus: case ServerOP_ExpeditionReplayOnJoin: case ServerOP_ExpeditionDzCompass: @@ -1383,8 +1381,10 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } case ServerOP_ExpeditionCreate: - case ServerOP_ExpeditionDeleted: case ServerOP_ExpeditionGetOnlineMembers: + case ServerOP_ExpeditionMemberChange: + case ServerOP_ExpeditionMemberSwap: + case ServerOP_ExpeditionMembersRemoved: case ServerOP_ExpeditionDzAddPlayer: case ServerOP_ExpeditionDzMakeLeader: case ServerOP_ExpeditionRemoveCharLockouts: diff --git a/zone/expedition.cpp b/zone/expedition.cpp index b58210ab9..6ecd960f9 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -132,7 +132,7 @@ Expedition* Expedition::TryCreate( auto inserted = zone->expedition_cache.emplace(expedition_id, std::move(expedition)); inserted.first->second->SendUpdatesToZoneMembers(); - inserted.first->second->SendWorldExpeditionUpdate(); // cache in other zones + inserted.first->second->SendWorldExpeditionUpdate(ServerOP_ExpeditionCreate); // cache in other zones Client* leader_client = request.GetLeaderClient(); @@ -486,11 +486,10 @@ void Expedition::RemoveAllMembers(bool enable_removal_timers, bool update_dz_exp m_dynamiczone.UpdateExpireTime(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); } - ExpeditionDatabase::DeleteAllMembers(m_id); - ExpeditionDatabase::DeleteExpedition(m_id); + ExpeditionDatabase::UpdateAllMembersRemoved(m_id); SendUpdatesToZoneMembers(true); - SendWorldExpeditionUpdate(true); + SendWorldExpeditionUpdate(ServerOP_ExpeditionMembersRemoved); } bool Expedition::RemoveMember(const std::string& remove_char_name) @@ -519,8 +518,6 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) uint32_t member_count = ExpeditionDatabase::GetExpeditionMemberCount(m_id); if (member_count == 0) { - // zone cache removal will occur in world message handler - ExpeditionDatabase::DeleteExpedition(m_id); if (RuleB(Expedition, EmptyDzShutdownEnabled)) { m_dynamiczone.UpdateExpireTime(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); @@ -1412,11 +1409,10 @@ std::unique_ptr Expedition::CreateLeaderNamePacket() return outapp; } -void Expedition::SendWorldExpeditionUpdate(bool destroyed) +void Expedition::SendWorldExpeditionUpdate(uint16_t server_opcode) { - uint16_t opcode = destroyed ? ServerOP_ExpeditionDeleted : ServerOP_ExpeditionCreate; uint32_t pack_size = sizeof(ServerExpeditionID_Struct); - auto pack = std::unique_ptr(new ServerPacket(opcode, pack_size)); + auto pack = std::unique_ptr(new ServerPacket(server_opcode, pack_size)); auto buf = reinterpret_cast(pack->pBuffer); buf->expedition_id = GetID(); buf->sender_zone_id = zone ? zone->GetZoneID() : 0; @@ -1605,24 +1601,32 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) break; } case ServerOP_ExpeditionDeleted: - case ServerOP_ExpeditionExpired: { + // sent by world when it deletes expired or empty expeditions auto buf = reinterpret_cast(pack->pBuffer); auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (zone && expedition) { - if (!zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) - { - // expired deletions should be silent - bool notify_members = (pack->opcode == ServerOP_ExpeditionDeleted); - expedition->SendUpdatesToZoneMembers(true, notify_members); - } + expedition->SendUpdatesToZoneMembers(true, false); // any members silently removed - // remove even from sender zone + LogExpeditionsModerate("Deleting expedition [{}] from zone cache", buf->expedition_id); zone->expedition_cache.erase(buf->expedition_id); } break; } + case ServerOP_ExpeditionMembersRemoved: + { + 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 (expedition) + { + expedition->SendUpdatesToZoneMembers(true); + } + } + break; + } case ServerOP_ExpeditionLeaderChanged: { auto buf = reinterpret_cast(pack->pBuffer); @@ -1656,11 +1660,6 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition && zone) { - LogExpeditionsDetail( - "World member change message -- remove: [{}] name: [{}] zone: [{}]:[{}] sender: [{}]:[{}]", - buf->removed, buf->char_name, zone->GetZoneID(), zone->GetInstanceID(), buf->sender_zone_id, buf->sender_instance_id - ); - if (!zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) { if (buf->removed) @@ -1672,12 +1671,6 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) expedition->ProcessMemberAdded(buf->char_name, buf->char_id); } } - - // remove this expedition from zone cache if last member was removed - if (buf->removed && expedition->GetMemberCount() == 0) - { - zone->expedition_cache.erase(buf->expedition_id); - } } break; } diff --git a/zone/expedition.h b/zone/expedition.h index 1232d76ba..dfe337907 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -156,7 +156,7 @@ private: void SendLeaderMessage(Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& parameters = {}); void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); - void SendWorldExpeditionUpdate(bool destroyed = false); + void SendWorldExpeditionUpdate(uint16_t server_opcode); void SendWorldGetOnlineMembers(); void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); void SendWorldLeaderChanged(); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 8dc5bb294..310613878 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -320,18 +320,6 @@ void ExpeditionDatabase::DeletePendingLockouts(uint32_t 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); @@ -348,21 +336,6 @@ void ExpeditionDatabase::DeleteLockout(uint32_t expedition_id, const std::string } } -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); @@ -675,6 +648,18 @@ void ExpeditionDatabase::UpdateMemberRemoved(uint32_t expedition_id, uint32_t ch } } +void ExpeditionDatabase::UpdateAllMembersRemoved(uint32_t expedition_id) +{ + LogExpeditionsDetail("Updating all members of expedition [{}] as removed", expedition_id); + + auto query = fmt::format(SQL( + UPDATE expedition_members SET is_current_member = FALSE + WHERE expedition_id = {}; + ), expedition_id); + + database.QueryDatabase(query); +} + void ExpeditionDatabase::UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join) { LogExpeditionsDetail("Updating replay lockout on join [{}] for expedition [{}]", add_on_join, expedition_id); diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 2fe68efa9..7c5692794 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -44,11 +44,9 @@ namespace ExpeditionDatabase 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 DeleteAllCharacterLockouts(uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id, const std::string& expedition_name); 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& members, const std::string& expedition_name, const std::string& event_name); @@ -69,6 +67,7 @@ namespace ExpeditionDatabase void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); void UpdateLockState(uint32_t expedition_id, bool is_locked); void UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id); + void UpdateAllMembersRemoved(uint32_t expedition_id); void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); }; diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 3d46733f1..ee1e0589a 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2901,13 +2901,13 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } case ServerOP_ExpeditionCreate: case ServerOP_ExpeditionDeleted: - case ServerOP_ExpeditionExpired: case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: case ServerOP_ExpeditionLockState: case ServerOP_ExpeditionMemberChange: case ServerOP_ExpeditionMemberSwap: case ServerOP_ExpeditionMemberStatus: + case ServerOP_ExpeditionMembersRemoved: case ServerOP_ExpeditionReplayOnJoin: case ServerOP_ExpeditionGetOnlineMembers: case ServerOP_ExpeditionDzAddPlayer: From 9164073d143779b8ed8335227b1ddc3975dbbc05 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 27 May 2020 23:26:47 -0400 Subject: [PATCH 062/196] Let world shutdown dz early for empty expeditions Since world now tracks empty expeditions it can determine when to shutdown dynamic zone instances when the rule is enabled rather than letting zones do it. --- common/servertalk.h | 6 ++++ world/expedition.cpp | 53 ++++++++++++++++++++++++++++++++++++ world/expedition.h | 6 ++++ zone/dynamiczone.cpp | 38 ++++++++------------------ zone/dynamiczone.h | 2 +- zone/expedition.cpp | 29 ++++++++------------ zone/expedition.h | 3 +- zone/expedition_database.cpp | 19 ------------- zone/expedition_database.h | 1 - zone/worldserver.cpp | 1 + zone/zone.cpp | 2 +- 11 files changed, 93 insertions(+), 67 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index d56491299..d537bb825 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -160,6 +160,7 @@ #define ServerOP_ExpeditionReplayOnJoin 0x0410 #define ServerOP_ExpeditionLockState 0x0411 #define ServerOP_ExpeditionMembersRemoved 0x0412 +#define ServerOP_ExpeditionDzDuration 0x0413 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 @@ -2073,6 +2074,11 @@ struct ServerExpeditionCharacterID_Struct { uint32_t character_id; }; +struct ServerExpeditionUpdateDuration_Struct { + uint32_t expedition_id; + uint32_t new_duration_seconds; +}; + struct ServerDzCommand_Struct { uint32 expedition_id; uint8 is_char_online; // 0: target name is offline, 1: online diff --git a/world/expedition.cpp b/world/expedition.cpp index a3e6e7207..ff2476e50 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -54,6 +54,42 @@ void Expedition::SendZonesExpeditionDeleted() zoneserver_list.SendPacket(pack.get()); } +void Expedition::SendZonesDurationUpdate() +{ + uint32_t packsize = sizeof(ServerExpeditionUpdateDuration_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDzDuration, packsize)); + auto packbuf = reinterpret_cast(pack->pBuffer); + packbuf->expedition_id = GetID(); + packbuf->new_duration_seconds = m_duration; + zoneserver_list.SendPacket(pack.get()); +} + +void Expedition::UpdateDzSecondsRemaining(uint32_t seconds_remaining) +{ + auto now = std::chrono::system_clock::now(); + auto update_time = std::chrono::seconds(seconds_remaining); + + auto current_remaining = m_expire_time - now; + if (current_remaining > update_time) // reduce only + { + LogExpeditionsDetail( + "Updating expedition [{}] dz instance [{}] seconds remaining to [{}]s", + GetID(), GetInstanceID(), seconds_remaining + ); + + // preserve original start time and adjust duration instead + auto new_expire_time = now + update_time; + auto new_duration = std::chrono::system_clock::to_time_t(new_expire_time) - m_start_time; + m_duration = static_cast(new_duration); + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + + ExpeditionDatabase::UpdateDzDuration(GetInstanceID(), m_duration); + + // update zone level caches and update the actual dz instance's timer + SendZonesDurationUpdate(); + } +} + void ExpeditionCache::LoadActiveExpeditions() { BenchTimer benchmark; @@ -151,6 +187,13 @@ void ExpeditionCache::Process() it->SendZonesExpeditionDeleted(); is_deleted = true; } + + if (it->IsEmpty() && !it->IsPendingDelete() && RuleB(Expedition, EmptyDzShutdownEnabled)) + { + it->UpdateDzSecondsRemaining(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); + } + + it->SetPendingDelete(true); } it = is_deleted ? m_expeditions.erase(it) : it + 1; @@ -334,6 +377,16 @@ void ExpeditionDatabase::DeleteExpeditions(const std::vector& expediti } } +void ExpeditionDatabase::UpdateDzDuration(uint16_t instance_id, uint32_t new_duration) +{ + std::string query = fmt::format( + "UPDATE instance_list SET duration = {} WHERE id = {};", + new_duration, instance_id + ); + + database.QueryDatabase(query); +} + void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) { switch (pack->opcode) diff --git a/world/expedition.h b/world/expedition.h index 6c6c78a3a..a933e5d9d 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -39,6 +39,7 @@ namespace ExpeditionDatabase std::vector LoadExpeditions(); Expedition LoadExpedition(uint32_t expedition_id); void DeleteExpeditions(const std::vector& expedition_ids); + void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); }; namespace ExpeditionMessage @@ -82,7 +83,11 @@ public: uint16_t GetZoneID() const { return static_cast(m_dz_zone_id); } bool IsEmpty() const { return m_member_ids.empty(); } bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } + bool IsPendingDelete() const { return m_pending_delete; } + void SendZonesDurationUpdate(); void SendZonesExpeditionDeleted(); + void SetPendingDelete(bool pending) { m_pending_delete = pending; } + void UpdateDzSecondsRemaining(uint32_t seconds_remaining); private: uint32_t m_expedition_id = 0; @@ -90,6 +95,7 @@ private: uint32_t m_dz_zone_id = 0; uint32_t m_start_time = 0; uint32_t m_duration = 0; + bool m_pending_delete = false; std::unordered_set m_member_ids; std::chrono::time_point m_expire_time; }; diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 38377fefe..157b487e3 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -437,32 +437,6 @@ void DynamicZone::SendInstanceCharacterChange(uint32_t character_id, bool remove } } -void DynamicZone::UpdateExpireTime(uint32_t seconds, bool reduce_only) -{ - if (GetInstanceID() == 0 || (reduce_only && GetSecondsRemaining() < seconds)) - { - return; - } - - m_expire_time = std::chrono::system_clock::now() + std::chrono::seconds(seconds); - m_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 = {}; - ), m_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; @@ -515,6 +489,18 @@ uint32_t DynamicZone::GetSecondsRemaining() const return 0; } +void DynamicZone::SetUpdatedDuration(uint32_t new_duration) +{ + // preserves original start time, just modifies duration and expire time + m_duration = new_duration; + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + + if (zone && IsCurrentZoneDzInstance()) + { + zone->SetInstanceTimer(GetSecondsRemaining()); + } +} + void DynamicZone::HandleWorldMessage(ServerPacket* pack) { switch (pack->opcode) diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index bb2bee16b..f9a64276b 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -89,7 +89,7 @@ public: 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, bool reduce_only = true); + void SetUpdatedDuration(uint32_t seconds); void LoadFromDatabase(uint32_t instance_id); uint32_t SaveToDatabase(); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 6ecd960f9..6c388fe7c 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -477,15 +477,10 @@ bool Expedition::AddMember(const std::string& add_char_name, uint32_t add_char_i return true; } -void Expedition::RemoveAllMembers(bool enable_removal_timers, bool update_dz_expire_time) +void Expedition::RemoveAllMembers(bool enable_removal_timers) { m_dynamiczone.RemoveAllCharacters(enable_removal_timers); - if (update_dz_expire_time && RuleB(Expedition, EmptyDzShutdownEnabled)) - { - m_dynamiczone.UpdateExpireTime(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); - } - ExpeditionDatabase::UpdateAllMembersRemoved(m_id); SendUpdatesToZoneMembers(true); @@ -512,18 +507,6 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) ChooseNewLeader(); } - // we can't check for empty member count via cache because if other zones - // remove members at the same time then we race. cache member count won't - // be accurate until the world messages from other zones are processed - uint32_t member_count = ExpeditionDatabase::GetExpeditionMemberCount(m_id); - if (member_count == 0) - { - if (RuleB(Expedition, EmptyDzShutdownEnabled)) - { - m_dynamiczone.UpdateExpireTime(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); - } - } - return true; } @@ -1813,6 +1796,16 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_ExpeditionDzDuration: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition) + { + expedition->SetDzDuration(buf->new_duration_seconds); + } + break; + } } } diff --git a/zone/expedition.h b/zone/expedition.h index dfe337907..4ad4460f5 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -102,7 +102,7 @@ public: bool AddMember(const std::string& add_char_name, uint32_t add_char_id); bool HasMember(const std::string& character_name); bool HasMember(uint32_t character_id); - void RemoveAllMembers(bool enable_removal_timers = true, bool update_dz_expire_time = true); + void RemoveAllMembers(bool enable_removal_timers = 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); @@ -134,6 +134,7 @@ public: 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); + void SetDzDuration(uint32_t new_duration) { m_dynamiczone.SetUpdatedDuration(new_duration); } static const uint32_t REPLAY_TIMER_ID; static const uint32_t EVENT_TIMER_ID; diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 310613878..fb913a9b2 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -395,25 +395,6 @@ ExpeditionMember ExpeditionDatabase::GetExpeditionLeader(uint32_t expedition_id) return leader; } -uint32_t ExpeditionDatabase::GetExpeditionMemberCount(uint32_t expedition_id) -{ - auto query = fmt::format(SQL( - SELECT COUNT(IF(is_current_member = TRUE, 1, NULL)) member_count - FROM expedition_members - WHERE expedition_id = {}; - ), expedition_id); - - auto results = database.QueryDatabase(query); - - uint32_t member_count = 0; - if (results.Success() && results.RowCount() > 0) - { - auto row = results.begin(); - member_count = static_cast(strtoul(row[0], nullptr, 10)); - } - return member_count; -} - void ExpeditionDatabase::InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, bool update_expire_times, bool is_pending) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 7c5692794..1773d2550 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -55,7 +55,6 @@ namespace ExpeditionDatabase uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); uint32_t GetExpeditionIDFromInstanceID(uint32_t instance_id); ExpeditionMember GetExpeditionLeader(uint32_t expedition_id); - uint32_t GetExpeditionMemberCount(uint32_t expedition_id); void InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, bool update_expire_times, bool is_pending = false); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index ee1e0589a..6fb577515 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2915,6 +2915,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionDzCompass: case ServerOP_ExpeditionDzSafeReturn: case ServerOP_ExpeditionDzZoneIn: + case ServerOP_ExpeditionDzDuration: case ServerOP_ExpeditionRemoveCharLockouts: { Expedition::HandleWorldMessage(pack); diff --git a/zone/zone.cpp b/zone/zone.cpp index a415e45f0..da2555785 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1495,7 +1495,7 @@ bool Zone::Process() { Expedition* expedition = Expedition::FindExpeditionByInstanceID(GetInstanceID()); if (expedition) { - expedition->RemoveAllMembers(false, false); // entity list will teleport clients out immediately + expedition->RemoveAllMembers(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(); From 9b82cf57e40001a4a28769d8d3fd45cc45672204 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 28 May 2020 00:24:07 -0400 Subject: [PATCH 063/196] Add #dz lockouts remove by event name --- common/servertalk.h | 3 ++- world/expedition.cpp | 2 +- zone/command.cpp | 19 ++++++++++++++----- zone/expedition.cpp | 25 +++++++++++++++---------- zone/expedition.h | 2 +- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index d537bb825..595f9d02e 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -2065,9 +2065,10 @@ struct ServerExpeditionSetting_Struct { uint8 enabled; }; -struct ServerExpeditionCharacterName_Struct { +struct ServerExpeditionCharacterLockout_Struct { char character_name[64]; char expedition_name[128]; + char event_name[256]; }; struct ServerExpeditionCharacterID_Struct { diff --git a/world/expedition.cpp b/world/expedition.cpp index ff2476e50..58c21a919 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -437,7 +437,7 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) } case ServerOP_ExpeditionRemoveCharLockouts: { - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); client_list.SendPacket(buf->character_name, pack); break; } diff --git a/zone/command.cpp b/zone/command.cpp index f7028cd96..caa7684ab 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6932,11 +6932,19 @@ void command_dz(Client* c, const Seperator* sep) { if (strcasecmp(sep->arg[2], "remove") == 0 && sep->arg[3][0] != '\0') { - c->Message(Chat::White, fmt::format( - "Removing [{}] lockouts on [{}].", sep->arg[4][0] ? sep->arg[4] : "all", sep->arg[3] - ).c_str()); - - Expedition::RemoveAllCharacterLockouts(sep->arg[3], sep->arg[4]); + if (sep->arg[5][0] == '\0') + { + c->Message(Chat::White, fmt::format( + "Removing [{}] lockouts on [{}].", sep->arg[4][0] ? sep->arg[4] : "all", sep->arg[3] + ).c_str()); + } + else + { + c->Message(Chat::White, fmt::format( + "Removing [{}]:[{}] lockout on [{}].", sep->arg[4], sep->arg[5], sep->arg[3] + ).c_str()); + } + Expedition::RemoveCharacterLockouts(sep->arg[3], sep->arg[4], sep->arg[5]); } } else @@ -6948,6 +6956,7 @@ void command_dz(Client* c, const Seperator* sep) c->Message(Chat::White, "#dz list [all] - list dynamic zone instances from database -- 'all' includes expired"); c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); c->Message(Chat::White, "#dz lockouts remove \"\" - delete lockouts by expedition"); + c->Message(Chat::White, "#dz lockouts remove \"\" \"\" - delete lockout by expedition event"); } } diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 6c388fe7c..5057bcaa3 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1556,17 +1556,15 @@ void Expedition::SendWorldGetOnlineMembers() worldserver.SendPacket(pack.get()); } -void Expedition::RemoveAllCharacterLockouts(std::string character_name, std::string expedition_name) +void Expedition::RemoveCharacterLockouts( + std::string character_name, std::string expedition_name, std::string event_name) { - uint32_t pack_size = sizeof(ServerExpeditionCharacterName_Struct); + uint32_t pack_size = sizeof(ServerExpeditionCharacterLockout_Struct); auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionRemoveCharLockouts, pack_size)); - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); strn0cpy(buf->character_name, character_name.c_str(), sizeof(buf->character_name)); - buf->expedition_name[0] = '\0'; - if (!expedition_name.empty()) - { - strn0cpy(buf->expedition_name, expedition_name.c_str(), sizeof(buf->expedition_name)); - } + strn0cpy(buf->expedition_name, expedition_name.c_str(), sizeof(buf->expedition_name)); + strn0cpy(buf->event_name, event_name.c_str(), sizeof(buf->event_name)); worldserver.SendPacket(pack.get()); } @@ -1788,11 +1786,18 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } case ServerOP_ExpeditionRemoveCharLockouts: { - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); Client* client = entity_list.GetClientByName(buf->character_name); if (client) { - client->RemoveAllExpeditionLockouts(buf->expedition_name); + if (buf->event_name[0] != '\0') + { + client->RemoveExpeditionLockout(buf->expedition_name, buf->event_name, true); + } + else + { + client->RemoveAllExpeditionLockouts(buf->expedition_name); + } } break; } diff --git a/zone/expedition.h b/zone/expedition.h index 4ad4460f5..cb40227f0 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -84,7 +84,7 @@ public: static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name); static Expedition* FindCachedExpeditionByID(uint32_t expedition_id); static Expedition* FindExpeditionByInstanceID(uint32_t instance_id); - static void RemoveAllCharacterLockouts(std::string character_name, std::string expedition_name = {}); + static void RemoveCharacterLockouts(std::string character_name, std::string expedition_name = {}, std::string event_name = {}); static void HandleWorldMessage(ServerPacket* pack); uint32_t GetID() const { return m_id; } From 428462409629a182595979d384811c595df7621b Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 30 May 2020 20:33:51 -0400 Subject: [PATCH 064/196] Remove expedition includes from client header Make quest compass a vec3 to remove include dependency Quest compass location doesn't require a zone id since it can only be set in the zone that it's drawn in. Drop the DynamicZoneLocation member and forward declare to remove the dependency on header. Move ExpeditionInvite struct to zone common header Including expedition.h in client.h just to use the ExpeditionInvite struct is an unnecessary header dependency that increases incremental build time. This allows expedition classes to be forward declared in client header. --- zone/client.cpp | 6 +++--- zone/client.h | 10 +++++++--- zone/common.h | 7 +++++++ zone/entity.cpp | 1 + zone/expedition.h | 13 ++++--------- zone/expedition_request.h | 2 +- zone/lua_client.cpp | 1 + zone/zoning.cpp | 1 + 8 files changed, 25 insertions(+), 16 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 3bf285d78..e946559ed 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -6159,11 +6159,11 @@ void Client::MarkSingleCompassLoc(float in_x, float in_y, float in_z, uint8 coun { if (count == 0) { - m_quest_compass.zone_id = 0; + m_has_quest_compass = false; } else { - m_quest_compass.zone_id = zone ? zone->GetZoneID() : 0; + m_has_quest_compass = true; m_quest_compass.x = in_x; m_quest_compass.y = in_y; m_quest_compass.z = in_z; @@ -9856,7 +9856,7 @@ void Client::SendDzCompassUpdate() // todo: shared tasks, missions, and quests with an associated dz // compass set via MarkSingleCompassLocation() - if (zone && zone->GetZoneID() == m_quest_compass.zone_id && zone->GetInstanceID() == 0) + if (m_has_quest_compass) { DynamicZoneCompassEntry_Struct entry; entry.dz_zone_id = 0; diff --git a/zone/client.h b/zone/client.h index 17f25d70b..c12f5f26b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -21,12 +21,17 @@ class Client; class EQApplicationPacket; class EQStream; +class DynamicZone; +class Expedition; +class ExpeditionLockoutTimer; +class ExpeditionRequest; class Group; class NPC; class Object; class Raid; class Seperator; class ServerPacket; +struct DynamicZoneLocation; enum WaterRegionType : int; namespace EQ @@ -52,8 +57,6 @@ namespace EQ #include "aggromanager.h" #include "common.h" -#include "expedition.h" -#include "dynamiczone.h" #include "merc.h" #include "mob.h" #include "qglobals.h" @@ -1702,7 +1705,8 @@ private: uint32 m_expedition_id = 0; ExpeditionInvite m_pending_expedition_invite { 0 }; std::vector m_expedition_lockouts; - DynamicZoneLocation m_quest_compass; + glm::vec3 m_quest_compass; + bool m_has_quest_compass = false; #ifdef BOTS diff --git a/zone/common.h b/zone/common.h index 19df1a576..c05c6a95b 100644 --- a/zone/common.h +++ b/zone/common.h @@ -786,5 +786,12 @@ struct DamageHitInfo { EQ::skills::SkillType skill; }; +struct ExpeditionInvite +{ + uint32_t expedition_id; + std::string inviter_name; + std::string swap_remove_name; +}; + #endif diff --git a/zone/entity.cpp b/zone/entity.cpp index e19ecdf73..68364ff82 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -32,6 +32,7 @@ #include "../common/features.h" #include "../common/guilds.h" +#include "dynamiczone.h" #include "guild_mgr.h" #include "petitions.h" #include "quest_parser_collection.h" diff --git a/zone/expedition.h b/zone/expedition.h index cb40227f0..403dff438 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -32,6 +32,7 @@ class Client; class EQApplicationPacket; +struct ExpeditionInvite; class ExpeditionRequest; class MySQLRequestResult; class ServerPacket; @@ -54,19 +55,13 @@ struct ExpeditionMember std::string name; ExpeditionMemberStatus status = ExpeditionMemberStatus::Online; - ExpeditionMember() {} - ExpeditionMember(uint32_t char_id_, const std::string& name_) : char_id(char_id_), name(name_) {} + ExpeditionMember() = default; + 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_) {} }; -struct ExpeditionInvite -{ - uint32_t expedition_id; - std::string inviter_name; - std::string swap_remove_name; -}; - class Expedition { public: diff --git a/zone/expedition_request.h b/zone/expedition_request.h index 864aa855a..569dcc6ae 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -21,6 +21,7 @@ #ifndef EXPEDITION_REQUEST_H #define EXPEDITION_REQUEST_H +#include "expedition.h" #include "expedition_lockout_timer.h" #include #include @@ -32,7 +33,6 @@ class Group; class MySQLRequestResult; class Raid; class ServerPacket; -struct ExpeditionMember; class ExpeditionRequest { diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 8ed8142ad..dfd9104ff 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -4,6 +4,7 @@ #include #include "client.h" +#include "dynamiczone.h" #include "expedition_lockout_timer.h" #include "expedition_request.h" #include "lua_client.h" diff --git a/zone/zoning.cpp b/zone/zoning.cpp index 35dec1ca1..e294caa4e 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -21,6 +21,7 @@ #include "../common/rulesys.h" #include "../common/string_util.h" +#include "expedition.h" #include "queryserv.h" #include "quest_parser_collection.h" #include "string_ids.h" From f9eafa52f961998de0235148bb4ed6285ebe9169 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 1 Jun 2020 18:21:12 -0400 Subject: [PATCH 065/196] Ignore expired lockouts on expedition invite This fixes an edge case with client invites sometimes failing because an expired lockout hasn't been removed from client yet Clients no longer receive expired lockouts from expeditions when joining --- zone/client.cpp | 30 ++++++++++++++++-------------- zone/client.h | 5 +++-- zone/expedition.cpp | 6 +++--- zone/expedition_lockout_timer.h | 2 +- zone/expedition_request.cpp | 16 ++++++++++------ 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index e946559ed..f53e4997a 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9613,19 +9613,6 @@ Expedition* Client::GetExpedition() const return nullptr; } -std::vector Client::GetExpeditionLockouts(const std::string& expedition_name) -{ - std::vector lockouts; - for (const auto& lockout : m_expedition_lockouts) - { - if (lockout.GetExpeditionName() == expedition_name) - { - lockouts.emplace_back(lockout); - } - } - return lockouts; -} - void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db, bool update_client) { // todo: support for account based lockouts like live AoC expeditions @@ -9707,7 +9694,7 @@ const ExpeditionLockoutTimer* Client::GetExpeditionLockout( { for (const auto& expedition_lockout : m_expedition_lockouts) { - if ((include_expired || expedition_lockout.GetSecondsRemaining() > 0) && + if ((include_expired || !expedition_lockout.IsExpired()) && expedition_lockout.IsSameLockout(expedition_name, event_name)) { return &expedition_lockout; @@ -9716,6 +9703,21 @@ const ExpeditionLockoutTimer* Client::GetExpeditionLockout( return nullptr; } +std::vector Client::GetExpeditionLockouts( + const std::string& expedition_name, bool include_expired) +{ + std::vector lockouts; + for (const auto& lockout : m_expedition_lockouts) + { + if ((include_expired || !lockout.IsExpired()) && + lockout.GetExpeditionName() == expedition_name) + { + lockouts.emplace_back(lockout); + } + } + return lockouts; +} + bool Client::HasExpeditionLockout( const std::string& expedition_name, const std::string& event_name, bool include_expired) { diff --git a/zone/client.h b/zone/client.h index c12f5f26b..09b2f8020 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1128,12 +1128,13 @@ public: 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); + std::vector GetExpeditionLockouts(const std::string& expedition_name, bool include_expired = false); uint32 GetPendingExpeditionInviteID() const { return m_pending_expedition_invite.expedition_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 RemoveAllExpeditionLockouts(std::string expedition_name = {}); - void RemoveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, bool update_db = false, bool update_client = true); + void RemoveExpeditionLockout( + const std::string& expedition_name, const std::string& event_name, bool update_db = false, bool update_client = true); void RequestPendingExpeditionInvite(); void SendExpeditionLockoutTimers(); void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 5057bcaa3..d326131b3 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -613,7 +613,7 @@ void Expedition::SendClientExpeditionInvite( { // live doesn't issue a warning for the dz's replay timer const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (!lockout.IsInherited() && !lockout.IsReplayTimer() && + if (!lockout.IsInherited() && !lockout.IsExpired() && !lockout.IsReplayTimer() && !client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) { if (!warned) @@ -680,7 +680,7 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, } } - // check any extra event lockouts for this expedition that the client has and leader doesn't + // check any extra event lockouts for this expedition that the client has and expedition doesn't auto client_lockouts = add_client->GetExpeditionLockouts(m_expedition_name); for (const auto& client_lockout : client_lockouts) { @@ -776,7 +776,7 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: for (const auto& lockout_iter : m_lockouts) { const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (!lockout.IsInherited() && + if (!lockout.IsInherited() && !lockout.IsExpired() && !add_client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) { // replay timers are optionally added to new members immediately on diff --git a/zone/expedition_lockout_timer.h b/zone/expedition_lockout_timer.h index ff70864c4..45155a111 100644 --- a/zone/expedition_lockout_timer.h +++ b/zone/expedition_lockout_timer.h @@ -25,7 +25,6 @@ extern const char* const DZ_REPLAY_TIMER_NAME; -// DynamicZoneEventTimer and DynamicZoneReplayTimer in client class ExpeditionLockoutTimer { public: @@ -47,6 +46,7 @@ public: 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 IsExpired() const { return GetSecondsRemaining() == 0; } bool IsInherited() const { return m_is_inherited; } bool IsReplayTimer() const { return m_is_replay_timer; } bool IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const; diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 77bdaa884..0474c18e6 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -194,6 +194,8 @@ bool ExpeditionRequest::LoadLeaderLockouts() return false; } + auto leeway_seconds = static_cast(RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)); + for (auto row = results.begin(); row != results.end(); ++row) { uint64_t expire_time = strtoull(row[0], nullptr, 10); @@ -201,11 +203,11 @@ bool ExpeditionRequest::LoadLeaderLockouts() ExpeditionLockoutTimer lockout{m_expedition_name, row[2], expire_time, duration, true}; // client window hides timers with less than 60s remaining, optionally count them as expired - if (lockout.GetSecondsRemaining() <= RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)) + if (lockout.GetSecondsRemaining() <= leeway_seconds) { LogExpeditionsModerate( - "Ignoring leader [{}] lockout [{}] with [{}] seconds remaining due to expired leeway rule", - m_leader_id, lockout.GetEventName(), lockout.GetSecondsRemaining() + "Ignoring leader [{}] lockout [{}] with [{}s] remaining due to leeway rule [{}s]", + m_leader_id, lockout.GetEventName(), lockout.GetSecondsRemaining(), leeway_seconds ); } else @@ -230,6 +232,8 @@ bool ExpeditionRequest::CheckMembersForConflicts(MySQLRequestResult& results, bo std::vector member_lockout_conflicts; + auto leeway_seconds = static_cast(RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)); + bool leader_processed = false; uint32_t last_character_id = 0; for (auto row = results.begin(); row != results.end(); ++row) @@ -275,11 +279,11 @@ bool ExpeditionRequest::CheckMembersForConflicts(MySQLRequestResult& results, bo ExpeditionLockoutTimer lockout(m_expedition_name, event_name, expire_time, original_duration); // client window hides timers with less than 60s remaining, optionally count them as expired - if (lockout.GetSecondsRemaining() <= RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)) + if (lockout.GetSecondsRemaining() <= leeway_seconds) { LogExpeditionsModerate( - "Ignoring character [{}] lockout [{}] with [{}] seconds remaining due to expired leeway rule", - character_id, lockout.GetEventName(), lockout.GetSecondsRemaining() + "Ignoring character [{}] lockout [{}] with [{}s] remaining due to leeway rule [{}s]", + character_id, lockout.GetEventName(), lockout.GetSecondsRemaining(), leeway_seconds ); } else From 2c4f505309d08b98e6242c494f449c554cb63b22 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 30 May 2020 23:03:01 -0400 Subject: [PATCH 066/196] Refactor zone expedition caching This optimizes caching all expeditions by loading dynamic zone data and expedition members in bulk instead of for each expedition separately. This reduces the number of queries from 1+2n to 3 total. Expedition members are now joined in the initial query since empty expeditions aren't cached anyway. Optional internal lockouts for all cached expeditions are loaded in a single bulk query afterwards. Dynamic Zone data is also loaded as a single bulk query afterwards to simplify processing and keep dz database logic separated. It might be worth investigating if joining dz data in the initial expeditions load query is worth refactoring for. --- zone/dynamiczone.cpp | 124 +++++++++++++++++++++------------ zone/dynamiczone.h | 15 ++-- zone/expedition.cpp | 116 ++++++++++++++++++------------- zone/expedition.h | 1 - zone/expedition_database.cpp | 130 +++++++++++++++++++---------------- zone/expedition_database.h | 24 ++++++- 6 files changed, 254 insertions(+), 156 deletions(-) diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 157b487e3..754e7c31a 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -62,6 +62,42 @@ DynamicZone DynamicZone::LoadDzFromDatabase(uint32_t instance_id) return dynamic_zone; } +std::unordered_map DynamicZone::LoadMultipleDzFromDatabase( + const std::vector& instance_ids) +{ + LogDynamicZonesDetail("Loading dynamic zone data for [{}] instances", instance_ids.size()); + + std::string in_instance_ids_query; + for (const auto& instance_id : instance_ids) + { + fmt::format_to(std::back_inserter(in_instance_ids_query), "{},", instance_id); + } + + std::unordered_map dynamic_zones; + + if (!in_instance_ids_query.empty()) + { + in_instance_ids_query.pop_back(); // trailing comma + + std::string query = fmt::format(SQL( + {} WHERE dynamic_zones.instance_id IN ({}); + ), DynamicZoneSelectQuery(), in_instance_ids_query); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + DynamicZone dz; + dz.LoadDatabaseResult(row); + dynamic_zones.emplace(dz.GetInstanceID(), dz); + } + } + } + + return dynamic_zones; +} + uint32_t DynamicZone::CreateInstance() { if (m_instance_id) @@ -107,26 +143,11 @@ uint32_t DynamicZone::CreateInstance() return m_instance_id; } -void DynamicZone::LoadFromDatabase(uint32_t instance_id) +std::string DynamicZone::DynamicZoneSelectQuery() { - 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( + return std::string(SQL( SELECT + instance_list.id, instance_list.zone, instance_list.version, instance_list.start_time, @@ -149,36 +170,53 @@ void DynamicZone::LoadFromDatabase(uint32_t instance_id) 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); + )); +} + +void DynamicZone::LoadDatabaseResult(MySQLRequestRow& row) +{ + m_instance_id = strtoul(row[0], nullptr, 10); + m_zone_id = strtoul(row[1], nullptr, 10); + m_version = strtoul(row[2], nullptr, 10); + m_start_time = strtoul(row[3], nullptr, 10); + m_duration = strtoul(row[4], nullptr, 10); + m_never_expires = (strtoul(row[5], nullptr, 10) != 0); + m_type = static_cast(strtoul(row[6], nullptr, 10)); + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + m_compass.zone_id = strtoul(row[7], nullptr, 10); + m_compass.x = strtof(row[8], nullptr); + m_compass.y = strtof(row[9], nullptr); + m_compass.z = strtof(row[10], nullptr); + m_safereturn.zone_id = strtoul(row[11], nullptr, 10); + m_safereturn.x = strtof(row[12], nullptr); + m_safereturn.y = strtof(row[13], nullptr); + m_safereturn.z = strtof(row[14], nullptr); + m_safereturn.heading = strtof(row[15], nullptr); + m_zonein.x = strtof(row[16], nullptr); + m_zonein.y = strtof(row[17], nullptr); + m_zonein.z = strtof(row[18], nullptr); + m_zonein.heading = strtof(row[19], nullptr); + m_has_zonein = (strtoul(row[20], nullptr, 10) != 0); +} + +void DynamicZone::LoadFromDatabase(uint32_t instance_id) +{ + if (instance_id == 0) + { + return; + } + + LogDynamicZonesDetail("Loading dz instance [{}] from database", instance_id); + + std::string query = fmt::format(SQL( + {} WHERE dynamic_zones.instance_id = {}; + ), DynamicZoneSelectQuery(), 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); + LoadDatabaseResult(row); } } diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index f9a64276b..54c58be9f 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -24,12 +24,14 @@ #include #include #include +#include #include #include +class MySQLRequestRow; class ServerPacket; -enum class DynamicZoneType : uint8_t // DynamicZoneActiveType +enum class DynamicZoneType : uint8_t { None = 0, Expedition, @@ -47,7 +49,7 @@ struct DynamicZoneLocation float z = 0.0f; float heading = 0.0f; - DynamicZoneLocation() {} + DynamicZoneLocation() = default; 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_) {} }; @@ -58,9 +60,12 @@ 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) { } + DynamicZone(uint32_t instance_id) : m_instance_id(instance_id) {} + DynamicZone(DynamicZoneType type) : m_type(type) {} static DynamicZone LoadDzFromDatabase(uint32_t instance_id); + static std::unordered_map LoadMultipleDzFromDatabase( + const std::vector& instance_ids); static void HandleWorldMessage(ServerPacket* pack); DynamicZoneType GetType() const { return m_type; } @@ -95,6 +100,8 @@ public: uint32_t SaveToDatabase(); private: + static std::string DynamicZoneSelectQuery(); + void LoadDatabaseResult(MySQLRequestRow& row); void DeleteFromDatabase(); void SaveCompassToDatabase(); void SaveSafeReturnToDatabase(); @@ -111,7 +118,7 @@ private: DynamicZoneLocation m_compass; DynamicZoneLocation m_safereturn; DynamicZoneLocation m_zonein; - std::chrono::time_point m_expire_time; //uint64_t m_expire_time = 0; + std::chrono::time_point m_expire_time; }; #endif diff --git a/zone/expedition.cpp b/zone/expedition.cpp index d326131b3..2938882d4 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -153,58 +153,95 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) return; } + std::vector expedition_ids; + std::vector instance_ids; + + using col = LoadExpeditionColumns::eLoadExpeditionColumns; + + Expedition* current_expedition = nullptr; uint32_t last_expedition_id = 0; + for (auto row = results.begin(); row != results.end(); ++row) { - auto expedition_id = strtoul(row[0], nullptr, 10); + auto expedition_id = strtoul(row[col::id], nullptr, 10); + if (expedition_id != last_expedition_id) { - auto leader_id = static_cast(strtoul(row[3], nullptr, 10)); - ExpeditionMember leader{ leader_id, row[9] }; // id, name - auto instance_id = row[1] ? strtoul(row[1], nullptr, 10) : 0; // can be null from fk constraint + // finished parsing previous expedition members, send member updates + if (current_expedition) + { + current_expedition->SendWorldGetOnlineMembers(); + current_expedition->SendUpdatesToZoneMembers(); + } - DynamicZone dynamic_zone = DynamicZone::LoadDzFromDatabase(instance_id); + expedition_ids.emplace_back(expedition_id); + + uint32_t leader_id = strtoul(row[col::leader_id], nullptr, 10); + uint32_t instance_id = row[col::instance_id] ? strtoul(row[col::instance_id], nullptr, 10) : 0; + if (instance_id) // can be null from fk constraint + { + instance_ids.emplace_back(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 + DynamicZone{instance_id}, + row[col::expedition_name], // expedition name + ExpeditionMember{leader_id, row[col::leader_name]}, // expedition leader id, name + strtoul(row[col::min_players], nullptr, 10), // min_players + strtoul(row[col::max_players], nullptr, 10), // max_players + (strtoul(row[col::has_replay_timer], nullptr, 10) != 0) // has_replay_timer )); - bool add_replay_on_join = (strtoul(row[7], nullptr, 10) != 0); - bool is_locked = (strtoul(row[8], nullptr, 10) != 0); + bool add_replay_on_join = (strtoul(row[col::add_replay_on_join], nullptr, 10) != 0); + bool is_locked = (strtoul(row[col::is_locked], nullptr, 10) != 0); expedition->SetReplayLockoutOnMemberJoin(add_replay_on_join); expedition->SetLocked(is_locked); - expedition->LoadMembers(); - expedition->SendUpdatesToZoneMembers(); - // don't bother caching empty expeditions - if (expedition->GetMemberCount() > 0) - { - zone->expedition_cache.emplace(expedition_id, std::move(expedition)); - } + zone->expedition_cache.emplace(expedition_id, std::move(expedition)); } last_expedition_id = expedition_id; - // optional lockouts from left join - if (row[10] && row[11] && row[12] && row[13]) + // looping expedition members + current_expedition = Expedition::FindCachedExpeditionByID(last_expedition_id); + if (current_expedition) { - auto it = zone->expedition_cache.find(last_expedition_id); - if (it != zone->expedition_cache.end()) + auto member_id = strtoul(row[col::member_id], nullptr, 10); + bool is_current_member = (strtoul(row[col::is_current_member], nullptr, 10) != 0); + current_expedition->AddInternalMember( + row[col::member_name], member_id, ExpeditionMemberStatus::Offline, is_current_member + ); + } + } + + // update for the last cached expedition + if (current_expedition) + { + current_expedition->SendWorldGetOnlineMembers(); + current_expedition->SendUpdatesToZoneMembers(); + } + + // bulk load dynamic zone data and expedition lockouts for cached expeditions + auto dynamic_zones = DynamicZone::LoadMultipleDzFromDatabase(instance_ids); + auto expedition_lockouts = ExpeditionDatabase::LoadMultipleExpeditionLockouts(expedition_ids); + + for (const auto& expedition_id : expedition_ids) + { + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) + { + auto dz_iter = dynamic_zones.find(expedition->GetInstanceID()); + if (dz_iter != dynamic_zones.end()) { - it->second->AddInternalLockout(ExpeditionLockoutTimer{ - row[2], // expedition_name - row[10], // event_name - strtoull(row[11], nullptr, 10), // expire_time - static_cast(strtoul(row[12], nullptr, 10)), // original duration - (strtoul(row[13], nullptr, 10) != 0) // is_inherited - }); + expedition->m_dynamiczone = dz_iter->second; + } + + auto lockout_iter = expedition_lockouts.find(expedition->GetID()); + if (lockout_iter != expedition_lockouts.end()) + { + expedition->m_lockouts = lockout_iter->second; } } } @@ -257,23 +294,6 @@ bool Expedition::CacheAllFromDatabase() return true; } -void Expedition::LoadMembers() -{ - m_members.clear(); - - auto results = ExpeditionDatabase::LoadExpeditionMembers(m_id); - if (results.Success()) - { - for (auto row = results.begin(); row != results.end(); ++row) - { - auto character_id = strtoul(row[0], nullptr, 10); - bool is_current_member = strtoul(row[1], nullptr, 10); - AddInternalMember(row[2], character_id, ExpeditionMemberStatus::Offline, is_current_member); - } - SendWorldGetOnlineMembers(); - } -} - void Expedition::SaveLockouts(ExpeditionRequest& request) { m_lockouts = std::move(request).TakeLockouts(); diff --git a/zone/expedition.h b/zone/expedition.h index 403dff438..11bc6fda4 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -139,7 +139,6 @@ private: 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(); 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); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index fb913a9b2..1c4bf6207 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -48,11 +48,9 @@ uint32_t ExpeditionDatabase::InsertExpedition( return results.LastInsertedID(); } -MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) +std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() { - LogExpeditionsDetail("Loading expedition [{}]", expedition_id); - - std::string query = fmt::format(SQL( + return std::string(SQL( SELECT expedition_details.id, expedition_details.instance_id, @@ -64,53 +62,36 @@ MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) expedition_details.add_replay_on_join, expedition_details.is_locked, character_data.name leader_name, - expedition_lockouts.event_name, - UNIX_TIMESTAMP(expedition_lockouts.expire_time), - expedition_lockouts.duration, - expedition_lockouts.is_inherited + expedition_members.character_id, + expedition_members.is_current_member, + member_data.name 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); + INNER JOIN expedition_members ON expedition_details.id = expedition_members.expedition_id + INNER JOIN character_data member_data ON expedition_members.character_id = member_data.id + )); +} - auto results = database.QueryDatabase(query); - return results; +MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) +{ + LogExpeditionsDetail("Loading expedition [{}]", expedition_id); + + std::string query = fmt::format(SQL( + {} WHERE expedition_details.id = {}; + ), LoadExpeditionsSelectQuery(), expedition_id); + + return database.QueryDatabase(query); } 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, - expedition_details.add_replay_on_join, - expedition_details.is_locked, - 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; - ); + std::string query = fmt::format(SQL( + {} ORDER BY expedition_details.id; + ), LoadExpeditionsSelectQuery()); - auto results = database.QueryDatabase(query); - return results; + return database.QueryDatabase(query); } MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts(uint32_t character_id) @@ -151,26 +132,58 @@ MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts( return database.QueryDatabase(query); } -MySQLRequestResult ExpeditionDatabase::LoadExpeditionMembers(uint32_t expedition_id) +std::unordered_map> +ExpeditionDatabase::LoadMultipleExpeditionLockouts( + const std::vector& expedition_ids) { - LogExpeditionsDetail("Loading all members for expedition [{}]", expedition_id); + LogExpeditionsDetail("Loading internal lockouts for [{}] expeditions", expedition_ids.size()); - 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()) + std::string in_expedition_ids_query; + for (const auto& expedition_id : expedition_ids) { - LogExpeditions("Failed to load expedition [{}] members from db", expedition_id); + fmt::format_to(std::back_inserter(in_expedition_ids_query), "{},", expedition_id); } - return results; + + // these are loaded into the same container type expedition's use to store lockouts + std::unordered_map> lockouts; + + if (!in_expedition_ids_query.empty()) + { + in_expedition_ids_query.pop_back(); // trailing comma + + std::string query = fmt::format(SQL( + SELECT + expedition_lockouts.expedition_id, + expedition_lockouts.event_name, + UNIX_TIMESTAMP(expedition_lockouts.expire_time), + expedition_lockouts.duration, + expedition_lockouts.is_inherited, + expedition_details.expedition_name + FROM expedition_lockouts + INNER JOIN expedition_details ON expedition_lockouts.expedition_id = expedition_details.id + WHERE expedition_id IN ({}) + ORDER BY expedition_id; + ), in_expedition_ids_query); + + auto results = database.QueryDatabase(query); + + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + auto expedition_id = strtoul(row[0], nullptr, 10); + lockouts[expedition_id].emplace(row[1], ExpeditionLockoutTimer{ + row[5], // expedition_name + row[1], // event_name + strtoull(row[2], nullptr, 10), // expire_time + static_cast(strtoul(row[3], nullptr, 10)), // original duration + (strtoul(row[4], nullptr, 10) != 0) // is_inherited + }); + } + } + } + + return lockouts; } MySQLRequestResult ExpeditionDatabase::LoadValidationData( @@ -200,8 +213,7 @@ MySQLRequestResult ExpeditionDatabase::LoadValidationData( ORDER BY character_data.id; ), expedition_name, character_names); - auto results = database.QueryDatabase(query); - return results; + return database.QueryDatabase(query); } void ExpeditionDatabase::DeleteAllCharacterLockouts(uint32_t character_id) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 1773d2550..e6e35368e 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -38,12 +38,14 @@ namespace ExpeditionDatabase uint32_t InsertExpedition( 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); + std::string LoadExpeditionsSelectQuery(); 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); + std::unordered_map> + LoadMultipleExpeditionLockouts(const std::vector& expedition_ids); void DeleteAllCharacterLockouts(uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id, const std::string& expedition_name); void DeleteCharacterLockout(uint32_t character_id, const std::string& expedition_name, const std::string& event_name); @@ -70,4 +72,24 @@ namespace ExpeditionDatabase void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); }; +namespace LoadExpeditionColumns +{ + enum eLoadExpeditionColumns + { + id = 0, + instance_id, + expedition_name, + leader_id, + min_players, + max_players, + has_replay_timer, + add_replay_on_join, + is_locked, + leader_name, + member_id, + is_current_member, + member_name + }; +}; + #endif From 402491b36be14df5cc5ea5026d85c4dba6606fd6 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 1 Jun 2020 19:45:12 -0400 Subject: [PATCH 067/196] Refactor expedition member online status requests This optimizes character status requests by only sending a single bulk request to world for characters in all expeditions instead of sending a separate request for each expedition on zone startup --- common/servertalk.h | 2 +- world/expedition.cpp | 16 ++++++++-------- zone/expedition.cpp | 28 ++++++++++++++++------------ zone/expedition.h | 5 +++-- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 595f9d02e..ab9ff17ca 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -2034,6 +2034,7 @@ struct ServerExpeditionMemberStatus_Struct { }; struct ServerExpeditionCharacterEntry_Struct { + uint32 expedition_id; uint32 character_id; uint32 character_zone_id; uint16 character_instance_id; @@ -2041,7 +2042,6 @@ struct ServerExpeditionCharacterEntry_Struct { }; struct ServerExpeditionCharacters_Struct { - uint32 expedition_id; uint32 sender_zone_id; uint16 sender_instance_id; uint32 count; diff --git a/world/expedition.cpp b/world/expedition.cpp index 58c21a919..1d30fcd67 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -510,15 +510,15 @@ void ExpeditionMessage::GetOnlineMembers(ServerPacket* pack) for (uint32_t i = 0; i < buf->count; ++i) { - for (const auto& cle : all_clients) + auto it = std::find_if(all_clients.begin(), all_clients.end(), [&](const ClientListEntry* cle) { + return (cle && cle->CharID() == buf->entries[i].character_id); + }); + + if (it != all_clients.end()) { - 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; - } + buf->entries[i].character_zone_id = (*it)->zone(); + buf->entries[i].character_instance_id = (*it)->instance(); + buf->entries[i].character_online = true; } } diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 2938882d4..ef7ea5b73 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -155,6 +155,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) std::vector expedition_ids; std::vector instance_ids; + std::vector> expedition_character_ids; using col = LoadExpeditionColumns::eLoadExpeditionColumns; @@ -170,7 +171,6 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) // finished parsing previous expedition members, send member updates if (current_expedition) { - current_expedition->SendWorldGetOnlineMembers(); current_expedition->SendUpdatesToZoneMembers(); } @@ -213,16 +213,19 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) current_expedition->AddInternalMember( row[col::member_name], member_id, ExpeditionMemberStatus::Offline, is_current_member ); + expedition_character_ids.emplace_back(std::make_pair(expedition_id, member_id)); } } // update for the last cached expedition if (current_expedition) { - current_expedition->SendWorldGetOnlineMembers(); current_expedition->SendUpdatesToZoneMembers(); } + // ask world for online members from all cached expeditions at once + Expedition::SendWorldGetOnlineMembers(expedition_character_ids); + // bulk load dynamic zone data and expedition lockouts for cached expeditions auto dynamic_zones = DynamicZone::LoadMultipleDzFromDatabase(instance_ids); auto expedition_lockouts = ExpeditionDatabase::LoadMultipleExpeditionLockouts(expedition_ids); @@ -1554,21 +1557,22 @@ void Expedition::SendWorldSettingChanged(uint16_t server_opcode, bool setting_va worldserver.SendPacket(pack.get()); } -void Expedition::SendWorldGetOnlineMembers() +void Expedition::SendWorldGetOnlineMembers( + const std::vector>& expedition_character_ids) { - // request online status of all characters in our expedition tracked by world - uint32_t count = static_cast(m_members.size()); + // request online status of characters + uint32_t count = static_cast(expedition_character_ids.size()); uint32_t entries_size = sizeof(ServerExpeditionCharacterEntry_Struct) * count; uint32_t pack_size = sizeof(ServerExpeditionCharacters_Struct) + entries_size; auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionGetOnlineMembers, pack_size)); auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); buf->sender_zone_id = zone ? zone->GetZoneID() : 0; buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; buf->count = count; for (uint32_t i = 0; i < buf->count; ++i) { - buf->entries[i].character_id = m_members[i].char_id; + buf->entries[i].expedition_id = expedition_character_ids[i].first; + buf->entries[i].character_id = expedition_character_ids[i].second; buf->entries[i].character_zone_id = 0; buf->entries[i].character_instance_id = 0; buf->entries[i].character_online = false; @@ -1725,14 +1729,14 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } case ServerOP_ExpeditionGetOnlineMembers: { - // reply from world for online member statuses request + // reply from world for online member statuses request (for multiple expeditions) auto buf = reinterpret_cast(pack->pBuffer); - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) + for (uint32_t i = 0; i < buf->count; ++i) { - for (uint32_t i = 0; i < buf->count; ++i) + auto member = reinterpret_cast(&buf->entries[i]); + auto expedition = Expedition::FindCachedExpeditionByID(member->expedition_id); + if (expedition) { - 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->GetDynamicZone().IsInstanceID(member->character_instance_id)) diff --git a/zone/expedition.h b/zone/expedition.h index 11bc6fda4..0e58e18a5 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -74,7 +74,6 @@ public: static void CacheFromDatabase(uint32_t expedition_id); static bool CacheAllFromDatabase(); - static void CacheExpeditions(MySQLRequestResult& results); static Expedition* FindCachedExpeditionByCharacterID(uint32_t character_id); static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name); static Expedition* FindCachedExpeditionByID(uint32_t expedition_id); @@ -135,6 +134,9 @@ public: static const uint32_t EVENT_TIMER_ID; private: + static void CacheExpeditions(MySQLRequestResult& results); + static void SendWorldGetOnlineMembers(const std::vector>& expedition_character_ids); + void AddInternalLockout(ExpeditionLockoutTimer&& lockout_timer); void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status, bool is_current_member = true); bool ChooseNewLeader(); @@ -152,7 +154,6 @@ private: void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); void SendWorldExpeditionUpdate(uint16_t server_opcode); - void SendWorldGetOnlineMembers(); void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); void SendWorldLeaderChanged(); void SendWorldLockoutUpdate(const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove = false); From f287e9318e817881cb99824274de4cb29e43a086 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 8 Jun 2020 20:41:13 -0400 Subject: [PATCH 068/196] Refactor member validation in expedition requests Small cleanup of logic and unused variables Rename LoadValidationData to LoadMembersForCreateRequest Remove unnecessary early string building for members query Remove unnecessary lockout expired check for leader messages --- zone/expedition_database.cpp | 63 +++++++++++++++++++------------ zone/expedition_database.h | 3 +- zone/expedition_request.cpp | 73 +++++++++++++----------------------- zone/expedition_request.h | 4 +- 4 files changed, 70 insertions(+), 73 deletions(-) diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 1c4bf6207..27b634acf 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -186,34 +186,49 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts( return lockouts; } -MySQLRequestResult ExpeditionDatabase::LoadValidationData( - const std::string& character_names, const std::string& expedition_name) +MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( + const std::vector& 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); + std::string in_character_names_query; + for (const auto& character_name : character_names) + { + fmt::format_to(std::back_inserter(in_character_names_query), "'{}',", character_name); + } - return database.QueryDatabase(query); + MySQLRequestResult results; + + if (!in_character_names_query.empty()) + { + in_character_names_query.pop_back(); // trailing comma + + // 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, in_character_names_query); + + results = database.QueryDatabase(query); + } + + return results; } void ExpeditionDatabase::DeleteAllCharacterLockouts(uint32_t character_id) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index e6e35368e..ba9f99c3d 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -43,7 +43,8 @@ namespace ExpeditionDatabase MySQLRequestResult LoadAllExpeditions(); MySQLRequestResult LoadCharacterLockouts(uint32_t character_id); MySQLRequestResult LoadCharacterLockouts(uint32_t character_id, const std::string& expedition_name); - MySQLRequestResult LoadValidationData(const std::string& character_names_query, const std::string& expedition_name); + MySQLRequestResult LoadMembersForCreateRequest( + const std::vector& character_names, const std::string& expedition_name); std::unordered_map> LoadMultipleExpeditionLockouts(const std::vector& expedition_ids); void DeleteAllCharacterLockouts(uint32_t character_id); diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 0474c18e6..8cc0eead0 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -78,7 +78,7 @@ bool ExpeditionRequest::Validate(Client* requester) 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); + requirements_met = ValidateMembers({m_leader_name}); } auto elapsed = benchmark.elapsed(); @@ -93,23 +93,16 @@ bool ExpeditionRequest::CanRaidRequest(Raid* raid) 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; + std::vector 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; + member_names.emplace_back(raid->members[i].membername); } } - if (!query_member_names.empty()) - { - query_member_names.pop_back(); // trailing comma - } - - return ValidateMembers(query_member_names, count); + return ValidateMembers(member_names); } bool ExpeditionRequest::CanGroupRequest(Group* group) @@ -124,22 +117,16 @@ bool ExpeditionRequest::CanGroupRequest(Group* group) m_leader_id = m_leader ? m_leader->CharacterID() : database.GetCharacterID(m_leader_name.c_str()); uint32_t count = 0; - std::string query_member_names; + std::vector 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; + member_names.emplace_back(group->membername[i]); } } - if (!query_member_names.empty()) - { - query_member_names.pop_back(); // trailing comma - } - - return ValidateMembers(query_member_names, count); + return ValidateMembers(member_names); } std::string ExpeditionRequest::GetGroupLeaderName(uint32_t group_id) @@ -149,27 +136,16 @@ std::string ExpeditionRequest::GetGroupLeaderName(uint32_t group_id) return std::string(leader_name_buffer); } -bool ExpeditionRequest::ValidateMembers(const std::string& query_member_names, uint32_t member_count) +bool ExpeditionRequest::ValidateMembers(const std::vector& member_names) { - if (query_member_names.empty() || !LoadLeaderLockouts()) + if (member_names.empty()) { 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) + if (CheckMembersForConflicts(member_names)) { requirements_met = false; } @@ -178,7 +154,7 @@ bool ExpeditionRequest::ValidateMembers(const std::string& query_member_names, u // maybe it's done intentionally as a way to preview lockout conflicts if (requirements_met) { - requirements_met = IsPlayerCountValidated(member_count); + requirements_met = IsPlayerCountValidated(static_cast(member_names.size())); } return requirements_met; @@ -225,21 +201,29 @@ bool ExpeditionRequest::LoadLeaderLockouts() return true; } -bool ExpeditionRequest::CheckMembersForConflicts(MySQLRequestResult& results, bool is_solo) +bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& member_names) { - // leader lockouts were pre-loaded to compare with members below + // load data for each member and compare with leader lockouts + auto results = ExpeditionDatabase::LoadMembersForCreateRequest(member_names, m_expedition_name); + if (!results.Success() || !LoadLeaderLockouts()) + { + LogExpeditions("Failed to load data to verify members for expedition request"); + return true; + } + + bool is_solo = (member_names.size() == 1); bool has_conflicts = false; std::vector member_lockout_conflicts; auto leeway_seconds = static_cast(RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)); - bool leader_processed = false; uint32_t last_character_id = 0; for (auto row = results.begin(); row != results.end(); ++row) { auto character_id = static_cast(std::strtoul(row[0], nullptr, 10)); std::string character_name(row[1]); + bool has_expedition = (row[2] != nullptr); // in expedition_members with another expedition if (character_id != last_character_id) { @@ -253,8 +237,7 @@ bool ExpeditionRequest::CheckMembersForConflicts(MySQLRequestResult& results, bo } member_lockout_conflicts.clear(); - // current character existing expedition check - if (row[2]) + if (has_expedition) { has_conflicts = true; SendLeaderMemberInExpedition(character_name, is_solo); @@ -293,15 +276,13 @@ bool ExpeditionRequest::CheckMembersForConflicts(MySQLRequestResult& results, bo { 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 + // leader doesn't have this lockout. queue instead of messaging + // now so message comes after any replay lockout messages has_conflicts = true; member_lockout_conflicts.emplace_back(ExpeditionRequestConflict{character_name, lockout}); } @@ -348,7 +329,7 @@ void ExpeditionRequest::SendLeaderMemberInExpedition(const std::string& member_n void ExpeditionRequest::SendLeaderMemberReplayLockout( const std::string& member_name, const ExpeditionLockoutTimer& lockout, bool is_solo) { - if (m_disable_messages || lockout.GetSecondsRemaining() <= 0) + if (m_disable_messages) { return; } @@ -371,7 +352,7 @@ void ExpeditionRequest::SendLeaderMemberReplayLockout( void ExpeditionRequest::SendLeaderMemberEventLockout( const std::string& member_name, const ExpeditionLockoutTimer& lockout) { - if (m_disable_messages || lockout.GetSecondsRemaining() <= 0) + if (m_disable_messages) { return; } diff --git a/zone/expedition_request.h b/zone/expedition_request.h index 569dcc6ae..f128cc4c4 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -54,10 +54,10 @@ public: std::unordered_map TakeLockouts() && { return std::move(m_lockouts); } private: - bool ValidateMembers(const std::string& query_member_names, uint32_t member_count); + bool ValidateMembers(const std::vector& member_names); bool CanRaidRequest(Raid* raid); bool CanGroupRequest(Group* group); - bool CheckMembersForConflicts(MySQLRequestResult& results, bool is_solo); + bool CheckMembersForConflicts(const std::vector& member_names); std::string GetGroupLeaderName(uint32_t group_id); bool IsPlayerCountValidated(uint32_t member_count); bool LoadLeaderLockouts(); From 59d10a9db3d995e74c33902eddfb786b4035bbe5 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 10 Jun 2020 22:29:52 -0400 Subject: [PATCH 069/196] Process character lockout results in db methods --- zone/client.cpp | 12 +++------ zone/expedition_database.cpp | 50 +++++++++++++++++++++++++++++------- zone/expedition_database.h | 4 +-- zone/expedition_request.cpp | 27 +++++++------------ 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index f53e4997a..9dd8c9969 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9726,16 +9726,10 @@ bool Client::HasExpeditionLockout( void Client::LoadAllExpeditionLockouts() { - auto results = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); - if (results.Success()) + auto lockouts = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); + for (const auto& lockout : lockouts) { - for (auto row = results.begin(); row != results.end(); ++row) - { - auto expire_time = strtoull(row[0], nullptr, 10); - auto original_duration = static_cast(strtoul(row[1], nullptr, 10)); - ExpeditionLockoutTimer lockout{ row[2], row[3], expire_time, original_duration }; - AddExpeditionLockout(lockout, false, false); - } + AddExpeditionLockout(lockout, false, false); } SendExpeditionLockoutTimers(); } diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 27b634acf..2e0816d97 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -94,33 +94,51 @@ MySQLRequestResult ExpeditionDatabase::LoadAllExpeditions() return database.QueryDatabase(query); } -MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts(uint32_t character_id) +std::vector ExpeditionDatabase::LoadCharacterLockouts(uint32_t character_id) { LogExpeditionsDetail("Loading character [{}] lockouts", character_id); + std::vector lockouts; + auto query = fmt::format(SQL( SELECT - UNIX_TIMESTAMP(expire_time), - duration, expedition_name, - event_name + event_name, + UNIX_TIMESTAMP(expire_time), + duration FROM expedition_character_lockouts WHERE character_id = {} AND is_pending = FALSE AND expire_time > NOW(); ), character_id); - return database.QueryDatabase(query); + auto results = database.QueryDatabase(query); + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + lockouts.emplace_back(ExpeditionLockoutTimer{ + row[0], // expedition_name + row[1], // event_name + strtoull(row[2], nullptr, 10), // expire_time + static_cast(strtoul(row[3], nullptr, 10)) // duration + }); + } + } + + return lockouts; } -MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts( +std::vector ExpeditionDatabase::LoadCharacterLockouts( uint32_t character_id, const std::string& expedition_name) { LogExpeditionsDetail("Loading character [{}] lockouts for [{}]", character_id, expedition_name); + std::vector lockouts; + auto query = fmt::format(SQL( SELECT + event_name, UNIX_TIMESTAMP(expire_time), - duration, - event_name + duration FROM expedition_character_lockouts WHERE character_id = {} @@ -129,7 +147,21 @@ MySQLRequestResult ExpeditionDatabase::LoadCharacterLockouts( AND expedition_name = '{}'; ), character_id, expedition_name); - return database.QueryDatabase(query); + auto results = database.QueryDatabase(query); + if (results.Success()) + { + for (auto row = results.begin(); row != results.end(); ++row) + { + lockouts.emplace_back(ExpeditionLockoutTimer{ + expedition_name, + row[0], // event_name + strtoull(row[1], nullptr, 10), // expire_time + static_cast(strtoul(row[2], nullptr, 10)) // duration + }); + } + } + + return lockouts; } std::unordered_map> diff --git a/zone/expedition_database.h b/zone/expedition_database.h index ba9f99c3d..470840225 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -41,10 +41,10 @@ namespace ExpeditionDatabase std::string LoadExpeditionsSelectQuery(); 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 LoadMembersForCreateRequest( const std::vector& character_names, const std::string& expedition_name); + std::vector LoadCharacterLockouts(uint32_t character_id); + std::vector LoadCharacterLockouts(uint32_t character_id, const std::string& expedition_name); std::unordered_map> LoadMultipleExpeditionLockouts(const std::vector& expedition_ids); void DeleteAllCharacterLockouts(uint32_t character_id); diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 8cc0eead0..1f9375153 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -116,7 +116,6 @@ bool ExpeditionRequest::CanGroupRequest(Group* group) m_leader_name = m_leader ? m_leader->GetName() : GetGroupLeaderName(group->GetID()); // group->GetLeaderName(); m_leader_id = m_leader ? m_leader->CharacterID() : database.GetCharacterID(m_leader_name.c_str()); - uint32_t count = 0; std::vector member_names; for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) { @@ -163,20 +162,13 @@ bool ExpeditionRequest::ValidateMembers(const std::vector& member_n 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; - } + auto lockouts = ExpeditionDatabase::LoadCharacterLockouts(m_leader_id, m_expedition_name); auto leeway_seconds = static_cast(RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)); - for (auto row = results.begin(); row != results.end(); ++row) + for (auto& lockout : lockouts) { - uint64_t expire_time = strtoull(row[0], nullptr, 10); - uint32_t duration = strtoul(row[1], nullptr, 10); - ExpeditionLockoutTimer lockout{m_expedition_name, row[2], expire_time, duration, true}; + lockout.SetInherited(true); // client window hides timers with less than 60s remaining, optionally count them as expired if (lockout.GetSecondsRemaining() <= leeway_seconds) @@ -188,10 +180,10 @@ bool ExpeditionRequest::LoadLeaderLockouts() } else { - m_lockouts.emplace(row[2], lockout); + m_lockouts.emplace(lockout.GetEventName(), lockout); // 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) + if (m_check_event_lockouts && m_has_replay_timer && lockout.IsReplayTimer()) { m_check_event_lockouts = false; } @@ -256,10 +248,9 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& 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]); + auto duration = static_cast(strtoul(row[4], nullptr, 10)); - ExpeditionLockoutTimer lockout(m_expedition_name, event_name, expire_time, original_duration); + ExpeditionLockoutTimer lockout{m_expedition_name, row[5], expire_time, duration}; // client window hides timers with less than 60s remaining, optionally count them as expired if (lockout.GetSecondsRemaining() <= leeway_seconds) @@ -272,14 +263,14 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& else { // replay timer conflict messages always show up before event conflicts - if (/*m_has_replay_timer && */event_name == DZ_REPLAY_TIMER_NAME) + if (/*m_has_replay_timer && */lockout.IsReplayTimer()) { has_conflicts = true; SendLeaderMemberReplayLockout(character_name, lockout, is_solo); } else if (m_check_event_lockouts && character_id != m_leader_id) { - if (m_lockouts.find(event_name) == m_lockouts.end()) + if (m_lockouts.find(lockout.GetEventName()) == m_lockouts.end()) { // leader doesn't have this lockout. queue instead of messaging // now so message comes after any replay lockout messages From 61655501339bd4fa12b4e83e1c3a9a73a9deb5b5 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 10 Jun 2020 22:57:44 -0400 Subject: [PATCH 070/196] Copy instead of moving expedition request data --- zone/expedition.cpp | 12 +++++------- zone/expedition_request.h | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index ef7ea5b73..e6d46e34d 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -299,13 +299,13 @@ bool Expedition::CacheAllFromDatabase() void Expedition::SaveLockouts(ExpeditionRequest& request) { - m_lockouts = std::move(request).TakeLockouts(); + m_lockouts = request.GetLockouts(); ExpeditionDatabase::InsertLockouts(m_id, m_lockouts); } void Expedition::SaveMembers(ExpeditionRequest& request) { - m_members = std::move(request).TakeMembers(); + m_members = request.GetMembers(); for (const auto& member : m_members) { m_member_id_history.emplace(member.char_id); @@ -1047,8 +1047,7 @@ void Expedition::DzSwapPlayer( if (remove_char_name.empty() || !HasMember(remove_char_name)) { - remove_char_name = FormatName(remove_char_name); - requester->MessageString(Chat::Red, DZSWAP_CANNOT_REMOVE, remove_char_name.c_str()); + requester->MessageString(Chat::Red, DZSWAP_CANNOT_REMOVE, FormatName(remove_char_name).c_str()); return; } @@ -1180,7 +1179,6 @@ void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t re return; } - // cache a re-usable packet for each member auto outapp_member_name = CreateMemberListNamePacket(removed_char_name, true); for (auto it = m_members.begin(); it != m_members.end();) @@ -1212,8 +1210,8 @@ void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t re } LogExpeditionsDetail( - "Processed member [{}] ({}) removal, current zone cache member count: [{}]", - removed_char_name, removed_char_id, m_members.size() + "Processed member [{}] ({}) removal from [{}], cache member count: [{}]", + removed_char_name, removed_char_id, m_id, m_members.size() ); } diff --git a/zone/expedition_request.h b/zone/expedition_request.h index f128cc4c4..fdd71e5e2 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -50,8 +50,8 @@ public: 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); } + std::vector GetMembers() const { return m_members; } + std::unordered_map GetLockouts() const { return m_lockouts; } private: bool ValidateMembers(const std::vector& member_names); From 17be2bf2f77dc1bbb7dabfcc05bbd1fe56f5df03 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 13 Jun 2020 21:28:21 -0400 Subject: [PATCH 071/196] Clear stale pending expedition lockouts Delete pending lockouts of members on expedition creation Delete pending lockouts when all members removed from expedition This fixes an edge case where members could incorrectly be assigned pending lockouts that were never cleared from the database (from a server crash or other situation) after entering another dz. --- zone/dynamiczone.cpp | 2 +- zone/dynamiczone.h | 2 +- zone/expedition.cpp | 6 ++++++ zone/expedition_database.cpp | 23 +++++++++++++++++++++++ zone/expedition_database.h | 1 + 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 754e7c31a..362f70140 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -427,7 +427,7 @@ void DynamicZone::RemoveAllCharacters(bool enable_removal_timers) database.RemoveClientsFromInstance(GetInstanceID()); } -void DynamicZone::SaveInstanceMembersToDatabase(const std::unordered_set character_ids) +void DynamicZone::SaveInstanceMembersToDatabase(const std::unordered_set& character_ids) { std::string insert_values; for (const auto& character_id : character_ids) diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index 54c58be9f..83063d207 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -75,7 +75,7 @@ public: uint32_t CreateInstance(); void AddCharacter(uint32_t character_id); - void SaveInstanceMembersToDatabase(const std::unordered_set character_ids); + 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); }; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index e6d46e34d..3dfb5403d 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -310,7 +310,9 @@ void Expedition::SaveMembers(ExpeditionRequest& request) { m_member_id_history.emplace(member.char_id); } + ExpeditionDatabase::InsertMembers(m_id, m_members); + ExpeditionDatabase::DeleteAllMembersPendingLockouts(m_members); m_dynamiczone.SaveInstanceMembersToDatabase(m_member_id_history); // all are current members here } @@ -504,10 +506,13 @@ void Expedition::RemoveAllMembers(bool enable_removal_timers) { m_dynamiczone.RemoveAllCharacters(enable_removal_timers); + ExpeditionDatabase::DeleteAllMembersPendingLockouts(m_members); ExpeditionDatabase::UpdateAllMembersRemoved(m_id); SendUpdatesToZoneMembers(true); SendWorldExpeditionUpdate(ServerOP_ExpeditionMembersRemoved); + + m_members.clear(); } bool Expedition::RemoveMember(const std::string& remove_char_name) @@ -1626,6 +1631,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) if (expedition) { expedition->SendUpdatesToZoneMembers(true); + expedition->m_members.clear(); } } break; diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 2e0816d97..2b9ddb658 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -379,6 +379,29 @@ void ExpeditionDatabase::DeletePendingLockouts(uint32_t character_id) database.QueryDatabase(query); } +void ExpeditionDatabase::DeleteAllMembersPendingLockouts(const std::vector& members) +{ + LogExpeditionsDetail("Deleting pending lockouts for [{}] characters", members.size()); + + 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 = TRUE; + ), query_character_ids); + + database.QueryDatabase(query); + } +} + void ExpeditionDatabase::DeleteLockout(uint32_t expedition_id, const std::string& event_name) { LogExpeditionsDetail("Deleting expedition [{}] lockout event [{}]", expedition_id, event_name); diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 470840225..a267e8abd 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -55,6 +55,7 @@ namespace ExpeditionDatabase const std::vector& 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); + void DeleteAllMembersPendingLockouts(const std::vector& members); uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); uint32_t GetExpeditionIDFromInstanceID(uint32_t instance_id); ExpeditionMember GetExpeditionLeader(uint32_t expedition_id); From 006f7bf9e910fdf3c232f9c8b07b7050dec11075 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 13 Jun 2020 22:44:29 -0400 Subject: [PATCH 072/196] Don't remove expedition lockouts from non-members Clients still inside a dz after being removed from an expedition should only ever have lockouts added --- zone/expedition.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 3dfb5403d..040292c82 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1253,7 +1253,7 @@ void Expedition::ProcessLockoutUpdate( // if this is the expedition's dz instance, all clients inside the zone need // to receive added lockouts. this is done on live to avoid exploits where // members leave the expedition but haven't been kicked from zone yet - if (m_dynamiczone.IsCurrentZoneDzInstance()) + if (!remove && m_dynamiczone.IsCurrentZoneDzInstance()) { std::vector non_members; for (const auto& client_iter : entity_list.GetClientList()) @@ -1261,17 +1261,12 @@ void Expedition::ProcessLockoutUpdate( Client* client = client_iter.second; if (client && client->GetExpeditionID() != GetID()) { - non_members.emplace_back(ExpeditionMember{ client->CharacterID(), client->GetName() }); - - if (!remove) { - client->AddExpeditionLockout(lockout); - } else { - client->RemoveExpeditionLockout(m_expedition_name, event_name); - } + non_members.emplace_back(ExpeditionMember{client->CharacterID(), client->GetName()}); + client->AddExpeditionLockout(lockout); } } - if (!remove && !non_members.empty()) // expedition members were already updated in db + if (!non_members.empty()) { ExpeditionDatabase::InsertMembersLockout(non_members, lockout); } From ea0b37b7fccf844c06310cdc1bdf86602255020f Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 14 Jun 2020 08:31:14 -0400 Subject: [PATCH 073/196] Store expedition duration and times as chrono This simplifies comparisons and reduces conversions --- world/expedition.cpp | 14 ++++++-------- world/expedition.h | 4 ++-- zone/dynamiczone.cpp | 18 +++++++++--------- zone/dynamiczone.h | 4 ++-- zone/expedition_lockout_timer.cpp | 12 ++++++------ zone/expedition_lockout_timer.h | 15 +++++++++------ 6 files changed, 34 insertions(+), 33 deletions(-) diff --git a/world/expedition.cpp b/world/expedition.cpp index 1d30fcd67..eecd91559 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -39,10 +39,10 @@ Expedition::Expedition( m_expedition_id(expedition_id), m_dz_instance_id(instance_id), m_dz_zone_id(dz_zone_id), - m_start_time(start_time), + m_start_time(std::chrono::system_clock::from_time_t(start_time)), m_duration(duration) { - m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + m_expire_time = m_start_time + m_duration; } void Expedition::SendZonesExpeditionDeleted() @@ -60,7 +60,7 @@ void Expedition::SendZonesDurationUpdate() auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDzDuration, packsize)); auto packbuf = reinterpret_cast(pack->pBuffer); packbuf->expedition_id = GetID(); - packbuf->new_duration_seconds = m_duration; + packbuf->new_duration_seconds = static_cast(m_duration.count()); zoneserver_list.SendPacket(pack.get()); } @@ -78,12 +78,10 @@ void Expedition::UpdateDzSecondsRemaining(uint32_t seconds_remaining) ); // preserve original start time and adjust duration instead - auto new_expire_time = now + update_time; - auto new_duration = std::chrono::system_clock::to_time_t(new_expire_time) - m_start_time; - m_duration = static_cast(new_duration); - m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + m_expire_time = now + update_time; + m_duration = std::chrono::duration_cast(m_expire_time - m_start_time); - ExpeditionDatabase::UpdateDzDuration(GetInstanceID(), m_duration); + ExpeditionDatabase::UpdateDzDuration(GetInstanceID(), static_cast(m_duration.count())); // update zone level caches and update the actual dz instance's timer SendZonesDurationUpdate(); diff --git a/world/expedition.h b/world/expedition.h index a933e5d9d..8f14a334d 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -93,10 +93,10 @@ private: uint32_t m_expedition_id = 0; uint32_t m_dz_instance_id = 0; uint32_t m_dz_zone_id = 0; - uint32_t m_start_time = 0; - uint32_t m_duration = 0; bool m_pending_delete = false; std::unordered_set m_member_ids; + std::chrono::seconds m_duration; + std::chrono::time_point m_start_time; std::chrono::time_point m_expire_time; }; diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 362f70140..65f92e321 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -119,14 +119,15 @@ uint32_t DynamicZone::CreateInstance() return 0; } - auto start_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + m_start_time = std::chrono::system_clock::now(); + auto start_time = std::chrono::system_clock::to_time_t(m_start_time); 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); + ), instance_id, m_zone_id, m_version, start_time, m_duration.count()); auto results = database.QueryDatabase(query); if (!results.Success()) @@ -136,9 +137,8 @@ uint32_t DynamicZone::CreateInstance() } 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); + m_expire_time = m_start_time + m_duration; return m_instance_id; } @@ -178,11 +178,11 @@ void DynamicZone::LoadDatabaseResult(MySQLRequestRow& row) m_instance_id = strtoul(row[0], nullptr, 10); m_zone_id = strtoul(row[1], nullptr, 10); m_version = strtoul(row[2], nullptr, 10); - m_start_time = strtoul(row[3], nullptr, 10); - m_duration = strtoul(row[4], nullptr, 10); + m_start_time = std::chrono::system_clock::from_time_t(strtoul(row[3], nullptr, 10)); + m_duration = std::chrono::seconds(strtoul(row[4], nullptr, 10)); + m_expire_time = m_start_time + m_duration; m_never_expires = (strtoul(row[5], nullptr, 10) != 0); m_type = static_cast(strtoul(row[6], nullptr, 10)); - m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); m_compass.zone_id = strtoul(row[7], nullptr, 10); m_compass.x = strtof(row[8], nullptr); m_compass.y = strtof(row[9], nullptr); @@ -530,8 +530,8 @@ uint32_t DynamicZone::GetSecondsRemaining() const void DynamicZone::SetUpdatedDuration(uint32_t new_duration) { // preserves original start time, just modifies duration and expire time - m_duration = new_duration; - m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + m_duration = std::chrono::seconds(new_duration); + m_expire_time = m_start_time + m_duration; if (zone && IsCurrentZoneDzInstance()) { diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index 83063d207..6951fe517 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -110,14 +110,14 @@ private: 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::seconds m_duration; + std::chrono::time_point m_start_time; std::chrono::time_point m_expire_time; }; diff --git a/zone/expedition_lockout_timer.cpp b/zone/expedition_lockout_timer.cpp index e7eb8ea76..25743df8d 100644 --- a/zone/expedition_lockout_timer.cpp +++ b/zone/expedition_lockout_timer.cpp @@ -26,11 +26,12 @@ 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 + 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_expire_time(std::chrono::system_clock::from_time_t(expire_time)), m_duration(duration), m_is_inherited(inherited) { @@ -43,11 +44,10 @@ ExpeditionLockoutTimer::ExpeditionLockoutTimer( 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) + if (m_expire_time > now) { - auto time_remaining = std::chrono::duration_cast(expire_time - now).count(); - return static_cast(time_remaining); + auto remaining = m_expire_time - now; + return static_cast(std::chrono::duration_cast(remaining).count()); } return 0; } diff --git a/zone/expedition_lockout_timer.h b/zone/expedition_lockout_timer.h index 45155a111..abd455ff5 100644 --- a/zone/expedition_lockout_timer.h +++ b/zone/expedition_lockout_timer.h @@ -21,6 +21,7 @@ #ifndef EXPEDITION_LOCKOUT_TIMER_H #define EXPEDITION_LOCKOUT_TIMER_H +#include #include extern const char* const DZ_REPLAY_TIMER_NAME; @@ -29,7 +30,9 @@ class ExpeditionLockoutTimer { public: ExpeditionLockoutTimer() {} - ExpeditionLockoutTimer(std::string expedition_name, std::string event_name, uint64_t expire_time, uint32_t duration, bool inherited = false); + ExpeditionLockoutTimer( + std::string expedition_name, std::string event_name, + uint64_t expire_time, uint32_t duration, bool inherited = false); struct DaysHoursMinutes { @@ -38,13 +41,13 @@ public: std::string mins; }; - uint32_t GetDuration() const { return m_duration; } - uint64_t GetExpireTime() const { return m_expire_time; } + uint32_t GetDuration() const { return static_cast(m_duration.count()); } + uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(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 SetExpireTime(uint64_t expire_time) { m_expire_time = std::chrono::system_clock::from_time_t(expire_time); } void SetInherited(bool is_inherited) { m_is_inherited = is_inherited; } bool IsExpired() const { return GetSecondsRemaining() == 0; } bool IsInherited() const { return m_is_inherited; } @@ -55,10 +58,10 @@ public: 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; + std::chrono::seconds m_duration; + std::chrono::time_point m_expire_time; }; #endif From 4af5f79328a0e2113b22420d8e3de8373983df80 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 14 Jun 2020 17:16:03 -0400 Subject: [PATCH 074/196] Remove logging of expedition query failures This is redundant with sql error logging. Logging the operations is enough to determine the source of any errors --- world/expedition.cpp | 32 +++++++---------- zone/dynamiczone.cpp | 8 ++--- zone/expedition_database.cpp | 69 ++++++------------------------------ 3 files changed, 26 insertions(+), 83 deletions(-) diff --git a/world/expedition.cpp b/world/expedition.cpp index eecd91559..74cea7c36 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -205,6 +205,8 @@ void ExpeditionCache::Process() void ExpeditionDatabase::PurgeExpiredExpeditions() { + LogExpeditionsDetail("Purging expired expeditions"); + std::string query = SQL( DELETE expedition FROM expedition_details expedition LEFT JOIN instance_list ON expedition.instance_id = instance_list.id @@ -220,29 +222,25 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); ); - auto results = database.QueryDatabase(query); - if (!results.Success()) - { - LogExpeditions("Failed to purge expired and empty expeditions"); - } + database.QueryDatabase(query); } void ExpeditionDatabase::PurgeExpiredCharacterLockouts() { + LogExpeditionsDetail("Purging expired lockouts"); + 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"); - } + database.QueryDatabase(query); } std::vector ExpeditionDatabase::LoadExpeditions() { + LogExpeditionsDetail("Loading expeditions for world cache"); + std::vector expeditions; std::string query = SQL( @@ -262,11 +260,7 @@ std::vector ExpeditionDatabase::LoadExpeditions() ); auto results = database.QueryDatabase(query); - if (!results.Success()) - { - LogExpeditions("Failed to load expeditions for world cache"); - } - else + if (results.Success()) { uint32_t last_expedition_id = 0; @@ -297,6 +291,8 @@ std::vector ExpeditionDatabase::LoadExpeditions() Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) { + LogExpeditions("Loading expedition [{}] for world cache", expedition_id); + Expedition expedition; std::string query = fmt::format(SQL( @@ -316,11 +312,7 @@ Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) ), expedition_id); auto results = database.QueryDatabase(query); - if (!results.Success()) - { - LogExpeditions("Failed to load expedition [{}] for world cache", expedition_id); - } - else + if (results.Success()) { bool created = false; for (auto row = results.begin(); row != results.end(); ++row) diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 65f92e321..afa5fc7b7 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -429,6 +429,8 @@ void DynamicZone::RemoveAllCharacters(bool enable_removal_timers) void DynamicZone::SaveInstanceMembersToDatabase(const std::unordered_set& character_ids) { + LogDynamicZonesDetail("Saving [{}] instance members to database", character_ids.size()); + std::string insert_values; for (const auto& character_id : character_ids) { @@ -443,11 +445,7 @@ void DynamicZone::SaveInstanceMembersToDatabase(const std::unordered_set Date: Mon, 15 Jun 2020 18:02:07 -0400 Subject: [PATCH 075/196] Don't allow expedition re-invite inside dz Live requires characters that quit an expedition to zone out before being re-added. This is probably to avoid exploiting max player requirements by constantly swapping players in and out --- zone/expedition.cpp | 8 +++++++- zone/string_ids.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 040292c82..d76737ad2 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -673,13 +673,19 @@ void Expedition::SendLeaderMessage( bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping) { - if (!add_client) // a null leader_client handled by SendLeaderMessage fallback + if (!add_client) // a null leader_client is handled by SendLeaderMessage fallback { return true; } bool has_conflict = false; + if (m_dynamiczone.IsCurrentZoneDzInstance()) + { + SendLeaderMessage(leader_client, Chat::Red, DZADD_LEAVE_ZONE_FIRST, { add_client->GetName() }); + has_conflict = true; + } + auto expedition_id = add_client->GetExpeditionID(); if (expedition_id) { diff --git a/zone/string_ids.h b/zone/string_ids.h index 40aa13057..88a3f9f3e 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -327,6 +327,7 @@ #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_LEAVE_ZONE_FIRST 3589 //You can not add %1 since they first need to leave the zone before being allowed back in. #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. From 892556e26d301a7da7151e2547fda8352254b312 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 17 Jun 2020 18:28:49 -0400 Subject: [PATCH 076/196] Fix replay timer assignment to new members Ignore expired state of replay timers when assigning to new members This fixes a regression from a previous change that stopped assigning expired lockouts to new members. Only expired event timers should be ignored for new members. Replay Timers should always be added with a a fresh lockout --- zone/expedition.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index d76737ad2..66a015832 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -810,7 +810,7 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: for (const auto& lockout_iter : m_lockouts) { const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (!lockout.IsInherited() && !lockout.IsExpired() && + if (!lockout.IsInherited() && !add_client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) { // replay timers are optionally added to new members immediately on @@ -823,7 +823,7 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetDuration()); } } - else + else if (!lockout.IsExpired()) { pending_lockouts.emplace_back(lockout); } From f23ca8055f394d52b7659a5e3a1aa88bf7afeebf Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 17 Jun 2020 22:15:46 -0400 Subject: [PATCH 077/196] Remove replay timer argument to CreateExpedition Breaking change to the current API has_replay_timer column removed from expedition_details table This argument is unnecessary and just creates confusion. Expedition replay timers use a hardcoded name precisely for this purpose and those lockouts are already being checked on creation requests. --- utils/sql/git/required/wip_expeditions.sql | 1 - zone/client.cpp | 4 +-- zone/client.h | 2 +- zone/expedition.cpp | 18 ++++++-------- zone/expedition.h | 3 +-- zone/expedition_database.cpp | 9 +++---- zone/expedition_database.h | 3 +-- zone/expedition_request.cpp | 29 +++++++++------------- zone/expedition_request.h | 4 +-- zone/lua_client.cpp | 20 +++------------ zone/lua_client.h | 3 +-- 11 files changed, 34 insertions(+), 62 deletions(-) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 035330f79..6e2174753 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -5,7 +5,6 @@ CREATE TABLE `expedition_details` ( `leader_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, `min_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, `max_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, - `has_replay_timer` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, `add_replay_on_join` TINYINT(3) UNSIGNED NOT NULL DEFAULT 1, `is_locked` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), diff --git a/zone/client.cpp b/zone/client.cpp index 9dd8c9969..57063efff 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9593,10 +9593,10 @@ Expedition* Client::CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest Expedition* Client::CreateExpedition( std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, - uint32 min_players, uint32 max_players, bool has_replay_timer, bool disable_messages) + uint32 min_players, uint32 max_players, bool disable_messages) { DynamicZone dz_instance{ zone_name, version, duration, DynamicZoneType::Expedition }; - ExpeditionRequest request{ expedition_name, min_players, max_players, has_replay_timer, disable_messages }; + ExpeditionRequest request{ expedition_name, min_players, max_players, disable_messages }; return Expedition::TryCreate(this, dz_instance, request); } diff --git a/zone/client.h b/zone/client.h index 09b2f8020..239ad72c8 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1122,7 +1122,7 @@ public: Expedition* CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request); 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, bool disable_messages = false); + uint32 min_players, uint32 max_players, bool disable_messages = false); Expedition* GetExpedition() const; uint32 GetExpeditionID() const { return m_expedition_id; } const ExpeditionLockoutTimer* GetExpeditionLockout( diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 66a015832..09fc6ab89 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -47,15 +47,14 @@ const uint32_t Expedition::EVENT_TIMER_ID = 1; Expedition::Expedition( 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 + const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players ) : m_id(id), m_dynamiczone(dynamic_zone), m_expedition_name(expedition_name), m_leader(leader), m_min_players(min_players), - m_max_players(max_players), - m_has_replay_timer(replay_timer) + m_max_players(max_players) { } @@ -98,8 +97,7 @@ Expedition* Expedition::TryCreate( request.GetExpeditionName(), leader.char_id, request.GetMinPlayers(), - request.GetMaxPlayers(), - request.HasReplayTimer() + request.GetMaxPlayers() ); if (expedition_id) @@ -112,8 +110,7 @@ Expedition* Expedition::TryCreate( request.GetExpeditionName(), leader, request.GetMinPlayers(), - request.GetMaxPlayers(), - request.HasReplayTimer() + request.GetMaxPlayers() )); LogExpeditions( @@ -189,8 +186,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) row[col::expedition_name], // expedition name ExpeditionMember{leader_id, row[col::leader_name]}, // expedition leader id, name strtoul(row[col::min_players], nullptr, 10), // min_players - strtoul(row[col::max_players], nullptr, 10), // max_players - (strtoul(row[col::has_replay_timer], nullptr, 10) != 0) // has_replay_timer + strtoul(row[col::max_players], nullptr, 10) // max_players )); bool add_replay_on_join = (strtoul(row[col::add_replay_on_join], nullptr, 10) != 0); @@ -697,7 +693,7 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, // client with a replay lockout is allowed only if they were a previous member auto member_iter = m_member_id_history.find(add_client->CharacterID()); bool was_member = (member_iter != m_member_id_history.end()); - if (!was_member && m_has_replay_timer) + if (!was_member) { auto replay_lockout = add_client->GetExpeditionLockout(m_expedition_name, DZ_REPLAY_TIMER_NAME); if (replay_lockout) @@ -815,7 +811,7 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: { // replay timers are optionally added to new members immediately on // join with a fresh expire time using the original duration. - if (m_has_replay_timer && lockout.IsReplayTimer()) + if (lockout.IsReplayTimer()) { if (m_add_replay_on_join) { diff --git a/zone/expedition.h b/zone/expedition.h index 0e58e18a5..14be738bb 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -68,7 +68,7 @@ public: Expedition() = delete; 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); + uint32_t min_players, uint32_t max_players); static Expedition* TryCreate(Client* requester, DynamicZone& dynamiczone, ExpeditionRequest& request); @@ -178,7 +178,6 @@ private: uint32_t m_min_players = 0; uint32_t m_max_players = 0; bool m_is_locked = false; - bool m_has_replay_timer = false; bool m_add_replay_on_join = true; std::string m_expedition_name; DynamicZone m_dynamiczone { DynamicZoneType::Expedition }; diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index cce037a90..06366fc8f 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -27,16 +27,16 @@ uint32_t ExpeditionDatabase::InsertExpedition( 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) + uint32_t min_players, uint32_t max_players) { LogExpeditionsDetail("Inserting new expedition [{}] leader [{}]", expedition_name, leader_id); std::string query = fmt::format(SQL( INSERT INTO expedition_details - (instance_id, expedition_name, leader_id, min_players, max_players, has_replay_timer) + (instance_id, expedition_name, leader_id, min_players, max_players) VALUES - ({}, '{}', {}, {}, {}, {}); - ), instance_id, expedition_name, leader_id, min_players, max_players, has_replay_lockout); + ({}, '{}', {}, {}, {}); + ), instance_id, expedition_name, leader_id, min_players, max_players); auto results = database.QueryDatabase(query); if (!results.Success()) @@ -58,7 +58,6 @@ std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() expedition_details.leader_id, expedition_details.min_players, expedition_details.max_players, - expedition_details.has_replay_timer, expedition_details.add_replay_on_join, expedition_details.is_locked, character_data.name leader_name, diff --git a/zone/expedition_database.h b/zone/expedition_database.h index a267e8abd..ce7edfcdc 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -37,7 +37,7 @@ namespace ExpeditionDatabase { uint32_t InsertExpedition( 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); + uint32_t min_players, uint32_t max_players); std::string LoadExpeditionsSelectQuery(); MySQLRequestResult LoadExpedition(uint32_t expedition_id); MySQLRequestResult LoadAllExpeditions(); @@ -84,7 +84,6 @@ namespace LoadExpeditionColumns leader_id, min_players, max_players, - has_replay_timer, add_replay_on_join, is_locked, leader_name, diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 1f9375153..63b14c131 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -38,13 +38,11 @@ struct ExpeditionRequestConflict }; ExpeditionRequest::ExpeditionRequest( - std::string expedition_name, uint32_t min_players, uint32_t max_players, - bool has_replay_timer, bool disable_messages + std::string expedition_name, uint32_t min_players, uint32_t max_players, bool disable_messages ) : m_expedition_name(expedition_name), m_min_players(min_players), m_max_players(max_players), - m_has_replay_timer(has_replay_timer), m_disable_messages(disable_messages) { } @@ -183,7 +181,7 @@ bool ExpeditionRequest::LoadLeaderLockouts() m_lockouts.emplace(lockout.GetEventName(), lockout); // on live if leader has a replay lockout it never bothers checking for event conflicts - if (m_check_event_lockouts && m_has_replay_timer && lockout.IsReplayTimer()) + if (m_check_event_lockouts && lockout.IsReplayTimer()) { m_check_event_lockouts = false; } @@ -260,23 +258,20 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& character_id, lockout.GetEventName(), lockout.GetSecondsRemaining(), leeway_seconds ); } - else + else if (lockout.IsReplayTimer()) { // replay timer conflict messages always show up before event conflicts - if (/*m_has_replay_timer && */lockout.IsReplayTimer()) + has_conflicts = true; + SendLeaderMemberReplayLockout(character_name, lockout, is_solo); + } + else if (m_check_event_lockouts && character_id != m_leader_id) + { + if (m_lockouts.find(lockout.GetEventName()) == m_lockouts.end()) { + // leader doesn't have this lockout. queue instead of messaging + // now so message comes after any replay lockout messages has_conflicts = true; - SendLeaderMemberReplayLockout(character_name, lockout, is_solo); - } - else if (m_check_event_lockouts && character_id != m_leader_id) - { - if (m_lockouts.find(lockout.GetEventName()) == m_lockouts.end()) - { - // leader doesn't have this lockout. queue instead of messaging - // now so message comes after any replay lockout messages - has_conflicts = true; - member_lockout_conflicts.emplace_back(ExpeditionRequestConflict{character_name, lockout}); - } + member_lockout_conflicts.emplace_back(ExpeditionRequestConflict{character_name, lockout}); } } } diff --git a/zone/expedition_request.h b/zone/expedition_request.h index fdd71e5e2..db47ba6ca 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -39,7 +39,7 @@ class ExpeditionRequest public: ExpeditionRequest( std::string expedition_name, uint32_t min_players, uint32_t max_players, - bool has_replay_timer, bool disable_messages = false); + bool disable_messages = false); bool Validate(Client* requester); @@ -49,7 +49,6 @@ public: 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 GetMembers() const { return m_members; } std::unordered_map GetLockouts() const { return m_lockouts; } @@ -73,7 +72,6 @@ private: uint32_t m_max_players = 0; bool m_check_event_lockouts = true; bool m_disable_messages = false; - bool m_has_replay_timer = false; std::string m_expedition_name; std::string m_leader_name; std::vector m_members; diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index dfd9104ff..f4211b84f 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1701,20 +1701,14 @@ Lua_Expedition Lua_Client::CreateExpedition(luabind::object dz_info, luabind::ob std::string expedition_name = luabind::object_cast(expedition_info[1]); uint32_t min_players = luabind::object_cast(expedition_info[2]); uint32_t max_players = luabind::object_cast(expedition_info[3]); - bool has_replay_timer = false; bool disable_messages = false; if (luabind::type(expedition_info[4]) == LUA_TBOOLEAN) { - has_replay_timer = luabind::object_cast(expedition_info[4]); + disable_messages = luabind::object_cast(expedition_info[4]); } - if (luabind::type(expedition_info[5]) == LUA_TBOOLEAN) - { - disable_messages = luabind::object_cast(expedition_info[5]); - } - - ExpeditionRequest request{ expedition_name, min_players, max_players, has_replay_timer, disable_messages }; + ExpeditionRequest request{ expedition_name, min_players, max_players, disable_messages }; return self->CreateExpedition(dz, request); } @@ -1724,14 +1718,9 @@ Lua_Expedition Lua_Client::CreateExpedition(std::string zone_name, uint32 versio return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, 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, 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 disable_messages) { Lua_Safe_Call_Class(Lua_Expedition); - return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players, 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, bool disable_messages) { - Lua_Safe_Call_Class(Lua_Expedition); - return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players, has_replay_timer, disable_messages); + return self->CreateExpedition(zone_name, version, duration, expedition_name, min_players, max_players, disable_messages); } Lua_Expedition Lua_Client::GetExpedition() { @@ -2124,7 +2113,6 @@ luabind::scope lua_register_client() { .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(luabind::object, luabind::object))&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("CreateExpedition", (Lua_Expedition(Lua_Client::*)(std::string, uint32, uint32, std::string, uint32, uint32, bool, 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) diff --git a/zone/lua_client.h b/zone/lua_client.h index c2f80a6fa..bdcd04cc5 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -341,8 +341,7 @@ public: Lua_Expedition CreateExpedition(luabind::object dz_info, luabind::object expedition_info); 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 CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool has_replay_timer, bool disable_messages); + Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages); Lua_Expedition GetExpedition(); luabind::object GetExpeditionLockouts(lua_State* L); luabind::object GetExpeditionLockouts(lua_State* L, std::string expedition_name); From 6a7980ec7588c986c868de66847d19b7c86ed657 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 7 Jun 2020 19:14:46 -0400 Subject: [PATCH 078/196] Assign expeditions a uuid --- utils/sql/git/required/wip_expeditions.sql | 3 ++- zone/expedition.cpp | 13 ++++++++++--- zone/expedition.h | 6 ++++-- zone/expedition_database.cpp | 15 +++++++++------ zone/expedition_database.h | 5 +++-- zone/lua_expedition.cpp | 6 ++++++ zone/lua_expedition.h | 7 ++++--- 7 files changed, 38 insertions(+), 17 deletions(-) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 6e2174753..463e5d59e 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -1,5 +1,6 @@ CREATE TABLE `expedition_details` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `uuid` VARCHAR(36) NOT NULL, `instance_id` INT(10) NULL DEFAULT NULL, `expedition_name` VARCHAR(128) NOT NULL, `leader_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, @@ -11,7 +12,7 @@ CREATE TABLE `expedition_details` ( UNIQUE INDEX `instance_id` (`instance_id`), CONSTRAINT `FK_expedition_details_instance_list` FOREIGN KEY (`instance_id`) REFERENCES `instance_list` (`id`) ON DELETE SET NULL ) -COLLATE='latin1_swedish_ci' +COLLATE='utf8mb4_general_ci' ENGINE=InnoDB ; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 09fc6ab89..ee0c1339c 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -29,6 +29,7 @@ #include "worldserver.h" #include "zonedb.h" #include "../common/eqemu_logsys.h" +#include "../common/util/uuid.h" extern WorldServer worldserver; extern Zone* zone; @@ -46,10 +47,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, const DynamicZone& dynamic_zone, std::string expedition_name, + uint32_t id, const std::string& uuid, const DynamicZone& dynamic_zone, std::string expedition_name, const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players ) : m_id(id), + m_uuid(uuid), m_dynamiczone(dynamic_zone), m_expedition_name(expedition_name), m_leader(leader), @@ -89,13 +91,14 @@ Expedition* Expedition::TryCreate( return nullptr; } - ExpeditionMember leader{ request.GetLeaderID(), request.GetLeaderName() }; + std::string expedition_uuid = EQ::Util::UUID::Generate().ToString(); // unique expedition ids are created from database via auto-increment column auto expedition_id = ExpeditionDatabase::InsertExpedition( + expedition_uuid, dynamiczone.GetInstanceID(), request.GetExpeditionName(), - leader.char_id, + request.GetLeaderID(), request.GetMinPlayers(), request.GetMaxPlayers() ); @@ -104,8 +107,11 @@ Expedition* Expedition::TryCreate( { dynamiczone.SaveToDatabase(); + ExpeditionMember leader{request.GetLeaderID(), request.GetLeaderName()}; + auto expedition = std::unique_ptr(new Expedition( expedition_id, + expedition_uuid, dynamiczone, request.GetExpeditionName(), leader, @@ -182,6 +188,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) std::unique_ptr expedition = std::unique_ptr(new Expedition( expedition_id, + row[col::uuid], // expedition uuid DynamicZone{instance_id}, row[col::expedition_name], // expedition name ExpeditionMember{leader_id, row[col::leader_name]}, // expedition leader id, name diff --git a/zone/expedition.h b/zone/expedition.h index 14be738bb..cc674a5cd 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -67,8 +67,8 @@ class Expedition public: Expedition() = delete; Expedition( - uint32_t id, const DynamicZone& dz, std::string expedition_name, const ExpeditionMember& leader, - uint32_t min_players, uint32_t max_players); + uint32_t id, const std::string& uuid, const DynamicZone& dz, std::string expedition_name, + const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players); static Expedition* TryCreate(Client* requester, DynamicZone& dynamiczone, ExpeditionRequest& request); @@ -90,6 +90,7 @@ public: 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::string& GetUUID() const { return m_uuid; } const std::unordered_map& GetLockouts() const { return m_lockouts; } const std::vector& GetMembers() const { return m_members; } @@ -179,6 +180,7 @@ private: uint32_t m_max_players = 0; bool m_is_locked = false; bool m_add_replay_on_join = true; + std::string m_uuid; std::string m_expedition_name; DynamicZone m_dynamiczone { DynamicZoneType::Expedition }; ExpeditionMember m_leader; diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 06366fc8f..5ebf33b67 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -26,17 +26,19 @@ #include uint32_t ExpeditionDatabase::InsertExpedition( - uint32_t instance_id, const std::string& expedition_name, uint32_t leader_id, - uint32_t min_players, uint32_t max_players) + const std::string& uuid, uint32_t instance_id, const std::string& expedition_name, + uint32_t leader_id, uint32_t min_players, uint32_t max_players) { - LogExpeditionsDetail("Inserting new expedition [{}] leader [{}]", expedition_name, leader_id); + LogExpeditionsDetail( + "Inserting new expedition [{}] leader [{}] uuid [{}]", expedition_name, leader_id, uuid + ); std::string query = fmt::format(SQL( INSERT INTO expedition_details - (instance_id, expedition_name, leader_id, min_players, max_players) + (uuid, instance_id, expedition_name, leader_id, min_players, max_players) VALUES - ({}, '{}', {}, {}, {}); - ), instance_id, expedition_name, leader_id, min_players, max_players); + ('{}', {}, '{}', {}, {}, {}); + ), uuid, instance_id, expedition_name, leader_id, min_players, max_players); auto results = database.QueryDatabase(query); if (!results.Success()) @@ -53,6 +55,7 @@ std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() return std::string(SQL( SELECT expedition_details.id, + expedition_details.uuid, expedition_details.instance_id, expedition_details.expedition_name, expedition_details.leader_id, diff --git a/zone/expedition_database.h b/zone/expedition_database.h index ce7edfcdc..14bc34f1c 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -36,8 +36,8 @@ class MySQLRequestResult; namespace ExpeditionDatabase { uint32_t InsertExpedition( - uint32_t instance_id, const std::string& expedition_name, uint32_t leader_id, - uint32_t min_players, uint32_t max_players); + const std::string& uuid, uint32_t instance_id, const std::string& expedition_name, + uint32_t leader_id, uint32_t min_players, uint32_t max_players); std::string LoadExpeditionsSelectQuery(); MySQLRequestResult LoadExpedition(uint32_t expedition_id); MySQLRequestResult LoadAllExpeditions(); @@ -79,6 +79,7 @@ namespace LoadExpeditionColumns enum eLoadExpeditionColumns { id = 0, + uuid, instance_id, expedition_name, leader_id, diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index f33718568..c07379410 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -95,6 +95,11 @@ int Lua_Expedition::GetSecondsRemaining() { return self->GetDynamicZone().GetSecondsRemaining(); } +std::string Lua_Expedition::GetUUID() { + Lua_Safe_Call_String(); + return self->GetUUID(); +} + int Lua_Expedition::GetZoneID() { Lua_Safe_Call_Int(); return self->GetDynamicZone().GetZoneID(); @@ -170,6 +175,7 @@ luabind::scope lua_register_expedition() { .def("GetMembers", &Lua_Expedition::GetMembers) .def("GetName", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetName) .def("GetSecondsRemaining", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetSecondsRemaining) + .def("GetUUID", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetUUID) .def("GetZoneID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetZoneID) .def("HasLockout", (bool(Lua_Expedition::*)(std::string))&Lua_Expedition::HasLockout) .def("HasReplayLockout", (bool(Lua_Expedition::*)(void))&Lua_Expedition::HasReplayLockout) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 3bc9893e6..09d09be97 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -57,21 +57,22 @@ public: uint32_t GetID(); int GetInstanceID(); std::string GetLeaderName(); + luabind::object GetLockouts(lua_State* L); uint32_t GetMemberCount(); luabind::object GetMembers(lua_State* L); std::string GetName(); int GetSecondsRemaining(); + std::string GetUUID(); int GetZoneID(); - luabind::object GetLockouts(lua_State* L); bool HasLockout(std::string event_name); bool HasReplayLockout(); void RemoveCompass(); void RemoveLockout(std::string event_name); - void SetCompass(uint32 zone_id, float x, float y, float z); + void SetCompass(uint32_t zone_id, float x, float y, float z); void SetCompass(std::string zone_name, float x, float y, float z); void SetLocked(bool lock_expedition); void SetReplayLockoutOnMemberJoin(bool enable); - void SetSafeReturn(uint32 zone_id, float x, float y, float z, float heading); + void SetSafeReturn(uint32_t 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); }; From fa21d835d9bb643f595789b262929a81c9b0bf3d Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 10 Jun 2020 23:00:34 -0400 Subject: [PATCH 079/196] Store lockouts with source expedition uuid Add Client::GetLockoutExpeditionUUID quest api Refactor lockout update methods to take ExpeditionLockoutTimer parameter Fix updating expedition lockout cache for multiple AddLockout calls Fix updating lockout duration when replacing a lockout in database Replace lockout timer inherited flags with expedition uuid comparisons Remove is_inherited column from expedition_lockouts table --- utils/sql/git/required/wip_expeditions.sql | 4 +- zone/client.cpp | 35 ++++--- zone/client.h | 3 +- zone/expedition.cpp | 55 +++++----- zone/expedition.h | 5 +- zone/expedition_database.cpp | 113 ++++++++++++++------- zone/expedition_database.h | 2 +- zone/expedition_lockout_timer.cpp | 8 +- zone/expedition_lockout_timer.h | 16 +-- zone/expedition_request.cpp | 10 +- zone/lua_client.cpp | 18 ++++ zone/lua_client.h | 2 + 12 files changed, 163 insertions(+), 108 deletions(-) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 463e5d59e..e69fca8d6 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -22,7 +22,7 @@ CREATE TABLE `expedition_lockouts` ( `event_name` VARCHAR(256) NOT NULL, `expire_time` DATETIME NOT NULL DEFAULT current_timestamp(), `duration` INT(10) UNSIGNED NOT NULL DEFAULT 0, - `is_inherited` TINYINT(4) UNSIGNED NOT NULL DEFAULT 0, + `from_expedition_uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `expedition_id_event_name` (`expedition_id`, `event_name`), CONSTRAINT `FK_expedition_lockouts_expedition_details` FOREIGN KEY (`expedition_id`) REFERENCES `expedition_details` (`id`) ON DELETE CASCADE @@ -49,6 +49,7 @@ CREATE TABLE `expedition_character_lockouts` ( `character_id` INT(10) UNSIGNED NOT NULL, `expire_time` DATETIME NOT NULL DEFAULT current_timestamp(), `duration` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `from_expedition_uuid` VARCHAR(36) NOT NULL, `expedition_name` VARCHAR(128) NOT NULL, `event_name` VARCHAR(256) NOT NULL, `is_pending` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, @@ -57,5 +58,4 @@ CREATE TABLE `expedition_character_lockouts` ( ) COLLATE='latin1_swedish_ci' ENGINE=InnoDB -ROW_FORMAT=DYNAMIC ; diff --git a/zone/client.cpp b/zone/client.cpp index 57063efff..b502c4381 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -39,6 +39,7 @@ extern volatile bool RunLoops; #include "../common/string_util.h" #include "../common/data_verification.h" #include "../common/profanity_manager.h" +#include "../common/util/uuid.h" #include "data_bucket.h" #include "expedition.h" #include "expedition_database.h" @@ -9613,22 +9614,19 @@ Expedition* Client::GetExpedition() const return nullptr; } -void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db, bool update_client) +void Client::AddExpeditionLockout( + const ExpeditionLockoutTimer& lockout, bool update_db, bool update_client) { // todo: support for account based lockouts like live AoC expeditions - auto it = std::find_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), + + // if client already has this lockout, we're replacing it with the new one + m_expedition_lockouts.erase(std::remove_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), [&](const ExpeditionLockoutTimer& existing_lockout) { return existing_lockout.IsSameLockout(lockout); - }); + } + ), m_expedition_lockouts.end()); - if (it != m_expedition_lockouts.end()) - { - it->SetExpireTime(lockout.GetExpireTime()); - } - else - { - m_expedition_lockouts.emplace_back(lockout); - } + m_expedition_lockouts.emplace_back(lockout); if (update_db) // for quest api { @@ -9642,11 +9640,14 @@ void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool up } void Client::AddNewExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, uint32_t seconds) + const std::string& expedition_name, const std::string& event_name, uint32_t seconds, std::string uuid) { - auto expire_at = std::chrono::system_clock::now() + std::chrono::seconds(seconds); - auto expire_time = static_cast(std::chrono::system_clock::to_time_t(expire_at)); - ExpeditionLockoutTimer lockout{ expedition_name, event_name, expire_time, seconds }; + if (uuid.empty()) + { + uuid = EQ::Util::UUID::Generate().ToString(); + } + ExpeditionLockoutTimer lockout{uuid, expedition_name, event_name, 0, seconds}; + lockout.Reset(); // sets expire time AddExpeditionLockout(lockout, true); } @@ -9794,7 +9795,9 @@ void Client::DzListTimers() 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(), + time_remaining.days.c_str(), + time_remaining.hours.c_str(), + time_remaining.mins.c_str(), lockout.GetExpeditionName().c_str() ); } diff --git a/zone/client.h b/zone/client.h index 239ad72c8..f55b9502f 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1118,7 +1118,8 @@ public: uint32_t string_id, const std::initializer_list& parameters = {}); void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false, bool update_client = true); - void AddNewExpeditionLockout(const std::string& expedition_name, const std::string& event_name, uint32_t duration); + void AddNewExpeditionLockout( + const std::string& expedition_name, const std::string& event_name, uint32_t duration, std::string uuid = {}); Expedition* CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request); Expedition* CreateExpedition( std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, diff --git a/zone/expedition.cpp b/zone/expedition.cpp index ee0c1339c..1f15d18e7 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -443,17 +443,15 @@ void Expedition::AddReplayLockout(uint32_t seconds) void Expedition::AddLockout(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(std::chrono::system_clock::to_time_t(expire_at)); - - // both expedition and current members get the lockout data, expirations updated on duplicates - ExpeditionLockoutTimer lockout{ m_expedition_name, event_name, expire_time, seconds }; + // any current lockouts for the event are updated with new expiration time + ExpeditionLockoutTimer lockout{m_uuid, m_expedition_name, event_name, 0, seconds}; + lockout.Reset(); // sets expire time ExpeditionDatabase::InsertLockout(m_id, lockout); ExpeditionDatabase::InsertMembersLockout(m_members, lockout); - ProcessLockoutUpdate(event_name, expire_time, seconds, false); - SendWorldLockoutUpdate(event_name, expire_time, seconds); + ProcessLockoutUpdate(lockout, false); + SendWorldLockoutUpdate(lockout, false); } void Expedition::RemoveLockout(const std::string& event_name) @@ -461,13 +459,9 @@ void Expedition::RemoveLockout(const std::string& event_name) ExpeditionDatabase::DeleteLockout(m_id, event_name); ExpeditionDatabase::DeleteMembersLockout(m_members, m_expedition_name, event_name); - ProcessLockoutUpdate(event_name, 0, 0, true); - SendWorldLockoutUpdate(event_name, 0, 0, true); -} - -void Expedition::AddInternalLockout(ExpeditionLockoutTimer&& lockout_timer) -{ - m_lockouts.emplace(lockout_timer.GetEventName(), std::move(lockout_timer)); + ExpeditionLockoutTimer lockout{m_uuid, m_expedition_name, event_name, 0, 0}; + ProcessLockoutUpdate(lockout, true); + SendWorldLockoutUpdate(lockout, true); } void Expedition::AddInternalMember( @@ -644,7 +638,7 @@ void Expedition::SendClientExpeditionInvite( { // live doesn't issue a warning for the dz's replay timer const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (!lockout.IsInherited() && !lockout.IsExpired() && !lockout.IsReplayTimer() && + if (!lockout.IsReplayTimer() && !lockout.IsExpired() && lockout.IsFromExpedition(m_uuid) && !client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) { if (!warned) @@ -813,7 +807,7 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: for (const auto& lockout_iter : m_lockouts) { const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (!lockout.IsInherited() && + if (lockout.IsFromExpedition(m_uuid) && !add_client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) { // replay timers are optionally added to new members immediately on @@ -822,8 +816,9 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: { if (m_add_replay_on_join) { - add_client->AddNewExpeditionLockout( - lockout.GetExpeditionName(), lockout.GetEventName(), lockout.GetDuration()); + ExpeditionLockoutTimer replay_timer = lockout; + replay_timer.Reset(); + add_client->AddExpeditionLockout(replay_timer, true); } } else if (!lockout.IsExpired()) @@ -1229,18 +1224,15 @@ void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t re ); } -void Expedition::ProcessLockoutUpdate( - const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove) +void Expedition::ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove) { - ExpeditionLockoutTimer lockout{ m_expedition_name, event_name, expire_time, duration }; - if (!remove) { - m_lockouts.emplace(event_name, lockout); + m_lockouts[lockout.GetEventName()] = lockout; } else { - m_lockouts.erase(event_name); + m_lockouts.erase(lockout.GetEventName()); } for (const auto& member : m_members) @@ -1254,7 +1246,7 @@ void Expedition::ProcessLockoutUpdate( } else { - member_client->RemoveExpeditionLockout(m_expedition_name, event_name); + member_client->RemoveExpeditionLockout(m_expedition_name, lockout.GetEventName()); } } } @@ -1462,18 +1454,18 @@ void Expedition::SendWorldLeaderChanged() } void Expedition::SendWorldLockoutUpdate( - const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove) + const ExpeditionLockoutTimer& lockout, bool remove) { uint32_t pack_size = sizeof(ServerExpeditionLockout_Struct); auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionLockout, pack_size)); auto buf = reinterpret_cast(pack->pBuffer); buf->expedition_id = GetID(); - buf->expire_time = expire_time; - buf->duration = duration; + buf->expire_time = lockout.GetExpireTime(); + buf->duration = lockout.GetDuration(); buf->sender_zone_id = zone ? zone->GetZoneID() : 0; buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; buf->remove = remove; - strn0cpy(buf->event_name, event_name.c_str(), sizeof(buf->event_name)); + strn0cpy(buf->event_name, lockout.GetEventName().c_str(), sizeof(buf->event_name)); worldserver.SendPacket(pack.get()); } @@ -1661,7 +1653,10 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition) { - expedition->ProcessLockoutUpdate(buf->event_name, buf->expire_time, buf->duration, buf->remove); + ExpeditionLockoutTimer lockout{ + expedition->GetUUID(), expedition->GetName(), buf->event_name, buf->expire_time, buf->duration + }; + expedition->ProcessLockoutUpdate(lockout, buf->remove); } } break; diff --git a/zone/expedition.h b/zone/expedition.h index cc674a5cd..2dfa46f3c 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -138,13 +138,12 @@ private: static void CacheExpeditions(MySQLRequestResult& results); static void SendWorldGetOnlineMembers(const std::vector>& expedition_character_ids); - void AddInternalLockout(ExpeditionLockoutTimer&& lockout_timer); void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status, bool is_current_member = true); bool ChooseNewLeader(); bool ConfirmLeaderCommand(Client* requester); 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 ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, 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); @@ -157,7 +156,7 @@ private: void SendWorldExpeditionUpdate(uint16_t server_opcode); void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); void SendWorldLeaderChanged(); - void SendWorldLockoutUpdate(const std::string& event_name, uint64_t expire_time, uint32_t duration, bool remove = false); + void SendWorldLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove); 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); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 5ebf33b67..18d952e4a 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -104,6 +104,7 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts(ui auto query = fmt::format(SQL( SELECT + from_expedition_uuid, expedition_name, event_name, UNIX_TIMESTAMP(expire_time), @@ -118,10 +119,11 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts(ui for (auto row = results.begin(); row != results.end(); ++row) { lockouts.emplace_back(ExpeditionLockoutTimer{ - row[0], // expedition_name - row[1], // event_name - strtoull(row[2], nullptr, 10), // expire_time - static_cast(strtoul(row[3], nullptr, 10)) // duration + row[0], // expedition_uuid + row[1], // expedition_name + row[2], // event_name + strtoull(row[3], nullptr, 10), // expire_time + static_cast(strtoul(row[4], nullptr, 10)) // duration }); } } @@ -138,6 +140,7 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts( auto query = fmt::format(SQL( SELECT + from_expedition_uuid, event_name, UNIX_TIMESTAMP(expire_time), duration @@ -155,10 +158,11 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts( for (auto row = results.begin(); row != results.end(); ++row) { lockouts.emplace_back(ExpeditionLockoutTimer{ + row[0], // expedition_uuid expedition_name, - row[0], // event_name - strtoull(row[1], nullptr, 10), // expire_time - static_cast(strtoul(row[2], nullptr, 10)) // duration + row[1], // event_name + strtoull(row[2], nullptr, 10), // expire_time + static_cast(strtoul(row[3], nullptr, 10)) // duration }); } } @@ -178,7 +182,7 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts( fmt::format_to(std::back_inserter(in_expedition_ids_query), "{},", expedition_id); } - // these are loaded into the same container type expedition's use to store lockouts + // these are loaded into the same container type expeditions use to store lockouts std::unordered_map> lockouts; if (!in_expedition_ids_query.empty()) @@ -188,11 +192,11 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts( std::string query = fmt::format(SQL( SELECT expedition_lockouts.expedition_id, + expedition_lockouts.from_expedition_uuid, + expedition_details.expedition_name, expedition_lockouts.event_name, UNIX_TIMESTAMP(expedition_lockouts.expire_time), - expedition_lockouts.duration, - expedition_lockouts.is_inherited, - expedition_details.expedition_name + expedition_lockouts.duration FROM expedition_lockouts INNER JOIN expedition_details ON expedition_lockouts.expedition_id = expedition_details.id WHERE expedition_id IN ({}) @@ -206,12 +210,12 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts( for (auto row = results.begin(); row != results.end(); ++row) { auto expedition_id = strtoul(row[0], nullptr, 10); - lockouts[expedition_id].emplace(row[1], ExpeditionLockoutTimer{ - row[5], // expedition_name - row[1], // event_name - strtoull(row[2], nullptr, 10), // expire_time - static_cast(strtoul(row[3], nullptr, 10)), // original duration - (strtoul(row[4], nullptr, 10) != 0) // is_inherited + lockouts[expedition_id].emplace(row[3], ExpeditionLockoutTimer{ + row[1], // expedition_uuid + row[2], // expedition_name + row[3], // event_name + strtoull(row[4], nullptr, 10), // expire_time + static_cast(strtoul(row[5], nullptr, 10)) // original duration }); } } @@ -223,7 +227,7 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts( MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( const std::vector& character_names, const std::string& expedition_name) { - LogExpeditionsDetail("Loading multiple characters data for [{}] request validation", expedition_name); + LogExpeditionsDetail("Loading multiple characters data for [{}] request", expedition_name); std::string in_character_names_query; for (const auto& character_name : character_names) @@ -243,6 +247,7 @@ MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( character_data.id, character_data.name, member.expedition_id, + lockout.from_expedition_uuid, UNIX_TIMESTAMP(lockout.expire_time), lockout.duration, lockout.event_name @@ -299,7 +304,9 @@ void ExpeditionDatabase::DeleteAllCharacterLockouts( 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); + LogExpeditionsDetail( + "Deleting character [{}] lockout: [{}]:[{}]", character_id, expedition_name, event_name + ); auto query = fmt::format(SQL( DELETE FROM expedition_character_lockouts @@ -466,7 +473,7 @@ ExpeditionMember ExpeditionDatabase::GetExpeditionLeader(uint32_t expedition_id) void ExpeditionDatabase::InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, - bool update_expire_times, bool is_pending) + bool replace_timer, bool is_pending) { LogExpeditionsDetail("Inserting character [{}] lockouts", character_id); @@ -474,10 +481,11 @@ void ExpeditionDatabase::InsertCharacterLockouts( for (const auto& lockout : lockouts) { fmt::format_to(std::back_inserter(insert_values), - "({}, FROM_UNIXTIME({}), {}, '{}', '{}', {}),", + "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}', {}),", character_id, lockout.GetExpireTime(), lockout.GetDuration(), + lockout.GetExpeditionUUID(), lockout.GetExpeditionName(), lockout.GetEventName(), is_pending @@ -489,15 +497,30 @@ void ExpeditionDatabase::InsertCharacterLockouts( insert_values.pop_back(); // trailing comma std::string on_duplicate; - if (update_expire_times) { - on_duplicate = "expire_time = VALUES(expire_time)"; - } else { + if (replace_timer) + { + on_duplicate = SQL( + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = VALUES(expire_time), + duration = VALUES(duration) + ); + } + 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) + ( + character_id, + expire_time, + duration, + from_expedition_uuid, + expedition_name, + event_name, + is_pending + ) VALUES {} ON DUPLICATE KEY UPDATE {}; ), insert_values, on_duplicate); @@ -518,10 +541,11 @@ void ExpeditionDatabase::InsertMembersLockout( for (const auto& member : members) { fmt::format_to(std::back_inserter(insert_values), - "({}, FROM_UNIXTIME({}), {}, '{}', '{}'),", + "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", member.char_id, lockout.GetExpireTime(), lockout.GetDuration(), + lockout.GetExpeditionUUID(), lockout.GetExpeditionName(), lockout.GetEventName() ); @@ -533,9 +557,12 @@ void ExpeditionDatabase::InsertMembersLockout( auto query = fmt::format(SQL( INSERT INTO expedition_character_lockouts - (character_id, expire_time, duration, expedition_name, event_name) + (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) VALUES {} - ON DUPLICATE KEY UPDATE expire_time = VALUES(expire_time); + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = VALUES(expire_time), + duration = VALUES(duration); ), insert_values); database.QueryDatabase(query); @@ -552,11 +579,20 @@ void ExpeditionDatabase::InsertLockout( auto query = fmt::format(SQL( INSERT INTO expedition_lockouts - (expedition_id, event_name, expire_time, duration, is_inherited) + (expedition_id, from_expedition_uuid, event_name, expire_time, duration) VALUES - ({}, '{}', FROM_UNIXTIME({}), {}, FALSE) - ON DUPLICATE KEY UPDATE expire_time = VALUES(expire_time); - ), expedition_id, lockout.GetEventName(), lockout.GetExpireTime(), lockout.GetDuration()); + ({}, '{}', '{}', FROM_UNIXTIME({}), {}) + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = VALUES(expire_time), + duration = VALUES(duration); + ), + expedition_id, + lockout.GetExpeditionUUID(), + lockout.GetEventName(), + lockout.GetExpireTime(), + lockout.GetDuration() + ); database.QueryDatabase(query); } @@ -570,12 +606,12 @@ void ExpeditionDatabase::InsertLockouts( for (const auto& lockout : lockouts) { fmt::format_to(std::back_inserter(insert_values), - "({}, '{}', FROM_UNIXTIME({}), {}, {}),", + "({}, '{}', '{}', FROM_UNIXTIME({}), {}),", expedition_id, + lockout.second.GetExpeditionUUID(), lockout.second.GetEventName(), lockout.second.GetExpireTime(), - lockout.second.GetDuration(), - lockout.second.IsInherited() + lockout.second.GetDuration() ); } @@ -585,9 +621,12 @@ void ExpeditionDatabase::InsertLockouts( auto query = fmt::format(SQL( INSERT INTO expedition_lockouts - (expedition_id, event_name, expire_time, duration, is_inherited) + (expedition_id, from_expedition_uuid, event_name, expire_time, duration) VALUES {} - ON DUPLICATE KEY UPDATE expire_time = VALUES(expire_time); + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = VALUES(expire_time), + duration = VALUES(duration); ), insert_values); database.QueryDatabase(query); diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 14bc34f1c..66aee8852 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -61,7 +61,7 @@ namespace ExpeditionDatabase ExpeditionMember GetExpeditionLeader(uint32_t expedition_id); void InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, - bool update_expire_times, bool is_pending = false); + bool replace_timer, bool is_pending = false); void InsertMembersLockout(const std::vector& members, const ExpeditionLockoutTimer& lockout); void InsertLockout(uint32_t expedition_id, const ExpeditionLockoutTimer& lockout); void InsertLockouts(uint32_t expedition_id, const std::unordered_map& lockouts); diff --git a/zone/expedition_lockout_timer.cpp b/zone/expedition_lockout_timer.cpp index 25743df8d..01b1e607a 100644 --- a/zone/expedition_lockout_timer.cpp +++ b/zone/expedition_lockout_timer.cpp @@ -26,14 +26,14 @@ 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 + const std::string& expedition_uuid, const std::string& expedition_name, + const std::string& event_name, uint64_t expire_time, uint32_t duration ) : + m_expedition_uuid(expedition_uuid), m_expedition_name(expedition_name), m_event_name(event_name), m_expire_time(std::chrono::system_clock::from_time_t(expire_time)), - m_duration(duration), - m_is_inherited(inherited) + m_duration(duration) { if (event_name == DZ_REPLAY_TIMER_NAME) { diff --git a/zone/expedition_lockout_timer.h b/zone/expedition_lockout_timer.h index abd455ff5..af3042b0c 100644 --- a/zone/expedition_lockout_timer.h +++ b/zone/expedition_lockout_timer.h @@ -29,10 +29,10 @@ extern const char* const DZ_REPLAY_TIMER_NAME; class ExpeditionLockoutTimer { public: - ExpeditionLockoutTimer() {} + ExpeditionLockoutTimer() = default; ExpeditionLockoutTimer( - std::string expedition_name, std::string event_name, - uint64_t expire_time, uint32_t duration, bool inherited = false); + const std::string& expedition_uuid, const std::string& expedition_name, + const std::string& event_name, uint64_t expire_time, uint32_t duration); struct DaysHoursMinutes { @@ -46,20 +46,20 @@ public: uint32_t GetSecondsRemaining() const; DaysHoursMinutes GetDaysHoursMinutesRemaining() const; const std::string& GetExpeditionName() const { return m_expedition_name; } + const std::string& GetExpeditionUUID() const { return m_expedition_uuid; } const std::string& GetEventName() const { return m_event_name; } - void SetExpireTime(uint64_t expire_time) { m_expire_time = std::chrono::system_clock::from_time_t(expire_time); } - void SetInherited(bool is_inherited) { m_is_inherited = is_inherited; } bool IsExpired() const { return GetSecondsRemaining() == 0; } - bool IsInherited() const { return m_is_inherited; } + bool IsFromExpedition(const std::string& uuid) const { return uuid == m_expedition_uuid; } 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; + void Reset() { m_expire_time = std::chrono::system_clock::now() + m_duration; } private: + bool m_is_replay_timer = false; + std::string m_expedition_uuid; // expedition received in std::string m_expedition_name; std::string m_event_name; - bool m_is_inherited = false; // inherited from expedition leader - bool m_is_replay_timer = false; std::chrono::seconds m_duration; std::chrono::time_point m_expire_time; }; diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 63b14c131..2e7f77582 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -166,8 +166,6 @@ bool ExpeditionRequest::LoadLeaderLockouts() for (auto& lockout : lockouts) { - lockout.SetInherited(true); - // client window hides timers with less than 60s remaining, optionally count them as expired if (lockout.GetSecondsRemaining() <= leeway_seconds) { @@ -243,12 +241,12 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& last_character_id = character_id; // compare member lockouts with leader lockouts - if (row[3] && row[4] && row[5]) + if (row[3] && row[4] && row[5] && row[6]) { - auto expire_time = strtoull(row[3], nullptr, 10); - auto duration = static_cast(strtoul(row[4], nullptr, 10)); + auto expire_time = strtoull(row[4], nullptr, 10); + auto duration = static_cast(strtoul(row[5], nullptr, 10)); - ExpeditionLockoutTimer lockout{m_expedition_name, row[5], expire_time, duration}; + ExpeditionLockoutTimer lockout{row[3], m_expedition_name, row[6], expire_time, duration}; // client window hides timers with less than 60s remaining, optionally count them as expired if (lockout.GetSecondsRemaining() <= leeway_seconds) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index f4211b84f..bf896e32d 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1768,11 +1768,27 @@ luabind::object Lua_Client::GetExpeditionLockouts(lua_State* L, std::string expe return lua_table; } +std::string Lua_Client::GetLockoutExpeditionUUID(std::string expedition_name, std::string event_name) { + Lua_Safe_Call_String(); + std::string uuid; + auto lockout = self->GetExpeditionLockout(expedition_name, event_name); + if (lockout) + { + uuid = lockout->GetExpeditionUUID(); + } + return uuid; +} + void Lua_Client::AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds) { Lua_Safe_Call_Void(); self->AddNewExpeditionLockout(expedition_name, event_name, seconds); } +void Lua_Client::AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { + Lua_Safe_Call_Void(); + self->AddNewExpeditionLockout(expedition_name, event_name, seconds, uuid); +} + void Lua_Client::RemoveAllExpeditionLockouts() { Lua_Safe_Call_Void(); self->RemoveAllExpeditionLockouts(); @@ -2116,7 +2132,9 @@ luabind::scope lua_register_client() { .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("GetLockoutExpeditionUUID", (std::string(Lua_Client::*)(std::string, std::string))&Lua_Client::GetLockoutExpeditionUUID) .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32))&Lua_Client::AddExpeditionLockout) + .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32, std::string))&Lua_Client::AddExpeditionLockout) .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(void))&Lua_Client::RemoveAllExpeditionLockouts) .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(std::string))&Lua_Client::RemoveAllExpeditionLockouts) .def("RemoveExpeditionLockout", (void(Lua_Client::*)(std::string, std::string))&Lua_Client::RemoveExpeditionLockout) diff --git a/zone/lua_client.h b/zone/lua_client.h index bdcd04cc5..f66504616 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -345,7 +345,9 @@ public: Lua_Expedition GetExpedition(); luabind::object GetExpeditionLockouts(lua_State* L); luabind::object GetExpeditionLockouts(lua_State* L, std::string expedition_name); + std::string GetLockoutExpeditionUUID(std::string expedition_name, std::string event_name); void AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds); + void AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid); void RemoveAllExpeditionLockouts(); void RemoveAllExpeditionLockouts(std::string expedition_name); void RemoveExpeditionLockout(std::string expedition_name, std::string event_name); From da2a6205ed14f51f876b0e07478f807f0f8cad41 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 13 Jun 2020 19:44:27 -0400 Subject: [PATCH 080/196] Use replay timer uuid to allow re-invite Instead of allowing all previous members to bypass a replay timer conflict, only allow if expedition uuid of the lockout matches This fixes an exploit for expeditions that add delayed replay timers. Members could be part of an expedition on creation and then quit to form another expedition. They could then always be re-invited to the original expedition even with a conflicting replay timer lockout. --- zone/expedition.cpp | 63 ++++++++++++++++++------------------ zone/expedition_database.cpp | 4 ++- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 1f15d18e7..60a699293 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -691,44 +691,43 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, has_conflict = true; } - // client with a replay lockout is allowed only if they were a previous member - auto member_iter = m_member_id_history.find(add_client->CharacterID()); - bool was_member = (member_iter != m_member_id_history.end()); - if (!was_member) - { - auto replay_lockout = add_client->GetExpeditionLockout(m_expedition_name, DZ_REPLAY_TIMER_NAME); - if (replay_lockout) - { - has_conflict = true; - - auto time_remaining = replay_lockout->GetDaysHoursMinutesRemaining(); - SendLeaderMessage(leader_client, Chat::Red, DZADD_REPLAY_TIMER, { - add_client->GetName(), - time_remaining.days, - time_remaining.hours, - time_remaining.mins - }); - } - } - // check any extra event lockouts for this expedition that the client has and expedition doesn't auto client_lockouts = add_client->GetExpeditionLockouts(m_expedition_name); for (const auto& client_lockout : client_lockouts) { - bool is_missing_lockout = (m_lockouts.find(client_lockout.GetEventName()) == m_lockouts.end()); - if (!client_lockout.IsReplayTimer() && is_missing_lockout) + if (client_lockout.IsReplayTimer()) { - has_conflict = true; + // client with a replay lockout is allowed only if the replay timer was from this expedition + if (client_lockout.GetExpeditionUUID() != GetUUID()) + { + has_conflict = true; - auto time_remaining = client_lockout.GetDaysHoursMinutesRemaining(); - SendLeaderMessage(leader_client, Chat::Red, DZADD_EVENT_TIMER, { - add_client->GetName(), - client_lockout.GetEventName(), - time_remaining.days, - time_remaining.hours, - time_remaining.mins, - client_lockout.GetEventName() - }); + auto time_remaining = client_lockout.GetDaysHoursMinutesRemaining(); + SendLeaderMessage(leader_client, Chat::Red, DZADD_REPLAY_TIMER, { + add_client->GetName(), + time_remaining.days, + time_remaining.hours, + time_remaining.mins + }); + } + } + else + { + bool is_missing_lockout = (m_lockouts.find(client_lockout.GetEventName()) == m_lockouts.end()); + if (is_missing_lockout) + { + has_conflict = true; + + auto time_remaining = client_lockout.GetDaysHoursMinutesRemaining(); + SendLeaderMessage(leader_client, Chat::Red, DZADD_EVENT_TIMER, { + add_client->GetName(), + client_lockout.GetEventName(), + time_remaining.days, + time_remaining.hours, + time_remaining.mins, + client_lockout.GetEventName() + }); + } } } diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 18d952e4a..00c7b6418 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -227,7 +227,9 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts( MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( const std::vector& character_names, const std::string& expedition_name) { - LogExpeditionsDetail("Loading multiple characters data for [{}] request", expedition_name); + LogExpeditionsDetail( + "Loading data of [{}] characters for [{}] request", character_names.size(), expedition_name + ); std::string in_character_names_query; for (const auto& character_name : character_names) From 06d84b83deb69644d61892ca3d6cf5ae0e549539 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 13 Jun 2020 20:19:03 -0400 Subject: [PATCH 081/196] Remove member history from expeditions Expedition uuids are now used to check if characters may re-join --- utils/sql/git/required/wip_expeditions.sql | 1 - world/expedition.cpp | 21 ++++--------- zone/dynamiczone.cpp | 2 +- zone/dynamiczone.h | 3 +- zone/expedition.cpp | 36 ++++++++++------------ zone/expedition.h | 6 ++-- zone/expedition_database.cpp | 29 ++++++++--------- zone/expedition_database.h | 5 ++- 8 files changed, 40 insertions(+), 63 deletions(-) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index e69fca8d6..27ca3f5ae 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -35,7 +35,6 @@ CREATE TABLE `expedition_members` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `expedition_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, `character_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, - `is_current_member` TINYINT(4) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE INDEX `expedition_id_character_id` (`expedition_id`, `character_id`), CONSTRAINT `FK_expedition_members_expedition_details` FOREIGN KEY (`expedition_id`) REFERENCES `expedition_details` (`id`) ON DELETE CASCADE diff --git a/world/expedition.cpp b/world/expedition.cpp index 74cea7c36..98675a7c9 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -205,20 +205,19 @@ void ExpeditionCache::Process() void ExpeditionDatabase::PurgeExpiredExpeditions() { - LogExpeditionsDetail("Purging expired expeditions"); - std::string query = SQL( - DELETE expedition FROM expedition_details expedition + 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 + SELECT expedition_id, COUNT(*) member_count FROM expedition_members GROUP BY expedition_id ) AS expedition_members ON expedition_members.expedition_id = expedition.id WHERE expedition.instance_id IS NULL - OR expedition_members.member_count = 0 + OR expedition_members.member_count IS NULL OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); ); @@ -227,8 +226,6 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() void ExpeditionDatabase::PurgeExpiredCharacterLockouts() { - LogExpeditionsDetail("Purging expired lockouts"); - std::string query = SQL( DELETE FROM expedition_character_lockouts WHERE expire_time <= NOW(); @@ -239,8 +236,6 @@ void ExpeditionDatabase::PurgeExpiredCharacterLockouts() std::vector ExpeditionDatabase::LoadExpeditions() { - LogExpeditionsDetail("Loading expeditions for world cache"); - std::vector expeditions; std::string query = SQL( @@ -253,9 +248,7 @@ std::vector ExpeditionDatabase::LoadExpeditions() expedition_members.character_id FROM expedition_details INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id - INNER JOIN expedition_members - ON expedition_members.expedition_id = expedition_details.id - AND expedition_members.is_current_member = TRUE + INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id ORDER BY expedition_details.id; ); @@ -305,9 +298,7 @@ Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) expedition_members.character_id FROM expedition_details INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id - INNER JOIN expedition_members - ON expedition_members.expedition_id = expedition_details.id - AND expedition_members.is_current_member = TRUE + INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id WHERE expedition_details.id = {}; ), expedition_id); diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index afa5fc7b7..0a7bad7e7 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -427,7 +427,7 @@ void DynamicZone::RemoveAllCharacters(bool enable_removal_timers) database.RemoveClientsFromInstance(GetInstanceID()); } -void DynamicZone::SaveInstanceMembersToDatabase(const std::unordered_set& character_ids) +void DynamicZone::SaveInstanceMembersToDatabase(const std::vector& character_ids) { LogDynamicZonesDetail("Saving [{}] instance members to database", character_ids.size()); diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index 6951fe517..b1f56031e 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -25,7 +25,6 @@ #include #include #include -#include #include class MySQLRequestRow; @@ -75,7 +74,7 @@ public: uint32_t CreateInstance(); void AddCharacter(uint32_t character_id); - void SaveInstanceMembersToDatabase(const std::unordered_set& character_ids); + void SaveInstanceMembersToDatabase(const std::vector& 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); }; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 60a699293..3ad0ed113 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -212,9 +212,8 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) if (current_expedition) { auto member_id = strtoul(row[col::member_id], nullptr, 10); - bool is_current_member = (strtoul(row[col::is_current_member], nullptr, 10) != 0); current_expedition->AddInternalMember( - row[col::member_name], member_id, ExpeditionMemberStatus::Offline, is_current_member + row[col::member_name], member_id, ExpeditionMemberStatus::Offline ); expedition_character_ids.emplace_back(std::make_pair(expedition_id, member_id)); } @@ -309,14 +308,16 @@ void Expedition::SaveLockouts(ExpeditionRequest& request) void Expedition::SaveMembers(ExpeditionRequest& request) { m_members = request.GetMembers(); + + std::vector member_ids; for (const auto& member : m_members) { - m_member_id_history.emplace(member.char_id); + member_ids.emplace_back(member.char_id); } ExpeditionDatabase::InsertMembers(m_id, m_members); ExpeditionDatabase::DeleteAllMembersPendingLockouts(m_members); - m_dynamiczone.SaveInstanceMembersToDatabase(m_member_id_history); // all are current members here + m_dynamiczone.SaveInstanceMembersToDatabase(member_ids); } Expedition* Expedition::FindCachedExpeditionByCharacterID(uint32_t character_id) @@ -465,22 +466,17 @@ void Expedition::RemoveLockout(const std::string& event_name) } void Expedition::AddInternalMember( - const std::string& char_name, uint32_t character_id, ExpeditionMemberStatus status, bool is_current_member) + const std::string& char_name, uint32_t character_id, ExpeditionMemberStatus status) { - if (is_current_member) + auto it = std::find_if(m_members.begin(), m_members.end(), + [character_id](const ExpeditionMember& member) { + return member.char_id == character_id; + }); + + if (it == m_members.end()) { - auto it = std::find_if(m_members.begin(), m_members.end(), - [character_id](const ExpeditionMember& member) { - return member.char_id == character_id; - }); - - if (it == m_members.end()) - { - m_members.emplace_back(ExpeditionMember{character_id, char_name, status}); - } + m_members.emplace_back(ExpeditionMember{character_id, char_name, status}); } - - m_member_id_history.emplace(character_id); } bool Expedition::AddMember(const std::string& add_char_name, uint32_t add_char_id) @@ -504,7 +500,7 @@ void Expedition::RemoveAllMembers(bool enable_removal_timers) m_dynamiczone.RemoveAllCharacters(enable_removal_timers); ExpeditionDatabase::DeleteAllMembersPendingLockouts(m_members); - ExpeditionDatabase::UpdateAllMembersRemoved(m_id); + ExpeditionDatabase::DeleteAllMembers(m_id); SendUpdatesToZoneMembers(true); SendWorldExpeditionUpdate(ServerOP_ExpeditionMembersRemoved); @@ -520,7 +516,7 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) return false; } - ExpeditionDatabase::UpdateMemberRemoved(m_id, member.char_id); + ExpeditionDatabase::DeleteMember(m_id, member.char_id); m_dynamiczone.RemoveCharacter(member.char_id); ProcessMemberRemoved(member.name, member.char_id); @@ -549,7 +545,7 @@ 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::DeleteMember(m_id, member.char_id); ExpeditionDatabase::InsertMember(m_id, add_client->CharacterID()); m_dynamiczone.RemoveCharacter(member.char_id); m_dynamiczone.AddCharacter(add_client->CharacterID()); diff --git a/zone/expedition.h b/zone/expedition.h index 2dfa46f3c..cbd8fe896 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -27,7 +27,6 @@ #include #include #include -#include #include class Client; @@ -138,7 +137,7 @@ private: static void CacheExpeditions(MySQLRequestResult& results); static void SendWorldGetOnlineMembers(const std::vector>& expedition_character_ids); - void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status, bool is_current_member = true); + void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status); bool ChooseNewLeader(); bool ConfirmLeaderCommand(Client* requester); bool ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping); @@ -183,8 +182,7 @@ private: 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 + std::vector m_members; std::unordered_map m_lockouts; }; diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 00c7b6418..d69e36263 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -65,7 +65,6 @@ std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() expedition_details.is_locked, character_data.name leader_name, expedition_members.character_id, - expedition_members.is_current_member, member_data.name FROM expedition_details INNER JOIN character_data ON expedition_details.leader_id = character_data.id @@ -259,9 +258,7 @@ MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( 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 + LEFT JOIN expedition_members member ON character_data.id = member.character_id WHERE character_data.name IN ({}) ORDER BY character_data.id; ), expedition_name, in_character_names_query); @@ -420,8 +417,7 @@ uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_i uint32_t expedition_id = 0; auto query = fmt::format(SQL( - SELECT expedition_id FROM expedition_members - WHERE character_id = {} AND is_current_member = TRUE; + SELECT expedition_id FROM expedition_members WHERE character_id = {}; ), character_id); auto results = database.QueryDatabase(query); @@ -641,10 +637,10 @@ void ExpeditionDatabase::InsertMember(uint32_t expedition_id, uint32_t character auto query = fmt::format(SQL( INSERT INTO expedition_members - (expedition_id, character_id, is_current_member) + (expedition_id, character_id) VALUES - ({}, {}, TRUE) - ON DUPLICATE KEY UPDATE is_current_member = TRUE; + ({}, {}) + ON DUPLICATE KEY UPDATE character_id = VALUES(character_id); ), expedition_id, character_id); database.QueryDatabase(query); @@ -659,7 +655,7 @@ void ExpeditionDatabase::InsertMembers( for (const auto& member : members) { fmt::format_to(std::back_inserter(insert_values), - "({}, {}, TRUE),", + "({}, {}),", expedition_id, member.char_id ); } @@ -669,7 +665,8 @@ void ExpeditionDatabase::InsertMembers( insert_values.pop_back(); // trailing comma auto query = fmt::format(SQL( - INSERT INTO expedition_members (expedition_id, character_id, is_current_member) + INSERT INTO expedition_members + (expedition_id, character_id) VALUES {}; ), insert_values); @@ -699,25 +696,23 @@ void ExpeditionDatabase::UpdateLockState(uint32_t expedition_id, bool is_locked) database.QueryDatabase(query); } -void ExpeditionDatabase::UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id) +void ExpeditionDatabase::DeleteMember(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 = {}; + DELETE FROM expedition_members WHERE expedition_id = {} AND character_id = {}; ), expedition_id, character_id); database.QueryDatabase(query); } -void ExpeditionDatabase::UpdateAllMembersRemoved(uint32_t expedition_id) +void ExpeditionDatabase::DeleteAllMembers(uint32_t expedition_id) { LogExpeditionsDetail("Updating all members of expedition [{}] as removed", expedition_id); auto query = fmt::format(SQL( - UPDATE expedition_members SET is_current_member = FALSE - WHERE expedition_id = {}; + DELETE FROM expedition_members WHERE expedition_id = {}; ), expedition_id); database.QueryDatabase(query); diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 66aee8852..a3c283351 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -47,6 +47,8 @@ namespace ExpeditionDatabase std::vector LoadCharacterLockouts(uint32_t character_id, const std::string& expedition_name); std::unordered_map> LoadMultipleExpeditionLockouts(const std::vector& expedition_ids); + void DeleteAllMembers(uint32_t expedition_id); + void DeleteMember(uint32_t expedition_id, uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id, const std::string& expedition_name); void DeleteCharacterLockout(uint32_t character_id, const std::string& expedition_name, const std::string& event_name); @@ -69,8 +71,6 @@ namespace ExpeditionDatabase void InsertMembers(uint32_t expedition_id, const std::vector& members); void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); void UpdateLockState(uint32_t expedition_id, bool is_locked); - void UpdateMemberRemoved(uint32_t expedition_id, uint32_t character_id); - void UpdateAllMembersRemoved(uint32_t expedition_id); void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); }; @@ -89,7 +89,6 @@ namespace LoadExpeditionColumns is_locked, leader_name, member_id, - is_current_member, member_name }; }; From f97cc7cdec1ad7f88606067b2501d2eaf5774405 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 14 Jun 2020 19:21:46 -0400 Subject: [PATCH 082/196] Add expedition event loot api Add SetLootEventByNPCTypeID and SetLootEventBySpawnID quest apis These associate events with npcs or entities inside the dz to prevent them from being looted by characters that didn't receive the event lockout from the current expedition. This fixes an exploit that allowed a player that already had a lockout from another expedition being added to loot after the event is complete --- zone/corpse.cpp | 15 ++++++ zone/expedition.cpp | 93 ++++++++++++++++++++++++++++++++++-- zone/expedition.h | 10 +++- zone/expedition_database.cpp | 18 ------- zone/expedition_database.h | 1 - zone/lua_expedition.cpp | 24 ++++++++++ zone/lua_expedition.h | 4 ++ zone/questmgr.cpp | 4 +- zone/zone.cpp | 2 +- 9 files changed, 143 insertions(+), 28 deletions(-) diff --git a/zone/corpse.cpp b/zone/corpse.cpp index e0e1d1992..9bdedc492 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -38,6 +38,7 @@ Child of the Mob class. #include "corpse.h" #include "entity.h" +#include "expedition.h" #include "groups.h" #include "mob.h" #include "raids.h" @@ -1275,6 +1276,20 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) return; } + if (zone && zone->GetInstanceID() != 0) + { + // expeditions may prevent looting based on client's lockouts + auto expedition = Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); + if (expedition && !expedition->CanClientLootCorpse(client, GetNPCTypeID(), GetID())) + { + client->MessageString(Chat::Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); + client->QueuePacket(app); + SendEndLootErrorPacket(client); + ResetLooter(); + delete inst; + return; + } + } // do we want this to have a fail option too? parse->EventItem(EVENT_LOOT, client, inst, this, buf, 0); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 3ad0ed113..3ed57a099 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -363,13 +363,17 @@ Expedition* Expedition::FindCachedExpeditionByID(uint32_t expedition_id) return nullptr; } -Expedition* Expedition::FindExpeditionByInstanceID(uint32_t instance_id) +Expedition* Expedition::FindCachedExpeditionByInstanceID(uint32_t instance_id) { - if (instance_id) + if (instance_id && zone) { - // ask database since it may have expired - auto expedition_id = ExpeditionDatabase::GetExpeditionIDFromInstanceID(instance_id); - return Expedition::FindCachedExpeditionByID(expedition_id); + for (const auto& cached_expedition : zone->expedition_cache) + { + if (cached_expedition.second->GetInstanceID() == instance_id) + { + return cached_expedition.second.get(); + } + } } return nullptr; } @@ -1891,3 +1895,82 @@ void Expedition::SetDzZoneInLocation(float x, float y, float z, float heading, b SendWorldDzLocationUpdate(ServerOP_ExpeditionDzZoneIn, location); } } + +bool Expedition::CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id) +{ + if (client && m_dynamiczone.IsCurrentZoneDzInstance()) + { + // entity id takes priority, falls back to checking by npc type if not set + std::string event_name = GetLootEventBySpawnID(spawn_id); + if (event_name.empty()) + { + event_name = GetLootEventByNPCTypeID(npc_type_id); + } + + if (!event_name.empty()) + { + auto client_lockout = client->GetExpeditionLockout(GetName(), event_name); + if (!client_lockout || client_lockout->GetExpeditionUUID() != GetUUID()) + { + // client lockout not received in this expedition, prevent looting + LogExpeditions( + "Character [{}] denied looting npc [{}] spawn [{}] for lockout event [{}]", + client->CharacterID(), npc_type_id, spawn_id, event_name + ); + return false; + } + } + } + + return true; +} + +void Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string& event_name) +{ + if (npc_type_id && m_dynamiczone.IsCurrentZoneDzInstance()) + { + LogExpeditions("Setting loot event [{}] for npc type id [{}]", event_name, npc_type_id); + m_npc_loot_events[npc_type_id] = event_name; + } +} + +void Expedition::SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name) +{ + if (spawn_id && m_dynamiczone.IsCurrentZoneDzInstance()) + { + LogExpeditions("Setting loot event [{}] for entity id [{}]", event_name, spawn_id); + m_spawn_loot_events[spawn_id] = event_name; + } +} + +std::string Expedition::GetLootEventByNPCTypeID(uint32_t npc_type_id) +{ + std::string event_name; + + if (npc_type_id && m_dynamiczone.IsCurrentZoneDzInstance()) + { + auto it = m_npc_loot_events.find(npc_type_id); + if (it != m_npc_loot_events.end()) + { + event_name = it->second; + } + } + + return event_name; +} + +std::string Expedition::GetLootEventBySpawnID(uint32_t spawn_id) +{ + std::string event_name; + + if (spawn_id && m_dynamiczone.IsCurrentZoneDzInstance()) + { + auto it = m_spawn_loot_events.find(spawn_id); + if (it != m_spawn_loot_events.end()) + { + event_name = it->second; + } + } + + return event_name; +} diff --git a/zone/expedition.h b/zone/expedition.h index cbd8fe896..53cc32306 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -76,7 +76,7 @@ public: 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 Expedition* FindCachedExpeditionByInstanceID(uint32_t instance_id); static void RemoveCharacterLockouts(std::string character_name, std::string expedition_name = {}, std::string event_name = {}); static void HandleWorldMessage(ServerPacket* pack); @@ -110,6 +110,12 @@ public: void RemoveLockout(const std::string& event_name); void SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db = false); + bool CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id); + std::string GetLootEventByNPCTypeID(uint32_t npc_id); + std::string GetLootEventBySpawnID(uint32_t spawn_id); + void SetLootEventByNPCTypeID(uint32_t npc_type_id, const std::string& event_name); + void SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name); + void SendClientExpeditionInfo(Client* client); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); @@ -184,6 +190,8 @@ private: ExpeditionMember m_leader; std::vector m_members; std::unordered_map m_lockouts; + std::unordered_map m_npc_loot_events; // only valid inside dz zone + std::unordered_map m_spawn_loot_events; // only valid inside dz zone }; #endif diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index d69e36263..9f77d537c 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -429,24 +429,6 @@ uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_i 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); diff --git a/zone/expedition_database.h b/zone/expedition_database.h index a3c283351..184fdb1d7 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -59,7 +59,6 @@ namespace ExpeditionDatabase void DeletePendingLockouts(uint32_t character_id); void DeleteAllMembersPendingLockouts(const std::vector& members); 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& lockouts, diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index c07379410..c819b4092 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -66,6 +66,16 @@ luabind::object Lua_Expedition::GetLockouts(lua_State* L) { return lua_table; } +std::string Lua_Expedition::GetLootEventByNPCTypeID(uint32_t npc_type_id) { + Lua_Safe_Call_String(); + return self->GetLootEventByNPCTypeID(npc_type_id); +} + +std::string Lua_Expedition::GetLootEventBySpawnID(uint32_t spawn_id) { + Lua_Safe_Call_String(); + return self->GetLootEventBySpawnID(spawn_id); +} + uint32_t Lua_Expedition::GetMemberCount() { Lua_Safe_Call_Int(); return self->GetMemberCount(); @@ -140,6 +150,16 @@ void Lua_Expedition::SetLocked(bool lock_expedition) { self->SetLocked(lock_expedition, true); } +void Lua_Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, std::string event_name) { + Lua_Safe_Call_Void(); + self->SetLootEventByNPCTypeID(npc_type_id, event_name); +} + +void Lua_Expedition::SetLootEventBySpawnID(uint32_t spawn_id, std::string event_name) { + Lua_Safe_Call_Void(); + self->SetLootEventBySpawnID(spawn_id, event_name); +} + void Lua_Expedition::SetReplayLockoutOnMemberJoin(bool enable) { Lua_Safe_Call_Void(); self->SetReplayLockoutOnMemberJoin(enable, true); @@ -171,6 +191,8 @@ luabind::scope lua_register_expedition() { .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("GetLootEventByNPCTypeID", (std::string(Lua_Expedition::*)(uint32_t))&Lua_Expedition::GetLootEventByNPCTypeID) + .def("GetLootEventBySpawnID", (std::string(Lua_Expedition::*)(uint32_t))&Lua_Expedition::GetLootEventBySpawnID) .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) @@ -184,6 +206,8 @@ luabind::scope lua_register_expedition() { .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("SetLocked", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetLocked) + .def("SetLootEventByNPCTypeID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventByNPCTypeID) + .def("SetLootEventBySpawnID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventBySpawnID) .def("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) .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) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 09d09be97..09881bf0f 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -58,6 +58,8 @@ public: int GetInstanceID(); std::string GetLeaderName(); luabind::object GetLockouts(lua_State* L); + std::string GetLootEventByNPCTypeID(uint32_t npc_type_id); + std::string GetLootEventBySpawnID(uint32_t spawn_id); uint32_t GetMemberCount(); luabind::object GetMembers(lua_State* L); std::string GetName(); @@ -71,6 +73,8 @@ public: void SetCompass(uint32_t zone_id, float x, float y, float z); void SetCompass(std::string zone_name, float x, float y, float z); void SetLocked(bool lock_expedition); + void SetLootEventByNPCTypeID(uint32_t npc_type_id, std::string event_name); + void SetLootEventBySpawnID(uint32_t spawn_id, std::string event_name); void SetReplayLockoutOnMemberJoin(bool enable); void SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading); void SetSafeReturn(std::string zone_name, float x, float y, float z, float heading); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index a255d110f..0529bced0 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -4336,14 +4336,14 @@ Expedition* QuestManager::GetExpeditionByCharID(uint32 char_id) Expedition* QuestManager::GetExpeditionByInstanceID(uint32 instance_id) { - return Expedition::FindExpeditionByInstanceID(instance_id); + return Expedition::FindCachedExpeditionByInstanceID(instance_id); } Expedition* QuestManager::GetExpeditionForCurrentInstance() { if (zone && zone->GetInstanceID() != 0) { - return Expedition::FindExpeditionByInstanceID(zone->GetInstanceID()); + return Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); } return nullptr; } diff --git a/zone/zone.cpp b/zone/zone.cpp index da2555785..0630dff50 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1492,7 +1492,7 @@ bool Zone::Process() { if(Instance_Timer->Check()) { // if this is a dynamic zone instance notify system associated with it - Expedition* expedition = Expedition::FindExpeditionByInstanceID(GetInstanceID()); + Expedition* expedition = Expedition::FindCachedExpeditionByInstanceID(GetInstanceID()); if (expedition) { expedition->RemoveAllMembers(false); // entity list will teleport clients out immediately From e8d250827d7d98b492e4b98b83a300fe7f64cf8e Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 17 Jun 2020 18:24:56 -0400 Subject: [PATCH 083/196] Add api to update lockout duration Some live expeditions update a lockout's duration during progression The current AddLockout method replaces lockout timers. This updates the expiration of an existing lockout by modifying the original duration Only members are updated and not the internal expedition timer by default. This is so new members receive the original duration like live --- common/servertalk.h | 1 + zone/expedition.cpp | 50 ++++++++++++++++++++++++--------- zone/expedition.h | 6 ++-- zone/expedition_lockout_timer.h | 1 + zone/lua_expedition.cpp | 14 ++++++++- zone/lua_expedition.h | 2 ++ 6 files changed, 58 insertions(+), 16 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index ab9ff17ca..80185ea05 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -2055,6 +2055,7 @@ struct ServerExpeditionLockout_Struct { uint32 sender_zone_id; uint16 sender_instance_id; uint8 remove; + uint8 members_only; char event_name[256]; }; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 3ed57a099..eaa90c382 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -448,15 +448,34 @@ void Expedition::AddReplayLockout(uint32_t seconds) void Expedition::AddLockout(const std::string& event_name, uint32_t seconds) { - // any current lockouts for the event are updated with new expiration time ExpeditionLockoutTimer lockout{m_uuid, m_expedition_name, event_name, 0, seconds}; lockout.Reset(); // sets expire time + AddLockout(lockout); +} - ExpeditionDatabase::InsertLockout(m_id, lockout); +void Expedition::AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only) +{ + if (!members_only) + { + ExpeditionDatabase::InsertLockout(m_id, lockout); + } ExpeditionDatabase::InsertMembersLockout(m_members, lockout); - ProcessLockoutUpdate(lockout, false); - SendWorldLockoutUpdate(lockout, false); + ProcessLockoutUpdate(lockout, false, members_only); + SendWorldLockoutUpdate(lockout, false, members_only); +} + +void Expedition::UpdateLockoutDuration( + const std::string& event_name, uint32_t seconds, bool members_only) +{ + // some live expeditions update existing lockout timers during progression + auto it = m_lockouts.find(event_name); + if (it != m_lockouts.end()) + { + uint64_t expire_time = it->second.GetStartTime() + seconds; + ExpeditionLockoutTimer lockout{m_uuid, m_expedition_name, event_name, expire_time, seconds}; + AddLockout(lockout, members_only); + } } void Expedition::RemoveLockout(const std::string& event_name) @@ -1223,15 +1242,19 @@ void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t re ); } -void Expedition::ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove) +void Expedition::ProcessLockoutUpdate( + const ExpeditionLockoutTimer& lockout, bool remove, bool members_only) { - if (!remove) + if (!members_only) { - m_lockouts[lockout.GetEventName()] = lockout; - } - else - { - m_lockouts.erase(lockout.GetEventName()); + if (!remove) + { + m_lockouts[lockout.GetEventName()] = lockout; + } + else + { + m_lockouts.erase(lockout.GetEventName()); + } } for (const auto& member : m_members) @@ -1453,7 +1476,7 @@ void Expedition::SendWorldLeaderChanged() } void Expedition::SendWorldLockoutUpdate( - const ExpeditionLockoutTimer& lockout, bool remove) + const ExpeditionLockoutTimer& lockout, bool remove, bool members_only) { uint32_t pack_size = sizeof(ServerExpeditionLockout_Struct); auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionLockout, pack_size)); @@ -1464,6 +1487,7 @@ void Expedition::SendWorldLockoutUpdate( buf->sender_zone_id = zone ? zone->GetZoneID() : 0; buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; buf->remove = remove; + buf->members_only = members_only; strn0cpy(buf->event_name, lockout.GetEventName().c_str(), sizeof(buf->event_name)); worldserver.SendPacket(pack.get()); } @@ -1655,7 +1679,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) ExpeditionLockoutTimer lockout{ expedition->GetUUID(), expedition->GetName(), buf->event_name, buf->expire_time, buf->duration }; - expedition->ProcessLockoutUpdate(lockout, buf->remove); + expedition->ProcessLockoutUpdate(lockout, buf->remove, buf->members_only); } } break; diff --git a/zone/expedition.h b/zone/expedition.h index 53cc32306..5e9d4c28f 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -109,6 +109,7 @@ public: bool HasReplayLockout(); void RemoveLockout(const std::string& event_name); void SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db = false); + void UpdateLockoutDuration(const std::string& event_name, uint32_t seconds, bool members_only = true); bool CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id); std::string GetLootEventByNPCTypeID(uint32_t npc_id); @@ -143,12 +144,13 @@ private: static void CacheExpeditions(MySQLRequestResult& results); static void SendWorldGetOnlineMembers(const std::vector>& expedition_character_ids); + void AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only = false); void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status); bool ChooseNewLeader(); bool ConfirmLeaderCommand(Client* requester); 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 ExpeditionLockoutTimer& lockout, bool remove); + void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); 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); @@ -161,7 +163,7 @@ private: void SendWorldExpeditionUpdate(uint16_t server_opcode); void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); void SendWorldLeaderChanged(); - void SendWorldLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove); + void SendWorldLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = 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); diff --git a/zone/expedition_lockout_timer.h b/zone/expedition_lockout_timer.h index af3042b0c..5325d0f78 100644 --- a/zone/expedition_lockout_timer.h +++ b/zone/expedition_lockout_timer.h @@ -43,6 +43,7 @@ public: uint32_t GetDuration() const { return static_cast(m_duration.count()); } uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(m_expire_time); } + uint64_t GetStartTime() const { return std::chrono::system_clock::to_time_t(m_expire_time - m_duration); } uint32_t GetSecondsRemaining() const; DaysHoursMinutes GetDaysHoursMinutesRemaining() const; const std::string& GetExpeditionName() const { return m_expedition_name; } diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index c819b4092..55abece45 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -180,6 +180,16 @@ void Lua_Expedition::SetZoneInLocation(float x, float y, float z, float heading) self->SetDzZoneInLocation(x, y, z, heading, true); } +void Lua_Expedition::UpdateLockoutDuration(std::string event_name, uint32_t duration) { + Lua_Safe_Call_Void(); + self->UpdateLockoutDuration(event_name, duration); +} + +void Lua_Expedition::UpdateLockoutDuration(std::string event_name, uint32_t duration, bool members_only) { + Lua_Safe_Call_Void(); + self->UpdateLockoutDuration(event_name, duration, members_only); +} + luabind::scope lua_register_expedition() { return luabind::class_("Expedition") .def(luabind::constructor<>()) @@ -211,7 +221,9 @@ luabind::scope lua_register_expedition() { .def("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) .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); + .def("SetZoneInLocation", (void(Lua_Expedition::*)(float, float, float, float))&Lua_Expedition::SetZoneInLocation) + .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t))&Lua_Expedition::UpdateLockoutDuration) + .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t, bool))&Lua_Expedition::UpdateLockoutDuration); } #endif // LUA_EQEMU diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 09881bf0f..adc54a65a 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -79,6 +79,8 @@ public: void SetSafeReturn(uint32_t 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); + void UpdateLockoutDuration(std::string event_name, uint32_t duration); + void UpdateLockoutDuration(std::string event_name, uint32_t duration, bool members_only); }; #endif // LUA_EQEMU From 70161aecc448973238a592f68856da5d454db94b Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 30 Jun 2020 19:59:31 -0400 Subject: [PATCH 084/196] Remove fk constraints in expedition tables Add expedition tables to database schema lists --- common/database_instances.cpp | 10 +++- common/database_schema.h | 6 +++ utils/sql/git/required/wip_dynamiczones.sql | 3 +- utils/sql/git/required/wip_expeditions.sql | 15 +++--- world/expedition.cpp | 52 +++++++++++++-------- 5 files changed, 53 insertions(+), 33 deletions(-) diff --git a/common/database_instances.cpp b/common/database_instances.cpp index 372c6751f..f0bdda42e 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -493,6 +493,12 @@ void Database::DeleteInstance(uint16 instance_id) query = StringFormat("DELETE FROM spawn_condition_values WHERE instance_id=%u", instance_id); QueryDatabase(query); + query = fmt::format("DELETE FROM dynamic_zones WHERE instance_id={}", instance_id); + QueryDatabase(query); + + query = fmt::format("DELETE FROM expedition_details WHERE instance_id={}", instance_id); + QueryDatabase(query); + BuryCorpsesInInstance(instance_id); } @@ -582,8 +588,8 @@ void Database::PurgeExpiredInstances() QueryDatabase(fmt::format("DELETE FROM respawn_times WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("DELETE FROM spawn_condition_values WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("UPDATE character_corpses SET is_buried = 1, instance_id = 0 WHERE instance_id IN ({})", imploded_instance_ids)); - - + QueryDatabase(fmt::format("DELETE FROM dynamic_zones WHERE instance_id IN ({})", imploded_instance_ids)); + QueryDatabase(fmt::format("DELETE FROM expedition_details WHERE instance_id IN ({})", imploded_instance_ids)); } void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) diff --git a/common/database_schema.h b/common/database_schema.h index 0b6536d96..ad0b227cb 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -66,6 +66,7 @@ namespace DatabaseSchema { {"character_tribute", "id"}, {"completed_tasks", "charid"}, {"data_buckets", "id"}, + {"expedition_character_lockouts", "character_id"}, {"faction_values", "char_id"}, {"friends", "charid"}, {"guild_members", "char_id"}, @@ -131,6 +132,7 @@ namespace DatabaseSchema { "completed_tasks", "data_buckets", "discovered_items", + "expedition_character_lockouts", "faction_values", "friends", "guild_bank", @@ -305,7 +307,11 @@ namespace DatabaseSchema { "banned_ips", "bug_reports", "bugs", + "dynamic_zones", "eventlog", + "expedition_details", + "expedition_lockouts", + "expedition_members", "gm_ips", "group_id", "group_leaders", diff --git a/utils/sql/git/required/wip_dynamiczones.sql b/utils/sql/git/required/wip_dynamiczones.sql index c38487e31..dcc4575a2 100644 --- a/utils/sql/git/required/wip_dynamiczones.sql +++ b/utils/sql/git/required/wip_dynamiczones.sql @@ -17,8 +17,7 @@ CREATE TABLE `dynamic_zones` ( `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 + UNIQUE INDEX `instance_id` (`instance_id`) ) COLLATE='utf8mb4_general_ci' ENGINE=InnoDB diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 27ca3f5ae..971880e37 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -1,7 +1,7 @@ CREATE TABLE `expedition_details` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` VARCHAR(36) NOT NULL, - `instance_id` INT(10) NULL DEFAULT NULL, + `instance_id` INT(10) NOT NULL, `expedition_name` VARCHAR(128) NOT NULL, `leader_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, `min_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, @@ -9,8 +9,7 @@ CREATE TABLE `expedition_details` ( `add_replay_on_join` TINYINT(3) UNSIGNED NOT NULL DEFAULT 1, `is_locked` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), - UNIQUE INDEX `instance_id` (`instance_id`), - CONSTRAINT `FK_expedition_details_instance_list` FOREIGN KEY (`instance_id`) REFERENCES `instance_list` (`id`) ON DELETE SET NULL + UNIQUE INDEX `instance_id` (`instance_id`) ) COLLATE='utf8mb4_general_ci' ENGINE=InnoDB @@ -24,8 +23,7 @@ CREATE TABLE `expedition_lockouts` ( `duration` INT(10) UNSIGNED NOT NULL DEFAULT 0, `from_expedition_uuid` VARCHAR(36) NOT NULL, PRIMARY KEY (`id`), - UNIQUE INDEX `expedition_id_event_name` (`expedition_id`, `event_name`), - CONSTRAINT `FK_expedition_lockouts_expedition_details` FOREIGN KEY (`expedition_id`) REFERENCES `expedition_details` (`id`) ON DELETE CASCADE + UNIQUE INDEX `expedition_id_event_name` (`expedition_id`, `event_name`) ) COLLATE='latin1_swedish_ci' ENGINE=InnoDB @@ -36,8 +34,7 @@ CREATE TABLE `expedition_members` ( `expedition_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, `character_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), - UNIQUE INDEX `expedition_id_character_id` (`expedition_id`, `character_id`), - CONSTRAINT `FK_expedition_members_expedition_details` FOREIGN KEY (`expedition_id`) REFERENCES `expedition_details` (`id`) ON DELETE CASCADE + UNIQUE INDEX `expedition_id_character_id` (`expedition_id`, `character_id`) ) COLLATE='utf8mb4_general_ci' ENGINE=InnoDB @@ -46,11 +43,11 @@ ENGINE=InnoDB CREATE TABLE `expedition_character_lockouts` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `character_id` INT(10) UNSIGNED NOT NULL, + `expedition_name` VARCHAR(128) NOT NULL, + `event_name` VARCHAR(256) NOT NULL, `expire_time` DATETIME NOT NULL DEFAULT current_timestamp(), `duration` INT(10) UNSIGNED NOT NULL DEFAULT 0, `from_expedition_uuid` VARCHAR(36) NOT NULL, - `expedition_name` VARCHAR(128) NOT NULL, - `event_name` VARCHAR(256) NOT NULL, `is_pending` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE INDEX `character_id_expedition_name_event_name` (`character_id`, `expedition_name`, `event_name`) diff --git a/world/expedition.cpp b/world/expedition.cpp index 98675a7c9..7bd77b6c7 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -206,22 +206,33 @@ void ExpeditionCache::Process() void ExpeditionDatabase::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(*) member_count - FROM expedition_members - GROUP BY expedition_id - ) AS expedition_members - ON expedition_members.expedition_id = expedition.id + SELECT + expedition_details.id + FROM expedition_details + LEFT JOIN instance_list ON expedition_details.instance_id = instance_list.id + LEFT JOIN + ( + SELECT expedition_id, COUNT(*) member_count + FROM expedition_members + GROUP BY expedition_id + ) expedition_members + ON expedition_members.expedition_id = expedition_details.id WHERE - expedition.instance_id IS NULL + instance_list.id IS NULL OR expedition_members.member_count IS NULL OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); ); - database.QueryDatabase(query); + auto results = database.QueryDatabase(query); + if (results.Success()) + { + std::vector expedition_ids; + for (auto row = results.begin(); row != results.end(); ++row) + { + expedition_ids.emplace_back(static_cast(strtoul(row[0], nullptr, 10))); + } + ExpeditionDatabase::DeleteExpeditions(expedition_ids); + } } void ExpeditionDatabase::PurgeExpiredCharacterLockouts() @@ -330,6 +341,8 @@ Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) void ExpeditionDatabase::DeleteExpeditions(const std::vector& expedition_ids) { + LogExpeditionsDetail("Deleting [{}] expedition(s)", expedition_ids.size()); + std::string expedition_ids_query; for (const auto& expedition_id : expedition_ids) { @@ -345,16 +358,15 @@ void ExpeditionDatabase::DeleteExpeditions(const std::vector& expediti ); database.QueryDatabase(query); - // todo: if not using foreign key constraints - //query = fmt::format( - // "DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query - //); - //database.QueryDatabase(query); + query = fmt::format( + "DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); - //query = fmt::format( - // "DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query - //); - //database.QueryDatabase(query); + query = fmt::format( + "DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); } } From e99528fe732ccd12d9ed3c0a3a315ad6c095fb25 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 30 Jun 2020 20:53:05 -0400 Subject: [PATCH 085/196] Change lockout packet variable to signed --- common/eq_packet_structs.h | 2 +- common/patches/rof.cpp | 2 +- common/patches/rof2.cpp | 2 +- common/patches/rof2_structs.h | 2 +- common/patches/rof_structs.h | 2 +- common/patches/sod.cpp | 2 +- common/patches/sod_structs.h | 2 +- common/patches/sof.cpp | 2 +- common/patches/sof_structs.h | 2 +- common/patches/titanium.cpp | 2 +- common/patches/titanium_structs.h | 2 +- common/patches/uf.cpp | 2 +- common/patches/uf_structs.h | 2 +- zone/expedition.cpp | 4 ++-- zone/expedition.h | 4 ++-- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index da589abbf..22be26b1d 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -4896,7 +4896,7 @@ struct ExpeditionLockoutTimerEntry_Struct { /*000*/ char expedition_name[128]; // variable length, null terminated, max 0x80 (128) /*000*/ uint32 seconds_remaining; -/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ int32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers /*000*/ char event_name[256]; // variable length, null terminated, max 0x100 (256) }; diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 6d7d0021b..3549a0c2c 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -811,7 +811,7 @@ namespace RoF { buf.WriteString(emu->timers[i].expedition_name); buf.WriteUInt32(emu->timers[i].seconds_remaining); - buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteInt32(emu->timers[i].event_type); buf.WriteString(emu->timers[i].event_name); } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 671d539ed..bc53369ae 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -860,7 +860,7 @@ namespace RoF2 { buf.WriteString(emu->timers[i].expedition_name); buf.WriteUInt32(emu->timers[i].seconds_remaining); - buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteInt32(emu->timers[i].event_type); buf.WriteString(emu->timers[i].event_name); } diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 5e2b99ba2..b1be0c00a 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -4944,7 +4944,7 @@ struct ExpeditionLockoutTimerEntry_Struct { /*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) /*000*/ uint32 seconds_remaining; -/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ int32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers /*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) }; diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index e99a6b96a..6fa44177e 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -4876,7 +4876,7 @@ struct ExpeditionLockoutTimerEntry_Struct { /*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) /*000*/ uint32 seconds_remaining; -/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ int32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers /*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) }; diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index e84641919..1857cd731 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -584,7 +584,7 @@ namespace SoD { buf.WriteString(emu->timers[i].expedition_name); buf.WriteUInt32(emu->timers[i].seconds_remaining); - buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteInt32(emu->timers[i].event_type); buf.WriteString(emu->timers[i].event_name); } diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index f31ee6792..514722cb1 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -4231,7 +4231,7 @@ struct ExpeditionLockoutTimerEntry_Struct { /*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) /*000*/ uint32 seconds_remaining; -/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ int32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers /*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) }; diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index ccb1fd19e..ddef71c4c 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -572,7 +572,7 @@ namespace SoF { buf.WriteString(emu->timers[i].expedition_name); buf.WriteUInt32(emu->timers[i].seconds_remaining); - buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteInt32(emu->timers[i].event_type); buf.WriteString(emu->timers[i].event_name); } diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index fac58e554..7755e767f 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -4145,7 +4145,7 @@ struct ExpeditionLockoutTimerEntry_Struct { /*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) /*000*/ uint32 seconds_remaining; -/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ int32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers /*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) }; diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 5b97f7110..04fd10d9c 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -515,7 +515,7 @@ namespace Titanium { buf.WriteString(emu->timers[i].expedition_name); buf.WriteUInt32(emu->timers[i].seconds_remaining); - buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteInt32(emu->timers[i].event_type); buf.WriteString(emu->timers[i].event_name); } diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index ba5bb6ab5..092c3d2a3 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -3356,7 +3356,7 @@ struct ExpeditionLockoutTimerEntry_Struct { /*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) /*000*/ uint32 seconds_remaining; -/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ int32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers /*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) }; diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index fa3000346..599272ad4 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -714,7 +714,7 @@ namespace UF { buf.WriteString(emu->timers[i].expedition_name); buf.WriteUInt32(emu->timers[i].seconds_remaining); - buf.WriteUInt32(emu->timers[i].event_type); + buf.WriteInt32(emu->timers[i].event_type); buf.WriteString(emu->timers[i].event_name); } diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 7a8eab757..70c1c6cd1 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -4312,7 +4312,7 @@ struct ExpeditionLockoutTimerEntry_Struct { /*000*/ char expedition_name[1]; // variable length, null terminated, max 0x80 (128) /*000*/ uint32 seconds_remaining; -/*000*/ uint32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers +/*000*/ int32 event_type; // seen -1 (0xffffffff) for replay timers and 1 for event timers /*000*/ char event_name[1]; // variable length, null terminated, max 0x100 (256) }; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index eaa90c382..cb9ab6d0e 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -43,8 +43,8 @@ const char* const DZADD_INVITE_WARNING = "Warning! You will be given repla const char* const DZADD_INVITE_WARNING_TIMER = "%s - %sD:%sH:%sM"; const char* const KICKPLAYERS_EVERYONE = "Everyone"; -const uint32_t Expedition::REPLAY_TIMER_ID = std::numeric_limits::max(); -const uint32_t Expedition::EVENT_TIMER_ID = 1; +const int32_t Expedition::REPLAY_TIMER_ID = -1; +const int32_t Expedition::EVENT_TIMER_ID = 1; Expedition::Expedition( uint32_t id, const std::string& uuid, const DynamicZone& dynamic_zone, std::string expedition_name, diff --git a/zone/expedition.h b/zone/expedition.h index 5e9d4c28f..fbaebcb46 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -137,8 +137,8 @@ public: void SetDzZoneInLocation(float x, float y, float z, float heading, bool update_db = false); void SetDzDuration(uint32_t new_duration) { m_dynamiczone.SetUpdatedDuration(new_duration); } - static const uint32_t REPLAY_TIMER_ID; - static const uint32_t EVENT_TIMER_ID; + static const int32_t REPLAY_TIMER_ID; + static const int32_t EVENT_TIMER_ID; private: static void CacheExpeditions(MySQLRequestResult& results); From 184ea679f2efb0649bcd2c5ba3eaf0ded8674720 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 30 Jun 2020 21:34:50 -0400 Subject: [PATCH 086/196] Refactor client cross zone message helpers Remove extra cross zone message server opcode and struct. Existing function already exists for normal messages Group CZClientMessageString struct with other CZ structs --- common/servertalk.h | 26 +++++--------- world/zoneserver.cpp | 8 +---- zone/client.cpp | 67 +++++++++++++++++++------------------ zone/client.h | 4 +-- zone/expedition.cpp | 4 +-- zone/expedition.h | 3 +- zone/expedition_request.cpp | 4 +-- zone/expedition_request.h | 2 +- zone/worldserver.cpp | 11 +----- 9 files changed, 54 insertions(+), 75 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 80185ea05..5c8cf7a5d 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -282,8 +282,7 @@ #define ServerOP_CZTaskRemoveGroup 0x4560 #define ServerOP_CZTaskRemoveRaid 0x4561 #define ServerOP_CZTaskRemoveGuild 0x4562 -#define ServerOP_CZClientMessage 0x4563 -#define ServerOP_CZClientMessageString 0x4564 +#define ServerOP_CZClientMessageString 0x4563 #define ServerOP_WWAssignTask 0x4750 #define ServerOP_WWCastSpell 0x4751 @@ -1460,6 +1459,14 @@ struct CZNPCSignal_Struct { uint32 signal; }; +struct CZClientMessageString_Struct { + uint32 string_id; + uint16 chat_type; + char character_name[64]; + uint32 args_size; + char args[1]; // null delimited +}; + struct CZClientSignalByName_Struct { char character_name[64]; uint32 signal; @@ -1985,21 +1992,6 @@ 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; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 71af4a1d1..3628991f0 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1356,15 +1356,9 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { cle->ProcessTellQueue(); break; } - case ServerOP_CZClientMessage: - { - auto buf = reinterpret_cast(pack->pBuffer); - client_list.SendPacket(buf->character_name, pack); - break; - } case ServerOP_CZClientMessageString: { - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); client_list.SendPacket(buf->character_name, pack); break; } diff --git a/zone/client.cpp b/zone/client.cpp index b502c4381..898b4b5ac 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -3207,22 +3207,22 @@ void Client::MessageString(uint32 type, uint32 string_id, const char* message1, safe_delete(outapp); } -void Client::MessageString(const ServerCZClientMessageString_Struct* msg) +void Client::MessageString(const CZClientMessageString_Struct* msg) { if (msg) { - if (msg->string_params_size == 0) + if (msg->args_size == 0) { MessageString(msg->chat_type, msg->string_id); } else { - uint32_t outsize = sizeof(FormattedMessage_Struct) + msg->string_params_size; + uint32_t outsize = sizeof(FormattedMessage_Struct) + msg->args_size; auto outapp = std::unique_ptr(new EQApplicationPacket(OP_FormattedMessage, outsize)); auto outbuf = reinterpret_cast(outapp->pBuffer); outbuf->string_id = msg->string_id; outbuf->type = msg->chat_type; - memcpy(outbuf->message, msg->string_params, msg->string_params_size); + memcpy(outbuf->message, msg->args, msg->args_size); QueuePacket(outapp.get()); } } @@ -9496,7 +9496,7 @@ 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) + if (!client && !character_name.empty()) { client = entity_list.GetClientByName(character_name.c_str()); } @@ -9505,16 +9505,14 @@ void Client::SendCrossZoneMessage( { client->Message(chat_type, message.c_str()); } - else if (message.size() > 0) + else if (!character_name.empty() && !message.empty()) { - uint32_t msg_size = static_cast(message.size()) + 1; - uint32_t pack_size = sizeof(ServerCZClientMessage_Struct) + msg_size; - auto pack = std::unique_ptr(new ServerPacket(ServerOP_CZClientMessage, pack_size)); - auto buf = reinterpret_cast(pack->pBuffer); - buf->chat_type = chat_type; + uint32_t pack_size = sizeof(CZMessagePlayer_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_CZMessagePlayer, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->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); + strn0cpy(buf->message, message.c_str(), sizeof(buf->message)); worldserver.SendPacket(pack.get()); } @@ -9522,32 +9520,35 @@ void Client::SendCrossZoneMessage( void Client::SendCrossZoneMessageString( Client* client, const std::string& character_name, uint16_t chat_type, - uint32_t string_id, const std::initializer_list& parameters) + uint32_t string_id, const std::initializer_list& arguments) { // 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(parameter_buffer.size()); - auto pack = std::unique_ptr(new ServerPacket(ServerOP_CZClientMessageString, pack_size)); - auto buf = reinterpret_cast(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(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 + if (!client && !character_name.empty()) // double check client isn't in this zone { client = entity_list.GetClientByName(character_name.c_str()); } + if (!client && character_name.empty()) + { + return; + } + + SerializeBuffer argument_buffer; + for (const auto& argument : arguments) + { + argument_buffer.WriteString(argument); + } + + uint32_t args_size = static_cast(argument_buffer.size()); + uint32_t pack_size = sizeof(CZClientMessageString_Struct) + args_size; + auto pack = std::unique_ptr(new ServerPacket(ServerOP_CZClientMessageString, pack_size)); + auto buf = reinterpret_cast(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->args_size = args_size; + memcpy(buf->args, argument_buffer.buffer(), argument_buffer.size()); + if (client) { client->MessageString(buf); diff --git a/zone/client.h b/zone/client.h index f55b9502f..ea86e47fd 100644 --- a/zone/client.h +++ b/zone/client.h @@ -288,7 +288,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); + void MessageString(const CZClientMessageString_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, @@ -1115,7 +1115,7 @@ public: 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& parameters = {}); + uint32_t string_id, const std::initializer_list& arguments = {}); void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false, bool update_client = true); void AddNewExpeditionLockout( diff --git a/zone/expedition.cpp b/zone/expedition.cpp index cb9ab6d0e..098ed66f3 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -682,9 +682,9 @@ void Expedition::SendClientExpeditionInvite( } void Expedition::SendLeaderMessage( - Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& parameters) + Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& args) { - Client::SendCrossZoneMessageString(leader_client, m_leader.name, chat_type, string_id, parameters); + Client::SendCrossZoneMessageString(leader_client, m_leader.name, chat_type, string_id, args); } bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping) diff --git a/zone/expedition.h b/zone/expedition.h index fbaebcb46..5233c99d3 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -157,7 +157,8 @@ private: 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& parameters = {}); + void SendLeaderMessage(Client* leader_client, uint16_t chat_type, uint32_t string_id, + const std::initializer_list& args = {}); void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); void SendWorldExpeditionUpdate(uint16_t server_opcode); diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 2e7f77582..23f4e566b 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -285,11 +285,11 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& } void ExpeditionRequest::SendLeaderMessage( - uint16_t chat_type, uint32_t string_id, const std::initializer_list& parameters) + uint16_t chat_type, uint32_t string_id, const std::initializer_list& args) { if (!m_disable_messages) { - Client::SendCrossZoneMessageString(m_leader, m_leader_name, chat_type, string_id, parameters); + Client::SendCrossZoneMessageString(m_leader, m_leader_name, chat_type, string_id, args); } } diff --git a/zone/expedition_request.h b/zone/expedition_request.h index db47ba6ca..d0afb46c0 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -63,7 +63,7 @@ private: 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& parameters = {}); + void SendLeaderMessage(uint16_t chat_type, uint32_t string_id, const std::initializer_list& args = {}); Client* m_requester = nullptr; Client* m_leader = nullptr; diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 6fb577515..fb8faeb1c 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2881,18 +2881,9 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } break; } - case ServerOP_CZClientMessage: - { - auto buf = reinterpret_cast(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(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); Client* client = entity_list.GetClientByName(buf->character_name); if (client) { client->MessageString(buf); From 3843ed6540cdb6254cb4e21a532a0ddef023af4b Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 1 Jul 2020 18:57:56 -0400 Subject: [PATCH 087/196] Don't log expired lockouts in expedition request Removes logging of lockouts with timer under leeway rule --- zone/expedition_request.cpp | 49 +++++++++++++++---------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 23f4e566b..55451431b 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -166,15 +166,8 @@ bool ExpeditionRequest::LoadLeaderLockouts() for (auto& lockout : lockouts) { - // client window hides timers with less than 60s remaining, optionally count them as expired - if (lockout.GetSecondsRemaining() <= leeway_seconds) - { - LogExpeditionsModerate( - "Ignoring leader [{}] lockout [{}] with [{}s] remaining due to leeway rule [{}s]", - m_leader_id, lockout.GetEventName(), lockout.GetSecondsRemaining(), leeway_seconds - ); - } - else + bool is_expired = lockout.IsExpired() || lockout.GetSecondsRemaining() <= leeway_seconds; + if (!is_expired) { m_lockouts.emplace(lockout.GetEventName(), lockout); @@ -248,28 +241,25 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& ExpeditionLockoutTimer lockout{row[3], m_expedition_name, row[6], expire_time, duration}; - // client window hides timers with less than 60s remaining, optionally count them as expired - if (lockout.GetSecondsRemaining() <= leeway_seconds) + // client window hides timers with less than 60s remaining, optionally count as expired + bool is_expired = lockout.IsExpired() || lockout.GetSecondsRemaining() <= leeway_seconds; + if (!is_expired) { - LogExpeditionsModerate( - "Ignoring character [{}] lockout [{}] with [{}s] remaining due to leeway rule [{}s]", - character_id, lockout.GetEventName(), lockout.GetSecondsRemaining(), leeway_seconds - ); - } - else if (lockout.IsReplayTimer()) - { - // replay timer conflict messages always show up before event conflicts - has_conflicts = true; - SendLeaderMemberReplayLockout(character_name, lockout, is_solo); - } - else if (m_check_event_lockouts && character_id != m_leader_id) - { - if (m_lockouts.find(lockout.GetEventName()) == m_lockouts.end()) + if (lockout.IsReplayTimer()) { - // leader doesn't have this lockout. queue instead of messaging - // now so message comes after any replay lockout messages + // replay timer conflict messages always show up before event conflicts has_conflicts = true; - member_lockout_conflicts.emplace_back(ExpeditionRequestConflict{character_name, lockout}); + SendLeaderMemberReplayLockout(character_name, lockout, is_solo); + } + else if (m_check_event_lockouts && character_id != m_leader_id) + { + if (m_lockouts.find(lockout.GetEventName()) == m_lockouts.end()) + { + // leader doesn't have this lockout. queue instead of messaging + // now so message comes after any replay lockout messages + has_conflicts = true; + member_lockout_conflicts.emplace_back(ExpeditionRequestConflict{character_name, lockout}); + } } } } @@ -295,7 +285,8 @@ void ExpeditionRequest::SendLeaderMessage( void ExpeditionRequest::SendLeaderMemberInExpedition(const std::string& member_name, bool is_solo) { - if (m_disable_messages) { + if (m_disable_messages) + { return; } From 5a826add92556cf2c16a3745836e5b3a00970b8e Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 1 Jul 2020 20:18:00 -0400 Subject: [PATCH 088/196] Cleanup expedition headers and unused variables Modify some expedition and dz logging Remove unnecessary includes in expedition sources --- zone/client.cpp | 16 ++-------------- zone/dynamiczone.cpp | 6 ++---- zone/dynamiczone.h | 24 ++++++++++-------------- zone/entity.cpp | 5 ++--- zone/expedition.cpp | 15 +++------------ zone/expedition.h | 12 ++++++++---- zone/expedition_lockout_timer.cpp | 1 - zone/expedition_request.cpp | 1 - 8 files changed, 27 insertions(+), 53 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 898b4b5ac..3bfa0f4e0 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9766,7 +9766,6 @@ void Client::SendExpeditionLockoutTimers() uint32_t outsize = sizeof(ExpeditionLockoutTimers_Struct) + lockout_entries_size; auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzExpeditionLockoutTimers, outsize)); auto outbuf = reinterpret_cast(outapp->pBuffer); - outbuf->client_id = 0; outbuf->count = lockout_count; if (!lockout_entries.empty()) { @@ -9853,7 +9852,7 @@ void Client::SendDzCompassUpdate() } } - // todo: shared tasks, missions, and quests with an associated dz + // todo: tasks, missions, and quests with an associated dz // compass set via MarkSingleCompassLocation() if (m_has_quest_compass) @@ -9874,7 +9873,6 @@ void Client::SendDzCompassUpdate() 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); @@ -9884,15 +9882,7 @@ void Client::SendDzCompassUpdate() 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 + "Sending character [{}] to safereturn zone [{}] or bind", CharacterID(), safereturn.zone_id ); if (safereturn.zone_id == 0) @@ -9925,7 +9915,6 @@ void Client::MovePCDynamicZone(uint32 zone_id) 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)); @@ -9970,7 +9959,6 @@ void Client::MovePCDynamicZone(uint32 zone_id) 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); diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 0a7bad7e7..17de013d8 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -23,7 +23,6 @@ #include "worldserver.h" #include "zonedb.h" #include "../common/eqemu_logsys.h" -#include extern WorldServer worldserver; @@ -558,10 +557,9 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack) { for (const auto& client_list_iter : entity_list.GetClientList()) { - Client* client = client_list_iter.second; - if (client) + if (client_list_iter.second) { - client->SetDzRemovalTimer(true); + client_list_iter.second->SetDzRemovalTimer(true); } } } diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index b1f56031e..cdff3c4e2 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -67,37 +67,33 @@ public: const std::vector& instance_ids); static void HandleWorldMessage(ServerPacket* pack); - DynamicZoneType GetType() const { return m_type; } + 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 GetSecondsRemaining() const; + uint16_t GetZoneID() const { return static_cast(m_zone_id); }; + uint32_t GetZoneVersion() const { return m_version; }; + 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::vector& 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; }; - + uint32_t CreateInstance(); 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 LoadFromDatabase(uint32_t instance_id); void RemoveAllCharacters(bool enable_removal_timers = true); void RemoveCharacter(uint32_t character_id); + void SaveInstanceMembersToDatabase(const std::vector& character_ids); + uint32_t SaveToDatabase(); 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 SetUpdatedDuration(uint32_t seconds); - void LoadFromDatabase(uint32_t instance_id); - uint32_t SaveToDatabase(); - private: static std::string DynamicZoneSelectQuery(); void LoadDatabaseResult(MySQLRequestRow& row); diff --git a/zone/entity.cpp b/zone/entity.cpp index 68364ff82..02c901689 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -5218,11 +5218,10 @@ void EntityList::GateAllClientsToSafeReturn() for (const auto& client_list_iter : client_list) { - Client* client = client_list_iter.second; - if (client) + if (client_list_iter.second) { // falls back to gating clients to bind if dz invalid - client->GoToDzSafeReturnOrBind(dz.GetSafeReturnLocation()); + client_list_iter.second->GoToDzSafeReturnOrBind(dz.GetSafeReturnLocation()); } } } diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 098ed66f3..1ffa2e386 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -23,8 +23,6 @@ #include "expedition_lockout_timer.h" #include "expedition_request.h" #include "client.h" -#include "groups.h" -#include "raids.h" #include "string_ids.h" #include "worldserver.h" #include "zonedb.h" @@ -71,9 +69,7 @@ Expedition* Expedition::TryCreate( // request parses leader, members list, and lockouts while validating if (!request.Validate(requester)) { - LogExpeditionsModerate( - "Creation of [{}] by [{}] denied", request.GetExpeditionName(), requester->GetName() - ); + LogExpeditionsModerate("[{}] request by [{}] denied", request.GetExpeditionName(), requester->GetName()); return nullptr; } @@ -900,8 +896,8 @@ bool Expedition::ConfirmLeaderCommand(Client* requester) } void Expedition::TryAddClient( - Client* add_client, std::string inviter_name, std::string orig_add_name, - std::string swap_remove_name, Client* leader_client) + Client* add_client, const std::string& inviter_name, const std::string& orig_add_name, + const std::string& swap_remove_name, Client* leader_client) { if (!add_client) { @@ -1350,7 +1346,6 @@ std::unique_ptr Expedition::CreateInfoPacket(bool clear) auto info = reinterpret_cast(outapp->pBuffer); if (!clear) { - info->client_id = 0; info->assigned = true; strn0cpy(info->expedition_name, m_expedition_name.c_str(), sizeof(info->expedition_name)); strn0cpy(info->leader_name, m_leader.name.c_str(), sizeof(info->leader_name)); @@ -1382,7 +1377,6 @@ std::unique_ptr Expedition::CreateMemberListPacket(bool cle auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzMemberList, outsize)); auto buf = reinterpret_cast(outapp->pBuffer); - buf->client_id = 0; buf->count = member_count; if (!clear) @@ -1403,7 +1397,6 @@ std::unique_ptr Expedition::CreateMemberListNamePacket( uint32_t outsize = sizeof(ExpeditionMemberListName_Struct); auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzMemberListName, outsize)); auto buf = reinterpret_cast(outapp->pBuffer); - buf->client_id = 0; buf->add_name = !remove_name; strn0cpy(buf->name, name.c_str(), sizeof(buf->name)); return outapp; @@ -1416,7 +1409,6 @@ std::unique_ptr Expedition::CreateMemberListStatusPacket( uint32_t outsize = sizeof(ExpeditionMemberList_Struct) + sizeof(ExpeditionMemberEntry_Struct); auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzMemberListStatus, outsize)); auto buf = reinterpret_cast(outapp->pBuffer); - buf->client_id = 0; buf->count = 1; auto entry = reinterpret_cast(buf->members); @@ -1431,7 +1423,6 @@ std::unique_ptr Expedition::CreateLeaderNamePacket() uint32_t outsize = sizeof(ExpeditionSetLeaderName_Struct); auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzSetLeaderName, outsize)); auto buf = reinterpret_cast(outapp->pBuffer); - buf->client_id = 0; strn0cpy(buf->leader_name, m_leader.name.c_str(), sizeof(buf->leader_name)); return outapp; } diff --git a/zone/expedition.h b/zone/expedition.h index 5233c99d3..002b3c0d4 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -156,21 +156,25 @@ private: 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 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& args = {}); void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); void SendWorldExpeditionUpdate(uint16_t server_opcode); - void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); + void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, + const std::string& add_name, bool pending = false); void SendWorldLeaderChanged(); void SendWorldLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = 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 SendWorldMemberSwapped(const std::string& remove_char_name, uint32_t remove_char_id, + const std::string& add_char_name, uint32_t add_char_id); void SendWorldSettingChanged(uint16_t server_opcode, bool setting_value); - void TryAddClient(Client* add_client, std::string inviter_name, std::string orig_add_name, std::string swap_remove_name, Client* leader_client = nullptr); + void TryAddClient(Client* add_client, const std::string& inviter_name, const std::string& orig_add_name, + const std::string& swap_remove_name, Client* leader_client = nullptr); void UpdateMemberStatus(uint32_t update_character_id, ExpeditionMemberStatus status); ExpeditionMember GetMemberData(uint32_t character_id); diff --git a/zone/expedition_lockout_timer.cpp b/zone/expedition_lockout_timer.cpp index 01b1e607a..a070ce582 100644 --- a/zone/expedition_lockout_timer.cpp +++ b/zone/expedition_lockout_timer.cpp @@ -21,7 +21,6 @@ #include "expedition_lockout_timer.h" #include "../common/string_util.h" #include -#include const char* const DZ_REPLAY_TIMER_NAME = "Replay Timer"; // see December 14, 2016 patch notes diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 55451431b..663460e20 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -27,7 +27,6 @@ #include "raids.h" #include "string_ids.h" #include "worldserver.h" -#include extern WorldServer worldserver; From 3e373210c5986118cbce42e79af078d39c52cedd Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 2 Jul 2020 00:15:53 -0400 Subject: [PATCH 089/196] Remove dz check in expedition invite reply Characters can no longer be re-invited while inside a dz, these checks aren't necessary --- zone/expedition.cpp | 9 +-------- zone/expedition_database.cpp | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 1ffa2e386..3fc07b099 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -842,10 +842,8 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: } } - bool add_immediately = m_dynamiczone.IsCurrentZoneDzInstance(); - ExpeditionDatabase::InsertCharacterLockouts( - add_client->CharacterID(), pending_lockouts, false, !add_immediately); + add_client->CharacterID(), pending_lockouts, false, true); if (was_swap_invite) { @@ -855,11 +853,6 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: { AddMember(add_client->GetName(), add_client->CharacterID()); } - - if (m_dynamiczone.IsCurrentZoneDzInstance()) - { - SetMemberStatus(add_client, ExpeditionMemberStatus::InDynamicZone); - } } } diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 9f77d537c..f8df4cb81 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -455,7 +455,7 @@ void ExpeditionDatabase::InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, bool replace_timer, bool is_pending) { - LogExpeditionsDetail("Inserting character [{}] lockouts", character_id); + LogExpeditionsDetail("Inserting [{}] lockouts for character [{}]", lockouts.size(), character_id); std::string insert_values; for (const auto& lockout : lockouts) From d61879fd3cc1490bfb076ebcc2a474123b8fb904 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 2 Jul 2020 22:50:56 -0400 Subject: [PATCH 090/196] Add character id based expedition apis Add static expedition methods to add or remove character lockouts Add CreateLockout static helper to ExpeditionLockoutTimer Refactor existing character lockout removal to allow removal of lockouts for offline characters (was only used by #dz lockouts remove command) Fix #dz list member count --- common/servertalk.h | 12 ++-- world/expedition.cpp | 8 ++- world/zoneserver.cpp | 2 +- zone/client.cpp | 24 ++++--- zone/client.h | 2 +- zone/command.cpp | 4 +- zone/expedition.cpp | 103 +++++++++++++++++++++++++--- zone/expedition.h | 11 ++- zone/expedition_lockout_timer.cpp | 14 ++++ zone/expedition_lockout_timer.h | 4 ++ zone/lua_client.cpp | 4 +- zone/lua_general.cpp | 107 ++++++++++++++++++++++++++++-- zone/questmgr.cpp | 20 ------ zone/questmgr.h | 3 - zone/worldserver.cpp | 2 +- 15 files changed, 257 insertions(+), 63 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 5c8cf7a5d..32e3cd812 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -154,7 +154,7 @@ #define ServerOP_ExpeditionDzCompass 0x040a #define ServerOP_ExpeditionDzSafeReturn 0x040b #define ServerOP_ExpeditionDzZoneIn 0x040c -#define ServerOP_ExpeditionRemoveCharLockouts 0x040d +#define ServerOP_ExpeditionCharacterLockout 0x040d #define ServerOP_ExpeditionSaveInvite 0x040e #define ServerOP_ExpeditionRequestInvite 0x040f #define ServerOP_ExpeditionReplayOnJoin 0x0410 @@ -2059,9 +2059,13 @@ struct ServerExpeditionSetting_Struct { }; struct ServerExpeditionCharacterLockout_Struct { - char character_name[64]; - char expedition_name[128]; - char event_name[256]; + uint8 remove; + uint32 character_id; + uint64 expire_time; + uint32 duration; + char uuid[37]; + char expedition_name[128]; + char event_name[256]; }; struct ServerExpeditionCharacterID_Struct { diff --git a/world/expedition.cpp b/world/expedition.cpp index 7bd77b6c7..c880a2855 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -428,10 +428,14 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) ExpeditionMessage::MakeLeader(pack); break; } - case ServerOP_ExpeditionRemoveCharLockouts: + case ServerOP_ExpeditionCharacterLockout: { auto buf = reinterpret_cast(pack->pBuffer); - client_list.SendPacket(buf->character_name, pack); + auto cle = client_list.FindCLEByCharacterID(buf->character_id); + if (cle && cle->Server()) + { + cle->Server()->SendPacket(pack); + } break; } case ServerOP_ExpeditionSaveInvite: diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 3628991f0..4b750ed99 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1381,7 +1381,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ExpeditionMembersRemoved: case ServerOP_ExpeditionDzAddPlayer: case ServerOP_ExpeditionDzMakeLeader: - case ServerOP_ExpeditionRemoveCharLockouts: + case ServerOP_ExpeditionCharacterLockout: case ServerOP_ExpeditionSaveInvite: case ServerOP_ExpeditionRequestInvite: { diff --git a/zone/client.cpp b/zone/client.cpp index 3bfa0f4e0..71fffe804 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -39,7 +39,6 @@ extern volatile bool RunLoops; #include "../common/string_util.h" #include "../common/data_verification.h" #include "../common/profanity_manager.h" -#include "../common/util/uuid.h" #include "data_bucket.h" #include "expedition.h" #include "expedition_database.h" @@ -9643,12 +9642,7 @@ void Client::AddExpeditionLockout( void Client::AddNewExpeditionLockout( const std::string& expedition_name, const std::string& event_name, uint32_t seconds, std::string uuid) { - if (uuid.empty()) - { - uuid = EQ::Util::UUID::Generate().ToString(); - } - ExpeditionLockoutTimer lockout{uuid, expedition_name, event_name, 0, seconds}; - lockout.Reset(); // sets expire time + auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); AddExpeditionLockout(lockout, true); } @@ -9672,22 +9666,30 @@ void Client::RemoveExpeditionLockout( } } -void Client::RemoveAllExpeditionLockouts(std::string expedition_name) +void Client::RemoveAllExpeditionLockouts(const std::string& expedition_name, bool update_db) { if (expedition_name.empty()) { - ExpeditionDatabase::DeleteAllCharacterLockouts(CharacterID()); + if (update_db) + { + ExpeditionDatabase::DeleteAllCharacterLockouts(CharacterID()); + } m_expedition_lockouts.clear(); } else { - ExpeditionDatabase::DeleteAllCharacterLockouts(CharacterID(), expedition_name); + if (update_db) + { + ExpeditionDatabase::DeleteAllCharacterLockouts(CharacterID(), expedition_name); + } + m_expedition_lockouts.erase(std::remove_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) { return lockout.GetExpeditionName() == expedition_name; } ), m_expedition_lockouts.end()); } + SendExpeditionLockoutTimers(); } @@ -9728,6 +9730,8 @@ bool Client::HasExpeditionLockout( void Client::LoadAllExpeditionLockouts() { + m_expedition_lockouts.clear(); + auto lockouts = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); for (const auto& lockout : lockouts) { diff --git a/zone/client.h b/zone/client.h index ea86e47fd..50abfddc3 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1133,7 +1133,7 @@ public: uint32 GetPendingExpeditionInviteID() const { return m_pending_expedition_invite.expedition_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 RemoveAllExpeditionLockouts(std::string expedition_name = {}); + void RemoveAllExpeditionLockouts(const std::string& expedition_name, bool update_db = false); void RemoveExpeditionLockout( const std::string& expedition_name, const std::string& event_name, bool update_db = false, bool update_client = true); void RequestPendingExpeditionInvite(); diff --git a/zone/command.cpp b/zone/command.cpp index caa7684ab..f2667ebd9 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6889,7 +6889,7 @@ void command_dz(Client* c, const Seperator* sep) instance_list.version, instance_list.start_time, instance_list.duration, - COUNT(instance_list.id) member_count + COUNT(instance_list_player.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 @@ -6944,7 +6944,7 @@ void command_dz(Client* c, const Seperator* sep) "Removing [{}]:[{}] lockout on [{}].", sep->arg[4], sep->arg[5], sep->arg[3] ).c_str()); } - Expedition::RemoveCharacterLockouts(sep->arg[3], sep->arg[4], sep->arg[5]); + Expedition::RemoveLockoutsByCharacterName(sep->arg[3], sep->arg[4], sep->arg[5]); } } else diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 3fc07b099..6fb0ec226 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1586,18 +1586,78 @@ void Expedition::SendWorldGetOnlineMembers( worldserver.SendPacket(pack.get()); } -void Expedition::RemoveCharacterLockouts( - std::string character_name, std::string expedition_name, std::string event_name) +void Expedition::SendWorldCharacterLockout( + uint32_t character_id, const ExpeditionLockoutTimer& lockout, bool remove) { uint32_t pack_size = sizeof(ServerExpeditionCharacterLockout_Struct); - auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionRemoveCharLockouts, pack_size)); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionCharacterLockout, pack_size)); auto buf = reinterpret_cast(pack->pBuffer); - strn0cpy(buf->character_name, character_name.c_str(), sizeof(buf->character_name)); - strn0cpy(buf->expedition_name, expedition_name.c_str(), sizeof(buf->expedition_name)); - strn0cpy(buf->event_name, event_name.c_str(), sizeof(buf->event_name)); + buf->remove = remove; + buf->character_id = character_id; + buf->expire_time = lockout.GetExpireTime(); + buf->duration = lockout.GetDuration(); + strn0cpy(buf->uuid, lockout.GetExpeditionUUID().c_str(), sizeof(buf->uuid)); + strn0cpy(buf->expedition_name, lockout.GetExpeditionName().c_str(), sizeof(buf->expedition_name)); + strn0cpy(buf->event_name, lockout.GetEventName().c_str(), sizeof(buf->event_name)); worldserver.SendPacket(pack.get()); } +void Expedition::AddLockoutByCharacterID( + uint32_t character_id, const std::string& expedition_name, const std::string& event_name, + uint32_t seconds, const std::string& uuid) +{ + if (character_id) + { + auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); + ExpeditionDatabase::InsertCharacterLockouts(character_id, { lockout }, true); + SendWorldCharacterLockout(character_id, lockout, false); + } +} + +void Expedition::AddLockoutByCharacterName( + const std::string& character_name, const std::string& expedition_name, const std::string& event_name, + uint32_t seconds, const std::string& uuid) +{ + if (!character_name.empty()) + { + uint32_t character_id = database.GetCharacterID(character_name.c_str()); + AddLockoutByCharacterID(character_id, expedition_name, event_name, seconds, uuid); + } +} + +void Expedition::RemoveLockoutsByCharacterID( + uint32_t character_id, const std::string& expedition_name, const std::string& event_name) +{ + if (character_id) + { + if (!event_name.empty()) + { + ExpeditionDatabase::DeleteCharacterLockout(character_id, expedition_name, event_name); + } + else if (!expedition_name.empty()) + { + ExpeditionDatabase::DeleteAllCharacterLockouts(character_id, expedition_name); + } + else + { + ExpeditionDatabase::DeleteAllCharacterLockouts(character_id); + } + + ExpeditionLockoutTimer lockout{{}, expedition_name, event_name, 0, 0}; + SendWorldCharacterLockout(character_id, lockout, true); + } +} + +void Expedition::RemoveLockoutsByCharacterName( + const std::string& character_name, const std::string& expedition_name, const std::string& event_name) +{ + if (!character_name.empty()) + { + uint32_t character_id = database.GetCharacterID(character_name.c_str()); + RemoveLockoutsByCharacterID(character_id, expedition_name, event_name); + } +} + void Expedition::HandleWorldMessage(ServerPacket* pack) { switch (pack->opcode) @@ -1818,15 +1878,21 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } - case ServerOP_ExpeditionRemoveCharLockouts: + case ServerOP_ExpeditionCharacterLockout: { auto buf = reinterpret_cast(pack->pBuffer); - Client* client = entity_list.GetClientByName(buf->character_name); + Client* client = entity_list.GetClientByCharID(buf->character_id); if (client) { - if (buf->event_name[0] != '\0') + if (!buf->remove) { - client->RemoveExpeditionLockout(buf->expedition_name, buf->event_name, true); + client->AddExpeditionLockout(ExpeditionLockoutTimer{ + buf->uuid, buf->expedition_name, buf->event_name, buf->expire_time, buf->duration + }); + } + else if (buf->event_name[0] != '\0') + { + client->RemoveExpeditionLockout(buf->expedition_name, buf->event_name); } else { @@ -1982,3 +2048,20 @@ std::string Expedition::GetLootEventBySpawnID(uint32_t spawn_id) return event_name; } + +std::vector Expedition::GetExpeditionLockoutsByCharacterID(uint32_t character_id) +{ + std::vector lockouts; + + auto client = entity_list.GetClientByCharID(character_id); + if (client) + { + lockouts = client->GetExpeditionLockouts(); + } + else + { + lockouts = ExpeditionDatabase::LoadCharacterLockouts(character_id); + } + + return lockouts; +} diff --git a/zone/expedition.h b/zone/expedition.h index 002b3c0d4..ba6ba09e9 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -77,8 +77,16 @@ public: static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name); static Expedition* FindCachedExpeditionByID(uint32_t expedition_id); static Expedition* FindCachedExpeditionByInstanceID(uint32_t instance_id); - static void RemoveCharacterLockouts(std::string character_name, std::string expedition_name = {}, std::string event_name = {}); + static std::vector GetExpeditionLockoutsByCharacterID(uint32_t character_id); static void HandleWorldMessage(ServerPacket* pack); + static void AddLockoutByCharacterID(uint32_t character_id, const std::string& expedition_name, + const std::string& event_name, uint32_t seconds, const std::string& uuid = {}); + static void AddLockoutByCharacterName(const std::string& character_name, const std::string& expedition_name, + const std::string& event_name, uint32_t seconds, const std::string& uuid = {}); + static void RemoveLockoutsByCharacterID(uint32_t character_id, + const std::string& expedition_name = {}, const std::string& event_name = {}); + static void RemoveLockoutsByCharacterName(const std::string& character_name, + const std::string& expedition_name = {}, const std::string& event_name = {}); uint32_t GetID() const { return m_id; } uint16_t GetInstanceID() const { return m_dynamiczone.GetInstanceID(); } @@ -143,6 +151,7 @@ public: private: static void CacheExpeditions(MySQLRequestResult& results); static void SendWorldGetOnlineMembers(const std::vector>& expedition_character_ids); + static void SendWorldCharacterLockout(uint32_t character_id, const ExpeditionLockoutTimer& lockout, bool remove); void AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only = false); void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status); diff --git a/zone/expedition_lockout_timer.cpp b/zone/expedition_lockout_timer.cpp index a070ce582..ef0358122 100644 --- a/zone/expedition_lockout_timer.cpp +++ b/zone/expedition_lockout_timer.cpp @@ -20,6 +20,7 @@ #include "expedition_lockout_timer.h" #include "../common/string_util.h" +#include "../common/util/uuid.h" #include const char* const DZ_REPLAY_TIMER_NAME = "Replay Timer"; // see December 14, 2016 patch notes @@ -40,6 +41,19 @@ ExpeditionLockoutTimer::ExpeditionLockoutTimer( } } +ExpeditionLockoutTimer ExpeditionLockoutTimer::CreateLockout( + const std::string& expedition_name, const std::string& event_name, uint32_t seconds, std::string uuid) +{ + if (uuid.empty()) + { + uuid = EQ::Util::UUID::Generate().ToString(); + } + + ExpeditionLockoutTimer lockout{uuid, expedition_name, event_name, 0, seconds}; + lockout.Reset(); // sets expire time + return lockout; +} + uint32_t ExpeditionLockoutTimer::GetSecondsRemaining() const { auto now = std::chrono::system_clock::now(); diff --git a/zone/expedition_lockout_timer.h b/zone/expedition_lockout_timer.h index 5325d0f78..5527e42cf 100644 --- a/zone/expedition_lockout_timer.h +++ b/zone/expedition_lockout_timer.h @@ -34,6 +34,10 @@ public: const std::string& expedition_uuid, const std::string& expedition_name, const std::string& event_name, uint64_t expire_time, uint32_t duration); + static ExpeditionLockoutTimer CreateLockout( + const std::string& expedition_name, const std::string& event_name, + uint32_t seconds, std::string uuid = {}); + struct DaysHoursMinutes { std::string days; diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index bf896e32d..67eba3e27 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1791,12 +1791,12 @@ void Lua_Client::AddExpeditionLockout(std::string expedition_name, std::string e void Lua_Client::RemoveAllExpeditionLockouts() { Lua_Safe_Call_Void(); - self->RemoveAllExpeditionLockouts(); + self->RemoveAllExpeditionLockouts({}, true); } void Lua_Client::RemoveAllExpeditionLockouts(std::string expedition_name) { Lua_Safe_Call_Void(); - self->RemoveAllExpeditionLockouts(expedition_name); + self->RemoveAllExpeditionLockouts(expedition_name, true); } void Lua_Client::RemoveExpeditionLockout(std::string expedition_name, std::string event_name) { diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 5dbb8108c..1e5e12272 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -25,6 +25,7 @@ #include "encounter.h" #include "lua_encounter.h" #include "data_bucket.h" +#include "expedition.h" struct Events { }; struct Factions { }; @@ -2180,15 +2181,101 @@ void lua_set_content_flag(std::string flag_name, bool enabled){ } Lua_Expedition lua_get_expedition() { - return quest_manager.GetExpeditionForCurrentInstance(); + if (zone && zone->GetInstanceID() != 0) + { + return Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); + } + return nullptr; } Lua_Expedition lua_get_expedition_by_char_id(uint32 char_id) { - return quest_manager.GetExpeditionByCharID(char_id); + return Expedition::FindCachedExpeditionByCharacterID(char_id); } Lua_Expedition lua_get_expedition_by_instance_id(uint32 instance_id) { - return quest_manager.GetExpeditionByInstanceID(instance_id); + return Expedition::FindCachedExpeditionByInstanceID(instance_id); +} + +luabind::object lua_get_expedition_lockout_by_char_id(lua_State* L, uint32 char_id, std::string expedition_name, std::string event_name) { + luabind::adl::object lua_table = luabind::newtable(L); + + auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + + auto it = std::find_if(lockouts.begin(), lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) { + return lockout.IsSameLockout(expedition_name, event_name); + }); + + if (it != lockouts.end()) + { + lua_table["remaining"] = it->GetSecondsRemaining(); + lua_table["uuid"] = it->GetExpeditionUUID(); + } + + return lua_table; +} + +luabind::object lua_get_expedition_lockouts_by_char_id(lua_State* L, uint32 char_id) { + luabind::adl::object lua_table = luabind::newtable(L); + + auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + for (const auto& lockout : lockouts) + { + auto lockout_table = lua_table[lockout.GetExpeditionName()]; + if (luabind::type(lockout_table) != LUA_TTABLE) + { + lockout_table = luabind::newtable(L); + } + + auto event_table = lockout_table[lockout.GetEventName()]; + if (luabind::type(event_table) != LUA_TTABLE) + { + event_table = luabind::newtable(L); + } + + event_table["remaining"] = lockout.GetSecondsRemaining(); + event_table["uuid"] = lockout.GetExpeditionUUID(); + } + return lua_table; +} + +luabind::object lua_get_expedition_lockouts_by_char_id(lua_State* L, uint32 char_id, std::string expedition_name) { + luabind::adl::object lua_table = luabind::newtable(L); + + auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(char_id); + for (const auto& lockout : lockouts) + { + if (lockout.GetExpeditionName() == expedition_name) + { + auto event_table = lua_table[lockout.GetEventName()]; + if (luabind::type(event_table) != LUA_TTABLE) + { + event_table = luabind::newtable(L); + } + event_table["remaining"] = lockout.GetSecondsRemaining(); + event_table["uuid"] = lockout.GetExpeditionUUID(); + } + } + return lua_table; +} + +void lua_add_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name, uint32 seconds) { + Expedition::AddLockoutByCharacterID(char_id, expedition_name, event_name, seconds); +} + +void lua_add_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { + Expedition::AddLockoutByCharacterID(char_id, expedition_name, event_name, seconds, uuid); +} + +void lua_remove_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name) { + Expedition::RemoveLockoutsByCharacterID(char_id, expedition_name, event_name); +} + +void lua_remove_all_expedition_lockouts_by_char_id(uint32 char_id) { + Expedition::RemoveLockoutsByCharacterID(char_id); +} + +void lua_remove_all_expedition_lockouts_by_char_id(uint32 char_id, std::string expedition_name) { + Expedition::RemoveLockoutsByCharacterID(char_id, expedition_name); } #define LuaCreateNPCParse(name, c_type, default_value) do { \ @@ -2790,9 +2877,17 @@ luabind::scope lua_register_general() { luabind::def("is_content_flag_enabled", (bool(*)(std::string))&lua_is_content_flag_enabled), luabind::def("set_content_flag", (void(*)(std::string, bool))&lua_set_content_flag), - luabind::def("get_expedition", (Lua_Expedition(*)())&lua_get_expedition), - luabind::def("get_expedition_by_char_id", (Lua_Expedition(*)(uint32 char_id))&lua_get_expedition_by_char_id), - luabind::def("get_expedition_by_instance_id", (Lua_Expedition(*)(uint32 instance_id))&lua_get_expedition_by_instance_id) + luabind::def("get_expedition", &lua_get_expedition), + luabind::def("get_expedition_by_char_id", &lua_get_expedition_by_char_id), + luabind::def("get_expedition_by_instance_id", &lua_get_expedition_by_instance_id), + luabind::def("get_expedition_lockout_by_char_id", &lua_get_expedition_lockout_by_char_id), + luabind::def("get_expedition_lockouts_by_char_id", (luabind::object(*)(lua_State*, uint32))&lua_get_expedition_lockouts_by_char_id), + luabind::def("get_expedition_lockouts_by_char_id", (luabind::object(*)(lua_State*, uint32, std::string))&lua_get_expedition_lockouts_by_char_id), + luabind::def("add_expedition_lockout_by_char_id", (void(*)(uint32, std::string, std::string, uint32))&lua_add_expedition_lockout_by_char_id), + luabind::def("add_expedition_lockout_by_char_id", (void(*)(uint32, std::string, std::string, uint32, std::string))&lua_add_expedition_lockout_by_char_id), + luabind::def("remove_expedition_lockout_by_char_id", &lua_remove_expedition_lockout_by_char_id), + luabind::def("remove_all_expedition_lockouts_by_char_id", (void(*)(uint32))&lua_remove_all_expedition_lockouts_by_char_id), + luabind::def("remove_all_expedition_lockouts_by_char_id", (void(*)(uint32, std::string))&lua_remove_all_expedition_lockouts_by_char_id) ]; } diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 0529bced0..631bb1218 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -26,7 +26,6 @@ #include "entity.h" #include "event_codes.h" -#include "expedition.h" #include "guild_mgr.h" #include "qglobals.h" #include "queryserv.h" @@ -4328,22 +4327,3 @@ void QuestManager::UpdateZoneHeader(std::string type, std::string value) { entity_list.QueueClients(0, outapp); safe_delete(outapp); } - -Expedition* QuestManager::GetExpeditionByCharID(uint32 char_id) -{ - return Expedition::FindCachedExpeditionByCharacterID(char_id); -} - -Expedition* QuestManager::GetExpeditionByInstanceID(uint32 instance_id) -{ - return Expedition::FindCachedExpeditionByInstanceID(instance_id); -} - -Expedition* QuestManager::GetExpeditionForCurrentInstance() -{ - if (zone && zone->GetInstanceID() != 0) - { - return Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); - } - return nullptr; -} diff --git a/zone/questmgr.h b/zone/questmgr.h index 84dad5d02..f3f656e51 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -366,9 +366,6 @@ public: bool DisableRecipe(uint32 recipe_id); void ClearNPCTypeCache(int npctype_id); void ReloadZoneStaticData(); - Expedition* GetExpeditionByCharID(uint32 char_id); - Expedition* GetExpeditionByInstanceID(uint32 instance_id); - Expedition* GetExpeditionForCurrentInstance(); Client *GetInitiator() const; NPC *GetNPC() const; diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index fb8faeb1c..07c97e410 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2907,7 +2907,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionDzSafeReturn: case ServerOP_ExpeditionDzZoneIn: case ServerOP_ExpeditionDzDuration: - case ServerOP_ExpeditionRemoveCharLockouts: + case ServerOP_ExpeditionCharacterLockout: { Expedition::HandleWorldMessage(pack); break; From cb4a117503f5f5ddba83e854e2f89f09f5c07417 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 7 Jul 2020 18:15:04 -0400 Subject: [PATCH 091/196] Cleanup some expedition logging and formatting Remove logging unsanitized input Make unsigned comparison not compare < 0 Cleanup some FormatName and string usage. Some of these strings could probably be moved instead Remove unnecessary expedition lookup in a world message handler --- world/expedition.cpp | 2 +- zone/client.cpp | 12 +++------ zone/client.h | 2 +- zone/expedition.cpp | 53 +++++++++++++++---------------------- zone/expedition.h | 8 +++--- zone/expedition_request.cpp | 2 +- 6 files changed, 32 insertions(+), 47 deletions(-) diff --git a/world/expedition.cpp b/world/expedition.cpp index c880a2855..e94c52fa6 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -95,7 +95,7 @@ void ExpeditionCache::LoadActiveExpeditions() m_expeditions = ExpeditionDatabase::LoadExpeditions(); auto elapsed = benchmark.elapsed(); - LogExpeditions("World caching [{}] expeditions took {}s", m_expeditions.size(), elapsed); + LogExpeditions("World caching [{}] expeditions took [{}s]", m_expeditions.size(), elapsed); } void ExpeditionCache::AddExpedition(uint32_t expedition_id) diff --git a/zone/client.cpp b/zone/client.cpp index 71fffe804..ff090b443 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9593,7 +9593,7 @@ Expedition* Client::CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest } Expedition* Client::CreateExpedition( - std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, + const std::string& zone_name, uint32 version, uint32 duration, const std::string& expedition_name, uint32 min_players, uint32 max_players, bool disable_messages) { DynamicZone dz_instance{ zone_name, version, duration, DynamicZoneType::Expedition }; @@ -9747,8 +9747,8 @@ void Client::SendExpeditionLockoutTimers() // 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) + uint32_t seconds_remaining = it->GetSecondsRemaining(); + if (seconds_remaining == 0) { it = m_expedition_lockouts.erase(it); } @@ -9933,11 +9933,7 @@ void Client::MovePCDynamicZone(uint32 zone_id) } 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 + if (single_dz.IsValid()) { DynamicZoneLocation zonein = single_dz.GetZoneInLocation(); ZoneMode zone_mode = ZoneMode::ZoneToSafeCoords; diff --git a/zone/client.h b/zone/client.h index 50abfddc3..f1c231db5 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1122,7 +1122,7 @@ public: const std::string& expedition_name, const std::string& event_name, uint32_t duration, std::string uuid = {}); Expedition* CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request); Expedition* CreateExpedition( - std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, + const std::string& zone_name, uint32 version, uint32 duration, const std::string& expedition_name, uint32 min_players, uint32 max_players, bool disable_messages = false); Expedition* GetExpedition() const; uint32 GetExpeditionID() const { return m_expedition_id; } diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 6fb0ec226..7f93204bf 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -45,7 +45,7 @@ const int32_t Expedition::REPLAY_TIMER_ID = -1; const int32_t Expedition::EVENT_TIMER_ID = 1; Expedition::Expedition( - uint32_t id, const std::string& uuid, const DynamicZone& dynamic_zone, std::string expedition_name, + uint32_t id, const std::string& uuid, const DynamicZone& dynamic_zone, const std::string& expedition_name, const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players ) : m_id(id), @@ -103,20 +103,18 @@ Expedition* Expedition::TryCreate( { dynamiczone.SaveToDatabase(); - ExpeditionMember leader{request.GetLeaderID(), request.GetLeaderName()}; - auto expedition = std::unique_ptr(new Expedition( expedition_id, expedition_uuid, dynamiczone, request.GetExpeditionName(), - leader, + ExpeditionMember{ request.GetLeaderID(), request.GetLeaderName() }, request.GetMinPlayers(), request.GetMaxPlayers() )); LogExpeditions( - "Created [{}] ({}) instance id: [{}] leader: [{}] minplayers: [{}] maxplayers: [{}]", + "Created [{}] [{}] instance id: [{}] leader: [{}] minplayers: [{}] maxplayers: [{}]", expedition->GetID(), expedition->GetName(), expedition->GetInstanceID(), @@ -133,10 +131,8 @@ Expedition* Expedition::TryCreate( inserted.first->second->SendUpdatesToZoneMembers(); inserted.first->second->SendWorldExpeditionUpdate(ServerOP_ExpeditionCreate); // cache in other zones - Client* leader_client = request.GetLeaderClient(); - - Client::SendCrossZoneMessageString( - leader_client, leader.name, Chat::Yellow, EXPEDITION_AVAILABLE, { request.GetExpeditionName() } + inserted.first->second->SendLeaderMessage( + request.GetLeaderClient(), Chat::Yellow, EXPEDITION_AVAILABLE, { request.GetExpeditionName() } ); return inserted.first->second.get(); @@ -185,9 +181,9 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) std::unique_ptr expedition = std::unique_ptr(new Expedition( expedition_id, row[col::uuid], // expedition uuid - DynamicZone{instance_id}, + DynamicZone{ instance_id }, row[col::expedition_name], // expedition name - ExpeditionMember{leader_id, row[col::leader_name]}, // expedition leader id, name + ExpeditionMember{ leader_id, row[col::leader_name] }, // expedition leader id, name strtoul(row[col::min_players], nullptr, 10), // min_players strtoul(row[col::max_players], nullptr, 10) // max_players )); @@ -264,7 +260,7 @@ void Expedition::CacheFromDatabase(uint32_t expedition_id) CacheExpeditions(results); auto elapsed = benchmark.elapsed(); - LogExpeditions("Caching new expedition [{}] took {}s", expedition_id, elapsed); + LogExpeditions("Caching new expedition [{}] took [{}s]", expedition_id, elapsed); } } @@ -290,7 +286,7 @@ bool Expedition::CacheAllFromDatabase() CacheExpeditions(results); auto elapsed = benchmark.elapsed(); - LogExpeditions("Caching [{}] expedition(s) took {}s", zone->expedition_cache.size(), elapsed); + LogExpeditions("Caching [{}] expedition(s) took [{}s]", zone->expedition_cache.size(), elapsed); return true; } @@ -923,7 +919,7 @@ void Expedition::TryAddClient( } void Expedition::DzAddPlayer( - Client* requester, std::string add_char_name, std::string swap_remove_name) + Client* requester, const std::string& add_char_name, const std::string& swap_remove_name) { if (!requester || !ConfirmLeaderCommand(requester)) { @@ -1015,7 +1011,7 @@ void Expedition::DzMakeLeader(Client* requester, std::string new_leader_name) else { // new leader not in this zone, let world verify and pass to new leader's zone - SendWorldMakeLeaderRequest(requester->GetName(), FormatName(new_leader_name)); + SendWorldMakeLeaderRequest(requester->GetName(), new_leader_name); } } @@ -1026,22 +1022,15 @@ void Expedition::DzRemovePlayer(Client* requester, std::string char_name) return; } - LogExpeditionsModerate( - "Remove player request for expedition [{}] by [{}] leader [{}] remove name [{}]", - m_id, requester->GetName(), m_leader.name, char_name - ); - - char_name = FormatName(char_name); - // live only seems to enforce min_players for requesting expeditions, no need to check here bool removed = RemoveMember(char_name); if (!removed) { - requester->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, char_name.c_str()); + requester->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, FormatName(char_name).c_str()); } else { - requester->MessageString(Chat::Yellow, EXPEDITION_REMOVED, char_name.c_str(), m_expedition_name.c_str()); + requester->MessageString(Chat::Yellow, EXPEDITION_REMOVED, FormatName(char_name).c_str(), m_expedition_name.c_str()); } } @@ -1165,7 +1154,7 @@ void Expedition::ProcessMakeLeader( } } -void Expedition::ProcessMemberAdded(std::string char_name, uint32_t added_char_id) +void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added_char_id) { // adds the member to this expedition and notifies both leader and new member Client* leader_client = entity_list.GetClientByCharID(m_leader.char_id); @@ -1188,7 +1177,7 @@ void Expedition::ProcessMemberAdded(std::string char_name, uint32_t added_char_i SendUpdatesToZoneMembers(); // live sends full update when member added } -void Expedition::ProcessMemberRemoved(std::string removed_char_name, uint32_t removed_char_id) +void Expedition::ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id) { if (m_members.empty()) { @@ -1731,11 +1720,10 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) case ServerOP_ExpeditionMemberChange: { auto buf = reinterpret_cast(pack->pBuffer); - - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition && zone) + if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) { - if (!zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition) { if (buf->removed) { @@ -1834,8 +1822,9 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) Client* leader = entity_list.GetClientByName(buf->requester_name); if (leader) { - leader->MessageString(Chat::Red, DZADD_NOT_ONLINE, FormatName(buf->target_name).c_str()); - leader->MessageString(Chat::Red, DZADD_INVITE_FAIL, FormatName(buf->target_name).c_str()); + std::string target_name = FormatName(buf->target_name); + leader->MessageString(Chat::Red, DZADD_NOT_ONLINE, target_name.c_str()); + leader->MessageString(Chat::Red, DZADD_INVITE_FAIL, target_name.c_str()); } } break; diff --git a/zone/expedition.h b/zone/expedition.h index ba6ba09e9..94dde9627 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -66,7 +66,7 @@ class Expedition public: Expedition() = delete; Expedition( - uint32_t id, const std::string& uuid, const DynamicZone& dz, std::string expedition_name, + uint32_t id, const std::string& uuid, const DynamicZone& dz, const std::string& expedition_name, const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players); static Expedition* TryCreate(Client* requester, DynamicZone& dynamiczone, ExpeditionRequest& request); @@ -128,7 +128,7 @@ public: void SendClientExpeditionInfo(Client* client); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); - void DzAddPlayer(Client* requester, std::string add_char_name, std::string swap_remove_name = {}); + void DzAddPlayer(Client* requester, const std::string& add_char_name, const 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, const std::string& swap_remove_name); void DzMakeLeader(Client* requester, std::string new_leader_name); @@ -161,8 +161,8 @@ private: void ProcessLeaderChanged(uint32_t new_leader_id, const std::string& new_leader_name); void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); 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 ProcessMemberAdded(const std::string& added_char_name, uint32_t added_char_id); + void ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id); void SaveLockouts(ExpeditionRequest& request); void SaveMembers(ExpeditionRequest& request); void SendClientExpeditionInvite( diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 663460e20..29a9ccb10 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -79,7 +79,7 @@ bool ExpeditionRequest::Validate(Client* requester) } auto elapsed = benchmark.elapsed(); - LogExpeditions("Create validation for [{}] members took {}s", m_members.size(), elapsed); + LogExpeditions("Create validation for [{}] members took [{}s]", m_members.size(), elapsed); return requirements_met; } From 15235d77f7b559015a0ce36759e70823d176bfd2 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 10 Jul 2020 22:35:39 -0400 Subject: [PATCH 092/196] Fix regression deleting expedition from db Set expedition's instance id NULL instead of deleting it from the database when instances are deleted. Only expedition functions should delete expeditions This fixes a regression caused by removing foreign key constraints The expedition_details row was being deleted and not the corresponding expedition_members and expedition_lockouts rows. Any characters inside the members table could no longer join expeditions --- common/database_instances.cpp | 4 ++-- utils/sql/git/required/wip_expeditions.sql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/database_instances.cpp b/common/database_instances.cpp index f0bdda42e..0949a6708 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -496,7 +496,7 @@ void Database::DeleteInstance(uint16 instance_id) query = fmt::format("DELETE FROM dynamic_zones WHERE instance_id={}", instance_id); QueryDatabase(query); - query = fmt::format("DELETE FROM expedition_details WHERE instance_id={}", instance_id); + query = fmt::format("UPDATE expedition_details SET instance_id = NULL WHERE instance_id={}", instance_id); QueryDatabase(query); BuryCorpsesInInstance(instance_id); @@ -589,7 +589,7 @@ void Database::PurgeExpiredInstances() QueryDatabase(fmt::format("DELETE FROM spawn_condition_values WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("UPDATE character_corpses SET is_buried = 1, instance_id = 0 WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("DELETE FROM dynamic_zones WHERE instance_id IN ({})", imploded_instance_ids)); - QueryDatabase(fmt::format("DELETE FROM expedition_details WHERE instance_id IN ({})", imploded_instance_ids)); + QueryDatabase(fmt::format("UPDATE expedition_details SET instance_id = NULL WHERE instance_id IN ({})", imploded_instance_ids)); } void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 971880e37..fe92bb2b4 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -1,7 +1,7 @@ CREATE TABLE `expedition_details` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` VARCHAR(36) NOT NULL, - `instance_id` INT(10) NOT NULL, + `instance_id` INT(10) NULL DEFAULT 0, `expedition_name` VARCHAR(128) NOT NULL, `leader_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, `min_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, From ba9ce2335e985f8764dff96c664e562a9f765541 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 11 Jul 2020 09:12:32 -0400 Subject: [PATCH 093/196] Set new expedition leader on leader disconnect Only choose an online member as new leader on leader changes Keep leader online status updated in zone expedition caches Currently this will also trigger a leader change if the leader goes linkdead. On live the character retains leadership while linkdead and a new one is only chosen once kicked offline --- zone/expedition.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 7f93204bf..c04d8159b 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -581,6 +581,13 @@ void Expedition::SetMemberStatus(Client* client, ExpeditionMemberStatus status) { UpdateMemberStatus(client->CharacterID(), status); SendWorldMemberStatus(client->CharacterID(), status); + + // we either changed leader status here or leader was already offline and + // a member coming online needs to trigger a leader change + if (m_leader.status == ExpeditionMemberStatus::Offline) + { + ChooseNewLeader(); + } } } @@ -607,13 +614,18 @@ void Expedition::UpdateMemberStatus(uint32_t update_member_id, ExpeditionMemberS member_client->QueuePacket(outapp_member_status.get()); } } + + if (update_member_id == m_leader.char_id) + { + m_leader.status = status; + } } bool Expedition::ChooseNewLeader() { for (const auto& member : m_members) { - if (member.char_id != m_leader.char_id) + if (member.char_id != m_leader.char_id && member.status == ExpeditionMemberStatus::Online) { LogExpeditionsModerate("Replacing leader [{}] with [{}]", m_leader.name, member.name); SetNewLeader(member.char_id, member.name); @@ -1111,8 +1123,7 @@ void Expedition::SetNewLeader(uint32_t new_leader_id, const std::string& new_lea void Expedition::ProcessLeaderChanged(uint32_t new_leader_id, const std::string& new_leader_name) { - m_leader.char_id = new_leader_id; - m_leader.name = new_leader_name; + m_leader = { new_leader_id, new_leader_name, ExpeditionMemberStatus::Online }; // update each client's expedition window in this zone auto outapp_leader = CreateLeaderNamePacket(); From d8b825f4782f880521ac23601f321d42cb5de82d Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 11 Jul 2020 12:39:21 -0400 Subject: [PATCH 094/196] Fix linkdead expedition member offline update This was leaving expedition members with linkdead status even after the client was timed out by the server --- zone/client_process.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 5a2f91875..66c06559d 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -180,6 +180,13 @@ bool Client::Process() { if (myraid) { myraid->MemberZoned(this); } + + Expedition* expedition = GetExpedition(); + if (expedition) + { + expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline); + } + return false; //delete client } @@ -655,11 +662,6 @@ bool Client::Process() { myraid->MemberZoned(this); } } - Expedition* expedition = GetExpedition(); - if (expedition && !bZoning) - { - expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline); - } OnDisconnect(false); return false; } @@ -701,12 +703,6 @@ 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 */ @@ -716,6 +712,12 @@ void Client::OnDisconnect(bool hard_disconnect) { } } + Expedition* expedition = GetExpedition(); + if (expedition && !bZoning) + { + expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline); + } + RemoveAllAuras(); Mob *Other = trade->With(); From 9c4df6f0261120517f33e38ed317e8872ad2d175 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 11 Jul 2020 20:59:36 -0400 Subject: [PATCH 095/196] Don't set member offline before linkdead This prevents toggling to offline state before setting to and from linkdead status. Without this change, a new expedition leader will be chosen as soon as the current leader goes linkdead. On live an expedition leader retains leadership while linkdead --- zone/client.h | 2 +- zone/client_process.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/zone/client.h b/zone/client.h index f1c231db5..e5f9ee615 100644 --- a/zone/client.h +++ b/zone/client.h @@ -738,7 +738,7 @@ public: bool TGB() const { return tgb; } - void OnDisconnect(bool hard_disconnect); + void OnDisconnect(bool hard_disconnect, bool linkdead = false); uint16 GetSkillPoints() { return m_pp.points;} void SetSkillPoints(int inp) { m_pp.points = inp;} diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 66c06559d..144bce500 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -560,17 +560,20 @@ bool Client::Process() { } if (client_state != CLIENT_LINKDEAD && !eqs->CheckState(ESTABLISHED)) { - OnDisconnect(true); LogInfo("Client linkdead: {}", name); if (Admin() > 100) { + OnDisconnect(true); if (GetMerc()) { GetMerc()->Save(); GetMerc()->Depop(); } return false; } - else if (!linkdead_timer.Enabled()) { + + OnDisconnect(true, true); + + if (!linkdead_timer.Enabled()) { linkdead_timer.Start(RuleI(Zone, ClientLinkdeadMS)); client_state = CLIENT_LINKDEAD; AI_Start(CLIENT_LD_TIMEOUT); @@ -689,7 +692,7 @@ bool Client::Process() { } /* Just a set of actions preformed all over in Client::Process */ -void Client::OnDisconnect(bool hard_disconnect) { +void Client::OnDisconnect(bool hard_disconnect, bool linkdead) { if(hard_disconnect) { LeaveGroup(); @@ -713,7 +716,7 @@ void Client::OnDisconnect(bool hard_disconnect) { } Expedition* expedition = GetExpedition(); - if (expedition && !bZoning) + if (expedition && !bZoning && !linkdead && !linkdead_timer.Enabled()) { expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline); } From 1f3c5af996b69cefdd761662df6022e2450c35ac Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 11 Jul 2020 21:13:07 -0400 Subject: [PATCH 096/196] Add #dz makeleader command --- zone/command.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/zone/command.cpp b/zone/command.cpp index f2667ebd9..73eaa26e7 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6947,6 +6947,33 @@ void command_dz(Client* c, const Seperator* sep) Expedition::RemoveLockoutsByCharacterName(sep->arg[3], sep->arg[4], sep->arg[5]); } } + else if (strcasecmp(sep->arg[1], "makeleader") == 0 && sep->IsNumber(2) && sep->arg[3][0] != '\0') + { + auto expedition_id = std::strtoul(sep->arg[2], nullptr, 10); + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) + { + uint32_t char_id = database.GetCharacterID(sep->arg[3]); + auto char_name = FormatName(sep->arg[3]); + if (char_id == 0) + { + c->Message(Chat::Red, fmt::format("Failed to find character id for [{}]", char_name).c_str()); + } + else if (!expedition->HasMember(char_id)) + { + c->Message(Chat::Red, fmt::format("Character [{}] is not in that expedition", char_name).c_str()); + } + else + { + c->Message(Chat::White, fmt::format("Setting expedition [{}] leader to [{}]", expedition_id, char_name).c_str()); + expedition->SetNewLeader(char_id, char_name); + } + } + else + { + c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", expedition_id).c_str()); + } + } else { c->Message(Chat::White, "#dz usage:"); @@ -6957,6 +6984,7 @@ void command_dz(Client* c, const Seperator* sep) c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); c->Message(Chat::White, "#dz lockouts remove \"\" - delete lockouts by expedition"); c->Message(Chat::White, "#dz lockouts remove \"\" \"\" - delete lockout by expedition event"); + c->Message(Chat::White, "#dz makeleader - set new expedition leader"); } } From ce19deb5d1368678dcb37e65a6e7c9a62bf5cd57 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 12 Jul 2020 10:56:00 -0400 Subject: [PATCH 097/196] Add rule to always notify new expedition leader On live, new expedition leaders are only notified if made leader from the /dzmakeleader command (or from ui). This rule makes it so the new leader is always messaged on a leader change for cases where previous leader goes offline or quits --- common/ruletypes.h | 1 + zone/expedition.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 34773f441..5af72acb9 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -792,6 +792,7 @@ RULE_BOOL(Expedition, EmptyDzShutdownEnabled, true, "Enable early instance shutd RULE_INT(Expedition, EmptyDzShutdownDelaySeconds, 900, "Seconds to set dynamic zone instance expiration if early shutdown enabled") RULE_INT(Expedition, RequestExpiredLockoutLeewaySeconds, 60, "Seconds remaining on lockout to count as expired for creation requests (client hides timers under 60s remaining)") RULE_INT(Expedition, WorldExpeditionProcessRateMS, 6000, "Timer interval (ms) that world checks expedition states") +RULE_BOOL(Expedition, AlwaysNotifyNewLeaderOnChange, false, "Always notify clients when made expedition leader. If false (live-like) new leaders are only notified when made leader via /dzmakeleader") RULE_CATEGORY_END() RULE_CATEGORY(DynamicZone) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index c04d8159b..e4f56d749 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1133,6 +1133,11 @@ void Expedition::ProcessLeaderChanged(uint32_t new_leader_id, const std::string& if (member_client) { member_client->QueuePacket(outapp_leader.get()); + + if (member.char_id == new_leader_id && RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) + { + member_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + } } } } @@ -1160,7 +1165,10 @@ void Expedition::ProcessMakeLeader( if (new_leader_client) { - new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + if (!RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) + { + new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + } SetNewLeader(new_leader_client->CharacterID(), new_leader_client->GetName()); } } From fba078bbe9a9280796cef6dc9b20da231fc926eb Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 12 Jul 2020 11:21:47 -0400 Subject: [PATCH 098/196] Format name in expedition invite message Live uses the original unformatted user input in invite messages, but this doesn't seem necessary to emulate --- zone/expedition.cpp | 14 +++++++------- zone/expedition.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index e4f56d749..8ebc5b4e6 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -897,8 +897,8 @@ bool Expedition::ConfirmLeaderCommand(Client* requester) } void Expedition::TryAddClient( - Client* add_client, const std::string& inviter_name, const std::string& orig_add_name, - const std::string& swap_remove_name, Client* leader_client) + Client* add_client, const std::string& inviter_name, const std::string& swap_remove_name, + Client* leader_client) { if (!add_client) { @@ -907,7 +907,7 @@ void Expedition::TryAddClient( LogExpeditionsModerate( "Add player request for expedition [{}] by inviter [{}] add name [{}] swap name [{}]", - m_id, inviter_name, orig_add_name, swap_remove_name + m_id, inviter_name, add_client->GetName(), swap_remove_name ); // null leader client handled by ProcessAddConflicts/SendLeaderMessage fallbacks @@ -921,7 +921,7 @@ void Expedition::TryAddClient( { // live uses the original unsanitized input string in invite messages uint32_t string_id = swap_remove_name.empty() ? DZADD_INVITE : DZSWAP_INVITE; - SendLeaderMessage(leader_client, Chat::Yellow, string_id, { orig_add_name.c_str() }); + SendLeaderMessage(leader_client, Chat::Yellow, string_id, { add_client->GetName() }); SendClientExpeditionInvite(add_client, inviter_name.c_str(), swap_remove_name); } else if (swap_remove_name.empty()) // swap command doesn't result in this message @@ -971,12 +971,12 @@ void Expedition::DzAddPlayer( if (add_client) { // client is online in this zone - TryAddClient(add_client, requester->GetName(), add_char_name, swap_remove_name, requester); + TryAddClient(add_client, requester->GetName(), swap_remove_name, requester); } else { // forward to world to check if client is online and perform cross-zone invite - SendWorldAddPlayerInvite(requester->GetName(), swap_remove_name, add_char_name); + SendWorldAddPlayerInvite(requester->GetName(), swap_remove_name, FormatName(add_char_name)); } } @@ -987,7 +987,7 @@ void Expedition::DzAddPlayerContinue( Client* add_client = entity_list.GetClientByName(add_name.c_str()); if (add_client) { - TryAddClient(add_client, inviter_name, add_name, swap_remove_name); + TryAddClient(add_client, inviter_name, swap_remove_name); } } diff --git a/zone/expedition.h b/zone/expedition.h index 94dde9627..611ad6c0c 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -182,7 +182,7 @@ private: 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 SendWorldSettingChanged(uint16_t server_opcode, bool setting_value); - void TryAddClient(Client* add_client, const std::string& inviter_name, const std::string& orig_add_name, + void TryAddClient(Client* add_client, const std::string& inviter_name, const std::string& swap_remove_name, Client* leader_client = nullptr); void UpdateMemberStatus(uint32_t update_character_id, ExpeditionMemberStatus status); From 6e5ca19d1869bfab4889bd6fadd40dbc5deea089 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 16 Jul 2020 19:37:51 -0400 Subject: [PATCH 099/196] Get dz safe return from cache not db This was loading the dz from database to get safe return data every time a client's dz removal timer triggered Add the Zone::GetDynamicZone() method so zones that are dz instances can find the data from the cache of any dz systems --- zone/client.cpp | 5 +++-- zone/client.h | 2 +- zone/client_process.cpp | 3 +-- zone/entity.cpp | 4 ++-- zone/zone.cpp | 18 ++++++++++++++++++ zone/zone.h | 2 ++ 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index ff090b443..d52c0888d 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9883,13 +9883,14 @@ void Client::SendDzCompassUpdate() QueuePacket(outapp.get()); } -void Client::GoToDzSafeReturnOrBind(const DynamicZoneLocation& safereturn) +void Client::GoToDzSafeReturnOrBind(const DynamicZone& dynamic_zone) { + auto safereturn = dynamic_zone.GetSafeReturnLocation(); LogDynamicZonesDetail( "Sending character [{}] to safereturn zone [{}] or bind", CharacterID(), safereturn.zone_id ); - if (safereturn.zone_id == 0) + if (!dynamic_zone.IsValid() || safereturn.zone_id == 0) { GoToBind(); } diff --git a/zone/client.h b/zone/client.h index e5f9ee615..716a94332 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1145,7 +1145,7 @@ public: void DzListTimers(); void SetDzRemovalTimer(bool enable_timer); void SendDzCompassUpdate(); - void GoToDzSafeReturnOrBind(const DynamicZoneLocation& safereturn); + void GoToDzSafeReturnOrBind(const DynamicZone& dynamic_zone); void MovePCDynamicZone(uint32 zone_id); void MovePCDynamicZone(const std::string& zone_name); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 144bce500..929b4006f 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -164,8 +164,7 @@ bool Client::Process() { if (dynamiczone_removal_timer.Check() && zone && zone->GetInstanceID() != 0) { dynamiczone_removal_timer.Disable(); - DynamicZone dz = DynamicZone::LoadDzFromDatabase(zone->GetInstanceID()); - GoToDzSafeReturnOrBind(dz.GetSafeReturnLocation()); + GoToDzSafeReturnOrBind(zone->GetDynamicZone()); } if (linkdead_timer.Check()) { diff --git a/zone/entity.cpp b/zone/entity.cpp index 02c901689..1ad03eea0 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -5208,7 +5208,7 @@ void EntityList::GateAllClientsToSafeReturn() DynamicZone dz; if (zone) { - dz = DynamicZone::LoadDzFromDatabase(zone->GetInstanceID()); + dz = zone->GetDynamicZone(); LogDynamicZones( "Sending all clients in zone: [{}] instance: [{}] to dz safereturn or bind", @@ -5221,7 +5221,7 @@ void EntityList::GateAllClientsToSafeReturn() if (client_list_iter.second) { // falls back to gating clients to bind if dz invalid - client_list_iter.second->GoToDzSafeReturnOrBind(dz.GetSafeReturnLocation()); + client_list_iter.second->GoToDzSafeReturnOrBind(dz); } } } diff --git a/zone/zone.cpp b/zone/zone.cpp index 0630dff50..34ad7f79b 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -2714,3 +2714,21 @@ bool Zone::IsZone(uint32 zone_id, uint16 instance_id) const { return (zoneid == zone_id && instanceid == instance_id); } + +DynamicZone Zone::GetDynamicZone() +{ + if (GetInstanceID() == 0) + { + return {}; // invalid + } + + auto expedition = Expedition::FindCachedExpeditionByInstanceID(GetInstanceID()); + if (expedition) + { + return expedition->GetDynamicZone(); + } + + // todo: tasks, missions, and quests with an associated dz for this instance id + + return {}; // invalid +} diff --git a/zone/zone.h b/zone/zone.h index 32a98722f..31c0f16ee 100755 --- a/zone/zone.h +++ b/zone/zone.h @@ -33,6 +33,7 @@ #include "spawn2.h" #include "spawngroup.h" #include "aa_ability.h" +#include "dynamiczone.h" #include "pathfinder_interface.h" #include "global_loot_manager.h" @@ -177,6 +178,7 @@ public: void DumpMerchantList(uint32 npcid); int SaveTempItem(uint32 merchantid, uint32 npcid, uint32 item, int32 charges, bool sold = false); int32 MobsAggroCount() { return aggroedmobs; } + DynamicZone GetDynamicZone(); IPathfinder *pathing; LinkedList NPCEmoteList; From d6ab87e2c443cdec15f4d007b3f854d1d98bf664 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 16 Jul 2020 21:13:07 -0400 Subject: [PATCH 100/196] Avoid world message on offline member invite Expedition member status can be used to choose the message --- zone/expedition.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 8ebc5b4e6..264740af2 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -952,11 +952,18 @@ void Expedition::DzAddPlayer( } else { - // we can avoid checking online status in world if we trust member status accuracy auto member_data = GetMemberData(add_char_name); - if (member_data.char_id != 0 && member_data.status != ExpeditionMemberStatus::Offline) + if (member_data.char_id != 0) { - requester->MessageString(Chat::Red, DZADD_ALREADY_PART, add_char_name.c_str()); + // live prioritizes offline message before already a member message + if (member_data.status == ExpeditionMemberStatus::Offline) + { + requester->MessageString(Chat::Red, DZADD_NOT_ONLINE, add_char_name.c_str()); + } + else + { + requester->MessageString(Chat::Red, DZADD_ALREADY_PART, add_char_name.c_str()); + } invite_failed = true; } } From 779fc6265eff2b0d5e28527f152cf8e5377dc83a Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 4 Aug 2020 20:35:38 -0400 Subject: [PATCH 101/196] Load character lockouts without looping --- zone/client.cpp | 16 +++------------- zone/client.h | 2 +- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index d52c0888d..92412439d 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9614,8 +9614,7 @@ Expedition* Client::GetExpedition() const return nullptr; } -void Client::AddExpeditionLockout( - const ExpeditionLockoutTimer& lockout, bool update_db, bool update_client) +void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db) { // todo: support for account based lockouts like live AoC expeditions @@ -9633,10 +9632,7 @@ void Client::AddExpeditionLockout( ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { lockout }, true); } - if (update_client) - { - SendExpeditionLockoutTimers(); - } + SendExpeditionLockoutTimers(); } void Client::AddNewExpeditionLockout( @@ -9730,13 +9726,7 @@ bool Client::HasExpeditionLockout( void Client::LoadAllExpeditionLockouts() { - m_expedition_lockouts.clear(); - - auto lockouts = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); - for (const auto& lockout : lockouts) - { - AddExpeditionLockout(lockout, false, false); - } + m_expedition_lockouts = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); SendExpeditionLockoutTimers(); } diff --git a/zone/client.h b/zone/client.h index 716a94332..a5f78a7f3 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1117,7 +1117,7 @@ public: Client* client, const std::string& client_name, uint16_t chat_type, uint32_t string_id, const std::initializer_list& arguments = {}); - void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false, bool update_client = true); + void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false); void AddNewExpeditionLockout( const std::string& expedition_name, const std::string& event_name, uint32_t duration, std::string uuid = {}); Expedition* CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request); From 0c5bd9b338906193f3d4230e22517112b1424547 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 16 Aug 2020 11:26:12 -0400 Subject: [PATCH 102/196] Escape expedition and event names in queries Fixes names that have apostrophes --- zone/expedition_database.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index f8df4cb81..2d77f2d04 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -23,6 +23,7 @@ #include "expedition_lockout_timer.h" #include "zonedb.h" #include "../common/database.h" +#include "../common/string_util.h" #include uint32_t ExpeditionDatabase::InsertExpedition( @@ -38,7 +39,7 @@ uint32_t ExpeditionDatabase::InsertExpedition( (uuid, instance_id, expedition_name, leader_id, min_players, max_players) VALUES ('{}', {}, '{}', {}, {}, {}); - ), uuid, instance_id, expedition_name, leader_id, min_players, max_players); + ), uuid, instance_id, EscapeString(expedition_name), leader_id, min_players, max_players); auto results = database.QueryDatabase(query); if (!results.Success()) @@ -149,7 +150,7 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts( AND is_pending = FALSE AND expire_time > NOW() AND expedition_name = '{}'; - ), character_id, expedition_name); + ), character_id, EscapeString(expedition_name)); auto results = database.QueryDatabase(query); if (results.Success()) @@ -261,7 +262,7 @@ MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( LEFT JOIN expedition_members member ON character_data.id = member.character_id WHERE character_data.name IN ({}) ORDER BY character_data.id; - ), expedition_name, in_character_names_query); + ), EscapeString(expedition_name), in_character_names_query); results = database.QueryDatabase(query); } @@ -294,7 +295,7 @@ void ExpeditionDatabase::DeleteAllCharacterLockouts( std::string query = fmt::format(SQL( DELETE FROM expedition_character_lockouts WHERE character_id = {} AND expedition_name = '{}'; - ), character_id, expedition_name); + ), character_id, EscapeString(expedition_name)); database.QueryDatabase(query); } @@ -314,7 +315,7 @@ void ExpeditionDatabase::DeleteCharacterLockout( AND is_pending = FALSE AND expedition_name = '{}' AND event_name = '{}'; - ), character_id, expedition_name, event_name); + ), character_id, EscapeString(expedition_name), EscapeString(event_name)); database.QueryDatabase(query); } @@ -342,7 +343,7 @@ void ExpeditionDatabase::DeleteMembersLockout( AND is_pending = FALSE AND expedition_name = '{}' AND event_name = '{}'; - ), query_character_ids, expedition_name, event_name); + ), query_character_ids, EscapeString(expedition_name), EscapeString(event_name)); database.QueryDatabase(query); } @@ -359,7 +360,7 @@ void ExpeditionDatabase::AssignPendingLockouts(uint32_t character_id, const std: character_id = {} AND is_pending = TRUE AND expedition_name = '{}'; - ), character_id, expedition_name); + ), character_id, EscapeString(expedition_name)); database.QueryDatabase(query); } @@ -406,7 +407,7 @@ void ExpeditionDatabase::DeleteLockout(uint32_t expedition_id, const std::string auto query = fmt::format(SQL( DELETE FROM expedition_lockouts WHERE expedition_id = {} AND event_name = '{}'; - ), expedition_id, event_name); + ), expedition_id, EscapeString(event_name)); database.QueryDatabase(query); } @@ -466,8 +467,8 @@ void ExpeditionDatabase::InsertCharacterLockouts( lockout.GetExpireTime(), lockout.GetDuration(), lockout.GetExpeditionUUID(), - lockout.GetExpeditionName(), - lockout.GetEventName(), + EscapeString(lockout.GetExpeditionName()), + EscapeString(lockout.GetEventName()), is_pending ); } @@ -526,8 +527,8 @@ void ExpeditionDatabase::InsertMembersLockout( lockout.GetExpireTime(), lockout.GetDuration(), lockout.GetExpeditionUUID(), - lockout.GetExpeditionName(), - lockout.GetEventName() + EscapeString(lockout.GetExpeditionName()), + EscapeString(lockout.GetEventName()) ); } @@ -569,7 +570,7 @@ void ExpeditionDatabase::InsertLockout( ), expedition_id, lockout.GetExpeditionUUID(), - lockout.GetEventName(), + EscapeString(lockout.GetEventName()), lockout.GetExpireTime(), lockout.GetDuration() ); @@ -589,7 +590,7 @@ void ExpeditionDatabase::InsertLockouts( "({}, '{}', '{}', FROM_UNIXTIME({}), {}),", expedition_id, lockout.second.GetExpeditionUUID(), - lockout.second.GetEventName(), + EscapeString(lockout.second.GetEventName()), lockout.second.GetExpireTime(), lockout.second.GetDuration() ); From f822798c63b8d315ecdea062a972a5700dd29a1a Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 16 Aug 2020 19:48:19 -0400 Subject: [PATCH 103/196] Cleanup some vector emplacements in expeditions --- world/expedition.cpp | 4 ++-- zone/expedition.cpp | 6 +++--- zone/expedition_database.cpp | 8 ++++---- zone/expedition_request.cpp | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/world/expedition.cpp b/world/expedition.cpp index e94c52fa6..4ed06e42a 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -274,13 +274,13 @@ std::vector ExpeditionDatabase::LoadExpeditions() if (last_expedition_id != expedition_id) { - expeditions.emplace_back(Expedition{ + expeditions.emplace_back( static_cast(strtoul(row[0], nullptr, 10)), // expedition_id static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id static_cast(strtoul(row[3], nullptr, 10)), // start_time static_cast(strtoul(row[4], nullptr, 10)) // duration - }); + ); } last_expedition_id = expedition_id; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 264740af2..a955a6661 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -207,7 +207,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) current_expedition->AddInternalMember( row[col::member_name], member_id, ExpeditionMemberStatus::Offline ); - expedition_character_ids.emplace_back(std::make_pair(expedition_id, member_id)); + expedition_character_ids.emplace_back(expedition_id, member_id); } } @@ -490,7 +490,7 @@ void Expedition::AddInternalMember( if (it == m_members.end()) { - m_members.emplace_back(ExpeditionMember{character_id, char_name, status}); + m_members.emplace_back(character_id, char_name, status); } } @@ -1288,7 +1288,7 @@ void Expedition::ProcessLockoutUpdate( Client* client = client_iter.second; if (client && client->GetExpeditionID() != GetID()) { - non_members.emplace_back(ExpeditionMember{client->CharacterID(), client->GetName()}); + non_members.emplace_back(client->CharacterID(), client->GetName()); client->AddExpeditionLockout(lockout); } } diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 2d77f2d04..44b7c6781 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -118,13 +118,13 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts(ui { for (auto row = results.begin(); row != results.end(); ++row) { - lockouts.emplace_back(ExpeditionLockoutTimer{ + lockouts.emplace_back( row[0], // expedition_uuid row[1], // expedition_name row[2], // event_name strtoull(row[3], nullptr, 10), // expire_time static_cast(strtoul(row[4], nullptr, 10)) // duration - }); + ); } } @@ -157,13 +157,13 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts( { for (auto row = results.begin(); row != results.end(); ++row) { - lockouts.emplace_back(ExpeditionLockoutTimer{ + lockouts.emplace_back( row[0], // expedition_uuid expedition_name, row[1], // event_name strtoull(row[2], nullptr, 10), // expire_time static_cast(strtoul(row[3], nullptr, 10)) // duration - }); + ); } } diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 29a9ccb10..474c1de08 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -208,7 +208,7 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& 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}); + m_members.emplace_back(character_id, character_name); // process event lockout conflict messages from the previous character for (const auto& member_lockout : member_lockout_conflicts) @@ -257,7 +257,7 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& // leader doesn't have this lockout. queue instead of messaging // now so message comes after any replay lockout messages has_conflicts = true; - member_lockout_conflicts.emplace_back(ExpeditionRequestConflict{character_name, lockout}); + member_lockout_conflicts.push_back({character_name, lockout}); } } } From 4d1abce0849c28411e59e4e1d9e83ce01eff5466 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 4 Sep 2020 21:11:20 -0400 Subject: [PATCH 104/196] Allow zone name in alt CreateExpedition api --- zone/lua_client.cpp | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 67eba3e27..23f546fce 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1667,24 +1667,37 @@ Lua_Expedition Lua_Client::CreateExpedition(luabind::object dz_info, luabind::ob // the dz_info table supports optional hash entries for 'compass', 'safereturn', and 'zonein' data if (luabind::type(dz_info["compass"]) == LUA_TTABLE) { - dz.SetCompass(DynamicZoneLocation{ - luabind::object_cast(dz_info["compass"][1]), - luabind::object_cast(dz_info["compass"][2]), - luabind::object_cast(dz_info["compass"][3]), - luabind::object_cast(dz_info["compass"][4]), - 0 - }); + luabind::object compass(dz_info["compass"]); + float x = luabind::object_cast(compass[2]); + float y = luabind::object_cast(compass[3]); + float z = luabind::object_cast(compass[4]); + + if (luabind::type(compass[1]) == LUA_TSTRING) + { + dz.SetCompass({ ZoneID(luabind::object_cast(compass[1])), x, y, z, 0.0f }); + } + else if (luabind::type(compass[1]) == LUA_TNUMBER) + { + dz.SetCompass({ luabind::object_cast(compass[1]), x, y, z, 0.0f }); + } } if (luabind::type(dz_info["safereturn"]) == LUA_TTABLE) { - dz.SetSafeReturn(DynamicZoneLocation{ - luabind::object_cast(dz_info["safereturn"][1]), - luabind::object_cast(dz_info["safereturn"][2]), - luabind::object_cast(dz_info["safereturn"][3]), - luabind::object_cast(dz_info["safereturn"][4]), - luabind::object_cast(dz_info["safereturn"][5]) - }); + luabind::object safereturn(dz_info["safereturn"]); + float x = luabind::object_cast(safereturn[2]); + float y = luabind::object_cast(safereturn[3]); + float z = luabind::object_cast(safereturn[4]); + float w = luabind::object_cast(safereturn[5]); + + if (luabind::type(safereturn[1]) == LUA_TSTRING) + { + dz.SetSafeReturn({ ZoneID(luabind::object_cast(safereturn[1])), x, y, z, w }); + } + else if (luabind::type(safereturn[1]) == LUA_TNUMBER) + { + dz.SetSafeReturn({ luabind::object_cast(safereturn[1]), x, y, z, w }); + } } if (luabind::type(dz_info["zonein"]) == LUA_TTABLE) From a597753bee072bc46b7981a350bb8b3547921bc6 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 6 Sep 2020 19:51:22 -0400 Subject: [PATCH 105/196] Increase default early dz shutdown time Increases default from 15 minutes to 25 minutes This compensates for current default graveyard timer code. Otherwise instances may shutdown before graveyards can process any corpses. A better fix needs implemented later to deal with corpses inside expired dynamic zone/instances. Zones without graveyards should move corpses to the non-instance version and zones with graveyards should somehow still be processed (maybe offloaded to world?) --- common/ruletypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 5af72acb9..d680ca21d 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -789,7 +789,7 @@ 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_INT(Expedition, EmptyDzShutdownDelaySeconds, 1500, "Seconds to set dynamic zone instance expiration if early shutdown enabled") RULE_INT(Expedition, RequestExpiredLockoutLeewaySeconds, 60, "Seconds remaining on lockout to count as expired for creation requests (client hides timers under 60s remaining)") RULE_INT(Expedition, WorldExpeditionProcessRateMS, 6000, "Timer interval (ms) that world checks expedition states") RULE_BOOL(Expedition, AlwaysNotifyNewLeaderOnChange, false, "Always notify clients when made expedition leader. If false (live-like) new leaders are only notified when made leader via /dzmakeleader") From 1d24432e47938bc9bcc3614f2137c30edb17c07f Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 8 Sep 2020 23:44:26 -0400 Subject: [PATCH 106/196] Truncate expedition members to max on creation This implements the new behavior from live's September 16, 2020 (test server's September 8, 2020) patch Expeditions can be created even when the client's group or raid exceeds the expedition's max player requirement. Members are added until the max player count is reached and the rest are ignored. Raid members are added ordered by their raid group number with ungrouped members having the lowest priority Rename expedition request method ValidateMembers to CanMembersJoin Change some expedition messages to System color (live changes) --- zone/expedition.cpp | 14 +++++++--- zone/expedition.h | 1 + zone/expedition_request.cpp | 55 +++++++++++++++++++++++++++++-------- zone/expedition_request.h | 4 ++- zone/raids.cpp | 13 +++++++++ zone/raids.h | 2 ++ 6 files changed, 72 insertions(+), 17 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index a955a6661..27a3d9324 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -40,6 +40,8 @@ const char* const EXPEDITION_OTHER_BELONGS = "{} attempted to create an expedi const char* const DZADD_INVITE_WARNING = "Warning! You will be given replay timers for the following events if you enter %s:"; const char* const DZADD_INVITE_WARNING_TIMER = "%s - %sD:%sH:%sM"; const char* const KICKPLAYERS_EVERYONE = "Everyone"; +// message string 8312 added in September 08 2020 Test patch (used by both dz and shared tasks) +const char* const CREATE_NOT_ALL_ADDED = "Not all players in your {} were added to the {}. The {} can take a maximum of {} players, and your {} has {}."; const int32_t Expedition::REPLAY_TIMER_ID = -1; const int32_t Expedition::EVENT_TIMER_ID = 1; @@ -130,10 +132,14 @@ Expedition* Expedition::TryCreate( inserted.first->second->SendUpdatesToZoneMembers(); inserted.first->second->SendWorldExpeditionUpdate(ServerOP_ExpeditionCreate); // cache in other zones + inserted.first->second->SendLeaderMessage(request.GetLeaderClient(), + Chat::System, EXPEDITION_AVAILABLE, { request.GetExpeditionName() }); - inserted.first->second->SendLeaderMessage( - request.GetLeaderClient(), Chat::Yellow, EXPEDITION_AVAILABLE, { request.GetExpeditionName() } - ); + if (!request.GetNotAllAddedMessage().empty()) + { + Client::SendCrossZoneMessage(request.GetLeaderClient(), request.GetLeaderName(), + Chat::System, request.GetNotAllAddedMessage()); + } return inserted.first->second.get(); } @@ -889,7 +895,7 @@ bool Expedition::ConfirmLeaderCommand(Client* requester) if (leader.char_id != requester->CharacterID()) { - requester->MessageString(Chat::Red, EXPEDITION_NOT_LEADER, leader.name.c_str()); + requester->MessageString(Chat::System, EXPEDITION_NOT_LEADER, leader.name.c_str()); return false; } diff --git a/zone/expedition.h b/zone/expedition.h index 611ad6c0c..490fd1f44 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -38,6 +38,7 @@ class ServerPacket; extern const char* const DZ_YOU_NOT_ASSIGNED; extern const char* const EXPEDITION_OTHER_BELONGS; +extern const char* const CREATE_NOT_ALL_ADDED; enum class ExpeditionMemberStatus : uint8_t { diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 474c1de08..5013d49a4 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -30,6 +30,8 @@ extern WorldServer worldserver; +constexpr char SystemName[] = "expedition"; + struct ExpeditionRequestConflict { std::string character_name; @@ -75,7 +77,7 @@ bool ExpeditionRequest::Validate(Client* requester) m_leader = m_requester; m_leader_id = m_requester->CharacterID(); m_leader_name = m_requester->GetName(); - requirements_met = ValidateMembers({m_leader_name}); + requirements_met = CanMembersJoin({m_leader_name}); } auto elapsed = benchmark.elapsed(); @@ -90,16 +92,34 @@ bool ExpeditionRequest::CanRaidRequest(Raid* raid) m_leader_name = raid->leadername; m_leader_id = m_leader ? m_leader->CharacterID() : database.GetCharacterID(raid->leadername); - std::vector member_names; - for (int i = 0; i < MAX_RAID_MEMBERS; ++i) + // live (as of September 16, 2020) supports creation even if raid count exceeds + // expedition max. members are added up to the max ordered by group number. + auto raid_members = raid->GetMembers(); + + if (raid_members.size() > m_max_players) { - if (raid->members[i].membername[0]) - { - member_names.emplace_back(raid->members[i].membername); - } + // stable_sort not needed, order within a raid group may not be what is displayed + std::sort(raid_members.begin(), raid_members.end(), + [&](const RaidMember& lhs, const RaidMember& rhs) { + if (m_leader_name == lhs.membername) { // leader always added first + return true; + } else if (m_leader_name == rhs.membername) { + return false; + } + return lhs.GroupNumber < rhs.GroupNumber; + }); + + m_not_all_added_msg = fmt::format(CREATE_NOT_ALL_ADDED, "raid", SystemName, + SystemName, m_max_players, "raid", raid_members.size()); } - return ValidateMembers(member_names); + std::vector member_names; + for (int i = 0; i < raid_members.size() && member_names.size() < m_max_players; ++i) + { + member_names.emplace_back(raid_members[i].membername); + } + + return CanMembersJoin(member_names); } bool ExpeditionRequest::CanGroupRequest(Group* group) @@ -109,20 +129,31 @@ bool ExpeditionRequest::CanGroupRequest(Group* group) { m_leader = group->GetLeader()->CastToClient(); } + // Group::GetLeaderName() is broken if group formed across zones, ask database instead m_leader_name = m_leader ? m_leader->GetName() : GetGroupLeaderName(group->GetID()); // group->GetLeaderName(); m_leader_id = m_leader ? m_leader->CharacterID() : database.GetCharacterID(m_leader_name.c_str()); std::vector member_names; + member_names.emplace_back(m_leader_name); // leader always added first + for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) { - if (group->membername[i][0]) + if (group->membername[i][0] && m_leader_name != group->membername[i]) { member_names.emplace_back(group->membername[i]); } } - return ValidateMembers(member_names); + if (member_names.size() > m_max_players) + { + m_not_all_added_msg = fmt::format(CREATE_NOT_ALL_ADDED, "group", SystemName, + SystemName, m_max_players, "group", member_names.size()); + + member_names.resize(m_max_players); + } + + return CanMembersJoin(member_names); } std::string ExpeditionRequest::GetGroupLeaderName(uint32_t group_id) @@ -132,7 +163,7 @@ std::string ExpeditionRequest::GetGroupLeaderName(uint32_t group_id) return std::string(leader_name_buffer); } -bool ExpeditionRequest::ValidateMembers(const std::vector& member_names) +bool ExpeditionRequest::CanMembersJoin(const std::vector& member_names) { if (member_names.empty()) { @@ -354,7 +385,7 @@ bool ExpeditionRequest::IsPlayerCountValidated(uint32_t member_count) { requirements_met = false; - SendLeaderMessage(Chat::Red, REQUIRED_PLAYER_COUNT, { + SendLeaderMessage(Chat::System, REQUIRED_PLAYER_COUNT, { fmt::format_int(member_count).str(), fmt::format_int(m_min_players).str(), fmt::format_int(m_max_players).str() diff --git a/zone/expedition_request.h b/zone/expedition_request.h index d0afb46c0..cf1151771 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -47,13 +47,14 @@ public: Client* GetLeaderClient() const { return m_leader; } uint32_t GetLeaderID() const { return m_leader_id; } const std::string& GetLeaderName() const { return m_leader_name; } + const std::string& GetNotAllAddedMessage() const { return m_not_all_added_msg; } uint32_t GetMinPlayers() const { return m_min_players; } uint32_t GetMaxPlayers() const { return m_max_players; } std::vector GetMembers() const { return m_members; } std::unordered_map GetLockouts() const { return m_lockouts; } private: - bool ValidateMembers(const std::vector& member_names); + bool CanMembersJoin(const std::vector& member_names); bool CanRaidRequest(Raid* raid); bool CanGroupRequest(Group* group); bool CheckMembersForConflicts(const std::vector& member_names); @@ -74,6 +75,7 @@ private: bool m_disable_messages = false; std::string m_expedition_name; std::string m_leader_name; + std::string m_not_all_added_msg; std::vector m_members; std::unordered_map m_lockouts; }; diff --git a/zone/raids.cpp b/zone/raids.cpp index 51c52d8d4..386eb70ac 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -1849,3 +1849,16 @@ void Raid::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_re } } } + +std::vector Raid::GetMembers() const +{ + std::vector raid_members; + for (int i = 0; i < MAX_RAID_MEMBERS; ++i) + { + if (members[i].membername[0]) + { + raid_members.emplace_back(members[i]); + } + } + return raid_members; +} diff --git a/zone/raids.h b/zone/raids.h index 26ba90a02..1e771e03d 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -239,6 +239,8 @@ public: void QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required = true, bool ignore_sender = true, float distance = 0, bool group_only = true); + std::vector GetMembers() const; + RaidMember members[MAX_RAID_MEMBERS]; char leadername[64]; protected: From 40717970ffe861e30c667c2c8eeaa4e8a0405d9c Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 11 Sep 2020 20:20:05 -0400 Subject: [PATCH 107/196] Cleanup some expedition message formatting --- zone/expedition.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 27a3d9324..f3f22fd06 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -211,8 +211,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) { auto member_id = strtoul(row[col::member_id], nullptr, 10); current_expedition->AddInternalMember( - row[col::member_name], member_id, ExpeditionMemberStatus::Offline - ); + row[col::member_name], member_id, ExpeditionMemberStatus::Offline); expedition_character_ids.emplace_back(expedition_id, member_id); } } @@ -654,11 +653,10 @@ void Expedition::SendClientExpeditionInvite( m_id, client->GetName(), inviter_name, swap_remove_name ); - client->SetPendingExpeditionInvite(ExpeditionInvite{m_id, inviter_name, swap_remove_name}); + client->SetPendingExpeditionInvite({ m_id, inviter_name, swap_remove_name }); - client->MessageString( - Chat::System, EXPEDITION_ASKED_TO_JOIN, m_leader.name.c_str(), m_expedition_name.c_str() - ); + client->MessageString(Chat::System, EXPEDITION_ASKED_TO_JOIN, + m_leader.name.c_str(), m_expedition_name.c_str()); // live (as of March 11 2020 patch) sends warnings for lockouts added // during current expedition that client would receive on entering dz @@ -1237,9 +1235,8 @@ void Expedition::ProcessMemberRemoved(const std::string& removed_char_name, uint 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() - ); + member_client->MessageString(Chat::Yellow, EXPEDITION_REMOVED, + it->name.c_str(), m_expedition_name.c_str()); } } @@ -1325,9 +1322,8 @@ void Expedition::SendUpdatesToZoneMembers(bool clear, bool message_on_clear) member_client->SendExpeditionLockoutTimers(); if (clear && message_on_clear) { - member_client->MessageString( - Chat::Yellow, EXPEDITION_REMOVED, member_client->GetName(), m_expedition_name.c_str() - ); + member_client->MessageString(Chat::Yellow, EXPEDITION_REMOVED, + member_client->GetName(), m_expedition_name.c_str()); } } } From ea34aa20303718b8c6d5d48fbd56016aef482b06 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 18 Sep 2020 13:42:11 -0400 Subject: [PATCH 108/196] Add group and raid api to check for lockout Add Group and Raid method DoesAnyMemberHaveExpeditionLockout This is required by some expeditions that perform a manual check for custom dialogue (Ikkinz group expeditions) --- zone/expedition.cpp | 24 ++++++++++++++++++++++++ zone/expedition.h | 4 ++++ zone/groups.cpp | 22 ++++++++++++++++++++++ zone/groups.h | 2 ++ zone/lua_group.cpp | 16 +++++++++++++++- zone/lua_group.h | 2 ++ zone/lua_raid.cpp | 15 ++++++++++++++- zone/lua_raid.h | 2 ++ zone/raids.cpp | 27 +++++++++++++++++++++++++++ zone/raids.h | 2 ++ 10 files changed, 114 insertions(+), 2 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index f3f22fd06..1b134bd28 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1642,6 +1642,26 @@ void Expedition::AddLockoutByCharacterName( } } +bool Expedition::HasLockoutByCharacterID( + uint32_t character_id, const std::string& expedition_name, const std::string& event_name) +{ + auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(character_id); + return std::any_of(lockouts.begin(), lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) { + return lockout.IsSameLockout(expedition_name, event_name); + }); +} + +bool Expedition::HasLockoutByCharacterName( + const std::string& character_name, const std::string& expedition_name, const std::string& event_name) +{ + if (!character_name.empty()) + { + uint32_t character_id = database.GetCharacterID(character_name.c_str()); + return HasLockoutByCharacterID(character_id, expedition_name, event_name); + } + return false; +} + void Expedition::RemoveLockoutsByCharacterID( uint32_t character_id, const std::string& expedition_name, const std::string& event_name) { @@ -2069,6 +2089,10 @@ std::string Expedition::GetLootEventBySpawnID(uint32_t spawn_id) std::vector Expedition::GetExpeditionLockoutsByCharacterID(uint32_t character_id) { std::vector lockouts; + if (character_id == 0) + { + return lockouts; + } auto client = entity_list.GetClientByCharID(character_id); if (client) diff --git a/zone/expedition.h b/zone/expedition.h index 490fd1f44..16e62a5d1 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -84,6 +84,10 @@ public: const std::string& event_name, uint32_t seconds, const std::string& uuid = {}); static void AddLockoutByCharacterName(const std::string& character_name, const std::string& expedition_name, const std::string& event_name, uint32_t seconds, const std::string& uuid = {}); + static bool HasLockoutByCharacterID(uint32_t character_id, + const std::string& expedition_name, const std::string& event_name); + static bool HasLockoutByCharacterName(const std::string& character_name, + const std::string& expedition_name, const std::string& event_name); static void RemoveLockoutsByCharacterID(uint32_t character_id, const std::string& expedition_name = {}, const std::string& event_name = {}); static void RemoveLockoutsByCharacterName(const std::string& character_name, diff --git a/zone/groups.cpp b/zone/groups.cpp index 60a58b99c..4b8e25d09 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -18,6 +18,7 @@ #include "../common/global_define.h" #include "../common/eqemu_logsys.h" +#include "expedition.h" #include "masterentity.h" #include "npc_ai.h" #include "../common/packet_functions.h" @@ -2503,3 +2504,24 @@ void Group::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_r } } } + +bool Group::DoesAnyMemberHaveExpeditionLockout( + const std::string& expedition_name, const std::string& event_name, int max_check_count) +{ + if (max_check_count <= 0) + { + max_check_count = MAX_GROUP_MEMBERS; + } + + for (int i = 0; i < MAX_GROUP_MEMBERS && i < max_check_count; ++i) + { + if (membername[i][0]) + { + if (Expedition::HasLockoutByCharacterName(membername[i], expedition_name, event_name)) + { + return true; + } + } + } + return false; +} diff --git a/zone/groups.h b/zone/groups.h index 0d39409e0..57dfdbfb6 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -153,6 +153,8 @@ public: inline int GetMentorPercent() { return mentor_percent; } inline Client *GetMentoree() { return mentoree; } + bool DoesAnyMemberHaveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, int max_check_count = 0); + Mob* members[MAX_GROUP_MEMBERS]; char membername[MAX_GROUP_MEMBERS][64]; uint8 MemberRoles[MAX_GROUP_MEMBERS]; diff --git a/zone/lua_group.cpp b/zone/lua_group.cpp index f297d72f1..2cac41418 100644 --- a/zone/lua_group.cpp +++ b/zone/lua_group.cpp @@ -107,6 +107,18 @@ Lua_Mob Lua_Group::GetMember(int index) { return self->members[index]; } +bool Lua_Group::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name) +{ + Lua_Safe_Call_Bool(); + return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name); +} + +bool Lua_Group::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name, int max_check_count) +{ + Lua_Safe_Call_Bool(); + return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name, max_check_count); +} + luabind::scope lua_register_group() { return luabind::class_("Group") .def(luabind::constructor<>()) @@ -129,7 +141,9 @@ luabind::scope lua_register_group() { .def("GetLowestLevel", (int(Lua_Group::*)(void))&Lua_Group::GetLowestLevel) .def("TeleportGroup", (void(Lua_Group::*)(Lua_Mob,uint32,uint32,float,float,float,float))&Lua_Group::TeleportGroup) .def("GetID", (int(Lua_Group::*)(void))&Lua_Group::GetID) - .def("GetMember", (Lua_Mob(Lua_Group::*)(int))&Lua_Group::GetMember); + .def("GetMember", (Lua_Mob(Lua_Group::*)(int))&Lua_Group::GetMember) + .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Group::*)(std::string, std::string))&Lua_Group::DoesAnyMemberHaveExpeditionLockout) + .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Group::*)(std::string, std::string, int))&Lua_Group::DoesAnyMemberHaveExpeditionLockout); } #endif diff --git a/zone/lua_group.h b/zone/lua_group.h index e1aa2a11d..46bc48f50 100644 --- a/zone/lua_group.h +++ b/zone/lua_group.h @@ -44,6 +44,8 @@ public: void TeleportGroup(Lua_Mob sender, uint32 zone_id, uint32 instance_id, float x, float y, float z, float h); int GetID(); Lua_Mob GetMember(int index); + bool DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name); + bool DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name, int max_check_count); }; #endif diff --git a/zone/lua_raid.cpp b/zone/lua_raid.cpp index c181b8bd2..ae01012d5 100644 --- a/zone/lua_raid.cpp +++ b/zone/lua_raid.cpp @@ -132,6 +132,17 @@ int Lua_Raid::GetGroupNumber(int index) { return self->members[index].GroupNumber; } +bool Lua_Raid::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name) +{ + Lua_Safe_Call_Bool(); + return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name); +} + +bool Lua_Raid::DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name, int max_check_count) +{ + Lua_Safe_Call_Bool(); + return self->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name, max_check_count); +} luabind::scope lua_register_raid() { return luabind::class_("Raid") @@ -158,7 +169,9 @@ luabind::scope lua_register_raid() { .def("TeleportRaid", (int(Lua_Raid::*)(Lua_Mob,uint32,uint32,float,float,float,float))&Lua_Raid::TeleportRaid) .def("GetID", (int(Lua_Raid::*)(void))&Lua_Raid::GetID) .def("GetMember", (Lua_Client(Lua_Raid::*)(int))&Lua_Raid::GetMember) - .def("GetGroupNumber", (int(Lua_Raid::*)(int))&Lua_Raid::GetGroupNumber); + .def("GetGroupNumber", (int(Lua_Raid::*)(int))&Lua_Raid::GetGroupNumber) + .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Raid::*)(std::string, std::string))&Lua_Raid::DoesAnyMemberHaveExpeditionLockout) + .def("DoesAnyMemberHaveExpeditionLockout", (bool(Lua_Raid::*)(std::string, std::string, int))&Lua_Raid::DoesAnyMemberHaveExpeditionLockout); } #endif diff --git a/zone/lua_raid.h b/zone/lua_raid.h index bc10a729c..9eb100f59 100644 --- a/zone/lua_raid.h +++ b/zone/lua_raid.h @@ -48,6 +48,8 @@ public: int GetID(); Lua_Client GetMember(int index); int GetGroupNumber(int index); + bool DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name); + bool DoesAnyMemberHaveExpeditionLockout(std::string expedition_name, std::string event_name, int max_check_count); }; #endif diff --git a/zone/raids.cpp b/zone/raids.cpp index 386eb70ac..55e6d9e55 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -20,6 +20,7 @@ #include "client.h" #include "entity.h" +#include "expedition.h" #include "groups.h" #include "mob.h" #include "raids.h" @@ -1862,3 +1863,29 @@ std::vector Raid::GetMembers() const } return raid_members; } + +bool Raid::DoesAnyMemberHaveExpeditionLockout( + const std::string& expedition_name, const std::string& event_name, int max_check_count) +{ + auto raid_members = GetMembers(); + + if (max_check_count > 0) + { + // priority is leader, group number, then ungrouped members + std::sort(raid_members.begin(), raid_members.end(), + [&](const RaidMember& lhs, const RaidMember& rhs) { + if (lhs.IsRaidLeader) { + return true; + } else if (rhs.IsRaidLeader) { + return false; + } + return lhs.GroupNumber < rhs.GroupNumber; + }); + + raid_members.resize(max_check_count); + } + + return std::any_of(raid_members.begin(), raid_members.end(), [&](const RaidMember& raid_member) { + return Expedition::HasLockoutByCharacterName(raid_member.membername, expedition_name, event_name); + }); +} diff --git a/zone/raids.h b/zone/raids.h index 1e771e03d..a04d1eb54 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -239,6 +239,8 @@ public: void QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required = true, bool ignore_sender = true, float distance = 0, bool group_only = true); + bool DoesAnyMemberHaveExpeditionLockout(const std::string& expedition_name, const std::string& event_name, int max_check_count = 0); + std::vector GetMembers() const; RaidMember members[MAX_RAID_MEMBERS]; From b5db40cba6bd3ab40783878c443b92ece3e05ec2 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 18 Sep 2020 18:30:20 -0400 Subject: [PATCH 109/196] Add GetZoneVersion to expedition api --- zone/lua_expedition.cpp | 6 ++++++ zone/lua_expedition.h | 1 + 2 files changed, 7 insertions(+) diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 55abece45..18ab84c96 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -115,6 +115,11 @@ int Lua_Expedition::GetZoneID() { return self->GetDynamicZone().GetZoneID(); } +int Lua_Expedition::GetZoneVersion() { + Lua_Safe_Call_Int(); + return self->GetDynamicZone().GetZoneVersion(); +} + bool Lua_Expedition::HasLockout(std::string event_name) { Lua_Safe_Call_Bool(); return self->HasLockout(event_name); @@ -209,6 +214,7 @@ luabind::scope lua_register_expedition() { .def("GetSecondsRemaining", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetSecondsRemaining) .def("GetUUID", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetUUID) .def("GetZoneID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetZoneID) + .def("GetZoneVersion", &Lua_Expedition::GetZoneVersion) .def("HasLockout", (bool(Lua_Expedition::*)(std::string))&Lua_Expedition::HasLockout) .def("HasReplayLockout", (bool(Lua_Expedition::*)(void))&Lua_Expedition::HasReplayLockout) .def("RemoveCompass", (void(Lua_Expedition::*)(void))&Lua_Expedition::RemoveCompass) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index adc54a65a..553472be7 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -66,6 +66,7 @@ public: int GetSecondsRemaining(); std::string GetUUID(); int GetZoneID(); + int GetZoneVersion(); bool HasLockout(std::string event_name); bool HasReplayLockout(); void RemoveCompass(); From 8e52dd0579b6217f27032f50b05aae0bed849cb1 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 18 Sep 2020 20:46:23 -0400 Subject: [PATCH 110/196] Add expedition lock messages --- common/servertalk.h | 8 ++++++++ zone/expedition.cpp | 41 +++++++++++++++++++++++++++++++---------- zone/expedition.h | 11 ++++++++++- zone/lua_expedition.cpp | 24 +++++++++++++++++++++++- zone/lua_expedition.h | 3 +++ zone/lua_parser.cpp | 3 ++- 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 32e3cd812..0b66ea474 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -2051,6 +2051,14 @@ struct ServerExpeditionLockout_Struct { char event_name[256]; }; +struct ServerExpeditionLockState_Struct { + uint32 expedition_id; + uint32 sender_zone_id; + uint16 sender_instance_id; + uint8 enabled; + uint8 lock_msg; // 0: none, 1: closing 2: trial begin +}; + struct ServerExpeditionSetting_Struct { uint32 expedition_id; uint32 sender_zone_id; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 1b134bd28..f7adc1e1d 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -42,6 +42,9 @@ const char* const DZADD_INVITE_WARNING_TIMER = "%s - %sD:%sH:%sM"; const char* const KICKPLAYERS_EVERYONE = "Everyone"; // message string 8312 added in September 08 2020 Test patch (used by both dz and shared tasks) const char* const CREATE_NOT_ALL_ADDED = "Not all players in your {} were added to the {}. The {} can take a maximum of {} players, and your {} has {}."; +// various expeditions re-use these strings when locking +constexpr char LOCK_CLOSE[] = "Your expedition is nearing its close. You cannot bring any additional people into your expedition at this time."; +constexpr char LOCK_BEGIN[] = "The trial has begun. You cannot bring any additional people into your expedition at this time."; const int32_t Expedition::REPLAY_TIMER_ID = -1; const int32_t Expedition::EVENT_TIMER_ID = 1; @@ -198,7 +201,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) bool is_locked = (strtoul(row[col::is_locked], nullptr, 10) != 0); expedition->SetReplayLockoutOnMemberJoin(add_replay_on_join); - expedition->SetLocked(is_locked); + expedition->SetLocked(is_locked, ExpeditionLockMessage::None); zone->expedition_cache.emplace(expedition_id, std::move(expedition)); } @@ -1114,10 +1117,23 @@ void Expedition::DzKickPlayers(Client* requester) requester->MessageString(Chat::Red, EXPEDITION_REMOVED, KICKPLAYERS_EVERYONE, m_expedition_name.c_str()); } -void Expedition::SetLocked(bool lock_expedition, bool update_db) +void Expedition::SetLocked( + bool lock_expedition, ExpeditionLockMessage lock_msg, bool update_db, uint32_t msg_color) { m_is_locked = lock_expedition; + if (m_is_locked && lock_msg != ExpeditionLockMessage::None && m_dynamiczone.IsCurrentZoneDzInstance()) + { + auto msg = (lock_msg == ExpeditionLockMessage::Close) ? LOCK_CLOSE : LOCK_BEGIN; + for (const auto& client_iter : entity_list.GetClientList()) + { + if (client_iter.second) + { + client_iter.second->Message(msg_color, msg); + } + } + } + if (update_db) { ExpeditionDatabase::UpdateLockState(m_id, lock_expedition); @@ -1813,6 +1829,18 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) break; } case ServerOP_ExpeditionLockState: + { + 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 (expedition) + { + expedition->SetLocked(buf->enabled, static_cast(buf->lock_msg)); + } + } + break; + } case ServerOP_ExpeditionReplayOnJoin: { auto buf = reinterpret_cast(pack->pBuffer); @@ -1821,14 +1849,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition) { - if (pack->opcode == ServerOP_ExpeditionLockState) - { - expedition->SetLocked(buf->enabled); - } - else if (pack->opcode == ServerOP_ExpeditionReplayOnJoin) - { - expedition->SetReplayLockoutOnMemberJoin(buf->enabled); - } + expedition->SetReplayLockoutOnMemberJoin(buf->enabled); } } break; diff --git a/zone/expedition.h b/zone/expedition.h index 16e62a5d1..23d57f359 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -23,6 +23,7 @@ #include "dynamiczone.h" #include "expedition_lockout_timer.h" +#include "../common/eq_constants.h" #include #include #include @@ -49,6 +50,13 @@ enum class ExpeditionMemberStatus : uint8_t LinkDead }; +enum class ExpeditionLockMessage : uint8_t +{ + None = 0, + Close, + Begin +}; + struct ExpeditionMember { uint32_t char_id = 0; @@ -114,7 +122,8 @@ public: 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 SetLocked(bool lock_expedition, bool update_db = false); + void SetLocked(bool lock_expedition, ExpeditionLockMessage lock_msg, + bool update_db = false, uint32_t msg_color = Chat::Yellow); void AddLockout(const std::string& event_name, uint32_t seconds); void AddReplayLockout(uint32_t seconds); diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 18ab84c96..1b3d66f78 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -152,7 +152,17 @@ void Lua_Expedition::SetCompass(std::string zone_name, float x, float y, float z void Lua_Expedition::SetLocked(bool lock_expedition) { Lua_Safe_Call_Void(); - self->SetLocked(lock_expedition, true); + self->SetLocked(lock_expedition, ExpeditionLockMessage::None, true); +} + +void Lua_Expedition::SetLocked(bool lock_expedition, int lock_msg) { + Lua_Safe_Call_Void(); + self->SetLocked(lock_expedition, static_cast(lock_msg), true); +} + +void Lua_Expedition::SetLocked(bool lock_expedition, int lock_msg, uint32_t msg_color) { + Lua_Safe_Call_Void(); + self->SetLocked(lock_expedition, static_cast(lock_msg), true, msg_color); } void Lua_Expedition::SetLootEventByNPCTypeID(uint32_t npc_type_id, std::string event_name) { @@ -222,6 +232,8 @@ luabind::scope lua_register_expedition() { .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("SetLocked", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetLocked) + .def("SetLocked", (void(Lua_Expedition::*)(bool, int))&Lua_Expedition::SetLocked) + .def("SetLocked", (void(Lua_Expedition::*)(bool, int, uint32_t))&Lua_Expedition::SetLocked) .def("SetLootEventByNPCTypeID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventByNPCTypeID) .def("SetLootEventBySpawnID", (void(Lua_Expedition::*)(uint32_t, std::string))&Lua_Expedition::SetLootEventBySpawnID) .def("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) @@ -232,4 +244,14 @@ luabind::scope lua_register_expedition() { .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t, bool))&Lua_Expedition::UpdateLockoutDuration); } +luabind::scope lua_register_expedition_lock_messages() { + return luabind::class_("ExpeditionLockMessage") + .enum_("constants") + [ + luabind::value("None", static_cast(ExpeditionLockMessage::None)), + luabind::value("Close", static_cast(ExpeditionLockMessage::Close)), + luabind::value("Begin", static_cast(ExpeditionLockMessage::Begin)) + ]; +} + #endif // LUA_EQEMU diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 553472be7..97c74b57e 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -39,6 +39,7 @@ namespace luabind { } luabind::scope lua_register_expedition(); +luabind::scope lua_register_expedition_lock_messages(); class Lua_Expedition : public Lua_Ptr { @@ -74,6 +75,8 @@ public: void SetCompass(uint32_t zone_id, float x, float y, float z); void SetCompass(std::string zone_name, float x, float y, float z); void SetLocked(bool lock_expedition); + void SetLocked(bool lock_expedition, int lock_msg); + void SetLocked(bool lock_expedition, int lock_msg, uint32_t color); void SetLootEventByNPCTypeID(uint32_t npc_type_id, std::string event_name); void SetLootEventBySpawnID(uint32_t spawn_id, std::string event_name); void SetReplayLockoutOnMemberJoin(bool enable); diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 063386713..494e3e0e2 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -1110,7 +1110,8 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_ruleb(), lua_register_journal_speakmode(), lua_register_journal_mode(), - lua_register_expedition() + lua_register_expedition(), + lua_register_expedition_lock_messages() ]; } catch(std::exception &ex) { From b377fd183a3f3ba9512036714ad3b5730a616f37 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 20 Sep 2020 10:58:21 -0400 Subject: [PATCH 111/196] Add api to add lockout duration Also supports reducing lockout duration Add Expedition::AddLockoutDuration Add Client::AddExpeditionLockoutDuration Some expeditions require adding to existing lockout durations during progression. These add the specified seconds to individual member lockout timers instead of setting a static duration based on internal expedition lockout like UpdateLockoutDuration. --- common/servertalk.h | 2 + world/zoneserver.cpp | 1 + zone/client.cpp | 32 +++++++ zone/client.h | 6 +- zone/expedition.cpp | 147 ++++++++++++++++++++++++++---- zone/expedition.h | 9 +- zone/expedition_database.cpp | 39 ++++++++ zone/expedition_database.h | 8 +- zone/expedition_lockout_timer.cpp | 9 ++ zone/expedition_lockout_timer.h | 2 + zone/lua_client.cpp | 12 +++ zone/lua_client.h | 2 + zone/lua_expedition.cpp | 12 +++ zone/lua_expedition.h | 2 + zone/worldserver.cpp | 3 +- 15 files changed, 261 insertions(+), 25 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 0b66ea474..b666980d0 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -161,6 +161,7 @@ #define ServerOP_ExpeditionLockState 0x0411 #define ServerOP_ExpeditionMembersRemoved 0x0412 #define ServerOP_ExpeditionDzDuration 0x0413 +#define ServerOP_ExpeditionLockoutDuration 0x0414 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 @@ -2048,6 +2049,7 @@ struct ServerExpeditionLockout_Struct { uint16 sender_instance_id; uint8 remove; uint8 members_only; + int seconds_adjust; char event_name[256]; }; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 4b750ed99..fc13394af 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1364,6 +1364,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { } case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: + case ServerOP_ExpeditionLockoutDuration: case ServerOP_ExpeditionLockState: case ServerOP_ExpeditionMemberStatus: case ServerOP_ExpeditionReplayOnJoin: diff --git a/zone/client.cpp b/zone/client.cpp index 92412439d..bd8155e39 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9642,6 +9642,38 @@ void Client::AddNewExpeditionLockout( AddExpeditionLockout(lockout, true); } +void Client::AddExpeditionLockoutDuration( + const std::string& expedition_name, const std::string& event_name, int seconds, + const std::string& uuid, bool update_db) +{ + auto it = std::find_if(m_expedition_lockouts.begin(), m_expedition_lockouts.end(), + [&](const ExpeditionLockoutTimer& lockout) { + return lockout.IsSameLockout(expedition_name, event_name); + }); + + if (it != m_expedition_lockouts.end()) + { + it->AddLockoutTime(seconds); + + if (!uuid.empty()) + { + it->SetUUID(uuid); + } + + if (update_db) + { + ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { *it }, true); + } + + SendExpeditionLockoutTimers(); + } + else if (seconds > 0) // missing lockouts inserted for reductions would be instantly expired + { + auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); + AddExpeditionLockout(lockout, update_db); + } +} + void Client::RemoveExpeditionLockout( const std::string& expedition_name, const std::string& event_name, bool update_db, bool update_client) { diff --git a/zone/client.h b/zone/client.h index a5f78a7f3..006b0b91a 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1118,8 +1118,10 @@ public: uint32_t string_id, const std::initializer_list& arguments = {}); void AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool update_db = false); - void AddNewExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, uint32_t duration, std::string uuid = {}); + void AddExpeditionLockoutDuration(const std::string& expedition_name, + const std::string& event_Name, int seconds, const std::string& uuid = {}, bool update_db = false); + void AddNewExpeditionLockout(const std::string& expedition_name, + const std::string& event_name, uint32_t duration, std::string uuid = {}); Expedition* CreateExpedition(DynamicZone& dz_instance, ExpeditionRequest& request); Expedition* CreateExpedition( const std::string& zone_name, uint32 version, uint32 duration, const std::string& expedition_name, diff --git a/zone/expedition.cpp b/zone/expedition.cpp index f7adc1e1d..f78838117 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -448,8 +448,7 @@ void Expedition::AddReplayLockout(uint32_t seconds) void Expedition::AddLockout(const std::string& event_name, uint32_t seconds) { - ExpeditionLockoutTimer lockout{m_uuid, m_expedition_name, event_name, 0, seconds}; - lockout.Reset(); // sets expire time + auto lockout = ExpeditionLockoutTimer::CreateLockout(m_expedition_name, event_name, seconds, m_uuid); AddLockout(lockout); } @@ -465,6 +464,33 @@ void Expedition::AddLockout(const ExpeditionLockoutTimer& lockout, bool members_ SendWorldLockoutUpdate(lockout, false, members_only); } +void Expedition::AddLockoutDuration(const std::string& event_name, int seconds, bool members_only) +{ + // lockout timers use unsigned durations to define intent but we may need + // to insert a new lockout while still supporting timer reductions + auto lockout = ExpeditionLockoutTimer::CreateLockout( + m_expedition_name, event_name, std::max(0, seconds), m_uuid); + + if (!members_only) + { + auto it = m_lockouts.find(event_name); + if (it != m_lockouts.end()) + { + it->second.AddLockoutTime(seconds); + ExpeditionDatabase::InsertLockout(m_id, it->second); // replaces current one + } + else + { + ExpeditionDatabase::InsertLockout(m_id, lockout); + } + } + + ExpeditionDatabase::AddLockoutDuration(m_members, lockout, seconds); + + ProcessLockoutDuration(lockout, seconds, members_only); + SendWorldLockoutDuration(lockout, seconds, members_only); +} + void Expedition::UpdateLockoutDuration( const std::string& event_name, uint32_t seconds, bool members_only) { @@ -473,8 +499,7 @@ void Expedition::UpdateLockoutDuration( if (it != m_lockouts.end()) { uint64_t expire_time = it->second.GetStartTime() + seconds; - ExpeditionLockoutTimer lockout{m_uuid, m_expedition_name, event_name, expire_time, seconds}; - AddLockout(lockout, members_only); + AddLockout({ m_uuid, m_expedition_name, event_name, expire_time, seconds }, members_only); } } @@ -1265,6 +1290,58 @@ void Expedition::ProcessMemberRemoved(const std::string& removed_char_name, uint ); } +void Expedition::ProcessLockoutDuration( + const ExpeditionLockoutTimer& lockout, int seconds, bool members_only) +{ + if (!members_only) + { + auto it = m_lockouts.find(lockout.GetEventName()); + if (it != m_lockouts.end()) + { + it->second.AddLockoutTime(seconds); + } + else + { + m_lockouts[lockout.GetEventName()] = lockout; + } + } + + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.char_id); + if (member_client) + { + member_client->AddExpeditionLockoutDuration(m_expedition_name, + lockout.GetEventName(), seconds, m_uuid); + } + } + + if (m_dynamiczone.IsCurrentZoneDzInstance()) + { + AddLockoutDurationNonMembers(lockout, seconds); + } +} + +void Expedition::AddLockoutDurationNonMembers(const ExpeditionLockoutTimer& lockout, int seconds) +{ + std::vector non_members; + for (const auto& client_iter : entity_list.GetClientList()) + { + Client* client = client_iter.second; + if (client && client->GetExpeditionID() != GetID()) + { + non_members.emplace_back(client->CharacterID(), client->GetName()); + client->AddExpeditionLockoutDuration(m_expedition_name, + lockout.GetEventName(), seconds, m_uuid); + } + } + + if (!non_members.empty()) + { + ExpeditionDatabase::AddLockoutDuration(non_members, lockout, seconds); + } +} + void Expedition::ProcessLockoutUpdate( const ExpeditionLockoutTimer& lockout, bool remove, bool members_only) { @@ -1301,22 +1378,27 @@ void Expedition::ProcessLockoutUpdate( // members leave the expedition but haven't been kicked from zone yet if (!remove && m_dynamiczone.IsCurrentZoneDzInstance()) { - std::vector non_members; - for (const auto& client_iter : entity_list.GetClientList()) - { - Client* client = client_iter.second; - if (client && client->GetExpeditionID() != GetID()) - { - non_members.emplace_back(client->CharacterID(), client->GetName()); - client->AddExpeditionLockout(lockout); - } - } + AddLockoutNonMembers(lockout); + } +} - if (!non_members.empty()) +void Expedition::AddLockoutNonMembers(const ExpeditionLockoutTimer& lockout) +{ + std::vector non_members; + for (const auto& client_iter : entity_list.GetClientList()) + { + Client* client = client_iter.second; + if (client && client->GetExpeditionID() != GetID()) { - ExpeditionDatabase::InsertMembersLockout(non_members, lockout); + non_members.emplace_back(client->CharacterID(), client->GetName()); + client->AddExpeditionLockout(lockout); } } + + if (!non_members.empty()) + { + ExpeditionDatabase::InsertMembersLockout(non_members, lockout); + } } void Expedition::SendUpdatesToZoneMembers(bool clear, bool message_on_clear) @@ -1492,6 +1574,23 @@ void Expedition::SendWorldLeaderChanged() worldserver.SendPacket(pack.get()); } +void Expedition::SendWorldLockoutDuration( + const ExpeditionLockoutTimer& lockout, int seconds, bool members_only) +{ + uint32_t pack_size = sizeof(ServerExpeditionLockout_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionLockoutDuration, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->expire_time = lockout.GetExpireTime(); + buf->duration = lockout.GetDuration(); + buf->sender_zone_id = zone ? zone->GetZoneID() : 0; + buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; + buf->members_only = members_only; + buf->seconds_adjust = seconds; + strn0cpy(buf->event_name, lockout.GetEventName().c_str(), sizeof(buf->event_name)); + worldserver.SendPacket(pack.get()); +} + void Expedition::SendWorldLockoutUpdate( const ExpeditionLockoutTimer& lockout, bool remove, bool members_only) { @@ -1766,6 +1865,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) break; } case ServerOP_ExpeditionLockout: + case ServerOP_ExpeditionLockoutDuration: { auto buf = reinterpret_cast(pack->pBuffer); if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) @@ -1773,10 +1873,17 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition) { - ExpeditionLockoutTimer lockout{ - expedition->GetUUID(), expedition->GetName(), buf->event_name, buf->expire_time, buf->duration - }; - expedition->ProcessLockoutUpdate(lockout, buf->remove, buf->members_only); + ExpeditionLockoutTimer lockout{ expedition->GetUUID(), expedition->GetName(), + buf->event_name, buf->expire_time, buf->duration }; + + if (pack->opcode == ServerOP_ExpeditionLockout) + { + expedition->ProcessLockoutUpdate(lockout, buf->remove, buf->members_only); + } + else if (pack->opcode == ServerOP_ExpeditionLockoutDuration) + { + expedition->ProcessLockoutDuration(lockout, buf->seconds_adjust, buf->members_only); + } } } break; diff --git a/zone/expedition.h b/zone/expedition.h index 23d57f359..a64a3a9bd 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -126,6 +126,7 @@ public: bool update_db = false, uint32_t msg_color = Chat::Yellow); void AddLockout(const std::string& event_name, uint32_t seconds); + void AddLockoutDuration(const std::string& event_name, int seconds, bool members_only = true); void AddReplayLockout(uint32_t seconds); bool HasLockout(const std::string& event_name); bool HasReplayLockout(); @@ -168,11 +169,14 @@ private: static void SendWorldCharacterLockout(uint32_t character_id, const ExpeditionLockoutTimer& lockout, bool remove); void AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only = false); + void AddLockoutDurationNonMembers(const ExpeditionLockoutTimer& lockout, int seconds); + void AddLockoutNonMembers(const ExpeditionLockoutTimer& lockout); void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status); bool ChooseNewLeader(); bool ConfirmLeaderCommand(Client* requester); bool ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping); void ProcessLeaderChanged(uint32_t new_leader_id, const std::string& new_leader_name); + void ProcessLockoutDuration(const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); void ProcessMakeLeader(Client* old_leader, Client* new_leader, const std::string& new_leader_name, bool is_online); void ProcessMemberAdded(const std::string& added_char_name, uint32_t added_char_id); @@ -189,7 +193,10 @@ private: void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); void SendWorldLeaderChanged(); - void SendWorldLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); + void SendWorldLockoutDuration( + const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); + void SendWorldLockoutUpdate( + const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = 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); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 44b7c6781..d818200f6 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -711,3 +711,42 @@ void ExpeditionDatabase::UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool database.QueryDatabase(query); } + +void ExpeditionDatabase::AddLockoutDuration(const std::vector& members, + const ExpeditionLockoutTimer& lockout, int seconds) +{ + LogExpeditionsDetail( + "Adding duration [{}] seconds to members lockouts [{}]:[{}]", + seconds, lockout.GetExpeditionName(), lockout.GetEventName()); + + 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.GetExpeditionUUID(), + EscapeString(lockout.GetExpeditionName()), + EscapeString(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, from_expedition_uuid, expedition_name, event_name) + VALUES {} + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = DATE_ADD(expire_time, INTERVAL {} SECOND), + duration = GREATEST(0, CAST(duration AS SIGNED) + {}); + ), insert_values, seconds, seconds); + + database.QueryDatabase(query); + } +} diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 184fdb1d7..9002dc600 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -44,7 +44,8 @@ namespace ExpeditionDatabase MySQLRequestResult LoadMembersForCreateRequest( const std::vector& character_names, const std::string& expedition_name); std::vector LoadCharacterLockouts(uint32_t character_id); - std::vector LoadCharacterLockouts(uint32_t character_id, const std::string& expedition_name); + std::vector LoadCharacterLockouts(uint32_t character_id, + const std::string& expedition_name); std::unordered_map> LoadMultipleExpeditionLockouts(const std::vector& expedition_ids); void DeleteAllMembers(uint32_t expedition_id); @@ -60,6 +61,9 @@ namespace ExpeditionDatabase void DeleteAllMembersPendingLockouts(const std::vector& members); uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); ExpeditionMember GetExpeditionLeader(uint32_t expedition_id); + std::pair, std::vector> GetMembersLockout( + const std::vector& members, const std::string& expedition_name, + const std::string& event_name); void InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, bool replace_timer, bool is_pending = false); @@ -71,6 +75,8 @@ namespace ExpeditionDatabase void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); void UpdateLockState(uint32_t expedition_id, bool is_locked); void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); + void AddLockoutDuration(const std::vector& members, + const ExpeditionLockoutTimer& lockout, int seconds); }; namespace LoadExpeditionColumns diff --git a/zone/expedition_lockout_timer.cpp b/zone/expedition_lockout_timer.cpp index ef0358122..208afb1ba 100644 --- a/zone/expedition_lockout_timer.cpp +++ b/zone/expedition_lockout_timer.cpp @@ -85,3 +85,12 @@ bool ExpeditionLockoutTimer::IsSameLockout( { return GetExpeditionName() == expedition_name && GetEventName() == event_name; } + +void ExpeditionLockoutTimer::AddLockoutTime(int seconds) +{ + auto new_duration = std::max(0, static_cast(m_duration.count()) + seconds); + + auto start_time = m_expire_time - m_duration; + m_duration = std::chrono::seconds(new_duration); + m_expire_time = start_time + m_duration; +} diff --git a/zone/expedition_lockout_timer.h b/zone/expedition_lockout_timer.h index 5527e42cf..d0fa4476b 100644 --- a/zone/expedition_lockout_timer.h +++ b/zone/expedition_lockout_timer.h @@ -45,6 +45,7 @@ public: std::string mins; }; + void AddLockoutTime(int seconds); uint32_t GetDuration() const { return static_cast(m_duration.count()); } uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(m_expire_time); } uint64_t GetStartTime() const { return std::chrono::system_clock::to_time_t(m_expire_time - m_duration); } @@ -59,6 +60,7 @@ public: bool IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const; bool IsSameLockout(const std::string& expedition_name, const std::string& event_name) const; void Reset() { m_expire_time = std::chrono::system_clock::now() + m_duration; } + void SetUUID(const std::string& uuid) { m_expedition_uuid = uuid; } private: bool m_is_replay_timer = false; diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 23f546fce..c8f91936c 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1802,6 +1802,16 @@ void Lua_Client::AddExpeditionLockout(std::string expedition_name, std::string e self->AddNewExpeditionLockout(expedition_name, event_name, seconds, uuid); } +void Lua_Client::AddExpeditionLockoutDuration(std::string expedition_name, std::string event_name, int seconds) { + Lua_Safe_Call_Void(); + self->AddExpeditionLockoutDuration(expedition_name, event_name, seconds, {}, true); +} + +void Lua_Client::AddExpeditionLockoutDuration(std::string expedition_name, std::string event_name, int seconds, std::string uuid) { + Lua_Safe_Call_Void(); + self->AddExpeditionLockoutDuration(expedition_name, event_name, seconds, uuid, true); +} + void Lua_Client::RemoveAllExpeditionLockouts() { Lua_Safe_Call_Void(); self->RemoveAllExpeditionLockouts({}, true); @@ -2148,6 +2158,8 @@ luabind::scope lua_register_client() { .def("GetLockoutExpeditionUUID", (std::string(Lua_Client::*)(std::string, std::string))&Lua_Client::GetLockoutExpeditionUUID) .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32))&Lua_Client::AddExpeditionLockout) .def("AddExpeditionLockout", (void(Lua_Client::*)(std::string, std::string, uint32, std::string))&Lua_Client::AddExpeditionLockout) + .def("AddExpeditionLockoutDuration", (void(Lua_Client::*)(std::string, std::string, int))&Lua_Client::AddExpeditionLockoutDuration) + .def("AddExpeditionLockoutDuration", (void(Lua_Client::*)(std::string, std::string, int, std::string))&Lua_Client::AddExpeditionLockoutDuration) .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(void))&Lua_Client::RemoveAllExpeditionLockouts) .def("RemoveAllExpeditionLockouts", (void(Lua_Client::*)(std::string))&Lua_Client::RemoveAllExpeditionLockouts) .def("RemoveExpeditionLockout", (void(Lua_Client::*)(std::string, std::string))&Lua_Client::RemoveExpeditionLockout) diff --git a/zone/lua_client.h b/zone/lua_client.h index f66504616..35782875b 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -348,6 +348,8 @@ public: std::string GetLockoutExpeditionUUID(std::string expedition_name, std::string event_name); void AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds); void AddExpeditionLockout(std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid); + void AddExpeditionLockoutDuration(std::string expedition_name, std::string event_name, int seconds); + void AddExpeditionLockoutDuration(std::string expedition_name, std::string event_name, int seconds, std::string uuid); void RemoveAllExpeditionLockouts(); void RemoveAllExpeditionLockouts(std::string expedition_name); void RemoveExpeditionLockout(std::string expedition_name, std::string event_name); diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 1b3d66f78..517d0c012 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -31,6 +31,16 @@ void Lua_Expedition::AddLockout(std::string event_name, uint32_t seconds) { self->AddLockout(event_name, seconds); } +void Lua_Expedition::AddLockoutDuration(std::string event_name, int seconds) { + Lua_Safe_Call_Void(); + self->AddLockoutDuration(event_name, seconds); +} + +void Lua_Expedition::AddLockoutDuration(std::string event_name, int seconds, bool members_only) { + Lua_Safe_Call_Void(); + self->AddLockoutDuration(event_name, seconds, members_only); +} + void Lua_Expedition::AddReplayLockout(uint32_t seconds) { Lua_Safe_Call_Void(); self->AddReplayLockout(seconds); @@ -211,6 +221,8 @@ luabind::scope lua_register_expedition() { .property("null", &Lua_Expedition::Null) .property("valid", &Lua_Expedition::Valid) .def("AddLockout", (void(Lua_Expedition::*)(std::string, uint32_t))&Lua_Expedition::AddLockout) + .def("AddLockoutDuration", (void(Lua_Expedition::*)(std::string, int))&Lua_Expedition::AddLockoutDuration) + .def("AddLockoutDuration", (void(Lua_Expedition::*)(std::string, int, bool))&Lua_Expedition::AddLockoutDuration) .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) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 97c74b57e..85166a65e 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -54,6 +54,8 @@ public: } void AddLockout(std::string event_name, uint32_t seconds); + void AddLockoutDuration(std::string event_name, int seconds); + void AddLockoutDuration(std::string event_name, int seconds, bool members_only); void AddReplayLockout(uint32_t seconds); uint32_t GetID(); int GetInstanceID(); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 07c97e410..c144aadf6 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2894,6 +2894,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionDeleted: case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: + case ServerOP_ExpeditionLockoutDuration: case ServerOP_ExpeditionLockState: case ServerOP_ExpeditionMemberChange: case ServerOP_ExpeditionMemberSwap: @@ -3302,4 +3303,4 @@ void WorldServer::OnKeepAlive(EQ::Timer *t) { ServerPacket pack(ServerOP_KeepAlive, 0); SendPacket(&pack); -} \ No newline at end of file +} From 76b5803f8371a1175589cb7ce45d7477051eb336 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 16 Jul 2020 19:13:19 -0400 Subject: [PATCH 112/196] Let MovePCDynamicZone filter on zone version Add client GetDynamicZones method to provide a single method to get all associated client dynamic zones Refactor compass update and MovePCDynamicZone to use this method instead of searching for client dzs separately Add optional disable message arg to MovePCDynamicZone --- zone/client.cpp | 95 ++++++++++++++++++++++----------------------- zone/client.h | 6 ++- zone/dynamiczone.h | 11 ++++++ zone/lua_client.cpp | 26 ++++++++++++- zone/lua_client.h | 4 ++ 5 files changed, 90 insertions(+), 52 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index bd8155e39..f05941ee6 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9856,20 +9856,18 @@ void Client::SetDzRemovalTimer(bool enable_timer) 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 + // client may be associated with multiple dynamic zone compasses in this zone std::vector compass_entries; - Expedition* expedition = GetExpedition(); - if (expedition) + for (const auto& client_dz : GetDynamicZones()) { - auto compass = expedition->GetDynamicZone().GetCompassLocation(); + auto compass = client_dz.dynamic_zone.GetCompassLocation(); if (zone && zone->GetZoneID() == compass.zone_id && zone->GetInstanceID() == 0) { 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.dz_zone_id = static_cast(client_dz.dynamic_zone.GetZoneID()); + entry.dz_instance_id = static_cast(client_dz.dynamic_zone.GetInstanceID()); + entry.dz_type = static_cast(client_dz.dynamic_zone.GetType()); entry.x = compass.x; entry.y = compass.y; entry.z = compass.z; @@ -9878,8 +9876,6 @@ void Client::SendDzCompassUpdate() } } - // todo: tasks, missions, and quests with an associated dz - // compass set via MarkSingleCompassLocation() if (m_has_quest_compass) { @@ -9922,57 +9918,52 @@ void Client::GoToDzSafeReturnOrBind(const DynamicZone& dynamic_zone) } } -void Client::MovePCDynamicZone(uint32 zone_id) +std::vector Client::GetDynamicZones(uint32_t zone_id, int zone_version) +{ + std::vector client_dzs; + + // check client systems for any associated dynamic zones optionally filtered by zone + Expedition* expedition = GetExpedition(); + if (expedition && + (zone_id == 0 || expedition->GetDynamicZone().GetZoneID() == zone_id) && + (zone_version < 0 || expedition->GetDynamicZone().GetZoneVersion() == zone_version)) + { + client_dzs.emplace_back(expedition->GetName(), expedition->GetLeaderName(), expedition->GetDynamicZone()); + } + + // todo: tasks, missions (shared tasks), and quests with an associated dz to zone_id + + return client_dzs; +} + +void Client::MovePCDynamicZone(uint32 zone_id, int zone_version, bool msg_if_invalid) { 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()); - 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 + auto client_dzs = GetDynamicZones(zone_id, zone_version); if (client_dzs.empty()) { - MessageString(Chat::Red, DYNAMICZONE_WAY_IS_BLOCKED); // unconfirmed message + if (msg_if_invalid) + { + MessageString(Chat::Red, DYNAMICZONE_WAY_IS_BLOCKED); // unconfirmed message + } } else if (client_dzs.size() == 1) { - if (single_dz.IsValid()) - { - 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); - } + const DynamicZone& dz = client_dzs[0].dynamic_zone; + DynamicZoneLocation zonein = dz.GetZoneInLocation(); + ZoneMode zone_mode = dz.HasZoneInLocation() ? ZoneMode::ZoneSolicited : ZoneMode::ZoneToSafeCoords; + MovePC(zone_id, dz.GetInstanceID(), zonein.x, zonein.y, zonein.z, zonein.heading, 0, zone_mode); } - else if (client_dzs.size() > 1) + else { LogDynamicZonesDetail( "Sending DzSwitchListWnd to character [{}] associated with [{}] dynamic zone(s)", - CharacterID(), client_dzs.size() - ); + 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 @@ -9983,14 +9974,20 @@ void Client::MovePCDynamicZone(uint32 zone_id) auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzChooseZone, outsize)); auto outbuf = reinterpret_cast(outapp->pBuffer); outbuf->count = count; - memcpy(outbuf->choices, client_dzs.data(), entries_size); - + for (int i = 0; i < client_dzs.size(); ++i) + { + outbuf->choices[i].dz_zone_id = client_dzs[i].dynamic_zone.GetZoneID(); + outbuf->choices[i].dz_instance_id = client_dzs[i].dynamic_zone.GetInstanceID(); + outbuf->choices[i].dz_type = static_cast(client_dzs[i].dynamic_zone.GetType()); + strn0cpy(outbuf->choices[i].description, client_dzs[i].description.c_str(), sizeof(outbuf->choices[i].description)); + strn0cpy(outbuf->choices[i].leader_name, client_dzs[i].leader_name.c_str(), sizeof(outbuf->choices[i].leader_name)); + } QueuePacket(outapp.get()); } } -void Client::MovePCDynamicZone(const std::string& zone_name) +void Client::MovePCDynamicZone(const std::string& zone_name, int zone_version, bool msg_if_invalid) { auto zone_id = ZoneID(zone_name.c_str()); - MovePCDynamicZone(zone_id); + MovePCDynamicZone(zone_id, zone_version, msg_if_invalid); } diff --git a/zone/client.h b/zone/client.h index 006b0b91a..3e19b6a92 100644 --- a/zone/client.h +++ b/zone/client.h @@ -31,6 +31,7 @@ class Object; class Raid; class Seperator; class ServerPacket; +struct DynamicZoneInfo; struct DynamicZoneLocation; enum WaterRegionType : int; @@ -1148,8 +1149,9 @@ public: void SetDzRemovalTimer(bool enable_timer); void SendDzCompassUpdate(); void GoToDzSafeReturnOrBind(const DynamicZone& dynamic_zone); - void MovePCDynamicZone(uint32 zone_id); - void MovePCDynamicZone(const std::string& zone_name); + void MovePCDynamicZone(uint32 zone_id, int zone_version = -1, bool msg_if_invalid = true); + void MovePCDynamicZone(const std::string& zone_name, int zone_version = -1, bool msg_if_invalid = true); + std::vector GetDynamicZones(uint32_t zone_id = 0, int zone_version = -1); void CalcItemScale(); bool CalcItemScale(uint32 slot_x, uint32 slot_y); // behavior change: 'slot_y' is now [RANGE]_END and not [RANGE]_END + 1 diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index cdff3c4e2..4d65b654e 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -116,4 +116,15 @@ private: std::chrono::time_point m_expire_time; }; +struct DynamicZoneInfo +{ + std::string description; // from owning system + std::string leader_name; + DynamicZone dynamic_zone; + + DynamicZoneInfo() = default; + DynamicZoneInfo(std::string desc, std::string leader, const DynamicZone& dz) + : description(std::move(desc)), leader_name(std::move(leader)), dynamic_zone(dz) {} +}; + #endif diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index c8f91936c..2946e90b9 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1837,11 +1837,31 @@ void Lua_Client::MovePCDynamicZone(uint32 zone_id) { return self->MovePCDynamicZone(zone_id); } +void Lua_Client::MovePCDynamicZone(uint32 zone_id, int zone_version) { + Lua_Safe_Call_Void(); + return self->MovePCDynamicZone(zone_id, zone_version); +} + +void Lua_Client::MovePCDynamicZone(uint32 zone_id, int zone_version, bool msg_if_invalid) { + Lua_Safe_Call_Void(); + return self->MovePCDynamicZone(zone_id, zone_version, msg_if_invalid); +} + void Lua_Client::MovePCDynamicZone(std::string zone_name) { Lua_Safe_Call_Void(); return self->MovePCDynamicZone(zone_name); } +void Lua_Client::MovePCDynamicZone(std::string zone_name, int zone_version) { + Lua_Safe_Call_Void(); + return self->MovePCDynamicZone(zone_name, zone_version); +} + +void Lua_Client::MovePCDynamicZone(std::string zone_name, int zone_version, bool msg_if_invalid) { + Lua_Safe_Call_Void(); + return self->MovePCDynamicZone(zone_name, zone_version, msg_if_invalid); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -2165,7 +2185,11 @@ luabind::scope lua_register_client() { .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("MovePCDynamicZone", (void(Lua_Client::*)(uint32))&Lua_Client::MovePCDynamicZone) - .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string))&Lua_Client::MovePCDynamicZone); + .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32, int))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32, int, bool))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int))&Lua_Client::MovePCDynamicZone) + .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int, bool))&Lua_Client::MovePCDynamicZone); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index 35782875b..41ce4e5cf 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -355,7 +355,11 @@ public: 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(uint32 zone_id, int zone_version); + void MovePCDynamicZone(uint32 zone_id, int zone_version, bool msg_if_invalid); void MovePCDynamicZone(std::string zone_name); + void MovePCDynamicZone(std::string zone_name, int zone_version); + void MovePCDynamicZone(std::string zone_name, int zone_version, bool msg_if_invalid); }; #endif From c45840173e9f2ff7fbe4b46423a94cabe33b5a73 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 21 Sep 2020 18:56:44 -0400 Subject: [PATCH 113/196] Load dz from cache on client switchlist reply --- zone/client_packet.cpp | 15 +++++++++------ zone/dynamiczone.cpp | 5 +++++ zone/dynamiczone.h | 2 ++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 824928c71..12df5cb0e 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5653,14 +5653,17 @@ void Client::Handle_OP_DzChooseZoneReply(const EQApplicationPacket *app) return; } - DynamicZone dz = DynamicZone::LoadDzFromDatabase(dzmsg->dz_instance_id); - DynamicZoneLocation loc = dz.GetZoneInLocation(); - ZoneMode zone_mode = ZoneMode::ZoneToSafeCoords; - if (dz.HasZoneInLocation()) + auto client_dzs = GetDynamicZones(); + auto it = std::find_if(client_dzs.begin(), client_dzs.end(), [&](const DynamicZoneInfo dz_info) { + return dz_info.dynamic_zone.IsSameDz(dzmsg->dz_zone_id, dzmsg->dz_instance_id); + }); + + if (it != client_dzs.end()) { - zone_mode = ZoneMode::ZoneSolicited; + DynamicZoneLocation loc = it->dynamic_zone.GetZoneInLocation(); + ZoneMode zone_mode = it->dynamic_zone.HasZoneInLocation() ? ZoneMode::ZoneSolicited : ZoneMode::ZoneToSafeCoords; + MovePC(dzmsg->dz_zone_id, dzmsg->dz_instance_id, loc.x, loc.y, loc.z, loc.heading, 0, zone_mode); } - 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/dynamiczone.cpp b/zone/dynamiczone.cpp index 17de013d8..64a28b8d2 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -513,6 +513,11 @@ bool DynamicZone::IsInstanceID(uint32_t instance_id) const return (GetInstanceID() != 0 && GetInstanceID() == instance_id); } +bool DynamicZone::IsSameDz(uint32_t zone_id, uint32_t instance_id) const +{ + return zone_id == m_zone_id && instance_id == m_instance_id; +} + uint32_t DynamicZone::GetSecondsRemaining() const { auto now = std::chrono::system_clock::now(); diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index 4d65b654e..693107840 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -71,6 +71,7 @@ public: uint16_t GetInstanceID() const { return static_cast(m_instance_id); }; uint32_t GetSecondsRemaining() const; uint16_t GetZoneID() const { return static_cast(m_zone_id); }; + uint32_t GetZoneIndex() const { return (m_instance_id << 16) | (m_zone_id & 0xffff); } uint32_t GetZoneVersion() const { return m_version; }; DynamicZoneType GetType() const { return m_type; } DynamicZoneLocation GetCompassLocation() const { return m_compass; } @@ -83,6 +84,7 @@ public: bool IsCurrentZoneDzInstance() const; bool IsInstanceID(uint32_t instance_id) const; bool IsValid() const { return m_instance_id != 0; } + bool IsSameDz(uint32_t zone_id, uint32_t instance_id) const; void LoadFromDatabase(uint32_t instance_id); void RemoveAllCharacters(bool enable_removal_timers = true); void RemoveCharacter(uint32_t character_id); From 3db23e402c4a83554f14dd4ce7a588cf03af4562 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 21 Sep 2020 20:15:08 -0400 Subject: [PATCH 114/196] Add api to add lockout to all clients in zone --- zone/expedition.cpp | 30 ++++++++++++++++-------------- zone/expedition.h | 4 ++-- zone/lua_general.cpp | 12 ++++++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index f78838117..9e3cbf855 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1318,27 +1318,28 @@ void Expedition::ProcessLockoutDuration( if (m_dynamiczone.IsCurrentZoneDzInstance()) { - AddLockoutDurationNonMembers(lockout, seconds); + AddLockoutDurationClients(lockout, seconds, GetID()); } } -void Expedition::AddLockoutDurationNonMembers(const ExpeditionLockoutTimer& lockout, int seconds) +void Expedition::AddLockoutDurationClients( + const ExpeditionLockoutTimer& lockout, int seconds, uint32_t exclude_id) { - std::vector non_members; + std::vector lockout_clients; for (const auto& client_iter : entity_list.GetClientList()) { Client* client = client_iter.second; - if (client && client->GetExpeditionID() != GetID()) + if (client && (exclude_id == 0 || client->GetExpeditionID() != exclude_id)) { - non_members.emplace_back(client->CharacterID(), client->GetName()); + lockout_clients.emplace_back(client->CharacterID(), client->GetName()); client->AddExpeditionLockoutDuration(m_expedition_name, lockout.GetEventName(), seconds, m_uuid); } } - if (!non_members.empty()) + if (!lockout_clients.empty()) { - ExpeditionDatabase::AddLockoutDuration(non_members, lockout, seconds); + ExpeditionDatabase::AddLockoutDuration(lockout_clients, lockout, seconds); } } @@ -1378,26 +1379,27 @@ void Expedition::ProcessLockoutUpdate( // members leave the expedition but haven't been kicked from zone yet if (!remove && m_dynamiczone.IsCurrentZoneDzInstance()) { - AddLockoutNonMembers(lockout); + AddLockoutClients(lockout, GetID()); } } -void Expedition::AddLockoutNonMembers(const ExpeditionLockoutTimer& lockout) +void Expedition::AddLockoutClients( + const ExpeditionLockoutTimer& lockout, uint32_t exclude_expedition_id) { - std::vector non_members; + std::vector lockout_clients; for (const auto& client_iter : entity_list.GetClientList()) { Client* client = client_iter.second; - if (client && client->GetExpeditionID() != GetID()) + if (client && (exclude_expedition_id == 0 || client->GetExpeditionID() != exclude_expedition_id)) { - non_members.emplace_back(client->CharacterID(), client->GetName()); + lockout_clients.emplace_back(client->CharacterID(), client->GetName()); client->AddExpeditionLockout(lockout); } } - if (!non_members.empty()) + if (!lockout_clients.empty()) { - ExpeditionDatabase::InsertMembersLockout(non_members, lockout); + ExpeditionDatabase::InsertMembersLockout(lockout_clients, lockout); } } diff --git a/zone/expedition.h b/zone/expedition.h index a64a3a9bd..57b50deb0 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -100,6 +100,7 @@ public: const std::string& expedition_name = {}, const std::string& event_name = {}); static void RemoveLockoutsByCharacterName(const std::string& character_name, const std::string& expedition_name = {}, const std::string& event_name = {}); + static void AddLockoutClients(const ExpeditionLockoutTimer& lockout, uint32_t exclude_id = 0); uint32_t GetID() const { return m_id; } uint16_t GetInstanceID() const { return m_dynamiczone.GetInstanceID(); } @@ -169,8 +170,7 @@ private: static void SendWorldCharacterLockout(uint32_t character_id, const ExpeditionLockoutTimer& lockout, bool remove); void AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only = false); - void AddLockoutDurationNonMembers(const ExpeditionLockoutTimer& lockout, int seconds); - void AddLockoutNonMembers(const ExpeditionLockoutTimer& lockout); + void AddLockoutDurationClients(const ExpeditionLockoutTimer& lockout, int seconds, uint32_t exclude_id = 0); void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status); bool ChooseNewLeader(); bool ConfirmLeaderCommand(Client* requester); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 1e5e12272..b5cc52684 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -2258,6 +2258,16 @@ luabind::object lua_get_expedition_lockouts_by_char_id(lua_State* L, uint32 char return lua_table; } +void lua_add_expedition_lockout_all_clients(std::string expedition_name, std::string event_name, uint32 seconds) { + auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds); + Expedition::AddLockoutClients(lockout); +} + +void lua_add_expedition_lockout_all_clients(std::string expedition_name, std::string event_name, uint32 seconds, std::string uuid) { + auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); + Expedition::AddLockoutClients(lockout); +} + void lua_add_expedition_lockout_by_char_id(uint32 char_id, std::string expedition_name, std::string event_name, uint32 seconds) { Expedition::AddLockoutByCharacterID(char_id, expedition_name, event_name, seconds); } @@ -2883,6 +2893,8 @@ luabind::scope lua_register_general() { luabind::def("get_expedition_lockout_by_char_id", &lua_get_expedition_lockout_by_char_id), luabind::def("get_expedition_lockouts_by_char_id", (luabind::object(*)(lua_State*, uint32))&lua_get_expedition_lockouts_by_char_id), luabind::def("get_expedition_lockouts_by_char_id", (luabind::object(*)(lua_State*, uint32, std::string))&lua_get_expedition_lockouts_by_char_id), + luabind::def("add_expedition_lockout_all_clients", (void(*)(std::string, std::string, uint32))&lua_add_expedition_lockout_all_clients), + luabind::def("add_expedition_lockout_all_clients", (void(*)(std::string, std::string, uint32, std::string))&lua_add_expedition_lockout_all_clients), luabind::def("add_expedition_lockout_by_char_id", (void(*)(uint32, std::string, std::string, uint32))&lua_add_expedition_lockout_by_char_id), luabind::def("add_expedition_lockout_by_char_id", (void(*)(uint32, std::string, std::string, uint32, std::string))&lua_add_expedition_lockout_by_char_id), luabind::def("remove_expedition_lockout_by_char_id", &lua_remove_expedition_lockout_by_char_id), From cbccd720502cf3a87d6dfeacd7a146a54b3f1be1 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 22 Sep 2020 18:18:24 -0400 Subject: [PATCH 115/196] Truncate members after conflict checks This more accurately matches live for the new behavior introduced in the September 16, 2020 patch. All members of a raid/group are still checked for conflicts and the truncation is only allowed if there are none. It might make sense to add a rule for this since ignoring members that would exceed the expedition max from the start makes it more convenient to create expeditions. Members that wouldn't be added anyway don't really need their conflicts checked. --- zone/expedition_database.cpp | 4 ++-- zone/expedition_request.cpp | 18 +++++++++++------- zone/expedition_request.h | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index d818200f6..1f7bbdeb7 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -261,8 +261,8 @@ MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( AND lockout.expedition_name = '{}' LEFT JOIN expedition_members member ON character_data.id = member.character_id WHERE character_data.name IN ({}) - ORDER BY character_data.id; - ), EscapeString(expedition_name), in_character_names_query); + ORDER BY FIELD(character_data.name, {}) + ), EscapeString(expedition_name), in_character_names_query, in_character_names_query); results = database.QueryDatabase(query); } diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 5013d49a4..1b8f5170b 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -113,8 +113,9 @@ bool ExpeditionRequest::CanRaidRequest(Raid* raid) SystemName, m_max_players, "raid", raid_members.size()); } + // live still performs conflict checks for all members even those beyond max std::vector member_names; - for (int i = 0; i < raid_members.size() && member_names.size() < m_max_players; ++i) + for (int i = 0; i < raid_members.size(); ++i) { member_names.emplace_back(raid_members[i].membername); } @@ -149,8 +150,6 @@ bool ExpeditionRequest::CanGroupRequest(Group* group) { m_not_all_added_msg = fmt::format(CREATE_NOT_ALL_ADDED, "group", SystemName, SystemName, m_max_players, "group", member_names.size()); - - member_names.resize(m_max_players); } return CanMembersJoin(member_names); @@ -181,7 +180,7 @@ bool ExpeditionRequest::CanMembersJoin(const std::vector& member_na // maybe it's done intentionally as a way to preview lockout conflicts if (requirements_met) { - requirements_met = IsPlayerCountValidated(static_cast(member_names.size())); + requirements_met = IsPlayerCountValidated(); } return requirements_met; @@ -373,7 +372,7 @@ void ExpeditionRequest::SendLeaderMemberEventLockout( }); } -bool ExpeditionRequest::IsPlayerCountValidated(uint32_t member_count) +bool ExpeditionRequest::IsPlayerCountValidated() { // note: offline group members count towards requirement but not added to expedition bool requirements_met = true; @@ -381,12 +380,17 @@ bool ExpeditionRequest::IsPlayerCountValidated(uint32_t member_count) auto bypass_status = RuleI(Expedition, MinStatusToBypassPlayerCountRequirements); 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)) + if (m_members.size() > m_max_players) + { + // members were sorted at start, truncate after conflict checks to act like live + m_members.resize(m_max_players); + } + else if (!gm_bypass && m_members.size() < m_min_players) { requirements_met = false; SendLeaderMessage(Chat::System, REQUIRED_PLAYER_COUNT, { - fmt::format_int(member_count).str(), + fmt::format_int(m_members.size()).str(), fmt::format_int(m_min_players).str(), fmt::format_int(m_max_players).str() }); diff --git a/zone/expedition_request.h b/zone/expedition_request.h index cf1151771..edfdd6122 100644 --- a/zone/expedition_request.h +++ b/zone/expedition_request.h @@ -59,7 +59,7 @@ private: bool CanGroupRequest(Group* group); bool CheckMembersForConflicts(const std::vector& member_names); std::string GetGroupLeaderName(uint32_t group_id); - bool IsPlayerCountValidated(uint32_t member_count); + bool IsPlayerCountValidated(); 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); From 2c2c2ac5ee1734eba84801da7ade36a3ae5de6fa Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 22 Sep 2020 22:46:35 -0400 Subject: [PATCH 116/196] Add api to get expedition's zone name --- zone/lua_expedition.cpp | 7 +++++++ zone/lua_expedition.h | 1 + 2 files changed, 8 insertions(+) diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 517d0c012..18df8e98a 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -22,6 +22,7 @@ #include "lua_expedition.h" #include "expedition.h" +#include "zone_store.h" #include "lua.hpp" #include #include @@ -125,6 +126,11 @@ int Lua_Expedition::GetZoneID() { return self->GetDynamicZone().GetZoneID(); } +std::string Lua_Expedition::GetZoneName() { + Lua_Safe_Call_String(); + return ZoneName(self->GetDynamicZone().GetZoneID()); +} + int Lua_Expedition::GetZoneVersion() { Lua_Safe_Call_Int(); return self->GetDynamicZone().GetZoneVersion(); @@ -236,6 +242,7 @@ luabind::scope lua_register_expedition() { .def("GetSecondsRemaining", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetSecondsRemaining) .def("GetUUID", (std::string(Lua_Expedition::*)(void))&Lua_Expedition::GetUUID) .def("GetZoneID", (int(Lua_Expedition::*)(void))&Lua_Expedition::GetZoneID) + .def("GetZoneName", &Lua_Expedition::GetZoneName) .def("GetZoneVersion", &Lua_Expedition::GetZoneVersion) .def("HasLockout", (bool(Lua_Expedition::*)(std::string))&Lua_Expedition::HasLockout) .def("HasReplayLockout", (bool(Lua_Expedition::*)(void))&Lua_Expedition::HasReplayLockout) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 85166a65e..7b4bbed30 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -69,6 +69,7 @@ public: int GetSecondsRemaining(); std::string GetUUID(); int GetZoneID(); + std::string GetZoneName(); int GetZoneVersion(); bool HasLockout(std::string event_name); bool HasReplayLockout(); From b965a165b119d55bcad0af68de0b0f0d8b388d0f Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 24 Sep 2020 19:23:37 -0400 Subject: [PATCH 117/196] Add api to update expedition expire time Add SetSecondsRemaining method to set expire time on expedition --- common/servertalk.h | 1 + world/expedition.cpp | 18 ++++++++++++++++++ world/expedition.h | 1 + world/zoneserver.cpp | 1 + zone/dynamiczone.cpp | 3 +++ zone/expedition.cpp | 17 ++++++++++++++++- zone/expedition.h | 4 +++- zone/lua_expedition.cpp | 7 +++++++ zone/lua_expedition.h | 1 + 9 files changed, 51 insertions(+), 2 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index b666980d0..fc1881d88 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -162,6 +162,7 @@ #define ServerOP_ExpeditionMembersRemoved 0x0412 #define ServerOP_ExpeditionDzDuration 0x0413 #define ServerOP_ExpeditionLockoutDuration 0x0414 +#define ServerOP_ExpeditionSecondsRemaining 0x0415 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 diff --git a/world/expedition.cpp b/world/expedition.cpp index 4ed06e42a..15ce39dcb 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -157,6 +157,18 @@ void ExpeditionCache::RemoveAllMembers(uint32_t expedition_id) } } +void ExpeditionCache::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + it->UpdateDzSecondsRemaining(seconds_remaining); + } +} + void ExpeditionCache::Process() { if (!m_process_throttle_timer.Check()) @@ -448,6 +460,12 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) ExpeditionMessage::RequestInvite(pack); break; } + case ServerOP_ExpeditionSecondsRemaining: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.SetSecondsRemaining(buf->expedition_id, buf->new_duration_seconds); + break; + } } } diff --git a/world/expedition.h b/world/expedition.h index 8f14a334d..f628c71a0 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -60,6 +60,7 @@ public: void LoadActiveExpeditions(); void MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove); void RemoveAllMembers(uint32_t expedition_id); + void SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining); void Process(); private: diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index fc13394af..a38b79bf2 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1385,6 +1385,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ExpeditionCharacterLockout: case ServerOP_ExpeditionSaveInvite: case ServerOP_ExpeditionRequestInvite: + case ServerOP_ExpeditionSecondsRemaining: { ExpeditionMessage::HandleZoneMessage(pack); break; diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 64a28b8d2..ea22e4b4f 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -535,6 +535,9 @@ void DynamicZone::SetUpdatedDuration(uint32_t new_duration) m_duration = std::chrono::seconds(new_duration); m_expire_time = m_start_time + m_duration; + LogDynamicZones("Updated zone [{}]:[{}] seconds remaining: [{}]", + m_zone_id, m_instance_id, GetSecondsRemaining()); + if (zone && IsCurrentZoneDzInstance()) { zone->SetInstanceTimer(GetSecondsRemaining()); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 9e3cbf855..32ac8919f 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1736,6 +1736,16 @@ void Expedition::SendWorldCharacterLockout( worldserver.SendPacket(pack.get()); } +void Expedition::SendWorldSetSecondsRemaining(uint32_t seconds_remaining) +{ + uint32_t pack_size = sizeof(ServerExpeditionUpdateDuration_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionSecondsRemaining, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->new_duration_seconds = seconds_remaining; + worldserver.SendPacket(pack.get()); +} + void Expedition::AddLockoutByCharacterID( uint32_t character_id, const std::string& expedition_name, const std::string& event_name, uint32_t seconds, const std::string& uuid) @@ -2074,7 +2084,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition) { - expedition->SetDzDuration(buf->new_duration_seconds); + expedition->UpdateDzDuration(buf->new_duration_seconds); } break; } @@ -2125,6 +2135,11 @@ void Expedition::SetDzSafeReturn(const std::string& zone_name, float x, float y, SetDzSafeReturn(zone_id, x, y, z, heading, update_db); } +void Expedition::SetDzSecondsRemaining(uint32_t seconds_remaining) +{ + SendWorldSetSecondsRemaining(seconds_remaining); // async +} + void Expedition::SetDzZoneInLocation(float x, float y, float z, float heading, bool update_db) { DynamicZoneLocation location{ 0, x, y, z, heading }; diff --git a/zone/expedition.h b/zone/expedition.h index 57b50deb0..eebe056d0 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -158,8 +158,8 @@ public: 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 SetDzSecondsRemaining(uint32_t seconds_remaining); void SetDzZoneInLocation(float x, float y, float z, float heading, bool update_db = false); - void SetDzDuration(uint32_t new_duration) { m_dynamiczone.SetUpdatedDuration(new_duration); } static const int32_t REPLAY_TIMER_ID; static const int32_t EVENT_TIMER_ID; @@ -202,9 +202,11 @@ private: 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 SendWorldSetSecondsRemaining(uint32_t seconds_remaining); void SendWorldSettingChanged(uint16_t server_opcode, bool setting_value); void TryAddClient(Client* add_client, const std::string& inviter_name, const std::string& swap_remove_name, Client* leader_client = nullptr); + void UpdateDzDuration(uint32_t new_duration) { m_dynamiczone.SetUpdatedDuration(new_duration); } void UpdateMemberStatus(uint32_t update_character_id, ExpeditionMemberStatus status); ExpeditionMember GetMemberData(uint32_t character_id); diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 18df8e98a..17e88ee1c 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -206,6 +206,12 @@ void Lua_Expedition::SetSafeReturn(std::string zone_name, float x, float y, floa self->SetDzSafeReturn(zone_name, x, y, z, heading, true); } +void Lua_Expedition::SetSecondsRemaining(uint32_t seconds_remaining) +{ + Lua_Safe_Call_Void(); + self->SetDzSecondsRemaining(seconds_remaining); +} + void Lua_Expedition::SetZoneInLocation(float x, float y, float z, float heading) { Lua_Safe_Call_Void(); self->SetDzZoneInLocation(x, y, z, heading, true); @@ -258,6 +264,7 @@ luabind::scope lua_register_expedition() { .def("SetReplayLockoutOnMemberJoin", (void(Lua_Expedition::*)(bool))&Lua_Expedition::SetReplayLockoutOnMemberJoin) .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("SetSecondsRemaining", &Lua_Expedition::SetSecondsRemaining) .def("SetZoneInLocation", (void(Lua_Expedition::*)(float, float, float, float))&Lua_Expedition::SetZoneInLocation) .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t))&Lua_Expedition::UpdateLockoutDuration) .def("UpdateLockoutDuration", (void(Lua_Expedition::*)(std::string, uint32_t, bool))&Lua_Expedition::UpdateLockoutDuration); diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 7b4bbed30..6d880ed8c 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -85,6 +85,7 @@ public: void SetReplayLockoutOnMemberJoin(bool enable); void SetSafeReturn(uint32_t 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 SetSecondsRemaining(uint32_t seconds_remaining); void SetZoneInLocation(float x, float y, float z, float heading); void UpdateLockoutDuration(std::string event_name, uint32_t duration); void UpdateLockoutDuration(std::string event_name, uint32_t duration, bool members_only); From a3a6e55d22ff655bbe9ccb5bb87edc405d02a878 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 24 Sep 2020 23:07:07 -0400 Subject: [PATCH 118/196] Add lockout timer multiplier rule This allows servers to adjust all new lockout durations added during special events like live does --- common/ruletypes.h | 1 + zone/expedition.cpp | 10 ++++++++-- zone/expedition_lockout_timer.cpp | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index d680ca21d..f70cd1d98 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -793,6 +793,7 @@ RULE_INT(Expedition, EmptyDzShutdownDelaySeconds, 1500, "Seconds to set dynamic RULE_INT(Expedition, RequestExpiredLockoutLeewaySeconds, 60, "Seconds remaining on lockout to count as expired for creation requests (client hides timers under 60s remaining)") RULE_INT(Expedition, WorldExpeditionProcessRateMS, 6000, "Timer interval (ms) that world checks expedition states") RULE_BOOL(Expedition, AlwaysNotifyNewLeaderOnChange, false, "Always notify clients when made expedition leader. If false (live-like) new leaders are only notified when made leader via /dzmakeleader") +RULE_REAL(Expedition, LockoutDurationMultiplier, 1.0, "Multiplies lockout duration by this value when new lockouts are added") RULE_CATEGORY_END() RULE_CATEGORY(DynamicZone) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 32ac8919f..96b5784ff 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -485,7 +485,10 @@ void Expedition::AddLockoutDuration(const std::string& event_name, int seconds, } } - ExpeditionDatabase::AddLockoutDuration(m_members, lockout, seconds); + // processing lockout duration applies multiplier again in client methods, + // update database with modified value now but pass original on + int modified_seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + ExpeditionDatabase::AddLockoutDuration(m_members, lockout, modified_seconds); ProcessLockoutDuration(lockout, seconds, members_only); SendWorldLockoutDuration(lockout, seconds, members_only); @@ -498,6 +501,8 @@ void Expedition::UpdateLockoutDuration( auto it = m_lockouts.find(event_name); if (it != m_lockouts.end()) { + seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + uint64_t expire_time = it->second.GetStartTime() + seconds; AddLockout({ m_uuid, m_expedition_name, event_name, expire_time, seconds }, members_only); } @@ -1339,7 +1344,8 @@ void Expedition::AddLockoutDurationClients( if (!lockout_clients.empty()) { - ExpeditionDatabase::AddLockoutDuration(lockout_clients, lockout, seconds); + int modified_seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + ExpeditionDatabase::AddLockoutDuration(lockout_clients, lockout, modified_seconds); } } diff --git a/zone/expedition_lockout_timer.cpp b/zone/expedition_lockout_timer.cpp index 208afb1ba..42a69c6bc 100644 --- a/zone/expedition_lockout_timer.cpp +++ b/zone/expedition_lockout_timer.cpp @@ -20,6 +20,7 @@ #include "expedition_lockout_timer.h" #include "../common/string_util.h" +#include "../common/rulesys.h" #include "../common/util/uuid.h" #include @@ -44,6 +45,8 @@ ExpeditionLockoutTimer::ExpeditionLockoutTimer( ExpeditionLockoutTimer ExpeditionLockoutTimer::CreateLockout( const std::string& expedition_name, const std::string& event_name, uint32_t seconds, std::string uuid) { + seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + if (uuid.empty()) { uuid = EQ::Util::UUID::Generate().ToString(); @@ -88,6 +91,8 @@ bool ExpeditionLockoutTimer::IsSameLockout( void ExpeditionLockoutTimer::AddLockoutTime(int seconds) { + seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); + auto new_duration = std::max(0, static_cast(m_duration.count()) + seconds); auto start_time = m_expire_time - m_duration; From c26c6af3569f44994b0dc972963c2bb472f4d0b1 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 26 Sep 2020 18:41:54 -0400 Subject: [PATCH 119/196] Remove rule to verify expedition leader with db This is a holdover from earlier system design and shouldn't be necessary --- common/ruletypes.h | 1 - zone/expedition.cpp | 16 +++------------- zone/expedition_database.cpp | 22 ---------------------- zone/expedition_database.h | 1 - 4 files changed, 3 insertions(+), 37 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index f70cd1d98..00264e545 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -787,7 +787,6 @@ 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, 1500, "Seconds to set dynamic zone instance expiration if early shutdown enabled") RULE_INT(Expedition, RequestExpiredLockoutLeewaySeconds, 60, "Seconds remaining on lockout to count as expired for creation requests (client hides timers under 60s remaining)") diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 96b5784ff..2eafa1dbe 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -908,25 +908,15 @@ bool Expedition::ConfirmLeaderCommand(Client* requester) return false; } - ExpeditionMember leader; - if (RuleB(Expedition, UseDatabaseToVerifyLeaderCommands)) - { - leader = ExpeditionDatabase::GetExpeditionLeader(m_id); - } - else - { - leader = m_leader; - } - - if (leader.char_id == 0) + if (m_leader.char_id == 0) { requester->MessageString(Chat::Red, UNABLE_RETRIEVE_LEADER); // unconfirmed message return false; } - if (leader.char_id != requester->CharacterID()) + if (m_leader.char_id != requester->CharacterID()) { - requester->MessageString(Chat::System, EXPEDITION_NOT_LEADER, leader.name.c_str()); + requester->MessageString(Chat::System, EXPEDITION_NOT_LEADER, m_leader.name.c_str()); return false; } diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 1f7bbdeb7..001f46730 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -430,28 +430,6 @@ uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_i 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& lockouts, bool replace_timer, bool is_pending) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 9002dc600..07c395267 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -60,7 +60,6 @@ namespace ExpeditionDatabase void DeletePendingLockouts(uint32_t character_id); void DeleteAllMembersPendingLockouts(const std::vector& members); uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); - ExpeditionMember GetExpeditionLeader(uint32_t expedition_id); std::pair, std::vector> GetMembersLockout( const std::vector& members, const std::string& expedition_name, const std::string& event_name); From 6266aa86a42c13ae61b99fed64a7bf0a88ad6738 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 15 May 2020 20:40:50 -0400 Subject: [PATCH 120/196] Add perl expedition api --- zone/CMakeLists.txt | 1 + zone/embparser.cpp | 3 + zone/embparser_api.cpp | 254 ++++++++++++++++ zone/embperl.cpp | 2 + zone/perl_client.cpp | 304 +++++++++++++++++++ zone/perl_expedition.cpp | 630 +++++++++++++++++++++++++++++++++++++++ zone/perl_groups.cpp | 26 ++ zone/perl_raids.cpp | 26 ++ 8 files changed, 1246 insertions(+) create mode 100644 zone/perl_expedition.cpp diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 4e85f25dc..0e1bb877b 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -106,6 +106,7 @@ SET(zone_sources perl_client.cpp perl_doors.cpp perl_entity.cpp + perl_expedition.cpp perl_groups.cpp perl_hateentry.cpp perl_mob.cpp diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 671a26d9c..087e44663 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -971,6 +971,9 @@ void PerlembParser::MapFunctions() "package Doors;" "&boot_Doors;" // load quest Doors XS + "package Expedition;" + "&boot_Expedition;" + #endif "package main;" "}" diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 1ae4c22c1..706e7a9a0 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -29,6 +29,7 @@ #include "embparser.h" #include "embxs.h" #include "entity.h" +#include "expedition.h" #include "queryserv.h" #include "questmgr.h" #include "zone.h" @@ -6059,6 +6060,251 @@ XS(XS__SetContentFlag) XSRETURN_EMPTY; } +XS(XS__get_expedition); +XS(XS__get_expedition) { + dXSARGS; + if (items != 0) { + Perl_croak(aTHX_ "Usage: quest::get_expedition()"); + } + + Expedition* RETVAL = nullptr; + if (zone && zone->GetInstanceID() != 0) + { + RETVAL = Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); + } + + EXTEND(sp, 1); // grow stack, function had 0 arguments + ST(0) = sv_newmortal(); // PUSHs(sv_newmortal()); + if (RETVAL) { + sv_setref_pv(ST(0), "Expedition", (void*)RETVAL); + } + + XSRETURN(1); +} + +XS(XS__get_expedition_by_char_id); +XS(XS__get_expedition_by_char_id) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: quest::get_expedition_by_char_id(uint32 character_id)"); + } + + uint32 character_id = (int)SvUV(ST(0)); + + Expedition* RETVAL = Expedition::FindCachedExpeditionByCharacterID(character_id); + + ST(0) = sv_newmortal(); + if (RETVAL) { + sv_setref_pv(ST(0), "Expedition", (void*)RETVAL); + } + + XSRETURN(1); +} + +XS(XS__get_expedition_by_instance_id); +XS(XS__get_expedition_by_instance_id) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: quest::GetExpeditionByInstanceID(uint16 instance_id)"); + } + + uint16 instance_id = (uint16)SvUV(ST(0)); + + Expedition* RETVAL = Expedition::FindCachedExpeditionByInstanceID(instance_id); + + ST(0) = sv_newmortal(); + if (RETVAL) { + sv_setref_pv(ST(0), "Expedition", (void*)RETVAL); + } + + XSRETURN(1); +} + +XS(XS__get_expedition_lockout_by_char_id); +XS(XS__get_expedition_lockout_by_char_id) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: quest::get_expedition_lockout_by_char_id" + "(uint32 character_id, string expedition_name, string event_name)"); + } + + uint32_t character_id = static_cast(SvUV(ST(0))); + std::string expedition_name = SvPV_nolen(ST(1)); + std::string event_name = SvPV_nolen(ST(2)); + + auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(character_id); + auto it = std::find_if(lockouts.begin(), lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) { + return lockout.IsSameLockout(expedition_name, event_name); + }); + + // mortalize so its refcnt is auto decremented on function exit to avoid leak + HV* hash = (HV*)sv_2mortal((SV*)newHV()); // hash refcnt +1 (mortal -1) + + if (it != lockouts.end()) + { + hv_store(hash, "remaining", 9, newSVuv(it->GetSecondsRemaining()), 0); + hv_store(hash, "uuid", 4, newSVpv(it->GetExpeditionUUID().c_str(), 0), 0); + } + + ST(0) = sv_2mortal(newRV((SV*)hash)); // hash refcnt: 2 (-1 mortal), reference: 1 (-1 mortal) + XSRETURN(1); +} + +XS(XS__get_expedition_lockouts_by_char_id); +XS(XS__get_expedition_lockouts_by_char_id) { + dXSARGS; + if (items != 1 && items != 2) { + Perl_croak(aTHX_ "Usage: quest::get_expedition_lockouts_by_char_id" + "(uint32 character_id [, string expedition_name])"); + } + + HV* hash = newHV(); // hash refcnt +1 (non-mortal, newRV_noinc to not inc) + SV* hash_ref = nullptr; // for expedition event hash if filtering on expedition + + uint32_t character_id = static_cast(SvUV(ST(0))); + std::string expedition_name; + if (items == 2) + { + expedition_name = SvPV_nolen(ST(1)); + } + + auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(character_id); + + for (const auto& lockout : lockouts) + { + uint32_t name_len = static_cast(lockout.GetExpeditionName().size()); + uint32_t event_len = static_cast(lockout.GetEventName().size()); + + // hashes are stored through references inside other hashes/arrays. we need + // to wrap newHV in newRV references when inserting nested hash values. + // we use newRV_noinc to not increment the hash's ref count; rv will own it + + SV** entry = hv_fetch(hash, lockout.GetExpeditionName().c_str(), name_len, false); + if (!entry) + { + // create expedition entry in hash with its value as ref to event hash + SV* event_hash_ref = newRV_noinc((SV*)newHV()); // ref takes ownership + if (!expedition_name.empty() && lockout.GetExpeditionName() == expedition_name) + { + hash_ref = event_hash_ref; // save ref for filtered expedition return + } + entry = hv_store(hash, lockout.GetExpeditionName().c_str(), name_len, event_hash_ref, 0); + } + + // *entry is a reference to expedition's event hash (which it owns). the + // event entry in the hash will contain ref to a lockout detail hash + if (entry && SvROK(*entry) && SvTYPE(SvRV(*entry)) == SVt_PVHV) // is ref to hash type + { + HV* details_hash = newHV(); // refcnt +1, reference will take ownership + hv_store(details_hash, "remaining", 9, newSVuv(lockout.GetSecondsRemaining()), 0); + hv_store(details_hash, "uuid", 4, newSVpv(lockout.GetExpeditionUUID().c_str(), 0), 0); + + HV* event_hash = (HV*)SvRV(*entry); + hv_store(event_hash, lockout.GetEventName().c_str(), event_len, + (SV*)newRV_noinc((SV*)details_hash), 0); + } + } + + SV* rv = &PL_sv_undef; + + if (!expedition_name.empty() && hash_ref) + { + rv = sv_2mortal(hash_ref); // ref that owns event hash + } + else + { + rv = sv_2mortal(newRV_noinc((SV*)hash)); // takes ownership of expedition hash + } + + ST(0) = rv; + XSRETURN(1); +} + +XS(XS__add_expedition_lockout_all_clients); +XS(XS__add_expedition_lockout_all_clients) { + dXSARGS; + if (items != 3 && items != 4) { + Perl_croak(aTHX_ "Usage: quest::add_expedition_lockout_all_clients" + "(string expedition_name, string event_name, uint32 seconds [, string uuid])"); + } + + std::string expedition_name = SvPV_nolen(ST(0)); + std::string event_name = SvPV_nolen(ST(1)); + uint32_t seconds = static_cast(SvUV(ST(2))); + std::string uuid; + + if (items == 4) + { + uuid = SvPV_nolen(ST(3)); + } + + auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); + Expedition::AddLockoutClients(lockout); + + XSRETURN_EMPTY; +} + +XS(XS__add_expedition_lockout_by_char_id); +XS(XS__add_expedition_lockout_by_char_id) { + dXSARGS; + if (items != 4 && items != 5) { + Perl_croak(aTHX_ "Usage: quest::add_expedition_lockout_by_char_id" + "(uint32 character_id, string expedition_name, string event_name, uint32 seconds [, string uuid])"); + } + + std::string uuid; + if (items == 5) + { + uuid = SvPV_nolen(ST(4)); + } + + uint32_t character_id = static_cast(SvUV(ST(0))); + std::string expedition_name = SvPV_nolen(ST(1)); + std::string event_name = SvPV_nolen(ST(2)); + uint32_t seconds = static_cast(SvUV(ST(3))); + + Expedition::AddLockoutByCharacterID(character_id, expedition_name, event_name, seconds, uuid); + + XSRETURN_EMPTY; +} + +XS(XS__remove_expedition_lockout_by_char_id); +XS(XS__remove_expedition_lockout_by_char_id) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: quest::remove_expedition_lockout_by_char_id" + "(uint32 character_id, string expedition_name, string event_name)"); + } + + uint32_t character_id = static_cast(SvUV(ST(0))); + std::string expedition_name = SvPV_nolen(ST(1)); + std::string event_name = SvPV_nolen(ST(2)); + + Expedition::RemoveLockoutsByCharacterID(character_id, expedition_name, event_name); + + XSRETURN_EMPTY; +} + +XS(XS__remove_all_expedition_lockouts_by_char_id); +XS(XS__remove_all_expedition_lockouts_by_char_id) { + dXSARGS; + if (items != 1 && items != 2) { + Perl_croak(aTHX_ "Usage: quest::remove_expedition_lockout_by_char_id" + "(uint32 character_id [, string expedition_name])"); + } + + std::string expedition_name; + if (items == 2) + { + expedition_name = SvPV_nolen(ST(1)); + } + + uint32_t character_id = static_cast(SvUV(ST(0))); + Expedition::RemoveLockoutsByCharacterID(character_id, expedition_name); + + XSRETURN_EMPTY; +} + /* This is the callback perl will look for to setup the quest package's XSUBs @@ -6129,6 +6375,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "activespeakactivity"), XS__activespeakactivity, file); newXS(strcpy(buf, "activespeaktask"), XS__activespeaktask, file); newXS(strcpy(buf, "activetasksinset"), XS__activetasksinset, file); + newXS(strcpy(buf, "add_expedition_lockout_by_char_id"), XS__add_expedition_lockout_by_char_id, file); newXS(strcpy(buf, "addldonloss"), XS__addldonpoints, file); newXS(strcpy(buf, "addldonpoints"), XS__addldonpoints, file); newXS(strcpy(buf, "addldonwin"), XS__addldonpoints, file); @@ -6263,6 +6510,11 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "getcharidbyname"), XS__getcharidbyname, file); newXS(strcpy(buf, "getclassname"), XS__getclassname, file); newXS(strcpy(buf, "getcurrencyid"), XS__getcurrencyid, file); + newXS(strcpy(buf, "get_expedition"), XS__get_expedition, file); + newXS(strcpy(buf, "get_expedition_by_char_id"), XS__get_expedition_by_char_id, file); + newXS(strcpy(buf, "get_expedition_by_instance_id"), XS__get_expedition_by_instance_id, file); + newXS(strcpy(buf, "get_expedition_lockout_by_char_id"), XS__get_expedition_lockout_by_char_id, file); + newXS(strcpy(buf, "get_expedition_lockouts_by_char_id"), XS__get_expedition_lockouts_by_char_id, file); newXS(strcpy(buf, "getinventoryslotid"), XS__getinventoryslotid, file); newXS(strcpy(buf, "getitemname"), XS__getitemname, file); newXS(strcpy(buf, "getItemName"), XS_qc_getItemName, file); @@ -6328,6 +6580,8 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "rain"), XS__rain, file); newXS(strcpy(buf, "rebind"), XS__rebind, file); newXS(strcpy(buf, "reloadzonestaticdata"), XS__reloadzonestaticdata, file); + newXS(strcpy(buf, "remove_all_expedition_lockouts_by_char_id"), XS__remove_all_expedition_lockouts_by_char_id, file); + newXS(strcpy(buf, "remove_expedition_lockout_by_char_id"), XS__remove_expedition_lockout_by_char_id, file); newXS(strcpy(buf, "removeitem"), XS__removeitem, file); newXS(strcpy(buf, "removetitle"), XS__removetitle, file); newXS(strcpy(buf, "repopzone"), XS__repopzone, file); diff --git a/zone/embperl.cpp b/zone/embperl.cpp index c7ebb8060..ce584c799 100644 --- a/zone/embperl.cpp +++ b/zone/embperl.cpp @@ -37,6 +37,7 @@ EXTERN_C XS(boot_HateEntry); EXTERN_C XS(boot_Object); EXTERN_C XS(boot_Doors); EXTERN_C XS(boot_PerlPacket); +EXTERN_C XS(boot_Expedition); #endif #endif @@ -87,6 +88,7 @@ EXTERN_C void xs_init(pTHX) newXS(strcpy(buf, "HateEntry::boot_HateEntry"), boot_HateEntry, file); newXS(strcpy(buf, "Object::boot_Object"), boot_Object, file); newXS(strcpy(buf, "Doors::boot_Doors"), boot_Doors, file); + newXS(strcpy(buf, "Expedition::boot_Expedition"), boot_Expedition, file); ; #endif #endif diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 82cf3b730..f7cb160be 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -37,12 +37,26 @@ #endif #include "client.h" +#include "expedition.h" #include "titles.h" #ifdef THIS /* this macro seems to leak out on some systems */ #undef THIS #endif +#define VALIDATE_THIS_IS_CLIENT \ + do { \ + if (sv_derived_from(ST(0), "Client")) { \ + IV tmp = SvIV((SV*)SvRV(ST(0))); \ + THIS = INT2PTR(Client*, tmp); \ + } else { \ + Perl_croak(aTHX_ "THIS is not of type Client"); \ + } \ + if (THIS == nullptr) { \ + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); \ + } \ + } while (0); + XS(XS_Client_SendSound); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_SendSound) { dXSARGS; @@ -6759,6 +6773,286 @@ XS(XS_Client_GetClientMaxLevel) { XSRETURN(1); } +XS(XS_Client_CreateExpedition); +XS(XS_Client_CreateExpedition) { + dXSARGS; + if (items != 7 && items != 8) { + Perl_croak(aTHX_ "Usage: Client::CreateExpedition(THIS, string zone_name, uint32 zone_version, " + "uint32 duration, string expedition_name, uint32 min_players, uint32 max_players, " + "[, bool disable_messages = false])"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + std::string zone_name(SvPV_nolen(ST(1))); + uint32 zone_version = (uint32)SvUV(ST(2)); + uint32 duration = (uint32)SvUV(ST(3)); + std::string expedition_name(SvPV_nolen(ST(4))); + uint32 min_players = (uint32)SvUV(ST(5)); + uint32 max_players = (uint32)SvUV(ST(6)); + bool disable_messages = (items > 7) ? (bool)SvTRUE(ST(7)) : false; + + Expedition* RETVAL = THIS->CreateExpedition(zone_name, zone_version, duration, + expedition_name, min_players, max_players, disable_messages); + + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Expedition", (void*)RETVAL); + + XSRETURN(1); +} + +XS(XS_Client_GetExpedition); +XS(XS_Client_GetExpedition) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Client::GetExpedition(THIS)"); + } + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + Expedition* RETVAL = THIS->GetExpedition(); + + ST(0) = sv_newmortal(); + sv_setref_pv(ST(0), "Expedition", (void*)RETVAL); + + XSRETURN(1); +} + +XS(XS_Client_GetExpeditionLockouts); +XS(XS_Client_GetExpeditionLockouts) { + dXSARGS; + if (items != 1 && items != 2) { + Perl_croak(aTHX_ "Usage: Client::GetExpeditionLockouts(THIS [, string expedition_name])"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + HV* hash = newHV(); + SV* hash_ref = nullptr; // for expedition event hash if filtering on expedition + + std::string expedition_name; + if (items == 2) + { + expedition_name = SvPV_nolen(ST(1)); + } + + auto lockouts = THIS->GetExpeditionLockouts(); + + for (const auto& lockout : lockouts) + { + uint32_t name_len = static_cast(lockout.GetExpeditionName().size()); + uint32_t event_len = static_cast(lockout.GetEventName().size()); + + SV** entry = hv_fetch(hash, lockout.GetExpeditionName().c_str(), name_len, false); + if (!entry) + { + SV* event_hash_ref = newRV_noinc((SV*)newHV()); // takes ownership of hash + if (!expedition_name.empty() && lockout.GetExpeditionName() == expedition_name) + { + hash_ref = event_hash_ref; // save ref to event hash for return + } + entry = hv_store(hash, lockout.GetExpeditionName().c_str(), name_len, event_hash_ref, 0); + } + + if (entry && SvROK(*entry) && SvTYPE(SvRV(*entry)) == SVt_PVHV) + { + HV* event_hash = (HV*)SvRV(*entry); + hv_store(event_hash, lockout.GetEventName().c_str(), event_len, + newSVuv(lockout.GetSecondsRemaining()), 0); + } + } + + SV* rv = &PL_sv_undef; + + if (!expedition_name.empty()) + { + rv = hash_ref ? sv_2mortal(hash_ref) : &PL_sv_undef; // ref that owns event hash for expedition + } + else + { + rv = sv_2mortal(newRV_noinc((SV*)hash)); // owns expedition hash + } + + ST(0) = rv; + XSRETURN(1); +} + +XS(XS_Client_GetLockoutExpeditionUUID); +XS(XS_Client_GetLockoutExpeditionUUID) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Client::GetLockoutExpeditionUUID(THIS, string expedition_name, string event_name)"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + std::string expedition_name = SvPV_nolen(ST(1)); + std::string event_name = SvPV_nolen(ST(2)); + + auto lockout = THIS->GetExpeditionLockout(expedition_name, event_name); + if (lockout) + { + XSRETURN_PV(lockout->GetExpeditionUUID().c_str()); + } + + XSRETURN_UNDEF; +} + +XS(XS_Client_AddExpeditionLockout); +XS(XS_Client_AddExpeditionLockout) { + dXSARGS; + if (items != 4 && items != 5) { + Perl_croak(aTHX_ "Usage: Client::AddExpeditionLockout(THIS, string expedition_name, string event_name, uint32 seconds [, string uuid])"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + std::string expedition_name(SvPV_nolen(ST(1))); + std::string event_name(SvPV_nolen(ST(2))); + uint32 seconds = (uint32)SvUV(ST(3)); + std::string uuid; + + if (items == 5) + { + uuid = SvPV_nolen(ST(4)); + } + + THIS->AddNewExpeditionLockout(expedition_name, event_name, seconds, uuid); + + XSRETURN_EMPTY; +} + +XS(XS_Client_AddExpeditionLockoutDuration); +XS(XS_Client_AddExpeditionLockoutDuration) { + dXSARGS; + if (items != 4 && items != 5) { + Perl_croak(aTHX_ "Usage: Client::AddExpeditionLockoutDuration(THIS, string expedition_name, string event_name, int seconds [, string uuid])"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + std::string expedition_name(SvPV_nolen(ST(1))); + std::string event_name(SvPV_nolen(ST(2))); + int seconds = static_cast(SvUV(ST(3))); + std::string uuid; + + if (items == 5) + { + uuid = SvPV_nolen(ST(4)); + } + + THIS->AddExpeditionLockoutDuration(expedition_name, event_name, seconds, uuid, true); + + XSRETURN_EMPTY; +} + +XS(XS_Client_RemoveAllExpeditionLockouts); +XS(XS_Client_RemoveAllExpeditionLockouts) { + dXSARGS; + if (items != 1 && items != 2) { + Perl_croak(aTHX_ "Usage: Client::RemoveAllExpeditionLockouts(THIS [, string expedition_name])"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + std::string expedition_name; + if (items == 2) + { + expedition_name = SvPV_nolen(ST(1)); + } + + THIS->RemoveAllExpeditionLockouts(expedition_name, true); + + XSRETURN_EMPTY; +} + +XS(XS_Client_RemoveExpeditionLockout); +XS(XS_Client_RemoveExpeditionLockout) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Client::RemoveExpeditionLockout(THIS, string expedition_name, string event_name)"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + std::string expedition_name(SvPV_nolen(ST(1))); + std::string event_name(SvPV_nolen(ST(2))); + + THIS->RemoveExpeditionLockout(expedition_name, event_name, true); + + XSRETURN_EMPTY; +} + +XS(XS_Client_HasExpeditionLockout); +XS(XS_Client_HasExpeditionLockout) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Client::HasExpeditionLockout(THIS, string expedition_name, string event_name)"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + std::string expedition_name(SvPV_nolen(ST(1))); + std::string event_name(SvPV_nolen(ST(2))); + + bool result = THIS->HasExpeditionLockout(expedition_name, event_name); + ST(0) = boolSV(result); + + XSRETURN(1); +} + +XS(XS_Client_MovePCDynamicZone); +XS(XS_Client_MovePCDynamicZone) { + dXSARGS; + if (items != 2 && items != 3 && items != 4) { + Perl_croak(aTHX_ "Usage: Client::MovePCDynamicZone(THIS, uint32 zone_id [, int zone_version = -1, bool message_if_invalid = true])\n" + "Usage: Client::MovePCDynamicZone(THIS, string zone_name [, int zone_version = -1, bool message_if_invalid = true])"); + } + + Client* THIS = nullptr; + VALIDATE_THIS_IS_CLIENT; + + if (SvTYPE(ST(1)) == SVt_PV) + { + std::string zone_name(SvPV_nolen(ST(1))); + int zone_version = (items >= 3) ? static_cast(SvIV(ST(2))) : -1; + if (items == 4) + { + THIS->MovePCDynamicZone(zone_name, zone_version, (bool)SvTRUE(ST(3))); + } + else + { + THIS->MovePCDynamicZone(zone_name, zone_version); + } + } + else if (SvTYPE(ST(1)) == SVt_IV) + { + uint32 zone_id = (uint32)SvUV(ST(1)); + int zone_version = (items >= 3) ? static_cast(SvIV(ST(2))) : -1; + if (items == 3) + { + THIS->MovePCDynamicZone(zone_id, zone_version, (bool)SvTRUE(ST(2))); + } + else + { + THIS->MovePCDynamicZone(zone_id, zone_version); + } + } + else + { + Perl_croak(aTHX_ "Client::MovePCDynamicZone expected an integer or string"); + } + + XSRETURN_EMPTY; +} #ifdef __cplusplus extern "C" @@ -6786,6 +7080,8 @@ XS(boot_Client) { newXSproto(strcpy(buf, "AddAlternateCurrencyValue"), XS_Client_AddAlternateCurrencyValue, file, "$$$"); newXSproto(strcpy(buf, "AddCrystals"), XS_Client_AddCrystals, file, "$$"); newXSproto(strcpy(buf, "AddEXP"), XS_Client_AddEXP, file, "$$;$$"); + newXSproto(strcpy(buf, "AddExpeditionLockout"), XS_Client_AddExpeditionLockout, file, "$$$$;$"); + newXSproto(strcpy(buf, "AddExpeditionLockoutDuration"), XS_Client_AddExpeditionLockoutDuration, file, "$$$$;$"); newXSproto(strcpy(buf, "AddLevelBasedExp"), XS_Client_AddLevelBasedExp, file, "$$;$$"); newXSproto(strcpy(buf, "AddMoneyToPP"), XS_Client_AddMoneyToPP, file, "$$$$$$"); newXSproto(strcpy(buf, "AddPVPPoints"), XS_Client_AddPVPPoints, file, "$$"); @@ -6804,6 +7100,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "CheckSpecializeIncrease"), XS_Client_CheckSpecializeIncrease, file, "$$"); newXSproto(strcpy(buf, "ClearCompassMark"), XS_Client_ClearCompassMark, file, "$"); newXSproto(strcpy(buf, "ClearZoneFlag"), XS_Client_ClearZoneFlag, file, "$$"); + newXSproto(strcpy(buf, "CreateExpedition"), XS_Client_CreateExpedition, file, "$$$$$$$;$"); newXSproto(strcpy(buf, "Connected"), XS_Client_Connected, file, "$"); newXSproto(strcpy(buf, "DecreaseByID"), XS_Client_DecreaseByID, file, "$$$"); newXSproto(strcpy(buf, "DeleteItemInInventory"), XS_Client_DeleteItemInInventory, file, "$$;$$"); @@ -6858,6 +7155,8 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetEndurance"), XS_Client_GetEndurance, file, "$"); newXSproto(strcpy(buf, "GetEnduranceRatio"), XS_Client_GetEnduranceRatio, file, "$"); newXSproto(strcpy(buf, "GetEXP"), XS_Client_GetEXP, file, "$"); + newXSproto(strcpy(buf, "GetExpedition"), XS_Client_GetExpedition, file, "$"); + newXSproto(strcpy(buf, "GetExpeditionLockouts"), XS_Client_GetExpeditionLockouts, file, "$;$"); newXSproto(strcpy(buf, "GetFace"), XS_Client_GetFace, file, "$"); newXSproto(strcpy(buf, "GetFactionLevel"), XS_Client_GetFactionLevel, file, "$$$$$$$$"); newXSproto(strcpy(buf, "GetFeigned"), XS_Client_GetFeigned, file, "$"); @@ -6880,6 +7179,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetLDoNPointsTheme"), XS_Client_GetLDoNPointsTheme, file, "$"); newXSproto(strcpy(buf, "GetLDoNWins"), XS_Client_GetLDoNWins, file, "$"); newXSproto(strcpy(buf, "GetLDoNWinsTheme"), XS_Client_GetLDoNWinsTheme, file, "$$"); + newXSproto(strcpy(buf, "GetLockoutExpeditionUUID"), XS_Client_GetLockoutExpeditionUUID, file, "$$$"); newXSproto(strcpy(buf, "GetMaxEndurance"), XS_Client_GetMaxEndurance, file, "$"); newXSproto(strcpy(buf, "GetModCharacterFactionLevel"), XS_Client_GetModCharacterFactionLevel, file, "$$"); newXSproto(strcpy(buf, "GetMoney"), XS_Client_GetMoney, file, "$$$"); @@ -6907,6 +7207,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GrantAlternateAdvancementAbility"), XS_Client_GrantAlternateAdvancementAbility, file, "$$$;$"); newXSproto(strcpy(buf, "GuildID"), XS_Client_GuildID, file, "$"); newXSproto(strcpy(buf, "GuildRank"), XS_Client_GuildRank, file, "$"); + newXSproto(strcpy(buf, "HasExpeditionLockout"), XS_Client_HasExpeditionLockout, file, "$$$"); newXSproto(strcpy(buf, "HasSkill"), XS_Client_HasSkill, file, "$$"); newXSproto(strcpy(buf, "HasSpellScribed"), XS_Client_HasSkill, file, "$$"); newXSproto(strcpy(buf, "HasZoneFlag"), XS_Client_HasZoneFlag, file, "$$"); @@ -6938,6 +7239,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "MaxSkill"), XS_Client_MaxSkill, file, "$$;$$"); newXSproto(strcpy(buf, "MemSpell"), XS_Client_MemSpell, file, "$$$;$"); newXSproto(strcpy(buf, "MovePC"), XS_Client_MovePC, file, "$$$$$$"); + newXSproto(strcpy(buf, "MovePCDynamicZone"), XS_Client_MovePCDynamicZone, file, "$$;$$"); newXSproto(strcpy(buf, "MovePCInstance"), XS_Client_MovePCInstance, file, "$$$$$$$"); newXSproto(strcpy(buf, "MoveZone"), XS_Client_MoveZone, file, "$$"); newXSproto(strcpy(buf, "MoveZoneGroup"), XS_Client_MoveZoneGroup, file, "$$"); @@ -6954,6 +7256,8 @@ XS(boot_Client) { newXSproto(strcpy(buf, "QuestReward"), XS_Client_QuestReward, file, "$$;$$$$$$$"); newXSproto(strcpy(buf, "ReadBook"), XS_Client_ReadBook, file, "$$$"); newXSproto(strcpy(buf, "RefundAA"), XS_Client_RefundAA, file, "$$"); + newXSproto(strcpy(buf, "RemoveAllExpeditionLockouts"), XS_Client_RemoveAllExpeditionLockouts, file, "$;$"); + newXSproto(strcpy(buf, "RemoveExpeditionLockout"), XS_Client_RemoveExpeditionLockout, file, "$$$"); newXSproto(strcpy(buf, "RemoveNoRent"), XS_Client_RemoveNoRent, file, "$"); newXSproto(strcpy(buf, "ResetAA"), XS_Client_ResetAA, file, "$"); newXSproto(strcpy(buf, "ResetDisciplineTimer"), XS_Client_ResetDisciplineTimer, file, "$$"); diff --git a/zone/perl_expedition.cpp b/zone/perl_expedition.cpp new file mode 100644 index 000000000..468dce248 --- /dev/null +++ b/zone/perl_expedition.cpp @@ -0,0 +1,630 @@ +/** + * 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 "../common/features.h" + +#ifdef EMBPERL_XS_CLASSES + +#include "expedition.h" +#include "zone_store.h" +#include "embperl.h" +#include "../common/global_define.h" + +#ifdef seed +#undef seed +#endif + +#ifdef THIS /* this macro seems to leak out on some systems */ +#undef THIS +#endif + +#define VALIDATE_THIS_IS_EXPEDITION \ + do { \ + if (sv_derived_from(ST(0), "Expedition")) { \ + IV tmp = SvIV((SV*)SvRV(ST(0))); \ + THIS = INT2PTR(Expedition*, tmp); \ + } else { \ + Perl_croak(aTHX_ "THIS is not of type Expedition"); \ + } \ + if (THIS == nullptr) { \ + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); \ + } \ + } while (0); + +XS(XS_Expedition_AddLockout); +XS(XS_Expedition_AddLockout) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Expedition::AddLockout(THIS, string event_name, uint32 seconds)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + std::string event_name(SvPV_nolen(ST(1))); + uint32_t seconds = static_cast(SvUV(ST(2))); + + THIS->AddLockout(event_name, seconds); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_AddLockoutDuration); +XS(XS_Expedition_AddLockoutDuration) { + dXSARGS; + if (items != 3 && items != 4) { + Perl_croak(aTHX_ "Usage: Expedition::AddLockout(THIS, string event_name, int seconds [, bool members_only = true])"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + std::string event_name(SvPV_nolen(ST(1))); + int seconds = static_cast(SvUV(ST(2))); + if (items == 4) + { + bool members_only = (bool)SvTRUE(ST(3)); + THIS->AddLockoutDuration(event_name, seconds, members_only); + } + else + { + THIS->AddLockoutDuration(event_name, seconds); + } + + XSRETURN_EMPTY; +} +XS(XS_Expedition_AddReplayLockout); +XS(XS_Expedition_AddReplayLockout) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: Expedition::AddReplayLockout(THIS, uint32 seconds)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + uint32_t seconds = static_cast(SvUV(ST(1))); + + THIS->AddReplayLockout(seconds); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_GetID); +XS(XS_Expedition_GetID) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetID(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_UV(THIS->GetID()); +} + +XS(XS_Expedition_GetInstanceID); +XS(XS_Expedition_GetInstanceID) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetInstanceID(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_UV(THIS->GetInstanceID()); +} + +XS(XS_Expedition_GetLeaderName); +XS(XS_Expedition_GetLeaderName) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetLeaderName(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_PV(THIS->GetLeaderName().c_str()); +} + +XS(XS_Expedition_GetLockouts); +XS(XS_Expedition_GetLockouts) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetLockouts(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + HV* hash = newHV(); + + auto lockouts = THIS->GetLockouts(); + for (const auto& lockout : lockouts) + { + hv_store(hash, lockout.first.c_str(), static_cast(lockout.first.size()), + newSVuv(lockout.second.GetSecondsRemaining()), 0); + } + + ST(0) = sv_2mortal(newRV_noinc((SV*)hash)); // take ownership of hash (refcnt remains 1) + XSRETURN(1); +} + +XS(XS_Expedition_GetLootEventByNPCTypeID); +XS(XS_Expedition_GetLootEventByNPCTypeID) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: Expedition::GetLootEventByNPCTypeID(THIS, uint32 npc_type_id)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + uint32_t npc_type_id = static_cast(SvUV(ST(1))); + + XSRETURN_PV(THIS->GetLootEventByNPCTypeID(npc_type_id).c_str()); +} + +XS(XS_Expedition_GetLootEventBySpawnID); +XS(XS_Expedition_GetLootEventBySpawnID) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: Expedition::GetLootEventBySpawnID(THIS, uint32 spawn_id)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + uint32_t spawn_id = static_cast(SvUV(ST(1))); + + XSRETURN_PV(THIS->GetLootEventBySpawnID(spawn_id).c_str()); +} + +XS(XS_Expedition_GetMemberCount); +XS(XS_Expedition_GetMemberCount) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetMemberCount(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_UV(THIS->GetMemberCount()); +} + +XS(XS_Expedition_GetMembers); +XS(XS_Expedition_GetMembers) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetMembers(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + HV* hash = newHV(); + + auto members = THIS->GetMembers(); + for (const auto& member : members) + { + hv_store(hash, member.name.c_str(), static_cast(member.name.size()), + newSVuv(member.char_id), 0); + } + + ST(0) = sv_2mortal(newRV_noinc((SV*)hash)); + XSRETURN(1); +} + +XS(XS_Expedition_GetName); +XS(XS_Expedition_GetName) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetName(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_PV(THIS->GetName().c_str()); +} + +XS(XS_Expedition_GetSecondsRemaining); +XS(XS_Expedition_GetSecondsRemaining) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetSecondsRemaining(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_UV(THIS->GetDynamicZone().GetSecondsRemaining()); +} + +XS(XS_Expedition_GetUUID); +XS(XS_Expedition_GetUUID) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetUUID(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_PV(THIS->GetUUID().c_str()); +} + +XS(XS_Expedition_GetZoneID); +XS(XS_Expedition_GetZoneID) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetZoneID(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_UV(THIS->GetDynamicZone().GetZoneID()); +} + +XS(XS_Expedition_GetZoneName); +XS(XS_Expedition_GetZoneName) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetZoneName(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_PV(ZoneName(THIS->GetDynamicZone().GetZoneID())); +} + +XS(XS_Expedition_GetZoneVersion); +XS(XS_Expedition_GetZoneVersion) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetZoneVersion(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_UV(THIS->GetDynamicZone().GetZoneVersion()); +} + +XS(XS_Expedition_HasLockout); +XS(XS_Expedition_HasLockout) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: Expedition::HasLockout(THIS, string event_name)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + std::string event_name(SvPV_nolen(ST(1))); + + bool result = THIS->HasLockout(event_name); + ST(0) = boolSV(result); + XSRETURN(1); +} + +XS(XS_Expedition_HasReplayLockout); +XS(XS_Expedition_HasReplayLockout) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::HasReplayLockout(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + bool result = THIS->HasReplayLockout(); + ST(0) = boolSV(result); + XSRETURN(1); +} + +XS(XS_Expedition_RemoveCompass); +XS(XS_Expedition_RemoveCompass) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::RemoveCompass(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + THIS->SetDzCompass(0, 0, 0, 0, true); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_RemoveLockout); +XS(XS_Expedition_RemoveLockout) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: Expedition::RemoveLockout(THIS, string event_name)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + std::string event_name(SvPV_nolen(ST(1))); + + THIS->RemoveLockout(event_name); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_SetCompass); +XS(XS_Expedition_SetCompass) { + dXSARGS; + if (items != 5) { + Perl_croak(aTHX_ "Usage: Expedition::SetCompass(THIS, uint32 zone_id|string zone_name, float x, float y, float z)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + float x = static_cast(SvNV(ST(2))); + float y = static_cast(SvNV(ST(3))); + float z = static_cast(SvNV(ST(4))); + + if (SvTYPE(ST(1)) == SVt_PV) + { + std::string zone_name(SvPV_nolen(ST(1))); + THIS->SetDzCompass(zone_name, x, y, z, true); + } + else if (SvTYPE(ST(1)) == SVt_IV) + { + uint32_t zone_id = static_cast(SvUV(ST(1))); + THIS->SetDzCompass(zone_id, x, y, z, true); + } + else + { + Perl_croak(aTHX_ "Expedition::SetCompass expected an integer or string"); + } + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_SetLocked); +XS(XS_Expedition_SetLocked) { + dXSARGS; + if (items != 2 && items != 3 && items != 4) { + Perl_croak(aTHX_ "Usage: Expedition::SetLocked(THIS, bool locked [, int lock_msg = 0, uint32 color = 15])"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + bool locked = (bool)SvTRUE(ST(1)); + int lock_msg = (items == 3) ? static_cast(SvIV(ST(2))) : 0; + if (items == 4) + { + THIS->SetLocked(locked, static_cast(lock_msg), true, (uint32)SvUV(ST(3))); + } + else + { + THIS->SetLocked(locked, static_cast(lock_msg), true); + } + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_SetLootEventByNPCTypeID); +XS(XS_Expedition_SetLootEventByNPCTypeID) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Expedition::SetLootEventByNPCTypeID(THIS, uint32 npc_type_id, string event_name)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + uint32_t npc_type_id = static_cast(SvUV(ST(1))); + std::string event_name(SvPV_nolen(ST(2))); + + THIS->SetLootEventByNPCTypeID(npc_type_id, event_name); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_SetLootEventBySpawnID); +XS(XS_Expedition_SetLootEventBySpawnID) { + dXSARGS; + if (items != 3) { + Perl_croak(aTHX_ "Usage: Expedition::SetLootEventBySpawnID(THIS, uint32 spawn_id, string event_name)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + uint32_t spawn_id = static_cast(SvUV(ST(1))); + std::string event_name(SvPV_nolen(ST(2))); + + THIS->SetLootEventBySpawnID(spawn_id, event_name); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_SetReplayLockoutOnMemberJoin); +XS(XS_Expedition_SetReplayLockoutOnMemberJoin) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: Expedition::SetReplayLockoutOnMemberJoin(THIS, bool enable)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + bool enable = (bool)SvTRUE(ST(1)); + + THIS->SetReplayLockoutOnMemberJoin(enable, true); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_SetSafeReturn); +XS(XS_Expedition_SetSafeReturn) { + dXSARGS; + if (items != 6) { + Perl_croak(aTHX_ "Usage: Expedition::SetSafeReturn(THIS, uint32 zone_id|string zone_name, float x, float y, float z, float heading)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + float x = static_cast(SvNV(ST(2))); + float y = static_cast(SvNV(ST(3))); + float z = static_cast(SvNV(ST(4))); + float heading = static_cast(SvNV(ST(5))); + + if (SvTYPE(ST(1)) == SVt_PV) + { + std::string zone_name(SvPV_nolen(ST(1))); + THIS->SetDzSafeReturn(zone_name, x, y, z, heading, true); + } + else if (SvTYPE(ST(1)) == SVt_IV) + { + uint32_t zone_id = static_cast(SvUV(ST(1))); + THIS->SetDzSafeReturn(zone_id, x, y, z, heading, true); + } + else + { + Perl_croak(aTHX_ "Expedition::SetSafeReturn expected an integer or string"); + } + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_SetSecondsRemaining); +XS(XS_Expedition_SetSecondsRemaining) { + dXSARGS; + if (items != 2) { + Perl_croak(aTHX_ "Usage: Expedition::SetSecondsRemaining(THIS, uint32 seconds_remaining)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + uint32_t seconds_remaining = static_cast(SvUV(ST(1))); + THIS->SetDzSecondsRemaining(seconds_remaining); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_SetZoneInLocation); +XS(XS_Expedition_SetZoneInLocation) { + dXSARGS; + if (items != 5) { + Perl_croak(aTHX_ "Usage: Expedition::SetZoneInLocation(THIS, float x, float y, float z, float heading)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + float x = static_cast(SvNV(ST(1))); + float y = static_cast(SvNV(ST(2))); + float z = static_cast(SvNV(ST(3))); + float heading = static_cast(SvNV(ST(4))); + + THIS->SetDzZoneInLocation(x, y, z, heading, true); + + XSRETURN_EMPTY; +} + +XS(XS_Expedition_UpdateLockoutDuration); +XS(XS_Expedition_UpdateLockoutDuration) { + dXSARGS; + if (items != 3 && items != 4) { + Perl_croak(aTHX_ "Usage: Expedition::UpdateLockoutDuration(THIS, string event_name, uint32 seconds [, bool members_only])"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + std::string event_name(SvPV_nolen(ST(1))); + uint32_t seconds = static_cast(SvUV(ST(2))); + + if (items == 4) + { + bool members_only = (bool)SvTRUE(ST(3)); + THIS->UpdateLockoutDuration(event_name, seconds, members_only); + } + else + { + THIS->UpdateLockoutDuration(event_name, seconds); + } + + XSRETURN_EMPTY; +} + +XS(boot_Expedition); +XS(boot_Expedition) { + dXSARGS; + char file[256]; + strncpy(file, __FILE__, 256); + file[255] = 0; + + if (items != 1) { + fprintf(stderr, "boot_Expedition does not take any arguments."); + } + char buf[128]; + + XS_VERSION_BOOTCHECK; + newXSproto(strcpy(buf, "AddLockout"), XS_Expedition_AddLockout, file, "$$$"); + newXSproto(strcpy(buf, "AddLockoutDuration"), XS_Expedition_AddLockoutDuration, file, "$$$;$"); + newXSproto(strcpy(buf, "AddReplayLockout"), XS_Expedition_AddReplayLockout, file, "$$"); + newXSproto(strcpy(buf, "GetID"), XS_Expedition_GetID, file, "$"); + newXSproto(strcpy(buf, "GetInstanceID"), XS_Expedition_GetInstanceID, file, "$"); + newXSproto(strcpy(buf, "GetLeaderName"), XS_Expedition_GetLeaderName, file, "$"); + newXSproto(strcpy(buf, "GetLockouts"), XS_Expedition_GetLockouts, file, "$"); + newXSproto(strcpy(buf, "GetLootEventByNPCTypeID"), XS_Expedition_GetLootEventByNPCTypeID, file, "$$"); + newXSproto(strcpy(buf, "GetLootEventBySpawnID"), XS_Expedition_GetLootEventBySpawnID, file, "$$"); + newXSproto(strcpy(buf, "GetMemberCount"), XS_Expedition_GetMemberCount, file, "$"); + newXSproto(strcpy(buf, "GetMembers"), XS_Expedition_GetMembers, file, "$"); + newXSproto(strcpy(buf, "GetName"), XS_Expedition_GetName, file, "$"); + newXSproto(strcpy(buf, "GetSecondsRemaining"), XS_Expedition_GetSecondsRemaining, file, "$"); + newXSproto(strcpy(buf, "GetUUID"), XS_Expedition_GetUUID, file, "$"); + newXSproto(strcpy(buf, "GetZoneID"), XS_Expedition_GetZoneID, file, "$"); + newXSproto(strcpy(buf, "GetZoneName"), XS_Expedition_GetZoneName, file, "$"); + newXSproto(strcpy(buf, "GetZoneVersion"), XS_Expedition_GetZoneVersion, file, "$"); + newXSproto(strcpy(buf, "HasLockout"), XS_Expedition_HasLockout, file, "$$"); + newXSproto(strcpy(buf, "HasReplayLockout"), XS_Expedition_HasReplayLockout, file, "$"); + newXSproto(strcpy(buf, "RemoveCompass"), XS_Expedition_RemoveCompass, file, "$"); + newXSproto(strcpy(buf, "RemoveLockout"), XS_Expedition_RemoveLockout, file, "$$"); + newXSproto(strcpy(buf, "SetCompass"), XS_Expedition_SetCompass, file, "$$$$$"); + newXSproto(strcpy(buf, "SetLocked"), XS_Expedition_SetLocked, file, "$$;$$"); + newXSproto(strcpy(buf, "SetLootEventByNPCTypeID"), XS_Expedition_SetLootEventByNPCTypeID, file, "$$$"); + newXSproto(strcpy(buf, "SetLootEventBySpawnID"), XS_Expedition_SetLootEventBySpawnID, file, "$$$"); + newXSproto(strcpy(buf, "SetReplayLockoutOnMemberJoin"), XS_Expedition_SetReplayLockoutOnMemberJoin, file, "$$"); + newXSproto(strcpy(buf, "SetSafeReturn"), XS_Expedition_SetSafeReturn, file, "$$$$$$"); + newXSproto(strcpy(buf, "SetSecondsRemaining"), XS_Expedition_SetSecondsRemaining, file, "$$"); + newXSproto(strcpy(buf, "SetZoneInLocation"), XS_Expedition_SetZoneInLocation, file, "$$$$$"); + newXSproto(strcpy(buf, "UpdateLockoutDuration"), XS_Expedition_UpdateLockoutDuration, file, "$$$;$"); + XSRETURN_YES; +} + +#endif //EMBPERL_XS_CLASSES diff --git a/zone/perl_groups.cpp b/zone/perl_groups.cpp index c23246b4a..adac6ddae 100644 --- a/zone/perl_groups.cpp +++ b/zone/perl_groups.cpp @@ -576,6 +576,31 @@ XS(XS_Group_GetMember) { XSRETURN(1); } +XS(XS_Group_DoesAnyMemberHaveExpeditionLockout); +XS(XS_Group_DoesAnyMemberHaveExpeditionLockout) { + dXSARGS; + if (items != 3 && items != 4) { + Perl_croak(aTHX_ "Usage: Group::DoesAnyMemberHaveExpeditionLockout(THIS, string expedition_name, string event_name [, int max_check_count = 0])"); + } + + Group* THIS = nullptr; + if (sv_derived_from(ST(0), "Group")) { + IV tmp = SvIV((SV *) SvRV(ST(0))); + THIS = INT2PTR(Group *, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Group"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + std::string expedition_name(SvPV_nolen(ST(1))); + std::string event_name(SvPV_nolen(ST(2))); + int max_check_count = (items == 4) ? static_cast(SvIV(ST(3))) : 0; + + bool result = THIS->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name, max_check_count); + ST(0) = boolSV(result); + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -612,6 +637,7 @@ XS(boot_Group) { newXSproto(strcpy(buf, "TeleportGroup"), XS_Group_TeleportGroup, file, "$$$$$$$"); newXSproto(strcpy(buf, "GetID"), XS_Group_GetID, file, "$"); newXSproto(strcpy(buf, "GetMember"), XS_Group_GetMember, file, "$$"); + newXSproto(strcpy(buf, "DoesAnyMemberHaveExpeditionLockout"), XS_Group_DoesAnyMemberHaveExpeditionLockout, file, "$$$;$"); XSRETURN_YES; } diff --git a/zone/perl_raids.cpp b/zone/perl_raids.cpp index 670d445e5..2c2d8ff3c 100644 --- a/zone/perl_raids.cpp +++ b/zone/perl_raids.cpp @@ -545,6 +545,31 @@ XS(XS_Raid_GetMember) { XSRETURN(1); } +XS(XS_Raid_DoesAnyMemberHaveExpeditionLockout); +XS(XS_Raid_DoesAnyMemberHaveExpeditionLockout) { + dXSARGS; + if (items != 3 && items != 4) { + Perl_croak(aTHX_ "Usage: Raid::DoesAnyMemberHaveExpeditionLockout(THIS, string expedition_name, string event_name [, int max_check_count = 0])"); + } + + Raid* THIS = nullptr; + if (sv_derived_from(ST(0), "Raid")) { + IV tmp = SvIV((SV *) SvRV(ST(0))); + THIS = INT2PTR(Raid *, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Raid"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + std::string expedition_name(SvPV_nolen(ST(1))); + std::string event_name(SvPV_nolen(ST(2))); + int max_check_count = (items == 4) ? static_cast(SvIV(ST(3))) : 0; + + bool result = THIS->DoesAnyMemberHaveExpeditionLockout(expedition_name, event_name, max_check_count); + ST(0) = boolSV(result); + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -581,6 +606,7 @@ XS(boot_Raid) { newXSproto(strcpy(buf, "TeleportRaid"), XS_Raid_TeleportRaid, file, "$$$$$$$"); newXSproto(strcpy(buf, "GetID"), XS_Raid_GetID, file, "$"); newXSproto(strcpy(buf, "GetMember"), XS_Raid_GetMember, file, "$$"); + newXSproto(strcpy(buf, "DoesAnyMemberHaveExpeditionLockout"), XS_Raid_DoesAnyMemberHaveExpeditionLockout, file, "$$$;$"); XSRETURN_YES; } From 18dd9e6637a95ed34ff1d36c1116313b3edbdc9a Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 27 Sep 2020 17:11:36 -0400 Subject: [PATCH 121/196] Add #dz expedition unlock command Add expedition name and zone to #dz expedition list Adjust #dz list output --- zone/command.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 73eaa26e7..e806474ae 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6844,10 +6844,14 @@ void command_dz(Client* c, const Seperator* sep) auto seconds = expedition.second->GetDynamicZone().GetSecondsRemaining(); c->Message(Chat::White, fmt::format( - "Expedition id: [{}]: leader: [{}] instance id: [{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + "Expedition id: [{}] name: [{}] leader: [{}] zone: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", expedition.second->GetID(), + expedition.second->GetName(), expedition.second->GetLeaderName(), + ZoneName(expedition.second->GetDynamicZone().GetZoneID()), + expedition.second->GetDynamicZone().GetZoneID(), expedition.second->GetInstanceID(), + expedition.second->GetDynamicZone().GetZoneVersion(), expedition.second->GetMemberCount(), seconds / 3600, // hours (seconds / 60) % 60, // minutes @@ -6868,9 +6872,8 @@ void command_dz(Client* c, const Seperator* sep) auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); if (expedition) { - c->Message(Chat::White, fmt::format( - "Destroying expedition [{}] ({})", expedition_id, expedition->GetName()).c_str() - ); + c->Message(Chat::White, fmt::format("Destroying expedition [{}] ({})", + expedition_id, expedition->GetName()).c_str()); expedition->RemoveAllMembers(); } else @@ -6878,6 +6881,20 @@ void command_dz(Client* c, const Seperator* sep) c->Message(Chat::Red, fmt::format("Failed to destroy expedition [{}]", sep->arg[3]).c_str()); } } + else if (strcasecmp(sep->arg[2], "unlock") == 0 && sep->IsNumber(3)) + { + auto expedition_id = std::strtoul(sep->arg[3], nullptr, 10); + auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); + if (expedition) + { + c->Message(Chat::White, fmt::format("Unlocking expedition [{}]", expedition_id).c_str()); + expedition->SetLocked(false, ExpeditionLockMessage::None, true); + } + else + { + c->Message(Chat::Red, fmt::format("Failed to find expedition [{}]", sep->arg[3]).c_str()); + } + } } else if (strcasecmp(sep->arg[1], "list") == 0) { @@ -6914,10 +6931,10 @@ void command_dz(Client* c, const Seperator* sep) if (!is_expired || strcasecmp(sep->arg[2], "all") == 0) { c->Message(Chat::White, fmt::format( - "type: [{}] instance: [{}] zone: [{}] version: [{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + "type: [{}] zone: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", strtoul(row[0], nullptr, 10), - strtoul(row[1], nullptr, 10), strtoul(row[2], nullptr, 10), + strtoul(row[1], nullptr, 10), strtoul(row[3], nullptr, 10), strtoul(row[6], nullptr, 10), seconds / 3600, // hours @@ -6980,6 +6997,7 @@ void command_dz(Client* c, const Seperator* sep) c->Message(Chat::White, "#dz expedition list - list expeditions in current zone cache"); c->Message(Chat::White, "#dz expedition reload - reload expedition zone cache from database"); c->Message(Chat::White, "#dz expedition destroy - destroy expedition globally (must be in cache)"); + c->Message(Chat::White, "#dz expedition unlock - unlock expedition"); c->Message(Chat::White, "#dz list [all] - list dynamic zone instances from database -- 'all' includes expired"); c->Message(Chat::White, "#dz lockouts remove - delete all of character's expedition lockouts"); c->Message(Chat::White, "#dz lockouts remove \"\" - delete lockouts by expedition"); From cd98b8bc6f29268cabed1e1922e7396e69c106d4 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 4 Oct 2020 18:45:16 -0400 Subject: [PATCH 122/196] Only send member updates after caching dz data This fixes characters not receiving a compass if they're already in the compass zone when an expedition is created from another zone --- zone/expedition.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 2eafa1dbe..4ff049de3 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -163,7 +163,6 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) using col = LoadExpeditionColumns::eLoadExpeditionColumns; - Expedition* current_expedition = nullptr; uint32_t last_expedition_id = 0; for (auto row = results.begin(); row != results.end(); ++row) @@ -172,12 +171,6 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) if (expedition_id != last_expedition_id) { - // finished parsing previous expedition members, send member updates - if (current_expedition) - { - current_expedition->SendUpdatesToZoneMembers(); - } - expedition_ids.emplace_back(expedition_id); uint32_t leader_id = strtoul(row[col::leader_id], nullptr, 10); @@ -209,7 +202,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) last_expedition_id = expedition_id; // looping expedition members - current_expedition = Expedition::FindCachedExpeditionByID(last_expedition_id); + auto current_expedition = Expedition::FindCachedExpeditionByID(last_expedition_id); if (current_expedition) { auto member_id = strtoul(row[col::member_id], nullptr, 10); @@ -219,12 +212,6 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) } } - // update for the last cached expedition - if (current_expedition) - { - current_expedition->SendUpdatesToZoneMembers(); - } - // ask world for online members from all cached expeditions at once Expedition::SendWorldGetOnlineMembers(expedition_character_ids); @@ -248,6 +235,9 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) { expedition->m_lockouts = lockout_iter->second; } + + // send member updates now that all data is loaded for the cached expedition(s) + expedition->SendUpdatesToZoneMembers(); } } } From 79287fc5072c675b4a94e46b25fda9bb6ba82478 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 4 Oct 2020 19:20:54 -0400 Subject: [PATCH 123/196] Require zone id to get expedition by instance id This is a breaking api change eq.get_expedition_by_instance_id(instance_id) is replaced with eq.get_expedition_by_zone_instance(zone_id, instance_id) This replaces the FindCachedExpeditionByInstanceID method of obtaining expeditions via instance id with a new method that requires the dz zone id as well --- zone/corpse.cpp | 2 +- zone/embparser_api.cpp | 17 +++++++++-------- zone/expedition.cpp | 7 ++++--- zone/expedition.h | 2 +- zone/lua_general.cpp | 8 ++++---- zone/zone.cpp | 4 ++-- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 9bdedc492..e04fd6a9d 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -1279,7 +1279,7 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) if (zone && zone->GetInstanceID() != 0) { // expeditions may prevent looting based on client's lockouts - auto expedition = Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); + auto expedition = Expedition::FindCachedExpeditionByZoneInstance(zone->GetZoneID(), zone->GetInstanceID()); if (expedition && !expedition->CanClientLootCorpse(client, GetNPCTypeID(), GetID())) { client->MessageString(Chat::Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 706e7a9a0..e402e1325 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -6070,7 +6070,7 @@ XS(XS__get_expedition) { Expedition* RETVAL = nullptr; if (zone && zone->GetInstanceID() != 0) { - RETVAL = Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); + RETVAL = Expedition::FindCachedExpeditionByZoneInstance(zone->GetZoneID(), zone->GetInstanceID()); } EXTEND(sp, 1); // grow stack, function had 0 arguments @@ -6101,16 +6101,17 @@ XS(XS__get_expedition_by_char_id) { XSRETURN(1); } -XS(XS__get_expedition_by_instance_id); -XS(XS__get_expedition_by_instance_id) { +XS(XS__get_expedition_by_zone_instance); +XS(XS__get_expedition_by_zone_instance) { dXSARGS; - if (items != 1) { - Perl_croak(aTHX_ "Usage: quest::GetExpeditionByInstanceID(uint16 instance_id)"); + if (items != 2) { + Perl_croak(aTHX_ "Usage: quest::GetExpeditionByZoneInstance(uint16 zone_id, uint16 instance_id)"); } - uint16 instance_id = (uint16)SvUV(ST(0)); + uint16 zone_id = (uint16)SvUV(ST(0)); + uint16 instance_id = (uint16)SvUV(ST(1)); - Expedition* RETVAL = Expedition::FindCachedExpeditionByInstanceID(instance_id); + Expedition* RETVAL = Expedition::FindCachedExpeditionByZoneInstance(zone_id, instance_id); ST(0) = sv_newmortal(); if (RETVAL) { @@ -6512,7 +6513,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "getcurrencyid"), XS__getcurrencyid, file); newXS(strcpy(buf, "get_expedition"), XS__get_expedition, file); newXS(strcpy(buf, "get_expedition_by_char_id"), XS__get_expedition_by_char_id, file); - newXS(strcpy(buf, "get_expedition_by_instance_id"), XS__get_expedition_by_instance_id, file); + newXS(strcpy(buf, "get_expedition_by_zone_instance"), XS__get_expedition_by_zone_instance, file); newXS(strcpy(buf, "get_expedition_lockout_by_char_id"), XS__get_expedition_lockout_by_char_id, file); newXS(strcpy(buf, "get_expedition_lockouts_by_char_id"), XS__get_expedition_lockouts_by_char_id, file); newXS(strcpy(buf, "getinventoryslotid"), XS__getinventoryslotid, file); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 4ff049de3..20b2ebaf1 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -353,13 +353,14 @@ Expedition* Expedition::FindCachedExpeditionByID(uint32_t expedition_id) return nullptr; } -Expedition* Expedition::FindCachedExpeditionByInstanceID(uint32_t instance_id) +Expedition* Expedition::FindCachedExpeditionByZoneInstance(uint32_t zone_id, uint32_t instance_id) { - if (instance_id && zone) + if (zone && zone_id != 0 && instance_id != 0) { for (const auto& cached_expedition : zone->expedition_cache) { - if (cached_expedition.second->GetInstanceID() == instance_id) + if (cached_expedition.second->GetDynamicZone().GetZoneID() == zone_id && + cached_expedition.second->GetDynamicZone().GetInstanceID() == instance_id) { return cached_expedition.second.get(); } diff --git a/zone/expedition.h b/zone/expedition.h index eebe056d0..b5c33570f 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -85,7 +85,7 @@ public: static Expedition* FindCachedExpeditionByCharacterID(uint32_t character_id); static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name); static Expedition* FindCachedExpeditionByID(uint32_t expedition_id); - static Expedition* FindCachedExpeditionByInstanceID(uint32_t instance_id); + static Expedition* FindCachedExpeditionByZoneInstance(uint32_t zone_id, uint32_t instance_id); static std::vector GetExpeditionLockoutsByCharacterID(uint32_t character_id); static void HandleWorldMessage(ServerPacket* pack); static void AddLockoutByCharacterID(uint32_t character_id, const std::string& expedition_name, diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index b5cc52684..f12d3a3d6 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -2183,7 +2183,7 @@ void lua_set_content_flag(std::string flag_name, bool enabled){ Lua_Expedition lua_get_expedition() { if (zone && zone->GetInstanceID() != 0) { - return Expedition::FindCachedExpeditionByInstanceID(zone->GetInstanceID()); + return Expedition::FindCachedExpeditionByZoneInstance(zone->GetZoneID(), zone->GetInstanceID()); } return nullptr; } @@ -2192,8 +2192,8 @@ Lua_Expedition lua_get_expedition_by_char_id(uint32 char_id) { return Expedition::FindCachedExpeditionByCharacterID(char_id); } -Lua_Expedition lua_get_expedition_by_instance_id(uint32 instance_id) { - return Expedition::FindCachedExpeditionByInstanceID(instance_id); +Lua_Expedition lua_get_expedition_by_zone_instance(uint32 zone_id, uint32 instance_id) { + return Expedition::FindCachedExpeditionByZoneInstance(zone_id, instance_id); } luabind::object lua_get_expedition_lockout_by_char_id(lua_State* L, uint32 char_id, std::string expedition_name, std::string event_name) { @@ -2889,7 +2889,7 @@ luabind::scope lua_register_general() { luabind::def("get_expedition", &lua_get_expedition), luabind::def("get_expedition_by_char_id", &lua_get_expedition_by_char_id), - luabind::def("get_expedition_by_instance_id", &lua_get_expedition_by_instance_id), + luabind::def("get_expedition_by_zone_instance", &lua_get_expedition_by_zone_instance), luabind::def("get_expedition_lockout_by_char_id", &lua_get_expedition_lockout_by_char_id), luabind::def("get_expedition_lockouts_by_char_id", (luabind::object(*)(lua_State*, uint32))&lua_get_expedition_lockouts_by_char_id), luabind::def("get_expedition_lockouts_by_char_id", (luabind::object(*)(lua_State*, uint32, std::string))&lua_get_expedition_lockouts_by_char_id), diff --git a/zone/zone.cpp b/zone/zone.cpp index 34ad7f79b..3d46a0009 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1492,7 +1492,7 @@ bool Zone::Process() { if(Instance_Timer->Check()) { // if this is a dynamic zone instance notify system associated with it - Expedition* expedition = Expedition::FindCachedExpeditionByInstanceID(GetInstanceID()); + auto expedition = Expedition::FindCachedExpeditionByZoneInstance(GetZoneID(), GetInstanceID()); if (expedition) { expedition->RemoveAllMembers(false); // entity list will teleport clients out immediately @@ -2722,7 +2722,7 @@ DynamicZone Zone::GetDynamicZone() return {}; // invalid } - auto expedition = Expedition::FindCachedExpeditionByInstanceID(GetInstanceID()); + auto expedition = Expedition::FindCachedExpeditionByZoneInstance(GetZoneID(), GetInstanceID()); if (expedition) { return expedition->GetDynamicZone(); From da5d4b9830f8d0f22837f7eb08ad9539b9a525f1 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 4 Oct 2020 20:17:50 -0400 Subject: [PATCH 124/196] Send all members expedition expire warnings All expedition members are notified not just those in dz This will only work if the dz is running. It might make more sense to move this to client or world processing so members are notified even if the zone instance isn't running --- common/servertalk.h | 6 ++++++ world/zoneserver.cpp | 1 + zone/expedition.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++++ zone/expedition.h | 3 +++ zone/worldserver.cpp | 1 + zone/zone.cpp | 22 +++++++++++++++++----- 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index fc1881d88..cb541bd6c 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -163,6 +163,7 @@ #define ServerOP_ExpeditionDzDuration 0x0413 #define ServerOP_ExpeditionLockoutDuration 0x0414 #define ServerOP_ExpeditionSecondsRemaining 0x0415 +#define ServerOP_ExpeditionExpireWarning 0x0416 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 @@ -2088,6 +2089,11 @@ struct ServerExpeditionUpdateDuration_Struct { uint32_t new_duration_seconds; }; +struct ServerExpeditionExpireWarning_Struct { + uint32_t expedition_id; + uint32_t minutes_remaining; +}; + struct ServerDzCommand_Struct { uint32 expedition_id; uint8 is_char_online; // 0: target name is offline, 1: online diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index a38b79bf2..e1beff363 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1371,6 +1371,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_ExpeditionDzCompass: case ServerOP_ExpeditionDzSafeReturn: case ServerOP_ExpeditionDzZoneIn: + case ServerOP_ExpeditionExpireWarning: { zoneserver_list.SendPacket(pack); break; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 20b2ebaf1..f2fcd808a 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1436,6 +1436,15 @@ void Expedition::SendWorldPendingInvite(const ExpeditionInvite& invite, const st SendWorldAddPlayerInvite(invite.inviter_name, invite.swap_remove_name, add_name, true); } +std::unique_ptr Expedition::CreateExpireWarningPacket(uint32_t minutes_remaining) +{ + uint32_t outsize = sizeof(ExpeditionExpireWarning); + auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzExpeditionEndsWarning, outsize)); + auto buf = reinterpret_cast(outapp->pBuffer); + buf->minutes_remaining = minutes_remaining; + return outapp; +} + std::unique_ptr Expedition::CreateInfoPacket(bool clear) { uint32_t outsize = sizeof(ExpeditionInfo_Struct); @@ -1733,6 +1742,16 @@ void Expedition::SendWorldSetSecondsRemaining(uint32_t seconds_remaining) worldserver.SendPacket(pack.get()); } +void Expedition::SendWorldExpireWarning(uint32_t minutes_remaining) +{ + uint32_t pack_size = sizeof(ServerExpeditionExpireWarning_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionExpireWarning, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->minutes_remaining = minutes_remaining; + worldserver.SendPacket(pack.get()); +} + void Expedition::AddLockoutByCharacterID( uint32_t character_id, const std::string& expedition_name, const std::string& event_name, uint32_t seconds, const std::string& uuid) @@ -2075,6 +2094,16 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_ExpeditionExpireWarning: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition) + { + expedition->SendMembersExpireWarning(buf->minutes_remaining); + } + break; + } } } @@ -2238,3 +2267,19 @@ std::vector Expedition::GetExpeditionLockoutsByCharacter return lockouts; } + +void Expedition::SendMembersExpireWarning(uint32_t minutes_remaining) +{ + // expeditions warn members in all zones not just the dz + auto outapp = CreateExpireWarningPacket(minutes_remaining); + for (const auto& member : m_members) + { + Client* member_client = entity_list.GetClientByCharID(member.char_id); + if (member_client) + { + member_client->QueuePacket(outapp.get()); + member_client->MessageString(Chat::Yellow, EXPEDITION_MIN_REMAIN, + fmt::format_int(minutes_remaining).c_str()); + } + } +} diff --git a/zone/expedition.h b/zone/expedition.h index b5c33570f..d8efb3052 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -142,6 +142,7 @@ public: void SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name); void SendClientExpeditionInfo(Client* client); + void SendWorldExpireWarning(uint32_t minutes); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); void DzAddPlayer(Client* requester, const std::string& add_char_name, const std::string& swap_remove_name = {}); @@ -187,6 +188,7 @@ private: 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& args = {}); + void SendMembersExpireWarning(uint32_t minutes); void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); void SendWorldExpeditionUpdate(uint16_t server_opcode); @@ -211,6 +213,7 @@ private: ExpeditionMember GetMemberData(uint32_t character_id); ExpeditionMember GetMemberData(const std::string& character_name); + std::unique_ptr CreateExpireWarningPacket(uint32_t minutes_remaining); std::unique_ptr CreateInfoPacket(bool clear = false); std::unique_ptr CreateInvitePacket(const std::string& inviter_name, const std::string& swap_remove_name); std::unique_ptr CreateMemberListPacket(bool clear = false); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index c144aadf6..920a3d958 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2909,6 +2909,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionDzZoneIn: case ServerOP_ExpeditionDzDuration: case ServerOP_ExpeditionCharacterLockout: + case ServerOP_ExpeditionExpireWarning: { Expedition::HandleWorldMessage(pack); break; diff --git a/zone/zone.cpp b/zone/zone.cpp index 3d46a0009..a4bd6cabe 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1508,19 +1508,31 @@ bool Zone::Process() { if(Instance_Warning_timer == nullptr) { uint32 rem_time = Instance_Timer->GetRemainingTime(); + uint32_t minutes_warning = 0; if(rem_time < 60000 && rem_time > 55000) { - entity_list.ExpeditionWarning(1); - Instance_Warning_timer = new Timer(10000); + minutes_warning = 1; } else if(rem_time < 300000 && rem_time > 295000) { - entity_list.ExpeditionWarning(5); - Instance_Warning_timer = new Timer(10000); + minutes_warning = 5; } else if(rem_time < 900000 && rem_time > 895000) { - entity_list.ExpeditionWarning(15); + minutes_warning = 15; + } + + if (minutes_warning > 0) + { + auto expedition = Expedition::FindCachedExpeditionByZoneInstance(GetZoneID(), GetInstanceID()); + if (expedition) + { + expedition->SendWorldExpireWarning(minutes_warning); + } + else + { + entity_list.ExpeditionWarning(minutes_warning); + } Instance_Warning_timer = new Timer(10000); } } From ca113cdd859895f7c48abfcc1b802d01a14957cb Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 6 Oct 2020 22:23:31 -0400 Subject: [PATCH 125/196] Let world dispatch expedition expire warnings This depends on C++14 remaining enabled for chrono literals --- world/expedition.cpp | 37 +++++++++++++++++++++++++++++++++++++ world/expedition.h | 4 ++++ zone/expedition.cpp | 10 ---------- zone/expedition.h | 1 - zone/zone.cpp | 9 +++------ 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/world/expedition.cpp b/world/expedition.cpp index 15ce39dcb..f06f61a7c 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -43,6 +43,7 @@ Expedition::Expedition( m_duration(duration) { m_expire_time = m_start_time + m_duration; + m_warning_cooldown_timer.Enable(); } void Expedition::SendZonesExpeditionDeleted() @@ -64,6 +65,16 @@ void Expedition::SendZonesDurationUpdate() zoneserver_list.SendPacket(pack.get()); } +void Expedition::SendZonesExpireWarning(uint32_t minutes_remaining) +{ + uint32_t pack_size = sizeof(ServerExpeditionExpireWarning_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionExpireWarning, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->minutes_remaining = minutes_remaining; + zoneserver_list.SendPacket(pack.get()); +} + void Expedition::UpdateDzSecondsRemaining(uint32_t seconds_remaining) { auto now = std::chrono::system_clock::now(); @@ -88,6 +99,28 @@ void Expedition::UpdateDzSecondsRemaining(uint32_t seconds_remaining) } } +std::chrono::system_clock::duration Expedition::GetRemainingDuration() const +{ + return m_expire_time - std::chrono::system_clock::now(); +} + +void Expedition::CheckExpireWarning() +{ + if (m_warning_cooldown_timer.Check(false)) + { + using namespace std::chrono_literals; + auto remaining = GetRemainingDuration(); + if ((remaining > 14min && remaining < 15min) || + (remaining > 4min && remaining < 5min) || + (remaining > 0min && remaining < 1min)) + { + int minutes = std::chrono::duration_cast(remaining).count() + 1; + SendZonesExpireWarning(minutes); + m_warning_cooldown_timer.Start(70000); // 1 minute 10 seconds + } + } +} + void ExpeditionCache::LoadActiveExpeditions() { BenchTimer benchmark; @@ -205,6 +238,10 @@ void ExpeditionCache::Process() it->SetPendingDelete(true); } + else + { + it->CheckExpireWarning(); + } it = is_deleted ? m_expeditions.erase(it) : it + 1; } diff --git a/world/expedition.h b/world/expedition.h index f628c71a0..02a460f6e 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -79,6 +79,7 @@ public: void AddMember(uint32_t character_id) { m_member_ids.emplace(character_id); } void RemoveMember(uint32_t character_id) { m_member_ids.erase(character_id); } void RemoveAllMembers() { m_member_ids.clear(); } + void CheckExpireWarning(); uint32_t GetID() const { return m_expedition_id; } uint16_t GetInstanceID() const { return static_cast(m_dz_instance_id); } uint16_t GetZoneID() const { return static_cast(m_dz_zone_id); } @@ -87,14 +88,17 @@ public: bool IsPendingDelete() const { return m_pending_delete; } void SendZonesDurationUpdate(); void SendZonesExpeditionDeleted(); + void SendZonesExpireWarning(uint32_t minutes_remaining); void SetPendingDelete(bool pending) { m_pending_delete = pending; } void UpdateDzSecondsRemaining(uint32_t seconds_remaining); + std::chrono::system_clock::duration GetRemainingDuration() const; private: uint32_t m_expedition_id = 0; uint32_t m_dz_instance_id = 0; uint32_t m_dz_zone_id = 0; bool m_pending_delete = false; + Timer m_warning_cooldown_timer; std::unordered_set m_member_ids; std::chrono::seconds m_duration; std::chrono::time_point m_start_time; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index f2fcd808a..d1713bc40 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1742,16 +1742,6 @@ void Expedition::SendWorldSetSecondsRemaining(uint32_t seconds_remaining) worldserver.SendPacket(pack.get()); } -void Expedition::SendWorldExpireWarning(uint32_t minutes_remaining) -{ - uint32_t pack_size = sizeof(ServerExpeditionExpireWarning_Struct); - auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionExpireWarning, pack_size)); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->minutes_remaining = minutes_remaining; - worldserver.SendPacket(pack.get()); -} - void Expedition::AddLockoutByCharacterID( uint32_t character_id, const std::string& expedition_name, const std::string& event_name, uint32_t seconds, const std::string& uuid) diff --git a/zone/expedition.h b/zone/expedition.h index d8efb3052..1c8ee5ec5 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -142,7 +142,6 @@ public: void SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name); void SendClientExpeditionInfo(Client* client); - void SendWorldExpireWarning(uint32_t minutes); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); void DzAddPlayer(Client* requester, const std::string& add_char_name, const std::string& swap_remove_name = {}); diff --git a/zone/zone.cpp b/zone/zone.cpp index a4bd6cabe..23e25c280 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1524,16 +1524,13 @@ bool Zone::Process() { if (minutes_warning > 0) { + // expedition expire warnings are handled by world auto expedition = Expedition::FindCachedExpeditionByZoneInstance(GetZoneID(), GetInstanceID()); - if (expedition) - { - expedition->SendWorldExpireWarning(minutes_warning); - } - else + if (!expedition) { entity_list.ExpeditionWarning(minutes_warning); + Instance_Warning_timer = new Timer(10000); } - Instance_Warning_timer = new Timer(10000); } } else if(Instance_Warning_timer->Check()) From 3a1eb51890946e97d9f00fc76b3429d024b280be Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 8 Oct 2020 19:03:43 -0400 Subject: [PATCH 126/196] Send lockout times with client rounding offset Add 60s to lockout times sent to clients. Lockout timers are rounded down to the nearest minute when displayed This replaces the lockout leeway rule with better behavior --- common/ruletypes.h | 1 - zone/client.cpp | 6 +++++- zone/expedition_request.cpp | 11 ++--------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 00264e545..7acf0b1b4 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -789,7 +789,6 @@ RULE_CATEGORY(Expedition) RULE_INT(Expedition, MinStatusToBypassPlayerCountRequirements, 80, "Minimum GM status to bypass minimum player requirements for Expedition creation") RULE_BOOL(Expedition, EmptyDzShutdownEnabled, true, "Enable early instance shutdown after last member of expedition removed") RULE_INT(Expedition, EmptyDzShutdownDelaySeconds, 1500, "Seconds to set dynamic zone instance expiration if early shutdown enabled") -RULE_INT(Expedition, RequestExpiredLockoutLeewaySeconds, 60, "Seconds remaining on lockout to count as expired for creation requests (client hides timers under 60s remaining)") RULE_INT(Expedition, WorldExpeditionProcessRateMS, 6000, "Timer interval (ms) that world checks expedition states") RULE_BOOL(Expedition, AlwaysNotifyNewLeaderOnChange, false, "Always notify clients when made expedition leader. If false (live-like) new leaders are only notified when made leader via /dzmakeleader") RULE_REAL(Expedition, LockoutDurationMultiplier, 1.0, "Multiplies lockout duration by this value when new lockouts are added") diff --git a/zone/client.cpp b/zone/client.cpp index f05941ee6..361a10ccc 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9766,6 +9766,10 @@ void Client::SendExpeditionLockoutTimers() { std::vector lockout_entries; + // client displays lockouts rounded down to nearest minute, send lockouts + // with 60s offset added to compensate (live does this too) + constexpr uint32_t rounding_seconds = 60; + // erases expired lockouts while building lockout timer list for (auto it = m_expedition_lockouts.begin(); it != m_expedition_lockouts.end();) { @@ -9778,7 +9782,7 @@ void Client::SendExpeditionLockoutTimers() { ExpeditionLockoutTimerEntry_Struct lockout; strn0cpy(lockout.expedition_name, it->GetExpeditionName().c_str(), sizeof(lockout.expedition_name)); - lockout.seconds_remaining = seconds_remaining; + lockout.seconds_remaining = seconds_remaining + rounding_seconds; 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)); diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index 1b8f5170b..e157498f2 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -191,12 +191,9 @@ bool ExpeditionRequest::LoadLeaderLockouts() // leader's lockouts are used to check member conflicts and later stored in expedition auto lockouts = ExpeditionDatabase::LoadCharacterLockouts(m_leader_id, m_expedition_name); - auto leeway_seconds = static_cast(RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)); - for (auto& lockout : lockouts) { - bool is_expired = lockout.IsExpired() || lockout.GetSecondsRemaining() <= leeway_seconds; - if (!is_expired) + if (!lockout.IsExpired()) { m_lockouts.emplace(lockout.GetEventName(), lockout); @@ -226,8 +223,6 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& std::vector member_lockout_conflicts; - auto leeway_seconds = static_cast(RuleI(Expedition, RequestExpiredLockoutLeewaySeconds)); - uint32_t last_character_id = 0; for (auto row = results.begin(); row != results.end(); ++row) { @@ -270,9 +265,7 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& ExpeditionLockoutTimer lockout{row[3], m_expedition_name, row[6], expire_time, duration}; - // client window hides timers with less than 60s remaining, optionally count as expired - bool is_expired = lockout.IsExpired() || lockout.GetSecondsRemaining() <= leeway_seconds; - if (!is_expired) + if (!lockout.IsExpired()) { if (lockout.IsReplayTimer()) { From 3ed7215a92f566ebd01a195e2c253683be27f408 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 10 Oct 2020 18:49:03 -0400 Subject: [PATCH 127/196] Rename character expedition lockouts table --- common/database_schema.h | 4 ++-- utils/sql/git/required/wip_expeditions.sql | 2 +- world/expedition.cpp | 2 +- zone/expedition_database.cpp | 26 +++++++++++----------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/common/database_schema.h b/common/database_schema.h index ad0b227cb..2abb10b8c 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -50,6 +50,7 @@ namespace DatabaseSchema { {"character_data", "id"}, {"character_disciplines", "id"}, {"character_enabledtasks", "charid"}, + {"character_expedition_lockouts", "character_id"}, {"character_inspect_messages", "id"}, {"character_item_recast", "id"}, {"character_languages", "id"}, @@ -66,7 +67,6 @@ namespace DatabaseSchema { {"character_tribute", "id"}, {"completed_tasks", "charid"}, {"data_buckets", "id"}, - {"expedition_character_lockouts", "character_id"}, {"faction_values", "char_id"}, {"friends", "charid"}, {"guild_members", "char_id"}, @@ -115,6 +115,7 @@ namespace DatabaseSchema { "character_data", "character_disciplines", "character_enabledtasks", + "character_expedition_lockouts", "character_inspect_messages", "character_item_recast", "character_languages", @@ -132,7 +133,6 @@ namespace DatabaseSchema { "completed_tasks", "data_buckets", "discovered_items", - "expedition_character_lockouts", "faction_values", "friends", "guild_bank", diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index fe92bb2b4..f3e2598c9 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -40,7 +40,7 @@ COLLATE='utf8mb4_general_ci' ENGINE=InnoDB ; -CREATE TABLE `expedition_character_lockouts` ( +CREATE TABLE `character_expedition_lockouts` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `character_id` INT(10) UNSIGNED NOT NULL, `expedition_name` VARCHAR(128) NOT NULL, diff --git a/world/expedition.cpp b/world/expedition.cpp index f06f61a7c..b88417e85 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -287,7 +287,7 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() void ExpeditionDatabase::PurgeExpiredCharacterLockouts() { std::string query = SQL( - DELETE FROM expedition_character_lockouts + DELETE FROM character_expedition_lockouts WHERE expire_time <= NOW(); ); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 001f46730..ca25357bc 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -109,7 +109,7 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts(ui event_name, UNIX_TIMESTAMP(expire_time), duration - FROM expedition_character_lockouts + FROM character_expedition_lockouts WHERE character_id = {} AND is_pending = FALSE AND expire_time > NOW(); ), character_id); @@ -144,7 +144,7 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts( event_name, UNIX_TIMESTAMP(expire_time), duration - FROM expedition_character_lockouts + FROM character_expedition_lockouts WHERE character_id = {} AND is_pending = FALSE @@ -254,7 +254,7 @@ MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( lockout.duration, lockout.event_name FROM character_data - LEFT JOIN expedition_character_lockouts lockout + LEFT JOIN character_expedition_lockouts lockout ON character_data.id = lockout.character_id AND lockout.is_pending = FALSE AND lockout.expire_time > NOW() @@ -277,7 +277,7 @@ void ExpeditionDatabase::DeleteAllCharacterLockouts(uint32_t character_id) if (character_id != 0) { std::string query = fmt::format(SQL( - DELETE FROM expedition_character_lockouts + DELETE FROM character_expedition_lockouts WHERE character_id = {}; ), character_id); @@ -293,7 +293,7 @@ void ExpeditionDatabase::DeleteAllCharacterLockouts( if (character_id != 0 && !expedition_name.empty()) { std::string query = fmt::format(SQL( - DELETE FROM expedition_character_lockouts + DELETE FROM character_expedition_lockouts WHERE character_id = {} AND expedition_name = '{}'; ), character_id, EscapeString(expedition_name)); @@ -309,7 +309,7 @@ void ExpeditionDatabase::DeleteCharacterLockout( ); auto query = fmt::format(SQL( - DELETE FROM expedition_character_lockouts + DELETE FROM character_expedition_lockouts WHERE character_id = {} AND is_pending = FALSE @@ -337,7 +337,7 @@ void ExpeditionDatabase::DeleteMembersLockout( query_character_ids.pop_back(); // trailing comma auto query = fmt::format(SQL( - DELETE FROM expedition_character_lockouts + DELETE FROM character_expedition_lockouts WHERE character_id IN ({}) AND is_pending = FALSE @@ -354,7 +354,7 @@ void ExpeditionDatabase::AssignPendingLockouts(uint32_t character_id, const std: LogExpeditionsDetail("Assigning character [{}] pending lockouts [{}]", character_id, expedition_name); auto query = fmt::format(SQL( - UPDATE expedition_character_lockouts + UPDATE character_expedition_lockouts SET is_pending = FALSE WHERE character_id = {} @@ -370,7 +370,7 @@ void ExpeditionDatabase::DeletePendingLockouts(uint32_t character_id) LogExpeditionsDetail("Deleting character [{}] pending lockouts", character_id); auto query = fmt::format(SQL( - DELETE FROM expedition_character_lockouts + DELETE FROM character_expedition_lockouts WHERE character_id = {} AND is_pending = TRUE; ), character_id); @@ -392,7 +392,7 @@ void ExpeditionDatabase::DeleteAllMembersPendingLockouts(const std::vector& insert_values.pop_back(); // trailing comma auto query = fmt::format(SQL( - INSERT INTO expedition_character_lockouts + INSERT INTO character_expedition_lockouts (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) VALUES {} ON DUPLICATE KEY UPDATE From 77406d7322feb202803d194077b4b43b350213b6 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 11 Oct 2020 15:36:05 -0400 Subject: [PATCH 128/196] Add AddReplayLockoutDuration api --- zone/expedition.cpp | 5 +++++ zone/expedition.h | 1 + zone/lua_expedition.cpp | 12 ++++++++++++ zone/lua_expedition.h | 2 ++ zone/perl_expedition.cpp | 26 ++++++++++++++++++++++++++ 5 files changed, 46 insertions(+) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index d1713bc40..619006680 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -485,6 +485,11 @@ void Expedition::AddLockoutDuration(const std::string& event_name, int seconds, SendWorldLockoutDuration(lockout, seconds, members_only); } +void Expedition::AddReplayLockoutDuration(int seconds, bool members_only) +{ + AddLockoutDuration(DZ_REPLAY_TIMER_NAME, seconds, members_only); +} + void Expedition::UpdateLockoutDuration( const std::string& event_name, uint32_t seconds, bool members_only) { diff --git a/zone/expedition.h b/zone/expedition.h index 1c8ee5ec5..52595da40 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -129,6 +129,7 @@ public: void AddLockout(const std::string& event_name, uint32_t seconds); void AddLockoutDuration(const std::string& event_name, int seconds, bool members_only = true); void AddReplayLockout(uint32_t seconds); + void AddReplayLockoutDuration(int seconds, bool members_only = true); bool HasLockout(const std::string& event_name); bool HasReplayLockout(); void RemoveLockout(const std::string& event_name); diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index 17e88ee1c..d5c1d4490 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -47,6 +47,16 @@ void Lua_Expedition::AddReplayLockout(uint32_t seconds) { self->AddReplayLockout(seconds); } +void Lua_Expedition::AddReplayLockoutDuration(int seconds) { + Lua_Safe_Call_Void(); + self->AddReplayLockoutDuration(seconds); +} + +void Lua_Expedition::AddReplayLockoutDuration(int seconds, bool members_only) { + Lua_Safe_Call_Void(); + self->AddReplayLockoutDuration(seconds, members_only); +} + uint32_t Lua_Expedition::GetID() { Lua_Safe_Call_Int(); return self->GetID(); @@ -236,6 +246,8 @@ luabind::scope lua_register_expedition() { .def("AddLockoutDuration", (void(Lua_Expedition::*)(std::string, int))&Lua_Expedition::AddLockoutDuration) .def("AddLockoutDuration", (void(Lua_Expedition::*)(std::string, int, bool))&Lua_Expedition::AddLockoutDuration) .def("AddReplayLockout", (void(Lua_Expedition::*)(uint32_t))&Lua_Expedition::AddReplayLockout) + .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int))&Lua_Expedition::AddReplayLockoutDuration) + .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int, bool))&Lua_Expedition::AddReplayLockoutDuration) .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) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index 6d880ed8c..d904d1680 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -57,6 +57,8 @@ public: void AddLockoutDuration(std::string event_name, int seconds); void AddLockoutDuration(std::string event_name, int seconds, bool members_only); void AddReplayLockout(uint32_t seconds); + void AddReplayLockoutDuration(int seconds); + void AddReplayLockoutDuration(int seconds, bool members_only); uint32_t GetID(); int GetInstanceID(); std::string GetLeaderName(); diff --git a/zone/perl_expedition.cpp b/zone/perl_expedition.cpp index 468dce248..5a90ee5e3 100644 --- a/zone/perl_expedition.cpp +++ b/zone/perl_expedition.cpp @@ -90,6 +90,7 @@ XS(XS_Expedition_AddLockoutDuration) { XSRETURN_EMPTY; } + XS(XS_Expedition_AddReplayLockout); XS(XS_Expedition_AddReplayLockout) { dXSARGS; @@ -107,6 +108,30 @@ XS(XS_Expedition_AddReplayLockout) { XSRETURN_EMPTY; } +XS(XS_Expedition_AddReplayLockoutDuration); +XS(XS_Expedition_AddReplayLockoutDuration) { + dXSARGS; + if (items != 2 && items != 3) { + Perl_croak(aTHX_ "Usage: Expedition::AddReplayLockoutDuration(THIS, int seconds [, bool members_only = true])"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + int seconds = static_cast(SvUV(ST(1))); + if (items == 3) + { + bool members_only = (bool)SvTRUE(ST(2)); + THIS->AddReplayLockoutDuration(seconds, members_only); + } + else + { + THIS->AddReplayLockoutDuration(seconds); + } + + XSRETURN_EMPTY; +} + XS(XS_Expedition_GetID); XS(XS_Expedition_GetID) { dXSARGS; @@ -597,6 +622,7 @@ XS(boot_Expedition) { newXSproto(strcpy(buf, "AddLockout"), XS_Expedition_AddLockout, file, "$$$"); newXSproto(strcpy(buf, "AddLockoutDuration"), XS_Expedition_AddLockoutDuration, file, "$$$;$"); newXSproto(strcpy(buf, "AddReplayLockout"), XS_Expedition_AddReplayLockout, file, "$$"); + newXSproto(strcpy(buf, "AddReplayLockoutDuration"), XS_Expedition_AddReplayLockoutDuration, file, "$$;$"); newXSproto(strcpy(buf, "GetID"), XS_Expedition_GetID, file, "$"); newXSproto(strcpy(buf, "GetInstanceID"), XS_Expedition_GetInstanceID, file, "$"); newXSproto(strcpy(buf, "GetLeaderName"), XS_Expedition_GetLeaderName, file, "$"); From ad51de052c51b1274a1f569acb50b122823d855b Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 11 Oct 2020 16:21:59 -0400 Subject: [PATCH 129/196] Cleanup expedition perl api croak messages --- zone/embparser_api.cpp | 18 ++++++------------ zone/perl_client.cpp | 15 ++++++--------- zone/perl_expedition.cpp | 12 ++++++------ zone/perl_groups.cpp | 2 +- zone/perl_raids.cpp | 2 +- 5 files changed, 20 insertions(+), 29 deletions(-) diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index e402e1325..71affe083 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -6125,8 +6125,7 @@ XS(XS__get_expedition_lockout_by_char_id); XS(XS__get_expedition_lockout_by_char_id) { dXSARGS; if (items != 3) { - Perl_croak(aTHX_ "Usage: quest::get_expedition_lockout_by_char_id" - "(uint32 character_id, string expedition_name, string event_name)"); + Perl_croak(aTHX_ "Usage: quest::get_expedition_lockout_by_char_id(uint32 character_id, string expedition_name, string event_name)"); } uint32_t character_id = static_cast(SvUV(ST(0))); @@ -6155,8 +6154,7 @@ XS(XS__get_expedition_lockouts_by_char_id); XS(XS__get_expedition_lockouts_by_char_id) { dXSARGS; if (items != 1 && items != 2) { - Perl_croak(aTHX_ "Usage: quest::get_expedition_lockouts_by_char_id" - "(uint32 character_id [, string expedition_name])"); + Perl_croak(aTHX_ "Usage: quest::get_expedition_lockouts_by_char_id(uint32 character_id, [string expedition_name])"); } HV* hash = newHV(); // hash refcnt +1 (non-mortal, newRV_noinc to not inc) @@ -6225,8 +6223,7 @@ XS(XS__add_expedition_lockout_all_clients); XS(XS__add_expedition_lockout_all_clients) { dXSARGS; if (items != 3 && items != 4) { - Perl_croak(aTHX_ "Usage: quest::add_expedition_lockout_all_clients" - "(string expedition_name, string event_name, uint32 seconds [, string uuid])"); + Perl_croak(aTHX_ "Usage: quest::add_expedition_lockout_all_clients(string expedition_name, string event_name, uint32 seconds, [string uuid])"); } std::string expedition_name = SvPV_nolen(ST(0)); @@ -6249,8 +6246,7 @@ XS(XS__add_expedition_lockout_by_char_id); XS(XS__add_expedition_lockout_by_char_id) { dXSARGS; if (items != 4 && items != 5) { - Perl_croak(aTHX_ "Usage: quest::add_expedition_lockout_by_char_id" - "(uint32 character_id, string expedition_name, string event_name, uint32 seconds [, string uuid])"); + Perl_croak(aTHX_ "Usage: quest::add_expedition_lockout_by_char_id(uint32 character_id, string expedition_name, string event_name, uint32 seconds, [string uuid])"); } std::string uuid; @@ -6273,8 +6269,7 @@ XS(XS__remove_expedition_lockout_by_char_id); XS(XS__remove_expedition_lockout_by_char_id) { dXSARGS; if (items != 3) { - Perl_croak(aTHX_ "Usage: quest::remove_expedition_lockout_by_char_id" - "(uint32 character_id, string expedition_name, string event_name)"); + Perl_croak(aTHX_ "Usage: quest::remove_expedition_lockout_by_char_id(uint32 character_id, string expedition_name, string event_name)"); } uint32_t character_id = static_cast(SvUV(ST(0))); @@ -6290,8 +6285,7 @@ XS(XS__remove_all_expedition_lockouts_by_char_id); XS(XS__remove_all_expedition_lockouts_by_char_id) { dXSARGS; if (items != 1 && items != 2) { - Perl_croak(aTHX_ "Usage: quest::remove_expedition_lockout_by_char_id" - "(uint32 character_id [, string expedition_name])"); + Perl_croak(aTHX_ "Usage: quest::remove_expedition_lockout_by_char_id(uint32 character_id, [string expedition_name])"); } std::string expedition_name; diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index f7cb160be..5b8ed1a05 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -6777,9 +6777,7 @@ XS(XS_Client_CreateExpedition); XS(XS_Client_CreateExpedition) { dXSARGS; if (items != 7 && items != 8) { - Perl_croak(aTHX_ "Usage: Client::CreateExpedition(THIS, string zone_name, uint32 zone_version, " - "uint32 duration, string expedition_name, uint32 min_players, uint32 max_players, " - "[, bool disable_messages = false])"); + Perl_croak(aTHX_ "Usage: Client::CreateExpedition(THIS, string zone_name, uint32 zone_version, uint32 duration, string expedition_name, uint32 min_players, uint32 max_players, [bool disable_messages = false])"); } Client* THIS = nullptr; @@ -6823,7 +6821,7 @@ XS(XS_Client_GetExpeditionLockouts); XS(XS_Client_GetExpeditionLockouts) { dXSARGS; if (items != 1 && items != 2) { - Perl_croak(aTHX_ "Usage: Client::GetExpeditionLockouts(THIS [, string expedition_name])"); + Perl_croak(aTHX_ "Usage: Client::GetExpeditionLockouts(THIS, [string expedition_name])"); } Client* THIS = nullptr; @@ -6905,7 +6903,7 @@ XS(XS_Client_AddExpeditionLockout); XS(XS_Client_AddExpeditionLockout) { dXSARGS; if (items != 4 && items != 5) { - Perl_croak(aTHX_ "Usage: Client::AddExpeditionLockout(THIS, string expedition_name, string event_name, uint32 seconds [, string uuid])"); + Perl_croak(aTHX_ "Usage: Client::AddExpeditionLockout(THIS, string expedition_name, string event_name, uint32 seconds, [string uuid])"); } Client* THIS = nullptr; @@ -6930,7 +6928,7 @@ XS(XS_Client_AddExpeditionLockoutDuration); XS(XS_Client_AddExpeditionLockoutDuration) { dXSARGS; if (items != 4 && items != 5) { - Perl_croak(aTHX_ "Usage: Client::AddExpeditionLockoutDuration(THIS, string expedition_name, string event_name, int seconds [, string uuid])"); + Perl_croak(aTHX_ "Usage: Client::AddExpeditionLockoutDuration(THIS, string expedition_name, string event_name, int seconds, [string uuid])"); } Client* THIS = nullptr; @@ -6955,7 +6953,7 @@ XS(XS_Client_RemoveAllExpeditionLockouts); XS(XS_Client_RemoveAllExpeditionLockouts) { dXSARGS; if (items != 1 && items != 2) { - Perl_croak(aTHX_ "Usage: Client::RemoveAllExpeditionLockouts(THIS [, string expedition_name])"); + Perl_croak(aTHX_ "Usage: Client::RemoveAllExpeditionLockouts(THIS, [string expedition_name])"); } Client* THIS = nullptr; @@ -7013,8 +7011,7 @@ XS(XS_Client_MovePCDynamicZone); XS(XS_Client_MovePCDynamicZone) { dXSARGS; if (items != 2 && items != 3 && items != 4) { - Perl_croak(aTHX_ "Usage: Client::MovePCDynamicZone(THIS, uint32 zone_id [, int zone_version = -1, bool message_if_invalid = true])\n" - "Usage: Client::MovePCDynamicZone(THIS, string zone_name [, int zone_version = -1, bool message_if_invalid = true])"); + Perl_croak(aTHX_ "Usage: Client::MovePCDynamicZone(THIS, uint32 zone_id | string zone_name, [int zone_version = -1], [bool message_if_invalid = true])"); } Client* THIS = nullptr; diff --git a/zone/perl_expedition.cpp b/zone/perl_expedition.cpp index 5a90ee5e3..e7e0f3bb2 100644 --- a/zone/perl_expedition.cpp +++ b/zone/perl_expedition.cpp @@ -70,7 +70,7 @@ XS(XS_Expedition_AddLockoutDuration); XS(XS_Expedition_AddLockoutDuration) { dXSARGS; if (items != 3 && items != 4) { - Perl_croak(aTHX_ "Usage: Expedition::AddLockout(THIS, string event_name, int seconds [, bool members_only = true])"); + Perl_croak(aTHX_ "Usage: Expedition::AddLockout(THIS, string event_name, int seconds, [bool members_only = true])"); } Expedition* THIS = nullptr; @@ -112,7 +112,7 @@ XS(XS_Expedition_AddReplayLockoutDuration); XS(XS_Expedition_AddReplayLockoutDuration) { dXSARGS; if (items != 2 && items != 3) { - Perl_croak(aTHX_ "Usage: Expedition::AddReplayLockoutDuration(THIS, int seconds [, bool members_only = true])"); + Perl_croak(aTHX_ "Usage: Expedition::AddReplayLockoutDuration(THIS, int seconds, [bool members_only = true])"); } Expedition* THIS = nullptr; @@ -406,7 +406,7 @@ XS(XS_Expedition_SetCompass); XS(XS_Expedition_SetCompass) { dXSARGS; if (items != 5) { - Perl_croak(aTHX_ "Usage: Expedition::SetCompass(THIS, uint32 zone_id|string zone_name, float x, float y, float z)"); + Perl_croak(aTHX_ "Usage: Expedition::SetCompass(THIS, uint32 zone_id | string zone_name, float x, float y, float z)"); } Expedition* THIS = nullptr; @@ -438,7 +438,7 @@ XS(XS_Expedition_SetLocked); XS(XS_Expedition_SetLocked) { dXSARGS; if (items != 2 && items != 3 && items != 4) { - Perl_croak(aTHX_ "Usage: Expedition::SetLocked(THIS, bool locked [, int lock_msg = 0, uint32 color = 15])"); + Perl_croak(aTHX_ "Usage: Expedition::SetLocked(THIS, bool locked, [int lock_msg = 0], [uint32 color = 15])"); } Expedition* THIS = nullptr; @@ -515,7 +515,7 @@ XS(XS_Expedition_SetSafeReturn); XS(XS_Expedition_SetSafeReturn) { dXSARGS; if (items != 6) { - Perl_croak(aTHX_ "Usage: Expedition::SetSafeReturn(THIS, uint32 zone_id|string zone_name, float x, float y, float z, float heading)"); + Perl_croak(aTHX_ "Usage: Expedition::SetSafeReturn(THIS, uint32 zone_id | string zone_name, float x, float y, float z, float heading)"); } Expedition* THIS = nullptr; @@ -584,7 +584,7 @@ XS(XS_Expedition_UpdateLockoutDuration); XS(XS_Expedition_UpdateLockoutDuration) { dXSARGS; if (items != 3 && items != 4) { - Perl_croak(aTHX_ "Usage: Expedition::UpdateLockoutDuration(THIS, string event_name, uint32 seconds [, bool members_only])"); + Perl_croak(aTHX_ "Usage: Expedition::UpdateLockoutDuration(THIS, string event_name, uint32 seconds, [bool members_only = true])"); } Expedition* THIS = nullptr; diff --git a/zone/perl_groups.cpp b/zone/perl_groups.cpp index adac6ddae..c663ce0ea 100644 --- a/zone/perl_groups.cpp +++ b/zone/perl_groups.cpp @@ -580,7 +580,7 @@ XS(XS_Group_DoesAnyMemberHaveExpeditionLockout); XS(XS_Group_DoesAnyMemberHaveExpeditionLockout) { dXSARGS; if (items != 3 && items != 4) { - Perl_croak(aTHX_ "Usage: Group::DoesAnyMemberHaveExpeditionLockout(THIS, string expedition_name, string event_name [, int max_check_count = 0])"); + Perl_croak(aTHX_ "Usage: Group::DoesAnyMemberHaveExpeditionLockout(THIS, string expedition_name, string event_name, [int max_check_count = 0])"); } Group* THIS = nullptr; diff --git a/zone/perl_raids.cpp b/zone/perl_raids.cpp index 2c2d8ff3c..5a7c80c1c 100644 --- a/zone/perl_raids.cpp +++ b/zone/perl_raids.cpp @@ -549,7 +549,7 @@ XS(XS_Raid_DoesAnyMemberHaveExpeditionLockout); XS(XS_Raid_DoesAnyMemberHaveExpeditionLockout) { dXSARGS; if (items != 3 && items != 4) { - Perl_croak(aTHX_ "Usage: Raid::DoesAnyMemberHaveExpeditionLockout(THIS, string expedition_name, string event_name [, int max_check_count = 0])"); + Perl_croak(aTHX_ "Usage: Raid::DoesAnyMemberHaveExpeditionLockout(THIS, string expedition_name, string event_name, [int max_check_count = 0])"); } Raid* THIS = nullptr; From b68994f25c27937f80c27121b5a5872db38a4ed3 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 11 Oct 2020 17:20:16 -0400 Subject: [PATCH 130/196] Change all expedition tables to latin1 The lockout tables are latin1 to support older MySQL and MariaDB versions that limit indexes --- utils/sql/git/required/wip_expeditions.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index f3e2598c9..60f5ca3b6 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -11,7 +11,7 @@ CREATE TABLE `expedition_details` ( PRIMARY KEY (`id`), UNIQUE INDEX `instance_id` (`instance_id`) ) -COLLATE='utf8mb4_general_ci' +COLLATE='latin1_swedish_ci' ENGINE=InnoDB ; @@ -36,7 +36,7 @@ CREATE TABLE `expedition_members` ( PRIMARY KEY (`id`), UNIQUE INDEX `expedition_id_character_id` (`expedition_id`, `character_id`) ) -COLLATE='utf8mb4_general_ci' +COLLATE='latin1_swedish_ci' ENGINE=InnoDB ; From 58b3708a47b19cbe602d46ce101e0447bec6a3bb Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 16 Oct 2020 22:18:00 -0400 Subject: [PATCH 131/196] Use named args in CreateExpedition lua api This is a breaking api change All expedition request parameters are now passed via a single table containing hash keyed options to define values --- zone/lua_client.cpp | 117 +++++++++++++++++++++++++------------------- zone/lua_client.h | 2 +- 2 files changed, 67 insertions(+), 52 deletions(-) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 2946e90b9..30518e520 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1648,77 +1648,92 @@ int Lua_Client::GetClientMaxLevel() { return self->GetClientMaxLevel(); } -Lua_Expedition Lua_Client::CreateExpedition(luabind::object dz_info, luabind::object expedition_info) { +DynamicZoneLocation GetDynamicZoneLocationFromTable(const luabind::object& lua_table) +{ + DynamicZoneLocation zone_location; + + if (luabind::type(lua_table) == LUA_TTABLE) + { + luabind::object lua_zone = lua_table["zone"]; + + // default invalid/missing args to 0 + uint32_t zone_id = 0; + if (luabind::type(lua_zone) == LUA_TSTRING) + { + zone_id = ZoneID(luabind::object_cast(lua_zone)); + } + else if (luabind::type(lua_zone) == LUA_TNUMBER) + { + zone_id = luabind::object_cast(lua_zone); + } + + float x = (luabind::type(lua_table["x"]) != LUA_TNIL) ? luabind::object_cast(lua_table["x"]) : 0.0f; + float y = (luabind::type(lua_table["y"]) != LUA_TNIL) ? luabind::object_cast(lua_table["y"]) : 0.0f; + float z = (luabind::type(lua_table["z"]) != LUA_TNIL) ? luabind::object_cast(lua_table["z"]) : 0.0f; + float h = (luabind::type(lua_table["h"]) != LUA_TNIL) ? luabind::object_cast(lua_table["h"]) : 0.0f; + + zone_location = { zone_id, x, y, z, h }; + } + + return zone_location; +} + +Lua_Expedition Lua_Client::CreateExpedition(luabind::object expedition_table) { Lua_Safe_Call_Class(Lua_Expedition); - if (luabind::type(dz_info) != LUA_TTABLE || luabind::type(expedition_info) != LUA_TTABLE) + if (luabind::type(expedition_table) != LUA_TTABLE) { return nullptr; } - // luabind will catch thrown cast_failed exceptions for invalid args, we - // shouldn't need to validate here unless we want non-ambiguous quest errors - std::string zone_name = luabind::object_cast(dz_info[1]); - uint32_t zone_version = luabind::object_cast(dz_info[2]); - uint32_t zone_duration = luabind::object_cast(dz_info[3]); + // luabind will catch thrown cast_failed exceptions for invalid/missing args + luabind::object instance_info = expedition_table["instance"]; + luabind::object zone = instance_info["zone"]; - DynamicZone dz{ zone_name, zone_version, zone_duration, DynamicZoneType::Expedition }; + uint32_t zone_id = 0; + if (luabind::type(zone) == LUA_TSTRING) + { + zone_id = ZoneID(luabind::object_cast(zone)); + } + else if (luabind::type(zone) == LUA_TNUMBER) + { + zone_id = luabind::object_cast(zone); + } + + uint32_t zone_version = luabind::object_cast(instance_info["version"]); + uint32_t zone_duration = luabind::object_cast(instance_info["duration"]); + + DynamicZone dz{ zone_id, zone_version, zone_duration, DynamicZoneType::Expedition }; // the dz_info table supports optional hash entries for 'compass', 'safereturn', and 'zonein' data - if (luabind::type(dz_info["compass"]) == LUA_TTABLE) + if (luabind::type(expedition_table["compass"]) == LUA_TTABLE) { - luabind::object compass(dz_info["compass"]); - float x = luabind::object_cast(compass[2]); - float y = luabind::object_cast(compass[3]); - float z = luabind::object_cast(compass[4]); - - if (luabind::type(compass[1]) == LUA_TSTRING) - { - dz.SetCompass({ ZoneID(luabind::object_cast(compass[1])), x, y, z, 0.0f }); - } - else if (luabind::type(compass[1]) == LUA_TNUMBER) - { - dz.SetCompass({ luabind::object_cast(compass[1]), x, y, z, 0.0f }); - } + auto compass_loc = GetDynamicZoneLocationFromTable(expedition_table["compass"]); + dz.SetCompass(compass_loc); } - if (luabind::type(dz_info["safereturn"]) == LUA_TTABLE) + if (luabind::type(expedition_table["safereturn"]) == LUA_TTABLE) { - luabind::object safereturn(dz_info["safereturn"]); - float x = luabind::object_cast(safereturn[2]); - float y = luabind::object_cast(safereturn[3]); - float z = luabind::object_cast(safereturn[4]); - float w = luabind::object_cast(safereturn[5]); - - if (luabind::type(safereturn[1]) == LUA_TSTRING) - { - dz.SetSafeReturn({ ZoneID(luabind::object_cast(safereturn[1])), x, y, z, w }); - } - else if (luabind::type(safereturn[1]) == LUA_TNUMBER) - { - dz.SetSafeReturn({ luabind::object_cast(safereturn[1]), x, y, z, w }); - } + auto safereturn_loc = GetDynamicZoneLocationFromTable(expedition_table["safereturn"]); + dz.SetSafeReturn(safereturn_loc); } - if (luabind::type(dz_info["zonein"]) == LUA_TTABLE) + if (luabind::type(expedition_table["zonein"]) == LUA_TTABLE) { - dz.SetZoneInLocation(DynamicZoneLocation{ - 0, - luabind::object_cast(dz_info["zonein"][1]), - luabind::object_cast(dz_info["zonein"][2]), - luabind::object_cast(dz_info["zonein"][3]), - luabind::object_cast(dz_info["zonein"][4]) - }); + auto zonein_loc = GetDynamicZoneLocationFromTable(expedition_table["zonein"]); + dz.SetZoneInLocation(zonein_loc); } - std::string expedition_name = luabind::object_cast(expedition_info[1]); - uint32_t min_players = luabind::object_cast(expedition_info[2]); - uint32_t max_players = luabind::object_cast(expedition_info[3]); + luabind::object expedition_info = expedition_table["expedition"]; + + std::string expedition_name = luabind::object_cast(expedition_info["name"]); + uint32_t min_players = luabind::object_cast(expedition_info["min_players"]); + uint32_t max_players = luabind::object_cast(expedition_info["max_players"]); bool disable_messages = false; - if (luabind::type(expedition_info[4]) == LUA_TBOOLEAN) + if (luabind::type(expedition_info["disable_messages"]) == LUA_TBOOLEAN) { - disable_messages = luabind::object_cast(expedition_info[4]); + disable_messages = luabind::object_cast(expedition_info["disable_messages"]); } ExpeditionRequest request{ expedition_name, min_players, max_players, disable_messages }; @@ -2169,7 +2184,7 @@ 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::*)(luabind::object, luabind::object))&Lua_Client::CreateExpedition) + .def("CreateExpedition", (Lua_Expedition(Lua_Client::*)(luabind::object))&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) diff --git a/zone/lua_client.h b/zone/lua_client.h index 41ce4e5cf..8bfa5bda0 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -339,7 +339,7 @@ public: void SetClientMaxLevel(int value); int GetClientMaxLevel(); - Lua_Expedition CreateExpedition(luabind::object dz_info, luabind::object expedition_info); + Lua_Expedition CreateExpedition(luabind::object expedition_info); 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 disable_messages); Lua_Expedition GetExpedition(); From 0f051b68aebf5e589a2c81bc92baccce6735ac49 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 17 Oct 2020 22:02:13 -0400 Subject: [PATCH 132/196] Separate world expedition source files --- world/CMakeLists.txt | 6 + world/expedition.cpp | 494 +--------------------------------- world/expedition.h | 47 +--- world/expedition_cache.cpp | 161 +++++++++++ world/expedition_cache.h | 49 ++++ world/expedition_database.cpp | 200 ++++++++++++++ world/expedition_database.h | 39 +++ world/expedition_message.cpp | 207 ++++++++++++++ world/expedition_message.h | 36 +++ world/main.cpp | 3 +- world/zoneserver.cpp | 2 +- 11 files changed, 705 insertions(+), 539 deletions(-) create mode 100644 world/expedition_cache.cpp create mode 100644 world/expedition_cache.h create mode 100644 world/expedition_database.cpp create mode 100644 world/expedition_database.h create mode 100644 world/expedition_message.cpp create mode 100644 world/expedition_message.h diff --git a/world/CMakeLists.txt b/world/CMakeLists.txt index a0830c6a2..1efd2707c 100644 --- a/world/CMakeLists.txt +++ b/world/CMakeLists.txt @@ -10,6 +10,9 @@ SET(world_sources eql_config.cpp eqemu_api_world_data_service.cpp expedition.cpp + expedition_cache.cpp + expedition_database.cpp + expedition_message.cpp launcher_link.cpp launcher_list.cpp lfplist.cpp @@ -41,6 +44,9 @@ SET(world_headers eql_config.h eqemu_api_world_data_service.h expedition.h + expedition_cache.h + expedition_database.h + expedition_message.h launcher_link.h launcher_list.h lfplist.h diff --git a/world/expedition.cpp b/world/expedition.cpp index b88417e85..9a2dd58df 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -19,17 +19,11 @@ */ #include "expedition.h" -#include "clientlist.h" -#include "cliententry.h" +#include "expedition_database.h" #include "zonelist.h" #include "zoneserver.h" -#include "worlddb.h" -#include "../common/servertalk.h" -#include "../common/string_util.h" +#include "../common/eqemu_logsys.h" -ExpeditionCache expedition_cache; - -extern ClientList client_list; extern ZSList zoneserver_list; Expedition::Expedition( @@ -120,487 +114,3 @@ void Expedition::CheckExpireWarning() } } } - -void ExpeditionCache::LoadActiveExpeditions() -{ - BenchTimer benchmark; - - m_expeditions = ExpeditionDatabase::LoadExpeditions(); - - auto elapsed = benchmark.elapsed(); - LogExpeditions("World caching [{}] expeditions took [{}s]", m_expeditions.size(), elapsed); -} - -void ExpeditionCache::AddExpedition(uint32_t expedition_id) -{ - if (expedition_id == 0) - { - return; - } - - auto expedition = ExpeditionDatabase::LoadExpedition(expedition_id); - - if (expedition.GetID() == expedition_id) - { - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it == m_expeditions.end()) - { - m_expeditions.emplace_back(expedition); - } - } -} - -void ExpeditionCache::RemoveExpedition(uint32_t expedition_id) -{ - m_expeditions.erase(std::remove_if(m_expeditions.begin(), m_expeditions.end(), - [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - } - ), m_expeditions.end()); -} - -void ExpeditionCache::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) -{ - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) - { - if (remove) { - it->RemoveMember(character_id); - } else { - it->AddMember(character_id); - } - } -} - -void ExpeditionCache::RemoveAllMembers(uint32_t expedition_id) -{ - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) - { - it->RemoveAllMembers(); - } -} - -void ExpeditionCache::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) -{ - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) - { - it->UpdateDzSecondsRemaining(seconds_remaining); - } -} - -void ExpeditionCache::Process() -{ - if (!m_process_throttle_timer.Check()) - { - return; - } - - std::vector expedition_ids; - - // check cache for expired or empty expeditions to delete and notify zones. - for (auto it = m_expeditions.begin(); it != m_expeditions.end();) - { - bool is_deleted = false; - - if (it->IsEmpty() || it->IsExpired()) - { - // don't delete expedition until its dz instance is empty. this prevents - // an exploit where all members leave expedition and complete an event - // before being kicked from removal timer. the lockout could never be - // applied because the zone expedition cache was already invalidated. - auto dz_zoneserver = zoneserver_list.FindByInstanceID(it->GetInstanceID()); - if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) - { - LogExpeditions("Expedition [{}] expired or empty, notifying zones and deleting", it->GetID()); - expedition_ids.emplace_back(it->GetID()); - it->SendZonesExpeditionDeleted(); - is_deleted = true; - } - - if (it->IsEmpty() && !it->IsPendingDelete() && RuleB(Expedition, EmptyDzShutdownEnabled)) - { - it->UpdateDzSecondsRemaining(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); - } - - it->SetPendingDelete(true); - } - else - { - it->CheckExpireWarning(); - } - - it = is_deleted ? m_expeditions.erase(it) : it + 1; - } - - if (!expedition_ids.empty()) - { - ExpeditionDatabase::DeleteExpeditions(expedition_ids); - } -} - -void ExpeditionDatabase::PurgeExpiredExpeditions() -{ - std::string query = SQL( - SELECT - expedition_details.id - FROM expedition_details - LEFT JOIN instance_list ON expedition_details.instance_id = instance_list.id - LEFT JOIN - ( - SELECT expedition_id, COUNT(*) member_count - FROM expedition_members - GROUP BY expedition_id - ) expedition_members - ON expedition_members.expedition_id = expedition_details.id - WHERE - instance_list.id IS NULL - OR expedition_members.member_count IS NULL - OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); - ); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - std::vector expedition_ids; - for (auto row = results.begin(); row != results.end(); ++row) - { - expedition_ids.emplace_back(static_cast(strtoul(row[0], nullptr, 10))); - } - ExpeditionDatabase::DeleteExpeditions(expedition_ids); - } -} - -void ExpeditionDatabase::PurgeExpiredCharacterLockouts() -{ - std::string query = SQL( - DELETE FROM character_expedition_lockouts - WHERE expire_time <= NOW(); - ); - - database.QueryDatabase(query); -} - -std::vector ExpeditionDatabase::LoadExpeditions() -{ - std::vector expeditions; - - std::string query = SQL( - SELECT - expedition_details.id, - expedition_details.instance_id, - instance_list.zone, - instance_list.start_time, - instance_list.duration, - expedition_members.character_id - FROM expedition_details - INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id - INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id - ORDER BY expedition_details.id; - ); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - uint32_t last_expedition_id = 0; - - for (auto row = results.begin(); row != results.end(); ++row) - { - uint32_t expedition_id = strtoul(row[0], nullptr, 10); - - if (last_expedition_id != expedition_id) - { - expeditions.emplace_back( - static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[3], nullptr, 10)), // start_time - static_cast(strtoul(row[4], nullptr, 10)) // duration - ); - } - - last_expedition_id = expedition_id; - - uint32_t member_id = static_cast(strtoul(row[5], nullptr, 10)); - expeditions.back().AddMember(member_id); - } - } - - return expeditions; -} - -Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) -{ - LogExpeditions("Loading expedition [{}] for world cache", expedition_id); - - Expedition expedition; - - std::string query = fmt::format(SQL( - SELECT - expedition_details.id, - expedition_details.instance_id, - instance_list.zone, - instance_list.start_time, - instance_list.duration, - expedition_members.character_id - FROM expedition_details - INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id - INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id - WHERE expedition_details.id = {}; - ), expedition_id); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - bool created = false; - for (auto row = results.begin(); row != results.end(); ++row) - { - if (!created) - { - expedition = Expedition{ - static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[3], nullptr, 10)), // start_time - static_cast(strtoul(row[4], nullptr, 10)) // duration - }; - created = true; - } - - auto member_id = static_cast(strtoul(row[5], nullptr, 10)); - expedition.AddMember(member_id); - } - } - - return expedition; -} - -void ExpeditionDatabase::DeleteExpeditions(const std::vector& expedition_ids) -{ - LogExpeditionsDetail("Deleting [{}] expedition(s)", expedition_ids.size()); - - std::string expedition_ids_query; - for (const auto& expedition_id : expedition_ids) - { - fmt::format_to(std::back_inserter(expedition_ids_query), "{},", expedition_id); - } - - if (!expedition_ids_query.empty()) - { - expedition_ids_query.pop_back(); // trailing comma - - std::string query = fmt::format( - "DELETE FROM expedition_details WHERE id IN ({});", expedition_ids_query - ); - database.QueryDatabase(query); - - query = fmt::format( - "DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query - ); - database.QueryDatabase(query); - - query = fmt::format( - "DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query - ); - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::UpdateDzDuration(uint16_t instance_id, uint32_t new_duration) -{ - std::string query = fmt::format( - "UPDATE instance_list SET duration = {} WHERE id = {};", - new_duration, instance_id - ); - - database.QueryDatabase(query); -} - -void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) -{ - switch (pack->opcode) - { - case ServerOP_ExpeditionCreate: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.AddExpedition(buf->expedition_id); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionMemberChange: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.MemberChange(buf->expedition_id, buf->char_id, buf->removed); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionMemberSwap: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.MemberChange(buf->expedition_id, buf->remove_char_id, true); - expedition_cache.MemberChange(buf->expedition_id, buf->add_char_id, false); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionMembersRemoved: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.RemoveAllMembers(buf->expedition_id); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionGetOnlineMembers: - { - ExpeditionMessage::GetOnlineMembers(pack); - break; - } - case ServerOP_ExpeditionDzAddPlayer: - { - ExpeditionMessage::AddPlayer(pack); - break; - } - case ServerOP_ExpeditionDzMakeLeader: - { - ExpeditionMessage::MakeLeader(pack); - break; - } - case ServerOP_ExpeditionCharacterLockout: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto cle = client_list.FindCLEByCharacterID(buf->character_id); - if (cle && cle->Server()) - { - cle->Server()->SendPacket(pack); - } - break; - } - case ServerOP_ExpeditionSaveInvite: - { - ExpeditionMessage::SaveInvite(pack); - break; - } - case ServerOP_ExpeditionRequestInvite: - { - ExpeditionMessage::RequestInvite(pack); - break; - } - case ServerOP_ExpeditionSecondsRemaining: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.SetSecondsRemaining(buf->expedition_id, buf->new_duration_seconds); - break; - } - } -} - -void ExpeditionMessage::AddPlayer(ServerPacket* pack) -{ - auto buf = reinterpret_cast(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 ExpeditionMessage::MakeLeader(ServerPacket* pack) -{ - auto buf = reinterpret_cast(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 ExpeditionMessage::GetOnlineMembers(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - // not efficient but only requested during caching - char zone_name[64] = {0}; - std::vector all_clients; - all_clients.reserve(client_list.GetClientCount()); - client_list.GetClients(zone_name, all_clients); - - for (uint32_t i = 0; i < buf->count; ++i) - { - auto it = std::find_if(all_clients.begin(), all_clients.end(), [&](const ClientListEntry* cle) { - return (cle && cle->CharID() == buf->entries[i].character_id); - }); - - if (it != all_clients.end()) - { - buf->entries[i].character_zone_id = (*it)->zone(); - buf->entries[i].character_instance_id = (*it)->instance(); - buf->entries[i].character_online = true; - } - } - - zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); -} - -void ExpeditionMessage::SaveInvite(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); - if (invited_cle) - { - // store packet on cle and re-send it when client requests it - buf->is_char_online = true; - pack->opcode = ServerOP_ExpeditionDzAddPlayer; - invited_cle->SetPendingExpeditionInvite(pack); - } -} - -void ExpeditionMessage::RequestInvite(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - ClientListEntry* cle = client_list.FindCLEByCharacterID(buf->character_id); - if (cle) - { - auto invite_pack = cle->GetPendingExpeditionInvite(); - if (invite_pack && cle->Server()) - { - cle->Server()->SendPacket(invite_pack.get()); - } - } -} diff --git a/world/expedition.h b/world/expedition.h index 02a460f6e..c0c0da9a9 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -21,59 +21,16 @@ #ifndef WORLD_EXPEDITION_H #define WORLD_EXPEDITION_H -#include "../common/rulesys.h" #include "../common/timer.h" #include +#include #include -#include - -extern class ExpeditionCache expedition_cache; - -class Expedition; -class ServerPacket; - -namespace ExpeditionDatabase -{ - void PurgeExpiredExpeditions(); - void PurgeExpiredCharacterLockouts(); - std::vector LoadExpeditions(); - Expedition LoadExpedition(uint32_t expedition_id); - void DeleteExpeditions(const std::vector& expedition_ids); - void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); -}; - -namespace ExpeditionMessage -{ - void HandleZoneMessage(ServerPacket* pack); - void AddPlayer(ServerPacket* pack); - void MakeLeader(ServerPacket* pack); - void GetOnlineMembers(ServerPacket* pack); - void SaveInvite(ServerPacket* pack); - void RequestInvite(ServerPacket* pack); -}; - -class ExpeditionCache -{ -public: - void AddExpedition(uint32_t expedition_id); - void RemoveExpedition(uint32_t expedition_id); - void LoadActiveExpeditions(); - void MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove); - void RemoveAllMembers(uint32_t expedition_id); - void SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining); - void Process(); - -private: - std::vector m_expeditions; - Timer m_process_throttle_timer{static_cast(RuleI(Expedition, WorldExpeditionProcessRateMS))}; -}; class Expedition { public: Expedition() = default; - Expedition( - uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, + Expedition(uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, uint32_t expire_time, uint32_t duration); void AddMember(uint32_t character_id) { m_member_ids.emplace(character_id); } diff --git a/world/expedition_cache.cpp b/world/expedition_cache.cpp new file mode 100644 index 000000000..2c1b68f5b --- /dev/null +++ b/world/expedition_cache.cpp @@ -0,0 +1,161 @@ +/** + * 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_cache.h" +#include "expedition.h" +#include "expedition_database.h" +#include "zonelist.h" +#include "zoneserver.h" +#include "../common/eqemu_logsys.h" +#include + +extern ZSList zoneserver_list; + +ExpeditionCache expedition_cache; + +void ExpeditionCache::LoadActiveExpeditions() +{ + BenchTimer benchmark; + + m_expeditions = ExpeditionDatabase::LoadExpeditions(); + + auto elapsed = benchmark.elapsed(); + LogExpeditions("World caching [{}] expeditions took [{}s]", m_expeditions.size(), elapsed); +} + +void ExpeditionCache::AddExpedition(uint32_t expedition_id) +{ + if (expedition_id == 0) + { + return; + } + + auto expedition = ExpeditionDatabase::LoadExpedition(expedition_id); + + if (expedition.GetID() == expedition_id) + { + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it == m_expeditions.end()) + { + m_expeditions.emplace_back(expedition); + } + } +} + +void ExpeditionCache::RemoveExpedition(uint32_t expedition_id) +{ + m_expeditions.erase(std::remove_if(m_expeditions.begin(), m_expeditions.end(), + [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + } + ), m_expeditions.end()); +} + +void ExpeditionCache::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + if (remove) { + it->RemoveMember(character_id); + } else { + it->AddMember(character_id); + } + } +} + +void ExpeditionCache::RemoveAllMembers(uint32_t expedition_id) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + it->RemoveAllMembers(); + } +} + +void ExpeditionCache::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + it->UpdateDzSecondsRemaining(seconds_remaining); + } +} + +void ExpeditionCache::Process() +{ + if (!m_process_throttle_timer.Check()) + { + return; + } + + std::vector expedition_ids; + + for (auto it = m_expeditions.begin(); it != m_expeditions.end();) + { + bool is_deleted = false; + + if (it->IsEmpty() || it->IsExpired()) + { + // don't delete expedition until its dz instance is empty. this prevents + // an exploit where all members leave expedition and complete an event + // before being kicked from removal timer. the lockout could never be + // applied because the zone expedition cache was already invalidated. + auto dz_zoneserver = zoneserver_list.FindByInstanceID(it->GetInstanceID()); + if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) + { + LogExpeditions("Expedition [{}] expired or empty, notifying zones and deleting", it->GetID()); + expedition_ids.emplace_back(it->GetID()); + it->SendZonesExpeditionDeleted(); + is_deleted = true; + } + + if (it->IsEmpty() && !it->IsPendingDelete() && RuleB(Expedition, EmptyDzShutdownEnabled)) + { + it->UpdateDzSecondsRemaining(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); + } + + it->SetPendingDelete(true); + } + else + { + it->CheckExpireWarning(); + } + + it = is_deleted ? m_expeditions.erase(it) : it + 1; + } + + if (!expedition_ids.empty()) + { + ExpeditionDatabase::DeleteExpeditions(expedition_ids); + } +} diff --git a/world/expedition_cache.h b/world/expedition_cache.h new file mode 100644 index 000000000..83632e51e --- /dev/null +++ b/world/expedition_cache.h @@ -0,0 +1,49 @@ +/** + * 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_CACHE_H +#define WORLD_EXPEDITION_CACHE_H + +#include "../common/rulesys.h" +#include "../common/timer.h" +#include +#include + +extern class ExpeditionCache expedition_cache; + +class Expedition; + +class ExpeditionCache +{ +public: + void AddExpedition(uint32_t expedition_id); + void LoadActiveExpeditions(); + void MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove); + void Process(); + void RemoveAllMembers(uint32_t expedition_id); + void RemoveExpedition(uint32_t expedition_id); + void SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining); + +private: + std::vector m_expeditions; + Timer m_process_throttle_timer{static_cast(RuleI(Expedition, WorldExpeditionProcessRateMS))}; +}; + +#endif diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp new file mode 100644 index 000000000..7d7387186 --- /dev/null +++ b/world/expedition_database.cpp @@ -0,0 +1,200 @@ +/** + * 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 "worlddb.h" + +void ExpeditionDatabase::PurgeExpiredExpeditions() +{ + std::string query = SQL( + SELECT + expedition_details.id + FROM expedition_details + LEFT JOIN instance_list ON expedition_details.instance_id = instance_list.id + LEFT JOIN + ( + SELECT expedition_id, COUNT(*) member_count + FROM expedition_members + GROUP BY expedition_id + ) expedition_members + ON expedition_members.expedition_id = expedition_details.id + WHERE + instance_list.id IS NULL + OR expedition_members.member_count IS NULL + OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); + ); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + std::vector expedition_ids; + for (auto row = results.begin(); row != results.end(); ++row) + { + expedition_ids.emplace_back(static_cast(strtoul(row[0], nullptr, 10))); + } + ExpeditionDatabase::DeleteExpeditions(expedition_ids); + } +} + +void ExpeditionDatabase::PurgeExpiredCharacterLockouts() +{ + std::string query = SQL( + DELETE FROM character_expedition_lockouts + WHERE expire_time <= NOW(); + ); + + database.QueryDatabase(query); +} + +std::vector ExpeditionDatabase::LoadExpeditions() +{ + std::vector expeditions; + + std::string query = SQL( + SELECT + expedition_details.id, + expedition_details.instance_id, + instance_list.zone, + instance_list.start_time, + instance_list.duration, + expedition_members.character_id + FROM expedition_details + INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id + INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id + ORDER BY expedition_details.id; + ); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + uint32_t last_expedition_id = 0; + + for (auto row = results.begin(); row != results.end(); ++row) + { + uint32_t expedition_id = strtoul(row[0], nullptr, 10); + + if (last_expedition_id != expedition_id) + { + expeditions.emplace_back( + static_cast(strtoul(row[0], nullptr, 10)), // expedition_id + static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[3], nullptr, 10)), // start_time + static_cast(strtoul(row[4], nullptr, 10)) // duration + ); + } + + last_expedition_id = expedition_id; + + uint32_t member_id = static_cast(strtoul(row[5], nullptr, 10)); + expeditions.back().AddMember(member_id); + } + } + + return expeditions; +} + +Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) +{ + LogExpeditions("Loading expedition [{}] for world cache", expedition_id); + + Expedition expedition; + + std::string query = fmt::format(SQL( + SELECT + expedition_details.id, + expedition_details.instance_id, + instance_list.zone, + instance_list.start_time, + instance_list.duration, + expedition_members.character_id + FROM expedition_details + INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id + INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id + WHERE expedition_details.id = {}; + ), expedition_id); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + bool created = false; + for (auto row = results.begin(); row != results.end(); ++row) + { + if (!created) + { + expedition = Expedition{ + static_cast(strtoul(row[0], nullptr, 10)), // expedition_id + static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[3], nullptr, 10)), // start_time + static_cast(strtoul(row[4], nullptr, 10)) // duration + }; + created = true; + } + + auto member_id = static_cast(strtoul(row[5], nullptr, 10)); + expedition.AddMember(member_id); + } + } + + return expedition; +} + +void ExpeditionDatabase::DeleteExpeditions(const std::vector& expedition_ids) +{ + LogExpeditionsDetail("Deleting [{}] expedition(s)", expedition_ids.size()); + + std::string expedition_ids_query; + for (const auto& expedition_id : expedition_ids) + { + fmt::format_to(std::back_inserter(expedition_ids_query), "{},", expedition_id); + } + + if (!expedition_ids_query.empty()) + { + expedition_ids_query.pop_back(); // trailing comma + + std::string query = fmt::format( + "DELETE FROM expedition_details WHERE id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); + + query = fmt::format( + "DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); + + query = fmt::format( + "DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); + } +} + +void ExpeditionDatabase::UpdateDzDuration(uint16_t instance_id, uint32_t new_duration) +{ + std::string query = fmt::format( + "UPDATE instance_list SET duration = {} WHERE id = {};", + new_duration, instance_id + ); + + database.QueryDatabase(query); +} diff --git a/world/expedition_database.h b/world/expedition_database.h new file mode 100644 index 000000000..ea33e823b --- /dev/null +++ b/world/expedition_database.h @@ -0,0 +1,39 @@ +/** + * 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_DATABASE_H +#define WORLD_EXPEDITION_DATABASE_H + +#include +#include + +class Expedition; + +namespace ExpeditionDatabase +{ + void DeleteExpeditions(const std::vector& expedition_ids); + std::vector LoadExpeditions(); + Expedition LoadExpedition(uint32_t expedition_id); + void PurgeExpiredExpeditions(); + void PurgeExpiredCharacterLockouts(); + void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); +}; + +#endif diff --git a/world/expedition_message.cpp b/world/expedition_message.cpp new file mode 100644 index 000000000..990c4dc41 --- /dev/null +++ b/world/expedition_message.cpp @@ -0,0 +1,207 @@ +/** + * 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_message.h" +#include "expedition_cache.h" +#include "cliententry.h" +#include "clientlist.h" +#include "zonelist.h" +#include "zoneserver.h" +#include "../common/servertalk.h" +#include + +extern ClientList client_list; +extern ZSList zoneserver_list; + +void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) +{ + switch (pack->opcode) + { + case ServerOP_ExpeditionCreate: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.AddExpedition(buf->expedition_id); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionMemberChange: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.MemberChange(buf->expedition_id, buf->char_id, buf->removed); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionMemberSwap: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.MemberChange(buf->expedition_id, buf->remove_char_id, true); + expedition_cache.MemberChange(buf->expedition_id, buf->add_char_id, false); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionMembersRemoved: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.RemoveAllMembers(buf->expedition_id); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionGetOnlineMembers: + { + ExpeditionMessage::GetOnlineMembers(pack); + break; + } + case ServerOP_ExpeditionDzAddPlayer: + { + ExpeditionMessage::AddPlayer(pack); + break; + } + case ServerOP_ExpeditionDzMakeLeader: + { + ExpeditionMessage::MakeLeader(pack); + break; + } + case ServerOP_ExpeditionCharacterLockout: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto cle = client_list.FindCLEByCharacterID(buf->character_id); + if (cle && cle->Server()) + { + cle->Server()->SendPacket(pack); + } + break; + } + case ServerOP_ExpeditionSaveInvite: + { + ExpeditionMessage::SaveInvite(pack); + break; + } + case ServerOP_ExpeditionRequestInvite: + { + ExpeditionMessage::RequestInvite(pack); + break; + } + case ServerOP_ExpeditionSecondsRemaining: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.SetSecondsRemaining(buf->expedition_id, buf->new_duration_seconds); + break; + } + } +} + +void ExpeditionMessage::AddPlayer(ServerPacket* pack) +{ + auto buf = reinterpret_cast(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 ExpeditionMessage::MakeLeader(ServerPacket* pack) +{ + auto buf = reinterpret_cast(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 ExpeditionMessage::GetOnlineMembers(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + + // not efficient but only requested during caching + char zone_name[64] = {0}; + std::vector all_clients; + all_clients.reserve(client_list.GetClientCount()); + client_list.GetClients(zone_name, all_clients); + + for (uint32_t i = 0; i < buf->count; ++i) + { + auto it = std::find_if(all_clients.begin(), all_clients.end(), [&](const ClientListEntry* cle) { + return (cle && cle->CharID() == buf->entries[i].character_id); + }); + + if (it != all_clients.end()) + { + buf->entries[i].character_zone_id = (*it)->zone(); + buf->entries[i].character_instance_id = (*it)->instance(); + buf->entries[i].character_online = true; + } + } + + zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); +} + +void ExpeditionMessage::SaveInvite(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + + ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); + if (invited_cle) + { + // store packet on cle and re-send it when client requests it + buf->is_char_online = true; + pack->opcode = ServerOP_ExpeditionDzAddPlayer; + invited_cle->SetPendingExpeditionInvite(pack); + } +} + +void ExpeditionMessage::RequestInvite(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + ClientListEntry* cle = client_list.FindCLEByCharacterID(buf->character_id); + if (cle) + { + auto invite_pack = cle->GetPendingExpeditionInvite(); + if (invite_pack && cle->Server()) + { + cle->Server()->SendPacket(invite_pack.get()); + } + } +} diff --git a/world/expedition_message.h b/world/expedition_message.h new file mode 100644 index 000000000..17470d788 --- /dev/null +++ b/world/expedition_message.h @@ -0,0 +1,36 @@ +/** + * 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_MESSAGE_H +#define WORLD_EXPEDITION_MESSAGE_H + +class ServerPacket; + +namespace ExpeditionMessage +{ + void AddPlayer(ServerPacket* pack); + void GetOnlineMembers(ServerPacket* pack); + void HandleZoneMessage(ServerPacket* pack); + void MakeLeader(ServerPacket* pack); + void RequestInvite(ServerPacket* pack); + void SaveInvite(ServerPacket* pack); +}; + +#endif diff --git a/world/main.cpp b/world/main.cpp index a3ca55e48..e10b09b45 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -88,7 +88,8 @@ union semun { #include "queryserv.h" #include "web_interface.h" #include "console.h" -#include "expedition.h" +#include "expedition_cache.h" +#include "expedition_database.h" #include "../common/net/servertalk_server.h" #include "../zone/data_bucket.h" diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index e1beff363..0a1a960d7 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -36,7 +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" +#include "expedition_message.h" extern ClientList client_list; extern GroupLFPList LFPGroupList; From 579c300cbc051c8f28b34cedd4aa7de6703e0ff7 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 17 Oct 2020 22:26:53 -0400 Subject: [PATCH 133/196] Rename expeditions table --- common/database_instances.cpp | 4 +-- common/database_schema.h | 2 +- utils/sql/git/required/wip_expeditions.sql | 2 +- world/expedition_database.cpp | 34 +++++++++--------- zone/expedition_database.cpp | 40 +++++++++++----------- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/common/database_instances.cpp b/common/database_instances.cpp index 0949a6708..7bfb548ce 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -496,7 +496,7 @@ void Database::DeleteInstance(uint16 instance_id) query = fmt::format("DELETE FROM dynamic_zones WHERE instance_id={}", instance_id); QueryDatabase(query); - query = fmt::format("UPDATE expedition_details SET instance_id = NULL WHERE instance_id={}", instance_id); + query = fmt::format("UPDATE expeditions SET instance_id = NULL WHERE instance_id={}", instance_id); QueryDatabase(query); BuryCorpsesInInstance(instance_id); @@ -589,7 +589,7 @@ void Database::PurgeExpiredInstances() QueryDatabase(fmt::format("DELETE FROM spawn_condition_values WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("UPDATE character_corpses SET is_buried = 1, instance_id = 0 WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("DELETE FROM dynamic_zones WHERE instance_id IN ({})", imploded_instance_ids)); - QueryDatabase(fmt::format("UPDATE expedition_details SET instance_id = NULL WHERE instance_id IN ({})", imploded_instance_ids)); + QueryDatabase(fmt::format("UPDATE expeditions SET instance_id = NULL WHERE instance_id IN ({})", imploded_instance_ids)); } void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) diff --git a/common/database_schema.h b/common/database_schema.h index 2abb10b8c..95a550d69 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -309,9 +309,9 @@ namespace DatabaseSchema { "bugs", "dynamic_zones", "eventlog", - "expedition_details", "expedition_lockouts", "expedition_members", + "expeditions", "gm_ips", "group_id", "group_leaders", diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 60f5ca3b6..90b3286c0 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -1,4 +1,4 @@ -CREATE TABLE `expedition_details` ( +CREATE TABLE `expeditions` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` VARCHAR(36) NOT NULL, `instance_id` INT(10) NULL DEFAULT 0, diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp index 7d7387186..32fa4cecb 100644 --- a/world/expedition_database.cpp +++ b/world/expedition_database.cpp @@ -26,16 +26,16 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() { std::string query = SQL( SELECT - expedition_details.id - FROM expedition_details - LEFT JOIN instance_list ON expedition_details.instance_id = instance_list.id + expeditions.id + FROM expeditions + LEFT JOIN instance_list ON expeditions.instance_id = instance_list.id LEFT JOIN ( SELECT expedition_id, COUNT(*) member_count FROM expedition_members GROUP BY expedition_id ) expedition_members - ON expedition_members.expedition_id = expedition_details.id + ON expedition_members.expedition_id = expeditions.id WHERE instance_list.id IS NULL OR expedition_members.member_count IS NULL @@ -70,16 +70,16 @@ std::vector ExpeditionDatabase::LoadExpeditions() std::string query = SQL( SELECT - expedition_details.id, - expedition_details.instance_id, + expeditions.id, + expeditions.instance_id, instance_list.zone, instance_list.start_time, instance_list.duration, expedition_members.character_id - FROM expedition_details - INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id - INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id - ORDER BY expedition_details.id; + FROM expeditions + INNER JOIN instance_list ON expeditions.instance_id = instance_list.id + INNER JOIN expedition_members ON expedition_members.expedition_id = expeditions.id + ORDER BY expeditions.id; ); auto results = database.QueryDatabase(query); @@ -120,16 +120,16 @@ Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) std::string query = fmt::format(SQL( SELECT - expedition_details.id, - expedition_details.instance_id, + expeditions.id, + expeditions.instance_id, instance_list.zone, instance_list.start_time, instance_list.duration, expedition_members.character_id - FROM expedition_details - INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id - INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id - WHERE expedition_details.id = {}; + FROM expeditions + INNER JOIN instance_list ON expeditions.instance_id = instance_list.id + INNER JOIN expedition_members ON expedition_members.expedition_id = expeditions.id + WHERE expeditions.id = {}; ), expedition_id); auto results = database.QueryDatabase(query); @@ -173,7 +173,7 @@ void ExpeditionDatabase::DeleteExpeditions(const std::vector& expediti expedition_ids_query.pop_back(); // trailing comma std::string query = fmt::format( - "DELETE FROM expedition_details WHERE id IN ({});", expedition_ids_query + "DELETE FROM expeditions WHERE id IN ({});", expedition_ids_query ); database.QueryDatabase(query); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index ca25357bc..41dcc8cad 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -35,7 +35,7 @@ uint32_t ExpeditionDatabase::InsertExpedition( ); std::string query = fmt::format(SQL( - INSERT INTO expedition_details + INSERT INTO expeditions (uuid, instance_id, expedition_name, leader_id, min_players, max_players) VALUES ('{}', {}, '{}', {}, {}, {}); @@ -55,21 +55,21 @@ std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() { return std::string(SQL( SELECT - expedition_details.id, - expedition_details.uuid, - expedition_details.instance_id, - expedition_details.expedition_name, - expedition_details.leader_id, - expedition_details.min_players, - expedition_details.max_players, - expedition_details.add_replay_on_join, - expedition_details.is_locked, + expeditions.id, + expeditions.uuid, + expeditions.instance_id, + expeditions.expedition_name, + expeditions.leader_id, + expeditions.min_players, + expeditions.max_players, + expeditions.add_replay_on_join, + expeditions.is_locked, character_data.name leader_name, expedition_members.character_id, member_data.name - FROM expedition_details - INNER JOIN character_data ON expedition_details.leader_id = character_data.id - INNER JOIN expedition_members ON expedition_details.id = expedition_members.expedition_id + FROM expeditions + INNER JOIN character_data ON expeditions.leader_id = character_data.id + INNER JOIN expedition_members ON expeditions.id = expedition_members.expedition_id INNER JOIN character_data member_data ON expedition_members.character_id = member_data.id )); } @@ -79,7 +79,7 @@ MySQLRequestResult ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) LogExpeditionsDetail("Loading expedition [{}]", expedition_id); std::string query = fmt::format(SQL( - {} WHERE expedition_details.id = {}; + {} WHERE expeditions.id = {}; ), LoadExpeditionsSelectQuery(), expedition_id); return database.QueryDatabase(query); @@ -90,7 +90,7 @@ MySQLRequestResult ExpeditionDatabase::LoadAllExpeditions() LogExpeditionsDetail("Loading all expeditions from database"); std::string query = fmt::format(SQL( - {} ORDER BY expedition_details.id; + {} ORDER BY expeditions.id; ), LoadExpeditionsSelectQuery()); return database.QueryDatabase(query); @@ -193,12 +193,12 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts( SELECT expedition_lockouts.expedition_id, expedition_lockouts.from_expedition_uuid, - expedition_details.expedition_name, + expeditions.expedition_name, expedition_lockouts.event_name, UNIX_TIMESTAMP(expedition_lockouts.expire_time), expedition_lockouts.duration FROM expedition_lockouts - INNER JOIN expedition_details ON expedition_lockouts.expedition_id = expedition_details.id + INNER JOIN expeditions ON expedition_lockouts.expedition_id = expeditions.id WHERE expedition_id IN ({}) ORDER BY expedition_id; ), in_expedition_ids_query); @@ -640,7 +640,7 @@ void ExpeditionDatabase::UpdateLeaderID(uint32_t expedition_id, uint32_t leader_ LogExpeditionsDetail("Updating leader [{}] for expedition [{}]", leader_id, expedition_id); auto query = fmt::format(SQL( - UPDATE expedition_details SET leader_id = {} WHERE id = {}; + UPDATE expeditions SET leader_id = {} WHERE id = {}; ), leader_id, expedition_id); database.QueryDatabase(query); @@ -651,7 +651,7 @@ void ExpeditionDatabase::UpdateLockState(uint32_t expedition_id, bool is_locked) LogExpeditionsDetail("Updating lock state [{}] for expedition [{}]", is_locked, expedition_id); auto query = fmt::format(SQL( - UPDATE expedition_details SET is_locked = {} WHERE id = {}; + UPDATE expeditions SET is_locked = {} WHERE id = {}; ), is_locked, expedition_id); database.QueryDatabase(query); @@ -684,7 +684,7 @@ void ExpeditionDatabase::UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool LogExpeditionsDetail("Updating replay lockout on join [{}] for expedition [{}]", add_on_join, expedition_id); auto query = fmt::format(SQL( - UPDATE expedition_details SET add_replay_on_join = {} WHERE id = {}; + UPDATE expeditions SET add_replay_on_join = {} WHERE id = {}; ), add_on_join, expedition_id); database.QueryDatabase(query); From 022f82291a8b8360405e07ad5de40d01e9057d4c Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 17 Oct 2020 22:28:49 -0400 Subject: [PATCH 134/196] Rename world expedition cache to state --- world/CMakeLists.txt | 4 ++-- world/expedition_message.cpp | 14 +++++++------- ...pedition_cache.cpp => expedition_state.cpp} | 18 +++++++++--------- .../{expedition_cache.h => expedition_state.h} | 8 ++++---- world/main.cpp | 6 +++--- 5 files changed, 25 insertions(+), 25 deletions(-) rename world/{expedition_cache.cpp => expedition_state.cpp} (89%) rename world/{expedition_cache.h => expedition_state.h} (92%) diff --git a/world/CMakeLists.txt b/world/CMakeLists.txt index 1efd2707c..50210f2f3 100644 --- a/world/CMakeLists.txt +++ b/world/CMakeLists.txt @@ -10,9 +10,9 @@ SET(world_sources eql_config.cpp eqemu_api_world_data_service.cpp expedition.cpp - expedition_cache.cpp expedition_database.cpp expedition_message.cpp + expedition_state.cpp launcher_link.cpp launcher_list.cpp lfplist.cpp @@ -44,9 +44,9 @@ SET(world_headers eql_config.h eqemu_api_world_data_service.h expedition.h - expedition_cache.h expedition_database.h expedition_message.h + expedition_state.h launcher_link.h launcher_list.h lfplist.h diff --git a/world/expedition_message.cpp b/world/expedition_message.cpp index 990c4dc41..282c2dbe8 100644 --- a/world/expedition_message.cpp +++ b/world/expedition_message.cpp @@ -19,7 +19,7 @@ */ #include "expedition_message.h" -#include "expedition_cache.h" +#include "expedition_state.h" #include "cliententry.h" #include "clientlist.h" #include "zonelist.h" @@ -37,29 +37,29 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) case ServerOP_ExpeditionCreate: { auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.AddExpedition(buf->expedition_id); + expedition_state.AddExpedition(buf->expedition_id); zoneserver_list.SendPacket(pack); break; } case ServerOP_ExpeditionMemberChange: { auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.MemberChange(buf->expedition_id, buf->char_id, buf->removed); + expedition_state.MemberChange(buf->expedition_id, buf->char_id, buf->removed); zoneserver_list.SendPacket(pack); break; } case ServerOP_ExpeditionMemberSwap: { auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.MemberChange(buf->expedition_id, buf->remove_char_id, true); - expedition_cache.MemberChange(buf->expedition_id, buf->add_char_id, false); + expedition_state.MemberChange(buf->expedition_id, buf->remove_char_id, true); + expedition_state.MemberChange(buf->expedition_id, buf->add_char_id, false); zoneserver_list.SendPacket(pack); break; } case ServerOP_ExpeditionMembersRemoved: { auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.RemoveAllMembers(buf->expedition_id); + expedition_state.RemoveAllMembers(buf->expedition_id); zoneserver_list.SendPacket(pack); break; } @@ -101,7 +101,7 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) case ServerOP_ExpeditionSecondsRemaining: { auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.SetSecondsRemaining(buf->expedition_id, buf->new_duration_seconds); + expedition_state.SetSecondsRemaining(buf->expedition_id, buf->new_duration_seconds); break; } } diff --git a/world/expedition_cache.cpp b/world/expedition_state.cpp similarity index 89% rename from world/expedition_cache.cpp rename to world/expedition_state.cpp index 2c1b68f5b..d97c4f72e 100644 --- a/world/expedition_cache.cpp +++ b/world/expedition_state.cpp @@ -18,7 +18,7 @@ * */ -#include "expedition_cache.h" +#include "expedition_state.h" #include "expedition.h" #include "expedition_database.h" #include "zonelist.h" @@ -28,9 +28,9 @@ extern ZSList zoneserver_list; -ExpeditionCache expedition_cache; +ExpeditionState expedition_state; -void ExpeditionCache::LoadActiveExpeditions() +void ExpeditionState::LoadActiveExpeditions() { BenchTimer benchmark; @@ -40,7 +40,7 @@ void ExpeditionCache::LoadActiveExpeditions() LogExpeditions("World caching [{}] expeditions took [{}s]", m_expeditions.size(), elapsed); } -void ExpeditionCache::AddExpedition(uint32_t expedition_id) +void ExpeditionState::AddExpedition(uint32_t expedition_id) { if (expedition_id == 0) { @@ -62,7 +62,7 @@ void ExpeditionCache::AddExpedition(uint32_t expedition_id) } } -void ExpeditionCache::RemoveExpedition(uint32_t expedition_id) +void ExpeditionState::RemoveExpedition(uint32_t expedition_id) { m_expeditions.erase(std::remove_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { @@ -71,7 +71,7 @@ void ExpeditionCache::RemoveExpedition(uint32_t expedition_id) ), m_expeditions.end()); } -void ExpeditionCache::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) +void ExpeditionState::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) { auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { return expedition.GetID() == expedition_id; @@ -87,7 +87,7 @@ void ExpeditionCache::MemberChange(uint32_t expedition_id, uint32_t character_id } } -void ExpeditionCache::RemoveAllMembers(uint32_t expedition_id) +void ExpeditionState::RemoveAllMembers(uint32_t expedition_id) { auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { return expedition.GetID() == expedition_id; @@ -99,7 +99,7 @@ void ExpeditionCache::RemoveAllMembers(uint32_t expedition_id) } } -void ExpeditionCache::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) +void ExpeditionState::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) { auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { return expedition.GetID() == expedition_id; @@ -111,7 +111,7 @@ void ExpeditionCache::SetSecondsRemaining(uint32_t expedition_id, uint32_t secon } } -void ExpeditionCache::Process() +void ExpeditionState::Process() { if (!m_process_throttle_timer.Check()) { diff --git a/world/expedition_cache.h b/world/expedition_state.h similarity index 92% rename from world/expedition_cache.h rename to world/expedition_state.h index 83632e51e..6a9f230c2 100644 --- a/world/expedition_cache.h +++ b/world/expedition_state.h @@ -18,19 +18,19 @@ * */ -#ifndef WORLD_EXPEDITION_CACHE_H -#define WORLD_EXPEDITION_CACHE_H +#ifndef WORLD_EXPEDITION_STATE_H +#define WORLD_EXPEDITION_STATE_H #include "../common/rulesys.h" #include "../common/timer.h" #include #include -extern class ExpeditionCache expedition_cache; +extern class ExpeditionState expedition_state; class Expedition; -class ExpeditionCache +class ExpeditionState { public: void AddExpedition(uint32_t expedition_id); diff --git a/world/main.cpp b/world/main.cpp index e10b09b45..01682b26a 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -88,8 +88,8 @@ union semun { #include "queryserv.h" #include "web_interface.h" #include "console.h" -#include "expedition_cache.h" #include "expedition_database.h" +#include "expedition_state.h" #include "../common/net/servertalk_server.h" #include "../zone/data_bucket.h" @@ -436,7 +436,7 @@ int main(int argc, char** argv) { ExpeditionDatabase::PurgeExpiredCharacterLockouts(); LogInfo("Loading active expeditions"); - expedition_cache.LoadActiveExpeditions(); + expedition_state.LoadActiveExpeditions(); LogInfo("Loading char create info"); content_db.LoadCharacterCreateAllocations(); @@ -624,7 +624,7 @@ int main(int argc, char** argv) { launcher_list.Process(); LFPGroupList.Process(); adventure_manager.Process(); - expedition_cache.Process(); + expedition_state.Process(); if (InterserverTimer.Check()) { InterserverTimer.Start(); From b85feb546140f8b623d3d3795a0288a1e590e0df Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 17 Oct 2020 22:39:38 -0400 Subject: [PATCH 135/196] Rename expedition packet fields --- common/eq_packet_structs.h | 6 +++--- common/patches/rof.cpp | 8 ++++---- common/patches/rof2.cpp | 8 ++++---- common/patches/rof2_structs.h | 6 +++--- common/patches/rof_structs.h | 6 +++--- common/patches/sod.cpp | 8 ++++---- common/patches/sod_structs.h | 6 +++--- common/patches/sof.cpp | 8 ++++---- common/patches/sof_structs.h | 6 +++--- common/patches/titanium.cpp | 8 ++++---- common/patches/titanium_structs.h | 6 +++--- common/patches/uf.cpp | 8 ++++---- common/patches/uf_structs.h | 6 +++--- zone/expedition.cpp | 8 ++++---- 14 files changed, 49 insertions(+), 49 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 22be26b1d..57d4c0795 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -4873,14 +4873,14 @@ struct ExpeditionInfo_Struct struct ExpeditionMemberEntry_Struct { -/*000*/ char name[64]; // variable length, null terminated, max 0x40 (64) -/*064*/ uint8 status; // 0: unknown, 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[64]; // variable length, null terminated, max 0x40 (64) +/*064*/ uint8 expedition_status; // 0: unknown, 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; struct ExpeditionMemberList_Struct { /*000*/ uint32 client_id; -/*004*/ uint32 count; +/*004*/ uint32 member_count; /*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length }; diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 3549a0c2c..cb5400f1f 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -839,11 +839,11 @@ namespace RoF SerializeBuffer buf; buf.WriteUInt32(emu->client_id); - buf.WriteUInt32(emu->count); - for (uint32 i = 0; i < emu->count; ++i) + buf.WriteUInt32(emu->member_count); + for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].status); + buf.WriteUInt8(emu->members[i].expedition_status); } __packet->size = buf.size(); @@ -868,7 +868,7 @@ namespace RoF ENCODE(OP_DzMemberListStatus) { auto emu = reinterpret_cast((*p)->pBuffer); - if (emu->count == 1) + if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index bc53369ae..4a902061d 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -888,11 +888,11 @@ namespace RoF2 SerializeBuffer buf; buf.WriteUInt32(emu->client_id); - buf.WriteUInt32(emu->count); - for (uint32 i = 0; i < emu->count; ++i) + buf.WriteUInt32(emu->member_count); + for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].status); + buf.WriteUInt8(emu->members[i].expedition_status); } __packet->size = buf.size(); @@ -917,7 +917,7 @@ namespace RoF2 ENCODE(OP_DzMemberListStatus) { auto emu = reinterpret_cast((*p)->pBuffer); - if (emu->count == 1) + if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); } diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index b1be0c00a..c5111da6d 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -4921,14 +4921,14 @@ struct ExpeditionInfo_Struct struct ExpeditionMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; struct ExpeditionMemberList_Struct { /*000*/ uint32 client_id; -/*004*/ uint32 count; // number of players in window +/*004*/ uint32 member_count; // number of players in window /*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length }; diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 6fa44177e..a67e7b972 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -4853,14 +4853,14 @@ struct ExpeditionInfo_Struct struct ExpeditionMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; struct ExpeditionMemberList_Struct { /*000*/ uint32 client_id; -/*004*/ uint32 count; // number of players in window +/*004*/ uint32 member_count; // number of players in window /*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length }; diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 1857cd731..d6eae5838 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -612,11 +612,11 @@ namespace SoD SerializeBuffer buf; buf.WriteUInt32(emu->client_id); - buf.WriteUInt32(emu->count); - for (uint32 i = 0; i < emu->count; ++i) + buf.WriteUInt32(emu->member_count); + for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].status); + buf.WriteUInt8(emu->members[i].expedition_status); } __packet->size = buf.size(); @@ -641,7 +641,7 @@ namespace SoD ENCODE(OP_DzMemberListStatus) { auto emu = reinterpret_cast((*p)->pBuffer); - if (emu->count == 1) + if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); } diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 514722cb1..4e7735bf1 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -4208,14 +4208,14 @@ struct ExpeditionInfo_Struct struct ExpeditionMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; struct ExpeditionMemberList_Struct { /*000*/ uint32 client_id; -/*004*/ uint32 count; // number of players in window +/*004*/ uint32 member_count; // number of players in window /*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length }; diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index ddef71c4c..f24f265ff 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -600,11 +600,11 @@ namespace SoF SerializeBuffer buf; buf.WriteUInt32(emu->client_id); - buf.WriteUInt32(emu->count); - for (uint32 i = 0; i < emu->count; ++i) + buf.WriteUInt32(emu->member_count); + for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].status); + buf.WriteUInt8(emu->members[i].expedition_status); } __packet->size = buf.size(); @@ -629,7 +629,7 @@ namespace SoF ENCODE(OP_DzMemberListStatus) { auto emu = reinterpret_cast((*p)->pBuffer); - if (emu->count == 1) + if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); } diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 7755e767f..bf88bf5c2 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -4123,14 +4123,14 @@ struct ExpeditionInfo_Struct struct ExpeditionMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; struct ExpeditionMemberList_Struct { /*000*/ uint32 client_id; -/*004*/ uint32 count; +/*004*/ uint32 member_count; /*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length }; diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 04fd10d9c..f7d97b8d9 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -543,11 +543,11 @@ namespace Titanium SerializeBuffer buf; buf.WriteUInt32(emu->client_id); - buf.WriteUInt32(emu->count); - for (uint32 i = 0; i < emu->count; ++i) + buf.WriteUInt32(emu->member_count); + for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].status); + buf.WriteUInt8(emu->members[i].expedition_status); } __packet->size = buf.size(); @@ -572,7 +572,7 @@ namespace Titanium ENCODE(OP_DzMemberListStatus) { auto emu = reinterpret_cast((*p)->pBuffer); - if (emu->count == 1) + if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); } diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index 092c3d2a3..b2beae0cd 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -3334,14 +3334,14 @@ struct ExpeditionInfo_Struct struct ExpeditionMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; struct ExpeditionMemberList_Struct { /*000*/ uint32 client_id; -/*004*/ uint32 count; +/*004*/ uint32 member_count; /*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length }; diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 599272ad4..47dafbbf3 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -742,11 +742,11 @@ namespace UF SerializeBuffer buf; buf.WriteUInt32(emu->client_id); - buf.WriteUInt32(emu->count); - for (uint32 i = 0; i < emu->count; ++i) + buf.WriteUInt32(emu->member_count); + for (uint32 i = 0; i < emu->member_count; ++i) { buf.WriteString(emu->members[i].name); - buf.WriteUInt8(emu->members[i].status); + buf.WriteUInt8(emu->members[i].expedition_status); } __packet->size = buf.size(); @@ -771,7 +771,7 @@ namespace UF ENCODE(OP_DzMemberListStatus) { auto emu = reinterpret_cast((*p)->pBuffer); - if (emu->count == 1) + if (emu->member_count == 1) { ENCODE_FORWARD(OP_DzMemberList); } diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 70c1c6cd1..4a4ac8a36 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -4289,14 +4289,14 @@ struct ExpeditionInfo_Struct struct ExpeditionMemberEntry_Struct { -/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) -/*000*/ uint8 status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead +/*000*/ char name[1]; // variable length, null terminated, max 0x40 (64) +/*000*/ uint8 expedition_status; // 0: unknown 1: Online, 2: Offline, 3: In Dynamic Zone, 4: Link Dead }; struct ExpeditionMemberList_Struct { /*000*/ uint32 client_id; -/*004*/ uint32 count; // number of players in window +/*004*/ uint32 member_count; // number of players in window /*008*/ ExpeditionMemberEntry_Struct members[0]; // variable length }; diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 619006680..4d8966c76 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1488,14 +1488,14 @@ std::unique_ptr Expedition::CreateMemberListPacket(bool cle auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzMemberList, outsize)); auto buf = reinterpret_cast(outapp->pBuffer); - buf->count = member_count; + buf->member_count = member_count; if (!clear) { for (auto i = 0; i < m_members.size(); ++i) { strn0cpy(buf->members[i].name, m_members[i].name.c_str(), sizeof(buf->members[i].name)); - buf->members[i].status = static_cast(m_members[i].status); + buf->members[i].expedition_status = static_cast(m_members[i].status); } } @@ -1520,11 +1520,11 @@ std::unique_ptr Expedition::CreateMemberListStatusPacket( uint32_t outsize = sizeof(ExpeditionMemberList_Struct) + sizeof(ExpeditionMemberEntry_Struct); auto outapp = std::unique_ptr(new EQApplicationPacket(OP_DzMemberListStatus, outsize)); auto buf = reinterpret_cast(outapp->pBuffer); - buf->count = 1; + buf->member_count = 1; auto entry = reinterpret_cast(buf->members); strn0cpy(entry->name, name.c_str(), sizeof(entry->name)); - entry->status = static_cast(status); + entry->expedition_status = static_cast(status); return outapp; } From d2ad2ec078a6ed12ec3f44b263ec618f13c5f676 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 17 Oct 2020 22:41:54 -0400 Subject: [PATCH 136/196] Add comment to FormatName --- common/string_util.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/common/string_util.cpp b/common/string_util.cpp index 9190207ee..4b3b59eb1 100644 --- a/common/string_util.cpp +++ b/common/string_util.cpp @@ -593,6 +593,7 @@ std::string numberToWords(unsigned long long int n) return res; } +// first letter capitalized and rest made lower case std::string FormatName(const std::string& char_name) { std::string formatted(char_name); From 54500b0e7295a956be81f906016174ea92451164 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 17 Oct 2020 23:16:20 -0400 Subject: [PATCH 137/196] Use column aliases for expedition request query --- zone/expedition_database.h | 14 ++++++++++++++ zone/expedition_request.cpp | 18 +++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 07c395267..4335f29b4 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -97,4 +97,18 @@ namespace LoadExpeditionColumns }; }; +namespace LoadMembersForCreateRequestColumns +{ + enum eLoadMembersForCreateRequestColumns + { + character_id = 0, + character_name, + character_expedition_id, + lockout_uuid, + lockout_expire_time, + lockout_duration, + lockout_event_name + }; +}; + #endif diff --git a/zone/expedition_request.cpp b/zone/expedition_request.cpp index e157498f2..dedff6dcc 100644 --- a/zone/expedition_request.cpp +++ b/zone/expedition_request.cpp @@ -221,14 +221,16 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& bool is_solo = (member_names.size() == 1); bool has_conflicts = false; + using col = LoadMembersForCreateRequestColumns::eLoadMembersForCreateRequestColumns; + std::vector member_lockout_conflicts; uint32_t last_character_id = 0; for (auto row = results.begin(); row != results.end(); ++row) { - auto character_id = static_cast(std::strtoul(row[0], nullptr, 10)); - std::string character_name(row[1]); - bool has_expedition = (row[2] != nullptr); // in expedition_members with another expedition + uint32_t character_id = std::strtoul(row[col::character_id], nullptr, 10); + std::string character_name = row[col::character_name]; + bool has_expedition = (row[col::character_expedition_id] != nullptr); if (character_id != last_character_id) { @@ -258,12 +260,14 @@ bool ExpeditionRequest::CheckMembersForConflicts(const std::vector& last_character_id = character_id; // compare member lockouts with leader lockouts - if (row[3] && row[4] && row[5] && row[6]) + if (row[col::lockout_uuid]) // lockout results may be null { - auto expire_time = strtoull(row[4], nullptr, 10); - auto duration = static_cast(strtoul(row[5], nullptr, 10)); + auto expire_time = strtoull(row[col::lockout_expire_time], nullptr, 10); + uint32_t duration = strtoul(row[col::lockout_duration], nullptr, 10); - ExpeditionLockoutTimer lockout{row[3], m_expedition_name, row[6], expire_time, duration}; + ExpeditionLockoutTimer lockout{ + row[col::lockout_uuid], m_expedition_name, row[col::lockout_event_name], expire_time, duration + }; if (!lockout.IsExpired()) { From b46eca4ec61f040f90d4c954c9487197c742e848 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 20 Oct 2020 23:51:30 -0400 Subject: [PATCH 138/196] Store expeditions with dz id not instance id This exposes dynamic zone ids for any future changes and will make it easier to preserve historic dz and expedition data. This also cleans up some dynamic zone creation for expedition requests When purging instances the expedition table is no longer updated since dynamic zone ids are not re-used like instance ids are Update #dz list commands to show dz id Add GetDynamicZoneID and get_expedition_by_dz_id quest apis --- common/database_instances.cpp | 4 - utils/sql/git/required/wip_expeditions.sql | 4 +- world/expedition.cpp | 8 +- world/expedition.h | 6 +- world/expedition_database.cpp | 69 +++++-------- world/expedition_database.h | 2 +- world/expedition_state.cpp | 2 +- zone/command.cpp | 21 ++-- zone/dynamiczone.cpp | 115 ++++++++------------- zone/dynamiczone.h | 18 ++-- zone/embparser_api.cpp | 20 ++++ zone/expedition.cpp | 45 ++++---- zone/expedition.h | 5 +- zone/expedition_database.cpp | 8 +- zone/expedition_database.h | 2 +- zone/lua_expedition.cpp | 6 ++ zone/lua_expedition.h | 1 + zone/lua_general.cpp | 5 + zone/perl_expedition.cpp | 14 +++ 19 files changed, 182 insertions(+), 173 deletions(-) diff --git a/common/database_instances.cpp b/common/database_instances.cpp index 7bfb548ce..2cf3923fa 100644 --- a/common/database_instances.cpp +++ b/common/database_instances.cpp @@ -496,9 +496,6 @@ void Database::DeleteInstance(uint16 instance_id) query = fmt::format("DELETE FROM dynamic_zones WHERE instance_id={}", instance_id); QueryDatabase(query); - query = fmt::format("UPDATE expeditions SET instance_id = NULL WHERE instance_id={}", instance_id); - QueryDatabase(query); - BuryCorpsesInInstance(instance_id); } @@ -589,7 +586,6 @@ void Database::PurgeExpiredInstances() QueryDatabase(fmt::format("DELETE FROM spawn_condition_values WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("UPDATE character_corpses SET is_buried = 1, instance_id = 0 WHERE instance_id IN ({})", imploded_instance_ids)); QueryDatabase(fmt::format("DELETE FROM dynamic_zones WHERE instance_id IN ({})", imploded_instance_ids)); - QueryDatabase(fmt::format("UPDATE expeditions SET instance_id = NULL WHERE instance_id IN ({})", imploded_instance_ids)); } void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 90b3286c0..64f99eecd 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -1,7 +1,7 @@ CREATE TABLE `expeditions` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` VARCHAR(36) NOT NULL, - `instance_id` INT(10) NULL DEFAULT 0, + `dynamic_zone_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, `expedition_name` VARCHAR(128) NOT NULL, `leader_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, `min_players` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, @@ -9,7 +9,7 @@ CREATE TABLE `expeditions` ( `add_replay_on_join` TINYINT(3) UNSIGNED NOT NULL DEFAULT 1, `is_locked` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), - UNIQUE INDEX `instance_id` (`instance_id`) + UNIQUE INDEX `dynamic_zone_id` (`dynamic_zone_id`) ) COLLATE='latin1_swedish_ci' ENGINE=InnoDB diff --git a/world/expedition.cpp b/world/expedition.cpp index 9a2dd58df..7b8b27ca0 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -26,12 +26,12 @@ extern ZSList zoneserver_list; -Expedition::Expedition( - uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, - uint32_t start_time, uint32_t duration +Expedition::Expedition(uint32_t expedition_id, uint32_t dz_id, uint32_t dz_instance_id, + uint32_t dz_zone_id, uint32_t start_time, uint32_t duration ) : m_expedition_id(expedition_id), - m_dz_instance_id(instance_id), + m_dz_id(dz_id), + m_dz_instance_id(dz_instance_id), m_dz_zone_id(dz_zone_id), m_start_time(std::chrono::system_clock::from_time_t(start_time)), m_duration(duration) diff --git a/world/expedition.h b/world/expedition.h index c0c0da9a9..7cd9af996 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -30,8 +30,8 @@ class Expedition { public: Expedition() = default; - Expedition(uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, - uint32_t expire_time, uint32_t duration); + Expedition(uint32_t expedition_id, uint32_t dz_id, uint32_t dz_instance_id, + uint32_t dz_zone_id, uint32_t expire_time, uint32_t duration); void AddMember(uint32_t character_id) { m_member_ids.emplace(character_id); } void RemoveMember(uint32_t character_id) { m_member_ids.erase(character_id); } @@ -43,6 +43,7 @@ public: bool IsEmpty() const { return m_member_ids.empty(); } bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } bool IsPendingDelete() const { return m_pending_delete; } + bool IsValid() const { return m_expedition_id != 0; } void SendZonesDurationUpdate(); void SendZonesExpeditionDeleted(); void SendZonesExpireWarning(uint32_t minutes_remaining); @@ -52,6 +53,7 @@ public: private: uint32_t m_expedition_id = 0; + uint32_t m_dz_id = 0; uint32_t m_dz_instance_id = 0; uint32_t m_dz_zone_id = 0; bool m_pending_delete = false; diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp index 32fa4cecb..a9bea02e5 100644 --- a/world/expedition_database.cpp +++ b/world/expedition_database.cpp @@ -28,7 +28,8 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() SELECT expeditions.id FROM expeditions - LEFT JOIN instance_list ON expeditions.instance_id = instance_list.id + LEFT JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id + LEFT JOIN instance_list ON dynamic_zones.instance_id = instance_list.id LEFT JOIN ( SELECT expedition_id, COUNT(*) member_count @@ -64,24 +65,34 @@ void ExpeditionDatabase::PurgeExpiredCharacterLockouts() database.QueryDatabase(query); } -std::vector ExpeditionDatabase::LoadExpeditions() +std::vector ExpeditionDatabase::LoadExpeditions(uint32_t select_expedition_id) { std::vector expeditions; std::string query = SQL( SELECT expeditions.id, - expeditions.instance_id, + expeditions.dynamic_zone_id, + instance_list.id, instance_list.zone, instance_list.start_time, instance_list.duration, expedition_members.character_id FROM expeditions - INNER JOIN instance_list ON expeditions.instance_id = instance_list.id + INNER JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id + INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id INNER JOIN expedition_members ON expedition_members.expedition_id = expeditions.id - ORDER BY expeditions.id; ); + if (select_expedition_id != 0) + { + query.append(fmt::format(" WHERE expeditions.id = {};", select_expedition_id)); + } + else + { + query.append(" ORDER BY expeditions.id;"); + } + auto results = database.QueryDatabase(query); if (results.Success()) { @@ -95,16 +106,17 @@ std::vector ExpeditionDatabase::LoadExpeditions() { expeditions.emplace_back( static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[3], nullptr, 10)), // start_time - static_cast(strtoul(row[4], nullptr, 10)) // duration + static_cast(strtoul(row[1], nullptr, 10)), // dz_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[3], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[4], nullptr, 10)), // start_time + static_cast(strtoul(row[5], nullptr, 10)) // duration ); } last_expedition_id = expedition_id; - uint32_t member_id = static_cast(strtoul(row[5], nullptr, 10)); + uint32_t member_id = static_cast(strtoul(row[6], nullptr, 10)); expeditions.back().AddMember(member_id); } } @@ -118,41 +130,10 @@ Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) Expedition expedition; - std::string query = fmt::format(SQL( - SELECT - expeditions.id, - expeditions.instance_id, - instance_list.zone, - instance_list.start_time, - instance_list.duration, - expedition_members.character_id - FROM expeditions - INNER JOIN instance_list ON expeditions.instance_id = instance_list.id - INNER JOIN expedition_members ON expedition_members.expedition_id = expeditions.id - WHERE expeditions.id = {}; - ), expedition_id); - - auto results = database.QueryDatabase(query); - if (results.Success()) + auto expeditions = LoadExpeditions(expedition_id); + if (!expeditions.empty()) { - bool created = false; - for (auto row = results.begin(); row != results.end(); ++row) - { - if (!created) - { - expedition = Expedition{ - static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[3], nullptr, 10)), // start_time - static_cast(strtoul(row[4], nullptr, 10)) // duration - }; - created = true; - } - - auto member_id = static_cast(strtoul(row[5], nullptr, 10)); - expedition.AddMember(member_id); - } + expedition = expeditions.front(); } return expedition; diff --git a/world/expedition_database.h b/world/expedition_database.h index ea33e823b..ff01f0107 100644 --- a/world/expedition_database.h +++ b/world/expedition_database.h @@ -29,7 +29,7 @@ class Expedition; namespace ExpeditionDatabase { void DeleteExpeditions(const std::vector& expedition_ids); - std::vector LoadExpeditions(); + std::vector LoadExpeditions(uint32_t select_expedition_id = 0); Expedition LoadExpedition(uint32_t expedition_id); void PurgeExpiredExpeditions(); void PurgeExpiredCharacterLockouts(); diff --git a/world/expedition_state.cpp b/world/expedition_state.cpp index d97c4f72e..d6a38e912 100644 --- a/world/expedition_state.cpp +++ b/world/expedition_state.cpp @@ -49,7 +49,7 @@ void ExpeditionState::AddExpedition(uint32_t expedition_id) auto expedition = ExpeditionDatabase::LoadExpedition(expedition_id); - if (expedition.GetID() == expedition_id) + if (expedition.IsValid()) { auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { return expedition.GetID() == expedition_id; diff --git a/zone/command.cpp b/zone/command.cpp index e806474ae..fd20a1ac1 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6844,8 +6844,9 @@ void command_dz(Client* c, const Seperator* sep) auto seconds = expedition.second->GetDynamicZone().GetSecondsRemaining(); c->Message(Chat::White, fmt::format( - "Expedition id: [{}] name: [{}] leader: [{}] zone: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + "expedition id: [{}] dz id: [{}] name: [{}] leader: [{}] zone: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", expedition.second->GetID(), + expedition.second->GetDynamicZoneID(), expedition.second->GetName(), expedition.second->GetLeaderName(), ZoneName(expedition.second->GetDynamicZone().GetZoneID()), @@ -6900,6 +6901,7 @@ void command_dz(Client* c, const Seperator* sep) { std::string query = SQL( SELECT + dynamic_zones.id, dynamic_zones.type, instance_list.id, instance_list.zone, @@ -6919,8 +6921,8 @@ void command_dz(Client* c, const Seperator* sep) 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 start_time = strtoul(row[5], nullptr, 10); + auto duration = strtoul(row[6], nullptr, 10); auto expire_time = std::chrono::system_clock::from_time_t(start_time + duration); auto now = std::chrono::system_clock::now(); @@ -6931,12 +6933,13 @@ void command_dz(Client* c, const Seperator* sep) if (!is_expired || strcasecmp(sep->arg[2], "all") == 0) { c->Message(Chat::White, fmt::format( - "type: [{}] zone: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", - strtoul(row[0], nullptr, 10), - strtoul(row[2], nullptr, 10), - strtoul(row[1], nullptr, 10), - strtoul(row[3], nullptr, 10), - strtoul(row[6], nullptr, 10), + "dz id: [{}] type: [{}] zone: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + strtoul(row[0], nullptr, 10), // dynamic_zone_id + strtoul(row[1], nullptr, 10), // dynamic_zone_type + strtoul(row[3], nullptr, 10), // instance_zone_id + strtoul(row[2], nullptr, 10), // instance_id + strtoul(row[4], nullptr, 10), // instance_zone_version + strtoul(row[7], nullptr, 10), // instance member_count seconds / 3600, // hours (seconds / 60) % 60, // minutes seconds % 60 // seconds diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index ea22e4b4f..5e86ddc00 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -51,36 +51,26 @@ DynamicZone::DynamicZone( } } -DynamicZone DynamicZone::LoadDzFromDatabase(uint32_t instance_id) -{ - DynamicZone dynamic_zone; - if (instance_id != 0) - { - dynamic_zone.LoadFromDatabase(instance_id); - } - return dynamic_zone; -} - std::unordered_map DynamicZone::LoadMultipleDzFromDatabase( - const std::vector& instance_ids) + const std::vector& dynamic_zone_ids) { - LogDynamicZonesDetail("Loading dynamic zone data for [{}] instances", instance_ids.size()); + LogDynamicZonesDetail("Loading dynamic zone data for [{}] instances", dynamic_zone_ids.size()); - std::string in_instance_ids_query; - for (const auto& instance_id : instance_ids) + std::string in_dynamic_zone_ids_query; + for (const auto& dynamic_zone_id : dynamic_zone_ids) { - fmt::format_to(std::back_inserter(in_instance_ids_query), "{},", instance_id); + fmt::format_to(std::back_inserter(in_dynamic_zone_ids_query), "{},", dynamic_zone_id); } std::unordered_map dynamic_zones; - if (!in_instance_ids_query.empty()) + if (!in_dynamic_zone_ids_query.empty()) { - in_instance_ids_query.pop_back(); // trailing comma + in_dynamic_zone_ids_query.pop_back(); // trailing comma std::string query = fmt::format(SQL( - {} WHERE dynamic_zones.instance_id IN ({}); - ), DynamicZoneSelectQuery(), in_instance_ids_query); + {} WHERE dynamic_zones.id IN ({}); + ), DynamicZoneSelectQuery(), in_dynamic_zone_ids_query); auto results = database.QueryDatabase(query); if (results.Success()) @@ -89,7 +79,7 @@ std::unordered_map DynamicZone::LoadMultipleDzFromDatabas { DynamicZone dz; dz.LoadDatabaseResult(row); - dynamic_zones.emplace(dz.GetInstanceID(), dz); + dynamic_zones.emplace(dz.GetID(), dz); } } } @@ -97,6 +87,23 @@ std::unordered_map DynamicZone::LoadMultipleDzFromDatabas return dynamic_zones; } +uint32_t DynamicZone::Create() +{ + if (m_id != 0) + { + return m_id; + } + + if (GetInstanceID() == 0) + { + CreateInstance(); + } + + m_id = SaveToDatabase(); + + return m_id; +} + uint32_t DynamicZone::CreateInstance() { if (m_instance_id) @@ -152,6 +159,7 @@ std::string DynamicZone::DynamicZoneSelectQuery() instance_list.start_time, instance_list.duration, instance_list.never_expires, + dynamic_zones.id, dynamic_zones.type, dynamic_zones.compass_zone_id, dynamic_zones.compass_x, @@ -181,42 +189,22 @@ void DynamicZone::LoadDatabaseResult(MySQLRequestRow& row) m_duration = std::chrono::seconds(strtoul(row[4], nullptr, 10)); m_expire_time = m_start_time + m_duration; m_never_expires = (strtoul(row[5], nullptr, 10) != 0); - m_type = static_cast(strtoul(row[6], nullptr, 10)); - m_compass.zone_id = strtoul(row[7], nullptr, 10); - m_compass.x = strtof(row[8], nullptr); - m_compass.y = strtof(row[9], nullptr); - m_compass.z = strtof(row[10], nullptr); - m_safereturn.zone_id = strtoul(row[11], nullptr, 10); - m_safereturn.x = strtof(row[12], nullptr); - m_safereturn.y = strtof(row[13], nullptr); - m_safereturn.z = strtof(row[14], nullptr); - m_safereturn.heading = strtof(row[15], nullptr); - m_zonein.x = strtof(row[16], nullptr); - m_zonein.y = strtof(row[17], nullptr); - m_zonein.z = strtof(row[18], nullptr); - m_zonein.heading = strtof(row[19], nullptr); - m_has_zonein = (strtoul(row[20], nullptr, 10) != 0); -} - -void DynamicZone::LoadFromDatabase(uint32_t instance_id) -{ - if (instance_id == 0) - { - return; - } - - LogDynamicZonesDetail("Loading dz instance [{}] from database", instance_id); - - std::string query = fmt::format(SQL( - {} WHERE dynamic_zones.instance_id = {}; - ), DynamicZoneSelectQuery(), instance_id); - - auto results = database.QueryDatabase(query); - if (results.Success() && results.RowCount() > 0) - { - auto row = results.begin(); - LoadDatabaseResult(row); - } + m_id = strtoul(row[6], nullptr, 10); + m_type = static_cast(strtoul(row[7], nullptr, 10)); + m_compass.zone_id = strtoul(row[8], nullptr, 10); + m_compass.x = strtof(row[9], nullptr); + m_compass.y = strtof(row[10], nullptr); + m_compass.z = strtof(row[11], nullptr); + m_safereturn.zone_id = strtoul(row[12], nullptr, 10); + m_safereturn.x = strtof(row[13], nullptr); + m_safereturn.y = strtof(row[14], nullptr); + m_safereturn.z = strtof(row[15], nullptr); + m_safereturn.heading = strtof(row[16], nullptr); + m_zonein.x = strtof(row[17], nullptr); + m_zonein.y = strtof(row[18], nullptr); + m_zonein.z = strtof(row[19], nullptr); + m_zonein.heading = strtof(row[20], nullptr); + m_has_zonein = (strtoul(row[21], nullptr, 10) != 0); } uint32_t DynamicZone::SaveToDatabase() @@ -363,21 +351,6 @@ void DynamicZone::SaveZoneInLocationToDatabase() } } - -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); diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index 693107840..d519539e0 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -59,37 +59,36 @@ 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(uint32_t instance_id) : m_instance_id(instance_id) {} + DynamicZone(uint32_t dz_id) : m_id(dz_id) {} DynamicZone(DynamicZoneType type) : m_type(type) {} - static DynamicZone LoadDzFromDatabase(uint32_t instance_id); static std::unordered_map LoadMultipleDzFromDatabase( - const std::vector& instance_ids); + const std::vector& dynamic_zone_ids); static void HandleWorldMessage(ServerPacket* pack); 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 GetID() const { return m_id; } + uint16_t GetInstanceID() const { return static_cast(m_instance_id); } uint32_t GetSecondsRemaining() const; - uint16_t GetZoneID() const { return static_cast(m_zone_id); }; + uint16_t GetZoneID() const { return static_cast(m_zone_id); } uint32_t GetZoneIndex() const { return (m_instance_id << 16) | (m_zone_id & 0xffff); } - uint32_t GetZoneVersion() const { return m_version; }; + uint32_t GetZoneVersion() const { return m_version; } DynamicZoneType GetType() const { return m_type; } DynamicZoneLocation GetCompassLocation() const { return m_compass; } DynamicZoneLocation GetSafeReturnLocation() const { return m_safereturn; } DynamicZoneLocation GetZoneInLocation() const { return m_zonein; } void AddCharacter(uint32_t character_id); + uint32_t Create(); uint32_t CreateInstance(); 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; } bool IsSameDz(uint32_t zone_id, uint32_t instance_id) const; - void LoadFromDatabase(uint32_t instance_id); void RemoveAllCharacters(bool enable_removal_timers = true); void RemoveCharacter(uint32_t character_id); void SaveInstanceMembersToDatabase(const std::vector& character_ids); - uint32_t SaveToDatabase(); 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); @@ -99,11 +98,12 @@ public: private: static std::string DynamicZoneSelectQuery(); void LoadDatabaseResult(MySQLRequestRow& row); - void DeleteFromDatabase(); void SaveCompassToDatabase(); void SaveSafeReturnToDatabase(); void SaveZoneInLocationToDatabase(); + uint32_t SaveToDatabase(); + uint32_t m_id = 0; uint32_t m_zone_id = 0; uint32_t m_instance_id = 0; uint32_t m_version = 0; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 71affe083..51414aef7 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -6101,6 +6101,25 @@ XS(XS__get_expedition_by_char_id) { XSRETURN(1); } +XS(XS__get_expedition_by_dz_id); +XS(XS__get_expedition_by_dz_id) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: quest::get_expedition_by_dz_id(uint32 dynamic_zone_id)"); + } + + uint32 dz_id = (int)SvUV(ST(0)); + + Expedition* RETVAL = Expedition::FindCachedExpeditionByDynamicZoneID(dz_id); + + ST(0) = sv_newmortal(); + if (RETVAL) { + sv_setref_pv(ST(0), "Expedition", (void*)RETVAL); + } + + XSRETURN(1); +} + XS(XS__get_expedition_by_zone_instance); XS(XS__get_expedition_by_zone_instance) { dXSARGS; @@ -6507,6 +6526,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "getcurrencyid"), XS__getcurrencyid, file); newXS(strcpy(buf, "get_expedition"), XS__get_expedition, file); newXS(strcpy(buf, "get_expedition_by_char_id"), XS__get_expedition_by_char_id, file); + newXS(strcpy(buf, "get_expedition_by_dz_id"), XS__get_expedition_by_dz_id, file); newXS(strcpy(buf, "get_expedition_by_zone_instance"), XS__get_expedition_by_zone_instance, file); newXS(strcpy(buf, "get_expedition_lockout_by_char_id"), XS__get_expedition_lockout_by_char_id, file); newXS(strcpy(buf, "get_expedition_lockouts_by_char_id"), XS__get_expedition_lockouts_by_char_id, file); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 4d8966c76..7f5e3d92c 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -78,12 +78,8 @@ Expedition* Expedition::TryCreate( return nullptr; } - if (dynamiczone.GetInstanceID() == 0) - { - dynamiczone.CreateInstance(); - } - - if (dynamiczone.GetInstanceID() == 0) + auto dynamic_zone_id = dynamiczone.Create(); + if (dynamic_zone_id == 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 @@ -97,7 +93,7 @@ Expedition* Expedition::TryCreate( // unique expedition ids are created from database via auto-increment column auto expedition_id = ExpeditionDatabase::InsertExpedition( expedition_uuid, - dynamiczone.GetInstanceID(), + dynamiczone.GetID(), request.GetExpeditionName(), request.GetLeaderID(), request.GetMinPlayers(), @@ -106,8 +102,6 @@ Expedition* Expedition::TryCreate( if (expedition_id) { - dynamiczone.SaveToDatabase(); - auto expedition = std::unique_ptr(new Expedition( expedition_id, expedition_uuid, @@ -158,7 +152,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) } std::vector expedition_ids; - std::vector instance_ids; + std::vector dynamic_zone_ids;; std::vector> expedition_character_ids; using col = LoadExpeditionColumns::eLoadExpeditionColumns; @@ -174,16 +168,14 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) expedition_ids.emplace_back(expedition_id); uint32_t leader_id = strtoul(row[col::leader_id], nullptr, 10); - uint32_t instance_id = row[col::instance_id] ? strtoul(row[col::instance_id], nullptr, 10) : 0; - if (instance_id) // can be null from fk constraint - { - instance_ids.emplace_back(instance_id); - } + uint32_t dynamic_zone_id = strtoul(row[col::dz_id], nullptr, 10); + + dynamic_zone_ids.emplace_back(dynamic_zone_id); std::unique_ptr expedition = std::unique_ptr(new Expedition( expedition_id, row[col::uuid], // expedition uuid - DynamicZone{ instance_id }, + DynamicZone{ dynamic_zone_id }, row[col::expedition_name], // expedition name ExpeditionMember{ leader_id, row[col::leader_name] }, // expedition leader id, name strtoul(row[col::min_players], nullptr, 10), // min_players @@ -216,7 +208,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) Expedition::SendWorldGetOnlineMembers(expedition_character_ids); // bulk load dynamic zone data and expedition lockouts for cached expeditions - auto dynamic_zones = DynamicZone::LoadMultipleDzFromDatabase(instance_ids); + auto dynamic_zones = DynamicZone::LoadMultipleDzFromDatabase(dynamic_zone_ids); auto expedition_lockouts = ExpeditionDatabase::LoadMultipleExpeditionLockouts(expedition_ids); for (const auto& expedition_id : expedition_ids) @@ -224,7 +216,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); if (expedition) { - auto dz_iter = dynamic_zones.find(expedition->GetInstanceID()); + auto dz_iter = dynamic_zones.find(expedition->GetDynamicZoneID()); if (dz_iter != dynamic_zones.end()) { expedition->m_dynamiczone = dz_iter->second; @@ -236,7 +228,7 @@ void Expedition::CacheExpeditions(MySQLRequestResult& results) expedition->m_lockouts = lockout_iter->second; } - // send member updates now that all data is loaded for the cached expedition(s) + // send member updates now that all data is loaded for the cached expedition expedition->SendUpdatesToZoneMembers(); } } @@ -340,6 +332,21 @@ Expedition* Expedition::FindCachedExpeditionByCharacterName(const std::string& c return nullptr; } +Expedition* Expedition::FindCachedExpeditionByDynamicZoneID(uint32_t dz_id) +{ + if (zone && dz_id != 0) + { + for (const auto& cached_expedition : zone->expedition_cache) + { + if (cached_expedition.second->GetDynamicZone().GetID() == dz_id) + { + return cached_expedition.second.get(); + } + } + } + return nullptr; +} + Expedition* Expedition::FindCachedExpeditionByID(uint32_t expedition_id) { if (zone && expedition_id) diff --git a/zone/expedition.h b/zone/expedition.h index 52595da40..59298093b 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -74,8 +74,7 @@ class Expedition { public: Expedition() = delete; - Expedition( - uint32_t id, const std::string& uuid, const DynamicZone& dz, const std::string& expedition_name, + Expedition(uint32_t id, const std::string& uuid, const DynamicZone& dz, const std::string& expedition_name, const ExpeditionMember& leader, uint32_t min_players, uint32_t max_players); static Expedition* TryCreate(Client* requester, DynamicZone& dynamiczone, ExpeditionRequest& request); @@ -84,6 +83,7 @@ public: static bool CacheAllFromDatabase(); static Expedition* FindCachedExpeditionByCharacterID(uint32_t character_id); static Expedition* FindCachedExpeditionByCharacterName(const std::string& char_name); + static Expedition* FindCachedExpeditionByDynamicZoneID(uint32_t dz_id); static Expedition* FindCachedExpeditionByID(uint32_t expedition_id); static Expedition* FindCachedExpeditionByZoneInstance(uint32_t zone_id, uint32_t instance_id); static std::vector GetExpeditionLockoutsByCharacterID(uint32_t character_id); @@ -102,6 +102,7 @@ public: const std::string& expedition_name = {}, const std::string& event_name = {}); static void AddLockoutClients(const ExpeditionLockoutTimer& lockout, uint32_t exclude_id = 0); + uint32_t GetDynamicZoneID() const { return m_dynamiczone.GetID(); } uint32_t GetID() const { return m_id; } uint16_t GetInstanceID() const { return m_dynamiczone.GetInstanceID(); } uint32_t GetLeaderID() const { return m_leader.char_id; } diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 41dcc8cad..1977b36bd 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -27,7 +27,7 @@ #include uint32_t ExpeditionDatabase::InsertExpedition( - const std::string& uuid, uint32_t instance_id, const std::string& expedition_name, + const std::string& uuid, uint32_t dz_id, const std::string& expedition_name, uint32_t leader_id, uint32_t min_players, uint32_t max_players) { LogExpeditionsDetail( @@ -36,10 +36,10 @@ uint32_t ExpeditionDatabase::InsertExpedition( std::string query = fmt::format(SQL( INSERT INTO expeditions - (uuid, instance_id, expedition_name, leader_id, min_players, max_players) + (uuid, dynamic_zone_id, expedition_name, leader_id, min_players, max_players) VALUES ('{}', {}, '{}', {}, {}, {}); - ), uuid, instance_id, EscapeString(expedition_name), leader_id, min_players, max_players); + ), uuid, dz_id, EscapeString(expedition_name), leader_id, min_players, max_players); auto results = database.QueryDatabase(query); if (!results.Success()) @@ -57,7 +57,7 @@ std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() SELECT expeditions.id, expeditions.uuid, - expeditions.instance_id, + expeditions.dynamic_zone_id, expeditions.expedition_name, expeditions.leader_id, expeditions.min_players, diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 4335f29b4..519a331f4 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -84,7 +84,7 @@ namespace LoadExpeditionColumns { id = 0, uuid, - instance_id, + dz_id, expedition_name, leader_id, min_players, diff --git a/zone/lua_expedition.cpp b/zone/lua_expedition.cpp index d5c1d4490..28f783dd1 100644 --- a/zone/lua_expedition.cpp +++ b/zone/lua_expedition.cpp @@ -57,6 +57,11 @@ void Lua_Expedition::AddReplayLockoutDuration(int seconds, bool members_only) { self->AddReplayLockoutDuration(seconds, members_only); } +uint32_t Lua_Expedition::GetDynamicZoneID() { + Lua_Safe_Call_Int(); + return self->GetDynamicZoneID(); +} + uint32_t Lua_Expedition::GetID() { Lua_Safe_Call_Int(); return self->GetID(); @@ -248,6 +253,7 @@ luabind::scope lua_register_expedition() { .def("AddReplayLockout", (void(Lua_Expedition::*)(uint32_t))&Lua_Expedition::AddReplayLockout) .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int))&Lua_Expedition::AddReplayLockoutDuration) .def("AddReplayLockoutDuration", (void(Lua_Expedition::*)(int, bool))&Lua_Expedition::AddReplayLockoutDuration) + .def("GetDynamicZoneID", &Lua_Expedition::GetDynamicZoneID) .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) diff --git a/zone/lua_expedition.h b/zone/lua_expedition.h index d904d1680..6409d574a 100644 --- a/zone/lua_expedition.h +++ b/zone/lua_expedition.h @@ -59,6 +59,7 @@ public: void AddReplayLockout(uint32_t seconds); void AddReplayLockoutDuration(int seconds); void AddReplayLockoutDuration(int seconds, bool members_only); + uint32_t GetDynamicZoneID(); uint32_t GetID(); int GetInstanceID(); std::string GetLeaderName(); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index f12d3a3d6..34cc30b29 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -2192,6 +2192,10 @@ Lua_Expedition lua_get_expedition_by_char_id(uint32 char_id) { return Expedition::FindCachedExpeditionByCharacterID(char_id); } +Lua_Expedition lua_get_expedition_by_dz_id(uint32 dz_id) { + return Expedition::FindCachedExpeditionByDynamicZoneID(dz_id); +} + Lua_Expedition lua_get_expedition_by_zone_instance(uint32 zone_id, uint32 instance_id) { return Expedition::FindCachedExpeditionByZoneInstance(zone_id, instance_id); } @@ -2889,6 +2893,7 @@ luabind::scope lua_register_general() { luabind::def("get_expedition", &lua_get_expedition), luabind::def("get_expedition_by_char_id", &lua_get_expedition_by_char_id), + luabind::def("get_expedition_by_dz_id", &lua_get_expedition_by_dz_id), luabind::def("get_expedition_by_zone_instance", &lua_get_expedition_by_zone_instance), luabind::def("get_expedition_lockout_by_char_id", &lua_get_expedition_lockout_by_char_id), luabind::def("get_expedition_lockouts_by_char_id", (luabind::object(*)(lua_State*, uint32))&lua_get_expedition_lockouts_by_char_id), diff --git a/zone/perl_expedition.cpp b/zone/perl_expedition.cpp index e7e0f3bb2..a3d7fc217 100644 --- a/zone/perl_expedition.cpp +++ b/zone/perl_expedition.cpp @@ -132,6 +132,19 @@ XS(XS_Expedition_AddReplayLockoutDuration) { XSRETURN_EMPTY; } +XS(XS_Expedition_GetDynamicZoneID); +XS(XS_Expedition_GetDynamicZoneID) { + dXSARGS; + if (items != 1) { + Perl_croak(aTHX_ "Usage: Expedition::GetDynamicZoneID(THIS)"); + } + + Expedition* THIS = nullptr; + VALIDATE_THIS_IS_EXPEDITION; + + XSRETURN_UV(THIS->GetDynamicZoneID()); +} + XS(XS_Expedition_GetID); XS(XS_Expedition_GetID) { dXSARGS; @@ -623,6 +636,7 @@ XS(boot_Expedition) { newXSproto(strcpy(buf, "AddLockoutDuration"), XS_Expedition_AddLockoutDuration, file, "$$$;$"); newXSproto(strcpy(buf, "AddReplayLockout"), XS_Expedition_AddReplayLockout, file, "$$"); newXSproto(strcpy(buf, "AddReplayLockoutDuration"), XS_Expedition_AddReplayLockoutDuration, file, "$$;$"); + newXSproto(strcpy(buf, "GetDynamicZoneID"), XS_Expedition_GetDynamicZoneID, file, "$"); newXSproto(strcpy(buf, "GetID"), XS_Expedition_GetID, file, "$"); newXSproto(strcpy(buf, "GetInstanceID"), XS_Expedition_GetInstanceID, file, "$"); newXSproto(strcpy(buf, "GetLeaderName"), XS_Expedition_GetLeaderName, file, "$"); From 074b7096df795e9dd6706686f748dca3986f831d Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 21 Oct 2020 23:28:43 -0400 Subject: [PATCH 139/196] Use fmt join for simple query strings --- world/expedition_database.cpp | 20 ++++---------------- zone/dynamiczone.cpp | 8 +------- zone/expedition_database.cpp | 8 +------- 3 files changed, 6 insertions(+), 30 deletions(-) diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp index a9bea02e5..9dde517e9 100644 --- a/world/expedition_database.cpp +++ b/world/expedition_database.cpp @@ -143,29 +143,17 @@ void ExpeditionDatabase::DeleteExpeditions(const std::vector& expediti { LogExpeditionsDetail("Deleting [{}] expedition(s)", expedition_ids.size()); - std::string expedition_ids_query; - for (const auto& expedition_id : expedition_ids) - { - fmt::format_to(std::back_inserter(expedition_ids_query), "{},", expedition_id); - } + std::string expedition_ids_query = fmt::format("{}", fmt::join(expedition_ids, ",")); if (!expedition_ids_query.empty()) { - expedition_ids_query.pop_back(); // trailing comma - - std::string query = fmt::format( - "DELETE FROM expeditions WHERE id IN ({});", expedition_ids_query - ); + auto query = fmt::format("DELETE FROM expeditions WHERE id IN ({});", expedition_ids_query); database.QueryDatabase(query); - query = fmt::format( - "DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query - ); + query = fmt::format("DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query); database.QueryDatabase(query); - query = fmt::format( - "DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query - ); + query = fmt::format("DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query); database.QueryDatabase(query); } } diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 5e86ddc00..9eeb7596d 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -56,18 +56,12 @@ std::unordered_map DynamicZone::LoadMultipleDzFromDatabas { LogDynamicZonesDetail("Loading dynamic zone data for [{}] instances", dynamic_zone_ids.size()); - std::string in_dynamic_zone_ids_query; - for (const auto& dynamic_zone_id : dynamic_zone_ids) - { - fmt::format_to(std::back_inserter(in_dynamic_zone_ids_query), "{},", dynamic_zone_id); - } + std::string in_dynamic_zone_ids_query = fmt::format("{}", fmt::join(dynamic_zone_ids, ",")); std::unordered_map dynamic_zones; if (!in_dynamic_zone_ids_query.empty()) { - in_dynamic_zone_ids_query.pop_back(); // trailing comma - std::string query = fmt::format(SQL( {} WHERE dynamic_zones.id IN ({}); ), DynamicZoneSelectQuery(), in_dynamic_zone_ids_query); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 1977b36bd..cb845cc50 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -176,19 +176,13 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts( { LogExpeditionsDetail("Loading internal lockouts for [{}] expeditions", expedition_ids.size()); - std::string in_expedition_ids_query; - for (const auto& expedition_id : expedition_ids) - { - fmt::format_to(std::back_inserter(in_expedition_ids_query), "{},", expedition_id); - } + std::string in_expedition_ids_query = fmt::format("{}", fmt::join(expedition_ids, ",")); // these are loaded into the same container type expeditions use to store lockouts std::unordered_map> lockouts; if (!in_expedition_ids_query.empty()) { - in_expedition_ids_query.pop_back(); // trailing comma - std::string query = fmt::format(SQL( SELECT expedition_lockouts.expedition_id, From f506ce9d58e8f297471ba8bcbe1f77865bcc4a47 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 23 Oct 2020 20:49:30 -0400 Subject: [PATCH 140/196] Reduce packet updates when expedition member added This fixes unnecessary packets and compass updates being sent for invited members and better matches live's packet update pattern This also fixes duplicate updates being sent to the added member Live only sends a MemberListName update for members added through a swap (player count doesn't change). For members added through an invite it sends expedition info and a full member list update A full member list update is sent for both cases in this patch. This is because MemberListName currently always adds members with status "unknown". This is either due to unknown packet fields or a change in future clients --- zone/expedition.cpp | 39 +++++++++++++++++++++++++++++++++------ zone/expedition.h | 3 ++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 7f5e3d92c..9263e47e0 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -607,7 +607,7 @@ void Expedition::SwapMember(Client* add_client, const std::string& remove_char_n m_dynamiczone.AddCharacter(add_client->CharacterID()); ProcessMemberRemoved(member.name, member.char_id); - ProcessMemberAdded(add_client->GetName(), add_client->CharacterID()); + ProcessMemberAdded(add_client->GetName(), add_client->CharacterID(), true); SendWorldMemberSwapped(member.name, member.char_id, add_client->GetName(), add_client->CharacterID()); if (!m_members.empty() && member.char_id == m_leader.char_id) @@ -1223,7 +1223,7 @@ void Expedition::ProcessMakeLeader( } } -void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added_char_id) +void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added_char_id, bool was_swapped) { // adds the member to this expedition and notifies both leader and new member Client* leader_client = entity_list.GetClientByCharID(m_leader.char_id); @@ -1232,6 +1232,8 @@ void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added leader_client->MessageString(Chat::Yellow, EXPEDITION_MEMBER_ADDED, char_name.c_str(), m_expedition_name.c_str()); } + AddInternalMember(char_name, added_char_id, ExpeditionMemberStatus::Online); + Client* member_client = entity_list.GetClientByCharID(added_char_id); if (member_client) { @@ -1241,9 +1243,7 @@ void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added member_client->MessageString(Chat::Yellow, EXPEDITION_MEMBER_ADDED, char_name.c_str(), m_expedition_name.c_str()); } - AddInternalMember(char_name, added_char_id, ExpeditionMemberStatus::Online); - - SendUpdatesToZoneMembers(); // live sends full update when member added + SendNewMemberAddedToZoneMembers(char_name, was_swapped); } void Expedition::ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id) @@ -1402,6 +1402,33 @@ void Expedition::AddLockoutClients( } } +void Expedition::SendNewMemberAddedToZoneMembers(const std::string& added_name, bool was_swapped) +{ + // live only sends a MemberListName update when members are added from a swap + // otherwise it updates info (for player count change) and the full member list + auto outapp_info = was_swapped ? nullptr : CreateInfoPacket(false); + + // always send full list, MemberListName adds as "unknown" status (missing packet fields or client change) + //auto outapp_members = was_swapped ? CreateMemberListNamePacket(added_name, false) : CreateMemberListPacket(false); + auto outapp_members = CreateMemberListPacket(false); + + for (const auto& member : m_members) + { + if (member.name != added_name) // new member already updated + { + Client* member_client = entity_list.GetClientByCharID(member.char_id); + if (member_client) + { + if (outapp_info) + { + member_client->QueuePacket(outapp_info.get()); + } + member_client->QueuePacket(outapp_members.get()); + } + } + } +} + void Expedition::SendUpdatesToZoneMembers(bool clear, bool message_on_clear) { if (!m_members.empty()) @@ -1937,7 +1964,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) if (expedition) { expedition->ProcessMemberRemoved(buf->remove_char_name, buf->remove_char_id); - expedition->ProcessMemberAdded(buf->add_char_name, buf->add_char_id); + expedition->ProcessMemberAdded(buf->add_char_name, buf->add_char_id, true); } } break; diff --git a/zone/expedition.h b/zone/expedition.h index 59298093b..0e7e21b63 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -181,7 +181,7 @@ private: void ProcessLockoutDuration(const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); void ProcessMakeLeader(Client* old_leader, Client* new_leader, const std::string& new_leader_name, bool is_online); - void ProcessMemberAdded(const std::string& added_char_name, uint32_t added_char_id); + void ProcessMemberAdded(const std::string& added_char_name, uint32_t added_char_id, bool was_swapped = false); void ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id); void SaveLockouts(ExpeditionRequest& request); void SaveMembers(ExpeditionRequest& request); @@ -190,6 +190,7 @@ private: void SendLeaderMessage(Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& args = {}); void SendMembersExpireWarning(uint32_t minutes); + void SendNewMemberAddedToZoneMembers(const std::string& added_name, bool was_swapped); void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); void SendWorldExpeditionUpdate(uint16_t server_opcode); From 87085648891deaf522ad0c13f57b54ba7ae23397 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 25 Oct 2020 22:17:12 -0400 Subject: [PATCH 141/196] Don't send info packet when new member added It isn't necessary to send this packet to current members when a new expedition member is added. The member list packet changes the current player count on the window --- zone/expedition.cpp | 24 +++++++++--------------- zone/expedition.h | 4 ++-- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 9263e47e0..148c8f335 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -607,7 +607,7 @@ void Expedition::SwapMember(Client* add_client, const std::string& remove_char_n m_dynamiczone.AddCharacter(add_client->CharacterID()); ProcessMemberRemoved(member.name, member.char_id); - ProcessMemberAdded(add_client->GetName(), add_client->CharacterID(), true); + ProcessMemberAdded(add_client->GetName(), add_client->CharacterID()); SendWorldMemberSwapped(member.name, member.char_id, add_client->GetName(), add_client->CharacterID()); if (!m_members.empty() && member.char_id == m_leader.char_id) @@ -1223,7 +1223,7 @@ void Expedition::ProcessMakeLeader( } } -void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added_char_id, bool was_swapped) +void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added_char_id) { // adds the member to this expedition and notifies both leader and new member Client* leader_client = entity_list.GetClientByCharID(m_leader.char_id); @@ -1243,7 +1243,7 @@ void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added member_client->MessageString(Chat::Yellow, EXPEDITION_MEMBER_ADDED, char_name.c_str(), m_expedition_name.c_str()); } - SendNewMemberAddedToZoneMembers(char_name, was_swapped); + SendNewMemberAddedToZoneMembers(char_name); } void Expedition::ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id) @@ -1402,14 +1402,12 @@ void Expedition::AddLockoutClients( } } -void Expedition::SendNewMemberAddedToZoneMembers(const std::string& added_name, bool was_swapped) +void Expedition::SendNewMemberAddedToZoneMembers(const std::string& added_name) { - // live only sends a MemberListName update when members are added from a swap - // otherwise it updates info (for player count change) and the full member list - auto outapp_info = was_swapped ? nullptr : CreateInfoPacket(false); - - // always send full list, MemberListName adds as "unknown" status (missing packet fields or client change) - //auto outapp_members = was_swapped ? CreateMemberListNamePacket(added_name, false) : CreateMemberListPacket(false); + // live only sends MemberListName when members are added from a swap, otherwise + // it sends expedition info (unnecessary) and the full member list + // we send a full member list update for both cases since MemberListName adds as + // "unknown" status (either due to unknown packet fields or future client change) auto outapp_members = CreateMemberListPacket(false); for (const auto& member : m_members) @@ -1419,10 +1417,6 @@ void Expedition::SendNewMemberAddedToZoneMembers(const std::string& added_name, Client* member_client = entity_list.GetClientByCharID(member.char_id); if (member_client) { - if (outapp_info) - { - member_client->QueuePacket(outapp_info.get()); - } member_client->QueuePacket(outapp_members.get()); } } @@ -1964,7 +1958,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) if (expedition) { expedition->ProcessMemberRemoved(buf->remove_char_name, buf->remove_char_id); - expedition->ProcessMemberAdded(buf->add_char_name, buf->add_char_id, true); + expedition->ProcessMemberAdded(buf->add_char_name, buf->add_char_id); } } break; diff --git a/zone/expedition.h b/zone/expedition.h index 0e7e21b63..d4bb1b43f 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -181,7 +181,7 @@ private: void ProcessLockoutDuration(const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); void ProcessMakeLeader(Client* old_leader, Client* new_leader, const std::string& new_leader_name, bool is_online); - void ProcessMemberAdded(const std::string& added_char_name, uint32_t added_char_id, bool was_swapped = false); + void ProcessMemberAdded(const std::string& added_char_name, uint32_t added_char_id); void ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id); void SaveLockouts(ExpeditionRequest& request); void SaveMembers(ExpeditionRequest& request); @@ -190,7 +190,7 @@ private: void SendLeaderMessage(Client* leader_client, uint16_t chat_type, uint32_t string_id, const std::initializer_list& args = {}); void SendMembersExpireWarning(uint32_t minutes); - void SendNewMemberAddedToZoneMembers(const std::string& added_name, bool was_swapped); + void SendNewMemberAddedToZoneMembers(const std::string& added_name); void SendUpdatesToZoneMembers(bool clear = false, bool message_on_clear = true); void SendWorldDzLocationUpdate(uint16_t server_opcode, const DynamicZoneLocation& location); void SendWorldExpeditionUpdate(uint16_t server_opcode); From cc5dd4cd8278ea78f624f382d42f9369928a5082 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 26 Oct 2020 20:21:43 -0400 Subject: [PATCH 142/196] Add missing expedition perl api declaration --- zone/embparser_api.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 51414aef7..7a235c965 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -6389,6 +6389,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "activespeakactivity"), XS__activespeakactivity, file); newXS(strcpy(buf, "activespeaktask"), XS__activespeaktask, file); newXS(strcpy(buf, "activetasksinset"), XS__activetasksinset, file); + newXS(strcpy(buf, "add_expedition_lockout_all_clients"), XS__add_expedition_lockout_all_clients, file); newXS(strcpy(buf, "add_expedition_lockout_by_char_id"), XS__add_expedition_lockout_by_char_id, file); newXS(strcpy(buf, "addldonloss"), XS__addldonpoints, file); newXS(strcpy(buf, "addldonpoints"), XS__addldonpoints, file); From c09ada67d6590143f6e87f4fb351adff0c100cb8 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 26 Oct 2020 21:16:14 -0400 Subject: [PATCH 143/196] Add expedition lock message constants to perl --- zone/perl_expedition.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zone/perl_expedition.cpp b/zone/perl_expedition.cpp index a3d7fc217..40fe1bad1 100644 --- a/zone/perl_expedition.cpp +++ b/zone/perl_expedition.cpp @@ -664,6 +664,12 @@ XS(boot_Expedition) { newXSproto(strcpy(buf, "SetSecondsRemaining"), XS_Expedition_SetSecondsRemaining, file, "$$"); newXSproto(strcpy(buf, "SetZoneInLocation"), XS_Expedition_SetZoneInLocation, file, "$$$$$"); newXSproto(strcpy(buf, "UpdateLockoutDuration"), XS_Expedition_UpdateLockoutDuration, file, "$$$;$"); + + HV* stash = gv_stashpvs("ExpeditionLockMessage", GV_ADD); + newCONSTSUB(stash, "None", newSViv(static_cast(ExpeditionLockMessage::None))); + newCONSTSUB(stash, "Close", newSViv(static_cast(ExpeditionLockMessage::Close))); + newCONSTSUB(stash, "Begin", newSViv(static_cast(ExpeditionLockMessage::Begin))); + XSRETURN_YES; } From c0b8bfde03d095735615e51613d5544f49bca12f Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 31 Oct 2020 18:50:32 -0400 Subject: [PATCH 144/196] Fix invalid return in perl expedition api This was returning an invalid (garbage) hash reference for empty results when filtering on expedition name --- zone/embparser_api.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 7a235c965..7a10cc129 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -6225,9 +6225,9 @@ XS(XS__get_expedition_lockouts_by_char_id) { SV* rv = &PL_sv_undef; - if (!expedition_name.empty() && hash_ref) + if (!expedition_name.empty()) { - rv = sv_2mortal(hash_ref); // ref that owns event hash + rv = hash_ref ? sv_2mortal(hash_ref) : &PL_sv_undef; // ref that owns event hash for expedition } else { From 4ae99048227d7143b9a21bf827ad018da41ce29e Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 31 Oct 2020 18:54:49 -0400 Subject: [PATCH 145/196] Use strlen for perl hash key size --- zone/embparser_api.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 7a10cc129..17e20b9ec 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -6161,8 +6161,8 @@ XS(XS__get_expedition_lockout_by_char_id) { if (it != lockouts.end()) { - hv_store(hash, "remaining", 9, newSVuv(it->GetSecondsRemaining()), 0); - hv_store(hash, "uuid", 4, newSVpv(it->GetExpeditionUUID().c_str(), 0), 0); + hv_store(hash, "remaining", strlen("remaining"), newSVuv(it->GetSecondsRemaining()), 0); + hv_store(hash, "uuid", strlen("uuid"), newSVpv(it->GetExpeditionUUID().c_str(), 0), 0); } ST(0) = sv_2mortal(newRV((SV*)hash)); // hash refcnt: 2 (-1 mortal), reference: 1 (-1 mortal) @@ -6214,8 +6214,8 @@ XS(XS__get_expedition_lockouts_by_char_id) { if (entry && SvROK(*entry) && SvTYPE(SvRV(*entry)) == SVt_PVHV) // is ref to hash type { HV* details_hash = newHV(); // refcnt +1, reference will take ownership - hv_store(details_hash, "remaining", 9, newSVuv(lockout.GetSecondsRemaining()), 0); - hv_store(details_hash, "uuid", 4, newSVpv(lockout.GetExpeditionUUID().c_str(), 0), 0); + hv_store(details_hash, "remaining", strlen("remaining"), newSVuv(lockout.GetSecondsRemaining()), 0); + hv_store(details_hash, "uuid", strlen("uuid"), newSVpv(lockout.GetExpeditionUUID().c_str(), 0), 0); HV* event_hash = (HV*)SvRV(*entry); hv_store(event_hash, lockout.GetEventName().c_str(), event_len, From 6e9f920931e673aeacf8d697071607cd5f9d568a Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 1 Nov 2020 15:01:08 -0500 Subject: [PATCH 146/196] Add saylinks to #dz list output --- zone/command.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index fd20a1ac1..811a2c2ba 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6841,14 +6841,20 @@ void command_dz(Client* c, const Seperator* sep) c->Message(Chat::White, fmt::format("Total Active Expeditions: [{}]", zone->expedition_cache.size()).c_str()); for (const auto& expedition : zone->expedition_cache) { + auto leader_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format( + "#goto {}", expedition.second->GetLeaderName()), false, expedition.second->GetLeaderName()); + auto zone_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format( + "#zoneinstance {}", expedition.second->GetInstanceID()), false, "zone"); + auto seconds = expedition.second->GetDynamicZone().GetSecondsRemaining(); c->Message(Chat::White, fmt::format( - "expedition id: [{}] dz id: [{}] name: [{}] leader: [{}] zone: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + "expedition id: [{}] dz id: [{}] name: [{}] leader: [{}] {}: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", expedition.second->GetID(), expedition.second->GetDynamicZoneID(), expedition.second->GetName(), - expedition.second->GetLeaderName(), + leader_saylink, + zone_saylink, ZoneName(expedition.second->GetDynamicZone().GetZoneID()), expedition.second->GetDynamicZone().GetZoneID(), expedition.second->GetInstanceID(), @@ -6932,12 +6938,17 @@ void command_dz(Client* c, const Seperator* sep) bool is_expired = now > expire_time; if (!is_expired || strcasecmp(sep->arg[2], "all") == 0) { + uint32_t instance_id = strtoul(row[2], nullptr, 10); + auto zone_saylink = is_expired ? "zone" : EQ::SayLinkEngine::GenerateQuestSaylink( + fmt::format("#zoneinstance {}", instance_id), false, "zone"); + c->Message(Chat::White, fmt::format( - "dz id: [{}] type: [{}] zone: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", + "dz id: [{}] type: [{}] {}: [{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", strtoul(row[0], nullptr, 10), // dynamic_zone_id strtoul(row[1], nullptr, 10), // dynamic_zone_type + zone_saylink, strtoul(row[3], nullptr, 10), // instance_zone_id - strtoul(row[2], nullptr, 10), // instance_id + instance_id, // instance_id strtoul(row[4], nullptr, 10), // instance_zone_version strtoul(row[7], nullptr, 10), // instance member_count seconds / 3600, // hours From 955cbeb826ba32bea5c439a40a74b556dfa4e00d Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 1 Nov 2020 19:48:36 -0500 Subject: [PATCH 147/196] Revert "Don't set member offline before linkdead" This reverts commit 8d27602aa0b3f766760f0952389c988f1b150de6. This doesn't work because a linkdead leader coming back online will toggle to offline state anyway --- zone/client.h | 2 +- zone/client_process.cpp | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/zone/client.h b/zone/client.h index 3e19b6a92..572170013 100644 --- a/zone/client.h +++ b/zone/client.h @@ -739,7 +739,7 @@ public: bool TGB() const { return tgb; } - void OnDisconnect(bool hard_disconnect, bool linkdead = false); + void OnDisconnect(bool hard_disconnect); uint16 GetSkillPoints() { return m_pp.points;} void SetSkillPoints(int inp) { m_pp.points = inp;} diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 929b4006f..15dfa160f 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -559,20 +559,17 @@ bool Client::Process() { } if (client_state != CLIENT_LINKDEAD && !eqs->CheckState(ESTABLISHED)) { + OnDisconnect(true); LogInfo("Client linkdead: {}", name); if (Admin() > 100) { - OnDisconnect(true); if (GetMerc()) { GetMerc()->Save(); GetMerc()->Depop(); } return false; } - - OnDisconnect(true, true); - - if (!linkdead_timer.Enabled()) { + else if (!linkdead_timer.Enabled()) { linkdead_timer.Start(RuleI(Zone, ClientLinkdeadMS)); client_state = CLIENT_LINKDEAD; AI_Start(CLIENT_LD_TIMEOUT); @@ -691,7 +688,7 @@ bool Client::Process() { } /* Just a set of actions preformed all over in Client::Process */ -void Client::OnDisconnect(bool hard_disconnect, bool linkdead) { +void Client::OnDisconnect(bool hard_disconnect) { if(hard_disconnect) { LeaveGroup(); @@ -715,7 +712,7 @@ void Client::OnDisconnect(bool hard_disconnect, bool linkdead) { } Expedition* expedition = GetExpedition(); - if (expedition && !bZoning && !linkdead && !linkdead_timer.Enabled()) + if (expedition && !bZoning) { expedition->SetMemberStatus(this, ExpeditionMemberStatus::Offline); } From 6acfc41778fb8bd6d20c974460de02e003d4ddc5 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 1 Nov 2020 15:01:43 -0500 Subject: [PATCH 148/196] Let world handle expedition leader changes This should eliminate race conditions caused by zones trying to set a leader when members in different zones quit at the same time Zone still detects when leader goes offline to trigger a change since it's easier than having world process expedition member status updates and perform expedition lookups --- common/servertalk.h | 6 +++ world/expedition.cpp | 71 +++++++++++++++++++++++++++- world/expedition.h | 15 ++++-- world/expedition_database.cpp | 17 ++++++- world/expedition_database.h | 1 + world/expedition_message.cpp | 24 +++++++++- world/expedition_message.h | 1 + world/expedition_state.cpp | 44 ++++++++---------- world/expedition_state.h | 1 + world/zoneserver.cpp | 2 +- zone/command.cpp | 9 +--- zone/expedition.cpp | 87 ++++++++--------------------------- zone/expedition.h | 7 +-- zone/expedition_database.cpp | 11 ----- zone/expedition_database.h | 1 - 15 files changed, 170 insertions(+), 127 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index cb541bd6c..ef7a3ecab 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -164,6 +164,7 @@ #define ServerOP_ExpeditionLockoutDuration 0x0414 #define ServerOP_ExpeditionSecondsRemaining 0x0415 #define ServerOP_ExpeditionExpireWarning 0x0416 +#define ServerOP_ExpeditionChooseNewLeader 0x0417 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 @@ -2001,6 +2002,11 @@ struct ServerExpeditionID_Struct { uint32 sender_instance_id; }; +struct ServerExpeditionLeaderID_Struct { + uint32 expedition_id; + uint32 leader_id; +}; + struct ServerExpeditionMemberChange_Struct { uint32 expedition_id; uint32 sender_zone_id; diff --git a/world/expedition.cpp b/world/expedition.cpp index 7b8b27ca0..c1bd5a35a 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -20,26 +20,83 @@ #include "expedition.h" #include "expedition_database.h" +#include "cliententry.h" +#include "clientlist.h" #include "zonelist.h" #include "zoneserver.h" #include "../common/eqemu_logsys.h" +extern ClientList client_list; extern ZSList zoneserver_list; Expedition::Expedition(uint32_t expedition_id, uint32_t dz_id, uint32_t dz_instance_id, - uint32_t dz_zone_id, uint32_t start_time, uint32_t duration + uint32_t dz_zone_id, uint32_t start_time, uint32_t duration, uint32_t leader_id ) : m_expedition_id(expedition_id), m_dz_id(dz_id), m_dz_instance_id(dz_instance_id), m_dz_zone_id(dz_zone_id), m_start_time(std::chrono::system_clock::from_time_t(start_time)), - m_duration(duration) + m_duration(duration), + m_leader_id(leader_id) { m_expire_time = m_start_time + m_duration; m_warning_cooldown_timer.Enable(); } +void Expedition::AddMember(uint32_t character_id) +{ + auto it = std::find_if(m_member_ids.begin(), m_member_ids.end(), + [&](uint32_t member_id) { return member_id == character_id; }); + + if (it == m_member_ids.end()) + { + m_member_ids.emplace_back(character_id); + } +} + +void Expedition::RemoveMember(uint32_t character_id) +{ + m_member_ids.erase(std::remove_if(m_member_ids.begin(), m_member_ids.end(), + [&](uint32_t member_id) { return member_id == character_id; } + ), m_member_ids.end()); + + if (!m_member_ids.empty() && character_id == m_leader_id) + { + ChooseNewLeader(); + } +} + +void Expedition::ChooseNewLeader() +{ + // we don't track expedition member status in world so may choose a linkdead member + // this is fine since it will trigger another change when that member goes offline + auto it = std::find_if(m_member_ids.begin(), m_member_ids.end(), [&](uint32_t member_id) { + auto member_cle = (member_id != m_leader_id) ? client_list.FindCLEByCharacterID(member_id) : nullptr; + return (member_id != m_leader_id && member_cle && member_cle->GetOnline() == CLE_Status::InZone); + }); + + if (it == m_member_ids.end()) + { + // no online members found, fallback to choosing any member + it = std::find_if(m_member_ids.begin(), m_member_ids.end(), + [&](uint32_t member_id) { return (member_id != m_leader_id); }); + } + + if (it != m_member_ids.end()) + { + SetNewLeader(*it); + } +} + +void Expedition::SetNewLeader(uint32_t character_id) +{ + LogExpeditionsModerate("Replacing [{}] leader [{}] with [{}]", m_expedition_id, m_leader_id, character_id); + ExpeditionDatabase::UpdateLeaderID(m_expedition_id, character_id); + m_leader_id = character_id; + SendZonesLeaderChanged(); +} + void Expedition::SendZonesExpeditionDeleted() { uint32_t pack_size = sizeof(ServerExpeditionID_Struct); @@ -69,6 +126,16 @@ void Expedition::SendZonesExpireWarning(uint32_t minutes_remaining) zoneserver_list.SendPacket(pack.get()); } +void Expedition::SendZonesLeaderChanged() +{ + uint32_t pack_size = sizeof(ServerExpeditionLeaderID_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionLeaderChanged, pack_size)); + auto buf = reinterpret_cast(pack->pBuffer); + buf->expedition_id = GetID(); + buf->leader_id = m_leader_id; + zoneserver_list.SendPacket(pack.get()); +} + void Expedition::UpdateDzSecondsRemaining(uint32_t seconds_remaining) { auto now = std::chrono::system_clock::now(); diff --git a/world/expedition.h b/world/expedition.h index 7cd9af996..77123d862 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -24,19 +24,20 @@ #include "../common/timer.h" #include #include -#include +#include class Expedition { public: Expedition() = default; Expedition(uint32_t expedition_id, uint32_t dz_id, uint32_t dz_instance_id, - uint32_t dz_zone_id, uint32_t expire_time, uint32_t duration); + uint32_t dz_zone_id, uint32_t expire_time, uint32_t duration, uint32_t leader_id); - void AddMember(uint32_t character_id) { m_member_ids.emplace(character_id); } - void RemoveMember(uint32_t character_id) { m_member_ids.erase(character_id); } + void AddMember(uint32_t character_id); + void RemoveMember(uint32_t character_id); void RemoveAllMembers() { m_member_ids.clear(); } void CheckExpireWarning(); + void ChooseNewLeader(); uint32_t GetID() const { return m_expedition_id; } uint16_t GetInstanceID() const { return static_cast(m_dz_instance_id); } uint16_t GetZoneID() const { return static_cast(m_dz_zone_id); } @@ -47,18 +48,22 @@ public: void SendZonesDurationUpdate(); void SendZonesExpeditionDeleted(); void SendZonesExpireWarning(uint32_t minutes_remaining); + void SetNewLeader(uint32_t new_leader_id); void SetPendingDelete(bool pending) { m_pending_delete = pending; } void UpdateDzSecondsRemaining(uint32_t seconds_remaining); std::chrono::system_clock::duration GetRemainingDuration() const; private: + void SendZonesLeaderChanged(); + uint32_t m_expedition_id = 0; uint32_t m_dz_id = 0; uint32_t m_dz_instance_id = 0; uint32_t m_dz_zone_id = 0; + uint32_t m_leader_id = 0; bool m_pending_delete = false; Timer m_warning_cooldown_timer; - std::unordered_set m_member_ids; + std::vector m_member_ids; std::chrono::seconds m_duration; std::chrono::time_point m_start_time; std::chrono::time_point m_expire_time; diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp index 9dde517e9..e3755befb 100644 --- a/world/expedition_database.cpp +++ b/world/expedition_database.cpp @@ -77,6 +77,7 @@ std::vector ExpeditionDatabase::LoadExpeditions(uint32_t select_expe instance_list.zone, instance_list.start_time, instance_list.duration, + expeditions.leader_id, expedition_members.character_id FROM expeditions INNER JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id @@ -110,13 +111,14 @@ std::vector ExpeditionDatabase::LoadExpeditions(uint32_t select_expe static_cast(strtoul(row[2], nullptr, 10)), // dz_instance_id static_cast(strtoul(row[3], nullptr, 10)), // dz_zone_id static_cast(strtoul(row[4], nullptr, 10)), // start_time - static_cast(strtoul(row[5], nullptr, 10)) // duration + static_cast(strtoul(row[5], nullptr, 10)), // duration + static_cast(strtoul(row[6], nullptr, 10)) // leader_id ); } last_expedition_id = expedition_id; - uint32_t member_id = static_cast(strtoul(row[6], nullptr, 10)); + uint32_t member_id = static_cast(strtoul(row[7], nullptr, 10)); expeditions.back().AddMember(member_id); } } @@ -167,3 +169,14 @@ void ExpeditionDatabase::UpdateDzDuration(uint16_t instance_id, uint32_t new_dur database.QueryDatabase(query); } + +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 expeditions SET leader_id = {} WHERE id = {}; + ), leader_id, expedition_id); + + database.QueryDatabase(query); +} diff --git a/world/expedition_database.h b/world/expedition_database.h index ff01f0107..302dba3e2 100644 --- a/world/expedition_database.h +++ b/world/expedition_database.h @@ -34,6 +34,7 @@ namespace ExpeditionDatabase void PurgeExpiredExpeditions(); void PurgeExpiredCharacterLockouts(); void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); + void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); }; #endif diff --git a/world/expedition_message.cpp b/world/expedition_message.cpp index 282c2dbe8..0dec74bde 100644 --- a/world/expedition_message.cpp +++ b/world/expedition_message.cpp @@ -18,6 +18,7 @@ * */ +#include "expedition.h" #include "expedition_message.h" #include "expedition_state.h" #include "cliententry.h" @@ -34,6 +35,11 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) { switch (pack->opcode) { + case ServerOP_ExpeditionChooseNewLeader: + { + ExpeditionMessage::ChooseNewLeader(pack); + break; + } case ServerOP_ExpeditionCreate: { auto buf = reinterpret_cast(pack->pBuffer); @@ -51,8 +57,8 @@ void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) case ServerOP_ExpeditionMemberSwap: { auto buf = reinterpret_cast(pack->pBuffer); - expedition_state.MemberChange(buf->expedition_id, buf->remove_char_id, true); expedition_state.MemberChange(buf->expedition_id, buf->add_char_id, false); + expedition_state.MemberChange(buf->expedition_id, buf->remove_char_id, true); zoneserver_list.SendPacket(pack); break; } @@ -141,6 +147,12 @@ void ExpeditionMessage::MakeLeader(ServerPacket* pack) buf->is_char_online = true; new_leader_zs = new_leader_cle->Server(); new_leader_zs->SendPacket(pack); + + auto expedition = expedition_state.GetExpedition(buf->expedition_id); + if (expedition) + { + expedition->SetNewLeader(new_leader_cle->CharID()); + } } // if old and new leader are in the same zone only send one message @@ -205,3 +217,13 @@ void ExpeditionMessage::RequestInvite(ServerPacket* pack) } } } + +void ExpeditionMessage::ChooseNewLeader(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + auto expedition = expedition_state.GetExpedition(buf->expedition_id); + if (expedition) + { + expedition->ChooseNewLeader(); + } +} diff --git a/world/expedition_message.h b/world/expedition_message.h index 17470d788..8789577da 100644 --- a/world/expedition_message.h +++ b/world/expedition_message.h @@ -26,6 +26,7 @@ class ServerPacket; namespace ExpeditionMessage { void AddPlayer(ServerPacket* pack); + void ChooseNewLeader(ServerPacket* pack); void GetOnlineMembers(ServerPacket* pack); void HandleZoneMessage(ServerPacket* pack); void MakeLeader(ServerPacket* pack); diff --git a/world/expedition_state.cpp b/world/expedition_state.cpp index d6a38e912..df09a5f2f 100644 --- a/world/expedition_state.cpp +++ b/world/expedition_state.cpp @@ -30,6 +30,14 @@ extern ZSList zoneserver_list; ExpeditionState expedition_state; +Expedition* ExpeditionState::GetExpedition(uint32_t expedition_id) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), + [&](const Expedition& expedition) { return expedition.GetID() == expedition_id; }); + + return (it != m_expeditions.end()) ? &(*it) : nullptr; +} + void ExpeditionState::LoadActiveExpeditions() { BenchTimer benchmark; @@ -51,11 +59,8 @@ void ExpeditionState::AddExpedition(uint32_t expedition_id) if (expedition.IsValid()) { - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it == m_expeditions.end()) + auto existing_expedition = GetExpedition(expedition_id); + if (!existing_expedition) { m_expeditions.emplace_back(expedition); } @@ -73,41 +78,32 @@ void ExpeditionState::RemoveExpedition(uint32_t expedition_id) void ExpeditionState::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) { - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) + auto expedition = GetExpedition(expedition_id); + if (expedition) { if (remove) { - it->RemoveMember(character_id); + expedition->RemoveMember(character_id); } else { - it->AddMember(character_id); + expedition->AddMember(character_id); } } } void ExpeditionState::RemoveAllMembers(uint32_t expedition_id) { - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) + auto expedition = GetExpedition(expedition_id); + if (expedition) { - it->RemoveAllMembers(); + expedition->RemoveAllMembers(); } } void ExpeditionState::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) { - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) + auto expedition = GetExpedition(expedition_id); + if (expedition) { - it->UpdateDzSecondsRemaining(seconds_remaining); + expedition->UpdateDzSecondsRemaining(seconds_remaining); } } diff --git a/world/expedition_state.h b/world/expedition_state.h index 6a9f230c2..9d54dcec9 100644 --- a/world/expedition_state.h +++ b/world/expedition_state.h @@ -34,6 +34,7 @@ class ExpeditionState { public: void AddExpedition(uint32_t expedition_id); + Expedition* GetExpedition(uint32_t expedition_id); void LoadActiveExpeditions(); void MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove); void Process(); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 0a1a960d7..f09d08db1 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1362,7 +1362,6 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { client_list.SendPacket(buf->character_name, pack); break; } - case ServerOP_ExpeditionLeaderChanged: case ServerOP_ExpeditionLockout: case ServerOP_ExpeditionLockoutDuration: case ServerOP_ExpeditionLockState: @@ -1376,6 +1375,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { zoneserver_list.SendPacket(pack); break; } + case ServerOP_ExpeditionChooseNewLeader: case ServerOP_ExpeditionCreate: case ServerOP_ExpeditionGetOnlineMembers: case ServerOP_ExpeditionMemberChange: diff --git a/zone/command.cpp b/zone/command.cpp index 811a2c2ba..21e50be81 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6984,20 +6984,15 @@ void command_dz(Client* c, const Seperator* sep) auto expedition = Expedition::FindCachedExpeditionByID(expedition_id); if (expedition) { - uint32_t char_id = database.GetCharacterID(sep->arg[3]); auto char_name = FormatName(sep->arg[3]); - if (char_id == 0) - { - c->Message(Chat::Red, fmt::format("Failed to find character id for [{}]", char_name).c_str()); - } - else if (!expedition->HasMember(char_id)) + if (!expedition->HasMember(char_name)) { c->Message(Chat::Red, fmt::format("Character [{}] is not in that expedition", char_name).c_str()); } else { c->Message(Chat::White, fmt::format("Setting expedition [{}] leader to [{}]", expedition_id, char_name).c_str()); - expedition->SetNewLeader(char_id, char_name); + expedition->SendWorldMakeLeaderRequest(c->GetName(), char_name); } } else diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 148c8f335..c72c185da 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -578,12 +578,6 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) ProcessMemberRemoved(member.name, member.char_id); SendWorldMemberChanged(member.name, member.char_id, true); - // live always sends a leader update but we can send only if leader changes - if (member.char_id == m_leader.char_id) - { - ChooseNewLeader(); - } - return true; } @@ -609,11 +603,6 @@ void Expedition::SwapMember(Client* add_client, const std::string& remove_char_n ProcessMemberRemoved(member.name, member.char_id); ProcessMemberAdded(add_client->GetName(), add_client->CharacterID()); SendWorldMemberSwapped(member.name, member.char_id, add_client->GetName(), add_client->CharacterID()); - - if (!m_members.empty() && member.char_id == m_leader.char_id) - { - ChooseNewLeader(); - } } void Expedition::SetMemberStatus(Client* client, ExpeditionMemberStatus status) @@ -623,11 +612,11 @@ void Expedition::SetMemberStatus(Client* client, ExpeditionMemberStatus status) UpdateMemberStatus(client->CharacterID(), status); SendWorldMemberStatus(client->CharacterID(), status); - // we either changed leader status here or leader was already offline and - // a member coming online needs to trigger a leader change + // world could detect this itself but it'd have to process member status updates + // a member coming online will trigger a leader change if all members were offline if (m_leader.status == ExpeditionMemberStatus::Offline) { - ChooseNewLeader(); + SendWorldExpeditionUpdate(ServerOP_ExpeditionChooseNewLeader); } } } @@ -662,20 +651,6 @@ void Expedition::UpdateMemberStatus(uint32_t update_member_id, ExpeditionMemberS } } -bool Expedition::ChooseNewLeader() -{ - for (const auto& member : m_members) - { - if (member.char_id != m_leader.char_id && member.status == ExpeditionMemberStatus::Online) - { - LogExpeditionsModerate("Replacing leader [{}] with [{}]", m_leader.name, member.name); - SetNewLeader(member.char_id, member.name); - return true; - } - } - return false; -} - void Expedition::SendClientExpeditionInvite( Client* client, const std::string& inviter_name, const std::string& swap_remove_name) { @@ -1051,17 +1026,8 @@ void Expedition::DzMakeLeader(Client* requester, std::string new_leader_name) return; } - // database is not updated until new leader client validated - Client* new_leader_client = entity_list.GetClientByName(new_leader_name.c_str()); - if (new_leader_client) - { - ProcessMakeLeader(requester, new_leader_client, new_leader_name, true); - } - else - { - // new leader not in this zone, let world verify and pass to new leader's zone - SendWorldMakeLeaderRequest(requester->GetName(), new_leader_name); - } + // leader can only be changed by world + SendWorldMakeLeaderRequest(requester->GetName(), new_leader_name); } void Expedition::DzRemovePlayer(Client* requester, std::string char_name) @@ -1164,16 +1130,18 @@ void Expedition::SetLocked( } } -void Expedition::SetNewLeader(uint32_t new_leader_id, const std::string& new_leader_name) +void Expedition::ProcessLeaderChanged(uint32_t new_leader_id) { - ExpeditionDatabase::UpdateLeaderID(m_id, new_leader_id); - ProcessLeaderChanged(new_leader_id, new_leader_name); - SendWorldLeaderChanged(); -} + auto new_leader = GetMemberData(new_leader_id); + if (new_leader.char_id == 0) + { + LogExpeditions("Processed invalid new leader id [{}] for expedition [{}]", new_leader_id, m_id); + return; + } -void Expedition::ProcessLeaderChanged(uint32_t new_leader_id, const std::string& new_leader_name) -{ - m_leader = { new_leader_id, new_leader_name, ExpeditionMemberStatus::Online }; + LogExpeditionsModerate("Replaced [{}] leader [{}] with [{}]", m_id, m_leader.name, new_leader.name); + + m_leader = new_leader; // update each client's expedition window in this zone auto outapp_leader = CreateLeaderNamePacket(); @@ -1219,7 +1187,6 @@ void Expedition::ProcessMakeLeader( { new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); } - SetNewLeader(new_leader_client->CharacterID(), new_leader_client->GetName()); } } @@ -1592,19 +1559,6 @@ void Expedition::SendWorldAddPlayerInvite( worldserver.SendPacket(pack.get()); } -void Expedition::SendWorldLeaderChanged() -{ - uint32_t pack_size = sizeof(ServerExpeditionMemberChange_Struct); - auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionLeaderChanged, pack_size)); - auto buf = reinterpret_cast(pack->pBuffer); - buf->expedition_id = GetID(); - buf->sender_zone_id = zone ? zone->GetZoneID() : 0; - buf->sender_instance_id = zone ? zone->GetInstanceID() : 0; - buf->char_id = m_leader.char_id; - strn0cpy(buf->char_name, m_leader.name.c_str(), sizeof(buf->char_name)); - worldserver.SendPacket(pack.get()); -} - void Expedition::SendWorldLockoutDuration( const ExpeditionLockoutTimer& lockout, int seconds, bool members_only) { @@ -1894,14 +1848,11 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } case ServerOP_ExpeditionLeaderChanged: { - auto buf = reinterpret_cast(pack->pBuffer); - if (zone && !zone->IsZone(buf->sender_zone_id, buf->sender_instance_id)) + auto buf = reinterpret_cast(pack->pBuffer); + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition) { - auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); - if (expedition) - { - expedition->ProcessLeaderChanged(buf->char_id, buf->char_name); - } + expedition->ProcessLeaderChanged(buf->leader_id); } break; } diff --git a/zone/expedition.h b/zone/expedition.h index d4bb1b43f..44ae06df5 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -122,7 +122,6 @@ public: void RemoveAllMembers(bool enable_removal_timers = 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); void SwapMember(Client* add_client, const std::string& remove_char_name); void SetLocked(bool lock_expedition, ExpeditionLockMessage lock_msg, bool update_db = false, uint32_t msg_color = Chat::Yellow); @@ -144,6 +143,7 @@ public: void SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name); void SendClientExpeditionInfo(Client* client); + void SendWorldMakeLeaderRequest(const std::string& requester_name, const std::string& new_leader_name); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); void DzAddPlayer(Client* requester, const std::string& add_char_name, const std::string& swap_remove_name = {}); @@ -174,10 +174,9 @@ private: void AddLockout(const ExpeditionLockoutTimer& lockout, bool members_only = false); void AddLockoutDurationClients(const ExpeditionLockoutTimer& lockout, int seconds, uint32_t exclude_id = 0); void AddInternalMember(const std::string& char_name, uint32_t char_id, ExpeditionMemberStatus status); - bool ChooseNewLeader(); bool ConfirmLeaderCommand(Client* requester); bool ProcessAddConflicts(Client* leader_client, Client* add_client, bool swapping); - void ProcessLeaderChanged(uint32_t new_leader_id, const std::string& new_leader_name); + void ProcessLeaderChanged(uint32_t new_leader_id); void ProcessLockoutDuration(const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); void ProcessMakeLeader(Client* old_leader, Client* new_leader, const std::string& new_leader_name, bool is_online); @@ -196,12 +195,10 @@ private: void SendWorldExpeditionUpdate(uint16_t server_opcode); void SendWorldAddPlayerInvite(const std::string& inviter_name, const std::string& swap_remove_name, const std::string& add_name, bool pending = false); - void SendWorldLeaderChanged(); void SendWorldLockoutDuration( const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); void SendWorldLockoutUpdate( const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = 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, diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index cb845cc50..782e96517 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -629,17 +629,6 @@ void ExpeditionDatabase::InsertMembers( } } -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 expeditions SET leader_id = {} WHERE id = {}; - ), leader_id, expedition_id); - - database.QueryDatabase(query); -} - void ExpeditionDatabase::UpdateLockState(uint32_t expedition_id, bool is_locked) { LogExpeditionsDetail("Updating lock state [{}] for expedition [{}]", is_locked, expedition_id); diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 519a331f4..a774ac2c5 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -71,7 +71,6 @@ namespace ExpeditionDatabase void InsertLockouts(uint32_t expedition_id, const std::unordered_map& lockouts); void InsertMember(uint32_t expedition_id, uint32_t character_id); void InsertMembers(uint32_t expedition_id, const std::vector& members); - void UpdateLeaderID(uint32_t expedition_id, uint32_t leader_id); void UpdateLockState(uint32_t expedition_id, bool is_locked); void UpdateReplayLockoutOnJoin(uint32_t expedition_id, bool add_on_join); void AddLockoutDuration(const std::vector& members, From 311042f06d6bb7aa84e98e56b83325a99b254bfb Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 2 Nov 2020 23:17:22 -0500 Subject: [PATCH 149/196] Add ExpeditionMember::IsValid method --- zone/expedition.cpp | 14 +++++++------- zone/expedition.h | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index c72c185da..062e11f55 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -567,7 +567,7 @@ void Expedition::RemoveAllMembers(bool enable_removal_timers) bool Expedition::RemoveMember(const std::string& remove_char_name) { auto member = GetMemberData(remove_char_name); - if (member.char_id == 0 || member.name.empty()) + if (!member.IsValid()) { return false; } @@ -589,7 +589,7 @@ void Expedition::SwapMember(Client* add_client, const std::string& remove_char_n } auto member = GetMemberData(remove_char_name); - if (member.char_id == 0 || member.name.empty()) + if (!member.IsValid()) { return; } @@ -624,7 +624,7 @@ void Expedition::SetMemberStatus(Client* client, ExpeditionMemberStatus status) void Expedition::UpdateMemberStatus(uint32_t update_member_id, ExpeditionMemberStatus status) { auto member_data = GetMemberData(update_member_id); - if (member_data.char_id == 0 || member_data.name.empty()) + if (!member_data.IsValid()) { return; } @@ -886,7 +886,7 @@ bool Expedition::ConfirmLeaderCommand(Client* requester) return false; } - if (m_leader.char_id == 0) + if (!m_leader.IsValid()) { requester->MessageString(Chat::Red, UNABLE_RETRIEVE_LEADER); // unconfirmed message return false; @@ -958,7 +958,7 @@ void Expedition::DzAddPlayer( else { auto member_data = GetMemberData(add_char_name); - if (member_data.char_id != 0) + if (member_data.IsValid()) { // live prioritizes offline message before already a member message if (member_data.status == ExpeditionMemberStatus::Offline) @@ -1020,7 +1020,7 @@ void Expedition::DzMakeLeader(Client* requester, std::string new_leader_name) } auto new_leader_data = GetMemberData(new_leader_name); - if (new_leader_data.char_id == 0) + if (!new_leader_data.IsValid()) { requester->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, new_leader_name.c_str()); return; @@ -1133,7 +1133,7 @@ void Expedition::SetLocked( void Expedition::ProcessLeaderChanged(uint32_t new_leader_id) { auto new_leader = GetMemberData(new_leader_id); - if (new_leader.char_id == 0) + if (!new_leader.IsValid()) { LogExpeditions("Processed invalid new leader id [{}] for expedition [{}]", new_leader_id, m_id); return; diff --git a/zone/expedition.h b/zone/expedition.h index 44ae06df5..044eef3f8 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -68,6 +68,8 @@ struct ExpeditionMember : 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_) {} + + bool IsValid() const { return char_id != 0 && !name.empty(); } }; class Expedition From 1de590137fcab67845f7eda85952dac688f41220 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 2 Nov 2020 23:41:21 -0500 Subject: [PATCH 150/196] Verify new expedition leader in world Cleanup makeleader methods World now checks if character is expedition member instead of zone --- common/servertalk.h | 8 ++++++ world/expedition.cpp | 14 +++++++++- world/expedition.h | 3 +- world/expedition_message.cpp | 14 +++++----- zone/command.cpp | 11 ++------ zone/expedition.cpp | 54 +++++++++++++----------------------- zone/expedition.h | 3 +- 7 files changed, 54 insertions(+), 53 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index ef7a3ecab..4370d6be0 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -2108,6 +2108,14 @@ struct ServerDzCommand_Struct { char remove_name[64]; // used for swap command }; +struct ServerDzCommandMakeLeader_Struct { + uint32 expedition_id; + uint8 is_online; // set by world, 0: new leader name offline, 1: online + uint8 is_success; // set by world, 0: makeleader failed, 1: success (is online member) + char requester_name[64]; + char new_leader_name[64]; +}; + struct ServerDzLocation_Struct { uint32 owner_id; // system associated with the dz (expedition, shared task, etc) uint16 dz_zone_id; diff --git a/world/expedition.cpp b/world/expedition.cpp index c1bd5a35a..2f1a8f0be 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -55,6 +55,12 @@ void Expedition::AddMember(uint32_t character_id) } } +bool Expedition::HasMember(uint32_t character_id) +{ + return std::any_of(m_member_ids.begin(), m_member_ids.end(), + [&](uint32_t member_id) { return member_id == character_id; }); +} + void Expedition::RemoveMember(uint32_t character_id) { m_member_ids.erase(std::remove_if(m_member_ids.begin(), m_member_ids.end(), @@ -89,12 +95,18 @@ void Expedition::ChooseNewLeader() } } -void Expedition::SetNewLeader(uint32_t character_id) +bool Expedition::SetNewLeader(uint32_t character_id) { + if (!HasMember(character_id)) + { + return false; + } + LogExpeditionsModerate("Replacing [{}] leader [{}] with [{}]", m_expedition_id, m_leader_id, character_id); ExpeditionDatabase::UpdateLeaderID(m_expedition_id, character_id); m_leader_id = character_id; SendZonesLeaderChanged(); + return true; } void Expedition::SendZonesExpeditionDeleted() diff --git a/world/expedition.h b/world/expedition.h index 77123d862..0b5f04e78 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -41,6 +41,7 @@ public: uint32_t GetID() const { return m_expedition_id; } uint16_t GetInstanceID() const { return static_cast(m_dz_instance_id); } uint16_t GetZoneID() const { return static_cast(m_dz_zone_id); } + bool HasMember(uint32_t character_id); bool IsEmpty() const { return m_member_ids.empty(); } bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } bool IsPendingDelete() const { return m_pending_delete; } @@ -48,7 +49,7 @@ public: void SendZonesDurationUpdate(); void SendZonesExpeditionDeleted(); void SendZonesExpireWarning(uint32_t minutes_remaining); - void SetNewLeader(uint32_t new_leader_id); + bool SetNewLeader(uint32_t new_leader_id); void SetPendingDelete(bool pending) { m_pending_delete = pending; } void UpdateDzSecondsRemaining(uint32_t seconds_remaining); std::chrono::system_clock::duration GetRemainingDuration() const; diff --git a/world/expedition_message.cpp b/world/expedition_message.cpp index 0dec74bde..8501c0e7a 100644 --- a/world/expedition_message.cpp +++ b/world/expedition_message.cpp @@ -137,22 +137,22 @@ void ExpeditionMessage::AddPlayer(ServerPacket* pack) void ExpeditionMessage::MakeLeader(ServerPacket* pack) { - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(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); + ClientListEntry* new_leader_cle = client_list.FindCharacter(buf->new_leader_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); - auto expedition = expedition_state.GetExpedition(buf->expedition_id); if (expedition) { - expedition->SetNewLeader(new_leader_cle->CharID()); + buf->is_success = expedition->SetNewLeader(new_leader_cle->CharID()); } + + buf->is_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 diff --git a/zone/command.cpp b/zone/command.cpp index 21e50be81..e1662f3ef 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6985,15 +6985,8 @@ void command_dz(Client* c, const Seperator* sep) if (expedition) { auto char_name = FormatName(sep->arg[3]); - if (!expedition->HasMember(char_name)) - { - c->Message(Chat::Red, fmt::format("Character [{}] is not in that expedition", char_name).c_str()); - } - else - { - c->Message(Chat::White, fmt::format("Setting expedition [{}] leader to [{}]", expedition_id, char_name).c_str()); - expedition->SendWorldMakeLeaderRequest(c->GetName(), char_name); - } + c->Message(Chat::White, fmt::format("Setting expedition [{}] leader to [{}]", expedition_id, char_name).c_str()); + expedition->SendWorldMakeLeaderRequest(c->GetName(), char_name); } else { diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 062e11f55..8223adec1 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1010,24 +1010,14 @@ void Expedition::DzMakeLeader(Client* requester, std::string new_leader_name) return; } - // live uses sanitized input name for all /dzmakeleader messages - new_leader_name = FormatName(new_leader_name); - if (new_leader_name.empty()) { requester->MessageString(Chat::Red, DZMAKELEADER_NOT_ONLINE, new_leader_name.c_str()); return; } - auto new_leader_data = GetMemberData(new_leader_name); - if (!new_leader_data.IsValid()) - { - requester->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, new_leader_name.c_str()); - return; - } - // leader can only be changed by world - SendWorldMakeLeaderRequest(requester->GetName(), new_leader_name); + SendWorldMakeLeaderRequest(requester->GetName(), FormatName(new_leader_name)); } void Expedition::DzRemovePlayer(Client* requester, std::string char_name) @@ -1160,34 +1150,30 @@ void Expedition::ProcessLeaderChanged(uint32_t new_leader_id) } } -void Expedition::ProcessMakeLeader( - Client* old_leader_client, Client* new_leader_client, const std::string& new_leader_name, bool is_online) +void Expedition::ProcessMakeLeader(Client* old_leader_client, Client* new_leader_client, + const std::string& new_leader_name, bool is_success, bool is_online) { if (old_leader_client) { - // online flag is set by world to verify new leader is online or not - if (is_online) + // success flag is set by world to indicate new leader set to an online member + if (is_success) { old_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_NAME, new_leader_name.c_str()); } - else + else if (!is_online) { old_leader_client->MessageString(Chat::Red, DZMAKELEADER_NOT_ONLINE, new_leader_name.c_str()); } - } - - if (!new_leader_client) - { - new_leader_client = entity_list.GetClientByName(new_leader_name.c_str()); - } - - if (new_leader_client) - { - if (!RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) + else { - new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + old_leader_client->MessageString(Chat::Red, EXPEDITION_NOT_MEMBER, new_leader_name.c_str()); } } + + if (is_success && new_leader_client && !RuleB(Expedition, AlwaysNotifyNewLeaderOnChange)) + { + new_leader_client->MessageString(Chat::Yellow, DZMAKELEADER_YOU); + } } void Expedition::ProcessMemberAdded(const std::string& char_name, uint32_t added_char_id) @@ -1596,13 +1582,12 @@ void Expedition::SendWorldLockoutUpdate( void Expedition::SendWorldMakeLeaderRequest( const std::string& requester_name, const std::string& new_leader_name) { - uint32_t pack_size = sizeof(ServerDzCommand_Struct); + uint32_t pack_size = sizeof(ServerDzCommandMakeLeader_Struct); auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDzMakeLeader, pack_size)); - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); buf->expedition_id = GetID(); - buf->is_char_online = false; strn0cpy(buf->requester_name, requester_name.c_str(), sizeof(buf->requester_name)); - strn0cpy(buf->target_name, new_leader_name.c_str(), sizeof(buf->target_name)); + strn0cpy(buf->new_leader_name, new_leader_name.c_str(), sizeof(buf->new_leader_name)); worldserver.SendPacket(pack.get()); } @@ -1999,13 +1984,14 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } case ServerOP_ExpeditionDzMakeLeader: { - auto buf = reinterpret_cast(pack->pBuffer); + auto buf = reinterpret_cast(pack->pBuffer); auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition) { auto old_leader_client = entity_list.GetClientByName(buf->requester_name); - auto new_leader_client = entity_list.GetClientByName(buf->target_name); - expedition->ProcessMakeLeader(old_leader_client, new_leader_client, buf->target_name, buf->is_char_online); + auto new_leader_client = entity_list.GetClientByName(buf->new_leader_name); + expedition->ProcessMakeLeader(old_leader_client, new_leader_client, + buf->new_leader_name, buf->is_success, buf->is_online); } break; } diff --git a/zone/expedition.h b/zone/expedition.h index 044eef3f8..7f264b723 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -181,7 +181,8 @@ private: void ProcessLeaderChanged(uint32_t new_leader_id); void ProcessLockoutDuration(const ExpeditionLockoutTimer& lockout, int seconds, bool members_only = false); void ProcessLockoutUpdate(const ExpeditionLockoutTimer& lockout, bool remove, bool members_only = false); - void ProcessMakeLeader(Client* old_leader, Client* new_leader, const std::string& new_leader_name, bool is_online); + void ProcessMakeLeader(Client* old_leader, Client* new_leader, + const std::string& new_leader_name, bool is_success, bool is_online); void ProcessMemberAdded(const std::string& added_char_name, uint32_t added_char_id); void ProcessMemberRemoved(const std::string& removed_char_name, uint32_t removed_char_id); void SaveLockouts(ExpeditionRequest& request); From e5916c5c03f5f5176cefcc1ba34c912f975a9a23 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 6 Nov 2020 20:05:26 -0500 Subject: [PATCH 151/196] Add rule to enable "In Dynamic Zone" status Live doesn't appear to ever update with this status --- common/ruletypes.h | 1 + zone/expedition.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/common/ruletypes.h b/common/ruletypes.h index 7acf0b1b4..5ff662c50 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -792,6 +792,7 @@ RULE_INT(Expedition, EmptyDzShutdownDelaySeconds, 1500, "Seconds to set dynamic RULE_INT(Expedition, WorldExpeditionProcessRateMS, 6000, "Timer interval (ms) that world checks expedition states") RULE_BOOL(Expedition, AlwaysNotifyNewLeaderOnChange, false, "Always notify clients when made expedition leader. If false (live-like) new leaders are only notified when made leader via /dzmakeleader") RULE_REAL(Expedition, LockoutDurationMultiplier, 1.0, "Multiplies lockout duration by this value when new lockouts are added") +RULE_BOOL(Expedition, EnableInDynamicZoneStatus, false, "Enables the 'In Dynamic Zone' member status in expedition window. If false (live-like) players inside the dz will show as 'Online'") RULE_CATEGORY_END() RULE_CATEGORY(DynamicZone) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 8223adec1..77dd25b0a 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -629,6 +629,11 @@ void Expedition::UpdateMemberStatus(uint32_t update_member_id, ExpeditionMemberS return; } + if (status == ExpeditionMemberStatus::InDynamicZone && !RuleB(Expedition, EnableInDynamicZoneStatus)) + { + status = ExpeditionMemberStatus::Online; + } + auto outapp_member_status = CreateMemberListStatusPacket(member_data.name, status); for (auto& member : m_members) From 738fd48163b9583c6aeb98264c60c56e292dc787 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 6 Nov 2020 20:36:40 -0500 Subject: [PATCH 152/196] Use id not name in dz makeleader world msg --- common/servertalk.h | 2 +- world/expedition_message.cpp | 2 +- zone/command.cpp | 2 +- zone/expedition.cpp | 9 ++++----- zone/expedition.h | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/common/servertalk.h b/common/servertalk.h index 4370d6be0..40be635ad 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -2110,9 +2110,9 @@ struct ServerDzCommand_Struct { struct ServerDzCommandMakeLeader_Struct { uint32 expedition_id; + uint32 requester_id; uint8 is_online; // set by world, 0: new leader name offline, 1: online uint8 is_success; // set by world, 0: makeleader failed, 1: success (is online member) - char requester_name[64]; char new_leader_name[64]; }; diff --git a/world/expedition_message.cpp b/world/expedition_message.cpp index 8501c0e7a..646e2272e 100644 --- a/world/expedition_message.cpp +++ b/world/expedition_message.cpp @@ -156,7 +156,7 @@ void ExpeditionMessage::MakeLeader(ServerPacket* pack) } // if old and new leader are in the same zone only send one message - ClientListEntry* requester_cle = client_list.FindCharacter(buf->requester_name); + ClientListEntry* requester_cle = client_list.FindCLEByCharacterID(buf->requester_id); if (requester_cle && requester_cle->Server() && requester_cle->Server() != new_leader_zs) { requester_cle->Server()->SendPacket(pack); diff --git a/zone/command.cpp b/zone/command.cpp index e1662f3ef..39e6c98d5 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6986,7 +6986,7 @@ void command_dz(Client* c, const Seperator* sep) { auto char_name = FormatName(sep->arg[3]); c->Message(Chat::White, fmt::format("Setting expedition [{}] leader to [{}]", expedition_id, char_name).c_str()); - expedition->SendWorldMakeLeaderRequest(c->GetName(), char_name); + expedition->SendWorldMakeLeaderRequest(c->CharacterID(), char_name); } else { diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 77dd25b0a..8e3465b7d 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -1022,7 +1022,7 @@ void Expedition::DzMakeLeader(Client* requester, std::string new_leader_name) } // leader can only be changed by world - SendWorldMakeLeaderRequest(requester->GetName(), FormatName(new_leader_name)); + SendWorldMakeLeaderRequest(requester->CharacterID(), FormatName(new_leader_name)); } void Expedition::DzRemovePlayer(Client* requester, std::string char_name) @@ -1584,14 +1584,13 @@ void Expedition::SendWorldLockoutUpdate( worldserver.SendPacket(pack.get()); } -void Expedition::SendWorldMakeLeaderRequest( - const std::string& requester_name, const std::string& new_leader_name) +void Expedition::SendWorldMakeLeaderRequest(uint32_t requester_id, const std::string& new_leader_name) { uint32_t pack_size = sizeof(ServerDzCommandMakeLeader_Struct); auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDzMakeLeader, pack_size)); auto buf = reinterpret_cast(pack->pBuffer); buf->expedition_id = GetID(); - strn0cpy(buf->requester_name, requester_name.c_str(), sizeof(buf->requester_name)); + buf->requester_id = requester_id; strn0cpy(buf->new_leader_name, new_leader_name.c_str(), sizeof(buf->new_leader_name)); worldserver.SendPacket(pack.get()); } @@ -1993,7 +1992,7 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); if (expedition) { - auto old_leader_client = entity_list.GetClientByName(buf->requester_name); + auto old_leader_client = entity_list.GetClientByCharID(buf->requester_id); auto new_leader_client = entity_list.GetClientByName(buf->new_leader_name); expedition->ProcessMakeLeader(old_leader_client, new_leader_client, buf->new_leader_name, buf->is_success, buf->is_online); diff --git a/zone/expedition.h b/zone/expedition.h index 7f264b723..5a0d6da56 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -145,7 +145,7 @@ public: void SetLootEventBySpawnID(uint32_t spawn_id, const std::string& event_name); void SendClientExpeditionInfo(Client* client); - void SendWorldMakeLeaderRequest(const std::string& requester_name, const std::string& new_leader_name); + void SendWorldMakeLeaderRequest(uint32_t requester_id, const std::string& new_leader_name); void SendWorldPendingInvite(const ExpeditionInvite& invite, const std::string& add_name); void DzAddPlayer(Client* requester, const std::string& add_char_name, const std::string& swap_remove_name = {}); From 54a175b3efdc28473f97e483880d212711e67f71 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 6 Nov 2020 20:40:08 -0500 Subject: [PATCH 153/196] Cleanup unused arg in remove lockout method --- zone/client.cpp | 7 ++----- zone/client.h | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 361a10ccc..1fb124799 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9675,7 +9675,7 @@ void Client::AddExpeditionLockoutDuration( } void Client::RemoveExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, bool update_db, bool update_client) + 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) { @@ -9688,10 +9688,7 @@ void Client::RemoveExpeditionLockout( ExpeditionDatabase::DeleteCharacterLockout(CharacterID(), expedition_name, event_name); } - if (update_client) - { - SendExpeditionLockoutTimers(); - } + SendExpeditionLockoutTimers(); } void Client::RemoveAllExpeditionLockouts(const std::string& expedition_name, bool update_db) diff --git a/zone/client.h b/zone/client.h index 572170013..8d786eb6e 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1137,8 +1137,8 @@ public: 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 RemoveAllExpeditionLockouts(const std::string& expedition_name, bool update_db = false); - void RemoveExpeditionLockout( - const std::string& expedition_name, const std::string& event_name, bool update_db = false, bool update_client = true); + void RemoveExpeditionLockout(const std::string& expedition_name, + const std::string& event_name, bool update_db = false); void RequestPendingExpeditionInvite(); void SendExpeditionLockoutTimers(); void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; From 81e4bd6040dd09c9c2d476495eccb11ae53b708f Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 6 Nov 2020 20:48:21 -0500 Subject: [PATCH 154/196] Reduce expedition member status packet updates Only send update for expedition member status if it changes Avoids unnecessary packets when members zone --- zone/expedition.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 8e3465b7d..6c7101be6 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -634,6 +634,20 @@ void Expedition::UpdateMemberStatus(uint32_t update_member_id, ExpeditionMemberS status = ExpeditionMemberStatus::Online; } + if (update_member_id == m_leader.char_id) + { + m_leader.status = status; + } + + // if zone already had this member status cached avoid packet update to clients + auto it = std::find_if(m_members.begin(), m_members.end(), + [&](const ExpeditionMember& member) { return member.char_id == update_member_id; }); + + if (it != m_members.end() && it->status == status) + { + return; + } + auto outapp_member_status = CreateMemberListStatusPacket(member_data.name, status); for (auto& member : m_members) @@ -649,11 +663,6 @@ void Expedition::UpdateMemberStatus(uint32_t update_member_id, ExpeditionMemberS member_client->QueuePacket(outapp_member_status.get()); } } - - if (update_member_id == m_leader.char_id) - { - m_leader.status = status; - } } void Expedition::SendClientExpeditionInvite( From fc7d8a82e5be0a7a391389dfd19fb5fb90c3ae14 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 8 Nov 2020 21:27:46 -0500 Subject: [PATCH 155/196] Sort #dz list output by dz id Sort #dz expedition list by expedition id --- zone/command.cpp | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 39e6c98d5..2dc55221c 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -6838,28 +6838,39 @@ void command_dz(Client* c, const Seperator* sep) { if (strcasecmp(sep->arg[2], "list") == 0) { - c->Message(Chat::White, fmt::format("Total Active Expeditions: [{}]", zone->expedition_cache.size()).c_str()); + std::vector expeditions; for (const auto& expedition : zone->expedition_cache) { - auto leader_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format( - "#goto {}", expedition.second->GetLeaderName()), false, expedition.second->GetLeaderName()); - auto zone_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format( - "#zoneinstance {}", expedition.second->GetInstanceID()), false, "zone"); + expeditions.emplace_back(expedition.second.get()); + } - auto seconds = expedition.second->GetDynamicZone().GetSecondsRemaining(); + std::sort(expeditions.begin(), expeditions.end(), + [](const Expedition* lhs, const Expedition* rhs) { + return lhs->GetID() < rhs->GetID(); + }); + + c->Message(Chat::White, fmt::format("Total Active Expeditions: [{}]", expeditions.size()).c_str()); + for (const auto& expedition : expeditions) + { + auto leader_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format( + "#goto {}", expedition->GetLeaderName()), false, expedition->GetLeaderName()); + auto zone_saylink = EQ::SayLinkEngine::GenerateQuestSaylink(fmt::format( + "#zoneinstance {}", expedition->GetInstanceID()), false, "zone"); + + auto seconds = expedition->GetDynamicZone().GetSecondsRemaining(); c->Message(Chat::White, fmt::format( "expedition id: [{}] dz id: [{}] name: [{}] leader: [{}] {}: [{}]:[{}]:[{}]:[{}] members: [{}] remaining: [{:02}:{:02}:{:02}]", - expedition.second->GetID(), - expedition.second->GetDynamicZoneID(), - expedition.second->GetName(), + expedition->GetID(), + expedition->GetDynamicZoneID(), + expedition->GetName(), leader_saylink, zone_saylink, - ZoneName(expedition.second->GetDynamicZone().GetZoneID()), - expedition.second->GetDynamicZone().GetZoneID(), - expedition.second->GetInstanceID(), - expedition.second->GetDynamicZone().GetZoneVersion(), - expedition.second->GetMemberCount(), + ZoneName(expedition->GetDynamicZone().GetZoneID()), + expedition->GetDynamicZone().GetZoneID(), + expedition->GetInstanceID(), + expedition->GetDynamicZone().GetZoneVersion(), + expedition->GetMemberCount(), seconds / 3600, // hours (seconds / 60) % 60, // minutes seconds % 60 // seconds @@ -6918,7 +6929,8 @@ void command_dz(Client* c, const Seperator* sep) 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; + GROUP BY instance_list.id + ORDER BY dynamic_zones.id; ); auto results = database.QueryDatabase(query); From 4f9eaf75748d52f80b2754d4a34f164a3dc272fd Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 8 Nov 2020 22:31:41 -0500 Subject: [PATCH 156/196] Sync character lockouts when entering dz This removes the is_pending column from character lockouts table Synchronizing character lockout timers with the expedition's when zoning into the dynamic zone simplifies adding missing lockouts to new members. This also matches live behavior that replaces any character lockout timers from another expedition with ones from the current expedition --- utils/sql/git/required/wip_expeditions.sql | 1 - zone/client.cpp | 18 ++-- zone/client.h | 1 - zone/expedition.cpp | 86 ++++++++++++------- zone/expedition.h | 1 + zone/expedition_database.cpp | 98 +++------------------- zone/expedition_database.h | 21 +++-- 7 files changed, 83 insertions(+), 143 deletions(-) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 64f99eecd..5524a2919 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -48,7 +48,6 @@ CREATE TABLE `character_expedition_lockouts` ( `expire_time` DATETIME NOT NULL DEFAULT current_timestamp(), `duration` INT(10) UNSIGNED NOT NULL DEFAULT 0, `from_expedition_uuid` VARCHAR(36) NOT NULL, - `is_pending` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE INDEX `character_id_expedition_name_event_name` (`character_id`, `expedition_name`, `event_name`) ) diff --git a/zone/client.cpp b/zone/client.cpp index 1fb124799..06f04ec1b 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9563,16 +9563,18 @@ void Client::UpdateExpeditionInfoAndLockouts() // this is processed by client after entering a zone SendDzCompassUpdate(); + m_expedition_lockouts = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); + auto expedition = GetExpedition(); if (expedition) { expedition->SendClientExpeditionInfo(this); - // live only adds lockouts obtained during the active expedition to new + // live synchronizes lockouts obtained during the active expedition to // members once they zone into the expedition's dynamic zone instance if (expedition->GetDynamicZone().IsCurrentZoneDzInstance()) { - ExpeditionDatabase::AssignPendingLockouts(CharacterID(), expedition->GetName()); + expedition->SyncCharacterLockouts(CharacterID(), m_expedition_lockouts); expedition->SetMemberStatus(this, ExpeditionMemberStatus::InDynamicZone); } else @@ -9581,7 +9583,7 @@ void Client::UpdateExpeditionInfoAndLockouts() } } - LoadAllExpeditionLockouts(); + SendExpeditionLockoutTimers(); // ask world for any pending invite we saved from a previous zone RequestPendingExpeditionInvite(); @@ -9629,7 +9631,7 @@ void Client::AddExpeditionLockout(const ExpeditionLockoutTimer& lockout, bool up if (update_db) // for quest api { - ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { lockout }, true); + ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { lockout }); } SendExpeditionLockoutTimers(); @@ -9662,7 +9664,7 @@ void Client::AddExpeditionLockoutDuration( if (update_db) { - ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { *it }, true); + ExpeditionDatabase::InsertCharacterLockouts(CharacterID(), { *it }); } SendExpeditionLockoutTimers(); @@ -9753,12 +9755,6 @@ bool Client::HasExpeditionLockout( return (GetExpeditionLockout(expedition_name, event_name, include_expired) != nullptr); } -void Client::LoadAllExpeditionLockouts() -{ - m_expedition_lockouts = ExpeditionDatabase::LoadCharacterLockouts(CharacterID()); - SendExpeditionLockoutTimers(); -} - void Client::SendExpeditionLockoutTimers() { std::vector lockout_entries; diff --git a/zone/client.h b/zone/client.h index 8d786eb6e..f2b1892c9 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1143,7 +1143,6 @@ public: void SendExpeditionLockoutTimers(); void SetExpeditionID(uint32 expedition_id) { m_expedition_id = expedition_id; }; void SetPendingExpeditionInvite(ExpeditionInvite&& invite) { m_pending_expedition_invite = invite; } - void LoadAllExpeditionLockouts(); void UpdateExpeditionInfoAndLockouts(); void DzListTimers(); void SetDzRemovalTimer(bool enable_timer); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 6c7101be6..b9e54a9ea 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -298,7 +298,6 @@ void Expedition::SaveMembers(ExpeditionRequest& request) } ExpeditionDatabase::InsertMembers(m_id, m_members); - ExpeditionDatabase::DeleteAllMembersPendingLockouts(m_members); m_dynamiczone.SaveInstanceMembersToDatabase(member_ids); } @@ -555,7 +554,6 @@ void Expedition::RemoveAllMembers(bool enable_removal_timers) { m_dynamiczone.RemoveAllCharacters(enable_removal_timers); - ExpeditionDatabase::DeleteAllMembersPendingLockouts(m_members); ExpeditionDatabase::DeleteAllMembers(m_id); SendUpdatesToZoneMembers(true); @@ -849,39 +847,20 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: { SendLeaderMessage(leader_client, Chat::Yellow, EXPEDITION_INVITE_ACCEPTED, { add_client->GetName() }); - // insert pending lockouts client will receive when entering dynamic zone. - // 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; - for (const auto& lockout_iter : m_lockouts) + // replay timers are optionally added to new members on join with fresh expire time + if (m_add_replay_on_join) { - const ExpeditionLockoutTimer& lockout = lockout_iter.second; - if (lockout.IsFromExpedition(m_uuid) && - !add_client->HasExpeditionLockout(m_expedition_name, lockout.GetEventName())) + auto replay_lockout = m_lockouts.find(DZ_REPLAY_TIMER_NAME); + if (replay_lockout != m_lockouts.end() && + replay_lockout->second.IsFromExpedition(m_uuid) && + !add_client->HasExpeditionLockout(m_expedition_name, DZ_REPLAY_TIMER_NAME)) { - // replay timers are optionally added to new members immediately on - // join with a fresh expire time using the original duration. - if (lockout.IsReplayTimer()) - { - if (m_add_replay_on_join) - { - ExpeditionLockoutTimer replay_timer = lockout; - replay_timer.Reset(); - add_client->AddExpeditionLockout(replay_timer, true); - } - } - else if (!lockout.IsExpired()) - { - pending_lockouts.emplace_back(lockout); - } + ExpeditionLockoutTimer replay_timer = replay_lockout->second; // copy + replay_timer.Reset(); + add_client->AddExpeditionLockout(replay_timer, true); } } - ExpeditionDatabase::InsertCharacterLockouts( - add_client->CharacterID(), pending_lockouts, false, true); - if (was_swap_invite) { SwapMember(add_client, swap_remove_name); @@ -1237,7 +1216,6 @@ void Expedition::ProcessMemberRemoved(const std::string& removed_char_name, uint // live doesn't clear expedition info on clients removed while inside dz. // it instead let's the dz kick timer do it even if character zones out // before it triggers. for simplicity we'll always clear immediately - ExpeditionDatabase::DeletePendingLockouts(member_client->CharacterID()); member_client->SetExpeditionID(0); member_client->SendDzCompassUpdate(); member_client->QueuePacket(CreateInfoPacket(true).get()); @@ -1734,7 +1712,7 @@ void Expedition::AddLockoutByCharacterID( if (character_id) { auto lockout = ExpeditionLockoutTimer::CreateLockout(expedition_name, event_name, seconds, uuid); - ExpeditionDatabase::InsertCharacterLockouts(character_id, { lockout }, true); + ExpeditionDatabase::InsertCharacterLockouts(character_id, { lockout }); SendWorldCharacterLockout(character_id, lockout, false); } } @@ -2256,3 +2234,47 @@ void Expedition::SendMembersExpireWarning(uint32_t minutes_remaining) } } } + +void Expedition::SyncCharacterLockouts( + uint32_t character_id, std::vector& client_lockouts) +{ + // adds missing event lockouts to client for this expedition and replaces + // client timers that are both shorter and from another expedition + BenchTimer benchmark; + + bool modified = false; + + for (const auto& lockout_iter : m_lockouts) + { + const ExpeditionLockoutTimer& lockout = lockout_iter.second; + if (lockout.IsReplayTimer() || lockout.IsExpired() || lockout.GetExpeditionUUID() != m_uuid) + { + continue; + } + + auto client_lockout_iter = std::find_if(client_lockouts.begin(), client_lockouts.end(), + [&](const ExpeditionLockoutTimer& client_lockout) { + return client_lockout.IsSameLockout(lockout); + }); + + if (client_lockout_iter == client_lockouts.end()) + { + modified = true; + client_lockouts.emplace_back(lockout); // insert missing + } + else if (client_lockout_iter->GetSecondsRemaining() < lockout.GetSecondsRemaining() && + client_lockout_iter->GetExpeditionUUID() != m_uuid) + { + modified = true; + client_lockouts.erase(client_lockout_iter); + client_lockouts.emplace_back(lockout); // replaced existing + } + } + + if (modified) + { + ExpeditionDatabase::InsertCharacterLockouts(character_id, client_lockouts); + } + + LogExpeditionsDetail("Syncing character lockouts with expedition took [{}] s", benchmark.elapsed()); +} diff --git a/zone/expedition.h b/zone/expedition.h index 5a0d6da56..7e6c4a048 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -136,6 +136,7 @@ public: bool HasReplayLockout(); void RemoveLockout(const std::string& event_name); void SetReplayLockoutOnMemberJoin(bool add_on_join, bool update_db = false); + void SyncCharacterLockouts(uint32_t character_id, std::vector& client_lockouts); void UpdateLockoutDuration(const std::string& event_name, uint32_t seconds, bool members_only = true); bool CanClientLootCorpse(Client* client, uint32_t npc_type_id, uint32_t spawn_id); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 782e96517..69a1ebfa7 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -110,7 +110,7 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts(ui UNIX_TIMESTAMP(expire_time), duration FROM character_expedition_lockouts - WHERE character_id = {} AND is_pending = FALSE AND expire_time > NOW(); + WHERE character_id = {} AND expire_time > NOW(); ), character_id); auto results = database.QueryDatabase(query); @@ -147,7 +147,6 @@ std::vector ExpeditionDatabase::LoadCharacterLockouts( FROM character_expedition_lockouts WHERE character_id = {} - AND is_pending = FALSE AND expire_time > NOW() AND expedition_name = '{}'; ), character_id, EscapeString(expedition_name)); @@ -250,7 +249,6 @@ MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( FROM character_data LEFT JOIN character_expedition_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 @@ -306,7 +304,6 @@ void ExpeditionDatabase::DeleteCharacterLockout( DELETE FROM character_expedition_lockouts WHERE character_id = {} - AND is_pending = FALSE AND expedition_name = '{}' AND event_name = '{}'; ), character_id, EscapeString(expedition_name), EscapeString(event_name)); @@ -334,7 +331,6 @@ void ExpeditionDatabase::DeleteMembersLockout( DELETE FROM character_expedition_lockouts WHERE character_id IN ({}) - AND is_pending = FALSE AND expedition_name = '{}' AND event_name = '{}'; ), query_character_ids, EscapeString(expedition_name), EscapeString(event_name)); @@ -343,57 +339,6 @@ void ExpeditionDatabase::DeleteMembersLockout( } } -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 character_expedition_lockouts - SET is_pending = FALSE - WHERE - character_id = {} - AND is_pending = TRUE - AND expedition_name = '{}'; - ), character_id, EscapeString(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 character_expedition_lockouts - WHERE character_id = {} AND is_pending = TRUE; - ), character_id); - - database.QueryDatabase(query); -} - -void ExpeditionDatabase::DeleteAllMembersPendingLockouts(const std::vector& members) -{ - LogExpeditionsDetail("Deleting pending lockouts for [{}] characters", members.size()); - - 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 character_expedition_lockouts - WHERE character_id IN ({}) AND is_pending = TRUE; - ), query_character_ids); - - database.QueryDatabase(query); - } -} - void ExpeditionDatabase::DeleteLockout(uint32_t expedition_id, const std::string& event_name) { LogExpeditionsDetail("Deleting expedition [{}] lockout event [{}]", expedition_id, event_name); @@ -424,9 +369,8 @@ uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_i return expedition_id; } -void ExpeditionDatabase::InsertCharacterLockouts( - uint32_t character_id, const std::vector& lockouts, - bool replace_timer, bool is_pending) +void ExpeditionDatabase::InsertCharacterLockouts(uint32_t character_id, + const std::vector& lockouts) { LogExpeditionsDetail("Inserting [{}] lockouts for character [{}]", lockouts.size(), character_id); @@ -434,14 +378,13 @@ void ExpeditionDatabase::InsertCharacterLockouts( for (const auto& lockout : lockouts) { fmt::format_to(std::back_inserter(insert_values), - "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}', {}),", + "({}, FROM_UNIXTIME({}), {}, '{}', '{}', '{}'),", character_id, lockout.GetExpireTime(), lockout.GetDuration(), lockout.GetExpeditionUUID(), EscapeString(lockout.GetExpeditionName()), - EscapeString(lockout.GetEventName()), - is_pending + EscapeString(lockout.GetEventName()) ); } @@ -449,34 +392,15 @@ void ExpeditionDatabase::InsertCharacterLockouts( { insert_values.pop_back(); // trailing comma - std::string on_duplicate; - if (replace_timer) - { - on_duplicate = SQL( - from_expedition_uuid = VALUES(from_expedition_uuid), - expire_time = VALUES(expire_time), - duration = VALUES(duration) - ); - } - else - { - on_duplicate = "character_id = VALUES(character_id)"; - } - auto query = fmt::format(SQL( INSERT INTO character_expedition_lockouts - ( - character_id, - expire_time, - duration, - from_expedition_uuid, - expedition_name, - event_name, - is_pending - ) + (character_id, expire_time, duration, from_expedition_uuid, expedition_name, event_name) VALUES {} - ON DUPLICATE KEY UPDATE {}; - ), insert_values, on_duplicate); + ON DUPLICATE KEY UPDATE + from_expedition_uuid = VALUES(from_expedition_uuid), + expire_time = VALUES(expire_time), + duration = VALUES(duration); + ), insert_values); database.QueryDatabase(query); } diff --git a/zone/expedition_database.h b/zone/expedition_database.h index a774ac2c5..2efeba2f6 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -52,23 +52,22 @@ namespace ExpeditionDatabase void DeleteMember(uint32_t expedition_id, uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id); void DeleteAllCharacterLockouts(uint32_t character_id, const std::string& expedition_name); - void DeleteCharacterLockout(uint32_t character_id, const std::string& expedition_name, const std::string& event_name); + void DeleteCharacterLockout(uint32_t character_id, const std::string& expedition_name, + const std::string& event_name); void DeleteLockout(uint32_t expedition_id, const std::string& event_name); - void DeleteMembersLockout( - const std::vector& 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); - void DeleteAllMembersPendingLockouts(const std::vector& members); + void DeleteMembersLockout(const std::vector& members, + const std::string& expedition_name, const std::string& event_name); uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); std::pair, std::vector> GetMembersLockout( const std::vector& members, const std::string& expedition_name, const std::string& event_name); - void InsertCharacterLockouts( - uint32_t character_id, const std::vector& lockouts, - bool replace_timer, bool is_pending = false); - void InsertMembersLockout(const std::vector& members, const ExpeditionLockoutTimer& lockout); + void InsertCharacterLockouts(uint32_t character_id, + const std::vector& lockouts); + void InsertMembersLockout(const std::vector& members, + const ExpeditionLockoutTimer& lockout); void InsertLockout(uint32_t expedition_id, const ExpeditionLockoutTimer& lockout); - void InsertLockouts(uint32_t expedition_id, const std::unordered_map& lockouts); + void InsertLockouts(uint32_t expedition_id, + const std::unordered_map& lockouts); void InsertMember(uint32_t expedition_id, uint32_t character_id); void InsertMembers(uint32_t expedition_id, const std::vector& members); void UpdateLockState(uint32_t expedition_id, bool is_locked); From 8b2b2db8486ef244c4d1f1b3645cb9797a3c6480 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 9 Nov 2020 20:09:16 -0500 Subject: [PATCH 157/196] Move offline players to dz safereturn This is an experimental change which allows members to log in at the safe return if they were offline when the expedition was deleted. Prior to this they would log in at bind instead Partially reverts commit 32c69d235d7fc8b816fa598b499be47d451ddba5 Removed expedition members are no longer hard deleted from db --- utils/sql/git/required/wip_expeditions.sql | 1 + world/expedition_database.cpp | 37 ++++++++++++++++++++-- world/expedition_database.h | 1 + world/expedition_state.cpp | 1 + world/main.cpp | 8 ++--- zone/expedition_database.cpp | 20 ++++++++---- 6 files changed, 55 insertions(+), 13 deletions(-) diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/wip_expeditions.sql index 5524a2919..a51d07095 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/wip_expeditions.sql @@ -33,6 +33,7 @@ CREATE TABLE `expedition_members` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `expedition_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, `character_id` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `is_current_member` TINYINT(3) UNSIGNED NOT NULL DEFAULT 1, PRIMARY KEY (`id`), UNIQUE INDEX `expedition_id_character_id` (`expedition_id`, `character_id`) ) diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp index e3755befb..f9df67918 100644 --- a/world/expedition_database.cpp +++ b/world/expedition_database.cpp @@ -32,7 +32,7 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() LEFT JOIN instance_list ON dynamic_zones.instance_id = instance_list.id LEFT JOIN ( - SELECT expedition_id, COUNT(*) member_count + SELECT expedition_id, COUNT(IF(is_current_member = TRUE, 1, NULL)) member_count FROM expedition_members GROUP BY expedition_id ) expedition_members @@ -40,6 +40,7 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() WHERE instance_list.id IS NULL OR expedition_members.member_count IS NULL + OR expedition_members.member_count = 0 OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); ); @@ -51,7 +52,12 @@ void ExpeditionDatabase::PurgeExpiredExpeditions() { expedition_ids.emplace_back(static_cast(strtoul(row[0], nullptr, 10))); } - ExpeditionDatabase::DeleteExpeditions(expedition_ids); + + if (!expedition_ids.empty()) + { + ExpeditionDatabase::MoveMembersToSafeReturn(expedition_ids); + ExpeditionDatabase::DeleteExpeditions(expedition_ids); + } } } @@ -83,6 +89,7 @@ std::vector ExpeditionDatabase::LoadExpeditions(uint32_t select_expe INNER JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id INNER JOIN expedition_members ON expedition_members.expedition_id = expeditions.id + AND expedition_members.is_current_member = TRUE ); if (select_expedition_id != 0) @@ -180,3 +187,29 @@ void ExpeditionDatabase::UpdateLeaderID(uint32_t expedition_id, uint32_t leader_ database.QueryDatabase(query); } + +void ExpeditionDatabase::MoveMembersToSafeReturn(const std::vector& expedition_ids) +{ + LogExpeditionsDetail("Moving members from [{}] expedition(s) to safereturn", expedition_ids.size()); + + // only offline members still in expired dz zones should be updated here + std::string query = fmt::format(SQL( + UPDATE character_data + INNER JOIN expedition_members ON character_data.id = expedition_members.character_id + INNER JOIN expeditions ON expedition_members.expedition_id = expeditions.id + INNER JOIN dynamic_zones ON expeditions.dynamic_zone_id = dynamic_zones.id + INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id + AND character_data.zone_instance = instance_list.id + AND character_data.zone_id = instance_list.zone + SET + zone_id = IF(safe_return_zone_id > 0, safe_return_zone_id, zone_id), + zone_instance = IF(safe_return_zone_id > 0, 0, zone_instance), + x = IF(safe_return_zone_id > 0, safe_return_x, x), + y = IF(safe_return_zone_id > 0, safe_return_y, y), + z = IF(safe_return_zone_id > 0, safe_return_z, z), + heading = IF(safe_return_zone_id > 0, safe_return_heading, heading) + WHERE expeditions.id IN ({}); + ), fmt::join(expedition_ids, ",")); + + database.QueryDatabase(query); +} diff --git a/world/expedition_database.h b/world/expedition_database.h index 302dba3e2..16341210c 100644 --- a/world/expedition_database.h +++ b/world/expedition_database.h @@ -31,6 +31,7 @@ namespace ExpeditionDatabase void DeleteExpeditions(const std::vector& expedition_ids); std::vector LoadExpeditions(uint32_t select_expedition_id = 0); Expedition LoadExpedition(uint32_t expedition_id); + void MoveMembersToSafeReturn(const std::vector& expedition_ids); void PurgeExpiredExpeditions(); void PurgeExpiredCharacterLockouts(); void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); diff --git a/world/expedition_state.cpp b/world/expedition_state.cpp index df09a5f2f..332231f1b 100644 --- a/world/expedition_state.cpp +++ b/world/expedition_state.cpp @@ -152,6 +152,7 @@ void ExpeditionState::Process() if (!expedition_ids.empty()) { + ExpeditionDatabase::MoveMembersToSafeReturn(expedition_ids); ExpeditionDatabase::DeleteExpeditions(expedition_ids); } } diff --git a/world/main.cpp b/world/main.cpp index 01682b26a..b426d13ff 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -425,16 +425,16 @@ int main(int argc, char** argv) { adventure_manager.LoadLeaderboardInfo(); + LogInfo("Purging expired expeditions"); + ExpeditionDatabase::PurgeExpiredExpeditions(); + ExpeditionDatabase::PurgeExpiredCharacterLockouts(); + LogInfo("Purging expired instances"); database.PurgeExpiredInstances(); Timer PurgeInstanceTimer(450000); PurgeInstanceTimer.Start(450000); - LogInfo("Purging expired expeditions"); - ExpeditionDatabase::PurgeExpiredExpeditions(); - ExpeditionDatabase::PurgeExpiredCharacterLockouts(); - LogInfo("Loading active expeditions"); expedition_state.LoadActiveExpeditions(); diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 69a1ebfa7..7d1fd7a3c 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -70,6 +70,7 @@ std::string ExpeditionDatabase::LoadExpeditionsSelectQuery() FROM expeditions INNER JOIN character_data ON expeditions.leader_id = character_data.id INNER JOIN expedition_members ON expeditions.id = expedition_members.expedition_id + AND expedition_members.is_current_member = TRUE INNER JOIN character_data member_data ON expedition_members.character_id = member_data.id )); } @@ -251,7 +252,9 @@ MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest( ON character_data.id = lockout.character_id AND lockout.expire_time > NOW() AND lockout.expedition_name = '{}' - LEFT JOIN expedition_members member ON character_data.id = member.character_id + 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 FIELD(character_data.name, {}) ), EscapeString(expedition_name), in_character_names_query, in_character_names_query); @@ -357,7 +360,8 @@ uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_i uint32_t expedition_id = 0; auto query = fmt::format(SQL( - SELECT expedition_id FROM expedition_members WHERE character_id = {}; + SELECT expedition_id FROM expedition_members + WHERE character_id = {} AND is_current_member = TRUE; ), character_id); auto results = database.QueryDatabase(query); @@ -519,7 +523,7 @@ void ExpeditionDatabase::InsertMember(uint32_t expedition_id, uint32_t character (expedition_id, character_id) VALUES ({}, {}) - ON DUPLICATE KEY UPDATE character_id = VALUES(character_id); + ON DUPLICATE KEY UPDATE is_current_member = TRUE; ), expedition_id, character_id); database.QueryDatabase(query); @@ -546,7 +550,8 @@ void ExpeditionDatabase::InsertMembers( auto query = fmt::format(SQL( INSERT INTO expedition_members (expedition_id, character_id) - VALUES {}; + VALUES {} + ON DUPLICATE KEY UPDATE is_current_member = TRUE; ), insert_values); database.QueryDatabase(query); @@ -569,7 +574,8 @@ void ExpeditionDatabase::DeleteMember(uint32_t expedition_id, uint32_t character LogExpeditionsDetail("Removing member [{}] from expedition [{}]", character_id, expedition_id); auto query = fmt::format(SQL( - DELETE FROM expedition_members WHERE expedition_id = {} AND character_id = {}; + UPDATE expedition_members SET is_current_member = FALSE + WHERE expedition_id = {} AND character_id = {}; ), expedition_id, character_id); database.QueryDatabase(query); @@ -577,10 +583,10 @@ void ExpeditionDatabase::DeleteMember(uint32_t expedition_id, uint32_t character void ExpeditionDatabase::DeleteAllMembers(uint32_t expedition_id) { - LogExpeditionsDetail("Updating all members of expedition [{}] as removed", expedition_id); + LogExpeditionsDetail("Removing all members of expedition [{}]", expedition_id); auto query = fmt::format(SQL( - DELETE FROM expedition_members WHERE expedition_id = {}; + UPDATE expedition_members SET is_current_member = FALSE WHERE expedition_id = {}; ), expedition_id); database.QueryDatabase(query); From a312cd6e1d4e1ef3f61de29dd0a4b6a003ee955c Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Wed, 11 Nov 2020 22:25:15 -0500 Subject: [PATCH 158/196] Don't sync character lockout uuid Fixes regression that caused loot event apis to stop working --- zone/expedition.cpp | 7 ++++--- zone/expedition_lockout_timer.h | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index b9e54a9ea..cc88b8c70 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -2238,7 +2238,7 @@ void Expedition::SendMembersExpireWarning(uint32_t minutes_remaining) void Expedition::SyncCharacterLockouts( uint32_t character_id, std::vector& client_lockouts) { - // adds missing event lockouts to client for this expedition and replaces + // adds missing event lockouts to client for this expedition and updates // client timers that are both shorter and from another expedition BenchTimer benchmark; @@ -2265,9 +2265,10 @@ void Expedition::SyncCharacterLockouts( else if (client_lockout_iter->GetSecondsRemaining() < lockout.GetSecondsRemaining() && client_lockout_iter->GetExpeditionUUID() != m_uuid) { + // only update lockout timer not uuid so loot event apis still work modified = true; - client_lockouts.erase(client_lockout_iter); - client_lockouts.emplace_back(lockout); // replaced existing + client_lockout_iter->SetDuration(lockout.GetDuration()); + client_lockout_iter->SetExpireTime(lockout.GetExpireTime()); } } diff --git a/zone/expedition_lockout_timer.h b/zone/expedition_lockout_timer.h index d0fa4476b..62c5e4705 100644 --- a/zone/expedition_lockout_timer.h +++ b/zone/expedition_lockout_timer.h @@ -60,6 +60,8 @@ public: bool IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const; bool IsSameLockout(const std::string& expedition_name, const std::string& event_name) const; void Reset() { m_expire_time = std::chrono::system_clock::now() + m_duration; } + void SetDuration(uint32_t seconds) { m_duration = std::chrono::seconds(seconds); } + void SetExpireTime(uint64_t expire_time) { m_expire_time = std::chrono::system_clock::from_time_t(expire_time); } void SetUUID(const std::string& uuid) { m_expedition_uuid = uuid; } private: From 33e5bd0b6751a019975384f38a99caf768936d35 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Tue, 22 Dec 2020 19:49:26 -0500 Subject: [PATCH 159/196] Assign db version to expedition schema --- common/version.h | 2 +- utils/sql/db_update_manifest.txt | 1 + ...s.sql => 2020_12_22_expedition_system.sql} | 25 +++++++++++++++++++ utils/sql/git/required/wip_dynamiczones.sql | 24 ------------------ 4 files changed, 27 insertions(+), 25 deletions(-) rename utils/sql/git/required/{wip_expeditions.sql => 2020_12_22_expedition_system.sql} (68%) delete mode 100644 utils/sql/git/required/wip_dynamiczones.sql diff --git a/common/version.h b/common/version.h index 82fe629b6..d6647f2c1 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9158 +#define CURRENT_BINARY_DATABASE_VERSION 9159 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9027 diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 9f005365b..bdc41a337 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -412,6 +412,7 @@ 9156|2020_08_16_virtual_zonepoints.sql|SHOW COLUMNS from `zone_points` LIKE 'is_virtual'|empty| 9157|2020_09_02_pet_taunting.sql|SHOW COLUMNS from `character_pet_info` LIKE 'taunting'|empty| 9158|2020_12_09_underworld.sql|SHOW COLUMNS from `zone` LIKE 'underworld_teleport_index'|empty| +9159|2020_12_22_expedition_system.sql|SELECT * FROM db_version WHERE version >= 9159|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/wip_expeditions.sql b/utils/sql/git/required/2020_12_22_expedition_system.sql similarity index 68% rename from utils/sql/git/required/wip_expeditions.sql rename to utils/sql/git/required/2020_12_22_expedition_system.sql index a51d07095..820940500 100644 --- a/utils/sql/git/required/wip_expeditions.sql +++ b/utils/sql/git/required/2020_12_22_expedition_system.sql @@ -55,3 +55,28 @@ CREATE TABLE `character_expedition_lockouts` ( COLLATE='latin1_swedish_ci' ENGINE=InnoDB ; + +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`) +) +COLLATE='utf8mb4_general_ci' +ENGINE=InnoDB +; diff --git a/utils/sql/git/required/wip_dynamiczones.sql b/utils/sql/git/required/wip_dynamiczones.sql deleted file mode 100644 index dcc4575a2..000000000 --- a/utils/sql/git/required/wip_dynamiczones.sql +++ /dev/null @@ -1,24 +0,0 @@ -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`) -) -COLLATE='utf8mb4_general_ci' -ENGINE=InnoDB -; From f854137ca029fa6e90cab5133bd8ee6194f8e1cb Mon Sep 17 00:00:00 2001 From: Trust Date: Fri, 4 Dec 2020 19:26:16 -0500 Subject: [PATCH 160/196] [BUG] Fix for Group Leader Disband Issue Added public/private class for oldleadername so we can save the previous leader name when the entity is destroyed then allow us to transfer leadership. Adjusted DelmemberOOZ and in zone functions to include removal of the old leader when disbanding. --- common/eqemu_logsys.h | 2 + common/servertalk.h | 12 ++++ world/zoneserver.cpp | 8 +++ zone/entity.cpp | 38 +++++++++++-- zone/entity.h | 3 +- zone/groups.cpp | 126 +++++++++++++++++++++++++----------------- zone/groups.h | 4 ++ zone/worldserver.cpp | 13 ++++- 8 files changed, 147 insertions(+), 59 deletions(-) diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index d28478a97..c8f724a17 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -120,6 +120,7 @@ namespace Logs { Loot, Expeditions, DynamicZones, + Group, MaxCategoryID /* Don't Remove this */ }; @@ -199,6 +200,7 @@ namespace Logs { "Loot", "Expeditions", "DynamicZones", + "Group", }; } diff --git a/common/servertalk.h b/common/servertalk.h index 40be635ad..816807b56 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -80,6 +80,7 @@ #define ServerOP_UpdateSpawn 0x003f #define ServerOP_SpawnStatusChange 0x0040 #define ServerOP_DropClient 0x0041 // DropClient +#define ServerOP_ChangeGroupLeader 0x0042 #define ServerOP_ReloadTasks 0x0060 #define ServerOP_DepopAllPlayersCorpses 0x0061 #define ServerOP_ReloadTitles 0x0062 @@ -861,6 +862,7 @@ struct ServerGroupLeave_Struct { uint16 instance_id; uint32 gid; char member_name[64]; //kick this member from the group + bool checkleader; }; struct ServerGroupJoin_Struct { @@ -870,10 +872,20 @@ struct ServerGroupJoin_Struct { char member_name[64]; //this person is joining the group }; +struct ServerGroupLeader_Struct { + uint32 zoneid; + uint16 instance_id; + uint32 gid; + char leader_name[64]; + char oldleader_name[64]; +}; + struct ServerForceGroupUpdate_Struct { uint32 origZoneID; uint16 instance_id; uint32 gid; + char leader_name[64]; + char oldleader_name[64]; }; struct ServerGroupChannelMessage_Struct { diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index f09d08db1..58bd9f177 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -277,6 +277,14 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } + case ServerOP_ChangeGroupLeader: { + if (pack->size != sizeof(ServerGroupLeader_Struct)) { + break; + } + zoneserver_list.SendPacket(pack); //bounce it to all zones + break; + } + case ServerOP_RaidAdd: { if (pack->size != sizeof(ServerRaidGeneralAction_Struct)) break; diff --git a/zone/entity.cpp b/zone/entity.cpp index 1ad03eea0..51d6043a4 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -4209,7 +4209,7 @@ void EntityList::ForceGroupUpdate(uint32 gid) } } -void EntityList::SendGroupLeave(uint32 gid, const char *name) +void EntityList::SendGroupLeave(uint32 gid, const char *name, bool checkleader) { auto it = client_list.begin(); while (it != client_list.end()) { @@ -4225,13 +4225,39 @@ void EntityList::SendGroupLeave(uint32 gid, const char *name) gj->action = groupActLeave; strcpy(gj->yourname, c->GetName()); Mob *Leader = g->GetLeader(); - if (Leader) + if (Leader) { Leader->CastToClient()->GetGroupAAs(&gj->leader_aas); + } c->QueuePacket(outapp); safe_delete(outapp); - g->DelMemberOOZ(name); - if (g->IsLeader(c) && c->IsLFP()) + g->DelMemberOOZ(name, checkleader); + if (g->IsLeader(c) && c->IsLFP()) { c->UpdateLFP(); + } + } + } + } + ++it; + } +} + +void EntityList::SendGroupLeader(uint32 gid, const char *lname, const char *oldlname) +{ + auto it = client_list.begin(); + while (it != client_list.end()) { + if (it->second){ + Group *g = nullptr; + g = it->second->GetGroup(); + if (g) { + if (g->GetID() == gid) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GroupUpdate,sizeof(GroupJoin_Struct)); + GroupJoin_Struct* gj = (GroupJoin_Struct*) outapp->pBuffer; + gj->action = groupActMakeLeader; + strcpy(gj->membername, lname); + strcpy(gj->yourname, oldlname); + it->second->QueuePacket(outapp); + Log(Logs::Detail, Logs::Group, "SendGroupLeader(): Entity loop leader update packet sent to: %s .", it->second->GetName()); + safe_delete(outapp); } } } @@ -4254,9 +4280,9 @@ void EntityList::SendGroupJoin(uint32 gid, const char *name) gj->action = groupActJoin; strcpy(gj->yourname, it->second->GetName()); Mob *Leader = g->GetLeader(); - if (Leader) + if (Leader) { Leader->CastToClient()->GetGroupAAs(&gj->leader_aas); - + } it->second->QueuePacket(outapp); safe_delete(outapp); } diff --git a/zone/entity.h b/zone/entity.h index b7f90b6f9..ec617acf7 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -478,8 +478,9 @@ public: void CameraEffect(uint32 duration, uint32 intensity); Mob* GetClosestMobByBodyType(Mob* sender, bodyType BodyType); void ForceGroupUpdate(uint32 gid); - void SendGroupLeave(uint32 gid, const char *name); + void SendGroupLeave(uint32 gid, const char *name, bool checkleader); void SendGroupJoin(uint32 gid, const char *name); + void SendGroupLeader(uint32 gid, const char *lname, const char *oldlname); void SaveAllClientsTaskState(); void ReloadAllClientsTaskState(int TaskID=0); diff --git a/zone/groups.cpp b/zone/groups.cpp index 4b8e25d09..abc868460 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -59,8 +59,12 @@ Group::Group(uint32 gid) } if(gid != 0) { - if(!LearnMembers()) + if(!LearnMembers()) { SetID(0); + } + if(GetLeader() != nullptr) { + SetOldLeaderName(GetLeaderName()); + } } for(int i = 0; i < MAX_MARKED_NPCS; ++i) MarkedNPCs[i] = 0; @@ -78,6 +82,8 @@ Group::Group(Mob* leader) members[0] = leader; leader->SetGrouped(true); SetLeader(leader); + SetOldLeaderName(leader->GetName()); + Log(Logs::Detail, Logs::Group, "Group:Group() Setting OldLeader to: %s and Leader to: %s", GetOldLeaderName(), leader->GetName()); AssistTargetID = 0; TankTargetID = 0; PullerTargetID = 0; @@ -604,39 +610,61 @@ void Group::SendGroupJoinOOZ(Mob* NewMember) { } -bool Group::DelMemberOOZ(const char *Name) { +bool Group::DelMemberOOZ(const char *Name, bool checkleader) { - if(!Name) return false; + if (!Name) return false; + bool removed = false; // If a member out of zone has disbanded, clear out their name. - // - for(unsigned int i = 0; i < MAX_GROUP_MEMBERS; i++) { - if(!strcasecmp(Name, membername[i])) + for (unsigned int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (!strcasecmp(Name, membername[i])) { // This shouldn't be called if the member is in this zone. - if(!members[i]) { - if(!strncmp(GetLeaderName(), Name, 64)) - { + if (!members[i]) { + if (!strncmp(GetLeaderName(), Name, 64)) { //TODO: Transfer leadership if leader disbands OOZ. UpdateGroupAAs(); } - memset(membername[i], 0, 64); - MemberRoles[i] = 0; - if(GroupCount() < 3) - { + if (GroupCount() < 3) { UnDelegateMarkNPC(NPCMarkerName.c_str()); - if (GetLeader() && GetLeader()->IsClient() && GetLeader()->CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoD) { - UnDelegateMainAssist(MainAssistName.c_str()); + if (GetLeader() && GetLeader()->IsClient() && + GetLeader()->CastToClient()->ClientVersion() < EQ::versions::ClientVersion::SoD) { + UnDelegateMainAssist(MainAssistName.c_str()); } ClearAllNPCMarks(); } - if (Name == mentoree_name) + if (Name == mentoree_name) { ClearGroupMentor(); - return true; + } + + memset(membername[i], 0, 64); + MemberRoles[i] = 0; + removed = true; + Log(Logs::Detail, Logs::Group, "DelMemberOOZ: Removed Member: %s", Name); + break; } + } } - return false; + if (GroupCount() < 2) { + DisbandGroup(); + return true; + } + + if (checkleader) { + Log(Logs::Detail, Logs::Group, "DelMemberOOZ: Checking leader..."); + if (strcmp(GetOldLeaderName(), Name) == 0 && GroupCount() >= 2) { + for (uint32 nl = 0; nl < MAX_GROUP_MEMBERS; nl++) { + if (members[nl]) { + if (members[nl]->IsClient()) { + ChangeLeader(members[nl]); + break; + } + } + } + } + } + return removed; } bool Group::DelMember(Mob* oldmember, bool ignoresender) @@ -646,16 +674,6 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender) return false; } - // TODO: fix this shit - // okay, so there is code below that tries to handle this. It does not. - // So instead of figuring it out now, lets just disband the group so the client doesn't - // sit there with a broken group and there isn't any group leader shuffling going on - // since the code below doesn't work. - if (oldmember == GetLeader()) { - DisbandGroup(); - return true; - } - for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) { if (members[i] == oldmember) @@ -664,44 +682,36 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender) membername[i][0] = '\0'; memset(membername[i],0,64); MemberRoles[i] = 0; + Log(Logs::Detail, Logs::Group, "DelMember: Removed Member: %s", oldmember->GetCleanName()); break; } } - /* This may seem pointless but the case above does not cover the following situation: - * Group has Leader a, member b, member c - * b and c are out of zone - * a disconnects/quits - * b or c zone back in and disconnects/quits - * a is still "leader" from GetLeader()'s perspective and will crash the zone when we DelMember(b) - * Ultimately we should think up a better solution to this. - */ - if(oldmember == GetLeader()) + if(GroupCount() < 2) { - SetLeader(nullptr); + DisbandGroup(); + return true; } - //handle leader quitting group gracefully - if (oldmember == GetLeader() && GroupCount() >= 2) + // If the leader has quit and we have 2 or more players left in group, we want to first check the zone the old leader was in for a new leader. + // If a suitable replacement cannot be found, we need to go out of zone. If checkleader remains true after this method completes, another + // loop will be run in DelMemberOOZ. + bool checkleader = true; + if (strcmp(GetOldLeaderName(),oldmember->GetCleanName()) == 0 && GroupCount() >= 2) { for(uint32 nl = 0; nl < MAX_GROUP_MEMBERS; nl++) { - if(members[nl]) + if(members[nl]) { if (members[nl]->IsClient()) { ChangeLeader(members[nl]); + checkleader = false; break; } } } } - - if (!GetLeaderName()) - { - DisbandGroup(); - return true; - } auto pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct)); ServerGroupLeave_Struct* gl = (ServerGroupLeave_Struct*)pack->pBuffer; @@ -709,6 +719,7 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender) gl->zoneid = zone->GetZoneID(); gl->instance_id = zone->GetInstanceID(); strcpy(gl->member_name, oldmember->GetCleanName()); + gl->checkleader = checkleader; worldserver.SendPacket(pack); safe_delete(pack); @@ -800,6 +811,7 @@ bool Group::DelMember(Mob* oldmember, bool ignoresender) Bot::UpdateGroupCastingRoles(this); #endif + safe_delete(outapp); return true; } @@ -2330,17 +2342,16 @@ void Group::ChangeLeader(Mob* newleader) // this changes the current group leader, notifies other members, and updates leadship AA // if the new leader is invalid, do nothing - if (!newleader || !newleader->IsClient()) + if (!newleader) { return; - - Mob* oldleader = GetLeader(); + } auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct)); GroupJoin_Struct* gu = (GroupJoin_Struct*) outapp->pBuffer; gu->action = groupActMakeLeader; strcpy(gu->membername, newleader->GetName()); - strcpy(gu->yourname, oldleader->GetName()); + strcpy(gu->yourname, GetOldLeaderName()); SetLeader(newleader); database.SetGroupLeaderName(GetID(), newleader->GetName()); UpdateGroupAAs(); @@ -2352,9 +2363,22 @@ void Group::ChangeLeader(Mob* newleader) members[i]->CastToClient()->SendGroupLeaderChangePacket(newleader->GetName()); members[i]->CastToClient()->QueuePacket(outapp); + Log(Logs::Detail, Logs::Group, "ChangeLeader(): Local leader update packet sent to: %s .", members[i]->GetName()); } } safe_delete(outapp); + + Log(Logs::Detail, Logs::Group, "ChangeLeader(): Old Leader is: %s New leader is: %s", GetOldLeaderName(), newleader->GetName()); + + ServerPacket* pack = new ServerPacket(ServerOP_ChangeGroupLeader, sizeof(ServerGroupLeader_Struct)); + ServerGroupLeader_Struct* fgu = (ServerGroupLeader_Struct*)pack->pBuffer; + fgu->zoneid = zone->GetZoneID(); + fgu->gid = GetID(); + strcpy(fgu->leader_name, newleader->GetName()); + strcpy(fgu->oldleader_name, GetOldLeaderName()); + worldserver.SendPacket(pack); + + SetOldLeaderName(newleader->GetName()); } const char *Group::GetClientNameByIndex(uint8 index) diff --git a/zone/groups.h b/zone/groups.h index 57dfdbfb6..c27f9d59a 100644 --- a/zone/groups.h +++ b/zone/groups.h @@ -37,6 +37,8 @@ public: GroupIDConsumer() { id = 0; } GroupIDConsumer(uint32 gid) { id = gid; } inline const uint32 GetID() const { return id; } + void SetOldLeaderName(const char* oldleader) { strcpy(oldleadername, oldleader); } + char* GetOldLeaderName() { return oldleadername; } protected: friend class EntityList; @@ -44,6 +46,7 @@ protected: inline void SetID(uint32 set_id) { id = set_id; } private: uint32 id; + char oldleadername[64]; // Keeps the previous leader name, so when the entity is destroyed we can still transfer leadership. }; class Group : public GroupIDConsumer { @@ -58,6 +61,7 @@ public: void SendLeadershipAAUpdate(); void SendWorldGroup(uint32 zone_id,Mob* zoningmember); bool DelMemberOOZ(const char *Name); + bool DelMemberOOZ(const char *Name, bool checkleader); bool DelMember(Mob* oldmember,bool ignoresender = false); void DisbandGroup(bool joinraid = false); void GetMemberList(std::list& member_list, bool clear_list = true); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 920a3d958..eea45d057 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -877,7 +877,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (gl->zoneid == zone->GetZoneID() && gl->instance_id == zone->GetInstanceID()) break; - entity_list.SendGroupLeave(gl->gid, gl->member_name); + entity_list.SendGroupLeave(gl->gid, gl->member_name, gl->checkleader); } break; } @@ -1114,6 +1114,17 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) break; } + case ServerOP_ChangeGroupLeader: { + ServerGroupLeader_Struct *fgu = (ServerGroupLeader_Struct *) pack->pBuffer; + if (zone) { + if (fgu->zoneid == zone->GetZoneID()) { + break; + } + entity_list.SendGroupLeader(fgu->gid, fgu->leader_name, fgu->oldleader_name); + } + break; + } + case ServerOP_OOZGroupMessage: { ServerGroupChannelMessage_Struct* gcm = (ServerGroupChannelMessage_Struct*)pack->pBuffer; if (zone) { From 83928fa4d09dec2407367ed40f6019380f03b446 Mon Sep 17 00:00:00 2001 From: Trust Date: Wed, 2 Dec 2020 17:21:22 -0500 Subject: [PATCH 161/196] [REBASE] Prevent Bards from auto equip loot when using instrument --- zone/inventory.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/zone/inventory.cpp b/zone/inventory.cpp index b047197d2..71749be45 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -1154,9 +1154,34 @@ bool Client::AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn, bool } } } + if( i == EQ::invslot::slotPrimary && m_inv[EQ::invslot::slotSecondary] ) { + uint8 instrument = m_inv[MainSecondary]->GetItem()->ItemType; + if( + instrument == EQ::item::ItemTypeWindInstrument || + instrument == EQ::item::ItemTypeStringedInstrument || + instrument == EQ::item::ItemTypeBrassInstrument || + instrument == EQ::item::ItemTypePercussionInstrument + ) { + LogInventory("Cannot equip a primary item with [{}] already in the secondary.", m_inv[MainSecondary]->GetItem()->Name); + continue; // Do not auto-equip Primary when instrument is in Secondary + } + } if (i == EQ::invslot::slotSecondary && m_inv[EQ::invslot::slotPrimary]) { // check to see if primary slot is a two hander - if (m_inv[EQ::invslot::slotPrimary]->GetItem()->IsType2HWeapon()) + uint8 instrument = inst.GetItem()->ItemType; + if( + instrument == EQ::item::ItemTypeWindInstrument || + instrument == EQ::item::ItemTypeStringedInstrument || + instrument == EQ::item::ItemTypeBrassInstrument || + instrument == EQ::item::ItemTypePercussionInstrument + ) { + LogInventory("Cannot equip a secondary instrument with [{}] already in the primary.", m_inv[MainPrimary]->GetItem()->Name); + continue; // Do not auto-equip instrument in Secondary when Primary is equipped. + } + + uint8 use = m_inv[MainPrimary]->GetItem()->ItemType; + if(use == EQ::item::ItemType2HSlash || use == EQ::item::ItemType2HBlunt || use == EQ::item::ItemType2HPiercing) { continue; + } } if (i == EQ::invslot::slotSecondary && inst.IsWeapon() && !CanThisClassDualWield()) { continue; @@ -1169,7 +1194,6 @@ bool Client::AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn, bool if (worn_slot_material != EQ::textures::materialInvalid) { SendWearChange(worn_slot_material); } - parse->EventItem(EVENT_EQUIP_ITEM, this, &inst, nullptr, "", i); return true; } From 789cfb24909a879a5b7b7b5ad1f043b09bd30d68 Mon Sep 17 00:00:00 2001 From: Trust Date: Fri, 4 Dec 2020 17:12:15 -0500 Subject: [PATCH 162/196] [REBASE] Barter was allowing the purchase of bags. Bags could contain anything and it would be traded. --- zone/trading.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/zone/trading.cpp b/zone/trading.cpp index 632fd4fb7..1d75bd941 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -2648,6 +2648,11 @@ void Client::SellToBuyer(const EQApplicationPacket *app) { return; } + if(item->IsClassBag()) { + Message(Chat::Red, "That item is a Bag."); + return; + } + if(!item->Stackable) { for(uint32 i = 0; i < Quantity; i++) { @@ -3001,29 +3006,27 @@ void Client::UpdateBuyLine(const EQApplicationPacket *app) { LogTrading("UpdateBuyLine: Char: [{}] BuySlot: [{}] ItemID [{}] [{}] Quantity [{}] Toggle: [{}] Price [{}] ItemCount [{}] LoreConflict [{}]", GetName(), BuySlot, ItemID, item->Name, Quantity, ToggleOnOff, Price, ItemCount, LoreConflict); - if((item->NoDrop != 0) && !LoreConflict && (Quantity > 0) && HasMoney(Quantity * Price) && ToggleOnOff && (ItemCount == 0)) { + if((item->NoDrop != 0) && (!item->IsClassBag())&& !LoreConflict && (Quantity > 0) && HasMoney(Quantity * Price) && ToggleOnOff && (ItemCount == 0)) { LogTrading("Adding to database"); database.AddBuyLine(CharacterID(), BuySlot, ItemID, ItemName, Quantity, Price); QueuePacket(app); } else { - if(ItemCount > 0) + if(ItemCount > 0) { Message(Chat::Red, "Buy line %s disabled as Item Compensation is not currently supported.", ItemName); - - else if(Quantity <= 0) + } else if(Quantity <= 0) { Message(Chat::Red, "Buy line %s disabled as the quantity is invalid.", ItemName); - - else if(LoreConflict) + } else if(LoreConflict) { Message(Chat::Red, "Buy line %s disabled as the item is LORE and you have one already.", ItemName); - - else if(item->NoDrop == 0) + } else if(item->NoDrop == 0) { Message(Chat::Red, "Buy line %s disabled as the item is NODROP.", ItemName); - - else if(ToggleOnOff) + } else if(item->IsClassBag()) { + Message(Chat::Red, "Buy line %s disabled as the item is a Bag.", ItemName); + } else if(ToggleOnOff) { Message(Chat::Red, "Buy line %s disabled due to insufficient funds.", ItemName); - - else + } else { database.RemoveBuyLine(CharacterID(), BuySlot); + } auto outapp = new EQApplicationPacket(OP_Barter, 936); From 538092d59ecb1bc87eb1ddffda57eddc07096068 Mon Sep 17 00:00:00 2001 From: Trust Date: Thu, 31 Dec 2020 11:33:04 -0500 Subject: [PATCH 163/196] Fix for incorrect slot definition. --- zone/inventory.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 71749be45..0a052824a 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -1155,14 +1155,14 @@ bool Client::AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn, bool } } if( i == EQ::invslot::slotPrimary && m_inv[EQ::invslot::slotSecondary] ) { - uint8 instrument = m_inv[MainSecondary]->GetItem()->ItemType; + uint8 instrument = m_inv[EQ::invslot::slotSecondary]->GetItem()->ItemType; if( instrument == EQ::item::ItemTypeWindInstrument || instrument == EQ::item::ItemTypeStringedInstrument || instrument == EQ::item::ItemTypeBrassInstrument || instrument == EQ::item::ItemTypePercussionInstrument ) { - LogInventory("Cannot equip a primary item with [{}] already in the secondary.", m_inv[MainSecondary]->GetItem()->Name); + LogInventory("Cannot equip a primary item with [{}] already in the secondary.", m_inv[EQ::invslot::slotSecondary]->GetItem()->Name); continue; // Do not auto-equip Primary when instrument is in Secondary } } @@ -1174,11 +1174,11 @@ bool Client::AutoPutLootInInventory(EQ::ItemInstance& inst, bool try_worn, bool instrument == EQ::item::ItemTypeBrassInstrument || instrument == EQ::item::ItemTypePercussionInstrument ) { - LogInventory("Cannot equip a secondary instrument with [{}] already in the primary.", m_inv[MainPrimary]->GetItem()->Name); + LogInventory("Cannot equip a secondary instrument with [{}] already in the primary.", m_inv[EQ::invslot::slotPrimary]->GetItem()->Name); continue; // Do not auto-equip instrument in Secondary when Primary is equipped. } - uint8 use = m_inv[MainPrimary]->GetItem()->ItemType; + uint8 use = m_inv[EQ::invslot::slotPrimary]->GetItem()->ItemType; if(use == EQ::item::ItemType2HSlash || use == EQ::item::ItemType2HBlunt || use == EQ::item::ItemType2HPiercing) { continue; } From 6c8c81f3db340d91f465d04408cd41c2a1f735c4 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 2 Jan 2021 19:19:31 -0500 Subject: [PATCH 164/196] Move player corpses on instance shutdown Moves corpses to graveyard when an expired instance shuts down. Zones without a graveyard move them to non-instance version instead. Fixes player corpses being left inside instances that expire before graveyards process or in instances without a graveyard --- zone/command.cpp | 5 ++++ zone/corpse.cpp | 67 ++++++++++++++++++++++++++++++++++++------------ zone/corpse.h | 3 +++ zone/entity.cpp | 40 +++++++++++++++++++++++++++++ zone/entity.h | 2 ++ zone/zone.cpp | 5 +++- zone/zonedb.cpp | 12 +++++++++ zone/zonedb.h | 1 + 8 files changed, 118 insertions(+), 17 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 2dc55221c..123d3889b 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -4840,6 +4840,10 @@ void command_corpse(Client *c, const Seperator *sep) c->Message(Chat::White, "Insufficient status to depop player corpse."); } + else if (strcasecmp(sep->arg[1], "moveallgraveyard") == 0) { + int count = entity_list.MovePlayerCorpsesToGraveyard(true); + c->Message(Chat::White, "Moved [%d] player corpse(s) to zone graveyard", count); + } else if (sep->arg[1][0] == 0 || strcasecmp(sep->arg[1], "help") == 0) { c->Message(Chat::White, "#Corpse Sub-Commands:"); c->Message(Chat::White, " DeleteNPCCorpses"); @@ -4847,6 +4851,7 @@ void command_corpse(Client *c, const Seperator *sep) c->Message(Chat::White, " ListNPC"); c->Message(Chat::White, " ListPlayer"); c->Message(Chat::White, " Lock - GM locks the corpse - cannot be looted by non-GM"); + c->Message(Chat::White, " MoveAllGraveyard - move all player corpses to zone's graveyard or non-instance"); c->Message(Chat::White, " UnLock"); c->Message(Chat::White, " RemoveCash"); c->Message(Chat::White, " InspectLoot"); diff --git a/zone/corpse.cpp b/zone/corpse.cpp index e04fd6a9d..ad75521a7 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -826,22 +826,7 @@ bool Corpse::Process() { } if (corpse_graveyard_timer.Check()) { - if (zone->HasGraveyard()) { - Save(); - player_corpse_depop = true; - database.SendCharacterCorpseToGraveyard(corpse_db_id, zone->graveyard_zoneid(), - (zone->GetZoneID() == zone->graveyard_zoneid()) ? zone->GetInstanceID() : 0, zone->GetGraveyardPoint()); - corpse_graveyard_timer.Disable(); - auto pack = new ServerPacket(ServerOP_SpawnPlayerCorpse, sizeof(SpawnPlayerCorpse_Struct)); - SpawnPlayerCorpse_Struct* spc = (SpawnPlayerCorpse_Struct*)pack->pBuffer; - spc->player_corpse_id = corpse_db_id; - spc->zone_id = zone->graveyard_zoneid(); - worldserver.SendPacket(pack); - safe_delete(pack); - LogDebug("Moved [{}] player corpse to the designated graveyard in zone [{}]", this->GetName(), ZoneName(zone->graveyard_zoneid())); - corpse_db_id = 0; - } - + MovePlayerCorpseToGraveyard(); corpse_graveyard_timer.Disable(); return false; } @@ -1643,3 +1628,53 @@ void Corpse::LoadPlayerCorpseDecayTime(uint32 corpse_db_id){ corpse_graveyard_timer.SetTimer(3000); } } + +void Corpse::SendWorldSpawnPlayerCorpseInZone(uint32_t zone_id) +{ + auto pack = std::unique_ptr(new ServerPacket(ServerOP_SpawnPlayerCorpse, sizeof(SpawnPlayerCorpse_Struct))); + SpawnPlayerCorpse_Struct* spc = reinterpret_cast(pack->pBuffer); + spc->player_corpse_id = corpse_db_id; + spc->zone_id = zone_id; + worldserver.SendPacket(pack.get()); +} + +bool Corpse::MovePlayerCorpseToGraveyard() +{ + if (IsPlayerCorpse() && zone && zone->HasGraveyard()) + { + Save(); + + uint16_t instance_id = (zone->GetZoneID() == zone->graveyard_zoneid()) ? zone->GetInstanceID() : 0; + database.SendCharacterCorpseToGraveyard(corpse_db_id, zone->graveyard_zoneid(), instance_id, zone->GetGraveyardPoint()); + SendWorldSpawnPlayerCorpseInZone(zone->graveyard_zoneid()); + + corpse_db_id = 0; + player_corpse_depop = true; + corpse_graveyard_timer.Disable(); + + LogDebug("Moved [{}] player corpse to the designated graveyard in zone [{}]", GetName(), ZoneName(zone->graveyard_zoneid())); + return true; + } + + return false; +} + +bool Corpse::MovePlayerCorpseToNonInstance() +{ + if (IsPlayerCorpse() && zone && zone->GetInstanceID() != 0) + { + Save(); + + database.SendCharacterCorpseToNonInstance(corpse_db_id); + SendWorldSpawnPlayerCorpseInZone(zone->GetZoneID()); + + corpse_db_id = 0; + player_corpse_depop = true; + corpse_graveyard_timer.Disable(); + + LogDebug("Moved [{}] player corpse to non-instance version of zone [{}]", GetName(), ZoneName(zone->GetZoneID())); + return true; + } + + return false; +} diff --git a/zone/corpse.h b/zone/corpse.h index 300569f89..cd564e80d 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -79,6 +79,9 @@ class Corpse : public Mob { void SetConsentGuildID(uint32 guild_id) { if (IsPlayerCorpse()) { consented_guild_id = guild_id; } } void AddConsentName(std::string consent_player_name); void RemoveConsentName(std::string consent_player_name); + void SendWorldSpawnPlayerCorpseInZone(uint32_t zone_id); + bool MovePlayerCorpseToGraveyard(); + bool MovePlayerCorpseToNonInstance(); void Delete(); void Bury(); diff --git a/zone/entity.cpp b/zone/entity.cpp index 1ad03eea0..659048d35 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -5225,3 +5225,43 @@ void EntityList::GateAllClientsToSafeReturn() } } } + +int EntityList::MovePlayerCorpsesToGraveyard(bool force_move_from_instance) +{ + if (!zone) + { + return 0; + } + + int moved_count = 0; + + for (auto it = corpse_list.begin(); it != corpse_list.end();) + { + bool moved = false; + if (it->second && it->second->IsPlayerCorpse()) + { + if (zone->HasGraveyard()) + { + moved = it->second->MovePlayerCorpseToGraveyard(); + } + else if (force_move_from_instance && zone->GetInstanceID() != 0) + { + moved = it->second->MovePlayerCorpseToNonInstance(); + } + } + + if (moved) + { + safe_delete(it->second); + free_ids.push(it->first); + it = corpse_list.erase(it); + ++moved_count; + } + else + { + ++it; + } + } + + return moved_count; +} diff --git a/zone/entity.h b/zone/entity.h index b7f90b6f9..4b0c38e7f 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -537,6 +537,8 @@ public: void UpdateAllTraps(bool respawn, bool repopnow = false); void ClearTrapPointers(); + int MovePlayerCorpsesToGraveyard(bool force_move_from_instance = false); + protected: friend class Zone; void Depop(bool StartSpawnTimer = false); diff --git a/zone/zone.cpp b/zone/zone.cpp index 23e25c280..ff45da839 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1497,7 +1497,10 @@ bool Zone::Process() { { expedition->RemoveAllMembers(false); // entity list will teleport clients out immediately } - // todo: move corpses to non-instanced version of dz at same coords (if no graveyard) + + // instance shutting down, move corpses to graveyard or non-instanced zone at same coords + entity_list.MovePlayerCorpsesToGraveyard(true); + entity_list.GateAllClientsToSafeReturn(); database.DeleteInstance(GetInstanceID()); Instance_Shutdown_Timer = new Timer(20000); //20 seconds diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index b157aeba2..bbd929450 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -4300,6 +4300,18 @@ uint32 ZoneDatabase::SendCharacterCorpseToGraveyard(uint32 dbid, uint32 zone_id, return dbid; } +void ZoneDatabase::SendCharacterCorpseToNonInstance(uint32 corpse_db_id) +{ + if (corpse_db_id != 0) + { + auto query = fmt::format(SQL( + UPDATE character_corpses SET instance_id = 0 WHERE id = {}; + ), corpse_db_id); + + QueryDatabase(query); + } +} + uint32 ZoneDatabase::GetCharacterCorpseDecayTimer(uint32 corpse_db_id){ std::string query = StringFormat("SELECT(UNIX_TIMESTAMP() - UNIX_TIMESTAMP(time_of_death)) FROM `character_corpses` WHERE `id` = %d AND NOT `time_of_death` = 0", corpse_db_id); auto results = QueryDatabase(query); diff --git a/zone/zonedb.h b/zone/zonedb.h index bb6311eb0..569819100 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -376,6 +376,7 @@ public: uint32 GetCharacterCorpseID(uint32 char_id, uint8 corpse); uint32 GetCharacterCorpseItemAt(uint32 corpse_id, uint16 slotid); uint32 GetPlayerCorpseTimeLeft(uint8 corpse, uint8 type); + void SendCharacterCorpseToNonInstance(uint32 corpse_db_id); /* Faction */ bool GetNPCFactionList(uint32 npcfaction_id, int32* faction_id, int32* value, uint8* temp, int32* primary_faction = 0); From 7fbf522aa3d7553bee3a673f00b34148c3abfd7d Mon Sep 17 00:00:00 2001 From: Trust Date: Sat, 2 Jan 2021 22:54:26 -0500 Subject: [PATCH 165/196] Added LogGroup to logging aliases. --- common/eqemu_logsys_log_aliases.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index efe25dabe..307cc9fc5 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -161,6 +161,11 @@ OutF(LogSys, Logs::Detail, Logs::Doors, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogGroup(message, ...) do {\ + if (LogSys.log_settings[Logs::Group].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::Group, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define LogGuilds(message, ...) do {\ if (LogSys.log_settings[Logs::Guilds].is_category_enabled == 1)\ OutF(LogSys, Logs::General, Logs::Guilds, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ @@ -719,6 +724,9 @@ #define LogDoorsDetail(message, ...) do {\ } while (0) +#define LogGroup(message, ...) do {\ +} while (0) + #define LogGuilds(message, ...) do {\ } while (0) From 29e693d443030325073b90e598ed41b4ce85083a Mon Sep 17 00:00:00 2001 From: Trust Date: Sat, 2 Jan 2021 22:57:57 -0500 Subject: [PATCH 166/196] Also added LogGroupDetail --- common/eqemu_logsys_log_aliases.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index 307cc9fc5..e32b43b4f 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -166,6 +166,11 @@ OutF(LogSys, Logs::General, Logs::Group, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogGroupDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::Group].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::Group, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define LogGuilds(message, ...) do {\ if (LogSys.log_settings[Logs::Guilds].is_category_enabled == 1)\ OutF(LogSys, Logs::General, Logs::Guilds, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ @@ -727,6 +732,9 @@ #define LogGroup(message, ...) do {\ } while (0) +#define LogGroupDetail(message, ...) do {\ +} while (0) + #define LogGuilds(message, ...) do {\ } while (0) From d6e29810f196133093b1bf0395fd1b55ea743174 Mon Sep 17 00:00:00 2001 From: Matthew Silvia Date: Sat, 2 Jan 2021 23:17:47 -0500 Subject: [PATCH 167/196] #endurance --- zone/command.cpp | 14 ++++++++++++++ zone/command.h | 1 + 2 files changed, 15 insertions(+) diff --git a/zone/command.cpp b/zone/command.cpp index 123d3889b..c9dc2cf7d 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -206,6 +206,7 @@ int command_init(void) command_add("emotesearch", "Searches NPC Emotes", 80, command_emotesearch) || command_add("emoteview", "Lists all NPC Emotes", 80, command_emoteview) || command_add("enablerecipe", "[recipe_id] - Enables a recipe using the recipe id.", 80, command_enablerecipe) || + command_add("endurance", "Restores you or your target's endurance.", 50, command_endurance) || command_add("equipitem", "[slotid(0-21)] - Equip the item on your cursor into the specified slot", 50, command_equipitem) || command_add("face", "- Change the face of your target", 80, command_face) || command_add("faction", "[Find (criteria | all ) | Review (criteria | all) | Reset (id)] - Resets Player's Faction", 80, command_faction) || @@ -737,6 +738,19 @@ void command_logcommand(Client *c, const char *message) /* * commands go below here */ +void command_endurance(Client *c, const Seperator *sep) +{ + Mob *t; + + t = c->GetTarget() ? c->GetTarget() : c; + + if (t->IsClient()) + t->CastToClient()->SetEndurance(t->CastToClient()->GetMaxEndurance()); + else + t->SetEndurance(t->GetMaxEndurance()); + + t->Message(Chat::White, "Your endurance has been refilled."); +} void command_setstat(Client* c, const Seperator* sep){ if(sep->arg[1][0] && sep->arg[2][0] && c->GetTarget()!=0 && c->GetTarget()->IsClient()){ c->GetTarget()->CastToClient()->SetStats(atoi(sep->arg[1]),atoi(sep->arg[2])); diff --git a/zone/command.h b/zone/command.h index a6f967d78..bae126019 100644 --- a/zone/command.h +++ b/zone/command.h @@ -99,6 +99,7 @@ void command_emote(Client *c, const Seperator *sep); void command_emotesearch(Client* c, const Seperator *sep); void command_emoteview(Client* c, const Seperator *sep); void command_enablerecipe(Client *c, const Seperator *sep); +void command_endurance(Client *c, const Seperator *sep); void command_equipitem(Client *c, const Seperator *sep); void command_face(Client *c, const Seperator *sep); void command_faction(Client *c, const Seperator *sep); From e13346560e8cc98957c577e57ee97e8626ca8177 Mon Sep 17 00:00:00 2001 From: Matthew Silvia Date: Sat, 2 Jan 2021 23:24:28 -0500 Subject: [PATCH 168/196] #ww --- zone/command.cpp | 11 +++++++++++ zone/command.h | 1 + 2 files changed, 12 insertions(+) diff --git a/zone/command.cpp b/zone/command.cpp index 123d3889b..bf3c95dee 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -438,6 +438,7 @@ int command_init(void) command_add("wp", "[add/delete] [grid_num] [pause] [wp_num] [-h] - Add/delete a waypoint to/from a wandering grid", 170, command_wp) || command_add("wpadd", "[pause] [-h] - Add your current location as a waypoint to your NPC target's AI path", 170, command_wpadd) || command_add("wpinfo", "- Show waypoint info about your NPC target", 170, command_wpinfo) || + command_add("ww", "Casts the provided spell ID to all players currently online. Use caution with this!", 250, command_ww) || command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", 250, command_xtargets) || command_add("zclip", "[min] [max] - modifies and resends zhdr packet", 80, command_zclip) || command_add("zcolor", "[red] [green] [blue] - Change sky color", 80, command_zcolor) || @@ -737,6 +738,16 @@ void command_logcommand(Client *c, const char *message) /* * commands go below here */ +void command_ww(Client *c, const Seperator *sep) +{ + if (sep->arg[1][0] && Seperator::IsNumber(sep->arg[1])) { + int spell_id = atoi(sep->arg[1]); + quest_manager.WorldWideCastSpell(spell_id, 0, 0); + worldserver.SendEmoteMessage(0, 0, 15, fmt::format(" A GM has cast {} world-wide!", GetSpellName(spell_id)).c_str()); + } + else + c->Message(Chat::Yellow, "Usage: #ww "); +} void command_setstat(Client* c, const Seperator* sep){ if(sep->arg[1][0] && sep->arg[2][0] && c->GetTarget()!=0 && c->GetTarget()->IsClient()){ c->GetTarget()->CastToClient()->SetStats(atoi(sep->arg[1]),atoi(sep->arg[2])); diff --git a/zone/command.h b/zone/command.h index a6f967d78..b42f1446d 100644 --- a/zone/command.h +++ b/zone/command.h @@ -344,6 +344,7 @@ void command_worldshutdown(Client *c, const Seperator *sep); void command_wp(Client *c, const Seperator *sep); void command_wpadd(Client *c, const Seperator *sep); void command_wpinfo(Client *c, const Seperator *sep); +void command_ww(Client *c, const Seperator *sep); void command_xtargets(Client *c, const Seperator *sep); void command_zclip(Client *c, const Seperator *sep); void command_zcolor(Client *c, const Seperator *sep); From 3fa236c2bb66ccb98bb2a94c31eff739dccb02bc Mon Sep 17 00:00:00 2001 From: Evan Alexander King Date: Sun, 3 Jan 2021 03:09:27 -0500 Subject: [PATCH 169/196] Add client->Fling() to Perl/Lua. - $client->Fling(value, target_x, target_y, target_z, ignore_los, clipping) in Perl. - client:Fling(value, target_x, target_y, target_z, ignore_los, clipping) in Lua. --- zone/client.cpp | 22 ++++++++++++++++++++++ zone/client.h | 2 ++ zone/lua_client.cpp | 20 +++++++++++++++++++- zone/lua_client.h | 3 +++ zone/perl_client.cpp | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/zone/client.cpp b/zone/client.cpp index 06f04ec1b..726d736e2 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9988,3 +9988,25 @@ void Client::MovePCDynamicZone(const std::string& zone_name, int zone_version, b auto zone_id = ZoneID(zone_name.c_str()); MovePCDynamicZone(zone_id, zone_version, msg_if_invalid); } + +void Client::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clipping) { + BuffFadeByEffect(SE_Levitate); + if (CheckLosFN(target_x, target_y, target_z, 6.0f) || ignore_los) { + auto outapp_fling = new EQApplicationPacket(OP_Fling, sizeof(fling_struct)); + fling_struct* flingTo = (fling_struct*)outapp_fling->pBuffer; + if(clipping) + flingTo->collision = 0; + else + flingTo->collision = -1; + + flingTo->travel_time = -1; + flingTo->unk3 = 1; + flingTo->disable_fall_damage = 1; + flingTo->speed_z = value; + flingTo->new_y = target_y; + flingTo->new_x = target_x; + flingTo->new_z = target_z; + outapp_fling->priority = 6; + FastQueuePacket(&outapp_fling); + } +} \ No newline at end of file diff --git a/zone/client.h b/zone/client.h index f2b1892c9..6829ce303 100644 --- a/zone/client.h +++ b/zone/client.h @@ -794,6 +794,8 @@ public: uint32 GetCharMaxLevelFromQGlobal(); uint32 GetCharMaxLevelFromBucket(); + void Fling(float value, float target_x, float target_y, float target_z, bool ignore_los = false, bool clipping = false); + inline bool IsStanding() const {return (playeraction == 0);} inline bool IsSitting() const {return (playeraction == 1);} inline bool IsCrouching() const {return (playeraction == 2);} diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 30518e520..7a843331b 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1877,6 +1877,21 @@ void Lua_Client::MovePCDynamicZone(std::string zone_name, int zone_version, bool return self->MovePCDynamicZone(zone_name, zone_version, msg_if_invalid); } +void Lua_Client::Fling(float value, float target_x, float target_y, float target_z) { + Lua_Safe_Call_Void(); + self->Fling(value, target_x, target_y, target_z); +} + +void Lua_Client::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los) { + Lua_Safe_Call_Void(); + self->Fling(value, target_x, target_y, target_z, ignore_los); +} + +void Lua_Client::Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clipping) { + Lua_Safe_Call_Void(); + self->Fling(value, target_x, target_y, target_z, ignore_los, clipping); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -2204,7 +2219,10 @@ luabind::scope lua_register_client() { .def("MovePCDynamicZone", (void(Lua_Client::*)(uint32, int, bool))&Lua_Client::MovePCDynamicZone) .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string))&Lua_Client::MovePCDynamicZone) .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int))&Lua_Client::MovePCDynamicZone) - .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int, bool))&Lua_Client::MovePCDynamicZone); + .def("MovePCDynamicZone", (void(Lua_Client::*)(std::string, int, bool))&Lua_Client::MovePCDynamicZone) + .def("Fling", (void(Lua_Client::*)(float,float,float,float))&Lua_Client::Fling) + .def("Fling", (void(Lua_Client::*)(float,float,float,float,bool))&Lua_Client::Fling) + .def("Fling", (void(Lua_Client::*)(float,float,float,float,bool,bool))&Lua_Client::Fling); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index 8bfa5bda0..e30a94fd2 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -360,6 +360,9 @@ public: void MovePCDynamicZone(std::string zone_name); void MovePCDynamicZone(std::string zone_name, int zone_version); void MovePCDynamicZone(std::string zone_name, int zone_version, bool msg_if_invalid); + void Fling(float value, float target_x, float target_y, float target_z); + void Fling(float value, float target_x, float target_y, float target_z, bool ignore_los); + void Fling(float value, float target_x, float target_y, float target_z, bool ignore_los, bool clipping); }; #endif diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 5b8ed1a05..1dc9f2128 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -7051,6 +7051,41 @@ XS(XS_Client_MovePCDynamicZone) { XSRETURN_EMPTY; } +XS(XS_Client_Fling); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_Fling) { + dXSARGS; + if (items < 5 || items > 7) + Perl_croak(aTHX_ "Usage: Client::Fling(THIS, value, target_x, target_y, target_z, ignore_los, clipping)"); + { + Client* THIS; + float value = (float) SvNV(ST(1)); + float target_x = (float) SvNV(ST(2)); + float target_y = (float) SvNV(ST(3)); + float target_z = (float) SvNV(ST(4)); + bool ignore_los = false; + bool clipping = false; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + + if(THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (items > 5) + ignore_los = (bool) SvTRUE(ST(5)); + + if (items > 6) + clipping = (bool) SvTRUE(ST(6)); + + THIS->Fling(value, target_x, target_y, target_z, ignore_los, clipping); + } + XSRETURN_EMPTY; +} + #ifdef __cplusplus extern "C" #endif @@ -7108,6 +7143,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "Escape"), XS_Client_Escape, file, "$"); newXSproto(strcpy(buf, "ExpeditionMessage"), XS_Client_ExpeditionMessage, file, "$$$"); newXSproto(strcpy(buf, "FailTask"), XS_Client_FailTask, file, "$$"); + newXSproto(strcpy(buf, "Fling"), XS_Client_Fling, file, "$$$$$;$$"); newXSproto(strcpy(buf, "ForageItem"), XS_Client_ForageItem, file, "$"); newXSproto(strcpy(buf, "Freeze"), XS_Client_Freeze, file, "$"); newXSproto(strcpy(buf, "GetAAExp"), XS_Client_GetAAExp, file, "$"); From c58ba2e6a55e78b4cf9ae1250b3428cd98e52f8a Mon Sep 17 00:00:00 2001 From: Evan Alexander King Date: Sun, 3 Jan 2021 03:20:57 -0500 Subject: [PATCH 170/196] Add GetTargetRingX(), GetTargetRingY(), and GetTargetRingZ() to Lua. --- zone/lua_client.cpp | 18 ++++++++++++++++++ zone/lua_client.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 30518e520..dfb315aac 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -329,6 +329,21 @@ uint32 Lua_Client::GetBindZoneID(int index) { return self->GetBindZoneID(index); } +float Lua_Client::GetTargetRingX() { + Lua_Safe_Call_Real(); + return self->GetTargetRingX(); +} + +float Lua_Client::GetTargetRingY() { + Lua_Safe_Call_Real(); + return self->GetTargetRingY(); +} + +float Lua_Client::GetTargetRingZ() { + Lua_Safe_Call_Real(); + return self->GetTargetRingZ(); +} + void Lua_Client::MovePC(int zone, float x, float y, float z, float heading) { Lua_Safe_Call_Void(); self->MovePC(zone, x, y, z, heading); @@ -1942,6 +1957,9 @@ luabind::scope lua_register_client() { .def("GetBindHeading", (float(Lua_Client::*)(int))&Lua_Client::GetBindHeading) .def("GetBindZoneID", (uint32(Lua_Client::*)(void))&Lua_Client::GetBindZoneID) .def("GetBindZoneID", (uint32(Lua_Client::*)(int))&Lua_Client::GetBindZoneID) + .def("GetTargetRingX", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingX) + .def("GetTargetRingY", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingY) + .def("GetTargetRingZ", (float(Lua_Client::*)(void))&Lua_Client::GetTargetRingZ) .def("SetPrimaryWeaponOrnamentation", (void(Lua_Client::*)(uint32))&Lua_Client::SetPrimaryWeaponOrnamentation) .def("SetSecondaryWeaponOrnamentation", (void(Lua_Client::*)(uint32))&Lua_Client::SetSecondaryWeaponOrnamentation) .def("MovePC", (void(Lua_Client::*)(int,float,float,float,float))&Lua_Client::MovePC) diff --git a/zone/lua_client.h b/zone/lua_client.h index 8bfa5bda0..cb53b30cc 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -92,6 +92,9 @@ public: float GetBindHeading(int index); uint32 GetBindZoneID(); uint32 GetBindZoneID(int index); + float GetTargetRingX(); + float GetTargetRingY(); + float GetTargetRingZ(); void MovePC(int zone, float x, float y, float z, float heading); void MovePCInstance(int zone, int instance, float x, float y, float z, float heading); void MoveZone(const char *zone_short_name); From a4d8d2261ada562cb3e7ce400ef834ef7609f787 Mon Sep 17 00:00:00 2001 From: Matthew Silvia Date: Sun, 3 Jan 2021 13:09:09 -0500 Subject: [PATCH 171/196] change name --- zone/command.cpp | 4 ++-- zone/command.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index bf3c95dee..b09e4e419 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -438,7 +438,7 @@ int command_init(void) command_add("wp", "[add/delete] [grid_num] [pause] [wp_num] [-h] - Add/delete a waypoint to/from a wandering grid", 170, command_wp) || command_add("wpadd", "[pause] [-h] - Add your current location as a waypoint to your NPC target's AI path", 170, command_wpadd) || command_add("wpinfo", "- Show waypoint info about your NPC target", 170, command_wpinfo) || - command_add("ww", "Casts the provided spell ID to all players currently online. Use caution with this!", 250, command_ww) || + command_add("wwcast", "Casts the provided spell ID to all players currently online. Use caution with this!", 250, command_ww) || command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", 250, command_xtargets) || command_add("zclip", "[min] [max] - modifies and resends zhdr packet", 80, command_zclip) || command_add("zcolor", "[red] [green] [blue] - Change sky color", 80, command_zcolor) || @@ -738,7 +738,7 @@ void command_logcommand(Client *c, const char *message) /* * commands go below here */ -void command_ww(Client *c, const Seperator *sep) +void command_wwcast(Client *c, const Seperator *sep) { if (sep->arg[1][0] && Seperator::IsNumber(sep->arg[1])) { int spell_id = atoi(sep->arg[1]); diff --git a/zone/command.h b/zone/command.h index b42f1446d..cb903a371 100644 --- a/zone/command.h +++ b/zone/command.h @@ -344,7 +344,7 @@ void command_worldshutdown(Client *c, const Seperator *sep); void command_wp(Client *c, const Seperator *sep); void command_wpadd(Client *c, const Seperator *sep); void command_wpinfo(Client *c, const Seperator *sep); -void command_ww(Client *c, const Seperator *sep); +void command_wwcast(Client *c, const Seperator *sep); void command_xtargets(Client *c, const Seperator *sep); void command_zclip(Client *c, const Seperator *sep); void command_zcolor(Client *c, const Seperator *sep); From 6fe05bbf5e1b4e21969693d728ca30636ee9930e Mon Sep 17 00:00:00 2001 From: Matthew Silvia Date: Sun, 3 Jan 2021 13:09:09 -0500 Subject: [PATCH 172/196] change name --- zone/command.cpp | 6 +++--- zone/command.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index bf3c95dee..bd95f3bbc 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -438,7 +438,7 @@ int command_init(void) command_add("wp", "[add/delete] [grid_num] [pause] [wp_num] [-h] - Add/delete a waypoint to/from a wandering grid", 170, command_wp) || command_add("wpadd", "[pause] [-h] - Add your current location as a waypoint to your NPC target's AI path", 170, command_wpadd) || command_add("wpinfo", "- Show waypoint info about your NPC target", 170, command_wpinfo) || - command_add("ww", "Casts the provided spell ID to all players currently online. Use caution with this!", 250, command_ww) || + command_add("wwcast", "Casts the provided spell ID to all players currently online. Use caution with this!", 250, command_wwcast) || command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", 250, command_xtargets) || command_add("zclip", "[min] [max] - modifies and resends zhdr packet", 80, command_zclip) || command_add("zcolor", "[red] [green] [blue] - Change sky color", 80, command_zcolor) || @@ -738,7 +738,7 @@ void command_logcommand(Client *c, const char *message) /* * commands go below here */ -void command_ww(Client *c, const Seperator *sep) +void command_wwcast(Client *c, const Seperator *sep) { if (sep->arg[1][0] && Seperator::IsNumber(sep->arg[1])) { int spell_id = atoi(sep->arg[1]); @@ -746,7 +746,7 @@ void command_ww(Client *c, const Seperator *sep) worldserver.SendEmoteMessage(0, 0, 15, fmt::format(" A GM has cast {} world-wide!", GetSpellName(spell_id)).c_str()); } else - c->Message(Chat::Yellow, "Usage: #ww "); + c->Message(Chat::Yellow, "Usage: #wwcast "); } void command_setstat(Client* c, const Seperator* sep){ if(sep->arg[1][0] && sep->arg[2][0] && c->GetTarget()!=0 && c->GetTarget()->IsClient()){ diff --git a/zone/command.h b/zone/command.h index b42f1446d..cb903a371 100644 --- a/zone/command.h +++ b/zone/command.h @@ -344,7 +344,7 @@ void command_worldshutdown(Client *c, const Seperator *sep); void command_wp(Client *c, const Seperator *sep); void command_wpadd(Client *c, const Seperator *sep); void command_wpinfo(Client *c, const Seperator *sep); -void command_ww(Client *c, const Seperator *sep); +void command_wwcast(Client *c, const Seperator *sep); void command_xtargets(Client *c, const Seperator *sep); void command_zclip(Client *c, const Seperator *sep); void command_zcolor(Client *c, const Seperator *sep); From 90295d8dece8a7bd2219ed6eae9bdf91cb82ab57 Mon Sep 17 00:00:00 2001 From: Matthew Silvia Date: Sun, 3 Jan 2021 13:18:33 -0500 Subject: [PATCH 173/196] change name --- zone/command.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/command.cpp b/zone/command.cpp index bd95f3bbc..ade29ce00 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -438,7 +438,7 @@ int command_init(void) command_add("wp", "[add/delete] [grid_num] [pause] [wp_num] [-h] - Add/delete a waypoint to/from a wandering grid", 170, command_wp) || command_add("wpadd", "[pause] [-h] - Add your current location as a waypoint to your NPC target's AI path", 170, command_wpadd) || command_add("wpinfo", "- Show waypoint info about your NPC target", 170, command_wpinfo) || - command_add("wwcast", "Casts the provided spell ID to all players currently online. Use caution with this!", 250, command_wwcast) || + command_add("wwcast", "Casts the provided spell ID to all players currently online. Use caution with this!!", 250, command_wwcast) || command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", 250, command_xtargets) || command_add("zclip", "[min] [max] - modifies and resends zhdr packet", 80, command_zclip) || command_add("zcolor", "[red] [green] [blue] - Change sky color", 80, command_zcolor) || From 7b9d88b70b2074d789b5db304069c2fa8acd5572 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 3 Jan 2021 16:40:56 -0600 Subject: [PATCH 174/196] Fix a situation where guards don't scan fast enough because they stand idle, moving mobs will add themselves to guards --- zone/npc.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index a5ccfac76..e7ce31806 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -715,7 +715,7 @@ bool NPC::Process() } return false; } - + if (IsStunned() && stunned_timer.Check()) { Mob::UnStun(); this->spun_timer.Disable(); @@ -724,7 +724,7 @@ bool NPC::Process() SpellProcess(); if (mob_close_scan_timer.Check()) { - entity_list.ScanCloseMobs(close_mobs, this); + entity_list.ScanCloseMobs(close_mobs, this, true); } const uint16 npc_mob_close_scan_timer_moving = 6000; @@ -1752,7 +1752,7 @@ void NPC::PickPocket(Client* thief) steal_item = false; break; } - + auto item_inst = database.CreateItem(loot_selection[random].first, loot_selection[random].second); if (item_inst == nullptr) { steal_item = false; @@ -1778,7 +1778,7 @@ void NPC::PickPocket(Client* thief) while (!steal_item && has_coin) { uint32 coin_amount = zone->random.Int(1, (steal_skill / 25) + 1); - + int coin_type = PickPocketPlatinum; while (coin_type <= PickPocketCopper) { if (money[coin_type]) { @@ -2514,10 +2514,10 @@ void NPC::LevelScale() { max_hp += (random_level - level) * 100; base_hp += (random_level - level) * 100; } - + current_hp = max_hp; } - + // Don't add max_dmg to dynamically scaled NPCs since this will be calculated later if (max_dmg > 0 || skip_auto_scale) { @@ -2817,7 +2817,7 @@ FACTION_VALUE NPC::CheckNPCFactionAlly(int32 other_faction) { // I believe that the assumption is, barring no entry in npc_faction_entries // that two npcs on like faction con ally to each other. This catches cases - // where an npc is on a faction but has no hits (hence no entry in + // where an npc is on a faction but has no hits (hence no entry in // npc_faction_entries). if (GetPrimaryFaction() == other_faction) From f5817677df39bc04e2d1a8b598d0a0f5b7449808 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 3 Jan 2021 16:42:34 -0600 Subject: [PATCH 175/196] Only add self to others when moving --- zone/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index e7ce31806..434781841 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -724,7 +724,7 @@ bool NPC::Process() SpellProcess(); if (mob_close_scan_timer.Check()) { - entity_list.ScanCloseMobs(close_mobs, this, true); + entity_list.ScanCloseMobs(close_mobs, this, IsMoving()); } const uint16 npc_mob_close_scan_timer_moving = 6000; From 53bbbbba1d006071c15a06567b8160b7c9be914d Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 3 Jan 2021 17:07:10 -0600 Subject: [PATCH 176/196] Add comments around close mob system [skip ci] --- zone/entity.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/zone/entity.cpp b/zone/entity.cpp index 659048d35..cfba6eb7d 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -2693,6 +2693,36 @@ void EntityList::RemoveAuraFromMobs(Mob *aura) } /** + * The purpose of this system is so that we cache relevant entities that are "close" + * + * In general; it becomes incredibly expensive to run zone-wide checks against every single mob in the zone when in reality + * we only care about entities closest to us + * + * A very simple example of where this is relevant is Aggro, the below example is skewed because the overall implementation + * of Aggro was also tweaked in conjunction with close lists. We also scan more aggressively when entities are moving (1-6 seconds) + * versus 60 seconds when idle. We also have entities that are moving add themselves to those closest to them so that their close + * lists remain always up to date + * + * Before: Aggro checks for NPC to Client aggro | (40 clients in zone) x (525 npcs) x 2 (times a second) = 2,520,000 checks a minute + * After: Aggro checks for NPC to Client aggro | (40 clients in zone) x (20-30 npcs) x 2 (times a second) = 144,000 checks a minute (This is actually far less today) + * + * Places in the code where this logic makes a huge impact + * + * Aggro checks (zone wide -> close) + * Aura processing (zone wide -> close) + * AE Taunt (zone wide -> close) + * AOE Spells (zone wide -> close) + * Bard Pulse AOE (zone wide -> close) + * Mass Group Buff (zone wide -> close) + * AE Attack (zone wide -> close) + * Packet QueueCloseClients (zone wide -> close) + * Check Close Beneficial Spells (Buffs; should I heal other npcs) (zone wide -> close) + * AI Yell for Help (NPC Assist other NPCs) (zone wide -> close) + * + * All of the above makes a tremendous impact on the bottom line of cpu cycle performance because we run an order of magnitude + * less checks by focusing our hot path logic down to a very small subset of relevant entities instead of looping an entire + * entity list (zone wide) + * * @param close_mobs * @param scanning_mob */ @@ -5190,6 +5220,8 @@ void EntityList::ReloadMerchants() { * If we have a distance requested that is greater than our scanning distance * then we return the full list * + * See comments @EntityList::ScanCloseMobs for system explanation + * * @param mob * @param distance * @return From 3fc1aea3ced25f7143f0e3980f3277b178bdadbe Mon Sep 17 00:00:00 2001 From: Akkadius Date: Mon, 4 Jan 2021 11:44:23 -0600 Subject: [PATCH 177/196] Naming suggestions from https://github.com/EQEmu/Server/pull/1170 --- zone/command.cpp | 29 +++++++++++++++++++++-------- zone/command.h | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 7c98bc6fa..408d978d3 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -439,7 +439,7 @@ int command_init(void) command_add("wp", "[add/delete] [grid_num] [pause] [wp_num] [-h] - Add/delete a waypoint to/from a wandering grid", 170, command_wp) || command_add("wpadd", "[pause] [-h] - Add your current location as a waypoint to your NPC target's AI path", 170, command_wpadd) || command_add("wpinfo", "- Show waypoint info about your NPC target", 170, command_wpinfo) || - command_add("wwcast", "Casts the provided spell ID to all players currently online. Use caution with this!!", 250, command_wwcast) || + command_add("worldwide", "Performs world-wide GM functions such as cast (can be extended for other commands). Use caution", 250, command_worldwide) || command_add("xtargets", "Show your targets Extended Targets and optionally set how many xtargets they can have.", 250, command_xtargets) || command_add("zclip", "[min] [max] - modifies and resends zhdr packet", 80, command_zclip) || command_add("zcolor", "[red] [green] [blue] - Change sky color", 80, command_zcolor) || @@ -739,15 +739,28 @@ void command_logcommand(Client *c, const char *message) /* * commands go below here */ -void command_wwcast(Client *c, const Seperator *sep) +void command_worldwide(Client *c, const Seperator *sep) { - if (sep->arg[1][0] && Seperator::IsNumber(sep->arg[1])) { - int spell_id = atoi(sep->arg[1]); - quest_manager.WorldWideCastSpell(spell_id, 0, 0); - worldserver.SendEmoteMessage(0, 0, 15, fmt::format(" A GM has cast {} world-wide!", GetSpellName(spell_id)).c_str()); + std::string sub_command; + if (sep->arg[1]) { + sub_command = sep->arg[1]; + } + + if (sub_command == "cast") { + if (sep->arg[2][0] && Seperator::IsNumber(sep->arg[2])) { + int spell_id = atoi(sep->arg[2]); + quest_manager.WorldWideCastSpell(spell_id, 0, 0); + worldserver.SendEmoteMessage(0, 0, 15, fmt::format(" A GM has cast [{}] world-wide!", GetSpellName(spell_id)).c_str()); + } + else { + c->Message(Chat::Yellow, "Usage: #worldwide cast [spellid]"); + } + } + + if (!sep->arg[1]) { + c->Message(Chat::White, "This command is used to perform world-wide tasks"); + c->Message(Chat::White, "Usage: #worldwide cast [spellid]"); } - else - c->Message(Chat::Yellow, "Usage: #wwcast "); } void command_endurance(Client *c, const Seperator *sep) { diff --git a/zone/command.h b/zone/command.h index 65544c510..303e0f132 100644 --- a/zone/command.h +++ b/zone/command.h @@ -345,7 +345,7 @@ void command_worldshutdown(Client *c, const Seperator *sep); void command_wp(Client *c, const Seperator *sep); void command_wpadd(Client *c, const Seperator *sep); void command_wpinfo(Client *c, const Seperator *sep); -void command_wwcast(Client *c, const Seperator *sep); +void command_worldwide(Client *c, const Seperator *sep); void command_xtargets(Client *c, const Seperator *sep); void command_zclip(Client *c, const Seperator *sep); void command_zcolor(Client *c, const Seperator *sep); From 312ee2b42c5a5ea4e66352728544254a52019850 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 7 Jan 2021 20:05:55 -0500 Subject: [PATCH 178/196] Don't respawn doors while clients are zoning Fixes regression from b08dc02a (PR #1051) The normal door list sent on zone entry caused unopenable double doors on clients if an api respawned them while the client was zoning. This waits until the client finishes zoning and has received the initial door list before sending any despawn/respawn packets. --- zone/entity.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zone/entity.cpp b/zone/entity.cpp index 4746ee984..b2b4b3706 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -2511,7 +2511,7 @@ void EntityList::DespawnAllDoors() auto outapp = new EQApplicationPacket(OP_RemoveAllDoors, 0); for (auto it = client_list.begin(); it != client_list.end(); ++it) { if (it->second) { - it->second->QueuePacket(outapp); + it->second->QueuePacket(outapp, true, Client::CLIENT_CONNECTED); } } safe_delete(outapp); @@ -2524,7 +2524,7 @@ void EntityList::RespawnAllDoors() if (it->second) { auto outapp = new EQApplicationPacket(); MakeDoorSpawnPacket(outapp, it->second); - it->second->FastQueuePacket(&outapp); + it->second->FastQueuePacket(&outapp, true, Client::CLIENT_CONNECTED); } ++it; } From 896dd4896053ca91fdfc78399b14d2bdb62f65b3 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 21 Aug 2020 18:10:27 -0400 Subject: [PATCH 179/196] Fix zone startup crash if maps not installed --- zone/mob_ai.cpp | 2 +- zone/object.cpp | 2 +- zone/zone.cpp | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 900279759..9d0ea02be 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1637,7 +1637,7 @@ void NPC::AI_DoMovement() { roambox_destination_y, m_Position.z + 15 ); - if (!zone->watermap->InLiquid(position)) { + if (zone->HasWaterMap() && !zone->watermap->InLiquid(position)) { roambox_destination_x = m_SpawnPoint.x; roambox_destination_y = m_SpawnPoint.y; roambox_destination_z = m_SpawnPoint.z; diff --git a/zone/object.cpp b/zone/object.cpp index 97fdcd69b..88038bbe8 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -463,7 +463,7 @@ void Object::RandomSpawn(bool send_packet) { m_data.x = zone->random.Real(m_min_x, m_max_x); m_data.y = zone->random.Real(m_min_y, m_max_y); - if(m_data.z == BEST_Z_INVALID) { + if (m_data.z == BEST_Z_INVALID && zone->HasMap()) { glm::vec3 me; me.x = m_data.x; me.y = m_data.y; diff --git a/zone/zone.cpp b/zone/zone.cpp index ff45da839..9efea0a84 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -275,7 +275,9 @@ bool Zone::LoadZoneObjects() position.y = data.y; position.z = data.z; - data.z = zone->zonemap->FindBestZ(position, nullptr); + if (zone->HasMap()) { + data.z = zone->zonemap->FindBestZ(position, nullptr); + } EQ::ItemInstance *inst = nullptr; // FatherNitwit: this dosent seem to work... From 836210404c9b870796e37283986f9cd89fecef22 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Fri, 8 Jan 2021 20:12:36 -0500 Subject: [PATCH 180/196] Fix zone crash on spawn condition change The NPC pointer held by Spawn2 wasn't reset if the npc was depopped without a respawn timer by #depop commands or depop(false) quest apis. If the NPC was part of a spawn condition then the condition would try to dereference that pointer (which gets deleted) on condition change. --- zone/npc.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zone/npc.cpp b/zone/npc.cpp index 434781841..e8110580a 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -968,9 +968,12 @@ void NPC::Depop(bool StartSpawnTimer) { if(emoteid != 0) this->DoNPCEmote(ONDESPAWN,emoteid); p_depop = true; - if (StartSpawnTimer) { - if (respawn2 != 0) { + if (respawn2) + { + if (StartSpawnTimer) { respawn2->DeathReset(); + } else { + respawn2->Depop(); } } } From eb24e333d791427130833affdd48f3f4a311c6fb Mon Sep 17 00:00:00 2001 From: Noudess Date: Mon, 11 Jan 2021 10:58:08 -0500 Subject: [PATCH 181/196] Allow boats flymode to be overridden by local db --- zone/mob.cpp | 4 ---- zone/npc.cpp | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/zone/mob.cpp b/zone/mob.cpp index e48ca6f26..d58ee4b54 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1197,10 +1197,6 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) else ns->spawn.flymode = flymode; - if(IsBoat()) { - ns->spawn.flymode = GravityBehavior::Floating; - } - ns->spawn.lastName[0] = '\0'; strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName)); diff --git a/zone/npc.cpp b/zone/npc.cpp index 434781841..38da238d6 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -230,9 +230,14 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi adventure_template_id = npc_type_data->adventure_template; flymode = iflymode; + // If server has set a flymode in db honor it over all else. + // If server has not set a flymde in db, and this is a boat - force floating. if (npc_type_data->flymode >= 0) { flymode = static_cast(npc_type_data->flymode); } + else if (IsBoat()) { + flymode = GravityBehavior::Floating; + } guard_anim = eaStanding; roambox_distance = 0; From 1c00edb4587342eec4a033a8f9422afcc2fa160e Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Fri, 15 Jan 2021 19:54:44 -0500 Subject: [PATCH 182/196] Add AA gain messages. --- zone/exp.cpp | 13 ++++++++++--- zone/string_ids.h | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/zone/exp.cpp b/zone/exp.cpp index b0eb13790..f6c682b21 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -664,11 +664,18 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { m_pp.aapoints += last_unspentAA; //figure out how many points were actually gained - /*uint32 gained = m_pp.aapoints - last_unspentAA;*/ //unused + uint32 gained = (m_pp.aapoints - last_unspentAA); //Message(Chat::Yellow, "You have gained %d skill points!!", m_pp.aapoints - last_unspentAA); - char val1[20]={0}; - MessageString(Chat::Experience, GAIN_ABILITY_POINT, ConvertArray(m_pp.aapoints, val1),m_pp.aapoints == 1 ? "" : "(s)"); //You have gained an ability point! You now have %1 ability point%2. + char val1[20] = { 0 }; + char val2[20] = { 0 }; + if (gained == 1 && m_pp.aapoints == 1) + MessageString(Chat::Experience, GAIN_SINGLE_AA_SINGLE_AA, ConvertArray(m_pp.aapoints, val1)); //You have gained an ability point! You now have %1 ability point. + else if (gained == 1 && m_pp.aapoints > 1) + MessageString(Chat::Experience, GAIN_SINGLE_AA_MULTI_AA, ConvertArray(m_pp.aapoints, val1)); //You have gained an ability point! You now have %1 ability points. + else + MessageString(Chat::Experience, GAIN_MULTI_AA_MULTI_AA, ConvertArray(gained, val1), ConvertArray(m_pp.aapoints, val2)); //You have gained %1 ability point(s)! You now have %2 ability point(s). + if (RuleB(AA, SoundForAAEarned)) { SendSound(); } diff --git a/zone/string_ids.h b/zone/string_ids.h index 88a3f9f3e..25c62d6b2 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -403,6 +403,9 @@ #define LDON_NO_LOCKPICK 7564 //You must have a lock pick in your inventory to do this. #define LDON_WAS_NOT_LOCKED 7565 //%1 was not locked. #define LDON_WAS_NOT_TRAPPED 7566 //%1 was not trapped +#define GAIN_SINGLE_AA_SINGLE_AA 8019 //You have gained an ability point! You now have %1 ability point. +#define GAIN_SINGLE_AA_MULTI_AA 8020 //You have gained an ability point! You now have %1 ability points. +#define GAIN_MULTI_AA_MULTI_AA 8021 //You have gained %1 ability point(s)! You now have %2 ability point(s). #define GAIN_GROUP_LEADERSHIP_POINT 8585 // #define GAIN_RAID_LEADERSHIP_POINT 8589 // #define MAX_GROUP_LEADERSHIP_POINTS 8584 // From 7e1e1651e4461b6979d3c96abcbc6410c7881d3d Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Sat, 16 Jan 2021 17:59:38 -0500 Subject: [PATCH 183/196] Add GetClassBitmask(), GetClassName(), GetRaceBitmask(), and GetRaceName() to Perl/Lua. --- zone/lua_client.cpp | 12 ++++++++++ zone/lua_client.h | 4 +++- zone/lua_mob.cpp | 12 ++++++++++ zone/lua_mob.h | 2 ++ zone/perl_client.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++ zone/perl_mob.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 139 insertions(+), 1 deletion(-) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index b0cc62467..c04345cc6 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -134,6 +134,16 @@ void Lua_Client::SetBaseGender(int v) { self->SetBaseGender(v); } +int Lua_Client::GetClassBitmask() { + Lua_Safe_Call_Int(); + return GetPlayerClassBit(self->GetClass()); +} + +int Lua_Client::GetRaceBitmask() { + Lua_Safe_Call_Int(); + return GetPlayerRaceBit(self->GetBaseRace()); +} + int Lua_Client::GetBaseFace() { Lua_Safe_Call_Int(); return self->GetBaseFace(); @@ -1933,6 +1943,8 @@ luabind::scope lua_register_client() { .def("SetBaseClass", (void(Lua_Client::*)(int))&Lua_Client::SetBaseClass) .def("SetBaseRace", (void(Lua_Client::*)(int))&Lua_Client::SetBaseRace) .def("SetBaseGender", (void(Lua_Client::*)(int))&Lua_Client::SetBaseGender) + .def("GetClassBitmask", (int(Lua_Client::*)(void))&Lua_Client::GetClassBitmask) + .def("GetRaceBitmask", (int(Lua_Client::*)(void))&Lua_Client::GetRaceBitmask) .def("GetBaseFace", (int(Lua_Client::*)(void))&Lua_Client::GetBaseFace) .def("GetLanguageSkill", (int(Lua_Client::*)(int))&Lua_Client::GetLanguageSkill) .def("GetLastName", (const char *(Lua_Client::*)(void))&Lua_Client::GetLastName) diff --git a/zone/lua_client.h b/zone/lua_client.h index 7162bfd6b..50043ab77 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -52,7 +52,9 @@ public: bool GetGM(); void SetBaseClass(int v); void SetBaseRace(int v); - void SetBaseGender(int v); + void SetBaseGender(int v); + int GetClassBitmask(); + int GetRaceBitmask(); int GetBaseFace(); int GetLanguageSkill(int skill_id); const char *GetLastName(); diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 53658e63d..f8379b82e 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -372,6 +372,11 @@ int Lua_Mob::GetRace() { return self->GetRace(); } +const char *Lua_Mob::GetRaceName() { + Lua_Safe_Call_String(); + return GetRaceIDName(self->GetRace()); +} + int Lua_Mob::GetGender() { Lua_Safe_Call_Int(); return self->GetGender(); @@ -442,6 +447,11 @@ int Lua_Mob::GetClass() { return self->GetClass(); } +const char *Lua_Mob::GetClassName() { + Lua_Safe_Call_String(); + return GetClassIDName(self->GetClass()); +} + int Lua_Mob::GetLevel() { Lua_Safe_Call_Int(); return self->GetLevel(); @@ -2316,6 +2326,7 @@ luabind::scope lua_register_mob() { .def("GetBaseGender", &Lua_Mob::GetBaseGender) .def("GetDeity", &Lua_Mob::GetDeity) .def("GetRace", &Lua_Mob::GetRace) + .def("GetRaceName", &Lua_Mob::GetRaceName) .def("GetGender", &Lua_Mob::GetGender) .def("GetTexture", &Lua_Mob::GetTexture) .def("GetHelmTexture", &Lua_Mob::GetHelmTexture) @@ -2330,6 +2341,7 @@ luabind::scope lua_register_mob() { .def("GetDrakkinTattoo", &Lua_Mob::GetDrakkinTattoo) .def("GetDrakkinDetails", &Lua_Mob::GetDrakkinDetails) .def("GetClass", &Lua_Mob::GetClass) + .def("GetClassName", &Lua_Mob::GetClassName) .def("GetLevel", &Lua_Mob::GetLevel) .def("GetCleanName", &Lua_Mob::GetCleanName) .def("GetTarget", &Lua_Mob::GetTarget) diff --git a/zone/lua_mob.h b/zone/lua_mob.h index e2dbf9e24..426ac01af 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -92,6 +92,8 @@ public: int GetBaseGender(); int GetDeity(); int GetRace(); + const char *GetClassName(); + const char *GetRaceName(); int GetGender(); int GetTexture(); int GetHelmTexture(); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 1dc9f2128..de30d0c50 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -7086,6 +7086,58 @@ XS(XS_Client_Fling) { XSRETURN_EMPTY; } +XS(XS_Client_GetClassBitmask); +XS(XS_Client_GetClassBitmask) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::GetClassBitmask(THIS)"); + { + Client* THIS; + int client_bitmask = 0; + dXSTARG; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client*, tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + client_bitmask = GetPlayerClassBit(THIS->GetClass()); + XSprePUSH; + PUSHu((UV) client_bitmask); + } + XSRETURN(1); +} + +XS(XS_Client_GetRaceBitmask); +XS(XS_Client_GetRaceBitmask) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::GetRaceBitmask(THIS)"); + { + Client* THIS; + int client_bitmask = 0; + dXSTARG; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client*, tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + client_bitmask = GetPlayerRaceBit(THIS->GetBaseRace()); + XSprePUSH; + PUSHu((UV) client_bitmask); + } + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -7174,6 +7226,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetBindZoneID"), XS_Client_GetBindZoneID, file, "$$"); newXSproto(strcpy(buf, "GetCarriedMoney"), XS_Client_GetCarriedMoney, file, "$"); newXSproto(strcpy(buf, "GetCharacterFactionLevel"), XS_Client_GetCharacterFactionLevel, file, "$$"); + newXSproto(strcpy(buf, "GetClassBitmask"), XS_Client_GetClassBitmask, file, "$"); newXSproto(strcpy(buf, "GetClientMaxLevel"), XS_Client_GetClientMaxLevel, file, "$"); newXSproto(strcpy(buf, "GetClientVersion"), XS_Client_GetClientVersion, file, "$"); newXSproto(strcpy(buf, "GetClientVersionBit"), XS_Client_GetClientVersionBit, file, "$"); @@ -7218,6 +7271,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetMoney"), XS_Client_GetMoney, file, "$$$"); newXSproto(strcpy(buf, "GetPVP"), XS_Client_GetPVP, file, "$"); newXSproto(strcpy(buf, "GetPVPPoints"), XS_Client_GetPVPPoints, file, "$"); + newXSproto(strcpy(buf, "GetRaceBitmask"), XS_Client_GetRaceBitmask, file, "$"); newXSproto(strcpy(buf, "GetRadiantCrystals"), XS_Client_GetRadiantCrystals, file, "$"); newXSproto(strcpy(buf, "GetRaid"), XS_Client_GetRaid, file, "$"); newXSproto(strcpy(buf, "GetRaidPoints"), XS_Client_GetRaidPoints, file, "$"); diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index f90435887..132719195 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -8594,6 +8594,60 @@ XS(XS_Mob_TryMoveAlong) { XSRETURN_EMPTY; } +XS(XS_Mob_GetClassName); +XS(XS_Mob_GetClassName) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::GetClassName(THIS)"); + { + Mob* THIS; + Const_char *class_name; + dXSTARG; + + if (sv_derived_from(ST(0), "Mob")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Mob*, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Mob"); + + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + class_name = GetClassIDName(THIS->GetClass()); + sv_setpv(TARG, class_name); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Mob_GetRaceName); +XS(XS_Mob_GetRaceName) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::GetRaceName(THIS)"); + { + Mob* THIS; + Const_char *race_name; + dXSTARG; + + if (sv_derived_from(ST(0), "Mob")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Mob*, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Mob"); + + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + race_name = GetRaceIDName(THIS->GetRace()); + sv_setpv(TARG, race_name); + XSprePUSH; + PUSHTARG; + } + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -8668,6 +8722,7 @@ XS(boot_Mob) { newXSproto(strcpy(buf, "GetBaseGender"), XS_Mob_GetBaseGender, file, "$"); newXSproto(strcpy(buf, "GetDeity"), XS_Mob_GetDeity, file, "$"); newXSproto(strcpy(buf, "GetRace"), XS_Mob_GetRace, file, "$"); + newXSproto(strcpy(buf, "GetRaceName"), XS_Mob_GetRaceName, file, "$"); newXSproto(strcpy(buf, "GetGender"), XS_Mob_GetGender, file, "$"); newXSproto(strcpy(buf, "GetTexture"), XS_Mob_GetTexture, file, "$"); newXSproto(strcpy(buf, "GetHelmTexture"), XS_Mob_GetHelmTexture, file, "$"); @@ -8682,6 +8737,7 @@ XS(boot_Mob) { newXSproto(strcpy(buf, "GetDrakkinTattoo"), XS_Mob_GetDrakkinTattoo, file, "$"); newXSproto(strcpy(buf, "GetDrakkinDetails"), XS_Mob_GetDrakkinDetails, file, "$"); newXSproto(strcpy(buf, "GetClass"), XS_Mob_GetClass, file, "$"); + newXSproto(strcpy(buf, "GetClassName"), XS_Mob_GetClassName, file, "$"); newXSproto(strcpy(buf, "GetLevel"), XS_Mob_GetLevel, file, "$"); newXSproto(strcpy(buf, "GetCleanName"), XS_Mob_GetCleanName, file, "$"); newXSproto(strcpy(buf, "GetTarget"), XS_Mob_GetTarget, file, "$"); From 0f859a2a0fe4c6e9239757592d7df3d7130c2b94 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Tue, 19 Jan 2021 18:34:19 -0500 Subject: [PATCH 184/196] Preferential changes to #finditem, doesn't show stack options if item isn't stackable. Only shows 1 or max stack size of item for summoning items. Also resolves the issues some people were having where this command would summon an item with 1 charge instead of max charges because 1 was supplied as charges. In my experience most people who summon items like this want either one item or a full stack of an item and the summon size here is relative to the current item's stack size. Also shows name first instead of ID so the formatting is better. --- zone/command.cpp | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/zone/command.cpp b/zone/command.cpp index 7c98bc6fa..0e9220fe4 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -7978,13 +7978,6 @@ void command_itemsearch(Client *c, const Seperator *sep) return; } - std::vector amounts = { - "1", - "10", - "100", - "1000" - }; - int count = 0; char sName[64]; char sCriteria[255]; @@ -7998,14 +7991,25 @@ void command_itemsearch(Client *c, const Seperator *sep) pdest = strstr(sName, sCriteria); if (pdest != nullptr) { linker.SetItemData(item); - - std::string saylink_commands; - for (auto &amount : amounts) { - saylink_commands += EQ::SayLinkEngine::GenerateQuestSaylink( - "#si " + std::to_string(item->ID) + " " + amount, + std::string item_id = std::to_string(item->ID); + std::string saylink_commands = + "[" + + EQ::SayLinkEngine::GenerateQuestSaylink( + "#si " + item_id, false, - "[" + amount + "] " - ); + "X" + ) + + "] "; + if (item->Stackable && item->StackSize > 1) { + std::string stack_size = std::to_string(item->StackSize); + saylink_commands += + "[" + + EQ::SayLinkEngine::GenerateQuestSaylink( + "#si " + item_id + " " + stack_size, + false, + stack_size + ) + + "]"; } c->Message( @@ -8013,8 +8017,8 @@ void command_itemsearch(Client *c, const Seperator *sep) fmt::format( " Summon {} [{}] [{}]", saylink_commands, - item->ID, - linker.GenerateLink() + linker.GenerateLink(), + item->ID ).c_str() ); From d87ae839a2daf24377fe73556c5ecbbe094e3a4d Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 21 Jan 2021 19:02:00 -0500 Subject: [PATCH 185/196] Verify members in db on expedition invites Fixes an exploit where multiple accepted cross zone invites could race with cache updates and allow an expedition to exceed its max members --- zone/expedition.cpp | 10 ++++++--- zone/expedition_database.cpp | 42 ++++++++++++++++++++++++++++++++++++ zone/expedition_database.h | 5 ++--- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index cc88b8c70..f75b09c3f 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -782,7 +782,7 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, } // swapping ignores the max player count check since it's a 1:1 change - if (!swapping && GetMemberCount() >= m_max_players) + if (!swapping && ExpeditionDatabase::GetMemberCount(m_id) >= m_max_players) { SendLeaderMessage(leader_client, Chat::Red, DZADD_EXCEED_MAX, { fmt::format_int(m_max_players).str() }); has_conflict = true; @@ -834,9 +834,13 @@ void Expedition::DzInviteResponse(Client* add_client, bool accepted, const std:: } // error if swapping and character was already removed before the accept - if (was_swap_invite && !HasMember(swap_remove_name)) + if (was_swap_invite) { - has_conflicts = true; + auto swap_member = GetMemberData(swap_remove_name); + if (!swap_member.IsValid() || !ExpeditionDatabase::HasMember(m_id, swap_member.char_id)) + { + has_conflicts = true; + } } if (has_conflicts) diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 7d1fd7a3c..d5bb4925d 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -373,6 +373,48 @@ uint32_t ExpeditionDatabase::GetExpeditionIDFromCharacterID(uint32_t character_i return expedition_id; } +uint32_t ExpeditionDatabase::GetMemberCount(uint32_t expedition_id) +{ + LogExpeditionsDetail("Getting expedition [{}] member count from db", expedition_id); + + uint32_t member_count = 0; + if (expedition_id != 0) + { + auto query = fmt::format(SQL( + SELECT COUNT(*) + FROM expedition_members + WHERE expedition_id = {} AND is_current_member = TRUE; + ), expedition_id); + + auto results = database.QueryDatabase(query); + if (results.Success() && results.RowCount() > 0) + { + auto row = results.begin(); + member_count = strtoul(row[0], nullptr, 10); + } + } + return member_count; +} + +bool ExpeditionDatabase::HasMember(uint32_t expedition_id, uint32_t character_id) +{ + LogExpeditionsDetail("Checking db expedition [{}] for character [{}]", expedition_id, character_id); + + if (expedition_id == 0 || character_id == 0) + { + return false; + } + + auto query = fmt::format(SQL( + SELECT id + FROM expedition_members + WHERE expedition_id = {} AND character_id = {} AND is_current_member = TRUE; + ), expedition_id, character_id); + + auto results = database.QueryDatabase(query); + return (results.Success() && results.RowCount() > 0); +} + void ExpeditionDatabase::InsertCharacterLockouts(uint32_t character_id, const std::vector& lockouts) { diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 2efeba2f6..5484ea25a 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -58,9 +58,8 @@ namespace ExpeditionDatabase void DeleteMembersLockout(const std::vector& members, const std::string& expedition_name, const std::string& event_name); uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); - std::pair, std::vector> GetMembersLockout( - const std::vector& members, const std::string& expedition_name, - const std::string& event_name); + uint32_t GetMemberCount(uint32_t expedition_id); + bool HasMember(uint32_t expedition_id, uint32_t character_id); void InsertCharacterLockouts(uint32_t character_id, const std::vector& lockouts); void InsertMembersLockout(const std::vector& members, From c481d52064bc07f1b8d727f225a0d93338279ae6 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 23 Jan 2021 10:23:50 -0500 Subject: [PATCH 186/196] Add HasDisciplineLearned() to Perl/Lua. --- zone/client.h | 1 + zone/effects.cpp | 11 +++++++++++ zone/lua_client.cpp | 6 ++++++ zone/lua_client.h | 1 + zone/perl_client.cpp | 27 +++++++++++++++++++++++++++ 5 files changed, 46 insertions(+) diff --git a/zone/client.h b/zone/client.h index 6829ce303..2b0693779 100644 --- a/zone/client.h +++ b/zone/client.h @@ -975,6 +975,7 @@ public: void SendDisciplineUpdate(); void SendDisciplineTimer(uint32 timer_id, uint32 duration); bool UseDiscipline(uint32 spell_id, uint32 target); + bool HasDisciplineLearned(uint16 spell_id); void SetLinkedSpellReuseTimer(uint32 timer_id, uint32 duration); bool IsLinkedSpellReuseTimerReady(uint32 timer_id); diff --git a/zone/effects.cpp b/zone/effects.cpp index c2a20cf71..b62a2b383 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -678,6 +678,17 @@ void Client::ResetDisciplineTimer(uint32 timer_id) { SendDisciplineTimer(timer_id, 0); } +bool Client::HasDisciplineLearned(uint16 spell_id) { + bool has_learned = false; + for (auto index = 0; index < MAX_PP_DISCIPLINES; ++index) { + if (GetPP().disciplines.values[index] == spell_id) { + has_learned = true; + break; + } + } + return has_learned; +} + void Client::SendDisciplineTimer(uint32 timer_id, uint32 duration) { if (timer_id < MAX_DISCIPLINE_TIMERS) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index c04345cc6..fbe9bebdb 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -929,6 +929,11 @@ bool Lua_Client::UseDiscipline(int spell_id, int target_id) { return self->UseDiscipline(spell_id, target_id); } +bool Lua_Client::HasDisciplineLearned(uint16 spell_id) { + Lua_Safe_Call_Bool(); + return self->HasDisciplineLearned(spell_id); +} + int Lua_Client::GetCharacterFactionLevel(int faction_id) { Lua_Safe_Call_Int(); return self->GetCharacterFactionLevel(faction_id); @@ -2104,6 +2109,7 @@ luabind::scope lua_register_client() { .def("GetDisciplineTimer", (uint32(Lua_Client::*)(uint32))&Lua_Client::GetDisciplineTimer) .def("ResetDisciplineTimer", (void(Lua_Client::*)(uint32))&Lua_Client::ResetDisciplineTimer) .def("UseDiscipline", (bool(Lua_Client::*)(int,int))&Lua_Client::UseDiscipline) + .def("HasDisciplineLearned", (bool(Lua_Client::*)(uint16))&Lua_Client::HasDisciplineLearned) .def("GetCharacterFactionLevel", (int(Lua_Client::*)(int))&Lua_Client::GetCharacterFactionLevel) .def("SetZoneFlag", (void(Lua_Client::*)(int))&Lua_Client::SetZoneFlag) .def("ClearZoneFlag", (void(Lua_Client::*)(int))&Lua_Client::ClearZoneFlag) diff --git a/zone/lua_client.h b/zone/lua_client.h index 50043ab77..fda6f08ee 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -213,6 +213,7 @@ public: uint32 GetDisciplineTimer(uint32 timer_id); void ResetDisciplineTimer(uint32 timer_id); bool UseDiscipline(int spell_id, int target_id); + bool HasDisciplineLearned(uint16 spell_id); int GetCharacterFactionLevel(int faction_id); void SetZoneFlag(int zone_id); void ClearZoneFlag(int zone_id); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index de30d0c50..3697335d3 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -7086,6 +7086,32 @@ XS(XS_Client_Fling) { XSRETURN_EMPTY; } +XS(XS_Client_HasDisciplineLearned); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_HasDisciplineLearned) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Client::HasDisciplineLearned(THIS, uint16 spell_id)"); + { + Client *THIS; + bool has_learned; + uint16 spell_id = (uint16) SvUV(ST(1)); + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV *) SvRV(ST(0))); + THIS = INT2PTR(Client *, tmp); + } else + Perl_croak(aTHX_ "THIS is not of type Client"); + + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + has_learned = THIS->HasDisciplineLearned(spell_id); + ST(0) = boolSV(has_learned); + sv_2mortal(ST(0)); + } + XSRETURN(1); +} + XS(XS_Client_GetClassBitmask); XS(XS_Client_GetClassBitmask) { dXSARGS; @@ -7294,6 +7320,7 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GrantAlternateAdvancementAbility"), XS_Client_GrantAlternateAdvancementAbility, file, "$$$;$"); newXSproto(strcpy(buf, "GuildID"), XS_Client_GuildID, file, "$"); newXSproto(strcpy(buf, "GuildRank"), XS_Client_GuildRank, file, "$"); + newXSproto(strcpy(buf, "HasDisciplineLearned"), XS_Client_HasDisciplineLearned, file, "$$"); newXSproto(strcpy(buf, "HasExpeditionLockout"), XS_Client_HasExpeditionLockout, file, "$$$"); newXSproto(strcpy(buf, "HasSkill"), XS_Client_HasSkill, file, "$$"); newXSproto(strcpy(buf, "HasSpellScribed"), XS_Client_HasSkill, file, "$$"); From 19ae461e363e9985cd142857365ec52ffe3fa78c Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Sat, 23 Jan 2021 10:46:36 -0500 Subject: [PATCH 187/196] Add message(color, message) and whisper(message) to Perl/Lua. - Add quest::message(color, message) to Perl. - Add eq.message(color, message) to Lua. - Add quest::whisper(message) to Perl. - Add eq.whisper(message) to Lua. These methods allow you to use implied client references. The whisper method also converts a widely used plugin in Perl to a Perl and Lua method that works on both Clients and NPCs. --- zone/embparser_api.cpp | 25 +++++++++++++++++++++++++ zone/lua_general.cpp | 10 ++++++++++ zone/questmgr.cpp | 18 ++++++++++++++++++ zone/questmgr.h | 2 ++ 4 files changed, 55 insertions(+) diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 17e20b9ec..fc541a088 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -2793,6 +2793,29 @@ XS(XS__we) { XSRETURN_EMPTY; } +XS(XS__message); +XS(XS__message) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: quest::message(int color, string message)"); + + int color = (int) SvIV(ST(0)); + char *message = (char *) SvPV_nolen(ST(1)); + quest_manager.message(color, message); + XSRETURN_EMPTY; +} + +XS(XS__whisper); +XS(XS__whisper) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::whisper(string message)"); + + char *message = (char *) SvPV_nolen(ST(0)); + quest_manager.whisper(message); + XSRETURN_EMPTY; +} + XS(XS__getlevel); XS(XS__getlevel) { dXSARGS; @@ -6569,6 +6592,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "log"), XS__log, file); newXS(strcpy(buf, "log_combat"), XS__log_combat, file); newXS(strcpy(buf, "me"), XS__me, file); + newXS(strcpy(buf, "message"), XS__message, file); newXS(strcpy(buf, "modifynpcstat"), XS__ModifyNPCStat, file); newXS(strcpy(buf, "movegrp"), XS__movegrp, file); newXS(strcpy(buf, "movepc"), XS__movepc, file); @@ -6665,6 +6689,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "voicetell"), XS__voicetell, file); newXS(strcpy(buf, "we"), XS__we, file); newXS(strcpy(buf, "wearchange"), XS__wearchange, file); + newXS(strcpy(buf, "whisper"), XS__whisper, file); newXS(strcpy(buf, "write"), XS__write, file); newXS(strcpy(buf, "ze"), XS__ze, file); newXS(strcpy(buf, "zone"), XS__zone, file); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 34cc30b29..568dd70e8 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -768,6 +768,14 @@ void lua_world_emote(int type, const char *str) { quest_manager.we(type, str); } +void lua_message(int color, const char *message) { + quest_manager.message(color, message); +} + +void lua_whisper(const char *message) { + quest_manager.whisper(message); +} + int lua_get_level(int type) { return quest_manager.getlevel(type); } @@ -2599,6 +2607,8 @@ luabind::scope lua_register_general() { luabind::def("clear_spawn_timers", &lua_clear_spawn_timers), luabind::def("zone_emote", &lua_zone_emote), luabind::def("world_emote", &lua_world_emote), + luabind::def("message", &lua_message), + luabind::def("whisper", &lua_whisper), luabind::def("get_level", &lua_get_level), luabind::def("create_ground_object", (void(*)(uint32,float,float,float,float))&lua_create_ground_object), luabind::def("create_ground_object", (void(*)(uint32,float,float,float,float,uint32))&lua_create_ground_object), diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 631bb1218..e75fa0a01 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2580,6 +2580,24 @@ void QuestManager::we(int type, const char *str) { worldserver.SendEmoteMessage(0, 0, type, str); } +void QuestManager::message(int color, const char *message) { + QuestManagerCurrentQuestVars(); + if (!initiator) + return; + + initiator->Message(color, message); +} + +void QuestManager::whisper(const char *message) { + QuestManagerCurrentQuestVars(); + if (!initiator || !owner) + return; + + std::string mob_name = owner->GetCleanName(); + std::string new_message = fmt::format("{} whispers, '{}'", mob_name, message); + initiator->Message(315, new_message.c_str()); +} + int QuestManager::getlevel(uint8 type) { QuestManagerCurrentQuestVars(); diff --git a/zone/questmgr.h b/zone/questmgr.h index f3f656e51..a5620941d 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -223,6 +223,8 @@ public: void clearspawntimers(); void ze(int type, const char *str); void we(int type, const char *str); + void message(int color, const char *message); + void whisper(const char *message); int getlevel(uint8 type); int collectitems(uint32 item_id, bool remove); int collectitems_processSlot(int16 slot_id, uint32 item_id, bool remove); From 102263f37d39170dedb7acbdfaa9aa297f5ede7e Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Sat, 23 Jan 2021 11:15:21 -0500 Subject: [PATCH 188/196] Fix Popup2 Perl croak. Noticed the Gitbook documentation listed SendFullPopup not Popup2 due to parsing the Perl croaks in the files, causing people not to be able to find the proper way to send a full popup window. --- zone/perl_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 3697335d3..33184a7ae 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -6634,7 +6634,7 @@ XS(XS_Client_Popup2); /* prototype to pass -Wmissing-prototypes */ XS(XS_Client_Popup2) { dXSARGS; if (items < 3 || items > 10) - Perl_croak(aTHX_ "Usage: Client::SendFullPopup(THIS, string title, string text, uint32 popup_id, uint32 negative_id, uint32 buttons, uint32 duration, string button_name_0, string button_name_1, uint32 sound_controls)"); + Perl_croak(aTHX_ "Usage: Client::Popup2(THIS, string title, string text, uint32 popup_id, uint32 negative_id, uint32 buttons, uint32 duration, string button_name_0, string button_name_1, uint32 sound_controls)"); { Client *THIS; char *Title = (char *) SvPV_nolen(ST(1)); From d30593c35eefb33876e02d797e34c7da3bfa331d Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Sat, 23 Jan 2021 20:36:30 -0500 Subject: [PATCH 189/196] Add 4 new special attacks to Perl/Lua. - IMMUNE_DAMAGE_CLIENT (47) Immune to all damage except NPC damage. - IMMUNE_DAMAGE_NPC (48) Immune to all damage except Client damage. - IMMUNE_AGGRO_CLIENT (49) Immune to aggro by a Client. - IMMUNE_AGGRO_NPC (50) Immune to aggro by an NPC, clients must attack directly to gain aggro, allows pet only boss mechanics and stuff. --- zone/aggro.cpp | 6 ++++++ zone/attack.cpp | 44 ++++++++++++++++++++++++++++++++++++++------ zone/client.cpp | 16 ++++++++++------ zone/common.h | 6 +++++- zone/entity.cpp | 7 ++++++- zone/lua_mob.cpp | 6 +++++- zone/mob.cpp | 6 ++++++ 7 files changed, 76 insertions(+), 15 deletions(-) diff --git a/zone/aggro.cpp b/zone/aggro.cpp index d23732df4..33221b63a 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -468,6 +468,12 @@ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) return false; } + if (target->GetSpecialAbility(IMMUNE_DAMAGE_CLIENT) && IsClient()) + return false; + + if (target->GetSpecialAbility(IMMUNE_DAMAGE_NPC) && IsNPC()) + return false; + // can't damage own pet (applies to everthing) Mob *target_owner = target->GetOwner(); Mob *our_owner = GetOwner(); diff --git a/zone/attack.cpp b/zone/attack.cpp index 2531024ee..3f614598a 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2667,6 +2667,12 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b if (IsFamiliar() || GetSpecialAbility(IMMUNE_AGGRO)) return; + if (GetSpecialAbility(IMMUNE_AGGRO_NPC) && other->IsNPC()) + return; + + if (GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && other->IsClient()) + return; + if (spell_id != SPELL_UNKNOWN && NoDetrimentalSpellAggro(spell_id)) return; @@ -2758,8 +2764,11 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b else { // cb:2007-08-17 // owner must get on list, but he's not actually gained any hate yet - if (!owner->GetSpecialAbility(IMMUNE_AGGRO)) - { + if ( + !owner->GetSpecialAbility(IMMUNE_AGGRO) && + !(this->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && owner->IsClient()) && + !(this->GetSpecialAbility(IMMUNE_AGGRO_NPC) && owner->IsNPC()) + ) { if (owner->IsClient() && !CheckAggro(owner)) owner->CastToClient()->AddAutoXTarget(this); hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic); @@ -2768,12 +2777,24 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b } if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it - if (!mypet->IsFamiliar() && !mypet->GetSpecialAbility(IMMUNE_AGGRO)) + if ( + !mypet->IsFamiliar() && + !mypet->GetSpecialAbility(IMMUNE_AGGRO) && + !(mypet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && this->IsClient()) && + !(mypet->GetSpecialAbility(IMMUNE_AGGRO_NPC) && this->IsNPC()) + ) { mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); + } } else if (myowner) { // I am a pet, add other to owner if it's NPC/LD - if (myowner->IsAIControlled() && !myowner->GetSpecialAbility(IMMUNE_AGGRO)) + if ( + myowner->IsAIControlled() && + !myowner->GetSpecialAbility(IMMUNE_AGGRO) && + !(this->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && myowner->IsClient()) && + !(this->GetSpecialAbility(IMMUNE_AGGRO_NPC) && myowner->IsNPC()) + ) { myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); + } } if (other->GetTempPetCount()) @@ -3467,8 +3488,19 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const // pets that have GHold will never automatically add NPCs // pets that have Hold and no Focus will add NPCs if they're engaged // pets that have Hold and Focus will not add NPCs - if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld() && !attacker->IsTrap()) - { + if ( + pet && + !pet->IsFamiliar() && + !pet->GetSpecialAbility(IMMUNE_AGGRO) && + !pet->IsEngaged() && + attacker && + !(pet->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && attacker->IsClient()) && + !(pet->GetSpecialAbility(IMMUNE_AGGRO_NPC) && attacker->IsNPC()) && + attacker != this && + !attacker->IsCorpse() && + !pet->IsGHeld() && + !attacker->IsTrap() + ) { if (!pet->IsHeld()) { LogAggro("Sending pet [{}] into battle due to attack", pet->GetName()); if (IsClient()) { diff --git a/zone/client.cpp b/zone/client.cpp index 726d736e2..12105109f 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -2433,13 +2433,17 @@ bool Client::CheckIncreaseSkill(EQ::skills::SkillType skillid, Mob *against_who, char buffer[24] = { 0 }; snprintf(buffer, 23, "%d %d", skillid, skillval); parse->EventPlayer(EVENT_USE_SKILL, this, buffer, 0); - if(against_who) - { - if(against_who->GetSpecialAbility(IMMUNE_AGGRO) || against_who->IsClient() || - GetLevelCon(against_who->GetLevel()) == CON_GRAY) - { + if (against_who) { + if ( + against_who->GetSpecialAbility(IMMUNE_AGGRO) || + against_who->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) || + against_who->IsClient() || + GetLevelCon(against_who->GetLevel()) == CON_GRAY + ) { //false by default - if( !mod_can_increase_skill(skillid, against_who) ) { return(false); } + if (!mod_can_increase_skill(skillid, against_who)) { + return false; + } } } diff --git a/zone/common.h b/zone/common.h index c05c6a95b..fd513bdde 100644 --- a/zone/common.h +++ b/zone/common.h @@ -195,7 +195,11 @@ enum { COUNTER_AVOID_DAMAGE = 44, PROX_AGGRO = 45, IMMUNE_RANGED_ATTACKS = 46, - MAX_SPECIAL_ATTACK = 47 + IMMUNE_DAMAGE_CLIENT = 47, + IMMUNE_DAMAGE_NPC = 48, + IMMUNE_AGGRO_CLIENT = 49, + IMMUNE_AGGRO_NPC = 50, + MAX_SPECIAL_ATTACK = 51 }; typedef enum { //fear states diff --git a/zone/entity.cpp b/zone/entity.cpp index b2b4b3706..f48b371ce 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -4135,8 +4135,13 @@ void EntityList::AddTempPetsToHateList(Mob *owner, Mob* other, bool bFrenzy) NPC* n = it->second; if (n->GetSwarmInfo()) { if (n->GetSwarmInfo()->owner_id == owner->GetID()) { - if (!n->GetSpecialAbility(IMMUNE_AGGRO)) + if ( + !n->GetSpecialAbility(IMMUNE_AGGRO) && + !(n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && other->IsClient()) && + !(n->GetSpecialAbility(IMMUNE_AGGRO_NPC) && other->IsNPC()) + ) { n->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); + } } } ++it; diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index f8379b82e..53d277b12 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -2708,7 +2708,11 @@ luabind::scope lua_register_special_abilities() { luabind::value("ignore_root_aggro_rules", static_cast(IGNORE_ROOT_AGGRO_RULES)), luabind::value("casting_resist_diff", static_cast(CASTING_RESIST_DIFF)), luabind::value("counter_avoid_damage", static_cast(COUNTER_AVOID_DAMAGE)), - luabind::value("immune_ranged_attacks", static_cast(IMMUNE_RANGED_ATTACKS)) + luabind::value("immune_ranged_attacks", static_cast(IMMUNE_RANGED_ATTACKS)), + luabind::value("immune_damage_client", static_cast(IMMUNE_DAMAGE_CLIENT)), + luabind::value("immune_damage_npc", static_cast(IMMUNE_DAMAGE_NPC)), + luabind::value("immune_aggro_client", static_cast(IMMUNE_AGGRO_CLIENT)), + luabind::value("immune_aggro_npc", static_cast(IMMUNE_AGGRO_NPC)) ]; } diff --git a/zone/mob.cpp b/zone/mob.cpp index d58ee4b54..825ae2538 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3132,6 +3132,12 @@ void Mob::ExecWeaponProc(const EQ::ItemInstance *inst, uint16 spell_id, Mob *on, return; } + if (on->GetSpecialAbility(IMMUNE_DAMAGE_CLIENT) && IsClient()) + return; + + if (on->GetSpecialAbility(IMMUNE_DAMAGE_NPC) && IsNPC()) + return; + if (IsNoCast()) return; From 9907984aca15eeda7b2439d63a1431650f20668d Mon Sep 17 00:00:00 2001 From: Noudess Date: Tue, 26 Jan 2021 09:08:45 -0500 Subject: [PATCH 190/196] Use passed heading in GMMove and change FaceTarget not to turn if npc is boat. --- zone/mob.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zone/mob.cpp b/zone/mob.cpp index 825ae2538..d2ed1e983 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -1734,6 +1734,7 @@ void Mob::GMMove(float x, float y, float z, float heading, bool SendUpdate) { m_Position.x = x; m_Position.y = y; m_Position.z = z; + SetHeading(heading); mMovementManager->SendCommandToClients(this, 0.0, 0.0, 0.0, 0.0, 0, ClientRangeAny); if (IsNPC()) { @@ -2820,6 +2821,11 @@ bool Mob::HateSummon() { } void Mob::FaceTarget(Mob* mob_to_face /*= 0*/) { + + if (IsBoat()) { + return; + } + Mob* faced_mob = mob_to_face; if(!faced_mob) { if(!GetTarget()) { From 6baf8412c6fd3aa0f45816379092b640a3620913 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 28 Jan 2021 19:42:02 -0500 Subject: [PATCH 191/196] Replace unescaped braces in format string Fixes zone crash when this is logged --- common/say_link.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/say_link.cpp b/common/say_link.cpp index 5fb847294..b47897112 100644 --- a/common/say_link.cpp +++ b/common/say_link.cpp @@ -101,7 +101,7 @@ const std::string &EQ::SayLinkEngine::GenerateLink() m_Error = true; m_Link = ""; LogError("SayLinkEngine::GenerateLink() failed to generate a useable say link"); - LogError(">> LinkType: {}, Lengths: {link: {}({}), body: {}({}), text: {}({})}", + LogError(">> LinkType: {}, Lengths: [link: {}({}), body: {}({}), text: {}({})]", m_LinkType, m_Link.length(), EQ::constants::SAY_LINK_MAXIMUM_SIZE, From a77d2408ac579abf9a49f351da576443c21c7886 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Thu, 28 Jan 2021 20:10:26 -0500 Subject: [PATCH 192/196] Add missing chat color constants to Lua --- zone/lua_general.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 568dd70e8..b903ca774 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -3403,7 +3403,24 @@ luabind::scope lua_register_message_types() { return luabind::class_("MT") .enum_("constants") [ + luabind::value("White", Chat::White), + luabind::value("DimGray", Chat::DimGray), + luabind::value("Default", Chat::Default), + luabind::value("Green", Chat::Green), + luabind::value("BrightBlue", Chat::BrightBlue), + luabind::value("LightBlue", Chat::LightBlue), + luabind::value("Magenta", Chat::Magenta), + luabind::value("Gray", Chat::Gray), + luabind::value("LightGray", Chat::LightGray), luabind::value("NPCQuestSay", Chat::NPCQuestSay), + luabind::value("DarkGray", Chat::DarkGray), + luabind::value("Red", Chat::Red), + luabind::value("Lime", Chat::Lime), + luabind::value("Yellow", Chat::Yellow), + luabind::value("Blue", Chat::Blue), + luabind::value("LightNavy", Chat::LightNavy), + luabind::value("Cyan", Chat::Cyan), + luabind::value("Black", Chat::Black), luabind::value("Say", Chat::Say), luabind::value("Tell", Chat::Tell), luabind::value("Group", Chat::Group), From c0129a6b8ac1200e2ea68352a98997c633f31a84 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Thu, 28 Jan 2021 19:59:31 -0500 Subject: [PATCH 193/196] Add new Spell methods to Perl and Lua. --- zone/client.cpp | 122 ++++++++++++++++++++++++++++- zone/client.h | 5 ++ zone/lua_client.cpp | 135 ++++++++++++++++++++++++++++++++ zone/lua_client.h | 9 +++ zone/perl_client.cpp | 129 +++++++++++++++++++++++++++++++ zone/questmgr.cpp | 179 +++++++------------------------------------ 6 files changed, 425 insertions(+), 154 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index 726d736e2..b46e99466 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -10009,4 +10009,124 @@ void Client::Fling(float value, float target_x, float target_y, float target_z, outapp_fling->priority = 6; FastQueuePacket(&outapp_fling); } -} \ No newline at end of file +} + +std::vector Client::GetLearnableDisciplines(uint8 min_level, uint8 max_level) { + bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); + bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); + bool SpellGlobalCheckResult = false; + bool SpellBucketCheckResult = false; + std::vector learnable_disciplines; + for (int spell_id = 0; spell_id < SPDAT_RECORDS; ++spell_id) { + bool learnable = false; + if (!IsValidSpell(spell_id)) + continue; + if (!IsDiscipline(spell_id)) + continue; + if (spells[spell_id].classes[WARRIOR] == 0) + continue; + if (max_level > 0 && spells[spell_id].classes[m_pp.class_ - 1] > max_level) + continue; + if (min_level > 1 && spells[spell_id].classes[m_pp.class_ - 1] < min_level) + continue; + if (spells[spell_id].skill == 52) + continue; + if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) + continue; + if (HasDisciplineLearned(spell_id)) + continue; + + if (SpellGlobalRule) { + SpellGlobalCheckResult = SpellGlobalCheck(spell_id, CharacterID()); + if (SpellGlobalCheckResult) { + learnable = true; + } + } else if (SpellBucketRule) { + SpellBucketCheckResult = SpellBucketCheck(spell_id, CharacterID()); + if (SpellBucketCheckResult) { + learnable = true; + } + } else { + learnable = true; + } + + if (learnable) { + learnable_disciplines.push_back(spell_id); + } + } + return learnable_disciplines; +} + +std::vector Client::GetLearnedDisciplines() { + std::vector learned_disciplines; + for (int index = 0; index < MAX_PP_DISCIPLINES; index++) { + if (IsValidSpell(m_pp.disciplines.values[index])) { + learned_disciplines.push_back(m_pp.disciplines.values[index]); + } + } + return learned_disciplines; +} + +std::vector Client::GetMemmedSpells() { + std::vector memmed_spells; + for (int index = 0; index < EQ::spells::SPELL_GEM_COUNT; index++) { + if (IsValidSpell(m_pp.mem_spells[index])) { + memmed_spells.push_back(m_pp.mem_spells[index]); + } + } + return memmed_spells; +} + +std::vector Client::GetScribeableSpells(uint8 min_level, uint8 max_level) { + bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); + bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); + bool SpellGlobalCheckResult = false; + bool SpellBucketCheckResult = false; + std::vector scribeable_spells; + for (int spell_id = 0; spell_id < SPDAT_RECORDS; ++spell_id) { + bool scribeable = false; + if (!IsValidSpell(spell_id)) + continue; + if (spells[spell_id].classes[WARRIOR] == 0) + continue; + if (max_level > 0 && spells[spell_id].classes[m_pp.class_ - 1] > max_level) + continue; + if (min_level > 1 && spells[spell_id].classes[m_pp.class_ - 1] < min_level) + continue; + if (spells[spell_id].skill == 52) + continue; + if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) + continue; + if (HasSpellScribed(spell_id)) + continue; + + if (SpellGlobalRule) { + SpellGlobalCheckResult = SpellGlobalCheck(spell_id, CharacterID()); + if (SpellGlobalCheckResult) { + scribeable = true; + } + } else if (SpellBucketRule) { + SpellBucketCheckResult = SpellBucketCheck(spell_id, CharacterID()); + if (SpellBucketCheckResult) { + scribeable = true; + } + } else { + scribeable = true; + } + + if (scribeable) { + scribeable_spells.push_back(spell_id); + } + } + return scribeable_spells; +} + +std::vector Client::GetScribedSpells() { + std::vector scribed_spells; + for(int index = 0; index < EQ::spells::SPELLBOOK_SIZE; index++) { + if (IsValidSpell(m_pp.spell_book[index])) { + scribed_spells.push_back(m_pp.spell_book[index]); + } + } + return scribed_spells; +} diff --git a/zone/client.h b/zone/client.h index 2b0693779..cc461bc22 100644 --- a/zone/client.h +++ b/zone/client.h @@ -784,6 +784,11 @@ public: void UnmemSpellAll(bool update_client = true); uint16 FindMemmedSpellBySlot(int slot); int MemmedCount(); + std::vector GetLearnableDisciplines(uint8 min_level = 1, uint8 max_level = 0); + std::vector GetLearnedDisciplines(); + std::vector GetMemmedSpells(); + std::vector GetScribeableSpells(uint8 min_level = 1, uint8 max_level = 0); + std::vector GetScribedSpells(); void ScribeSpell(uint16 spell_id, int slot, bool update_client = true); void UnscribeSpell(int slot, bool update_client = true); void UnscribeSpellAll(bool update_client = true); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index fbe9bebdb..41d082cf3 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -634,6 +634,132 @@ int Lua_Client::MemmedCount() { return self->MemmedCount(); } +luabind::object Lua_Client::GetLearnableDisciplines(lua_State* L) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto learnable_disciplines = self->GetLearnableDisciplines(); + int index = 0; + for (auto spell_id : learnable_disciplines) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + +luabind::object Lua_Client::GetLearnableDisciplines(lua_State* L, uint8 min_level) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto learnable_disciplines = self->GetLearnableDisciplines(min_level); + int index = 0; + for (auto spell_id : learnable_disciplines) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + +luabind::object Lua_Client::GetLearnableDisciplines(lua_State* L, uint8 min_level, uint8 max_level) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto learnable_disciplines = self->GetLearnableDisciplines(min_level, max_level); + int index = 0; + for (auto spell_id : learnable_disciplines) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + +luabind::object Lua_Client::GetLearnedDisciplines(lua_State* L) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto learned_disciplines = self->GetLearnedDisciplines(); + int index = 0; + for (auto spell_id : learned_disciplines) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + +luabind::object Lua_Client::GetMemmedSpells(lua_State* L) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto memmed_spells = self->GetMemmedSpells(); + int index = 0; + for (auto spell_id : memmed_spells) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + +luabind::object Lua_Client::GetScribeableSpells(lua_State* L) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto scribeable_spells = self->GetScribeableSpells(); + int index = 0; + for (auto spell_id : scribeable_spells) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + +luabind::object Lua_Client::GetScribeableSpells(lua_State* L, uint8 min_level) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto scribeable_spells = self->GetScribeableSpells(min_level); + int index = 0; + for (auto spell_id : scribeable_spells) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + +luabind::object Lua_Client::GetScribeableSpells(lua_State* L, uint8 min_level, uint8 max_level) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto scribeable_spells = self->GetScribeableSpells(min_level, max_level); + int index = 0; + for (auto spell_id : scribeable_spells) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + +luabind::object Lua_Client::GetScribedSpells(lua_State* L) { + auto lua_table = luabind::newtable(L); + if (d_) { + auto self = reinterpret_cast(d_); + auto scribed_spells = self->GetScribedSpells(); + int index = 0; + for (auto spell_id : scribed_spells) { + lua_table[index] = spell_id; + index++; + } + } + return lua_table; +} + void Lua_Client::ScribeSpell(int spell_id, int slot) { Lua_Safe_Call_Void(); self->ScribeSpell(spell_id, slot); @@ -2050,6 +2176,15 @@ luabind::scope lua_register_client() { .def("UnmemSpellAll", (void(Lua_Client::*)(bool))&Lua_Client::UnmemSpellAll) .def("FindMemmedSpellBySlot", (uint16(Lua_Client::*)(int))&Lua_Client::FindMemmedSpellBySlot) .def("MemmedCount", (int(Lua_Client::*)(void))&Lua_Client::MemmedCount) + .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetLearnableDisciplines) + .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L,uint8))&Lua_Client::GetLearnableDisciplines) + .def("GetLearnableDisciplines", (luabind::object(Lua_Client::*)(lua_State* L,uint8,uint8))&Lua_Client::GetLearnableDisciplines) + .def("GetLearnedDisciplines", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetLearnedDisciplines) + .def("GetMemmedSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetMemmedSpells) + .def("GetScribedSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetScribedSpells) + .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetScribeableSpells) + .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L,uint8))&Lua_Client::GetScribeableSpells) + .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L,uint8,uint8))&Lua_Client::GetScribeableSpells) .def("ScribeSpell", (void(Lua_Client::*)(int,int))&Lua_Client::ScribeSpell) .def("ScribeSpell", (void(Lua_Client::*)(int,int,bool))&Lua_Client::ScribeSpell) .def("UnscribeSpell", (void(Lua_Client::*)(int))&Lua_Client::UnscribeSpell) diff --git a/zone/lua_client.h b/zone/lua_client.h index fda6f08ee..90c15c065 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -152,6 +152,15 @@ public: void UnmemSpellAll(bool update_client); uint16 FindMemmedSpellBySlot(int slot); int MemmedCount(); + luabind::object GetLearnableDisciplines(lua_State* L); + luabind::object GetLearnableDisciplines(lua_State* L, uint8 min_level); + luabind::object GetLearnableDisciplines(lua_State* L, uint8 min_level, uint8 max_level); + luabind::object GetLearnedDisciplines(lua_State* L); + luabind::object GetMemmedSpells(lua_State* L); + luabind::object GetScribedSpells(lua_State* L); + luabind::object GetScribeableSpells(lua_State* L); + luabind::object GetScribeableSpells(lua_State* L, uint8 min_level); + luabind::object GetScribeableSpells(lua_State* L, uint8 min_level, uint8 max_level); void ScribeSpell(int spell_id, int slot); void ScribeSpell(int spell_id, int slot, bool update_client); void UnscribeSpell(int slot); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 3697335d3..9ffb96982 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -7164,6 +7164,130 @@ XS(XS_Client_GetRaceBitmask) { XSRETURN(1); } +XS(XS_Client_GetLearnableDisciplines); +XS(XS_Client_GetLearnableDisciplines) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: Client::GetLearnableDisciplines(THIS, [uint8 min_level, uint8 max_level])"); + + uint8 min_level = 1; + uint8 max_level = 0; + if (items > 1) + min_level = (uint8)SvUV(ST(1)); + if (items > 2) + max_level = (uint8)SvUV(ST(2)); + + Client* THIS; + VALIDATE_THIS_IS_CLIENT; + auto learnable_disciplines = THIS->GetLearnableDisciplines(min_level, max_level); + auto learnable_size = learnable_disciplines.size(); + if (learnable_size > 0) { + EXTEND(sp, learnable_size); + for (int index = 0; index < learnable_size; ++index) { + ST(index) = sv_2mortal(newSVuv(learnable_disciplines[index])); + } + XSRETURN(learnable_size); + } + SV* return_value = &PL_sv_undef; + ST(0) = return_value; + XSRETURN(1); +} + +XS(XS_Client_GetLearnedDisciplines); +XS(XS_Client_GetLearnedDisciplines) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::GetLearnedDisciplines(THIS)"); + + Client* THIS; + VALIDATE_THIS_IS_CLIENT; + auto learned_disciplines = THIS->GetLearnedDisciplines(); + auto learned_size = learned_disciplines.size(); + if (learned_size > 0) { + EXTEND(sp, learned_size); + for (int index = 0; index < learned_size; ++index) { + ST(index) = sv_2mortal(newSVuv(learned_disciplines[index])); + } + XSRETURN(learned_size); + } + SV* return_value = &PL_sv_undef; + ST(0) = return_value; + XSRETURN(1); +} + +XS(XS_Client_GetMemmedSpells); +XS(XS_Client_GetMemmedSpells) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::GetMemmedSpells(THIS)"); + + Client* THIS; + VALIDATE_THIS_IS_CLIENT; + auto memmed_spells = THIS->GetMemmedSpells(); + auto memmed_size = memmed_spells.size(); + if (memmed_size > 0) { + EXTEND(sp, memmed_size); + for (int index = 0; index < memmed_size; ++index) { + ST(index) = sv_2mortal(newSVuv(memmed_spells[index])); + } + XSRETURN(memmed_size); + } + SV* return_value = &PL_sv_undef; + ST(0) = return_value; + XSRETURN(1); +} + +XS(XS_Client_GetScribeableSpells); +XS(XS_Client_GetScribeableSpells) { + dXSARGS; + if (items < 1 || items > 3) + Perl_croak(aTHX_ "Usage: Client::GetScribeableSpells(THIS, [uint8 min_level, uint8 max_level])"); + + uint8 min_level = 1; + uint8 max_level = 0; + if (items > 1) + min_level = (uint8)SvUV(ST(1)); + if (items > 2) + max_level = (uint8)SvUV(ST(2)); + + Client* THIS; + VALIDATE_THIS_IS_CLIENT; + auto scribeable_spells = THIS->GetScribeableSpells(min_level, max_level); + auto scribeable_size = scribeable_spells.size(); + if (scribeable_size > 0) { + EXTEND(sp, scribeable_size); + for (int index = 0; index < scribeable_size; ++index) { + ST(index) = sv_2mortal(newSVuv(scribeable_spells[index])); + } + XSRETURN(scribeable_size); + } + SV* return_value = &PL_sv_undef; + ST(0) = return_value; + XSRETURN(1); +} + +XS(XS_Client_GetScribedSpells); +XS(XS_Client_GetScribedSpells) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Client::GetScribedSpells(THIS)"); + + Client* THIS; + VALIDATE_THIS_IS_CLIENT; + auto scribed_spells = THIS->GetScribedSpells(); + auto scribed_size = scribed_spells.size(); + if (scribed_size > 0) { + EXTEND(sp, scribed_size); + for (int index = 0; index < scribed_size; ++index) { + ST(index) = sv_2mortal(newSVuv(scribed_spells[index])); + } + XSRETURN(scribed_size); + } + SV* return_value = &PL_sv_undef; + ST(0) = return_value; + XSRETURN(1); +} + #ifdef __cplusplus extern "C" #endif @@ -7291,8 +7415,11 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetLDoNPointsTheme"), XS_Client_GetLDoNPointsTheme, file, "$"); newXSproto(strcpy(buf, "GetLDoNWins"), XS_Client_GetLDoNWins, file, "$"); newXSproto(strcpy(buf, "GetLDoNWinsTheme"), XS_Client_GetLDoNWinsTheme, file, "$$"); + newXSproto(strcpy(buf, "GetLearnableDisciplines"), XS_Client_GetLearnableDisciplines, file, "$;$$"); + newXSproto(strcpy(buf, "GetLearnedDisciplines"), XS_Client_GetLearnedDisciplines, file, "$"); newXSproto(strcpy(buf, "GetLockoutExpeditionUUID"), XS_Client_GetLockoutExpeditionUUID, file, "$$$"); newXSproto(strcpy(buf, "GetMaxEndurance"), XS_Client_GetMaxEndurance, file, "$"); + newXSproto(strcpy(buf, "GetMemmedSpells"), XS_Client_GetMemmedSpells, file, "$"); newXSproto(strcpy(buf, "GetModCharacterFactionLevel"), XS_Client_GetModCharacterFactionLevel, file, "$$"); newXSproto(strcpy(buf, "GetMoney"), XS_Client_GetMoney, file, "$$$"); newXSproto(strcpy(buf, "GetPVP"), XS_Client_GetPVP, file, "$"); @@ -7303,6 +7430,8 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetRaidPoints"), XS_Client_GetRaidPoints, file, "$"); newXSproto(strcpy(buf, "GetRawItemAC"), XS_Client_GetRawItemAC, file, "$"); newXSproto(strcpy(buf, "GetRawSkill"), XS_Client_GetRawSkill, file, "$$"); + newXSproto(strcpy(buf, "GetScribeableSpells"), XS_Client_GetScribeableSpells, file, "$;$$"); + newXSproto(strcpy(buf, "GetScribedSpells"), XS_Client_GetScribedSpells, file, "$"); newXSproto(strcpy(buf, "GetSkillPoints"), XS_Client_GetSkillPoints, file, "$"); newXSproto(strcpy(buf, "GetSpellBookSlotBySpellID"), XS_Client_GetSpellBookSlotBySpellID, file, "$$"); newXSproto(strcpy(buf, "GetSpellIDByBookSlot"), XS_Client_GetSpellIDByBookSlot, file, "$$"); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 631bb1218..23fb4757a 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1088,174 +1088,47 @@ void QuestManager::permagender(int gender_id) { uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); int book_slot = initiator->GetNextAvailableSpellBookSlot(); - int spell_id = 0; - int count = 0; - - uint32 char_id = initiator->CharacterID(); - bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); - bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); - bool SpellGlobalCheckResult = false; - bool SpellBucketCheckResult = false; - - for ( ; spell_id < SPDAT_RECORDS && book_slot < EQ::spells::SPELLBOOK_SIZE; ++spell_id) { - if (book_slot == -1) { - initiator->Message( - 13, - "Unable to scribe spell %s (%i) to spellbook: no more spell book slots available.", - ((spell_id >= 0 && spell_id < SPDAT_RECORDS) ? spells[spell_id].name : "Out-of-range"), - spell_id - ); - - break; - } - if (spell_id < 0 || spell_id >= SPDAT_RECORDS) { - initiator->Message(Chat::Red, "FATAL ERROR: Spell id out-of-range (id: %i, min: 0, max: %i)", spell_id, SPDAT_RECORDS); - return count; - } - if (book_slot < 0 || book_slot >= EQ::spells::SPELLBOOK_SIZE) { - initiator->Message(Chat::Red, "FATAL ERROR: Book slot out-of-range (slot: %i, min: 0, max: %i)", book_slot, EQ::spells::SPELLBOOK_SIZE); - return count; - } - - while (true) { - if (spells[spell_id].classes[WARRIOR] == 0) // check if spell exists + std::vector spell_ids = initiator->GetScribeableSpells(min_level, max_level); + int spell_count = spell_ids.size(); + if (spell_count > 0) { + for (auto spell_id : spell_ids) { + if (book_slot == -1) { + initiator->Message( + Chat::Red, + "Unable to scribe spell %s (%i) to Spell Book: Spell Book is Full.", spells[spell_id].name, spell_id + ); break; - if (spells[spell_id].classes[initiator->GetPP().class_ - 1] > max_level) // maximum level - break; - if (spells[spell_id].classes[initiator->GetPP().class_ - 1] < min_level) // minimum level - break; - if (spells[spell_id].skill == 52) - break; - if (spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) - break; - - uint16 spell_id_ = (uint16)spell_id; - if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { - initiator->Message(Chat::Red, "FATAL ERROR: Type conversion data loss with spell_id (%i != %u)", spell_id, spell_id_); - return count; } - - if (!IsDiscipline(spell_id_) && !initiator->HasSpellScribed(spell_id)) { // isn't a discipline & we don't already have it scribed - if (SpellGlobalRule) { - // bool to see if the character has the required QGlobal to scribe it if one exists in the Spell_Globals table - SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id_, char_id); - if (SpellGlobalCheckResult) { - initiator->ScribeSpell(spell_id_, book_slot); - ++count; - } - } - else if (SpellBucketRule) { - // bool to see if the character has the required bucket to train it if one exists in the spell_buckets table - SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id_, char_id); - if (SpellBucketCheckResult) { - initiator->ScribeSpell(spell_id_, book_slot); - ++count; - } - } - else { - initiator->ScribeSpell(spell_id_, book_slot); - ++count; - } - } - - break; + initiator->ScribeSpell(spell_id, book_slot); + book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot); } - - book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot); } - - return count; // how many spells were scribed successfully + return spell_count; } uint16 QuestManager::traindiscs(uint8 max_level, uint8 min_level) { QuestManagerCurrentQuestVars(); - int spell_id = 0; - int count = 0; - - uint32 char_id = initiator->CharacterID(); - bool SpellGlobalRule = RuleB(Spells, EnableSpellGlobals); - bool SpellBucketRule = RuleB(Spells, EnableSpellBuckets); - bool SpellGlobalCheckResult = false; - bool SpellBucketCheckResult = false; - - bool change = false; - - for( ; spell_id < SPDAT_RECORDS; ++spell_id) { - if (spell_id < 0 || spell_id >= SPDAT_RECORDS) { - initiator->Message(Chat::Red, "FATAL ERROR: Spell id out-of-range (id: %i, min: 0, max: %i)", spell_id, SPDAT_RECORDS); - return count; - } - - while (true) { - if (spells[spell_id].classes[WARRIOR] == 0) // check if spell exists - break; - if (spells[spell_id].classes[initiator->GetPP().class_ - 1] > max_level) // maximum level - break; - if (spells[spell_id].classes[initiator->GetPP().class_ - 1] < min_level) // minimum level - break; - if (spells[spell_id].skill == 52) - break; - if (RuleB(Spells, UseCHAScribeHack) && spells[spell_id].effectid[EFFECT_COUNT - 1] == 10) - break; - - uint16 spell_id_ = (uint16)spell_id; - if ((spell_id_ != spell_id) || (spell_id != spell_id_)) { - initiator->Message(Chat::Red, "FATAL ERROR: Type conversion data loss with spell_id (%i != %u)", spell_id, spell_id_); - return count; - } - - if (!IsDiscipline(spell_id_)) - break; - - for (uint32 r = 0; r < MAX_PP_DISCIPLINES; r++) { - if (initiator->GetPP().disciplines.values[r] == spell_id_) { - initiator->Message(Chat::Red, "You already know this discipline."); - break; // continue the 1st loop - } - else if (initiator->GetPP().disciplines.values[r] == 0) { - if (SpellGlobalRule) { - // bool to see if the character has the required QGlobal to train it if one exists in the Spell_Globals table - SpellGlobalCheckResult = initiator->SpellGlobalCheck(spell_id_, char_id); - if (SpellGlobalCheckResult) { - initiator->GetPP().disciplines.values[r] = spell_id_; - database.SaveCharacterDisc(char_id, r, spell_id_); - change = true; - initiator->Message(Chat::White, "You have learned a new discipline!"); - ++count; // success counter - } - break; // continue the 1st loop - } - else if (SpellBucketRule) { - // bool to see if the character has the required bucket to train it if one exists in the spell_buckets table - SpellBucketCheckResult = initiator->SpellBucketCheck(spell_id_, char_id); - if (SpellBucketCheckResult) { - initiator->GetPP().disciplines.values[r] = spell_id_; - database.SaveCharacterDisc(char_id, r, spell_id_); - change = true; - initiator->Message(Chat::White, "You have learned a new discipline!"); - ++count; - } - break; - } - else { - initiator->GetPP().disciplines.values[r] = spell_id_; - database.SaveCharacterDisc(char_id, r, spell_id_); - change = true;; - initiator->Message(Chat::White, "You have learned a new discipline!"); - ++count; // success counter - break; // continue the 1st loop - } + int character_id = initiator->CharacterID(); + std::vector spell_ids = initiator->GetLearnableDisciplines(min_level, max_level); + int discipline_count = spell_ids.size(); + bool discipline_learned = false; + if (discipline_count > 0) { + for (auto spell_id : spell_ids) { + for (uint32 index = 0; index < MAX_PP_DISCIPLINES; index++) { + if (initiator->GetPP().disciplines.values[index] == 0) { + initiator->GetPP().disciplines.values[index] = spell_id; + database.SaveCharacterDisc(character_id, index, spell_id); + initiator->Message(Chat::White, "You have learned a new discipline!"); + discipline_learned = true; } } - - break; } } - if (change) + if (discipline_learned) initiator->SendDisciplineUpdate(); - return count; // how many disciplines were learned successfully + return discipline_count; } void QuestManager::unscribespells() { From a90d836bcdac761dc2754874fa113500a51c4535 Mon Sep 17 00:00:00 2001 From: Kinglykrab Date: Thu, 28 Jan 2021 20:29:50 -0500 Subject: [PATCH 194/196] Fix GetTargetRingX(), GetTargetRingY(), and GetTargetRingZ() in Perl. --- zone/perl_client.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 33184a7ae..cf695d3a8 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -7308,9 +7308,9 @@ XS(boot_Client) { newXSproto(strcpy(buf, "GetSpellIDByBookSlot"), XS_Client_GetSpellIDByBookSlot, file, "$$"); newXSproto(strcpy(buf, "GetSpentAA"), XS_Client_GetSpentAA, file, "$$"); newXSproto(strcpy(buf, "GetStartZone"), XS_Client_GetStartZone, file, "$"); - newXSproto(strcpy(buf, "GetTargetRingX"), XS_Client_GetTargetRingX, file, "$$"); - newXSproto(strcpy(buf, "GetTargetRingY"), XS_Client_GetTargetRingY, file, "$$"); - newXSproto(strcpy(buf, "GetTargetRingZ"), XS_Client_GetTargetRingZ, file, "$$"); + newXSproto(strcpy(buf, "GetTargetRingX"), XS_Client_GetTargetRingX, file, "$"); + newXSproto(strcpy(buf, "GetTargetRingY"), XS_Client_GetTargetRingY, file, "$"); + newXSproto(strcpy(buf, "GetTargetRingZ"), XS_Client_GetTargetRingZ, file, "$"); newXSproto(strcpy(buf, "GetTaskActivityDoneCount"), XS_Client_GetTaskActivityDoneCount, file, "$$$"); newXSproto(strcpy(buf, "GetThirst"), XS_Client_GetThirst, file, "$$"); newXSproto(strcpy(buf, "GetTotalSecondsPlayed"), XS_Client_GetTotalSecondsPlayed, file, "$"); From c192590af68be04e33aefe2ee4d1dac07ec06d14 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Fri, 29 Jan 2021 18:13:37 -0600 Subject: [PATCH 195/196] Fix issue where sometimes under certain circumstances when issuing a database dump with large data over a network with extended inserts we hit a buffer threshold that kills a MySQL dump with Error 2013 --- common/database/database_dump_service.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/database/database_dump_service.cpp b/common/database/database_dump_service.cpp index df083901d..7efd80f83 100644 --- a/common/database/database_dump_service.cpp +++ b/common/database/database_dump_service.cpp @@ -300,8 +300,7 @@ void DatabaseDumpService::Dump() config->DatabaseUsername ); - std::string options = "--allow-keywords --extended-insert"; - + std::string options = "--allow-keywords --extended-insert --max-allowed-packet=1G --net-buffer-length=32704"; if (IsDumpWithNoData()) { options += " --no-data"; } From 62e480fed714d003ab83b398cfb9820ea4968b2d Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sat, 30 Jan 2021 18:31:19 -0500 Subject: [PATCH 196/196] Verify expedition is not empty on invites Fixes an edge case where a member could accept a pending invite into an empty expedition before world could invalidate it --- zone/expedition.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/zone/expedition.cpp b/zone/expedition.cpp index f75b09c3f..cf9ebc0c1 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -781,11 +781,19 @@ bool Expedition::ProcessAddConflicts(Client* leader_client, Client* add_client, } } - // swapping ignores the max player count check since it's a 1:1 change - if (!swapping && ExpeditionDatabase::GetMemberCount(m_id) >= m_max_players) + // member swapping integrity is handled by invite response + if (!swapping) { - SendLeaderMessage(leader_client, Chat::Red, DZADD_EXCEED_MAX, { fmt::format_int(m_max_players).str() }); - has_conflict = true; + auto member_count = ExpeditionDatabase::GetMemberCount(m_id); + if (member_count == 0) + { + has_conflict = true; + } + else if (member_count >= m_max_players) + { + SendLeaderMessage(leader_client, Chat::Red, DZADD_EXCEED_MAX, { fmt::format_int(m_max_players).str() }); + has_conflict = true; + } } auto invite_id = add_client->GetPendingExpeditionInviteID();