Browse Source

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 <capucrath@gmail.com>
Aleos 4 năm trước cách đây
mục cha
commit
3804d7f603

+ 2 - 0
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.

+ 2 - 0
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.

+ 2 - 0
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.

+ 2 - 0
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.

+ 2 - 0
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.

+ 1 - 1
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 */

+ 4 - 0
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);
 	}
 }
 

+ 25 - 0
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 <command>.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 <account id>.L <name id>.W <identified>.B <damaged>.B <refine>.B <card1>.W <card2>.W <card3>.W <card4>.W <equip location>.W <item type>.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
 }
 

+ 5 - 0
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);

+ 1 - 1
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);

+ 3 - 3
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;

+ 101 - 5
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<s_instance_data> 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;
 }

+ 2 - 1
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<int16> 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);

+ 2 - 1
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;
 

+ 6 - 6
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;
 }

+ 1 - 1
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?

+ 20 - 14
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);
 		}
 

+ 4 - 1
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