/// <summary> /// Completes skill, dropping a cobweb. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Complete(Creature creature, Skill skill, Packet packet) { var cobweb = new Item(ItemId); cobweb.Drop(creature.Region, creature.GetPosition(), 200, creature, true); Send.SkillComplete(creature, skill.Info.Id); }
/// <summary> /// Bolt specific use code. /// </summary> /// <param name="attacker"></param> /// <param name="skill"></param> /// <param name="target"></param> protected override void UseSkillOnTarget(Creature attacker, Skill skill, Creature mainTarget) { // Create actions var aAction = new AttackerAction(CombatActionType.RangeHit, attacker, skill.Info.Id, mainTarget.EntityId); aAction.Set(AttackerOptions.Result); var cap = new CombatActionPack(attacker, skill.Info.Id, aAction); var targets = new List<Creature>(); targets.Add(mainTarget); targets.AddRange(mainTarget.Region.GetCreaturesInRange(mainTarget.GetPosition(), SplashRange).Where(a => a != mainTarget && attacker.CanTarget(a) && attacker.CanAttack(a))); // Damage var damage = this.GetDamage(attacker, skill); var max = Math.Min(targets.Count, skill.Stacks); for (int i = 0; i < max; ++i) { var target = targets[i]; var targetDamage = damage; target.StopMove(); var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); tAction.Set(TargetOptions.Result); tAction.Stun = TargetStun; // Full damage for the first target, -10% for every subsequent one. targetDamage -= (targetDamage * 0.1f) * i; // Reduce damage var maxDamage = damage; //Damage without Defense and Protection // Reduce damage Defense.Handle(aAction, tAction); SkillHelper.HandleMagicDefenseProtection(target, ref targetDamage); ManaShield.Handle(target, ref targetDamage, tAction, maxDamage, true); // Deal damage if (targetDamage > 0) target.TakeDamage(tAction.Damage = targetDamage, attacker); if (target == mainTarget) target.Aggro(attacker); // Death/Knockback this.HandleKnockBack(attacker, target, tAction); cap.Add(tAction); } // Override stun set by defense aAction.Stun = AttackerStun; Send.Effect(attacker, Effect.UseMagic, EffectSkillName); Send.SkillUseStun(attacker, skill.Info.Id, aAction.Stun, 1); this.BeforeHandlingPack(attacker, skill); cap.Handle(); }
protected override bool CheckProp(Creature creature, long propEntityId) { // Milling behaves like a skill that doesn't require a prop, // but as we know, it does, so we'll do a static check for // Tir's Windmill. propEntityId = 0xA000010009042B; // Check prop var prop = creature.Region.GetProp(propEntityId); if (prop == null) { Log.Error("Milling.CheckProp: Windmill not found ({0:X16}).", propEntityId); Send.ServerMessage(creature, Localization.Get("Error in prop check, please report.")); return false; } // Check range if (!creature.GetPosition().InRange(prop.GetPosition(), 1500)) { Send.Notice(creature, Localization.Get("You are too far away.")); return false; } // Check state // Sanity check, client checks this. if (prop.State == "off") { Send.Notice(creature, Localization.Get("The Mill isn't working.")); return false; } return true; }
/// <summary> /// Completes skill, dropping a cobweb. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Complete(Creature creature, Skill skill, Packet packet) { var cobweb = new Item(ItemId); cobweb.Drop(creature.Region, creature.GetPosition().GetRandomInRange(200, RandomProvider.Get())); Send.SkillComplete(creature, skill.Info.Id); }
protected override bool CheckProp(Creature creature, long propEntityId) { // Check existence var prop = (propEntityId == 0 ? null : creature.Region.GetProp(propEntityId)); if (prop == null || !prop.HasTag("/refine/")) { Log.Warning("Refining.Prepare: Creature '{0:X16}' tried to use production skill with invalid prop.", creature.EntityId); return false; } // Check distance if (!creature.GetPosition().InRange(prop.GetPosition(), 1000)) { // Don't warn, could happen due to lag. Send.Notice(creature, Localization.Get("You are too far away.")); return false; } // Check state // Sanity check, client should handle it. if (prop.State != "on") { Send.Notice(creature, Localization.Get("The Waterwheel isn't working,\nand that means the Furnace won't fire.")); return false; } return true; }
/// <summary> /// Sends EnterDynamicRegion to creature's client. /// </summary> /// <param name="creature"></param> /// <param name="warpFromRegionId"></param> /// <param name="warpToRegion"></param> public static void EnterDynamicRegion(Creature creature, int warpFromRegionId, Region warpToRegion, int x, int y) { var warpTo = warpToRegion as DynamicRegion; if (warpTo == null) throw new ArgumentException("EnterDynamicRegion requires a dynamic region."); var pos = creature.GetPosition(); var packet = new Packet(Op.EnterDynamicRegion, MabiId.Broadcast); packet.PutLong(creature.EntityId); packet.PutInt(warpFromRegionId); // creature's current region or 0? packet.PutInt(warpToRegion.Id); packet.PutString(warpToRegion.Name); // dynamic region name packet.PutUInt(0x80000000); // bitmask? (|1 = time difference?) packet.PutInt(warpTo.BaseId); packet.PutString(warpTo.BaseName); packet.PutInt(200); // 100|200 (100 changes the lighting?) packet.PutByte(0); // 1 = next is empty? packet.PutString("data/world/{0}/{1}", warpTo.BaseName, warpTo.Variation); packet.PutByte(0); //if (^ true) //{ // pp.PutByte(1); // pp.PutInt(3100); // some region id? //} packet.PutInt(x); // target x pos packet.PutInt(y); // target y pos creature.Client.Send(packet); }
/// <summary> /// Uses skill, the actual usage is in Complete. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Use(Creature creature, Skill skill, Packet packet) { var location = packet.GetLong(); var unkInt1 = packet.GetInt(); var unkInt2 = packet.GetInt(); var areaPosition = new Position(location); // Check range if (!creature.GetPosition().InRange(areaPosition, Range)) { this.Cancel(creature, skill); Send.SkillUseSilentCancel(creature); Send.Notice(creature, Localization.Get("Out of range.")); return; } // Reduce Dice if (creature.Inventory.RightHand != null) creature.Inventory.Decrement(creature.Inventory.RightHand); var number = (byte)(RandomProvider.Get().Next(6)); Send.UseMotion(creature, 27, 2, false, false); Send.Effect(creature, Effect.Dice, 0, "process", location, number); // [200200, NA233 (2016-08-12)] New 0 int after effect id Send.SkillUse(creature, skill.Info.Id, location, unkInt1, unkInt2); skill.Stacks = 0; }
/// <summary> /// Prepares skill, skips right to used. /// </summary> /// <remarks> /// Doesn't check anything, like what you can gather with what, /// because at this point there's no chance for abuse. /// </remarks> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> /// <returns></returns> public bool Prepare(Creature creature, Skill skill, Packet packet) { var entityId = packet.GetLong(); var collectId = packet.GetInt(); // You shall stop creature.StopMove(); var creaturePosition = creature.GetPosition(); // Get target (either prop or creature) var targetEntity = this.GetTargetEntity(creature.Region, entityId); if (targetEntity != null) creature.Temp.GatheringTargetPosition = targetEntity.GetPosition(); // Check distance if (!creaturePosition.InRange(creature.Temp.GatheringTargetPosition, MaxDistance)) { Send.Notice(creature, Localization.Get("Your arms are too short to reach that from here.")); return false; } // ? (sets creatures position on the client side) Send.CollectAnimation(creature, entityId, collectId, creaturePosition); // Use Send.SkillUse(creature, skill.Info.Id, entityId, collectId); skill.State = SkillState.Used; return true; }
/// <summary> /// Uses skill, the actual usage is in Complete. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Use(Creature creature, Skill skill, Packet packet) { var location = packet.GetLong(); var unkInt1 = packet.GetInt(); var unkInt2 = packet.GetInt(); var areaPosition = new Position(location); // Check range if (!creature.GetPosition().InRange(areaPosition, Range)) { this.Cancel(creature, skill); Send.SkillUseSilentCancel(creature); Send.Notice(creature, Localization.Get("Out of range.")); return; } // Reduce Dice if (creature.Inventory.RightHand != null) creature.Inventory.Decrement(creature.Inventory.RightHand); Send.UseMotion(creature, 27, 2, false, false); Send.Effect(creature, Effect.Dice, "process", location, (byte)3); Send.SkillUse(creature, skill.Info.Id, location, unkInt1, unkInt2); skill.Stacks = 0; }
public bool Prepare(Creature creature, Skill skill, Packet packet) { var unkStr = packet.GetString(); // Get all items on floor and remove those that are within skill range var items = creature.Region.GetAllItems(); foreach (var item in items.Where(a => a.GetPosition().InRange(creature.GetPosition(), (int)skill.RankData.Var1))) creature.Region.RemoveItem(item); return false; // Silent cancel }
/// <summary> /// Broadcasts HittingProp in range of creature. /// </summary> /// <param name="creature"></param> /// <param name="propEntityId"></param> /// <param name="stunTime"></param> public static void HittingProp(Creature creature, long propEntityId, int stunTime) { var pos = creature.GetPosition(); var packet = new Packet(Op.HittingProp, creature.EntityId); packet.PutLong(propEntityId); packet.PutInt(stunTime); packet.PutFloat(pos.X); packet.PutFloat(pos.Y); creature.Region.Broadcast(packet, creature); }
/// <summary> /// Readies skill, activates fire effect. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> /// <returns></returns> public bool Ready(Creature creature, Skill skill, Packet packet) { creature.Temp.FireArrow = creature.Region.GetProps(a => a.Info.Id == 203 && a.GetPosition().InRange(creature.GetPosition(), 500)).Count > 0; if (creature.Temp.FireArrow) Send.Effect(creature, Effect.FireArrow, true); Send.SkillReady(creature, skill.Info.Id); creature.Lock(Locks.Run); return true; }
/// <summary> /// Sends EnterRegion to creature's client. /// </summary> /// <param name="creature"></param> public static void EnterRegion(Creature creature, int regionId, int x, int y) { var pos = creature.GetPosition(); var packet = new Packet(Op.EnterRegion, MabiId.Channel); packet.PutLong(creature.EntityId); packet.PutByte(true); // success? packet.PutInt(regionId); packet.PutInt(x); packet.PutInt(y); creature.Client.Send(packet); }
/// <summary> /// Adds the referred creature's data to the referenced packet. /// </summary> /// <param name="creature"></param> /// <param name="packet"></param> public static void AddPartyMember(this Packet packet, Creature creature) { var loc = creature.GetPosition(); packet.PutInt(creature.PartyPosition); packet.PutLong(creature.EntityId); packet.PutString(creature.Name); packet.PutByte(1); packet.PutInt(creature.Region.Id); packet.PutInt(loc.X); packet.PutInt(loc.Y); packet.PutByte(0); packet.PutInt((int)((creature.Life * 100) / creature.LifeMax)); packet.PutInt((int)100); }
/// <summary> /// Completes skill, teleporting behind target. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Complete(Creature creature, Skill skill, Packet packet) { var target = creature.Target; if (target != null) { var pos = creature.GetPosition(); var targetPos = target.GetPosition(); var telePos = pos.GetRelative(targetPos, DistanceToTarget); Send.Effect(creature, Effect.SilentMoveTeleport, (byte)2, telePos.X, telePos.Y); creature.Warp(creature.RegionId, telePos); } Send.SkillComplete(creature, skill.Info.Id); }
/// <summary> /// Broadcasts ForceRunTo in creature's range. /// </summary> /// <param name="creature"></param> /// <param name="to"></param> public static void ForceRunTo(Creature creature, Position to) { var pos = creature.GetPosition(); var packet = new Packet(Op.ForceRunTo, creature.EntityId); // From packet.PutInt(pos.X); packet.PutInt(pos.Y); // To packet.PutInt(to.X); packet.PutInt(to.Y); packet.PutByte(1); packet.PutByte(0); creature.Region.Broadcast(packet, creature); }
protected override bool CheckProp(Creature creature, long propEntityId) { // Check existence var prop = (propEntityId == 0 ? null : creature.Region.GetProp(propEntityId)); if (prop == null || !prop.HasTag("/spin/|/loom/")) { Log.Warning("Weaving.Prepare: Creature '{0:X16}' tried to use production skill with invalid prop.", creature.EntityId); return false; } // Check distance if (!creature.GetPosition().InRange(prop.GetPosition(), 1000)) { // Don't warn, could happen due to lag. Send.Notice(creature, Localization.Get("You can't reach a Spinning Wheel or Loom from here.")); return false; } return true; }
/// <summary> /// Starts rest skill. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="dict"></param> /// <returns></returns> public override StartStopResult Start(Creature creature, Skill skill, MabiDictionary dict) { creature.StopMove(); creature.IsInBattleStance = false; creature.AttemptingAttack = false; var chairItemEntityId = dict.GetLong("ITEMID"); if (chairItemEntityId != 0) this.SetUpChair(creature, chairItemEntityId); creature.Activate(CreatureStates.SitDown); if (skill.Info.Rank >= SkillRank.R9) creature.Activate(CreatureStatesEx.RestR9); Send.SitDown(creature); // Get bonuses if meditation isn't active. if (!creature.Conditions.Has(ConditionsE.Meditation)) { ApplyRestBonus(creature, skill, chairItemEntityId); } else { RestCampfireBonus(creature, skill, chairItemEntityId); } // Add bonus from campfire // TODO: Check for disappearing of campfire? (OnDisappears+Recheck) var campfires = creature.Region.GetProps(a => a.Info.Id == 203 && a.GetPosition().InRange(creature.GetPosition(), 500)); if (campfires.Count > 0) { Send.Notice(creature, Localization.Get("The fire feels very warm.")); } if (skill.Info.Rank == SkillRank.Novice) skill.Train(1); // Use Rest. return StartStopResult.Okay; }
public void Use(Creature creature, Skill skill, Packet packet) { var targetPos = new Position(packet.GetLong()); // Check distance to target position if (!creature.GetPosition().InRange(targetPos, (int)skill.RankData.Var2 + DistanceBuffer)) { Send.Notice(creature, Localization.Get("Out of range.")); Send.SkillUse(creature, skill.Info.Id, 0); return; } // Stop creature's movement. creature.StopMove(); // Teleport effect (does not actually teleport) Send.Effect(creature, Effect.SilentMoveTeleport, (byte)2, targetPos.X, targetPos.Y); // Teleport player to target position creature.SetPosition(targetPos.X, targetPos.Y); Send.SkillTeleport(creature, targetPos.X, targetPos.Y); Send.SkillUse(creature, skill.Info.Id, 1); }
/// <summary> /// Sends CombatAttackR to creature's client. /// </summary> /// <remarks> /// Contains creature's and target's position, sent for out of range, /// so the client knows it has to adjust the creature's position. /// </remarks> /// <param name="creature"></param> /// <param name="target"></param> public static void CombatAttackR(Creature creature, Creature target) { var creaturePos = creature.GetPosition(); var targetPos = target.GetPosition(); var packet = new Packet(Op.CombatAttackR, creature.EntityId); packet.PutByte(100); packet.PutLong(target.EntityId); packet.PutByte(0); packet.PutByte(0); packet.PutInt(creaturePos.X); packet.PutInt(creaturePos.Y); packet.PutByte(0); packet.PutInt(targetPos.X); packet.PutInt(targetPos.Y); packet.PutString(""); creature.Client.Send(packet); }
/// <summary> /// Bolt specific use code. /// </summary> /// <param name="attacker"></param> /// <param name="skill"></param> /// <param name="target"></param> protected override void UseSkillOnTarget(Creature attacker, Skill skill, Creature mainTarget) { // Create actions var aAction = new AttackerAction(CombatActionType.RangeHit, attacker, mainTarget.EntityId); aAction.Set(AttackerOptions.Result); var cap = new CombatActionPack(attacker, skill.Info.Id, aAction); // Get targets // Add the main target as first target, so it gets the first hit, // and the full damage. var targets = new List<Creature>(); targets.Add(mainTarget); var inSplashRange = attacker.GetTargetableCreaturesAround(mainTarget.GetPosition(), SplashRange); targets.AddRange(inSplashRange.Where(a => a != mainTarget)); // Damage var damage = this.GetDamage(attacker, skill); var max = Math.Min(targets.Count, skill.Stacks); for (int i = 0; i < max; ++i) { var target = targets[i]; var targetDamage = damage; target.StopMove(); var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); tAction.Set(TargetOptions.Result); tAction.Stun = TargetStun; // Full damage for the first target, -10% for every subsequent one. targetDamage -= (targetDamage * 0.1f) * i; // Critical Hit var critChance = attacker.GetTotalCritChance(target.Protection, true); CriticalHit.Handle(attacker, critChance, ref damage, tAction); // Reduce damage Defense.Handle(aAction, tAction, ref targetDamage); SkillHelper.HandleMagicDefenseProtection(target, ref targetDamage); SkillHelper.HandleConditions(attacker, target, ref damage); ManaShield.Handle(target, ref targetDamage, tAction); // Mana Deflector var mdResult = ManaDeflector.Handle(attacker, target, ref targetDamage, tAction); var delayReduction = mdResult.DelayReduction; var pinged = mdResult.Pinged; // Deal damage if (targetDamage > 0) target.TakeDamage(tAction.Damage = targetDamage, attacker); if (target == mainTarget) target.Aggro(attacker); // Reduce stun, based on ping if (pinged && delayReduction > 0) tAction.Stun = (short)Math.Max(0, tAction.Stun - (tAction.Stun / 100 * delayReduction)); // Death/Knockback if (target.IsDead) { tAction.Set(TargetOptions.FinishingKnockDown); } else { // If knocked down, instant recovery, // if repeat hit, knock down, // otherwise potential knock back. if (target.IsKnockedDown) { tAction.Stun = 0; } else if (target.Stability < MinStability) { tAction.Set(TargetOptions.KnockDown); } else { // If number of stacks is greater than the number of // targets hit, the targets are knocked back, which is // done by reducing the stability to min here. // Targets with high enough Mana Deflector might // negate this knock back, by reducing the stability // reduction to 0. var stabilityReduction = (skill.Stacks > targets.Count ? OverchargeStabilityReduction : StabilityReduction); // Reduce reduction, based on ping // While the Wiki says that "the Knockdown Gauge [does not] // build up", tests show that it does. However, it's // reduced, assumedly based on the MD rank. if (delayReduction > 0) stabilityReduction = (short)Math.Max(0, stabilityReduction - (stabilityReduction / 100 * delayReduction)); target.Stability -= stabilityReduction; if (target.IsUnstable) { tAction.Set(TargetOptions.KnockBack); } } } if (tAction.IsKnockBack) attacker.Shove(target, KnockbackDistance); cap.Add(tAction); } // Override stun set by defense aAction.Stun = AttackerStun; Send.Effect(attacker, Effect.UseMagic, EffectSkillName); Send.SkillUseStun(attacker, skill.Info.Id, aAction.Stun, 1); skill.Stacks = 0; // Update current weapon SkillHelper.UpdateWeapon(attacker, targets.FirstOrDefault(), ProficiencyGainType.Melee, attacker.RightHand); cap.Handle(); }
/// <summary> /// Sets new position for target, based on attacker's position /// and the distance, takes collision into consideration. /// </summary> /// <param name="target">Entity to be knocked back</param> /// <param name="distance">Distance to knock back the target</param> /// <returns>New position</returns> public Position Shove(Creature target, int distance) { var attackerPosition = this.GetPosition(); var targetPosition = target.GetPosition(); var newPos = attackerPosition.GetRelative(targetPosition, distance); Position intersection; if (target.Region.Collisions.Find(targetPosition, newPos, out intersection)) newPos = targetPosition.GetRelative(intersection, -50); target.SetPosition(newPos.X, newPos.Y); return newPos; }
/// <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); // Modify selling price based on scale. // The default selling price is 10% of the price. // If the scale is 2, double the base size (the // "usual" size so to speak), it fetches twice // the price. The formula is unofficial, but works. item.OptionInfo.SellingPrice = (int)(item.OptionInfo.SellingPrice * 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); // Set protection limit to max, since fished items that // drop out of the player's overfilled bags should be // protected indefinitely. item.ProtectionLimit = DateTime.MaxValue; } 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> /// Returns which creatures in the party are both in region, and a specified range. /// If no range is supplied, it returns all party creatures within visual(?) range. /// </summary> /// <remarks>3000 is a total guess as to the actual visible range.</remarks> /// <param name="creature"></param> /// <param name="range">Use 0 to get every member in the region.</param> /// <returns></returns> public List<Creature> GetMembersInRange(Creature creature, int range = -1) { var result = new List<Creature>(); var pos = creature.GetPosition(); if (range < 0) range = 3000; lock (_sync) { foreach (var member in _members.Where(a => a != creature && a.RegionId == this.Leader.RegionId)) { if (range == 0 || pos.InRange(member.GetPosition(), range)) result.Add(member); } } return result; }
/// <summary> /// Broadcasts PlayDead in range of creature. /// </summary> /// <param name="creature"></param> public static void PlayDead(Creature creature) { var pos = creature.GetPosition(); var packet = new Packet(Op.PlayDead, creature.EntityId); packet.PutByte(true); // ? packet.PutFloat(pos.X); packet.PutFloat(pos.Y); packet.PutInt(5000); creature.Region.Broadcast(packet, creature); }
/// <summary> /// Uses the skill. /// </summary> /// <param name="attacker"></param> /// <param name="skill"></param> /// <param name="targetEntityId"></param> /// <returns></returns> public CombatSkillResult Use(Creature attacker, Skill skill, long targetEntityId) { // Get target var target = attacker.Region.GetCreature(targetEntityId); if (target == null) return CombatSkillResult.InvalidTarget; if (target.IsNotReadyToBeHit) return CombatSkillResult.Okay; var targetPos = target.GetPosition(); var attackerPos = attacker.GetPosition(); // Check range //if (!attackerPos.InRange(targetPos, attacker.RightHand.OptionInfo.EffectiveRange + 100)) // return CombatSkillResult.OutOfRange; // Actions var cap = new CombatActionPack(attacker, skill.Info.Id); var aAction = new AttackerAction(CombatActionType.RangeHit, attacker, skill.Info.Id, targetEntityId); aAction.Set(AttackerOptions.Result); aAction.Stun = AttackerStun; cap.Add(aAction); // Hit by chance var chance = attacker.AimMeter.GetAimChance(target); var rnd = RandomProvider.Get(); if (rnd.NextDouble() * 100 < chance) { var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); tAction.Set(TargetOptions.Result); tAction.Stun = TargetStun; cap.Add(tAction); // Damage var damage = attacker.GetRndRangedDamage(); // More damage with fire arrow if (attacker.Temp.FireArrow) damage *= FireBonus; // Critical Hit var critShieldReduction = (target.LeftHand != null ? target.LeftHand.Data.DefenseBonusCrit : 0); var critChance = attacker.GetRightCritChance(target.Protection + critShieldReduction); CriticalHit.Handle(attacker, critChance, ref damage, tAction); var maxDamage = damage; //Damage without Defense and Protection // Subtract target def/prot SkillHelper.HandleDefenseProtection(target, ref damage); // Defense Defense.Handle(aAction, tAction, ref damage, true); // Mana Shield ManaShield.Handle(target, ref damage, tAction, maxDamage); // Deal with it! if (damage > 0) target.TakeDamage(tAction.Damage = damage, attacker); // Aggro target.Aggro(attacker); // Death/Knockback if (target.IsDead) { tAction.Set(TargetOptions.FinishingKnockDown); attacker.Shove(target, KnockBackDistance); } else { // Insta-recover in knock down // TODO: Tied to stability? if (target.IsKnockedDown) { tAction.Stun = 0; } // Knock down if hit repeatedly else if (target.Stability < 30) { tAction.Set(TargetOptions.KnockDown); } // Normal stability reduction else { target.Stability -= StabilityReduction; if (target.IsUnstable) { tAction.Set(TargetOptions.KnockBack); attacker.Shove(target, KnockBackDistance); } } } } // Skill training if (skill.Info.Rank == SkillRank.Novice || skill.Info.Rank == SkillRank.RF) skill.Train(1); // Try ranged attack. // Reduce arrows if (attacker.Magazine != null && !ChannelServer.Instance.Conf.World.InfiniteArrows) attacker.Inventory.Decrement(attacker.Magazine); // Disable fire arrow effect if (attacker.Temp.FireArrow) Send.Effect(attacker, Effect.FireArrow, false); // "Cancels" the skill // 800 = old load time? == aAction.Stun? Varies? Doesn't seem to be a stun. Send.SkillUse(attacker, skill.Info.Id, 800, 1); cap.Handle(); return CombatSkillResult.Okay; }
/// <summary> /// Uses skill on target. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Use(Creature creature, Skill skill, Packet packet) { var entityId = packet.GetLong(); var unkInt1 = packet.GetInt(); var unkInt2 = packet.GetInt(); // Get creature var target = creature.Region.GetCreature(entityId); if (target == null) goto L_End; // Check range if (!creature.GetPosition().InRange(target.GetPosition(), Range)) { Send.Notice(creature, Localization.Get("Not in range.")); // Unofficial goto L_End; } // TODO: Check target validity once we have skill target support // Calculate heal amount var rnd = RandomProvider.Get(); var healAmount = rnd.Next((int)skill.RankData.Var1, (int)skill.RankData.Var3 + 1); // Add magic attack bonus healAmount += (int)(creature.MagicAttack / 10); // Add wand bonus if (creature.RightHand != null && creature.RightHand.HasTag("/healing_wand/")) healAmount += 5; // Reduce user's stamina if target is the user if (target == creature && target.Life < target.LifeInjured) { creature.Stamina -= healAmount; Send.StatUpdate(creature, StatUpdateType.Private, Stat.Stamina, Stat.Hunger, Stat.StaminaMax); } // Skill training // Call before heal to calculate if in distress this.OnUseSkillOnTarget(creature, target); ChannelServer.Instance.Events.OnPlayerHealsCreature(creature, target, skill); // Heal target target.Life += healAmount; Send.StatUpdateDefault(target); Send.Effect(target, Effect.HealLife, healAmount); // Reduce stacks skill.Stacks--; L_End: Send.Effect(creature, Effect.StackUpdate, "healing_stack", (byte)skill.Stacks, (byte)0); Send.Effect(creature, Effect.UseMagic, "healing", entityId); Send.SkillUse(creature, skill.Info.Id, entityId, unkInt1, unkInt2); }
/// <summary> /// Handles skill usage. /// </summary> /// <param name="attacker"></param> /// <param name="skill"></param> /// <param name="targetEntityId"></param> /// <returns></returns> public virtual CombatSkillResult Use(Creature attacker, Skill skill, long targetEntityId) { // Check target var target = attacker.Region.GetCreature(targetEntityId); if (target == null) return CombatSkillResult.InvalidTarget; // Check distance var targetPosition = target.GetPosition(); var attackerPosition = attacker.GetPosition(); if (!attackerPosition.InRange(targetPosition, this.GetRange(attacker, skill))) return CombatSkillResult.OutOfRange; // Use this.UseSkillOnTarget(attacker, skill, target); return CombatSkillResult.Okay; }
/// <summary> /// Completes skill, healing the target. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Complete(Creature creature, Skill skill, Packet packet) { var entityId = packet.GetLong(); var unkInt1 = packet.GetInt(); var unkInt2 = packet.GetInt(); // Get target var target = ChannelServer.Instance.World.GetCreature(entityId); if (target == null) { Send.Notice(creature, Localization.Get("Invalid target.")); goto L_End; } // Check range if (!creature.GetPosition().InRange(target.GetPosition(), Range)) { Send.Notice(creature, Localization.Get("Out of range.")); goto L_End; } // Check bandage, make sure he still has the item and that // it wasn't switched with something else somehow. if (creature.Temp.SkillItem1 == null || !creature.Temp.SkillItem1.HasTag("/bandage/") || !creature.Inventory.Has(creature.Temp.SkillItem1)) { Log.Warning("FirstAid.Complete: Creature '{0:X16}' apparently switched the skill item somehow, between Ready and Complete.", creature.EntityId); Send.Notice(creature, Localization.Get("Invalid bandage.")); goto L_End; } // Remove bandage if (!creature.Inventory.Decrement(creature.Temp.SkillItem1)) { Log.Error("FirstAid.Complete: Decrementing the skill item failed somehow."); Send.Notice(creature, Localization.Get("Unknown error.")); goto L_End; } // Fails if target is moving. if (target.IsMoving) { // Unofficial Send.Notice(creature, Localization.Get("Failed because target was moving.")); // Fail motion? goto L_End; } // Heal injuries var rnd = RandomProvider.Get(); var heal = rnd.Next((int)skill.RankData.Var1, (int)skill.RankData.Var2 + 1); // Add bonus from higher grade bandages if (creature.Temp.SkillItem1.HasTag("/common_grade/")) heal += 3; else if (creature.Temp.SkillItem1.HasTag("/high_grade/")) heal += 6; else if (creature.Temp.SkillItem1.HasTag("/highest_grade/")) heal += 10; // 50% efficiency if target isn't resting if (!target.Has(CreatureStates.SitDown)) heal /= 2; target.Injuries -= heal; Send.StatUpdateDefault(target); // Skill training if (skill.Info.Rank == SkillRank.Novice) skill.Train(1); // Use First Aid. // First Aid animation Send.Effect(creature, Effect.UseMagic, "healing_firstaid", entityId); L_End: Send.SkillComplete(creature, skill.Info.Id, entityId, unkInt1, unkInt2); }
/// <summary> /// Checks if creature is able to enter a dungeon with the given item, /// at his current position, if so, a dungeon is created and the /// party is moved inside. /// </summary> /// <param name="creature"></param> /// <param name="item"></param> /// <returns></returns> public bool CheckDrop(Creature creature, Item item) { var currentRegionId = creature.RegionId; if (!_entryRegionIds.Contains(currentRegionId)) return false; var pos = creature.GetPosition(); var clientEvent = creature.Region.GetClientEvent(a => a.Data.IsAltar); if (clientEvent == null) { Log.Warning("DungeonManager.CheckDrop: No altar found."); return false; } if (!clientEvent.IsInside(pos.X, pos.Y)) { // Tell player to step on altar? return false; } var parameter = clientEvent.Data.Parameters.FirstOrDefault(a => a.EventType == EventType.Altar); if (parameter == null || parameter.XML == null || parameter.XML.Attribute("dungeonname") == null) { Log.Warning("DungeonManager.CheckDrop: No dungeon name found in altar event '{0:X16}'.", clientEvent.EntityId); return false; } var dungeonName = parameter.XML.Attribute("dungeonname").Value.ToLower(); // Check script var dungeonScript = ChannelServer.Instance.ScriptManager.DungeonScripts.Get(dungeonName); if (dungeonScript == null) { Send.ServerMessage(creature, "This dungeon hasn't been added yet."); Log.Warning("DungeonManager.CheckDrop: No routing dungeon script found for '{0}'.", dungeonName); return false; } // Check route if (!dungeonScript.Route(creature, item, ref dungeonName)) { // The response in case of a fail is handled by the router. return false; } // Check party if (creature.IsInParty && creature.Party.Leader != creature) { // Unofficial Send.Notice(creature, Localization.Get("Only the leader may create the dungeon.")); return false; } return this.CreateDungeonAndWarp(dungeonName, item.Info.Id, creature); }