context: /*
   * DAWN OF LIGHT - The first free open source DAoC server emulator
   *
   * 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.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   *
   */
  using System;
  using System.Collections.Generic;
  using System.Reflection;
  using System.Text.RegularExpressions;
  using System.Linq;
  
  using DOL.Database;
  using DOL.Events;
  using DOL.GS;
  using DOL.GS.ServerProperties;
  
  using log4net;
  
  
  namespace DOL.GS.PacketHandler.Client.v168
  {
  	/// <summary>
  	/// Character Create and Customization handler.  Please maintain all commented debug statements
  	/// in order to support future debugging. - Tolakram
  	/// </summary>
  	[PacketHandlerAttribute(PacketHandlerType.TCP, eClientPackets.CharacterCreateRequest, "Handles character creation requests", eClientStatus.LoggedIn)]
  	public class CharacterCreateRequestHandler : IPacketHandler
  	{
  		/// <summary>
  		/// Defines a logger for this class.
  		/// </summary>
  		private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  
  		/// <summary>
  		/// Max Points to allow on player creation
  		/// </summary>
  		public const int MaxStartingBonusPoints = 30;
  
  		/// <summary>
  		/// Client Operation Value.
  		/// </summary>
  		public enum eOperation : uint
  		{
  			Delete = 0x12345678,
  			Create = 0x23456789,
  			Customize = 0x3456789A,
  			Unknown = 0x456789AB,
  		}
  
  
  		public void HandlePacket(GameClient client, GSPacketIn packet)
  		{
  			if (client.Version >= GameClient.eClientVersion.Version1124) // 1124 support
  			{
  				_HandlePacket1124(client, packet);
  				return;
  			}
  
  			var accountName = packet.ReadString(24);
  			if (log.IsDebugEnabled)
  				log.DebugFormat("CharacterCreateRequestHandler for account {0} using version {1}", accountName, client.Version);
  
  			// TODO more correctly check, client send accountName as account-S, -N, -H (if it not fit in 20, then only account)
  			if (!accountName.StartsWith(client.Account.Name))
  			{
  				if (Properties.BAN_HACKERS)
  					client.BanAccount(string.Format("Autoban wrong Account '{0}'", accountName));
  
  				client.Disconnect();
  				return;
  			}
  
  			// Realm
  			eRealm currentRealm = eRealm.None;
  			if (accountName.EndsWith("-S")) currentRealm = eRealm.Albion;
  			else if (accountName.EndsWith("-N")) currentRealm = eRealm.Midgard;
  			else if (accountName.EndsWith("-H")) currentRealm = eRealm.Hibernia;
  
  
  			// Client character count support
  			int charsCount = client.Version < GameClient.eClientVersion.Version173 ? 8 : 10;
  			bool needRefresh = false;
  
  			for (int i = 0; i < charsCount; i++)
  			{
  				var pakdata = new CreationCharacterData(packet, client);
  
  				// Graveen: changed the following to allow GMs to have special chars in their names (_,-, etc..)
  				var nameCheck = new Regex("^[A-Z][a-zA-Z]");
  				if (!string.IsNullOrEmpty(pakdata.CharName) && (pakdata.CharName.Length < 3 || !nameCheck.IsMatch(pakdata.CharName)))
  				{
  					if ((ePrivLevel)client.Account.PrivLevel == ePrivLevel.Player)
  					{
  						if (ServerProperties.Properties.BAN_HACKERS)
  							client.BanAccount(string.Format("Autoban bad CharName '{0}'", pakdata.CharName));
  
  						client.Disconnect();
  						return;
  					}
  				}
  
  				switch ((eOperation)pakdata.Operation)
  				{
  					case eOperation.Delete:
  						if (string.IsNullOrEmpty(pakdata.CharName))
  						{
  							// Deletion in 1.104+ check for removed character.
  							needRefresh |= CheckForDeletedCharacter(accountName, client, i);
  						}
  						break;
  					case eOperation.Customize:
  						if (!string.IsNullOrEmpty(pakdata.CharName))
  						{
  							// Candidate for Customizing ?
  							var character = client.Account.Characters != null ? client.Account.Characters.FirstOrDefault(ch => ch.Name.Equals(pakdata.CharName, StringComparison.OrdinalIgnoreCase)) : null;
  							if (character != null)
  								needRefresh |= CheckCharacterForUpdates(pakdata, client, character);
  						}
  						break;
  					case eOperation.Create:
  						if (!string.IsNullOrEmpty(pakdata.CharName))
  						{
  							// Candidate for Creation ?
  							var character = client.Account.Characters != null ? client.Account.Characters.FirstOrDefault(ch => ch.Name.Equals(pakdata.CharName, StringComparison.OrdinalIgnoreCase)) : null;
  							if (character == null)
  								needRefresh |= CreateCharacter(pakdata, client, i);
  						}
  						break;
  					default:
  						break;
  				}
  			}
  
  			if (needRefresh)
  			{
  				client.Out.SendCharacterOverview(currentRealm);
  			}
  		}
  
  		#region caracter creation data
  		struct CreationCharacterData
  		{
  			public readonly string CharName;
  			public readonly int CustomMode;
  			public readonly int EyeSize;
  			public readonly int LipSize;
  			public readonly int EyeColor;
  			public readonly int HairColor;
  			public readonly int FaceType;
  			public readonly int HairStyle;
  			public readonly int MoodType;
  			public readonly uint Operation;
  			public readonly int Class;
  			public readonly int Realm;
  			public readonly int Race;
  			public readonly int Gender;
  			public readonly bool SIStartLocation;
  			public readonly ushort CreationModel;
  			public readonly int Region;
  
  			public readonly int Strength;
  			public readonly int Dexterity;
  			public readonly int Constitution;
  			public readonly int Quickness;
  			public readonly int Intelligence;
  			public readonly int Piety;
  			public readonly int Empathy;
  			public readonly int Charisma;
  			public readonly int NewConstitution;
  
  			public int ConstitutionDiff { get { return NewConstitution - Constitution; } }
  
  			/// <summary>
  			/// Reads up ONE character iteration on the packet stream
  			/// </summary>
  			/// <param name="packet"></param>
  			/// <param name="client"></param>
  			public CreationCharacterData(GSPacketIn packet, GameClient client)
  			{
  				//unk - probably indicates customize or create (these are moved from 1.99 4 added bytes)
  				if (client.Version >= GameClient.eClientVersion.Version1104)
  					packet.ReadIntLowEndian();
  
  				CharName = packet.ReadString(24);
  				CustomMode = packet.ReadByte();
  				EyeSize = packet.ReadByte();
  				LipSize = packet.ReadByte();
  				EyeColor = packet.ReadByte();
  				HairColor = packet.ReadByte();
  				FaceType = packet.ReadByte();
  				HairStyle = packet.ReadByte();
  				packet.Skip(3);
  				MoodType = packet.ReadByte();
  				packet.Skip(8);
  
  				Operation = packet.ReadInt();
  				var unk = packet.ReadByte();
  
  				packet.Skip(24); //Location String
  				packet.Skip(24); //Skip class name
  				packet.Skip(24); //Skip race name
  
  				var level = packet.ReadByte(); //not safe!
  				Class = packet.ReadByte();
  				Realm = packet.ReadByte();
  
  				//The following byte contains
  				//1bit=start location ... in ShroudedIsles you can choose ...
  				//1bit=first race bit
  				//1bit=unknown
  				//1bit=gender (0=male, 1=female)
  				//4bit=race
  				byte startRaceGender = (byte)packet.ReadByte();
  				Race = (startRaceGender & 0x0F) + ((startRaceGender & 0x40) >> 2);
  				Gender = ((startRaceGender >> 4) & 0x01);
  				SIStartLocation = ((startRaceGender >> 7) != 0);
  
  				CreationModel = packet.ReadShortLowEndian();
  				Region = packet.ReadByte();
  				packet.Skip(1); //TODO second byte of region unused currently
  				packet.Skip(4); //TODO Unknown Int / last used?
  
  				Strength = packet.ReadByte();
  				Dexterity = packet.ReadByte();
  				Constitution = packet.ReadByte();
  				Quickness = packet.ReadByte();
  				Intelligence = packet.ReadByte();
  				Piety = packet.ReadByte();
  				Empathy = packet.ReadByte();
  				Charisma = packet.ReadByte();
  
  				packet.Skip(40); //TODO equipment
  
  				var activeRightSlot = packet.ReadByte(); // 0x9C
  				var activeLeftSlot = packet.ReadByte(); // 0x9D
  				var siZone = packet.ReadByte(); // 0x9E
  
  				// skip 4 bytes added in 1.99
  				if (client.Version >= GameClient.eClientVersion.Version199 && client.Version < GameClient.eClientVersion.Version1104)
  					packet.Skip(4);
  
  				// New constitution must be read before skipping 4 bytes
  				NewConstitution = packet.ReadByte(); // 0x9F
  
  
  			}
  		}
  		#endregion
  
  		#region Create Character
  		private bool CreateCharacter(CreationCharacterData pdata, GameClient client, int accountSlot)
  		{
  			Account account = client.Account;
  			var ch = new DOLCharacters();
  			ch.AccountName = account.Name;
  			ch.Name = pdata.CharName;
  
  			if (pdata.CustomMode == 0x01)
  			{
  				ch.EyeSize = (byte)pdata.EyeSize;
  				ch.LipSize = (byte)pdata.LipSize;
  				ch.EyeColor = (byte)pdata.EyeColor;
  				ch.HairColor = (byte)pdata.HairColor;
  				ch.FaceType = (byte)pdata.FaceType;
  				ch.HairStyle = (byte)pdata.HairStyle;
  				ch.MoodType = (byte)pdata.MoodType;
  				ch.CustomisationStep = 2; // disable config button
  
  				if (log.IsDebugEnabled)
  					log.Debug("Disable Config Button");
  			}
  
  			ch.Level = 1;
  			// Set Realm and Class
  			ch.Realm = pdata.Realm;
  			ch.Class = pdata.Class;
  
  			// Set Account Slot, Gender
  			ch.AccountSlot = accountSlot + ch.Realm * 100;
  			ch.Gender = pdata.Gender;
  
  			// Set Race
  			ch.Race = pdata.Race;
  
  			ch.CreationModel = pdata.CreationModel;
  			ch.CurrentModel = ch.CreationModel;
  			ch.Region = pdata.Region;
  
  			ch.Strength = pdata.Strength;
  			ch.Dexterity = pdata.Dexterity;
  			ch.Constitution = pdata.Constitution;
  			ch.Quickness = pdata.Quickness;
  			ch.Intelligence = pdata.Intelligence;
  			ch.Piety = pdata.Piety;
  			ch.Empathy = pdata.Empathy;
  			ch.Charisma = pdata.Charisma;
  
  			// defaults
  			ch.CreationDate = DateTime.Now;
  
  			ch.Endurance = 100;
  			ch.MaxEndurance = 100;
  			ch.Concentration = 100;
  			ch.MaxSpeed = GamePlayer.PLAYER_BASE_SPEED;
  
  			if (log.IsDebugEnabled)
  				log.DebugFormat("Creation {0} character, class:{1}, realm:{2}", client.Version, ch.Class, ch.Realm);
  
  			// Is class disabled ?
  			int occurences = 0;
  			List<string> disabled_classes = Util.SplitCSV(Properties.DISABLED_CLASSES, true);
  			occurences = (from j in disabled_classes
  						  where j == ch.Class.ToString()
  						  select j).Count();
  
  			if (occurences > 0 && (ePrivLevel)client.Account.PrivLevel == ePrivLevel.Player)
  			{
  				if (log.IsDebugEnabled)
  					log.DebugFormat("Client {0} tried to create a disabled classe: {1}", client.Account.Name, (eCharacterClass)ch.Class);
  
  				return true;
  			}
  
  			// check if race disabled
  			List<string> disabled_races = Util.SplitCSV(Properties.DISABLED_RACES, true);
  			occurences = (from j in disabled_races
  						  where j == ch.Race.ToString()
  						  select j).Count();
  
  			if (occurences > 0 && (ePrivLevel)client.Account.PrivLevel == ePrivLevel.Player)
  			{
  				if (log.IsDebugEnabled)
  					log.DebugFormat("Client {0} tried to create a disabled race: {1}", client.Account.Name, (eRace)ch.Race);
  
  				return true;
  			}
  
  
  			// If sending invalid Class ID
  			if (!Enum.IsDefined(typeof(eCharacterClass), (eCharacterClass)ch.Class))
  			{
  				if (log.IsErrorEnabled)
  					log.ErrorFormat("{0} tried to create a character with wrong class ID: {1}, realm:{2}", client.Account.Name, ch.Class, ch.Realm);
  
  				if (ServerProperties.Properties.BAN_HACKERS)
  				{
  					client.BanAccount(string.Format("Autoban character create class: id:{0} realm:{1} name:{2} account:{3}", ch.Class, ch.Realm, ch.Name, account.Name));
  					client.Disconnect();
  					return false;
  				}
  				return true;
  			}
  
  			// check if client tried to create invalid char
  			if (!IsCharacterValid(ch))
  			{
  				if (log.IsWarnEnabled)
  				{
  					log.WarnFormat("{0} tried to create invalid character:\nchar name={1}, gender={2}, race={3}, realm={4}, class={5}, region={6}" +
  								   "\nstr={7}, con={8}, dex={9}, qui={10}, int={11}, pie={12}, emp={13}, chr={14}", ch.AccountName, ch.Name, ch.Gender,
  								  ch.Race, ch.Realm, ch.Class, ch.Region, ch.Strength, ch.Constitution, ch.Dexterity, ch.Quickness, ch.Intelligence, ch.Piety, ch.Empathy, ch.Charisma);
  				}
  				return true;
  			}
  
  			//Save the character in the database
  			GameServer.Database.AddObject(ch);
  
  			// Fire the character creation event
  			// This is Where Most Creation Script should take over to update any data they would like !
  			GameEventMgr.Notify(DatabaseEvent.CharacterCreated, null, new CharacterEventArgs(ch, client));
  
  			//write changes
  			GameServer.Database.SaveObject(ch);
  
  			// Log creation
  			AuditMgr.AddAuditEntry(client, AuditType.Account, AuditSubtype.CharacterCreate, "", pdata.CharName);
  
  			client.Account.Characters = null;
  
  			if (log.IsInfoEnabled)
  				log.InfoFormat("Character {0} created on Account {1}!", pdata.CharName, account);
  
  			// Reload Account Relations
  			GameServer.Database.FillObjectRelations(client.Account);
  
  			return true;
  		}
  
  		#endregion Create Character
  
  
  		#region Character Updates
  		/// <summary>
  		/// Check if a Character Needs update based to packet data
  		/// </summary>
  		/// <param name="pdata">packet data</param>
  		/// <param name="client">client</param>
  		/// <param name="character">db character</param>
  		/// <returns>True if character need refreshment false if no refresh needed.</returns>
  		private bool CheckCharacterForUpdates(CreationCharacterData pdata, GameClient client, DOLCharacters character)
  		{
  			int newModel = character.CurrentModel;
  
  			if (pdata.CustomMode == 1 || pdata.CustomMode == 2 || pdata.CustomMode == 3)
  			{
  				bool flagChangedStats = false;
  
  				if (Properties.ALLOW_CUSTOMIZE_FACE_AFTER_CREATION)
  				{
  					character.EyeSize = (byte)pdata.EyeSize;
  					character.LipSize = (byte)pdata.LipSize;
  
gt: 					character.EyeColor = (byte)pdata.EyeColor;