From 3804d7f603c40c6a22d62ad7f9275f1d045fab9f Mon Sep 17 00:00:00 2001 From: Aleos Date: Wed, 17 Jun 2020 14:52:22 -0400 Subject: [PATCH] Adds support for instance destruction button (#5073) * Adds support for instance destruction button * Adds an extra parameter in the instance database to toggle if an instance is destroy-able or not. * Adds support for being notified about character and Clan instances on login. * Fixes an issue with the instance window displaying wrong instance information on an instance map when multiples instances were running for the character. Co-authored-by: atemo --- db/import-tmpl/instance_db.yml | 2 + db/instance_db.yml | 2 + db/pre-re/instance_db.yml | 2 + db/re/instance_db.yml | 2 + doc/yaml/db/instance_db.yml | 2 + src/common/mmo.hpp | 2 +- src/map/clan.cpp | 4 ++ src/map/clif.cpp | 25 ++++++++ src/map/clif.hpp | 5 ++ src/map/clif_packetdb.hpp | 2 +- src/map/guild.cpp | 6 +- src/map/instance.cpp | 106 +++++++++++++++++++++++++++++++-- src/map/instance.hpp | 3 +- src/map/npc.hpp | 3 +- src/map/party.cpp | 12 ++-- src/map/party.hpp | 2 +- src/map/pc.cpp | 34 ++++++----- src/map/pc.hpp | 5 +- 18 files changed, 185 insertions(+), 34 deletions(-) diff --git a/db/import-tmpl/instance_db.yml b/db/import-tmpl/instance_db.yml index 16dd9900e3b..ca8b1ed7f5c 100644 --- a/db/import-tmpl/instance_db.yml +++ b/db/import-tmpl/instance_db.yml @@ -26,6 +26,8 @@ # Name Instance Name. # TimeLimit Total lifetime of instance in seconds. (Default: 3600) # IdleTimeOut Time before an idle instance is destroyed in seconds. (Default: 300) +# Destroyable Toggles the ability to destroy the instance using instance 'Destroy' button. (Default: true) +# Note: the button is displayed based on parties. For any mode, it requires the party leader to be the instance owner to destroy it. # Enter: Instance entrance coordinates. # Map Map Name where players start. # X X Coordinate where players start. diff --git a/db/instance_db.yml b/db/instance_db.yml index 03bcfba90c2..89913a4cbd6 100644 --- a/db/instance_db.yml +++ b/db/instance_db.yml @@ -26,6 +26,8 @@ # Name Instance Name. # TimeLimit Total lifetime of instance in seconds. (Default: 3600) # IdleTimeOut Time before an idle instance is destroyed in seconds. (Default: 300) +# Destroyable Toggles the ability to destroy the instance using instance 'Destroy' button. (Default: true) +# Note: the button is displayed based on parties. For any mode, it requires the party leader to be the instance owner to destroy it. # Enter: Instance entrance coordinates. # Map Map Name where players start. # X X Coordinate where players start. diff --git a/db/pre-re/instance_db.yml b/db/pre-re/instance_db.yml index fdd82c99ccb..8f4bd85c516 100644 --- a/db/pre-re/instance_db.yml +++ b/db/pre-re/instance_db.yml @@ -26,6 +26,8 @@ # Name Instance Name. # TimeLimit Total lifetime of instance in seconds. (Default: 3600) # IdleTimeOut Time before an idle instance is destroyed in seconds. (Default: 300) +# Destroyable Toggles the ability to destroy the instance using instance 'Destroy' button. (Default: true) +# Note: the button is displayed based on parties. For any mode, it requires the party leader to be the instance owner to destroy it. # Enter: Instance entrance coordinates. # Map Map Name where players start. # X X Coordinate where players start. diff --git a/db/re/instance_db.yml b/db/re/instance_db.yml index 1bc5d59ef43..30e0b6a2b35 100644 --- a/db/re/instance_db.yml +++ b/db/re/instance_db.yml @@ -26,6 +26,8 @@ # Name Instance Name. # TimeLimit Total lifetime of instance in seconds. (Default: 3600) # IdleTimeOut Time before an idle instance is destroyed in seconds. (Default: 300) +# Destroyable Toggles the ability to destroy the instance using instance 'Destroy' button. (Default: true) +# Note: the button is displayed based on parties. For any mode, it requires the party leader to be the instance owner to destroy it. # Enter: Instance entrance coordinates. # Map Map Name where players start. # X X Coordinate where players start. diff --git a/doc/yaml/db/instance_db.yml b/doc/yaml/db/instance_db.yml index b7c1cf8ffea..f708f78280a 100644 --- a/doc/yaml/db/instance_db.yml +++ b/doc/yaml/db/instance_db.yml @@ -9,6 +9,8 @@ # Name Instance Name. # TimeLimit Total lifetime of instance in seconds. (Default: 3600) # IdleTimeOut Time before an idle instance is destroyed in seconds. (Default: 300) +# Destroyable Toggles the ability to destroy the instance using instance 'Destroy' button. (Default: true) +# Note: the button is displayed based on parties. For any mode, it requires the party leader to be the instance owner to destroy it. # Enter: Instance entrance coordinates. # Map Map Name where players start. # X X Coordinate where players start. diff --git a/src/common/mmo.hpp b/src/common/mmo.hpp index 2424211ff66..0d071186bb8 100644 --- a/src/common/mmo.hpp +++ b/src/common/mmo.hpp @@ -680,7 +680,7 @@ struct guild { struct guild_expulsion expulsion[MAX_GUILDEXPULSION]; struct guild_skill skill[MAX_GUILDSKILL]; struct Channel *channel; - unsigned short instance_id; + int instance_id; time_t last_leader_change; /* Used by char-server to save events for guilds */ diff --git a/src/map/clan.cpp b/src/map/clan.cpp index ec05704ff0d..44e756631da 100644 --- a/src/map/clan.cpp +++ b/src/map/clan.cpp @@ -12,6 +12,7 @@ #include "../common/showmsg.hpp" #include "clif.hpp" +#include "instance.hpp" #include "intif.hpp" #include "log.hpp" #include "pc.hpp" @@ -128,6 +129,9 @@ void clan_member_joined( struct map_session_data* sd ){ intif_clan_member_joined(clan->id); clif_clan_onlinecount(clan); + + if (clan->instance_id > 0) + instance_reqinfo(sd, clan->instance_id); } } diff --git a/src/map/clif.cpp b/src/map/clif.cpp index 6f04192ef3a..c521e1a881e 100644 --- a/src/map/clif.cpp +++ b/src/map/clif.cpp @@ -17968,6 +17968,22 @@ void clif_instance_changestatus(int instance_id, e_instance_notify type, unsigne return; } +/// Destroy an instance from the status window +/// 02cf .L (CZ_MEMORIALDUNGEON_COMMAND) +void clif_parse_MemorialDungeonCommand(int fd, map_session_data *sd) +{ + if (pc_istrading(sd) || pc_isdead(sd) || map_getmapdata(sd->bl.m)->instance_id > 0) + return; + + const PACKET_CZ_MEMORIALDUNGEON_COMMAND *p = (PACKET_CZ_MEMORIALDUNGEON_COMMAND *)RFIFOP(fd, 0); + + switch (p->command) { + case COMMAND_MEMORIALDUNGEON_DESTROY_FORCE: + instance_destroy_command(sd); + break; + } +} + /// Notifies clients about item picked up by a party member. /// 02b8 .L .W .B .B .B .W .W .W .W .W .B (ZC_ITEM_PICKUP_PARTY) void clif_party_show_picker( struct map_session_data* sd, struct item* item_data ){ @@ -19047,6 +19063,15 @@ static void clif_loadConfirm( struct map_session_data *sd ){ p.packetType = HEADER_ZC_LOAD_CONFIRM; clif_send( &p, sizeof(p), &sd->bl, SELF ); + + if (sd->instance_id > 0) + instance_reqinfo(sd, sd->instance_id); + if (sd->status.party_id > 0) + party_member_joined(sd); + if (sd->status.guild_id > 0) + guild_member_joined(sd); + if (sd->status.clan_id > 0) + clan_member_joined(sd); #endif } diff --git a/src/map/clif.hpp b/src/map/clif.hpp index 4a74a2ee617..899699ec531 100644 --- a/src/map/clif.hpp +++ b/src/map/clif.hpp @@ -574,6 +574,10 @@ enum e_config_type : uint32 { CONFIG_HOMUNCULUS_AUTOFEED }; +enum e_memorial_dungeon_command : uint16 { + COMMAND_MEMORIALDUNGEON_DESTROY_FORCE = 0x3, +}; + int clif_setip(const char* ip); void clif_setbindip(const char* ip); void clif_setport(uint16 port); @@ -843,6 +847,7 @@ void clif_instance_create(int instance_id, int num); void clif_instance_changewait(int instance_id, int num); void clif_instance_status(int instance_id, unsigned int limit1, unsigned int limit2); void clif_instance_changestatus(int instance_id, e_instance_notify type, unsigned int limit); +void clif_parse_MemorialDungeonCommand(int fd, map_session_data *sd); // Custom Fonts void clif_font(struct map_session_data *sd); diff --git a/src/map/clif_packetdb.hpp b/src/map/clif_packetdb.hpp index 26511bfeebb..cb8b44bcdcc 100644 --- a/src/map/clif_packetdb.hpp +++ b/src/map/clif_packetdb.hpp @@ -1109,7 +1109,7 @@ packet(0x02cc,4); packet(0x02cd,26); packet(0x02ce,10); - packet(0x02cf,6); + parseable_packet(0x02cf,6,clif_parse_MemorialDungeonCommand,2); packet(0x02d0,-1); packet(0x02d1,-1); packet(0x02d2,-1); diff --git a/src/map/guild.cpp b/src/map/guild.cpp index 13092c477b8..28d5a2c7473 100644 --- a/src/map/guild.cpp +++ b/src/map/guild.cpp @@ -673,7 +673,7 @@ int guild_recv_info(struct guild *sg) { clif_guild_notice(sd); sd->guild_emblem_id = g->emblem_id; } - if (g->instance_id != 0) + if (g->instance_id > 0) instance_reqinfo(sd, g->instance_id); } @@ -818,7 +818,7 @@ void guild_member_joined(struct map_session_data *sd) { g->member[i].sd = sd; sd->guild = g; - if (g->instance_id != 0) + if (g->instance_id > 0) instance_reqinfo(sd, g->instance_id); if( channel_config.ally_tmpl.name[0] && (channel_config.ally_tmpl.opt&CHAN_OPT_AUTOJOIN) ) { channel_gjoin(sd,3); @@ -870,7 +870,7 @@ int guild_member_added(int guild_id,uint32 account_id,uint32 char_id,int flag) { //Next line commented because it do nothing, look at guild_recv_info [LuzZza] //clif_charnameupdate(sd); //Update display name [Skotlex] - if (g->instance_id != 0) + if (g->instance_id > 0) instance_reqinfo(sd, g->instance_id); return 0; diff --git a/src/map/instance.cpp b/src/map/instance.cpp index 3a9004d6bd1..5122faa1d39 100644 --- a/src/map/instance.cpp +++ b/src/map/instance.cpp @@ -111,7 +111,6 @@ uint64 InstanceDatabase::parseBodyNode(const YAML::Node &node) { instance->timeout = 300; } - /* if (this->nodeExists(node, "Destroyable")) { bool destroy; @@ -123,7 +122,6 @@ uint64 InstanceDatabase::parseBodyNode(const YAML::Node &node) { if (!exists) instance->destroyable = true; } - */ if (this->nodeExists(node, "Enter")) { const YAML::Node &enterNode = node["Enter"]; @@ -539,7 +537,7 @@ int instance_create(int owner_id, const char *name, e_instance_mode mode) { return -1; } - struct map_session_data *sd; + struct map_session_data *sd = nullptr; struct party_data *pd; struct guild *gd; struct clan* cd; @@ -603,15 +601,24 @@ int instance_create(int owner_id, const char *name, e_instance_mode mode) { break; case IM_PARTY: pd->instance_id = instance_id; + int32 i; + ARR_FIND(0, MAX_PARTY, i, pd->party.member[i].leader); + + if (i < MAX_PARTY) + sd = map_charid2sd(pd->party.member[i].char_id); break; case IM_GUILD: gd->instance_id = instance_id; + sd = map_charid2sd(gd->member[0].char_id); break; case IM_CLAN: cd->instance_id = instance_id; break; } + if (sd != nullptr) + sd->instance_mode = mode; + instance_wait.id.push_back(instance_id); clif_instance_create(instance_id, instance_wait.id.size()); instance_subscription_timer(0,0,0,0); @@ -742,6 +749,89 @@ int16 instance_mapid(int16 m, int instance_id) return m; } +/** + * Removes an instance, all its maps, and NPCs invoked by the client button. + * @param sd: Player data + */ +void instance_destroy_command(map_session_data *sd) { + nullpo_retv(sd); + + std::shared_ptr idata; + int instance_id = 0; + + if (sd->instance_mode == IM_CHAR && sd->instance_id > 0) { + idata = util::umap_find(instances, sd->instance_id); + + if (idata == nullptr) + return; + + instance_id = sd->instance_id; + } else if (sd->instance_mode == IM_PARTY && sd->status.party_id > 0) { + party_data *pd = party_search(sd->status.party_id); + + if (pd == nullptr) + return; + + idata = util::umap_find(instances, pd->instance_id); + + if (idata == nullptr) + return; + + int32 i; + + ARR_FIND(0, MAX_PARTY, i, pd->data[i].sd == sd && pd->party.member[i].leader); + + if (i == MAX_PARTY) // Player is not party leader + return; + + instance_id = pd->instance_id; + } else if (sd->instance_mode == IM_GUILD && sd->guild != nullptr && sd->guild->instance_id > 0) { + guild *gd = guild_search(sd->status.guild_id); + + if (gd == nullptr) + return; + + idata = util::umap_find(instances, gd->instance_id); + + if (idata == nullptr) + return; + + if (strcmp(sd->status.name, gd->master) != 0) // Player is not guild master + return; + + instance_id = gd->instance_id; + } + + if (instance_id == 0) // Checks above failed + return; + + if (!instance_db.find(idata->id)->destroyable) // Instance is flagged as non-destroyable + return; + + instance_destroy(instance_id); + + // Check for any other active instances and display their info + if (sd->instance_id > 0) + instance_reqinfo(sd, sd->instance_id); + if (sd->status.party_id > 0) { + party_data *pd = party_search(sd->status.party_id); + + if (pd == nullptr) + return; + + if (pd->instance_id > 0) + instance_reqinfo(sd, pd->instance_id); + } + if (sd->guild != nullptr && sd->guild->instance_id > 0) { + guild *gd = guild_search(sd->status.guild_id); + + if (gd == nullptr) + return; + + instance_reqinfo(sd, gd->instance_id); + } +} + /** * Removes an instance, all its maps, and NPCs. * @param instance_id: Instance to remove @@ -972,11 +1062,17 @@ bool instance_reqinfo(struct map_session_data *sd, int instance_id) for (int i = 0; i < instance_wait.id.size(); i++) { if (instance_wait.id[i] == instance_id) { clif_instance_create(instance_id, i + 1); + sd->instance_mode = idata->mode; break; } } - } else if(idata->state == INSTANCE_BUSY) // Give info on the instance if busy - clif_instance_status(instance_id, idata->keep_limit, idata->idle_limit); + } else if (idata->state == INSTANCE_BUSY) { // Give info on the instance if busy + int map_instance_id = map_getmapdata(sd->bl.m)->instance_id; + if (map_instance_id == 0 || map_instance_id == instance_id) { + clif_instance_status(instance_id, idata->keep_limit, idata->idle_limit); + sd->instance_mode = idata->mode; + } + } return true; } diff --git a/src/map/instance.hpp b/src/map/instance.hpp index 54edbe2da99..0902249e2d4 100644 --- a/src/map/instance.hpp +++ b/src/map/instance.hpp @@ -89,7 +89,7 @@ struct s_instance_db { std::string name; ///< Instance name uint32 limit, ///< Duration limit timeout; ///< Timeout limit - //bool destroyable; ///< Destroyable flag + bool destroyable; ///< Destroyable flag struct point enter; ///< Instance entry point std::vector maplist; ///< Maps in instance }; @@ -113,6 +113,7 @@ void instance_getsd(int instance_id, struct map_session_data *&sd, enum send_tar int instance_create(int owner_id, const char *name, e_instance_mode mode); bool instance_destroy(int instance_id); +void instance_destroy_command(map_session_data *sd); e_instance_enter instance_enter(struct map_session_data *sd, int instance_id, const char *name, short x, short y); bool instance_reqinfo(struct map_session_data *sd, int instance_id); bool instance_addusers(int instance_id); diff --git a/src/map/npc.hpp b/src/map/npc.hpp index 740b221a39d..42ca453d099 100644 --- a/src/map/npc.hpp +++ b/src/map/npc.hpp @@ -57,11 +57,12 @@ struct npc_data { struct view_data vd; struct status_change sc; //They can't have status changes, but.. they want the visual opt values. struct npc_data *master_nd; - short class_,speed,instance_id; + short class_,speed; char name[NPC_NAME_LENGTH+1];// display name char exname[NPC_NAME_LENGTH+1];// unique npc name int chat_id,touching_id; unsigned int next_walktime; + int instance_id; unsigned size : 2; diff --git a/src/map/party.cpp b/src/map/party.cpp index 4ddbda4bcc7..30afb9c8ca7 100644 --- a/src/map/party.cpp +++ b/src/map/party.cpp @@ -359,8 +359,8 @@ int party_recv_info(struct party* sp, uint32 char_id) } clif_party_info(p,NULL); - if( p->instance_id != 0 ) - instance_reqinfo(sd,p->instance_id); + if (p->instance_id > 0) + instance_reqinfo(sd, p->instance_id); } // If a player was renamed, make sure to resend the party information @@ -504,8 +504,8 @@ void party_member_joined(struct map_session_data *sd) if (i < MAX_PARTY) { p->data[i].sd = sd; - if( p->instance_id ) - instance_reqinfo(sd,p->instance_id); + if (p->instance_id > 0) + instance_reqinfo(sd, p->instance_id); } else sd->status.party_id = 0; //He does not belongs to the party really? } @@ -562,8 +562,8 @@ int party_member_added(int party_id,uint32 account_id,uint32 char_id, int flag) clif_party_xy(sd); clif_name_area(&sd->bl); //Update char name's display [Skotlex] - if( p->instance_id ) - instance_reqinfo(sd,p->instance_id); + if (p->instance_id > 0) + instance_reqinfo(sd, p->instance_id); return 0; } diff --git a/src/map/party.hpp b/src/map/party.hpp index a6465285d5f..2a2cf699360 100644 --- a/src/map/party.hpp +++ b/src/map/party.hpp @@ -26,7 +26,7 @@ struct party_data { struct party party; struct party_member_data data[MAX_PARTY]; uint8 itemc; //For item distribution, position of last picker in party - unsigned short instance_id; + int instance_id; struct { unsigned monk : 1; //There's at least one monk in party? unsigned sg : 1; //There's at least one Star Gladiator in party? diff --git a/src/map/pc.cpp b/src/map/pc.cpp index 0bc2285d615..d4705a2dbf6 100755 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -1843,12 +1843,24 @@ void pc_reg_received(struct map_session_data *sd) intif_storage_request(sd,TABLE_CART, 0, STOR_MODE_ALL); // Request cart data intif_storage_request(sd,TABLE_INVENTORY, 0, STOR_MODE_ALL); // Request inventory data - if (sd->status.party_id) + // Restore IM_CHAR instance to the player + for (const auto &instance : instances) { + if (instance.second->mode == IM_CHAR && instance.second->owner_id == sd->status.char_id) { + sd->instance_id = instance.first; + break; + } + } + +#if PACKETVER_MAIN_NUM < 20190403 || PACKETVER_RE_NUM < 20190320 || PACKETVER_ZERO_NUM < 20190410 + if (sd->instance_id > 0) + instance_reqinfo(sd, sd->instance_id); + if (sd->status.party_id > 0) party_member_joined(sd); - if (sd->status.guild_id) + if (sd->status.guild_id > 0) guild_member_joined(sd); - if( sd->status.clan_id ) + if (sd->status.clan_id > 0) clan_member_joined(sd); +#endif // pet if (sd->status.pet_id > 0) @@ -1908,14 +1920,6 @@ void pc_reg_received(struct map_session_data *sd) } channel_autojoin(sd); - - // Restore IM_CHAR instance to the player - for (const auto &instance : instances) { - if (instance.second->mode == IM_CHAR && instance.second->owner_id == sd->status.char_id) { - sd->instance_id = instance.first; - break; - } - } } static int pc_calc_skillpoint(struct map_session_data* sd) @@ -5904,13 +5908,15 @@ enum e_setpos pc_setpos(struct map_session_data* sd, unsigned short mapindex, in sd->state.workinprogress = WIP_DISABLE_NONE; if( sd->state.changemap ) { // Misc map-changing settings - unsigned short curr_map_instance_id = map_getmapdata(sd->bl.m)->instance_id, new_map_instance_id = (mapdata ? mapdata->instance_id : 0); + int curr_map_instance_id = map_getmapdata(sd->bl.m)->instance_id, new_map_instance_id = (mapdata ? mapdata->instance_id : 0); if (curr_map_instance_id != new_map_instance_id) { - if (curr_map_instance_id) // Update instance timer for the map on leave + if (curr_map_instance_id > 0) { // Update instance timer for the map on leave instance_delusers(curr_map_instance_id); + sd->instance_mode = util::umap_find(instances, curr_map_instance_id)->mode; // Store mode for instance destruction button checks + } - if (new_map_instance_id) // Update instance timer for the map on enter + if (new_map_instance_id > 0) // Update instance timer for the map on enter instance_addusers(new_map_instance_id); } diff --git a/src/map/pc.hpp b/src/map/pc.hpp index 7e044a8ccb9..61c52887054 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -24,6 +24,7 @@ #include "vending.hpp" // struct s_vending enum AtCommandType : uint8; +enum e_instance_mode : uint8; //enum e_log_chat_type : uint8; enum e_log_pick_type : uint32; enum sc_type : int16; @@ -762,7 +763,9 @@ struct map_session_data { t_tick tick; } roulette; - unsigned short instance_id; + int instance_id; + e_instance_mode instance_mode; ///< Mode of instance player last leaves from (used for instance destruction button) + short setlook_head_top, setlook_head_mid, setlook_head_bottom, setlook_robe; ///< Stores 'setlook' script command values. #if PACKETVER >= 20150513