Upgrade information that are combined in one meta data string called "WU".
示例#1
0
		public void WUExceptions()
		{
			var wu = new WUUpgrades();

			Assert.Throws(typeof(ArgumentOutOfRangeException), () => { wu.ChainCastSkillId = 1; });
			Assert.Throws(typeof(ArgumentOutOfRangeException), () => { wu.ChainCastLevel = 10; });
			Assert.DoesNotThrow(() => { wu.ChainCastSkillId = 10000; });
			Assert.DoesNotThrow(() => { wu.ChainCastLevel = 9; });
		}
示例#2
0
		public void WUModification()
		{
			var test1 = "12345603070809";
			var test2 = "65432723374859";
			var test3 = "100012fffefdfc";
			var test4 = "00000504030201";

			var wu = new WUUpgrades(test1);
			Assert.Equal(test1, wu.ToString());

			wu.ChainCastSkillId = 65432;
			wu.ChainCastLevel += 0x1;
			wu.ManaUse += 0x20;
			wu.ManaBurn += 0x30;
			wu.CastingSpeed += 0x40;
			wu.MagicDamage += 0x50;

			Assert.Equal(test2, wu.ToString());
			Assert.Equal(wu.ChainCastSkillId, 65432);
			Assert.Equal(wu.ChainCastLevel, 7);
			Assert.Equal(wu.ManaUse, 0x23);
			Assert.Equal(wu.ManaBurn, 0x37);
			Assert.Equal(wu.CastingSpeed, 0x48);
			Assert.Equal(wu.MagicDamage, 0x59);

			wu.ChainCastSkillId = 10001;
			wu.ChainCastLevel = 2;
			wu.ManaUse = -1;
			wu.ManaBurn = -2;
			wu.CastingSpeed = -3;
			wu.MagicDamage = -4;

			Assert.Equal(test3, wu.ToString());
			Assert.Equal(wu.ChainCastSkillId, 10001);
			Assert.Equal(wu.ChainCastLevel, 2);
			Assert.Equal(wu.ManaUse, -1);
			Assert.Equal(wu.ManaBurn, -2);
			Assert.Equal(wu.CastingSpeed, -3);
			Assert.Equal(wu.MagicDamage, -4);

			wu.ChainCastSkillId = 0;
			wu.ChainCastLevel = 5;
			wu.ManaUse = 4;
			wu.ManaBurn = 3;
			wu.CastingSpeed = 2;
			wu.MagicDamage = 1;

			Assert.Equal(test4, wu.ToString());
			Assert.Equal(wu.ChainCastSkillId, 0);
			Assert.Equal(wu.ChainCastLevel, 5);
			Assert.Equal(wu.ManaUse, 4);
			Assert.Equal(wu.ManaBurn, 3);
			Assert.Equal(wu.CastingSpeed, 2);
			Assert.Equal(wu.MagicDamage, 1);
		}
示例#3
0
		public void WUParsing()
		{
			var test = "12345603070809";
			var wu = new WUUpgrades(test);

			Assert.Equal(test, wu.ToString());
			Assert.Equal(wu.ChainCastSkillId, 12345);
			Assert.Equal(wu.ChainCastLevel, 6);
			Assert.Equal(wu.ManaUse, 3);
			Assert.Equal(wu.Unknown, 7);
			Assert.Equal(wu.CastingSpeed, 8);
			Assert.Equal(wu.MagicDamage, 9);
		}
示例#4
0
		public void WUSizeFix()
		{
			var test1_1 = "0504030201";
			var test1_2 = "00000504030201";
			var wu1 = new WUUpgrades(test1_1);

			Assert.Equal(test1_2, wu1.ToString());
			Assert.Equal(wu1.ChainCastSkillId, 0);
			Assert.Equal(wu1.ChainCastLevel, 5);
			Assert.Equal(wu1.ManaUse, 4);
			Assert.Equal(wu1.ManaBurn, 3);
			Assert.Equal(wu1.CastingSpeed, 2);
			Assert.Equal(wu1.MagicDamage, 1);
		}
示例#5
0
		public void WUParsing()
		{
			var test = "12345603070809";
			var wu = new WUUpgrades(test);

			Assert.Equal(test, wu.ToString());
			Assert.Equal(wu.ChainCastSkillId, 12345);
			Assert.Equal(wu.ChainCastLevel, 6);
			Assert.Equal(wu.ManaUse, 3);
			Assert.Equal(wu.ManaBurn, 7);
			Assert.Equal(wu.CastingSpeed, 8);
			Assert.Equal(wu.MagicDamage, 9);

			var test2 = "00006504030201";
			var wu2 = new WUUpgrades(test2);

			Assert.Equal(test2, wu2.ToString());
			Assert.Equal(wu2.ChainCastSkillId, 6);
			Assert.Equal(wu2.ChainCastLevel, 5);
			Assert.Equal(wu2.ManaUse, 4);
			Assert.Equal(wu2.ManaBurn, 3);
			Assert.Equal(wu2.CastingSpeed, 2);
			Assert.Equal(wu2.MagicDamage, 1);
		}
示例#6
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;
		}
示例#7
0
		/// <summary>
		/// Caches and applies WU upgrades for item.
		/// </summary>
		/// <param name="item"></param>
		private void HandleWUUpgrades(Item item)
		{
			// There's probably a better way to save these bonuses,
			// but I can't think of it right now.
			var wustr = item.MetaData1.GetString("WU");
			if (wustr != null)
			{
				var wu = new WUUpgrades(wustr);
				lock (_wuUpgrades)
					_wuUpgrades[item.EntityId] = wu;

				if (wu.MagicDamage != 0)
					_creature.StatMods.Add(Stat.MagicAttackMod, wu.MagicDamage, StatModSource.Equipment, item.EntityId);
			}
		}
示例#8
0
文件: NpcScript.cs 项目: Vinna/aura
		/// <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;

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