From c5e4cf35c00fbe6d5ae7aff064cc74e6b2985dac Mon Sep 17 00:00:00 2001 From: Zaela Date: Sat, 22 Jun 2013 08:35:33 -0700 Subject: [PATCH] Reworked RespawnFromHover framework to allow future customization (scripting) of respawn options. Added event_respawn to be triggered when a client respawns from hover into the current zone (may not be set up correctly!). --- zone/client.cpp | 190 ++++++++++++++++++++++++++++++------ zone/client.h | 17 ++++ zone/client_process.cpp | 209 +++++++++++++++++++++++++--------------- zone/embparser.cpp | 10 +- zone/event_codes.h | 1 + 5 files changed, 316 insertions(+), 111 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index a9212bda0..277768fb4 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -319,6 +319,8 @@ Client::Client(EQStreamInterface* ieqs) MaxXTargets = 5; XTargetAutoAddHaters = true; LoadAccountFlags(); + + initial_respawn_selection = 0; } Client::~Client() { @@ -415,6 +417,8 @@ Client::~Client() { safe_delete_array(adv_requested_data); safe_delete_array(adv_data); + ClearRespawnOptions(); + numclients--; UpdateWindowTitle(); if(zone) @@ -4310,45 +4314,68 @@ void Client::SendRespawnBinds() // Client will respond with a 4 byte packet that includes the number of the selection made // + //If no options have been given, default to Bind + Rez + if (respawn_options.empty()) + { + BindStruct* b = &m_pp.binds[0]; + RespawnOption opt; + opt.name = "Bind Location"; + opt.zoneid = b->zoneId; + opt.x = b->x; + opt.y = b->y; + opt.z = b->z; + opt.heading = b->heading; + respawn_options.push_front(opt); + } + //Rez is always added at the end + RespawnOption rez; + rez.name = "Resurrect"; + rez.zoneid = zone->GetZoneID(); + rez.x = GetX(); + rez.y = GetY(); + rez.z = GetZ(); + rez.heading = GetHeading(); + respawn_options.push_back(rez); - const char* BindName = "Bind Location"; - const char* Resurrect = "Resurrect"; + int num_options = respawn_options.size(); + uint32 PacketLength = 17 + (26 * num_options); //Header size + per-option invariant size + + std::list::iterator itr; + RespawnOption* opt; - int PacketLength; + //Find string size for each option + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + PacketLength += opt->name.size() + 1; //+1 for cstring + } - PacketLength = 17 + (26 * 2) + strlen(BindName) + strlen(Resurrect); // SoF + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RespawnWindow, PacketLength); + char* buffer = (char*)outapp->pBuffer; - EQApplicationPacket *outapp = new EQApplicationPacket(OP_RespawnWindow, PacketLength); + //Packet header + VARSTRUCT_ENCODE_TYPE(uint32, buffer, initial_respawn_selection); //initial selection (from 0) + VARSTRUCT_ENCODE_TYPE(uint32, buffer, RuleI(Character, RespawnFromHoverTimer) * 1000); + VARSTRUCT_ENCODE_TYPE(uint32, buffer, 0); //unknown + VARSTRUCT_ENCODE_TYPE(uint32, buffer, num_options); //number of options to display - char *Buffer = (char *)outapp->pBuffer; - - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Unknown - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, RuleI(Character, RespawnFromHoverTimer) * 1000); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Unknown - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 2); // Two options, Bind or Rez - - - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Entry 0 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, m_pp.binds[0].zoneId); - VARSTRUCT_ENCODE_TYPE(float, Buffer, m_pp.binds[0].x); - VARSTRUCT_ENCODE_TYPE(float, Buffer, m_pp.binds[0].y); - VARSTRUCT_ENCODE_TYPE(float, Buffer, m_pp.binds[0].z); - VARSTRUCT_ENCODE_TYPE(float, Buffer, m_pp.binds[0].heading); - VARSTRUCT_ENCODE_STRING(Buffer, BindName); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); - - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); // Entry 1 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, zone->GetZoneID()); - VARSTRUCT_ENCODE_TYPE(float, Buffer, GetX()); - VARSTRUCT_ENCODE_TYPE(float, Buffer, GetY()); - VARSTRUCT_ENCODE_TYPE(float, Buffer, GetZ()); - VARSTRUCT_ENCODE_TYPE(float, Buffer, GetHeading()); - VARSTRUCT_ENCODE_STRING(Buffer, Resurrect); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 1); + //Individual options + int count = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + VARSTRUCT_ENCODE_TYPE(uint32, buffer, count++); //option num (from 0) + VARSTRUCT_ENCODE_TYPE(uint32, buffer, opt->zoneid); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->x); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->y); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->z); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->heading); + VARSTRUCT_ENCODE_STRING(buffer, opt->name.c_str()); + VARSTRUCT_ENCODE_TYPE(uint8, buffer, (count == num_options)); //is this one Rez (the last option)? + } QueuePacket(outapp); safe_delete(outapp); - return; } @@ -7734,3 +7761,104 @@ void Client::TryItemTick(int slot) } } } + +void Client::AddRespawnOption(std::string option_name, uint32 zoneid, float x, float y, float z, float heading, bool initial_selection, int8 position) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn()) { return; } + + if (zoneid == 0) + zoneid = zone->GetZoneID(); + + //Create respawn option + RespawnOption res_opt; + res_opt.name = option_name; + res_opt.zoneid = zoneid; + res_opt.x = x; + res_opt.y = y; + res_opt.z = z; + res_opt.heading = heading; + + if (position == -1 || position >= respawn_options.size()) + { + //No position specified, or specified beyond the end, simply append + respawn_options.push_back(res_opt); + //Make this option the initial selection for the window if desired + if (initial_selection) + initial_respawn_selection = static_cast(respawn_options.size()) - 1; + } + else if (position == 0) + { + respawn_options.push_front(res_opt); + if (initial_selection) + initial_respawn_selection = 0; + } + else + { + //Insert new option between existing options + std::list::iterator itr; + uint8 pos = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + if (pos++ == position) + { + respawn_options.insert(itr,res_opt); + //Make this option the initial selection for the window if desired + if (initial_selection) + initial_respawn_selection = pos; + return; + } + } + } +} + +bool Client::RemoveRespawnOption(std::string option_name) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn() || respawn_options.empty()) { return false; } + + bool had = false; + RespawnOption* opt; + std::list::iterator itr; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + if (opt->name.compare(option_name) == 0) + { + respawn_options.erase(itr); + had = true; + //could be more with the same name, so keep going... + } + } + return had; +} + +bool Client::RemoveRespawnOption(uint8 position) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn() || respawn_options.empty()) { return false; } + + //Easy cases first... + if (position == 0) + { + respawn_options.pop_front(); + return true; + } + else if (position == (respawn_options.size() - 1)) + { + respawn_options.pop_back(); + return true; + } + + std::list::iterator itr; + uint8 pos = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + if (pos++ == position) + { + respawn_options.erase(itr); + return true; + } + } + return false; +} \ No newline at end of file diff --git a/zone/client.h b/zone/client.h index c4b338ed7..924580a70 100644 --- a/zone/client.h +++ b/zone/client.h @@ -179,6 +179,16 @@ struct XTarget_Struct char Name[65]; }; +struct RespawnOption +{ + std::string name; + uint32 zoneid; + float x; + float y; + float z; + float heading; +}; + const uint32 POPUPID_UPDATE_SHOWSTATSWINDOW = 1000000; @@ -1043,6 +1053,11 @@ public: bool MoveItemToInventory(ItemInst *BInst, bool UpdateClient = false); void HandleRespawnFromHover(uint32 Option); bool IsHoveringForRespawn() { return RespawnFromHoverTimer.Enabled(); } + std::list respawn_options; + void AddRespawnOption(std::string option_name, uint32 zoneid, float x, float y, float z, float h = 0, bool initial_selection = false, int8 position = -1); + bool RemoveRespawnOption(std::string option_name); + bool RemoveRespawnOption(uint8 position); + void ClearRespawnOptions() { respawn_options.clear(); } void SetPendingRezzData(int XP, uint32 DBID, uint16 SpellID, const char *CorpseName) { PendingRezzXP = XP; PendingRezzDBID = DBID; PendingRezzSpellID = SpellID; PendingRezzCorpseName = CorpseName; } bool IsRezzPending() { return PendingRezzSpellID > 0; } void ClearHover(); @@ -1449,6 +1464,8 @@ private: Timer ItemTickTimer; std::map accountflags; + + uint8 initial_respawn_selection; }; #include "parser.h" diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 31c93aa1c..290413565 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -2040,87 +2040,139 @@ void Client::HandleRespawnFromHover(uint32 Option) { RespawnFromHoverTimer.Disable(); - if(Option == 1) // Resurrect + RespawnOption* chosen = nullptr; + bool is_rez = false; + + //Find the selected option + if (Option == 0) { - if((PendingRezzXP < 0) || (PendingRezzSpellID == 0)) - { - _log(SPELLS__REZ, "Unexpected Rezz from hover request."); - return; - } - SetHP(GetMaxHP() / 5); - - Corpse* corpse = entity_list.GetCorpseByName(PendingRezzCorpseName.c_str()); - - if(corpse) - { - x_pos = corpse->GetX(); - y_pos = corpse->GetY(); - z_pos = corpse->GetZ(); - } - - EQApplicationPacket* outapp = new EQApplicationPacket(OP_ZonePlayerToBind, sizeof(ZonePlayerToBind_Struct) + 10); - ZonePlayerToBind_Struct* gmg = (ZonePlayerToBind_Struct*) outapp->pBuffer; - - gmg->bind_zone_id = zone->GetZoneID(); - gmg->bind_instance_id = zone->GetInstanceID(); - gmg->x = GetX(); - gmg->y = GetY(); - gmg->z = GetZ(); - gmg->heading = GetHeading(); - strcpy(gmg->zone_name, "Resurrect"); - - FastQueuePacket(&outapp); - - ClearHover(); - SendHPUpdate(); - OPRezzAnswer(1, PendingRezzSpellID, zone->GetZoneID(), zone->GetInstanceID(), GetX(), GetY(), GetZ()); - - if (corpse && corpse->IsCorpse()) { - _log(SPELLS__REZ, "Hover Rez in zone %s for corpse %s", - zone->GetShortName(), PendingRezzCorpseName.c_str()); - - _log(SPELLS__REZ, "Found corpse. Marking corpse as rezzed."); - - corpse->Rezzed(true); - corpse->CompleteRezz(); - } - return; + chosen = &respawn_options.front(); } - - // Respawn at Bind Point. - // - if(m_pp.binds[0].zoneId == zone->GetZoneID()) + else if (Option == (respawn_options.size() - 1)) { - PendingRezzSpellID = 0; - - EQApplicationPacket* outapp = new EQApplicationPacket(OP_ZonePlayerToBind, sizeof(ZonePlayerToBind_Struct) + 14); - ZonePlayerToBind_Struct* gmg = (ZonePlayerToBind_Struct*) outapp->pBuffer; - - gmg->bind_zone_id = m_pp.binds[0].zoneId; - gmg->x = m_pp.binds[0].x; - gmg->y = m_pp.binds[0].y; - gmg->z = m_pp.binds[0].z; - gmg->heading = 0; - strcpy(gmg->zone_name, "Bind Location"); - - FastQueuePacket(&outapp); - - CalcBonuses(); - SetHP(GetMaxHP()); - SetMana(GetMaxMana()); - SetEndurance(GetMaxEndurance()); - - x_pos = m_pp.binds[0].x; - y_pos = m_pp.binds[0].y; - z_pos = m_pp.binds[0].z; - - ClearHover(); - entity_list.RefreshClientXTargets(this); - SendHPUpdate(); - + chosen = &respawn_options.back(); + is_rez = true; //Rez must always be the last option } else { + std::list::iterator itr; + uint32 pos = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + if (pos++ == Option) + { + chosen = &(*itr); + break; + } + } + } + + //If they somehow chose an option they don't have, just send them to bind + RespawnOption default_to_bind; //must keep in scope! + if (!chosen) + { + /* put error logging here */ + BindStruct* b = &m_pp.binds[0]; + default_to_bind.name = "Bind Location"; + default_to_bind.zoneid = b->zoneId; + default_to_bind.x = b->x; + default_to_bind.y = b->y; + default_to_bind.z = b->z; + default_to_bind.heading = b->heading; + chosen = &default_to_bind; + is_rez = false; + } + + if (chosen->zoneid == zone->GetZoneID()) //If they should respawn in the current zone... + { + if (is_rez) + { + if (PendingRezzXP < 0 || PendingRezzSpellID == 0) + { + _log(SPELLS__REZ, "Unexpected Rezz from hover request."); + return; + } + SetHP(GetMaxHP() / 5); + + Corpse* corpse = entity_list.GetCorpseByName(PendingRezzCorpseName.c_str()); + + if (corpse) + { + x_pos = corpse->GetX(); + y_pos = corpse->GetY(); + z_pos = corpse->GetZ(); + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ZonePlayerToBind, sizeof(ZonePlayerToBind_Struct) + 10); + ZonePlayerToBind_Struct* gmg = (ZonePlayerToBind_Struct*) outapp->pBuffer; + + gmg->bind_zone_id = zone->GetZoneID(); + gmg->bind_instance_id = zone->GetInstanceID(); + gmg->x = GetX(); + gmg->y = GetY(); + gmg->z = GetZ(); + gmg->heading = GetHeading(); + strcpy(gmg->zone_name, "Resurrect"); + + FastQueuePacket(&outapp); + + ClearHover(); + SendHPUpdate(); + OPRezzAnswer(1, PendingRezzSpellID, zone->GetZoneID(), zone->GetInstanceID(), GetX(), GetY(), GetZ()); + + if (corpse && corpse->IsCorpse()) + { + _log(SPELLS__REZ, "Hover Rez in zone %s for corpse %s", + zone->GetShortName(), PendingRezzCorpseName.c_str()); + + _log(SPELLS__REZ, "Found corpse. Marking corpse as rezzed."); + + corpse->Rezzed(true); + corpse->CompleteRezz(); + } + } + else //Not rez + { + PendingRezzSpellID = 0; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ZonePlayerToBind, sizeof(ZonePlayerToBind_Struct) + chosen->name.length() + 1); + ZonePlayerToBind_Struct* gmg = (ZonePlayerToBind_Struct*) outapp->pBuffer; + + gmg->bind_zone_id = zone->GetZoneID(); + gmg->x = chosen->x; + gmg->y = chosen->y; + gmg->z = chosen->z; + gmg->heading = chosen->heading; + strcpy(gmg->zone_name, chosen->name.c_str()); + + FastQueuePacket(&outapp); + + CalcBonuses(); + SetHP(GetMaxHP()); + SetMana(GetMaxMana()); + SetEndurance(GetMaxEndurance()); + + x_pos = chosen->x; + y_pos = chosen->y; + z_pos = chosen->z; + heading = chosen->heading; + + ClearHover(); + entity_list.RefreshClientXTargets(this); + SendHPUpdate(); + } + + //After they've respawned into the same zone, trigger EVENT_RESPAWN + parse->EventPlayer(EVENT_RESPAWN, this, static_cast(itoa(Option)), is_rez ? 1 : 0); + + //Pop Rez option from the respawn options list; + //easiest way to make sure it stays at the end and + //doesn't disrupt adding/removing scripted options + respawn_options.pop_back(); + } + else + { + //Heading to a different zone if(isgrouped) { Group *g = GetGroup(); @@ -2129,17 +2181,16 @@ void Client::HandleRespawnFromHover(uint32 Option) } Raid* r = entity_list.GetRaidByClient(this); - if(r) r->MemberZoned(this); - m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zone_id = chosen->zoneid; m_pp.zoneInstance = 0; - database.MoveCharacterToZone(this->CharacterID(), database.GetZoneName(m_pp.zone_id)); + database.MoveCharacterToZone(this->CharacterID(), database.GetZoneName(chosen->zoneid)); Save(); - GoToDeath(); + MovePC(chosen->zoneid,chosen->x,chosen->y,chosen->z,chosen->heading,1); } } diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 535ce880b..c4158d308 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -97,7 +97,8 @@ const char *QuestEventSubroutines[_LargestEventID] = { "EVENT_CONNECT", "EVENT_ITEM_TICK", "EVENT_DUEL_WIN", - "EVENT_DUEL_LOSE" + "EVENT_DUEL_LOSE", + "EVENT_RESPAWN" }; extern Zone* zone; @@ -818,6 +819,13 @@ void PerlembParser::EventCommon(QuestEventID event, uint32 objid, const char * d break; } + case EVENT_RESPAWN: + { + ExportVar(packagename.c_str(), "respawn_option", data); + ExportVar(packagename.c_str(), "is_rez", extradata); + break; + } + //nothing special about these events case EVENT_DEATH: case EVENT_SPAWN: diff --git a/zone/event_codes.h b/zone/event_codes.h index 95bc77368..6ef0e96eb 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -61,6 +61,7 @@ typedef enum { EVENT_ITEM_TICK, EVENT_DUEL_WIN, EVENT_DUEL_LOSE, + EVENT_RESPAWN, //PC respawning from hover without changing zones _LargestEventID } QuestEventID;