/// <summary> /// Performs the auto attack from the <see cref="Attacker"/> to it's <see cref="Target"/>. /// </summary> /// <param name="context">The context of the operation.</param> private void PerformAttack(IOperationContext context) { var rng = new Random(); // Calculate the damage to inflict without any protections and reductions, // i.e. the amount of damage that the attacker can generate as it is. var attackPower = 1 + rng.Next(this.Attacker == null ? 10 : (int)this.Attacker.Skills[SkillType.NoWeapon].Level); var damageToApplyInfo = new DamageInfo(attackPower); var damageDoneInfo = this.Target.ApplyDamage(damageToApplyInfo, this.Attacker?.Id ?? 0); var distanceOfAttack = (this.Target.Location - (this.Attacker?.Location ?? this.Target.Location)).MaxValueIn2D; var packetsToSend = new List <IOutboundPacket> { new MagicEffectPacket(this.Target.Location, damageDoneInfo.Effect), }; if (damageDoneInfo.Damage != 0) { TextColor damageTextColor = damageDoneInfo.Blood switch { BloodType.Bones => TextColor.LightGrey, BloodType.Fire => TextColor.Orange, BloodType.Slime => TextColor.Green, _ => TextColor.Red, }; if (damageDoneInfo.Damage < 0) { damageTextColor = TextColor.LightBlue; } packetsToSend.Add(new AnimatedTextPacket(this.Target.Location, damageTextColor, Math.Abs(damageDoneInfo.Damage).ToString())); } if (distanceOfAttack > 1) { // TODO: actual projectile value. packetsToSend.Add(new ProjectilePacket(this.Attacker.Location, this.Target.Location, ProjectileType.Bolt)); } this.Target.ConsumeCredits(CombatCreditType.Defense, 1); this.Target.Skills[SkillType.Shield].IncreaseCounter(1); if (damageDoneInfo.ApplyBloodToEnvironment) { context.GameApi.CreateItemAtLocation(this.Target.Location, context.PredefinedItemSet.FindSplatterForBloodType(this.Target.BloodType)); } // Normalize the attacker's defense speed based on the global round time and round that up. context.Scheduler.ScheduleEvent( new RestoreCombatCreditOperation(this.Target, CombatCreditType.Defense), TimeSpan.FromMilliseconds((int)Math.Round(CombatConstants.DefaultCombatRoundTimeInMs / this.Target.DefenseSpeed))); if (this.Attacker != null) { // this.Target.RecordDamageTaken(this.Attacker.Id, damageToApply); this.Attacker.ConsumeCredits(CombatCreditType.Attack, 1); // TODO: increase the actual skill. this.Attacker.Skills[SkillType.NoWeapon].IncreaseCounter(1); // Normalize the attacker's attack speed based on the global round time and round that up. context.Scheduler.ScheduleEvent( new RestoreCombatCreditOperation(this.Attacker, CombatCreditType.Attack), TimeSpan.FromMilliseconds((int)Math.Round(CombatConstants.DefaultCombatRoundTimeInMs / this.Attacker.AttackSpeed))); if (this.Attacker.Location != this.Target.Location && this.Attacker.Id != this.Target.Id) { var directionToTarget = this.Attacker.Location.DirectionTo(this.Target.Location); var turnToDirOp = new TurnToDirectionOperation(this.Attacker, directionToTarget); context.Scheduler.ScheduleEvent(turnToDirOp); } } this.SendNotification(context, new GenericNotification(() => context.Map.PlayersThatCanSee(this.Target.Location), packetsToSend.ToArray())); if (this.Target is IPlayer targetPlayer) { var squarePacket = new SquarePacket(this.Attacker.Id, SquareColor.Black); this.SendNotification(context, new GenericNotification(() => targetPlayer.YieldSingleItem(), squarePacket)); } }
/// <summary> /// Performs the auto attack from the <see cref="Attacker"/> to it's <see cref="Target"/>. /// </summary> /// <param name="context">The context of the operation.</param> private void PerformAttack(IOperationContext context) { // TODO: this method has grown pretty big, it should at least be broken down when formulas get implemented. var rng = new Random(); // Calculate the damage to inflict without any protections and reductions, // i.e. the amount of damage that the attacker can generate as it is. var attackPower = 1 + rng.Next(this.Attacker == null ? 10 : (int)this.Attacker.Skills[SkillType.NoWeapon].Level); var damageToApplyInfo = new DamageInfo(attackPower, this.Attacker); var damageDoneInfo = this.Target.ApplyDamage(damageToApplyInfo, this.Attacker?.Id ?? 0); var distanceOfAttack = (this.Target.Location - (this.Attacker?.Location ?? this.Target.Location)).MaxValueIn2D; var packetsToSendToTargetPlayer = new List <IOutboundPacket>(); var packetsToSendToAllSpectators = new List <IOutboundPacket> { new MagicEffectPacket(this.Target.Location, damageDoneInfo.Effect), }; if (damageDoneInfo.Damage != 0) { TextColor damageTextColor = damageDoneInfo.Blood switch { BloodType.Bones => TextColor.LightGrey, BloodType.Fire => TextColor.Orange, BloodType.Slime => TextColor.Green, _ => TextColor.Red, }; if (damageDoneInfo.Damage < 0) { damageTextColor = TextColor.LightBlue; } else if (this.Target is IPlayer) { var hitpointsLostMessage = $"You lose {damageDoneInfo.Damage} hitpoints"; if (damageDoneInfo.Dealer != null) { var name = string.IsNullOrWhiteSpace(damageDoneInfo.Dealer.Article) ? damageDoneInfo.Dealer.Name : $"{damageDoneInfo.Dealer.Article} {damageDoneInfo.Dealer.Name}"; hitpointsLostMessage += $" due to an attack by {name}"; } hitpointsLostMessage += "."; packetsToSendToTargetPlayer.Add(new TextMessagePacket(MessageType.StatusDefault, hitpointsLostMessage)); } packetsToSendToAllSpectators.Add(new AnimatedTextPacket(this.Target.Location, damageTextColor, Math.Abs(damageDoneInfo.Damage).ToString())); } if (distanceOfAttack > 1) { // TODO: actual projectile value. packetsToSendToAllSpectators.Add(new ProjectilePacket(this.Attacker.Location, this.Target.Location, ProjectileType.Bolt)); } if (this.Target.Stats[CreatureStat.DefensePoints].Decrease(1)) { this.Target.Skills[SkillType.Shield].IncreaseCounter(1); context.Scheduler.ScheduleEvent( new StatChangeOperation(this.Target, CreatureStat.DefensePoints), TimeSpan.FromMilliseconds((int)Math.Round(CombatConstants.DefaultCombatRoundTimeInMs / this.Target.DefenseSpeed))); } if (damageDoneInfo.ApplyBloodToEnvironment) { context.GameApi.CreateItemAtLocation(this.Target.Location, context.PredefinedItemSet.FindSplatterForBloodType(this.Target.BloodType)); } if (this.Attacker != null) { packetsToSendToTargetPlayer.Add(new SquarePacket(this.Attacker.Id, SquareColor.Black)); if (this.Attacker.Stats[CreatureStat.AttackPoints].Decrease(1)) { // TODO: increase the actual skill. this.Attacker.Skills[SkillType.NoWeapon].IncreaseCounter(1); context.Scheduler.ScheduleEvent( new StatChangeOperation(this.Attacker, CreatureStat.AttackPoints), TimeSpan.FromMilliseconds((int)Math.Round(CombatConstants.DefaultCombatRoundTimeInMs / this.Attacker.AttackSpeed))); } if (this.Attacker.Location != this.Target.Location && this.Attacker.Id != this.Target.Id) { var directionToTarget = this.Attacker.Location.DirectionTo(this.Target.Location); var turnToDirOp = new TurnToDirectionOperation(this.Attacker, directionToTarget); context.Scheduler.ScheduleEvent(turnToDirOp); } if (this.Attacker is IPlayer attackerPlayer) { context.GameApi.AddOrAggregateCondition( attackerPlayer, new InFightCondition(attackerPlayer), duration: TimeSpan.FromMilliseconds(CombatConstants.DefaultInFightTimeInMs)); } } this.SendNotification(context, new GenericNotification(() => context.Map.PlayersThatCanSee(this.Target.Location), packetsToSendToAllSpectators.ToArray())); if (this.Target is IPlayer targetPlayer) { this.SendNotification(context, new GenericNotification(() => targetPlayer.YieldSingleItem(), packetsToSendToTargetPlayer.ToArray())); context.GameApi.AddOrAggregateCondition( targetPlayer, new InFightCondition(targetPlayer), duration: TimeSpan.FromMilliseconds(CombatConstants.DefaultInFightTimeInMs)); } }