feat: add combat and deleveling AI

This commit is contained in:
Иванов Иван
2024-08-15 17:23:24 +02:00
parent bdd026519f
commit 2943f7a50b
79 changed files with 61368 additions and 6746 deletions

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.AI.State
{
public class AnyState : BaseState
{
public AnyState(AI ai) : base(ai)
{
}
}
}

View File

@@ -0,0 +1,28 @@
using Client.Application.Components;
using Client.Domain.Entities;
using Client.Domain.Service;
namespace Client.Domain.AI.State
{
public class AttackGuardState : BaseState
{
public AttackGuardState(AI ai) : base(ai)
{
}
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
if (hero.Target == null)
{
return;
}
var skill = worldHandler.GetSkillById(config.Deleveling.SkillId);
if (skill != null && skill.IsReadyToUse && skill.Cost <= hero.VitalStats.Mp)
{
worldHandler.RequestUseSkill(skill.Id, true, false);
}
}
}
}

View File

@@ -0,0 +1,72 @@
using Client.Domain.AI.Combat;
using Client.Domain.Entities;
using Client.Domain.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Client.Domain.AI.State
{
public class AttackState : BaseState
{
public AttackState(AI ai) : base(ai)
{
}
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
if (hero.Target == null)
{
return;
}
if (!config.Combat.UseOnlySkills)
{
worldHandler.RequestAttackOrFollow(hero.Target.Id);
}
if (config.Combat.SpoilIfPossible)
{
NPC? npc = hero.Target as NPC;
var spoil = worldHandler.GetSkillById(config.Combat.SpoilSkillId);
if (spoil != null && npc != null && npc.SpoilState == Enums.SpoilStateEnum.None)
{
var excluded = config.Combat.ExcludedSpoilMobIds;
var included = config.Combat.IncludedSpoilMobIds;
if (!excluded.ContainsKey(npc.NpcId) && (included.Count == 0 || included.ContainsKey(npc.NpcId)))
{
if (spoil.IsReadyToUse && hero.VitalStats.Mp >= spoil.Cost)
{
worldHandler.RequestUseSkill(spoil.Id, false, false);
}
}
}
}
var skill = Helper.GetSkillByConfig(worldHandler, config, hero, hero.Target);
if (skill != null)
{
worldHandler.RequestUseSkill(skill.Id, false, false);
}
}
protected override void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
{
if (config.Combat.AutoUseShots)
{
// todo use only appropriate grade
foreach (var item in worldHandler.GetShotItems())
{
if (!item.IsAutoused)
{
worldHandler.RequestToggleAutouseSoulshot(item.Id);
}
}
}
}
}
}

View File

@@ -0,0 +1,85 @@
using Client.Domain.Entities;
using Client.Domain.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.AI.State
{
public abstract class BaseState
{
public enum Type
{
Any,
Attack,
Dead,
FindTarget,
Idle,
MoveToTarget,
Pickup,
Rest,
MoveToSpot,
AttackGuard,
FindGuard
}
public BaseState(AI ai)
{
this.ai = ai;
}
public void Execute()
{
var hero = ai.GetWorldHandler().Hero;
if (hero == null)
{
return;
}
DoExecute(ai.GetWorldHandler(), ai.GetConfig(), ai.GetAsyncPathMover(), hero);
}
public void OnEnter()
{
var hero = ai.GetWorldHandler().Hero;
if (hero == null)
{
return;
}
DoOnEnter(ai.GetWorldHandler(), ai.GetConfig(), hero);
}
public void OnLeave()
{
var hero = ai.GetWorldHandler().Hero;
if (hero == null)
{
return;
}
ai.GetAsyncPathMover().Unlock();
DoOnLeave(ai.GetWorldHandler(), ai.GetConfig(), hero);
}
protected virtual void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
}
protected virtual void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
{
}
protected virtual void DoOnLeave(WorldHandler worldHandler, Config config, Hero hero)
{
}
private readonly AI ai;
}
}

View File

@@ -0,0 +1,22 @@
using Client.Domain.Entities;
using Client.Domain.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.AI.State
{
public class DeadState : BaseState
{
public DeadState(AI ai) : base(ai)
{
}
protected override void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
{
worldHandler.RequestRestartPoint(Enums.RestartPointTypeEnum.Village);
}
}
}

View File

@@ -0,0 +1,28 @@
using Client.Domain.Entities;
using Client.Domain.Service;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.AI.State
{
public class FindGuardState : BaseState
{
public FindGuardState(AI ai) : base(ai)
{
}
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
var targetId = worldHandler.GetGuards().FirstOrDefault()?.Id;
if (targetId != null)
{
worldHandler.RequestAcquireTarget((uint)targetId);
}
}
}
}

View File

@@ -0,0 +1,34 @@
using Client.Domain.AI.Combat;
using Client.Domain.Entities;
using Client.Domain.Service;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.AI.State
{
public class FindTargetState : BaseState
{
public FindTargetState(AI ai) : base(ai)
{
}
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
uint? targetId = hero.AttackerIds.Count > 0 ? hero.AttackerIds.First() : null;
if (targetId == null)
{
targetId = Helper.GetMobsToAttackByConfig(worldHandler, config, hero).FirstOrDefault()?.Id;
}
if (targetId != null)
{
worldHandler.RequestAcquireTarget((uint)targetId);
}
}
}
}

View File

@@ -0,0 +1,17 @@
using Client.Domain.Entities;
using Client.Domain.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.AI.State
{
public class IdleState : BaseState
{
public IdleState(AI ai) : base(ai)
{
}
}
}

View File

@@ -0,0 +1,37 @@
using Client.Domain.Entities;
using Client.Domain.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Client.Domain.AI.State
{
public class MoveToSpotState : BaseState
{
public MoveToSpotState(AI ai) : base(ai)
{
}
protected override void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
{
worldHandler.RequestAcquireTarget(hero.Id);
}
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
if (asyncPathMover.IsLocked)
{
return;
}
asyncPathMover.MoveAsync(new ValueObjects.Vector3(
config.Combat.Zone.Center.X,
config.Combat.Zone.Center.Y,
hero.Transform.Position.Z
));
}
}
}

View File

@@ -0,0 +1,34 @@
using Client.Domain.AI.Combat;
using Client.Domain.Entities;
using Client.Domain.Service;
using Client.Infrastructure.Service;
namespace Client.Domain.AI.State
{
public class MoveToTargetState : BaseState
{
public MoveToTargetState(AI ai) : base(ai)
{
}
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
var target = hero.Target;
if (target == null)
{
target = hero;
}
var distance = hero.Transform.Position.HorizontalDistance(target.Transform.Position);
if (asyncPathMover.IsLocked)
{
return;
}
if (distance >= Helper.GetAttackDistanceByConfig(worldHandler, config, hero, target))
{
asyncPathMover.MoveAsync(target.Transform.Position);
}
}
}
}

View File

@@ -0,0 +1,87 @@
using Client.Domain.AI.Combat;
using Client.Domain.Entities;
using Client.Domain.Service;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Client.Domain.AI.State
{
public class PickupState : BaseState
{
public PickupState(AI ai) : base(ai)
{
}
public List<Drop> GetDrops(WorldHandler worldHandler, Config config)
{
var drops = Helper.GetDropByConfig(worldHandler, config);
for (var i = drops.Count - 1; i >= 0; i--)
{
if (pickupAttempts.ContainsKey(drops[0].Id) && pickupAttempts[drops[0].Id] > config.Combat.PickupAttemptsCount)
{
drops.RemoveAt(i);
}
}
return drops;
}
public bool IsSweeperMustBeUsed(WorldHandler worldHandler, Config config)
{
return GetSweepableMobs(worldHandler, config).Count > 0;
}
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
if (IsSweeperMustBeUsed(worldHandler, config))
{
var mob = GetSweepableMobs(worldHandler, config).First();
var sweeper = worldHandler.GetSkillById(config.Combat.SweeperSkillId);
if (sweeper != null && sweeper.IsReadyToUse && hero.VitalStats.Mp >= sweeper.Cost)
{
worldHandler.RequestAcquireTarget(mob.Id);
worldHandler.RequestUseSkill(sweeper.Id, false, false);
if (!sweepAttempts.ContainsKey(mob.Id))
{
sweepAttempts[mob.Id] = 0;
}
sweepAttempts[mob.Id]++;
}
}
if (!hero.Transform.IsMoving)
{
var drops = GetDrops(worldHandler, config);
if (drops.Count > 0)
{
worldHandler.RequestPickUp(drops[0].Id);
if (!pickupAttempts.ContainsKey(drops[0].Id))
{
pickupAttempts[drops[0].Id] = 0;
}
pickupAttempts[drops[0].Id]++;
}
}
}
protected override void DoOnLeave(WorldHandler worldHandler, Config config, Hero hero)
{
pickupAttempts.Clear();
sweepAttempts.Clear();
}
private List<NPC> GetSweepableMobs(WorldHandler worldHandler, Config config)
{
return worldHandler.GetDeadMobsSortedByDistanceToHero(config.Combat.MobsMaxDeltaZ)
.Where(x =>
{
return x.SpoilState == Enums.SpoilStateEnum.Sweepable &&
(!sweepAttempts.ContainsKey(x.Id) || sweepAttempts[x.Id] <= config.Combat.SweepAttemptsCount);
})
.ToList();
}
private Dictionary<uint, short> pickupAttempts = new Dictionary<uint, short>();
private Dictionary<uint, short> sweepAttempts = new Dictionary<uint, short>();
}
}

View File

@@ -0,0 +1,42 @@
using Client.Domain.Entities;
using Client.Domain.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client.Domain.AI.State
{
public class RestState : BaseState
{
public RestState(AI ai) : base(ai)
{
}
protected override void DoOnEnter(WorldHandler worldHandler, Config config, Hero hero)
{
worldHandler.RequestAcquireTarget(hero.Id);
}
protected override void DoExecute(WorldHandler worldHandler, Config config, AsyncPathMoverInterface asyncPathMover, Hero hero)
{
if (!hero.IsStanding)
{
return;
}
worldHandler.RequestSit();
}
protected override void DoOnLeave(WorldHandler worldHandler, Config config, Hero hero)
{
if (hero.IsStanding)
{
return;
}
worldHandler.RequestStand();
}
}
}