/// <summary> /// Execute an attack round /// </summary> /// <param name="actor">the person attacking</param> /// <param name="target">the person defending</param> public static bool ExecuteRound(IMobile actor, IMobile target) { //Stagger clearance comes before ending a fight if (actor.Stagger > 0) { actor.Stagger -= 1; //Send a ui update actor.WriteTo(null); return(true); } if (!actor.IsFighting()) { return(false); } if (actor == target) { target = null; } IFightingArt attack = actor.LastAttack; var rand = new Random(); //If we lack an attack or we're on the tail end of the attack just find a new one and start it if (attack == null || !actor.Executing) { IFightingArtCombination myCombo = actor.LastCombo; if (myCombo == null) { var weights = GetUsageWeights(actor, target); ulong distance = 0; if (target != null) { distance = 1; } var validCombos = actor.Combos.Where(combo => combo.IsValid(actor, target, distance)); if (validCombos.Count() == 0) { myCombo = actor.Combos.OrderByDescending(combo => weights[combo.SituationalUsage] * rand.NextDouble()).FirstOrDefault(); } else { myCombo = validCombos.OrderByDescending(combo => weights[combo.SituationalUsage] * rand.NextDouble()).FirstOrDefault(); } } //uhh k we need to use a fake combo logic to get a random attack if (myCombo == null) { var attacks = TemplateCache.GetAll <IFightingArt>(true); attack = attacks.Where(atk => atk.IsValid(actor, target, (ulong)Math.Abs(actor.Balance))).OrderBy(atk => Guid.NewGuid()).FirstOrDefault(); } else { attack = myCombo.GetNext(actor.LastAttack); } if (attack == null) { //we have no valid attacks, so we're tired actor.Stagger += 10; actor.Sleep(1); //recover stamina actor.WriteTo(null); return(true); } actor.Stagger = attack.Setup; actor.Sturdy = attack.Armor; actor.LastCombo = myCombo; actor.LastAttack = attack; actor.Executing = true; if (actor.Stagger > 0) { actor.Stagger -= 1; //Send a ui update actor.WriteTo(null); return(true); } //else we just run right into the combo if there's no setup } //execute the attack actor.Executing = false; //basics actor.Stagger = attack.Recovery; actor.LastAttack = attack; actor.LastCombo = null; actor.Balance = attack.DistanceChange; //numbers damage if (attack.Health.Actor > 0) { actor.Harm(attack.Health.Actor); } if (attack.Stamina.Actor > 0) { actor.Exhaust(attack.Stamina.Actor); } Tuple <string, string, string> messaging = null; if (target != null) { target.Balance = -1 * attack.DistanceChange; var targetReadiness = ReadinessState.Offensive; //attacking is default var targetDirection = AnatomyAim.Mid; //mid is default if (target.LastAttack != null) { targetReadiness = target.LastAttack.Readiness; targetDirection = target.LastAttack.Aim; } //TODO: for now we're just doing flat chances for avoidance states since we have no stats to base anything on var avoided = false; var blocked = false; double impact = attack.Impact; double damage = attack.Health.Victim; double staminaDrain = attack.Stamina.Victim; var aimDifferential = Math.Abs(attack.Aim - targetDirection); //no blocking if it's not an attack if (attack.Readiness != ReadinessState.Offensive || attack.Health.Victim > 0) { switch (targetReadiness) { case ReadinessState.Circle: //circle is dodge essentially avoided = rand.Next(0, 100) <= Math.Max(1, Math.Abs(target.Balance)) / (Math.Max(1, aimDifferential + target.Stagger) * 4) * 100; break; case ReadinessState.Block: blocked = rand.Next(0, 100) <= Math.Max(1, Math.Abs(target.Balance)) / (Math.Max(1, aimDifferential + target.Stagger) * 2) * 100; if (blocked) { impact *= .25; damage *= .50; staminaDrain *= .50; } ; break; case ReadinessState.Deflect: blocked = rand.Next(0, 100) <= Math.Max(1, Math.Abs(target.Balance)) / (Math.Max(1, aimDifferential + target.Stagger) * 2) * 100; if (blocked) { impact *= .50; damage *= .25; staminaDrain *= .25; } break; case ReadinessState.Redirect: avoided = rand.Next(0, 100) <= Math.Max(1, Math.Abs(target.Balance)) / (Math.Max(1, aimDifferential + target.Stagger) * 20) * 100; break; case ReadinessState.Offensive: //Clash mechanics, only works if the target is mid-execution if (target.LastAttack != null && aimDifferential == 0 && target.Executing) { var impactDifference = target.LastAttack.Impact + target.LastAttack.Armor - attack.Impact; if (impactDifference > 0) { blocked = true; impact = 0; damage /= impactDifference; staminaDrain *= .75; } } break; } } messaging = GetOutputStrings(attack, target, avoided || blocked, targetReadiness); if (!avoided) { var victStagger = target.Sturdy - (int)Math.Truncate(impact); //Affect the victim, sturdy/armor absorbs stagger impact and the remainder gets added to victim stagger target.Sturdy = Math.Max(0, victStagger); target.Stagger += Math.Abs(Math.Min(0, victStagger)); if (damage > 0) { target.Harm((int)Math.Truncate(damage)); } if (staminaDrain > 0) { target.Exhaust((int)Math.Truncate(staminaDrain)); } //affect qualities if (attack.QualityValue != 0 && !string.IsNullOrWhiteSpace(attack.ResultQuality)) { target.SetQuality(attack.QualityValue, attack.ResultQuality, attack.AdditiveQuality); } } } else { messaging = GetOutputStrings(attack, target, false, ReadinessState.Offensive); } var msg = new Message(new LexicalParagraph(messaging.Item1)) { ToTarget = new LexicalParagraph[] { new LexicalParagraph(messaging.Item2) }, ToOrigin = new LexicalParagraph[] { new LexicalParagraph(messaging.Item3) } }; msg.ExecuteMessaging(actor, null, target, actor.CurrentLocation.CurrentContainer, null, true); return(true); }