ソースを参照

Initial implementation of alliance chat

Fixes #9210

Thanks to @Pokye
Lemongrass3110 2 ヶ月 前
コミット
833449449d
5 ファイル変更179 行追加18 行削除
  1. 31 15
      src/map/atcommand.cpp
  2. 136 2
      src/map/clif.cpp
  3. 3 0
      src/map/clif.hpp
  4. 1 1
      src/map/clif_packetdb.hpp
  5. 8 0
      src/map/packets.hpp

+ 31 - 15
src/map/atcommand.cpp

@@ -11868,21 +11868,21 @@ bool is_atcommand(const int32 fd, map_session_data* sd, const char* message, int
 	if ( *message != atcommand_symbol && *message != charcommand_symbol )
 		return false;
 
-	// type value 0|2 = script|console invoked: bypass restrictions
-	if ( type == 1 || type == 3) {
-		//Commands are disabled on maps flagged as 'nocommand'
-		if ( pc_get_group_level(sd) < map_getmapflag(sd->bl.m, MF_NOCOMMAND) ) {
-			clif_displaymessage(fd, msg_txt(sd,143)); // Commands are disabled on this map.
-			return false;
-		}
-	}
-
 	if (*message == charcommand_symbol)
 		is_atcommand = false;
 
 	if (is_atcommand) { // @command
 		sprintf(atcmd_msg, "%s", message);
 		ssd = sd;
+
+		// type value 0|2 = script|console invoked: bypass restrictions
+		if ( type == 1 || type == 3) {
+			//Commands are disabled on maps flagged as 'nocommand'
+			if ( pc_get_group_level(sd) < map_getmapflag(sd->bl.m, MF_NOCOMMAND) ) {
+				clif_displaymessage(fd, msg_txt(sd,143)); // Commands are disabled on this map.
+				return false;
+			}
+		}
 	} else { // #command
 		char charname[NAME_LENGTH];
 		int32 n;
@@ -11891,13 +11891,20 @@ bool is_atcommand(const int32 fd, map_session_data* sd, const char* message, int
 		if ((n = sscanf(message, "%255s \"%23[^\"]\" %255[^\n]", command, charname, params)) < 2
 		 && (n = sscanf(message, "%255s %23s %255[^\n]", command, charname, params)) < 2
 		) {
-			if (pc_get_group_level(sd) == 0) {
-				if (n < 1)
-					return false; // No command found. Display as normal message.
-
-				info = get_atcommandinfo_byname(atcommand_alias_db.checkAlias(command + 1));
-				if (!info || info->char_groups[sd->group->index] == 0)  // If we can't use or doesn't exist: don't even display the command failed message
+			if (n < 1)
+				return false; // No command found. Display as normal message.
+
+			info = get_atcommandinfo_byname(atcommand_alias_db.checkAlias(command + 1));
+			if (!info || info->char_groups[sd->group->index] == 0)  // If we can't use or doesn't exist: don't even display the command failed message
+				return false;
+
+			// type value 0|2 = script|console invoked: bypass restrictions
+			if ( type == 1 || type == 3) {
+				//Commands are disabled on maps flagged as 'nocommand'
+				if ( pc_get_group_level(sd) < map_getmapflag(sd->bl.m, MF_NOCOMMAND) ) {
+					clif_displaymessage(fd, msg_txt(sd,143)); // Commands are disabled on this map.
 					return false;
+				}
 			}
 
 			sprintf(output, msg_txt(sd,1388), charcommand_symbol); // Charcommand failed (usage: %c<command> <char name> <parameters>).
@@ -11905,6 +11912,15 @@ bool is_atcommand(const int32 fd, map_session_data* sd, const char* message, int
 			return true;
 		}
 
+		// type value 0|2 = script|console invoked: bypass restrictions
+		if ( type == 1 || type == 3) {
+			//Commands are disabled on maps flagged as 'nocommand'
+			if ( pc_get_group_level(sd) < map_getmapflag(sd->bl.m, MF_NOCOMMAND) ) {
+				clif_displaymessage(fd, msg_txt(sd,143)); // Commands are disabled on this map.
+				return false;
+			}
+		}
+
 		ssd = map_nick2sd(charname,true);
 		if (ssd == nullptr) {
 			sprintf(output, msg_txt(sd,1389), command); // %s failed. Player not found.

+ 136 - 2
src/map/clif.cpp

@@ -640,7 +640,10 @@ int32 clif_send(const void* buf, int32 len, struct block_list* bl, enum send_tar
 	case GUILD_SAMEMAP_WOS:
 	case GUILD:
 	case GUILD_WOS:
-	case GUILD_NOBG: {
+	case GUILD_NOBG:
+	case GUILD_ALLIANCES:
+	case GUILD_ALLIANCES_FRIEND:
+	case GUILD_ALLIANCES_ENEMY: {
 		if (!sd || !sd->status.guild_id || !sd->guild)
 			break;
 
@@ -656,7 +659,7 @@ int32 clif_send(const void* buf, int32 len, struct block_list* bl, enum send_tar
 				if( sd->bl.id == bl->id && (type == GUILD_WOS || type == GUILD_SAMEMAP_WOS || type == GUILD_AREA_WOS) )
 					continue;
 
-				if( type != GUILD && type != GUILD_NOBG && type != GUILD_WOS && sd->bl.m != bl->m )
+				if( type != GUILD && type != GUILD_NOBG && type != GUILD_WOS && type != GUILD_ALLIANCES && type != GUILD_ALLIANCES_FRIEND && type != GUILD_ALLIANCES_ENEMY && sd->bl.m != bl->m )
 					continue;
 
 				if( (type == GUILD_AREA || type == GUILD_AREA_WOS) && (sd->bl.x < x0 || sd->bl.y < y0 || sd->bl.x > x1 || sd->bl.y > y1) )
@@ -667,6 +670,52 @@ int32 clif_send(const void* buf, int32 len, struct block_list* bl, enum send_tar
 				WFIFOSET(fd,len);
 			}
 		}
+
+		if( type == GUILD_ALLIANCES || type == GUILD_ALLIANCES_FRIEND || type == GUILD_ALLIANCES_ENEMY ){
+			for( const guild_alliance& alliance : g.alliance ){
+				if( alliance.guild_id == 0 ){
+					continue;
+				}
+
+				switch( type ){
+					case GUILD_ALLIANCES:
+						// Send it to both friends and enemies
+						break;
+
+					case GUILD_ALLIANCES_FRIEND:
+						// Only send it to friends
+						if( alliance.opposition != 0 ){
+							// Skip enemies
+							continue;
+						}
+						break;
+
+					case GUILD_ALLIANCES_ENEMY:
+						// Only send it to enemies
+						if( alliance.opposition == 0 ){
+							// Skip friends
+							continue;
+						}
+						break;
+				}
+
+				std::shared_ptr<MapGuild> alliance_guild = guild_search( alliance.guild_id );
+
+				if( alliance_guild == nullptr ){
+					continue;
+				}
+
+				map_session_data* alliance_sd = guild_getavailablesd( alliance_guild->guild );
+
+				if( alliance_sd == nullptr ){
+					continue;
+				}
+
+				// Send the packet to the alliance guild via clif_send again, in case a GM enabled guildspy on them
+				clif_send( buf, len, &alliance_sd->bl, GUILD );
+			}
+		}
+
 		if (!enable_spy) //Skip unnecessary parsing. [Skotlex]
 			break;
 
@@ -25576,6 +25625,91 @@ void clif_parse_macro_checker( int32 fd, map_session_data* sd ){
 #endif
 }
 
+void clif_guild_alliance_message( std::shared_ptr<MapGuild> guild, const char* message, size_t length ){
+#if PACKETVER_MAIN_NUM >= 20230607
+	// Check if it was an empty message
+	if( length == 0 ){
+		return;
+	}
+
+	map_session_data* sd = guild_getavailablesd( guild->guild );
+
+	if( sd == nullptr ){
+		return;
+	}
+
+	// Zero termination
+	length += 1;
+
+	if( length >= CHAT_SIZE_MAX ){
+		ShowWarning( "clif_guild_alliance_message: Truncated message '%s' (length=%" PRIuPTR ", CHAT_SIZE_MAX=% " PRIuPTR ", guild_id = % d).\n", message, length, CHAT_SIZE_MAX, guild->guild.guild_id );
+		length = CHAT_SIZE_MAX;
+	}
+
+	PACKET_ZC_ALLY_CHAT* p = reinterpret_cast<PACKET_ZC_ALLY_CHAT*>( packet_buffer );
+
+	p->packetType = HEADER_ZC_ALLY_CHAT;
+	p->packetLength = sizeof( *p );
+
+	static size_t maximum = static_cast<size_t>( std::numeric_limits<decltype( p->packetLength )>::max() ) - sizeof( *p );
+
+	if( length >= maximum ){
+		ShowWarning( "clif_guild_alliance_message: Truncated message '%s' (length=%" PRIuPTR ", maximum=% " PRIuPTR ", guild_id = % d).\n", message, length, maximum, guild->guild.guild_id );
+		length = maximum;
+	}
+
+	safestrncpy( p->message, message, length );
+	p->packetLength += static_cast<decltype( p->packetLength )>( length );
+
+	clif_send( p, p->packetLength, &sd->bl, GUILD_ALLIANCES_FRIEND );
+#endif
+}
+
+void clif_parse_guild_alliance_message( int fd, map_session_data* sd ){
+#if PACKETVER_MAIN_NUM >= 20230607
+	if( sd == nullptr ){
+		return;
+	}
+
+	// This packet is sent if the message starts with #
+	// Therefore if either the atcommand or the charcommand symbol is a #, we have to do the guild check later
+	if( atcommand_symbol != '#' && charcommand_symbol != '#' ){
+		// Check if the player is in a guild
+		if( sd->guild == nullptr ){
+			// Not in a guild, return early
+			return;
+		}
+	}
+
+	char name[NAME_LENGTH], message[CHAT_SIZE_MAX], output[CHAT_SIZE_MAX+NAME_LENGTH*2];
+
+	// Validate packet and retrieve name and message
+	if( !clif_process_message( sd, false, name, message, output ) ){
+		return;
+	}
+
+	// If either the atcommand or the charcommand symbol is a #, we have to try to parse the command
+	if( atcommand_symbol == '#' || charcommand_symbol == '#' ){
+		char cmd[CHAT_SIZE_MAX];
+
+		safesnprintf( cmd, sizeof( cmd ), "#%s", message );
+
+		// Check if the message contained a command
+		if( is_atcommand( fd, sd, cmd, 1 ) ){
+			// It was a command, no need to send it to the allicances
+			return;
+		}
+
+		// This check has to be done after clif_process_message, because the player may have used a command
+		if( sd->guild == nullptr ){
+			return;
+		}
+	}
+
+	clif_guild_alliance_message( sd->guild, output, strlen( output ) );
+#endif
+}
+
 /*==========================================
  * Main client packet processing function
  *------------------------------------------*/

+ 3 - 0
src/map/clif.hpp

@@ -225,6 +225,9 @@ enum send_target : uint8_t {
 	GUILD_AREA,
 	GUILD_AREA_WOS,
 	GUILD_NOBG,
+	GUILD_ALLIANCES,
+	GUILD_ALLIANCES_FRIEND,
+	GUILD_ALLIANCES_ENEMY,
 	DUEL,
 	DUEL_WOS,
 	SELF,

+ 1 - 1
src/map/clif_packetdb.hpp

@@ -2034,7 +2034,7 @@
 #endif
 
 #if PACKETVER_MAIN_NUM >= 20230607
-	parseable_packet( HEADER_CZ_ALLY_CHAT, -1, clif_parse_dull, 0 );
+	parseable_packet( HEADER_CZ_ALLY_CHAT, -1, clif_parse_guild_alliance_message, 2, 4 );
 #endif
 
 #if PACKETVER_MAIN_NUM >= 20230705

+ 8 - 0
src/map/packets.hpp

@@ -2022,6 +2022,7 @@ DEFINE_PACKET_HEADER(ZC_WHISPER_LIST, 0xd4);
 struct PACKET_CZ_ALLY_CHAT{
 	int16 packetType;
 	int16 packetLength;
+	char message[];
 } __attribute__((packed));
 DEFINE_PACKET_HEADER(CZ_ALLY_CHAT, 0xbdd);
 
@@ -2031,6 +2032,13 @@ struct PACKET_CZ_REQ_REPORT_USER{
 } __attribute__((packed));
 DEFINE_PACKET_HEADER(CZ_REQ_REPORT_USER, 0xbe2);
 
+struct PACKET_ZC_ALLY_CHAT{
+	int16 packetType;
+	int16 packetLength;
+	char message[];
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(ZC_ALLY_CHAT, 0xbde);
+
 // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute
 #if !defined( sun ) && ( !defined( __NETBSD__ ) || __NetBSD_Version__ >= 600000000 )
 	#pragma pack( pop )