feat: add combat and deleveling AI
This commit is contained in:
15
Client/Domain/AI/State/AnyState.cs
Normal file
15
Client/Domain/AI/State/AnyState.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
28
Client/Domain/AI/State/AttackGuardState.cs
Normal file
28
Client/Domain/AI/State/AttackGuardState.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Client/Domain/AI/State/AttackState.cs
Normal file
72
Client/Domain/AI/State/AttackState.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
85
Client/Domain/AI/State/BaseState.cs
Normal file
85
Client/Domain/AI/State/BaseState.cs
Normal 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;
|
||||
}
|
||||
}
|
22
Client/Domain/AI/State/DeadState.cs
Normal file
22
Client/Domain/AI/State/DeadState.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
28
Client/Domain/AI/State/FindGuardState.cs
Normal file
28
Client/Domain/AI/State/FindGuardState.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
Client/Domain/AI/State/FindTargetState.cs
Normal file
34
Client/Domain/AI/State/FindTargetState.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
Client/Domain/AI/State/IdleState.cs
Normal file
17
Client/Domain/AI/State/IdleState.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
37
Client/Domain/AI/State/MoveToSpotState.cs
Normal file
37
Client/Domain/AI/State/MoveToSpotState.cs
Normal 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
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
34
Client/Domain/AI/State/MoveToTargetState.cs
Normal file
34
Client/Domain/AI/State/MoveToTargetState.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
Client/Domain/AI/State/PickupState.cs
Normal file
87
Client/Domain/AI/State/PickupState.cs
Normal 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>();
|
||||
}
|
||||
}
|
42
Client/Domain/AI/State/RestState.cs
Normal file
42
Client/Domain/AI/State/RestState.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user