context: /*************************************************************************
   *
   *   file		    : ChatMgr.cs
   *   copyright		: (C) The WCell Team
   *   email		    : info@wcell.org
   *   last changed	: $LastChangedDate: 2008-02-18 07:46:37 -0800 (Tue, 19 Feb 2008) $
   *   last author	: $LastChangedBy: anonemous $
   *   revision		: $Rev: 148 $
   *
   *   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; either version 2 of the License, or
   *   (at your option) any later version.
   *
   *************************************************************************/
  
  using System;
  using System.Collections.Generic;
  using System.Text;
  using WCell.Constants;
  using WCell.Constants.Chat;
  using WCell.Constants.Factions;
  using WCell.Constants.Guilds;
  using WCell.Constants.Misc;
  using WCell.Core;
  using WCell.RealmServer.Commands;
  using WCell.RealmServer.Entities;
  using WCell.RealmServer.Global;
  using WCell.RealmServer.Guilds;
  using WCell.RealmServer.Lang;
  using WCell.RealmServer.Network;
  using WCell.Util;
  using WCell.RealmServer.Groups;
  
  namespace WCell.RealmServer.Chat
  {
  
  	/// <summary>
  	/// Manager for sending and receiving chat messages.
  	/// </summary>
  	public static partial class ChatMgr
  	{
  		/// <summary>
  		/// The start of the lowercase alphabet in the ASCII table.
  		/// </summary>
  		private const int LowercaseAlphabetStart = ('a');
  
  		/// <summary>
  		/// The end of the lowercase alphabet in the ASCII table.
  		/// </summary>
  		private const int LowercaseAlphabetEnd = ('z');
  
  		/// <summary>
  		/// The radius in which people can hear a someone else say something.
  		/// </summary>
  		public static float ListeningRadius = 50f;
  
  		/// <summary>
  		/// The radius in which people can hear a someone else say something.
  		/// </summary>
  		public static float YellRadius = 300f;
  
  		/// <summary>
  		/// Whether normal say/yell/emote is global. (sent to everyone in the world)
  		/// </summary>
  		public static bool GlobalChat = true;
  
  		/// <summary>
  		/// Whether chat messages of opposite factions will be scrambled
  		/// </summary>
  		public static bool ScrambleChat = true;
  
  		/// <summary>
  		/// Array of chat type handlers.
  		/// </summary.
  		public static readonly ChatParserDelegate[] ChatParsers = new ChatParserDelegate[(int)ChatMsgType.End];
  
  		/// <summary>
  		/// Default static constructor.
  		/// </summary>
  		static ChatMgr()
  		{
  			ChatParsers[(int)ChatMsgType.Say] = SayYellEmoteParser;
  			ChatParsers[(int)ChatMsgType.Yell] = SayYellEmoteParser;
  			ChatParsers[(int)ChatMsgType.Emote] = SayYellEmoteParser;
  			ChatParsers[(int)ChatMsgType.Party] = GroupParser;
  			ChatParsers[(int)ChatMsgType.Raid] = SubGroupParser;
  			ChatParsers[(int)ChatMsgType.RaidLeader] = SubGroupParser;
  			ChatParsers[(int)ChatMsgType.RaidWarn] = SubGroupParser;
  			ChatParsers[(int)ChatMsgType.Guild] = GuildParser;
  			ChatParsers[(int)ChatMsgType.Officer] = OfficerParser;
  			ChatParsers[(int)ChatMsgType.Whisper] = WhisperParser;
  			ChatParsers[(int)ChatMsgType.Channel] = ChannelParser;
  			ChatParsers[(int)ChatMsgType.AFK] = AFKParser;
  			ChatParsers[(int)ChatMsgType.DND] = AFKParser;
  		}
  
  		#region Parsers
  
  		/// <summary>
  		/// Parses any incoming say, yell, or emote messages.
  		/// </summary>
  		/// <param name="type">the type of chat message indicated by the client</param>
  		/// <param name="language">the chat language indicated by the client</param>
  		/// <param name="packet">the actual chat message packet</param>
  		private static void SayYellEmoteParser(Character sender, ChatMsgType type, ChatLanguage language, RealmPacketIn packet)
  		{
  			var msg = ReadMessage(packet);
  			if (msg.Length == 0)
  				return;
  
  			SayYellEmote(sender, type, language, msg, type == ChatMsgType.Yell ? YellRadius : ListeningRadius);
  		}
  
  		public static void SayYellEmote(this Character sender, ChatMsgType type, ChatLanguage language, string msg, float radius)
  		{
  			if (RealmCommandHandler.HandleCommand(sender, msg, sender.Target as Character))
  				return;
  
  			if (type != ChatMsgType.WhisperInform && msg.Length == 0)
  			{
  				return;
  			}
  
  			if (GlobalChat)
  			{
  				using (var packetOut = CreateCharChatMessage(type, language, sender.EntityId, sender.EntityId, null, msg, sender.ChatTag))
  				{
  					foreach (var chr in World.GetAllCharacters())
  					{
  						chr.Send(packetOut);
  					}
  				}
  			}
  			else
  			{
  				var faction = sender.FactionGroup;
  				RealmPacketOut pckt = null, scrambledPckt = null;
  
  				var scrambleDefault = ScrambleChat && sender.Role.ScrambleChat;
  				Func<WorldObject, bool> iterator = obj =>
  				{
  					if ((obj is Character))
  					{
  						var chr = (Character)obj;
  						if (!scrambleDefault || chr.FactionGroup == faction || !chr.Role.ScrambleChat)
  						{
  							if (pckt == null)
  							{
  								pckt = CreateCharChatMessage(type, language, sender.EntityId, sender.EntityId, null, msg, sender.ChatTag);
  							}
  							chr.Send(pckt);
  						}
  						else
  						{
  							if (scrambledPckt == null)
  							{
  								scrambledPckt = CreateCharChatMessage(type, language, sender.EntityId, sender.EntityId, null,
  																	  ScrambleMessage(msg), sender.ChatTag);
  							}
  							chr.Send(scrambledPckt);
  						}
  					}
  					return true;
  				};
  
  				if (radius == WorldObject.BroadcastRange)
  				{
  					sender.NearbyObjects.Iterate(iterator);
  				}
  				else
  				{
  					sender.IterateEnvironment(radius, iterator);
  				}
  
  				if (pckt != null)
  				{
  					pckt.Close();
  				}
  				if (scrambledPckt != null)
  				{
  					scrambledPckt.Close();
  				}
  			}
  		}
  
  		/// <summary>
  		/// Parses any incoming party or raid messages.
  		/// </summary>
  		/// <param name="sender">The character sending the message</param>
  		/// <param name="type">the type of chat message indicated by the client</param>
  		/// <param name="language">the chat language indicated by the client</param>
  		/// <param name="packet">the actual chat message packet</param>
  		private static void GroupParser(Character sender, ChatMsgType type, ChatLanguage language, RealmPacketIn packet)
  		{
  			var msg = ReadMessage(packet);
  
  			if (msg.Length == 0)
  				return;
  
  			SayGroup(sender, language, msg);
  		}
  
  		public static void SayGroup(this Character sender, ChatLanguage language, string msg)
  		{
  			if (RealmCommandHandler.HandleCommand(sender, msg, sender.Target as Character))
  				return;
  
  			var group = sender.Group;
  			if (group != null)
  				using (var packetOut =
  					CreateCharChatMessage(ChatMsgType.Party, ChatLanguage.Universal, sender, sender, null, msg, sender.ChatTag))
  				{
  					group.SendAll(packetOut);
  				}
  		}
  
  		/// <summary>
  		/// Parses any incoming party or raid messages.
  		/// </summary>
  		/// <param name="sender">The character sending the message</param>
  		/// <param name="type">the type of chat message indicated by the client</param>
  		/// <param name="language">the chat language indicated by the client</param>
  		/// <param name="packet">the actual chat message packet</param>
  		private static void SubGroupParser(Character sender, ChatMsgType type, ChatLanguage language, RealmPacketIn packet)
  		{
  			string msg = ReadMessage(packet);
  
  			if (msg.Length == 0)
  				return;
  
  			if (RealmCommandHandler.HandleCommand(sender, msg, sender.Target as Character))
  				return;
  
  			var group = sender.SubGroup;
  			if (group != null)
  			{
  				using (var packetOut = CreateCharChatMessage(type, ChatLanguage.Universal, sender, sender, null, msg))
  				{
  					group.Send(packetOut, null);
  				}
  			}
  		}
  
  		/// <summary>
  		/// Parses any incoming guild message.
  		/// </summary>
  		/// <param name="sender">The character sending the message</param>
  		/// <param name="type">the type of chat message indicated by the client</param>
  		/// <param name="language">the chat language indicated by the client</param>
  		/// <param name="packet">the actual chat message packet</param>
  		private static void GuildParser(Character sender, ChatMsgType type, ChatLanguage language, RealmPacketIn packet)
  		{
  			var msg = ReadMessage(packet);
  
  			if (msg.Length == 0)
  				return;
  
  			if (RealmCommandHandler.HandleCommand(sender, msg, sender.Target as Character))
  				return;
  
  			var guild = Guild.CheckPrivs(sender, GuildCommandId.MEMBER, GuildPrivileges.GCHATSPEAK);
  			if (guild != null)
  			{
  				SendGuildMessage(sender, guild, msg);
  			}
  		}
  
  		/// <summary>
  		/// Parses any incoming officer message.
  		/// </summary>
  		/// <param name="sender">The character sending the message</param>
  		/// <param name="type">the type of chat message indicated by the client</param>
  		/// <param name="language">the chat language indicated by the client</param>
  		/// <param name="packet">the actual chat message packet</param>
  		private static void OfficerParser(Character sender, ChatMsgType type, ChatLanguage language, RealmPacketIn packet)
  		{
  			string msg = ReadMessage(packet);
  
  			if (msg.Length == 0)
  				return;
  
  			if (RealmCommandHandler.HandleCommand(sender, msg, sender.Target as Character))
  				return;
  
  			var guild = Guild.CheckPrivs(sender, GuildCommandId.MEMBER, GuildPrivileges.GCHATSPEAK);
  			if (guild != null)
  			{
  				SendGuildOfficerMessage(sender, guild, msg);
  			}
  		}
  
  		/// <summary>
  		/// Parses any incoming whispers.
  		/// </summary>
  		/// <param name="type">the type of chat message indicated by the client</param>
  		/// <param name="language">the chat language indicated by the client</param>
  		/// <param name="packet">the actual chat message packet</param>
  		private static void WhisperParser(Character sender, ChatMsgType type, ChatLanguage language, RealmPacketIn packet)
  		{
  			var recipient = packet.ReadCString();
  			var msg = ReadMessage(packet);
  
  			if (msg.Length == 0)
  				return;
  
  			if (RealmCommandHandler.HandleCommand(sender, msg, sender.Target as Character))
  				return;
  
  			var targetChr = World.GetCharacter(recipient, false);
  			if (targetChr == null)
  			{
  				SendChatPlayerNotFoundReply(sender.Client, recipient);
  				return;
  			}
  
  			if (targetChr.Faction.Group != sender.Faction.Group)
  			{
  				SendChatPlayerWrongTeamReply(sender.Client);
  				return;
  			}
  
  			if (targetChr.IsIgnoring(sender))
  			{
  				using (var packetOut = CreateCharChatMessage(ChatMsgType.Ignored, ChatLanguage.Universal, targetChr, sender, null, msg))
  				{
  					sender.Send(packetOut);
  				}
  			}
  			else
  			{
  				using (var packetOut = CreateCharChatMessage(ChatMsgType.Whisper, ChatLanguage.Universal, sender, targetChr, null, msg))
  				{
  					targetChr.Send(packetOut);
  				}
  			}
  
  			using (var packetOut = CreateCharChatMessage(ChatMsgType.MsgReply, ChatLanguage.Universal, targetChr, targetChr, null, msg, sender.ChatTag))
  			{
  				sender.Send(packetOut);
  			}
  
  			// handle afk/dnd situations
  			if (targetChr.IsAFK)
  			{
  				using (var packetOut = CreateCharChatMessage(ChatMsgType.AFK, ChatLanguage.Universal, targetChr, sender, null, targetChr.AFKReason, targetChr.ChatTag))
  				{
  					sender.Send(packetOut);
  				}
  			}
  
  			if (targetChr.IsDND)
  			{
  				using (var packetOut = CreateCharChatMessage(ChatMsgType.DND, ChatLanguage.Universal, targetChr, sender, null, string.Empty, targetChr.ChatTag))
  				{
  					sender.Send(packetOut);
  				}
  			}
  		}
  
  		/// <summary>
  		/// Parses any incoming channel messages.
  		/// </summary>
  		/// <param name="type">the type of chat message indicated by the client</param>
  		/// <param name="language">the chat language indicated by the client</param>
  		/// <param name="packet">the actual chat message packet</param>
  		private static void ChannelParser(Character sender, ChatMsgType type, ChatLanguage language, RealmPacketIn packet)
  		{
  			var channel = packet.ReadCString();
  			var message = packet.ReadCString();
  
  			if (RealmCommandHandler.HandleCommand(sender, message, sender.Target as Character))
  				return;
  
  			var chan = ChatChannelGroup.RetrieveChannel(sender, channel);
  			if (chan == null)
  				return;
  
  			chan.SendMessage(sender, message);
  		}
  
  		private static void AFKParser(Character sender, ChatMsgType type, ChatLanguage language, RealmPacketIn packet)
  		{
  			var reason = packet.ReadCString();
  
  			if (type == ChatMsgType.AFK)
  			{
  				// flip their AFK flag
  				sender.IsAFK = !sender.IsAFK;
  
  				sender.AFKReason = (sender.IsAFK ? reason : "");
  			}
  
  			if (type == ChatMsgType.DND)
  			{
  				// flip their DND flag
  				sender.IsDND = !sender.IsDND;
  
  				sender.DNDReason = (sender.IsDND ? reason : "");
  			}
  		}
  
  		#endregion
  
  		#region Send/Reply methods
  
  		/// <summary>
  		/// Sends a message that the whisper target wasn't found.
  		/// </summary>
  		/// <param name="client">the client to send to</param>
  		/// <param name="recipient">the name of the target player</param>
  		public static void SendChatPlayerNotFoundReply(IPacketReceiver client, string recipient)
  		{
  			using (var packet = new RealmPacketOut(RealmServerOpCode.SMSG_CHAT_PLAYER_NOT_FOUND))
  			{
  				packet.WriteCString(recipient);
  				client.Send(packet);
  			}
  		}
  
  		/// <summary>
  		/// Sends a message that the whisper target isn't the same faction.
  		/// </summary>
  		/// <param name="client">the client to send to</param>
  		/// <param name="recipient">the name of the target player</param>
  		public static void SendChatPlayerWrongTeamReply(IPacketReceiver client)
  		{
  			using (var packet = new RealmPacketOut(RealmServerOpCode.SMSG_CHAT_WRONG_FACTION))
  			{
  				// just guessing here.
  				client.Send(packet);
  			}
  		}
  
  		#endregion
  
  		#region Send Helpers
  		private static RealmPacketOut CreateObjectChatMessage(ChatMsgType type, ChatLanguage language, INamedEntity obj)
  		{
  			var name = obj.Name;
  
  			var packet = new RealmPacketOut(RealmServerOpCode.SMSG_MESSAGECHAT, 31 + name.Length + 50);
  			packet.Write((byte)type);															// 1
  			packet.Write((uint)language);														// 5
  			packet.Write(obj.EntityId);																// 13
  			packet.Write(0);																	// 17
  			packet.WriteUIntPascalString(name);														// 21 + nameLength
  			packet.Write((long)0);																// 29 + nameLength
  			//packet.Write(obj.EntityId);
  
  			return packet;
  		}
  
  		/// <summary>
  		/// Creates a chat message packet for a non-player object.
  		/// </summary>
  		/// <param name="type">the type of chat message</param>
  		/// <param name="language">the language the message is in</param>
  		/// <param name="obj">the object "saying" the message</param>
  		/// <param name="msg">the message itself</param>
  		/// <param name="tag">any chat tags for the object</param>
  		/// <returns>the generated chat packet</returns>
  		private static RealmPacketOut CreateObjectChatMessage(ChatMsgType type,
  			ChatLanguage language, INamedEntity obj, string msg, ChatTag tag)
  		{
  
gt: 			var packet = CreateObjectChatMessage(type, language, obj);