private static void HandleCreateItemRecipe(Player player, WorldObject source, WorldObject target, Recipe recipe) { ActionChain craftChain = new ActionChain(); CreatureSkill skill = null; bool skillSuccess = true; // assume success, unless there's a skill check double percentSuccess = 1; UniversalMotion motion = new UniversalMotion(MotionStance.Standing, new MotionItem(MotionCommand.ClapHands)); craftChain.AddAction(player, () => player.HandleActionMotion(motion)); float craftAnimationLength = MotionTable.GetAnimationLength((uint)player.MotionTableId, MotionCommand.ClapHands); craftChain.AddDelaySeconds(craftAnimationLength); // craftChain.AddDelaySeconds(0.5); craftChain.AddAction(player, () => { if (recipe.SkillId != null && recipe.SkillDifficulty != null) { // there's a skill associated with this Skill skillId = (Skill)recipe.SkillId.Value; // this shouldn't happen, but sanity check for unexpected nulls if (!player.Skills.ContainsKey(skillId)) { log.Warn("Unexpectedly missing skill in Recipe usage"); player.SendUseDoneEvent(); return; } skill = player.Skills[skillId]; percentSuccess = skill.GetPercentSuccess(recipe.SkillDifficulty.Value); } // straight skill check, if applciable if (skill != null) { skillSuccess = _random.NextDouble() < percentSuccess; } if ((recipe.ResultFlags & (uint)RecipeResult.SourceItemDestroyed) > 0) { player.DestroyInventoryItem(source); } if ((recipe.ResultFlags & (uint)RecipeResult.TargetItemDestroyed) > 0) { player.DestroyInventoryItem(target); } if ((recipe.ResultFlags & (uint)RecipeResult.SourceItemUsesDecrement) > 0) { if (source.Structure <= 1) { player.DestroyInventoryItem(source); } else { source.Structure--; source.SendPartialUpdates(player.Session, _updateStructure); } } if ((recipe.ResultFlags & (uint)RecipeResult.TargetItemUsesDecrement) > 0) { if (target.Structure <= 1) { player.DestroyInventoryItem(target); } else { target.Structure--; target.SendPartialUpdates(player.Session, _updateStructure); } } if (skillSuccess) { WorldObject newObject1 = null; WorldObject newObject2 = null; if ((recipe.ResultFlags & (uint)RecipeResult.SuccessItem1) > 0 && recipe.SuccessItem1Wcid != null) { newObject1 = player.AddNewItemToInventory(recipe.SuccessItem1Wcid.Value); } if ((recipe.ResultFlags & (uint)RecipeResult.SuccessItem2) > 0 && recipe.SuccessItem2Wcid != null) { newObject2 = player.AddNewItemToInventory(recipe.SuccessItem2Wcid.Value); } var text = string.Format(recipe.SuccessMessage, source.Name, target.Name, newObject1?.Name, newObject2?.Name); var message = new GameMessageSystemChat(text, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); } else { WorldObject newObject1 = null; WorldObject newObject2 = null; if ((recipe.ResultFlags & (uint)RecipeResult.FailureItem1) > 0 && recipe.FailureItem1Wcid != null) { newObject1 = player.AddNewItemToInventory(recipe.FailureItem1Wcid.Value); } if ((recipe.ResultFlags & (uint)RecipeResult.FailureItem2) > 0 && recipe.FailureItem2Wcid != null) { newObject2 = player.AddNewItemToInventory(recipe.FailureItem2Wcid.Value); } var text = string.Format(recipe.FailMessage, source.Name, target.Name, newObject1?.Name, newObject2?.Name); var message = new GameMessageSystemChat(text, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); } player.SendUseDoneEvent(); }); craftChain.EnqueueChain(); }
private static void HandleHealingRecipe(Player player, WorldObject source, WorldObject target, Recipe recipe) { ActionChain chain = new ActionChain(); // skill will be null since the difficulty is calculated manually if (recipe.SkillId == null) { log.Warn($"healing recipe has null skill id (should almost certainly be healing, but who knows). recipe id {recipe.RecipeId}."); player.SendUseDoneEvent(); return; } if (!(target is Player)) { var message = new GameMessageSystemChat($"The {source.Name} cannot be used on {target.Name}.", ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(); return; } Player targetPlayer = target as Player; Ability vital = (Ability?)recipe.HealingAttribute ?? Ability.Health; // there's a skill associated with this Skill skillId = (Skill)recipe.SkillId.Value; // this shouldn't happen, but sanity check for unexpected nulls if (!player.Skills.ContainsKey(skillId)) { log.Warn("Unexpectedly missing skill in Recipe usage"); player.SendUseDoneEvent(); return; } CreatureSkill skill = player.Skills[skillId]; // at this point, we've validated that the target is a player, and the target is below max health if (target.Guid != player.Guid) { // TODO: validate range } MotionCommand cmd = MotionCommand.SkillHealSelf; if (target.Guid != player.Guid) { cmd = MotionCommand.Woah; // guess? nothing else stood out } // everything pre-validatable is validated. action will be attempted unless cancelled, so // queue up the animation and action UniversalMotion motion = new UniversalMotion(MotionStance.Standing, new MotionItem(cmd)); chain.AddAction(player, () => player.HandleActionMotion(motion)); chain.AddDelaySeconds(0.5); chain.AddAction(player, () => { // TODO: revalidate range if other player (they could have moved) double difficulty = 2 * (targetPlayer.Vitals[vital].MaxValue - targetPlayer.Vitals[vital].Current); if (difficulty <= 0) { // target is at max (or higher?) health, do nothing var text = "You are already at full health."; if (target.Guid != player.Guid) { text = $"{target.Name} is already at full health"; } player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Craft)); player.SendUseDoneEvent(); return; } if (player.CombatMode != CombatMode.NonCombat && player.CombatMode != CombatMode.Undef) { difficulty *= 1.1; } uint boost = source.Boost ?? 0; double multiplier = source.HealkitMod ?? 1; double playerSkill = skill.ActiveValue + boost; if (skill.Status == SkillStatus.Trained) { playerSkill *= 1.1; } else if (skill.Status == SkillStatus.Specialized) { playerSkill *= 1.5; } // usage is inevitable at this point, consume the use if ((recipe.ResultFlags & (uint)RecipeResult.SourceItemUsesDecrement) > 0) { if (source.Structure <= 1) { player.DestroyInventoryItem(source); } else { source.Structure--; source.SendPartialUpdates(player.Session, _updateStructure); } } double percentSuccess = CreatureSkill.GetPercentSuccess((uint)playerSkill, (uint)difficulty); if (_random.NextDouble() <= percentSuccess) { string expertly = ""; if (_random.NextDouble() < 0.1d) { expertly = "expertly "; multiplier *= 1.2; } // calculate amount restored uint maxRestore = targetPlayer.Vitals[vital].MaxValue - targetPlayer.Vitals[vital].Current; // TODO: get actual forumula for healing. this is COMPLETELY wrong. this is 60 + random(1-60). double amountRestored = 60d + _random.Next(1, 61); amountRestored *= multiplier; uint actualRestored = (uint)Math.Min(maxRestore, amountRestored); targetPlayer.Vitals[vital].Current += actualRestored; var updateVital = new GameMessagePrivateUpdateAttribute2ndLevel(player.Session, vital.GetVital(), targetPlayer.Vitals[vital].Current); player.Session.Network.EnqueueSend(updateVital); if (targetPlayer.Guid != player.Guid) { // tell the other player they got healed var updateVitalToTarget = new GameMessagePrivateUpdateAttribute2ndLevel(targetPlayer.Session, vital.GetVital(), targetPlayer.Vitals[vital].Current); targetPlayer.Session.Network.EnqueueSend(updateVitalToTarget); } string name = "yourself"; if (targetPlayer.Guid != player.Guid) { name = targetPlayer.Name; } string vitalName = "Health"; if (vital == Ability.Stamina) { vitalName = "Stamina"; } else if (vital == Ability.Mana) { vitalName = "Mana"; } string uses = source.Structure == 1 ? "use" : "uses"; var text = string.Format(recipe.SuccessMessage, expertly, name, actualRestored, vitalName, source.Name, source.Structure, uses); var message = new GameMessageSystemChat(text, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); if (targetPlayer.Guid != player.Guid) { // send text to the other player too text = string.Format(recipe.AlternateMessage, player.Name, expertly, actualRestored, vitalName); message = new GameMessageSystemChat(text, ChatMessageType.Craft); targetPlayer.Session.Network.EnqueueSend(message); } } player.SendUseDoneEvent(); }); chain.EnqueueChain(); }
public static void UseObjectOnTarget(Player player, WorldObject source, WorldObject target) { var recipe = DatabaseManager.World.GetCachedCookbook(source.WeenieClassId, target.WeenieClassId); if (recipe == null) { var message = new GameMessageSystemChat($"The {source.Name} cannot be used on the {target.Name}.", ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(); return; } ActionChain craftChain = new ActionChain(); CreatureSkill skill = null; bool skillSuccess = true; // assume success, unless there's a skill check double percentSuccess = 1; UniversalMotion motion = new UniversalMotion(MotionStance.Standing, new MotionItem(MotionCommand.ClapHands)); craftChain.AddAction(player, () => player.HandleActionMotion(motion)); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(player.MotionTableId); var craftAnimationLength = motionTable.GetAnimationLength(MotionCommand.ClapHands); craftChain.AddDelaySeconds(craftAnimationLength); craftChain.AddAction(player, () => { if (recipe.Recipe.Skill > 0 && recipe.Recipe.Difficulty > 0) { // there's a skill associated with this Skill skillId = (Skill)recipe.Recipe.Skill; // this shouldn't happen, but sanity check for unexpected nulls skill = player.GetCreatureSkill(skillId); if (skill == null) { log.Warn("Unexpectedly missing skill in Recipe usage"); player.SendUseDoneEvent(); return; } percentSuccess = skill.GetPercentSuccess(recipe.Recipe.Difficulty); //FIXME: Pretty certain this is broken } if (skill != null) { if (skill.Status == SkillStatus.Untrained) { var message = new GameEventWeenieError(player.Session, WeenieError.YouAreNotTrainedInThatTradeSkill); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(WeenieError.YouAreNotTrainedInThatTradeSkill); return; } } // straight skill check, if applicable if (skill != null) { skillSuccess = _random.NextDouble() < percentSuccess; } var components = recipe.Recipe.RecipeComponent.ToList(); if (skillSuccess) { var targetSuccess = components[0]; var sourceSuccess = components[1]; bool destroyTarget = _random.NextDouble() < targetSuccess.DestroyChance; bool destroySource = _random.NextDouble() < sourceSuccess.DestroyChance; if (destroyTarget) { if (target.OwnerId == player.Guid.Full) { player.TryRemoveItemFromInventoryWithNetworking(target, (ushort)targetSuccess.DestroyAmount); } else { target.Destroy(); } if (targetSuccess.DestroyMessage != "") { var destroyMessage = new GameMessageSystemChat(targetSuccess.DestroyMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (destroySource) { if (source.OwnerId == player.Guid.Full) { player.TryRemoveItemFromInventoryWithNetworking(source, (ushort)sourceSuccess.DestroyAmount); } else { source.Destroy(); } if (sourceSuccess.DestroyMessage != "") { var destroyMessage = new GameMessageSystemChat(sourceSuccess.DestroyMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (recipe.Recipe.SuccessWCID > 0) { var wo = WorldObjectFactory.CreateNewWorldObject(recipe.Recipe.SuccessWCID); if (wo != null) { if (recipe.Recipe.SuccessAmount > 1) { wo.StackSize = (ushort)recipe.Recipe.SuccessAmount; } player.TryCreateInInventoryWithNetworking(wo); } } var message = new GameMessageSystemChat(recipe.Recipe.SuccessMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); } else { var targetFail = components[2]; var sourceFail = components[3]; bool destroyTarget = _random.NextDouble() < targetFail.DestroyChance; bool destroySource = _random.NextDouble() < sourceFail.DestroyChance; if (destroyTarget) { if (target.OwnerId == player.Guid.Full) { player.TryRemoveItemFromInventoryWithNetworking(target, (ushort)targetFail.DestroyAmount); } else { target.Destroy(); } if (targetFail.DestroyMessage != "") { var destroyMessage = new GameMessageSystemChat(targetFail.DestroyMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (destroySource) { if (source.OwnerId == player.Guid.Full) { player.TryRemoveItemFromInventoryWithNetworking(source, (ushort)sourceFail.DestroyAmount); } else { source.Destroy(); } if (sourceFail.DestroyMessage != "") { var destroyMessage = new GameMessageSystemChat(sourceFail.DestroyMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (recipe.Recipe.FailWCID > 0) { var wo = WorldObjectFactory.CreateNewWorldObject(recipe.Recipe.FailWCID); if (wo != null) { if (recipe.Recipe.FailAmount > 1) { wo.StackSize = (ushort)recipe.Recipe.FailAmount; } player.TryCreateInInventoryWithNetworking(wo); } } var message = new GameMessageSystemChat(recipe.Recipe.FailMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); } player.SendUseDoneEvent(); }); craftChain.EnqueueChain(); }
public static void UseObjectOnTarget(Player player, WorldObject source, WorldObject target) { if (source == target) { var message = new GameMessageSystemChat($"The {source.Name} cannot be combined with itself.", ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(); return; } var recipe = DatabaseManager.World.GetCachedCookbook(source.WeenieClassId, target.WeenieClassId); if (recipe == null) { var message = new GameMessageSystemChat($"The {source.Name} cannot be used on the {target.Name}.", ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(); return; } ActionChain craftChain = new ActionChain(); CreatureSkill skill = null; bool skillSuccess = true; // assume success, unless there's a skill check double percentSuccess = 1; var motion = new Motion(MotionStance.NonCombat, MotionCommand.ClapHands); craftChain.AddAction(player, () => player.EnqueueBroadcastMotion(motion)); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(player.MotionTableId); var craftAnimationLength = motionTable.GetAnimationLength(MotionCommand.ClapHands); craftChain.AddDelaySeconds(craftAnimationLength); craftChain.AddAction(player, () => { if (recipe.Recipe.Skill > 0 && recipe.Recipe.Difficulty > 0) { // there's a skill associated with this Skill skillId = (Skill)recipe.Recipe.Skill; // this shouldn't happen, but sanity check for unexpected nulls skill = player.GetCreatureSkill(skillId); if (skill == null) { log.Warn("Unexpectedly missing skill in Recipe usage"); player.SendUseDoneEvent(); return; } //Console.WriteLine("Skill difficulty: " + recipe.Recipe.Difficulty); percentSuccess = skill.GetPercentSuccess(recipe.Recipe.Difficulty); //FIXME: Pretty certain this is broken } if (skill != null) { // check for pre-MoA skill // convert into appropriate post-MoA skill // pre-MoA melee weapons: get highest melee weapons skill var newSkill = player.ConvertToMoASkill(skill.Skill); skill = player.GetCreatureSkill(newSkill); //Console.WriteLine("Required skill: " + skill.Skill); if (skill.AdvancementClass <= SkillAdvancementClass.Untrained) { var message = new GameEventWeenieError(player.Session, WeenieError.YouAreNotTrainedInThatTradeSkill); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(WeenieError.YouAreNotTrainedInThatTradeSkill); return; } } // straight skill check, if applicable if (skill != null) { skillSuccess = _random.NextDouble() < percentSuccess; } if (skillSuccess) { bool destroyTarget = _random.NextDouble() < recipe.Recipe.SuccessDestroyTargetChance; bool destroySource = _random.NextDouble() < recipe.Recipe.SuccessDestroySourceChance; if (destroyTarget) { if (target.OwnerId == player.Guid.Full || player.GetInventoryItem(target.Guid) != null) { player.TryRemoveItemFromInventoryWithNetworkingWithDestroy(target, (ushort)recipe.Recipe.SuccessDestroyTargetAmount); } else if (target.WielderId == player.Guid.Full) { if (!player.TryRemoveItemWithNetworking(target)) { throw new Exception($"Failed to remove {target.Name} from player inventory."); } } else { target.Destroy(); } if (!String.IsNullOrEmpty(recipe.Recipe.SuccessDestroyTargetMessage)) { var destroyMessage = new GameMessageSystemChat(recipe.Recipe.SuccessDestroyTargetMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (destroySource) { if (source.OwnerId == player.Guid.Full || player.GetInventoryItem(target.Guid) != null) { player.TryRemoveItemFromInventoryWithNetworkingWithDestroy(source, (ushort)recipe.Recipe.SuccessDestroySourceAmount); } else if (source.WielderId == player.Guid.Full) { if (!player.TryRemoveItemWithNetworking(source)) { throw new Exception($"Failed to remove {source.Name} from player inventory."); } } else { source.Destroy(); } if (!String.IsNullOrEmpty(recipe.Recipe.SuccessDestroySourceMessage)) { var destroyMessage = new GameMessageSystemChat(recipe.Recipe.SuccessDestroySourceMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (recipe.Recipe.SuccessWCID > 0) { var wo = WorldObjectFactory.CreateNewWorldObject(recipe.Recipe.SuccessWCID); if (wo != null) { if (recipe.Recipe.SuccessAmount > 1) { wo.StackSize = (ushort)recipe.Recipe.SuccessAmount; } player.TryCreateInInventoryWithNetworking(wo); } } var message = new GameMessageSystemChat(recipe.Recipe.SuccessMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); } else { bool destroyTarget = _random.NextDouble() < recipe.Recipe.FailDestroyTargetChance; bool destroySource = _random.NextDouble() < recipe.Recipe.FailDestroySourceChance; if (destroyTarget) { if (target.OwnerId == player.Guid.Full || player.GetInventoryItem(target.Guid) != null) { player.TryRemoveItemFromInventoryWithNetworkingWithDestroy(target, (ushort)recipe.Recipe.FailDestroyTargetAmount); } else if (target.WielderId == player.Guid.Full) { if (!player.TryRemoveItemWithNetworking(target)) { throw new Exception($"Failed to remove {target.Name} from player inventory."); } } else { target.Destroy(); } if (!String.IsNullOrEmpty(recipe.Recipe.FailDestroyTargetMessage)) { var destroyMessage = new GameMessageSystemChat(recipe.Recipe.FailDestroyTargetMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (destroySource) { if (source.OwnerId == player.Guid.Full || player.GetInventoryItem(target.Guid) != null) { player.TryRemoveItemFromInventoryWithNetworkingWithDestroy(source, (ushort)recipe.Recipe.FailDestroySourceAmount); } else if (source.WielderId == player.Guid.Full) { if (!player.TryRemoveItemWithNetworking(source)) { throw new Exception($"Failed to remove {source.Name} from player inventory."); } } else { source.Destroy(); } if (!String.IsNullOrEmpty(recipe.Recipe.FailDestroySourceMessage)) { var destroyMessage = new GameMessageSystemChat(recipe.Recipe.FailDestroySourceMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(destroyMessage); } } if (recipe.Recipe.FailWCID > 0) { var wo = WorldObjectFactory.CreateNewWorldObject(recipe.Recipe.FailWCID); if (wo != null) { if (recipe.Recipe.FailAmount > 1) { wo.StackSize = (ushort)recipe.Recipe.FailAmount; } player.TryCreateInInventoryWithNetworking(wo); } } var message = new GameMessageSystemChat(recipe.Recipe.FailMessage, ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); } player.SendUseDoneEvent(); }); craftChain.EnqueueChain(); }
public void FiftyFiftyIsAccurate() { var result = CreatureSkill.GetPercentSuccess(100, 100); Assert.AreEqual(0.5d, result); }
public static void UseObjectOnTarget(Player player, WorldObject source, WorldObject target) { if (source == target) { var message = new GameMessageSystemChat($"The {source.Name} cannot be combined with itself.", ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(); return; } var recipe = DatabaseManager.World.GetCachedCookbook(source.WeenieClassId, target.WeenieClassId); if (recipe == null) { var message = new GameMessageSystemChat($"The {source.Name} cannot be used on the {target.Name}.", ChatMessageType.Craft); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(); return; } // verify requirements if (!VerifyRequirements(recipe.Recipe, player, source, target)) { player.SendUseDoneEvent(WeenieError.YouDoNotPassCraftingRequirements); return; } if (source.ItemType == ItemType.TinkeringMaterial) { HandleTinkering(player, source, target); return; } ActionChain craftChain = new ActionChain(); CreatureSkill skill = null; bool success = true; // assume success, unless there's a skill check double percentSuccess = 1; var motion = new Motion(MotionStance.NonCombat, MotionCommand.ClapHands); craftChain.AddAction(player, () => player.EnqueueBroadcastMotion(motion)); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(player.MotionTableId); var craftAnimationLength = motionTable.GetAnimationLength(MotionCommand.ClapHands); craftChain.AddDelaySeconds(craftAnimationLength); craftChain.AddAction(player, () => { if (recipe.Recipe.Skill > 0 && recipe.Recipe.Difficulty > 0) { // there's a skill associated with this Skill skillId = (Skill)recipe.Recipe.Skill; // this shouldn't happen, but sanity check for unexpected nulls skill = player.GetCreatureSkill(skillId); if (skill == null) { log.Warn("Unexpectedly missing skill in Recipe usage"); player.SendUseDoneEvent(); return; } //Console.WriteLine("Skill difficulty: " + recipe.Recipe.Difficulty); percentSuccess = skill.GetPercentSuccess(recipe.Recipe.Difficulty); //FIXME: Pretty certain this is broken } if (skill != null) { // check for pre-MoA skill // convert into appropriate post-MoA skill // pre-MoA melee weapons: get highest melee weapons skill var newSkill = player.ConvertToMoASkill(skill.Skill); skill = player.GetCreatureSkill(newSkill); //Console.WriteLine("Required skill: " + skill.Skill); if (skill.AdvancementClass <= SkillAdvancementClass.Untrained) { var message = new GameEventWeenieError(player.Session, WeenieError.YouAreNotTrainedInThatTradeSkill); player.Session.Network.EnqueueSend(message); player.SendUseDoneEvent(WeenieError.YouAreNotTrainedInThatTradeSkill); return; } } // perform skill check, if applicable if (skill != null) { success = ThreadSafeRandom.Next(0.0f, 1.0f) <= percentSuccess; } CreateDestroyItems(player, recipe.Recipe, source, target, success); player.SendUseDoneEvent(); }); craftChain.EnqueueChain(); }