/// <summary> /// Called once ready to pull the fish out. /// </summary> /// <remarks> /// When you catch something just before running out of bait, /// and you send MotionCancel2 from Cancel, there's a /// visual bug on Aura, where the item keeps flying to you until /// you move. This does not happen on NA for unknown reason. /// The workaround: Check for cancellation in advance and only /// send the real in effect if the skill wasn't canceled. /// </remarks> /// <param name="creature"></param> /// <param name="method">Method used on this try</param> /// <param name="success">Success of manual try</param> public void OnResponse(Creature creature, FishingMethod method, bool success) { // Get skill var skill = creature.Skills.Get(SkillId.Fishing); if (skill == null) { Log.Error("Fishing.OnResponse: Missing skill."); return; } var rnd = RandomProvider.Get(); // Update prop state creature.Temp.FishingProp.SetState("empty"); // Get auto success if (method == FishingMethod.Auto) { success = rnd.NextDouble() < skill.RankData.Var3 / 100f; } // Perfect fishing if (ChannelServer.Instance.Conf.World.PerfectFishing) { success = true; } // Check fishing ground if (creature.Temp.FishingDrop == null) { Send.ServerMessage(creature, "Error: No items found."); Log.Error("Fishing.OnResponse: Failing, no drop found."); success = false; } // Check equipment if (!this.CheckEquipment(creature)) { Send.ServerMessage(creature, "Error: Missing equipment."); Log.Error("Fishing.OnResponse: Failing, Missing equipment."); // TODO: Security violation once we're sure this can't happen // without modding. success = false; } var cancel = false; // Reduce durability if (creature.RightHand != null) { creature.Inventory.ReduceDurability(creature.RightHand, 15); // Check rod durability if (creature.RightHand.Durability == 0) { cancel = true; } } // Remove bait if (creature.Magazine != null && !ChannelServer.Instance.Conf.World.InfiniteBait) { creature.Inventory.Decrement(creature.Magazine); // Check if bait was removed because it was empty if (creature.Magazine == null) { cancel = true; } } // Fail Item item = null; if (!success) { Send.Notice(creature, Localization.Get("I was hesistating for a bit, and it got away...")); // More responses? Send.Effect(creature, Effect.Fishing, (byte)FishingEffectType.Fall, true); } // Success else { var propName = "prop_caught_objbox_01"; var propSize = 0; var size = 0; var dropData = creature.Temp.FishingDrop; // Create item if (dropData.QuestId != 0) { item = Item.CreateQuestScroll(dropData.QuestId); } else { item = new Item(dropData); } // Check fish var fish = AuraData.FishDb.Find(dropData.ItemId); if (fish != null) { propName = fish.PropName; propSize = fish.PropSize; // Random fish size, unofficial if (fish.SizeMin + fish.SizeMax != 0) { var min = fish.SizeMin; var max = fish.SizeMax; // Var1 bonus min += (int)skill.RankData.Var1; // Var4 bonus min += (int)Math.Max(0, (item.Data.BaseSize - fish.SizeMin) / 100f * skill.RankData.Var4); // Modify min and max, so the size falls into big or // small territory. var mid = (max - min) / 2; if (creature.Temp.CatchSize == CatchSize.BigOne) { min += mid; } else { max -= mid; } // Cap if (max < min) { max = min; } if (min > max) { min = max; } size = Math2.Clamp(fish.SizeMin, fish.SizeMax, rnd.Next(min, max + 1)); var scale = (1f / item.Data.BaseSize * size); item.MetaData1.SetFloat("SCALE", scale); } } // Set equipment durability to 0, does not apply to // unrepairable items, like Gargoyle Swords. // http://wiki.mabinogiworld.com/view/Fishing#Details if (item.HasTag("/equip/") && !item.HasTag("/not_repairable/")) { item.Durability = 0; } // Drop if inv add failed List <Item> changed; if (!creature.Inventory.Insert(item, false, out changed)) { item.Drop(creature.Region, creature.GetPosition(), 100, creature, false); } var itemEntityId = (changed == null || changed.Count == 0 ? item.EntityId : changed.First().EntityId); // Show acquire using the item's entity id if it wasn't added // to a stack, or using the stack's id if it was. Send.AcquireInfo2(creature, "fishing", itemEntityId); // Holding up fish effect if (!cancel) { Send.Effect(creature, Effect.Fishing, (byte)FishingEffectType.ReelIn, true, creature.Temp.FishingProp.EntityId, item.Info.Id, size, propName, propSize); } } creature.Temp.FishingDrop = null; // Handle training this.Training(creature, skill, success, item); // Fishing event ChannelServer.Instance.Events.OnCreatureFished(creature, item); // Cancel if (cancel) { creature.Skills.CancelActiveSkill(); return; } // Next round this.StartFishing(creature, 6000); }
/// <summary> /// Completes skill, creating the items. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Complete(Creature creature, Skill skill, Packet packet) { var materials = new List <ProductionMaterial>(); var stitches = new List <Point>(); var stage = (Stage)packet.GetByte(); var propEntityId = packet.GetLong(); var unkInt1 = packet.GetInt(); var existingItemEntityId = packet.GetLong(); var finishId = packet.GetInt(); if (stage == Stage.Progression) { // Materials if (!this.ReadMaterials(creature, packet, out materials)) { goto L_Fail; } } else if (stage == Stage.Finish) { // Stitches if (!this.ReadStitches(creature, packet, out stitches)) { goto L_Fail; } } else { Send.ServerMessage(creature, Localization.Get("Stage error, please report.")); Log.Error("Tailoring: Unknown progress stage '{0}'.", stage); goto L_Fail; } // Check tools if (!CheckTools(creature)) { goto L_Fail; } // Get manual var manualId = creature.Magazine.MetaData1.GetInt("FORMID"); var manualData = AuraData.ManualDb.Find(ManualCategory.Tailoring, manualId); if (manualData == null) { Log.Error("Tailoring.Complete: Manual '{0}' not found.", manualId); Send.ServerMessage(creature, Localization.Get("Failed to look up pattern, please report.")); goto L_Fail; } // Check existing item Item existingItem = null; if (existingItemEntityId != 0) { // Get item existingItem = creature.Inventory.GetItem(existingItemEntityId); if (existingItem == null) { Log.Warning("Tailoring.Complete: Creature '{0:X16}' tried to work on non-existing item.", creature.EntityId); goto L_Fail; } // Check id against manual if (existingItem.Info.Id != manualData.ItemId) { Log.Warning("Tailoring.Complete: Creature '{0:X16}' tried use an item with a different id than the manual.", creature.EntityId); goto L_Fail; } // Check progress if (!existingItem.MetaData1.Has(ProgressVar)) { Log.Warning("Tailoring.Complete: Creature '{0:X16}' tried work on an item that is already finished.", creature.EntityId); goto L_Fail; } } var rnd = RandomProvider.Get(); // Materials are only sent to Complete for progression, // finish materials are handled in Prepare. if (stage == Stage.Progression) { var requiredMaterials = manualData.GetMaterialList(); // Get items to decrement List <ProductionMaterial> toDecrement; if (!this.GetItemsToDecrement(creature, Stage.Progression, manualData, requiredMaterials, materials, out toDecrement)) { goto L_Fail; } // Decrement mats this.DecrementMaterialItems(creature, toDecrement, rnd); } // Reduce durability creature.Inventory.ReduceDurability(creature.RightHand, ToolDurabilityLoss); creature.Inventory.ReduceDurability(creature.Magazine, ManualDurabilityLoss); // Get to work var newItem = false; var success = false; var msg = ""; // Create new item if (existingItem == null) { existingItem = new Item(manualData.ItemId); existingItem.OptionInfo.Flags |= ItemFlags.Incomplete; existingItem.MetaData1.SetFloat(ProgressVar, 0); existingItem.MetaData1.SetLong(StclmtVar, DateTime.Now); newItem = true; } // Finish item if progress is >= 1, otherwise increase progress. var progress = (newItem ? 0 : existingItem.MetaData1.GetFloat(ProgressVar)); ProgressResult result; if (progress < 1) { // TODO: Random quality gain/loss? // "The combination of tools and materials was quite good! (Quality +{0}%)" // Get success // Unofficial and mostly based on guessing. If process was // determined to be successful, a good result will happen, // if not, a bad one. Both are then split into very good // and very bad, based on another random number. var chance = this.GetSuccessChance(creature, skill, manualData.Rank); success = (rnd.NextDouble() * 100 < chance); var rngFailSuccess = rnd.NextDouble(); // Calculate progress to add // Base line is between 50 and 100% of the max progress from // the db. For example, a Popo's skirt has 200%, which should // always put it on 100% instantly, as long as it's a success. var addProgress = rnd.Between(manualData.MaxProgress / 2, manualData.MaxProgress); var rankDiff = ((int)skill.Info.Rank - (int)manualData.Rank); // Apply RNG fail/success if (!success) { // 25% chance for very bad if (rngFailSuccess < 0.25f) { msg += Localization.Get("Catastrophic failure!"); addProgress /= 2f; result = ProgressResult.VeryBad; } // 75% chance for bad else { msg += Localization.Get("That didn't go so well..."); addProgress /= 1.5f; result = ProgressResult.Bad; } } else { // 25% chance for best, if manual is >= 2 ranks if (rngFailSuccess < 0.25f && rankDiff <= -2) { msg += Localization.Get("You created a masterpiece!"); addProgress *= 2f; result = ProgressResult.VeryGood; } // 75% chance for good else { // Too easy if more than two ranks below, which counts // as a training fail, according to the Wiki. if (rankDiff >= 2) { msg += Localization.Get("You did it, but that was way too easy."); result = ProgressResult.Bad; } else { msg += Localization.Get("Success!"); result = ProgressResult.Good; } } } // Weather bonus if (ChannelServer.Instance.Weather.GetWeatherType(creature.RegionId) == WeatherType.Rain) { addProgress += manualData.RainBonus; } progress = Math.Min(1, progress + addProgress); existingItem.MetaData1.SetFloat(ProgressVar, progress); if (progress == 1) { msg += Localization.Get("\nFinal Stage remaining"); } else { msg += string.Format(Localization.Get("\n{0}% completed."), (int)(progress * 100)); } this.OnProgress(creature, skill, result); Send.Notice(creature, msg); // Event ChannelServer.Instance.Events.OnCreatureFinishedProductionOrCollection(creature, success); } else { var quality = this.CalculateQuality(stitches, creature.Temp.TailoringMiniGameX, creature.Temp.TailoringMiniGameY); this.FinishItem(creature, skill, manualData, 0, existingItem, quality); this.OnProgress(creature, skill, ProgressResult.Finish); // Creation event ChannelServer.Instance.Events.OnCreatureCreatedItem(new CreationEventArgs(creature, CreationMethod.Tailoring, existingItem, manualData.Rank)); result = ProgressResult.Finish; success = true; } // Add or update item if (!newItem) { Send.ItemUpdate(creature, existingItem); } else { creature.Inventory.Add(existingItem, true); } // Acquire info once it's finished and updated. if (result == ProgressResult.Finish) { Send.AcquireInfo2(creature, "tailoring", existingItem.EntityId); } // Success motion if it was a good result, otherwise keep // going to fail. if (success) { Send.UseMotion(creature, 14, 0); // Success motion Send.Echo(creature, packet); return; } L_Fail: Send.UseMotion(creature, 14, 3); // Fail motion Send.Echo(creature, packet); }
/// <summary> /// Called once ready to pull the fish out. /// </summary> /// <remarks> /// When you catch something just before running out of bait, /// and you send MotionCancel2 from Cancel, there's a /// visual bug on Aura, where the item keeps flying to you until /// you move. This does not happen on NA for unknown reason. /// The workaround: Check for cancellation in advance and only /// send the real in effect if the skill wasn't canceled. /// </remarks> /// <param name="creature"></param> /// <param name="method">Method used on this try</param> /// <param name="success">Success of manual try</param> public void OnResponse(Creature creature, FishingMethod method, bool success) { // Get skill var skill = creature.Skills.Get(SkillId.Fishing); if (skill == null) { Log.Error("Fishing.OnResponse: Missing skill."); return; } var rnd = RandomProvider.Get(); // Update prop state // TODO: update prop state method creature.Temp.FishingProp.SetState("empty"); // Get auto success if (method == FishingMethod.Auto) { success = rnd.NextDouble() < skill.RankData.Var3 / 100f; } // Perfect fishing if (ChannelServer.Instance.Conf.World.PerfectFishing) { success = true; } // Check fishing ground if (creature.Temp.FishingDrop == null) { Send.ServerMessage(creature, "Error: No items found."); Log.Error("Fishing.OnResponse: Failing, no drop found."); success = false; } // Check equipment if (!this.CheckEquipment(creature)) { Send.ServerMessage(creature, "Error: Missing equipment."); Log.Error("Fishing.OnResponse: Failing, Missing equipment."); // TODO: Security violation once we're sure this can't happen // without modding. success = false; } var cancel = false; // Reduce durability if (creature.RightHand != null && !ChannelServer.Instance.Conf.World.NoDurabilityLoss) { var reduce = 15; // Half dura loss if blessed if (creature.RightHand.IsBlessed) { reduce = Math.Max(1, reduce / 2); } creature.RightHand.Durability -= reduce; Send.ItemDurabilityUpdate(creature, creature.RightHand); // Check rod durability if (creature.RightHand.Durability == 0) { cancel = true; } } // Remove bait if (creature.Magazine != null && !ChannelServer.Instance.Conf.World.InfiniteBait) { creature.Inventory.Decrement(creature.Magazine); // Check if bait was removed because it was empty if (creature.Magazine == null) { cancel = true; } } // Fail Item item = null; if (!success) { Send.Notice(creature, Localization.Get("I was hesistating for a bit, and it got away...")); // More responses? Send.Effect(creature, Effect.Fishing, (byte)FishingEffectType.Fall, true); } // Success else { var propName = "prop_caught_objbox_01"; var propSize = 0; var size = 0; // Create item item = new Item(creature.Temp.FishingDrop); // Check fish var fish = AuraData.FishDb.Find(creature.Temp.FishingDrop.ItemId); if (fish != null) { propName = fish.PropName; propSize = fish.PropSize; // Random fish size, unofficial if (fish.SizeMin + fish.SizeMax != 0) { var min = fish.SizeMin + (int)Math.Max(0, (item.Data.BaseSize - fish.SizeMin) / 100f * skill.RankData.Var4); var max = fish.SizeMax; size = Math2.Clamp(fish.SizeMin, fish.SizeMax, rnd.Next(min, max + 1) + (int)skill.RankData.Var1); var scale = (1f / item.Data.BaseSize * size); item.MetaData1.SetFloat("SCALE", scale); } } // Set equipment durability if (item.HasTag("/equip/") && item.OptionInfo.DurabilityMax >= 1) { item.Durability = 0; } // Drop if inv add failed List <Item> changed; if (!creature.Inventory.Insert(item, false, out changed)) { item.Drop(creature.Region, creature.GetPosition().GetRandomInRange(100, rnd), creature, false); } var itemEntityId = (changed == null || changed.Count == 0 ? item.EntityId : changed.First().EntityId); // Show acquire using the item's entity id if it wasn't added // to a stack, or using the stack's id if it was. Send.AcquireInfo2(creature, "fishing", itemEntityId); // Holding up fish effect if (!cancel) { Send.Effect(creature, Effect.Fishing, (byte)FishingEffectType.ReelIn, true, creature.Temp.FishingProp.EntityId, item.Info.Id, size, propName, propSize); } } creature.Temp.FishingDrop = null; // Handle training this.Training(creature, skill, success, item); // Cancel if (cancel) { creature.Skills.CancelActiveSkill(); return; } // Next round this.StartFishing(creature, 6000); }