Information about upgrade process
Does not require HadGold or something, the client disables upgrades you can't use because of insufficient gold or prof.
Пример #1
0
		/// <summary>
		/// Tries to upgrade item specified in the reply.
		/// </summary>
		/// <param name="upgradeReply"></param>
		/// <returns></returns>
		/// <remarks>
		/// Only warn when something goes wrong because problems can be caused
		/// by replies unknown to us or an outdated database.
		/// 
		/// The NPCs don't have replies for failed upgrades, because the client
		/// disables invalid upgrades, you shouldn't be able to get a fail,
		/// unless you "hacked", modified client files, or Aura is outdated.
		/// </remarks>
		public UpgradeResult Upgrade(string upgradeReply)
		{
			var result = new UpgradeResult();

			// Example: @upgrade:22518872341757176:broad_sword_balance1
			var args = upgradeReply.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
			if (args.Length != 3 || !long.TryParse(args[1], out result.ItemEntityId))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0}' (Account: {1}) sent invalid reply.", this.Player.EntityIdHex, this.Player.Client.Account.Id);
				return result;
			}

			// Get item
			result.Item = this.Player.Inventory.GetItem(result.ItemEntityId);
			if (result.Item == null || result.Item.OptionInfo.Upgraded == result.Item.OptionInfo.UpgradeMax)
			{
				Log.Warning("NpcScript.Upgrade: Player '{0}' (Account: {1}) tried to upgrade invalid item.", this.Player.EntityIdHex, this.Player.Client.Account.Id);
				return result;
			}

			// Get upgrade and check item and NPCs
			result.Upgrade = AuraData.ItemUpgradesDb.Find(args[2]);
			if (result.Upgrade == null)
			{
				Log.Warning("NpcScript.Upgrade: Player '{0}' (Account: {1}) tried to apply an unknown upgrade ({2}).", this.Player.EntityIdHex, this.Player.Client.Account.Id, args[2]);
				return result;
			}

			// Check upgrade and item
			if (!result.Item.Data.HasTag(result.Upgrade.Filter) || result.Item.Proficiency < result.Upgrade.Exp || !Math2.Between(result.Item.OptionInfo.Upgraded, result.Upgrade.UpgradeMin, result.Upgrade.UpgradeMax))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0}' (Account: {1}) tried to apply upgrade to invalid item.", this.Player.EntityIdHex, this.Player.Client.Account.Id);
				return result;
			}
			if (!result.Upgrade.Npcs.Contains(this.NPC.Name.TrimStart('_').ToLower()))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0}' (Account: {1}) tried to apply upgrade '{2}' at an invalid NPC ({3}).", this.Player.EntityIdHex, this.Player.Client.Account.Id, result.Upgrade.Ident, this.NPC.Name.TrimStart('_').ToLower());
				return result;
			}

			// Check gold
			if (this.Gold < result.Upgrade.Gold)
				return result;

			// Take gold and exp
			result.Item.Proficiency -= result.Upgrade.Exp;
			this.Gold -= result.Upgrade.Gold;

			// Increase upgrade count
			result.Item.OptionInfo.Upgraded++;
			if (ChannelServer.Instance.Conf.World.UnlimitedUpgrades && result.Item.OptionInfo.Upgraded == result.Item.OptionInfo.UpgradeMax)
				result.Item.OptionInfo.Upgraded = 0;

			// Upgrade
			foreach (var effect in result.Upgrade.Effects)
			{
				switch (effect.Key)
				{
					case "MinAttack": result.Item.OptionInfo.AttackMin = (ushort)Math2.Clamp(1, result.Item.OptionInfo.AttackMax, result.Item.OptionInfo.AttackMin + effect.Value[0]); break;
					case "MaxAttack":
						result.Item.OptionInfo.AttackMax = (ushort)Math2.Clamp(1, ushort.MaxValue, result.Item.OptionInfo.AttackMax + effect.Value[0]);
						if (result.Item.OptionInfo.AttackMax < result.Item.OptionInfo.AttackMin)
							result.Item.OptionInfo.AttackMin = result.Item.OptionInfo.AttackMax;
						break;

					case "MinInjury": result.Item.OptionInfo.InjuryMin = (ushort)Math2.Clamp(0, result.Item.OptionInfo.InjuryMax, result.Item.OptionInfo.InjuryMin + effect.Value[0]); break;
					case "MaxInjury":
						result.Item.OptionInfo.InjuryMax = (ushort)Math2.Clamp(0, ushort.MaxValue, result.Item.OptionInfo.InjuryMax + effect.Value[0]);
						if (result.Item.OptionInfo.InjuryMax < result.Item.OptionInfo.InjuryMin)
							result.Item.OptionInfo.InjuryMin = result.Item.OptionInfo.InjuryMax;
						break;

					case "Balance": result.Item.OptionInfo.Balance = (byte)Math2.Clamp(0, byte.MaxValue, result.Item.OptionInfo.Balance + effect.Value[0]); break;
					case "Critical": result.Item.OptionInfo.Critical = (sbyte)Math2.Clamp(0, sbyte.MaxValue, result.Item.OptionInfo.Critical + effect.Value[0]); break;
					case "Defense": result.Item.OptionInfo.Defense = (int)Math2.Clamp(0, int.MaxValue, result.Item.OptionInfo.Defense + (long)effect.Value[0]); break;
					case "Protection": result.Item.OptionInfo.Protection = (short)Math2.Clamp(0, short.MaxValue, result.Item.OptionInfo.Protection + effect.Value[0]); break;
					case "AttackRange": result.Item.OptionInfo.EffectiveRange = (short)Math2.Clamp(0, short.MaxValue, result.Item.OptionInfo.EffectiveRange + effect.Value[0]); break;

					case "MaxDurability":
						result.Item.OptionInfo.DurabilityMax = (int)Math2.Clamp(1000, int.MaxValue, result.Item.OptionInfo.DurabilityMax + (long)(effect.Value[0] * 1000));
						if (result.Item.OptionInfo.DurabilityMax < result.Item.OptionInfo.Durability)
							result.Item.OptionInfo.Durability = result.Item.OptionInfo.DurabilityMax;
						break;

					case "MagicDefense":
						// MDEF:f:1.000000;MPROT:f:1.000000;MTWR:1:1;
						var mdef = result.Item.MetaData1.GetFloat("MDEF");
						result.Item.MetaData1.SetFloat("MDEF", Math2.Clamp(0, int.MaxValue, mdef + effect.Value[0]));
						break;

					case "MagicProtection":
						// MDEF:f:1.000000;MPROT:f:1.000000;MTWR:1:1;
						var mprot = result.Item.MetaData1.GetFloat("MPROT");
						result.Item.MetaData1.SetFloat("MPROT", Math2.Clamp(0, int.MaxValue, mprot + effect.Value[0]));
						break;

					// TODO:
					// - CollectionSpeed
					// - CollectionBonus
					// - SplashRadius
					// - ManaUse
					// - ManaBurn
					// - MagicDamage
					// - CastingSpeed
					// - LancePiercing
					// - MusicBuffBonus
					// - MusicBuffDuration
					// - MaxBullets
					// - Artisan
					// - ChainCast

					default:
						Log.Unimplemented("Item upgrade '{0}'", effect.Key);
						break;
				}
			}

			// Update item
			Send.ItemUpdate(this.Player, result.Item);

			// Send result
			Send.ItemUpgradeResult(this.Player, result.Item, result.Upgrade.Ident);

			result.Success = true;

			return result;
		}
Пример #2
0
		/// <summary>
		/// Tries to upgrade item specified in the reply.
		/// </summary>
		/// <param name="upgradeReply"></param>
		/// <returns></returns>
		/// <remarks>
		/// Only warn when something goes wrong, because problems can be caused
		/// by replies unknown to us or an outdated database.
		/// 
		/// The NPCs don't have replies for failed upgrades, because the client
		/// disables invalid upgrades, you shouldn't be able to get a fail,
		/// unless you "hacked", modified client files, or Aura is outdated.
		/// </remarks>
		public UpgradeResult Upgrade(string upgradeReply)
		{
			var result = new UpgradeResult();

			// Example: @upgrade:22518872341757176:broad_sword_balance1
			var args = upgradeReply.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
			if (args.Length != 3 || !long.TryParse(args[1], out result.ItemEntityId))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) sent invalid reply.", this.Player.EntityId, this.Player.Client.Account.Id);
				return result;
			}

			// Get item
			result.Item = this.Player.Inventory.GetItem(result.ItemEntityId);
			if (result.Item == null || result.Item.OptionInfo.Upgraded == result.Item.OptionInfo.UpgradeMax)
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) tried to upgrade invalid item.", this.Player.EntityId, this.Player.Client.Account.Id);
				return result;
			}

			// Get upgrade and check item and NPCs
			result.Upgrade = AuraData.ItemUpgradesDb.Find(args[2]);
			if (result.Upgrade == null)
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) tried to apply an unknown upgrade ({2}).", this.Player.EntityId, this.Player.Client.Account.Id, args[2]);
				return result;
			}

			// Check upgrade and item
			if (!result.Item.Data.HasTag(result.Upgrade.Filter) || result.Item.Proficiency < result.Upgrade.Exp || !Math2.Between(result.Item.OptionInfo.Upgraded, result.Upgrade.UpgradeMin, result.Upgrade.UpgradeMax))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) tried to apply upgrade to invalid item.", this.Player.EntityId, this.Player.Client.Account.Id);
				return result;
			}
			if (!result.Upgrade.Npcs.Contains(this.NPC.Name.TrimStart('_').ToLower()))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) tried to apply upgrade '{2}' at an invalid NPC ({3}).", this.Player.EntityId, this.Player.Client.Account.Id, result.Upgrade.Ident, this.NPC.Name.TrimStart('_').ToLower());
				return result;
			}

			// Check for disabled Artisan
			// TODO: Feature check, once we do have Artisan.
			if (result.Upgrade.Effects.Any(a => a.Key == "Artisan"))
			{
				Send.MsgBox(this.Player, Localization.Get("Artisan upgrades aren't available yet."));
				return result;
			}

			// Check gold
			if (this.Gold < result.Upgrade.Gold)
				return result;

			// Take gold and exp
			result.Item.Proficiency -= result.Upgrade.Exp;
			this.Gold -= result.Upgrade.Gold;

			// Increase upgrade count
			result.Item.OptionInfo.Upgraded++;
			if (ChannelServer.Instance.Conf.World.UnlimitedUpgrades && result.Item.OptionInfo.Upgraded == result.Item.OptionInfo.UpgradeMax)
				result.Item.OptionInfo.Upgraded = 0;

			// Upgrade
			foreach (var effect in result.Upgrade.Effects)
			{
				switch (effect.Key)
				{
					case "MinAttack": result.Item.OptionInfo.AttackMin = (ushort)Math2.Clamp(1, result.Item.OptionInfo.AttackMax, result.Item.OptionInfo.AttackMin + effect.Value[0]); break;
					case "MaxAttack":
						result.Item.OptionInfo.AttackMax = (ushort)Math2.Clamp(1, ushort.MaxValue, result.Item.OptionInfo.AttackMax + effect.Value[0]);
						if (result.Item.OptionInfo.AttackMax < result.Item.OptionInfo.AttackMin)
							result.Item.OptionInfo.AttackMin = result.Item.OptionInfo.AttackMax;
						break;

					case "MinInjury": result.Item.OptionInfo.InjuryMin = (ushort)Math2.Clamp(0, result.Item.OptionInfo.InjuryMax, result.Item.OptionInfo.InjuryMin + effect.Value[0]); break;
					case "MaxInjury":
						result.Item.OptionInfo.InjuryMax = (ushort)Math2.Clamp(0, ushort.MaxValue, result.Item.OptionInfo.InjuryMax + effect.Value[0]);
						if (result.Item.OptionInfo.InjuryMax < result.Item.OptionInfo.InjuryMin)
							result.Item.OptionInfo.InjuryMin = result.Item.OptionInfo.InjuryMax;
						break;

					case "Balance": result.Item.OptionInfo.Balance = (byte)Math2.Clamp(0, byte.MaxValue, result.Item.OptionInfo.Balance + effect.Value[0]); break;
					case "Critical": result.Item.OptionInfo.Critical = (sbyte)Math2.Clamp(0, sbyte.MaxValue, result.Item.OptionInfo.Critical + effect.Value[0]); break;
					case "Defense": result.Item.OptionInfo.Defense = (int)Math2.Clamp(0, int.MaxValue, result.Item.OptionInfo.Defense + (long)effect.Value[0]); break;
					case "Protection": result.Item.OptionInfo.Protection = (short)Math2.Clamp(0, short.MaxValue, result.Item.OptionInfo.Protection + effect.Value[0]); break;
					case "AttackRange": result.Item.OptionInfo.EffectiveRange = (short)Math2.Clamp(0, short.MaxValue, result.Item.OptionInfo.EffectiveRange + effect.Value[0]); break;

					case "MaxDurability":
						result.Item.OptionInfo.DurabilityMax = (int)Math2.Clamp(1000, int.MaxValue, result.Item.OptionInfo.DurabilityMax + (long)(effect.Value[0] * 1000));
						if (result.Item.OptionInfo.DurabilityMax < result.Item.OptionInfo.Durability)
							result.Item.OptionInfo.Durability = result.Item.OptionInfo.DurabilityMax;
						break;

					case "MagicDefense":
						// MDEF:f:1.000000;MPROT:f:1.000000;MTWR:1:1;
						var mdef = result.Item.MetaData1.GetFloat("MDEF");
						result.Item.MetaData1.SetFloat("MDEF", Math2.Clamp(0, int.MaxValue, mdef + effect.Value[0]));
						break;

					case "MagicProtection":
						// MDEF:f:1.000000;MPROT:f:1.000000;MTWR:1:1;
						var mprot = result.Item.MetaData1.GetFloat("MPROT");
						result.Item.MetaData1.SetFloat("MPROT", Math2.Clamp(0, int.MaxValue, mprot + effect.Value[0]));
						break;

					case "ManaUse":
						// WU:s:00000003000000
						var manaUseWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));
						manaUseWU.ManaUse += (sbyte)effect.Value[0];
						result.Item.MetaData1.SetString("WU", manaUseWU.ToString());
						break;

					case "ManaBurn":
						var manaBurnWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));

						// Prior to G15S2 players lost all their Mana when
						// they unequipped a wand. This was removed via
						// feature, but before that this upgrade allowed
						// one to reduce the amount of Mana lost.
						// Afterwards the ManaBurn upgrade was turned into
						// a ManaUse automatically, but the bonus was halfed,
						// meaning if a ManaBurn upgrade gave -4% burn,
						// it gave -2% use after this update.
						if (!this.IsEnabled("ManaBurnRemove"))
							manaBurnWU.ManaBurn += (sbyte)effect.Value[0];
						else
							manaBurnWU.ManaUse += (sbyte)(effect.Value[0] / 2);

						result.Item.MetaData1.SetString("WU", manaBurnWU.ToString());
						break;

					case "ChainCasting":
						// Chain Casting: +4, Magic Attack: +21
						// EHLV:4:5;MTWR:1:1;OWNER:s:username;WU:s:30201400000015;
						var chainCastWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));
						chainCastWU.ChainCastSkillId = (ushort)effect.Value[0];
						chainCastWU.ChainCastLevel = (byte)effect.Value[1];
						result.Item.MetaData1.SetString("WU", chainCastWU.ToString());
						break;

					case "MagicDamage":
						// Charging Speed: +12%, MA: +16
						// EHLV:4:5;MTWR:1:1;OWNER:s:username;WU:s:00000000000c10;
						var magicDmgWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));
						magicDmgWU.MagicDamage += (sbyte)effect.Value[0];
						result.Item.MetaData1.SetString("WU", magicDmgWU.ToString());
						break;

					case "CastingSpeed":
						// Charging Speed: +12%, MA: +16
						// EHLV:4:5;MTWR:1:1;OWNER:s:username;WU:s:00000000000c10;
						var castingSpeedWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));
						castingSpeedWU.CastingSpeed += (sbyte)effect.Value[0];
						result.Item.MetaData1.SetString("WU", castingSpeedWU.ToString());
						break;

					case "MusicBuffBonus":
						// MBB:4:8;MBD:4:10;MTWR:1:2;OTU:1:1;SPTEC:1:1;
						var musicBuff = result.Item.MetaData1.GetInt("MBB");
						result.Item.MetaData1.SetInt("MBB", musicBuff + (int)effect.Value[0]);
						break;

					case "MusicBuffDuration":
						// MBB:4:8;MBD:4:10;MTWR:1:2;OTU:1:1;SPTEC:1:1;
						var musicBuffDur = result.Item.MetaData1.GetInt("MBD");
						result.Item.MetaData1.SetInt("MBD", musicBuffDur + (int)effect.Value[0]);
						break;

					case "CollectionBonus":
						// CTBONUS:2:40;CTSPEED:4:750;MTWR:1:1;
						var collectionBonusBuff = result.Item.MetaData1.GetShort("CTBONUS");
						result.Item.MetaData1.SetShort("CTBONUS", (short)(collectionBonusBuff + effect.Value[0]));
						break;

					case "CollectionSpeed":
						// CTBONUS:2:40;CTSPEED:4:750;MTWR:1:1;
						var collectionSpeedBuff = result.Item.MetaData1.GetInt("CTSPEED");
						result.Item.MetaData1.SetInt("CTSPEED", collectionSpeedBuff + (int)effect.Value[0]);
						break;

					case "LancePiercing":
						// EHLV:4:5;LKUP:8:262244;LP:1:4;LP_E:1:0;OWNER:s:character;SPTRP:1:1;   << Piercing Level 4
						// LP:1:1;QUAL:4:70;   << Piercing Level 1
						var lancePiercingBuff = result.Item.MetaData1.GetByte("LP");
						result.Item.MetaData1.SetByte("LP", (byte)(lancePiercingBuff + effect.Value[0]));
						break;

					case "SplashRadius":
						// SP_DMG:f:0.250000;SP_RAD:4:70;
						var splashRadiusBuff = result.Item.MetaData1.GetInt("SP_RAD");
						result.Item.MetaData1.SetInt("SP_RAD", splashRadiusBuff + (int)effect.Value[0]);
						break;

					case "SplashDamage":
						// SP_DMG:f:0.250000;SP_RAD:4:70;
						var splashDamageBuff = result.Item.MetaData1.GetFloat("SP_DMG");
						result.Item.MetaData1.SetFloat("SP_DMG", splashDamageBuff + effect.Value[0]);
						break;

					case "ImmuneMelee":
						// IM_MGC:f:0.050000;IM_MLE:f:0.050000;IM_RNG:f:0.050000;MDEF:f:2.000000;MPROT:f:3.000000;OTU:1:1;
						var immuneMeleeBuff = result.Item.MetaData1.GetFloat("IM_MLE");
						result.Item.MetaData1.SetFloat("IM_MLE", immuneMeleeBuff + effect.Value[0]);
						break;

					case "ImmuneRanged":
						// IM_MGC:f:0.050000;IM_MLE:f:0.050000;IM_RNG:f:0.050000;MDEF:f:2.000000;MPROT:f:3.000000;OTU:1:1;
						var immuneRangedBuff = result.Item.MetaData1.GetFloat("IM_RNG");
						result.Item.MetaData1.SetFloat("IM_RNG", immuneRangedBuff + effect.Value[0]);
						break;

					case "ImmuneMagic":
						// IM_MGC:f:0.050000;IM_MLE:f:0.050000;IM_RNG:f:0.050000;MDEF:f:2.000000;MPROT:f:3.000000;OTU:1:1;
						var immuneMagicBuff = result.Item.MetaData1.GetFloat("IM_MGC");
						result.Item.MetaData1.SetFloat("IM_MGC", immuneMagicBuff + effect.Value[0]);
						break;

					// TODO:
					// - MaxBullets
					// - Artisan

					default:
						Log.Unimplemented("Item upgrade '{0}'", effect.Key);
						break;
				}
			}

			// Personalization
			if (result.Upgrade.Personalize)
			{
				result.Item.OptionInfo.Flags |= ItemFlags.Personalized;
				result.Item.MetaData1.SetString("OWNER", this.Player.Name);
			}

			// Update item
			Send.ItemUpdate(this.Player, result.Item);

			// Send result
			Send.ItemUpgradeResult(this.Player, result.Item, result.Upgrade.Ident);

			result.Success = true;

			this.Player.Keywords.Give("ExperienceUpgrade");

			return result;
		}
Пример #3
0
		/// <summary>
		/// Tries to upgrade item specified in the reply.
		/// </summary>
		/// <param name="upgradeReply"></param>
		/// <returns></returns>
		/// <remarks>
		/// Only warn when something goes wrong, because problems can be caused
		/// by replies unknown to us or an outdated database.
		/// 
		/// The NPCs don't have replies for failed upgrades, because the client
		/// disables invalid upgrades, you shouldn't be able to get a fail,
		/// unless you "hacked", modified client files, or Aura is outdated.
		/// </remarks>
		public UpgradeResult Upgrade(string upgradeReply)
		{
			var result = new UpgradeResult();

			// Example: @upgrade:22518872341757176:broad_sword_balance1
			var args = upgradeReply.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
			if (args.Length != 3 || !long.TryParse(args[1], out result.ItemEntityId))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) sent invalid reply.", this.Player.EntityId, this.Player.Client.Account.Id);
				return result;
			}

			// Get item
			result.Item = this.Player.Inventory.GetItem(result.ItemEntityId);
			if (result.Item == null || result.Item.OptionInfo.Upgraded == result.Item.OptionInfo.UpgradeMax)
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) tried to upgrade invalid item.", this.Player.EntityId, this.Player.Client.Account.Id);
				return result;
			}

			// Get upgrade and check item and NPCs
			result.Upgrade = AuraData.ItemUpgradesDb.Find(args[2]);
			if (result.Upgrade == null)
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) tried to apply an unknown upgrade ({2}).", this.Player.EntityId, this.Player.Client.Account.Id, args[2]);
				return result;
			}

			// Check upgrade and item
			if (!result.Item.Data.HasTag(result.Upgrade.Filter) || result.Item.Proficiency < result.Upgrade.Exp || !Math2.Between(result.Item.OptionInfo.Upgraded, result.Upgrade.UpgradeMin, result.Upgrade.UpgradeMax))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) tried to apply upgrade to invalid item.", this.Player.EntityId, this.Player.Client.Account.Id);
				return result;
			}
			if (!result.Upgrade.Npcs.Contains(this.NPC.Name.TrimStart('_').ToLower()))
			{
				Log.Warning("NpcScript.Upgrade: Player '{0:X16}' (Account: {1}) tried to apply upgrade '{2}' at an invalid NPC ({3}).", this.Player.EntityId, this.Player.Client.Account.Id, result.Upgrade.Ident, this.NPC.Name.TrimStart('_').ToLower());
				return result;
			}

			// Check for disabled Artisan
			// TODO: Feature check, once we do have Artisan.
			if (result.Upgrade.Effects.Any(a => a.Key == "Artisan"))
			{
				Send.MsgBox(this.Player, Localization.Get("Artisan upgrades aren't available yet."));
				return result;
			}

			// Check gold
			if (this.Gold < result.Upgrade.Gold)
				return result;

			// Take gold and exp
			result.Item.Proficiency -= result.Upgrade.Exp;
			this.Gold -= result.Upgrade.Gold;

			// Increase upgrade count
			result.Item.OptionInfo.Upgraded++;
			if (ChannelServer.Instance.Conf.World.UnlimitedUpgrades && result.Item.OptionInfo.Upgraded == result.Item.OptionInfo.UpgradeMax)
				result.Item.OptionInfo.Upgraded = 0;

			// Upgrade
			foreach (var effect in result.Upgrade.Effects)
			{
				switch (effect.Key)
				{
					case "MinAttack": result.Item.OptionInfo.AttackMin = (ushort)Math2.Clamp(1, result.Item.OptionInfo.AttackMax, result.Item.OptionInfo.AttackMin + effect.Value[0]); break;
					case "MaxAttack":
						result.Item.OptionInfo.AttackMax = (ushort)Math2.Clamp(1, ushort.MaxValue, result.Item.OptionInfo.AttackMax + effect.Value[0]);
						if (result.Item.OptionInfo.AttackMax < result.Item.OptionInfo.AttackMin)
							result.Item.OptionInfo.AttackMin = result.Item.OptionInfo.AttackMax;
						break;

					case "MinInjury": result.Item.OptionInfo.InjuryMin = (ushort)Math2.Clamp(0, result.Item.OptionInfo.InjuryMax, result.Item.OptionInfo.InjuryMin + effect.Value[0]); break;
					case "MaxInjury":
						result.Item.OptionInfo.InjuryMax = (ushort)Math2.Clamp(0, ushort.MaxValue, result.Item.OptionInfo.InjuryMax + effect.Value[0]);
						if (result.Item.OptionInfo.InjuryMax < result.Item.OptionInfo.InjuryMin)
							result.Item.OptionInfo.InjuryMin = result.Item.OptionInfo.InjuryMax;
						break;

					case "Balance": result.Item.OptionInfo.Balance = (byte)Math2.Clamp(0, byte.MaxValue, result.Item.OptionInfo.Balance + effect.Value[0]); break;
					case "Critical": result.Item.OptionInfo.Critical = (sbyte)Math2.Clamp(0, sbyte.MaxValue, result.Item.OptionInfo.Critical + effect.Value[0]); break;
					case "Defense": result.Item.OptionInfo.Defense = (int)Math2.Clamp(0, int.MaxValue, result.Item.OptionInfo.Defense + (long)effect.Value[0]); break;
					case "Protection": result.Item.OptionInfo.Protection = (short)Math2.Clamp(0, short.MaxValue, result.Item.OptionInfo.Protection + effect.Value[0]); break;
					case "AttackRange": result.Item.OptionInfo.EffectiveRange = (short)Math2.Clamp(0, short.MaxValue, result.Item.OptionInfo.EffectiveRange + effect.Value[0]); break;

					case "MaxDurability":
						result.Item.OptionInfo.DurabilityMax = (int)Math2.Clamp(1000, int.MaxValue, result.Item.OptionInfo.DurabilityMax + (long)(effect.Value[0] * 1000));
						if (result.Item.OptionInfo.DurabilityMax < result.Item.OptionInfo.Durability)
							result.Item.OptionInfo.Durability = result.Item.OptionInfo.DurabilityMax;
						break;

					case "MagicDefense":
						// MDEF:f:1.000000;MPROT:f:1.000000;MTWR:1:1;
						var mdef = result.Item.MetaData1.GetFloat("MDEF");
						result.Item.MetaData1.SetFloat("MDEF", Math2.Clamp(0, int.MaxValue, mdef + effect.Value[0]));
						break;

					case "MagicProtection":
						// MDEF:f:1.000000;MPROT:f:1.000000;MTWR:1:1;
						var mprot = result.Item.MetaData1.GetFloat("MPROT");
						result.Item.MetaData1.SetFloat("MPROT", Math2.Clamp(0, int.MaxValue, mprot + effect.Value[0]));
						break;

					case "ManaUse":
						// WU:s:00000003000000
						var manaUseWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));
						manaUseWU.ManaUse += (sbyte)effect.Value[0];
						result.Item.MetaData1.SetString("WU", manaUseWU.ToString());
						break;

					case "ChainCast":
						// Chain Casting: +4, Magic Attack: +21
						// EHLV:4:5;MTWR:1:1;OWNER:s:username;WU:s:30201400000015;
						var chainCastWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));
						chainCastWU.ChainCastSkillId = (ushort)effect.Value[0];
						chainCastWU.ChainCastLevel = (byte)effect.Value[1];
						result.Item.MetaData1.SetString("WU", chainCastWU.ToString());
						break;

					case "MagicDamage":
						// Charging Speed: +12%, MA: +16
						// EHLV:4:5;MTWR:1:1;OWNER:s:username;WU:s:00000000000c10;
						var magicDmgWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));
						magicDmgWU.MagicDamage += (sbyte)effect.Value[0];
						result.Item.MetaData1.SetString("WU", magicDmgWU.ToString());
						break;

					case "CastingSpeed":
						// Charging Speed: +12%, MA: +16
						// EHLV:4:5;MTWR:1:1;OWNER:s:username;WU:s:00000000000c10;
						var castingSpeedWU = new WUUpgrades(result.Item.MetaData1.GetString("WU"));
						castingSpeedWU.CastingSpeed += (sbyte)effect.Value[0];
						result.Item.MetaData1.SetString("WU", castingSpeedWU.ToString());
						break;

					case "MusicBuffBonus":
						// MBB:4:8;MBD:4:10;MTWR:1:2;OTU:1:1;SPTEC:1:1;
						var musicBuff = result.Item.MetaData1.GetInt("MBB");
						result.Item.MetaData1.SetInt("MBB", musicBuff + effect.Value[0]);
						break;

					case "MusicBuffDuration":
						// MBB:4:8;MBD:4:10;MTWR:1:2;OTU:1:1;SPTEC:1:1;
						var musicBuffDur = result.Item.MetaData1.GetInt("MBD");
						result.Item.MetaData1.SetInt("MBD", musicBuffDur + effect.Value[0]);
						break;

					// TODO:
					// - CollectionSpeed
					// - CollectionBonus
					// - SplashRadius
					// - ManaBurn
					// - LancePiercing
					// - MaxBullets
					// - Artisan

					default:
						Log.Unimplemented("Item upgrade '{0}'", effect.Key);
						break;
				}
			}

			// Personalization
			if (result.Upgrade.Personalize)
			{
				result.Item.OptionInfo.Flags |= ItemFlags.Personalized;
				result.Item.MetaData1.SetString("OWNER", this.Player.Name);
			}

			// Update item
			Send.ItemUpdate(this.Player, result.Item);

			// Send result
			Send.ItemUpgradeResult(this.Player, result.Item, result.Upgrade.Ident);

			result.Success = true;

			return result;
		}