449 lines
17 KiB
C#
449 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using ClassicAssist.Shared;
|
|
using ClassicAssist.Data.Macros;
|
|
using ClassicAssist.Data.Macros.Commands;
|
|
using ClassicAssist.Shared.Resources;
|
|
using ClassicAssist.UO;
|
|
using ClassicAssist.UO.Data;
|
|
using ClassicAssist.UO.Network.Packets;
|
|
using ClassicAssist.UO.Objects;
|
|
using Newtonsoft.Json;
|
|
using UOC = ClassicAssist.Shared.UO.Commands;
|
|
|
|
namespace ClassicAssist.Data.Targeting
|
|
{
|
|
public class TargetManager
|
|
{
|
|
public delegate void dTargetChanged( int newSerial, int oldSerial );
|
|
|
|
private const int MAX_DISTANCE = 18;
|
|
private static TargetManager _instance;
|
|
private static readonly object _lock = new object();
|
|
private static readonly List<Mobile> _ignoreList = new List<Mobile>();
|
|
private readonly List<TargetBodyData> _bodyData;
|
|
|
|
private TargetManager()
|
|
{
|
|
string dataPath = Path.Combine( Engine.StartupPath ?? Environment.CurrentDirectory, "Data" );
|
|
|
|
_bodyData = JsonConvert
|
|
.DeserializeObject<TargetBodyData[]>( File.ReadAllText( Path.Combine( dataPath, "Bodies.json" ) ) )
|
|
.ToList();
|
|
}
|
|
|
|
public static event dTargetChanged EnemyChangedEvent;
|
|
public static event dTargetChanged FriendChangedEvent;
|
|
public static event dTargetChanged LastTargetChangedEvent;
|
|
|
|
public void SetEnemy( Entity m )
|
|
{
|
|
if ( !UOMath.IsMobile( m.Serial ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !MacroManager.QuietMode )
|
|
{
|
|
MsgCommands.HeadMsg( Options.CurrentOptions.EnemyTargetMessage, m.Serial );
|
|
}
|
|
|
|
MsgCommands.HeadMsg( $"Target: {m.Name?.Trim() ?? "Unknown"}" );
|
|
|
|
if ( m.Serial != Engine.Player.EnemyTargetSerial )
|
|
{
|
|
EnemyChangedEvent?.Invoke( m.Serial, Engine.Player.EnemyTargetSerial );
|
|
LastTargetChangedEvent?.Invoke( m.Serial, Engine.Player.LastTargetSerial );
|
|
}
|
|
|
|
Engine.Player.LastTargetSerial = m.Serial;
|
|
Engine.Player.EnemyTargetSerial = m.Serial;
|
|
Engine.SendPacketToClient( new ChangeCombatant( m.Serial ) );
|
|
}
|
|
|
|
public void SetFriend( Entity m )
|
|
{
|
|
if ( !UOMath.IsMobile( m.Serial ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !MacroManager.QuietMode )
|
|
{
|
|
MsgCommands.HeadMsg( Options.CurrentOptions.FriendTargetMessage, m.Serial );
|
|
}
|
|
|
|
MsgCommands.HeadMsg( $"Target: {m.Name?.Trim() ?? "Unknown"}" );
|
|
|
|
if ( m.Serial != Engine.Player.FriendTargetSerial )
|
|
{
|
|
FriendChangedEvent?.Invoke( m.Serial, Engine.Player.FriendTargetSerial );
|
|
LastTargetChangedEvent?.Invoke( m.Serial, Engine.Player.LastTargetSerial );
|
|
}
|
|
|
|
Engine.Player.LastTargetSerial = m.Serial;
|
|
Engine.Player.FriendTargetSerial = m.Serial;
|
|
Engine.SendPacketToClient( new ChangeCombatant( m.Serial ) );
|
|
}
|
|
|
|
public void SetLastTarget( Entity m )
|
|
{
|
|
if ( m.Serial != Engine.Player.LastTargetSerial )
|
|
{
|
|
LastTargetChangedEvent?.Invoke( m.Serial, Engine.Player.LastTargetSerial );
|
|
}
|
|
|
|
Engine.Player.LastTargetSerial = m.Serial;
|
|
|
|
if ( !UOMath.IsMobile( m.Serial ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !MacroManager.QuietMode )
|
|
{
|
|
MsgCommands.HeadMsg( Options.CurrentOptions.LastTargetMessage, m.Serial );
|
|
}
|
|
|
|
MsgCommands.HeadMsg( $"Target: {m.Name?.Trim() ?? "Unknown"}" );
|
|
Engine.SendPacketToClient( new ChangeCombatant( m.Serial ) );
|
|
}
|
|
|
|
public Mobile GetClosestMobile( IEnumerable<Notoriety> notoriety, TargetBodyType bodyType = TargetBodyType.Any,
|
|
TargetFriendType friendType = TargetFriendType.Include,
|
|
TargetInfliction inflictionType = TargetInfliction.Any )
|
|
{
|
|
Mobile mobile;
|
|
|
|
Func<int, bool> bodyTypePredicate;
|
|
|
|
switch ( bodyType )
|
|
{
|
|
case TargetBodyType.Any:
|
|
bodyTypePredicate = i => true;
|
|
break;
|
|
case TargetBodyType.Humanoid:
|
|
bodyTypePredicate = i =>
|
|
_bodyData.Where( bd => bd.BodyType == TargetBodyType.Humanoid ).Select( bd => bd.Graphic )
|
|
.Contains( i );
|
|
break;
|
|
case TargetBodyType.Transformation:
|
|
bodyTypePredicate = i =>
|
|
_bodyData.Where( bd => bd.BodyType == TargetBodyType.Transformation ).Select( bd => bd.Graphic )
|
|
.Contains( i );
|
|
break;
|
|
case TargetBodyType.Both:
|
|
bodyTypePredicate = i =>
|
|
_bodyData.Where( bd =>
|
|
bd.BodyType == TargetBodyType.Humanoid || bd.BodyType == TargetBodyType.Transformation )
|
|
.Select( bd => bd.Graphic ).Contains( i );
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException( nameof( bodyType ), bodyType, null );
|
|
}
|
|
|
|
if ( friendType == TargetFriendType.Only )
|
|
{
|
|
mobile = Engine.Mobiles.SelectEntities( m =>
|
|
MobileCommands.InFriendList( m.Serial ) && bodyTypePredicate( m.ID ) &&
|
|
( !Options.CurrentOptions.GetFriendEnemyUsesIgnoreList ||
|
|
!ObjectCommands.IgnoreList.Contains( m.Serial ) ) && m.Serial != Engine.Player?.Serial )
|
|
.OrderBy( m => m.Distance ).ByInflication( inflictionType ).FirstOrDefault();
|
|
}
|
|
else
|
|
{
|
|
mobile = Engine.Mobiles.SelectEntities( m =>
|
|
notoriety.Contains( m.Notoriety ) && m.Distance < MAX_DISTANCE && bodyTypePredicate( m.ID ) &&
|
|
( friendType == TargetFriendType.Include || !MobileCommands.InFriendList( m.Serial ) ) &&
|
|
( !Options.CurrentOptions.GetFriendEnemyUsesIgnoreList ||
|
|
!ObjectCommands.IgnoreList.Contains( m.Serial ) ) && m.Serial != Engine.Player?.Serial )
|
|
.OrderBy( m => m.Distance ).ByInflication( inflictionType )?.FirstOrDefault();
|
|
}
|
|
|
|
return mobile;
|
|
}
|
|
|
|
public Mobile GetNextMobile( IEnumerable<Notoriety> notoriety, TargetBodyType bodyType = TargetBodyType.Any,
|
|
int distance = MAX_DISTANCE, TargetFriendType friendType = TargetFriendType.Include,
|
|
TargetInfliction inflictionType = TargetInfliction.Any, bool reverse = false, Mobile previousMobile = null )
|
|
{
|
|
bool looped = false;
|
|
|
|
while ( true )
|
|
{
|
|
Mobile[] mobiles;
|
|
|
|
Func<int, bool> bodyTypePredicate;
|
|
|
|
switch ( bodyType )
|
|
{
|
|
case TargetBodyType.Any:
|
|
bodyTypePredicate = i => true;
|
|
break;
|
|
case TargetBodyType.Humanoid:
|
|
bodyTypePredicate = i =>
|
|
_bodyData.Where( bd => bd.BodyType == TargetBodyType.Humanoid ).Select( bd => bd.Graphic )
|
|
.Contains( i );
|
|
break;
|
|
case TargetBodyType.Transformation:
|
|
bodyTypePredicate = i =>
|
|
_bodyData.Where( bd => bd.BodyType == TargetBodyType.Transformation )
|
|
.Select( bd => bd.Graphic ).Contains( i );
|
|
break;
|
|
case TargetBodyType.Both:
|
|
bodyTypePredicate = i =>
|
|
_bodyData.Where( bd =>
|
|
bd.BodyType == TargetBodyType.Humanoid ||
|
|
bd.BodyType == TargetBodyType.Transformation )
|
|
.Select( bd => bd.Graphic ).Contains( i );
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException( nameof( bodyType ), bodyType, null );
|
|
}
|
|
|
|
if ( previousMobile != null )
|
|
{
|
|
_ignoreList.Clear();
|
|
}
|
|
|
|
if ( friendType == TargetFriendType.Only )
|
|
{
|
|
//Notoriety, bodyType ignored
|
|
mobiles = Engine.Mobiles.SelectEntities( m =>
|
|
m.Distance < distance && MobileCommands.InFriendList( m.Serial ) && bodyTypePredicate( m.ID ) &&
|
|
!_ignoreList.Contains( m ) && ( !Options.CurrentOptions.GetFriendEnemyUsesIgnoreList ||
|
|
!ObjectCommands.IgnoreList.Contains( m.Serial ) ) &&
|
|
m.Serial != Engine.Player?.Serial );
|
|
}
|
|
else
|
|
{
|
|
mobiles = Engine.Mobiles.SelectEntities( m =>
|
|
notoriety.Contains( m.Notoriety ) && m.Distance < distance && bodyTypePredicate( m.ID ) &&
|
|
!_ignoreList.Contains( m ) &&
|
|
( friendType == TargetFriendType.Include || !MobileCommands.InFriendList( m.Serial ) ) &&
|
|
( !Options.CurrentOptions.GetFriendEnemyUsesIgnoreList ||
|
|
!ObjectCommands.IgnoreList.Contains( m.Serial ) ) && m.Serial != Engine.Player?.Serial );
|
|
}
|
|
|
|
if ( previousMobile != null )
|
|
{
|
|
mobiles = mobiles.Where( m => m.Serial != previousMobile.Serial ).OrderBy( m =>
|
|
Math.Max( Math.Abs( m.X - previousMobile.X ), Math.Abs( m.Y - previousMobile.Y ) ) ).ToArray();
|
|
|
|
if ( mobiles.Length == 0 )
|
|
{
|
|
mobiles = new[] { previousMobile };
|
|
}
|
|
}
|
|
|
|
if ( reverse )
|
|
{
|
|
mobiles = mobiles.Reverse().ToArray();
|
|
}
|
|
|
|
mobiles = mobiles.ByInflication( inflictionType );
|
|
|
|
if ( mobiles == null || mobiles.Length == 0 )
|
|
{
|
|
_ignoreList.Clear();
|
|
|
|
if ( looped )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
looped = true;
|
|
continue;
|
|
}
|
|
|
|
Mobile mobile = mobiles.FirstOrDefault();
|
|
_ignoreList.Add( mobile );
|
|
return mobile;
|
|
}
|
|
}
|
|
|
|
public Entity PromptTarget()
|
|
{
|
|
int serial = UOC.GetTargeSerialAsync( Strings.Target_object___ ).Result;
|
|
|
|
if ( serial == 0 )
|
|
{
|
|
UOC.SystemMessage( Strings.Invalid_or_unknown_object_id );
|
|
return null;
|
|
}
|
|
|
|
Mobile mobile = Engine.Mobiles.GetMobile( serial );
|
|
|
|
if ( mobile != null )
|
|
{
|
|
return mobile;
|
|
}
|
|
|
|
UOC.SystemMessage( Strings.Mobile_not_found___ );
|
|
return null;
|
|
}
|
|
|
|
public static TargetManager GetInstance()
|
|
{
|
|
// ReSharper disable once InvertIf
|
|
if ( _instance == null )
|
|
{
|
|
lock ( _lock )
|
|
{
|
|
if ( _instance != null )
|
|
{
|
|
return _instance;
|
|
}
|
|
|
|
_instance = new TargetManager();
|
|
return _instance;
|
|
}
|
|
}
|
|
|
|
return _instance;
|
|
}
|
|
|
|
public bool GetEnemy( TargetNotoriety notoFlags, TargetBodyType bodyType, TargetDistance targetDistance,
|
|
TargetFriendType friendType = TargetFriendType.None,
|
|
TargetInfliction inflictionType = TargetInfliction.Any )
|
|
{
|
|
Mobile m = GetMobile( notoFlags, bodyType, targetDistance, friendType, inflictionType,
|
|
targetDistance == TargetDistance.Nearest ? AliasCommands.GetAlias( "enemy" ) : 0 );
|
|
|
|
if ( m == null )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SetEnemy( m );
|
|
return true;
|
|
}
|
|
|
|
public bool GetFriend( TargetNotoriety notoFlags, TargetBodyType bodyType, TargetDistance targetDistance,
|
|
TargetFriendType friendType = TargetFriendType.Include,
|
|
TargetInfliction inflictionType = TargetInfliction.Any )
|
|
{
|
|
Mobile m = GetMobile( notoFlags, bodyType, targetDistance, friendType, inflictionType,
|
|
targetDistance == TargetDistance.Nearest ? AliasCommands.GetAlias( "friend" ) : 0 );
|
|
|
|
if ( m == null )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SetFriend( m );
|
|
return true;
|
|
}
|
|
|
|
public Mobile GetMobile( TargetNotoriety notoFlags, TargetBodyType bodyType, TargetDistance targetDistance,
|
|
TargetFriendType friendType, TargetInfliction inflictionType, int previousSerial = 0 )
|
|
{
|
|
Notoriety[] noto = NotoFlagsToArray( notoFlags );
|
|
|
|
Mobile m;
|
|
|
|
switch ( targetDistance )
|
|
{
|
|
case TargetDistance.Next:
|
|
|
|
m = GetNextMobile( noto, bodyType, MAX_DISTANCE, friendType, inflictionType );
|
|
|
|
break;
|
|
case TargetDistance.Nearest:
|
|
|
|
Mobile previousMobile = Engine.Mobiles.GetMobile( previousSerial );
|
|
|
|
if ( previousMobile == null || previousMobile.Distance > MAX_DISTANCE )
|
|
{
|
|
m = GetClosestMobile( noto, bodyType, friendType, inflictionType );
|
|
}
|
|
else
|
|
{
|
|
m = GetNextMobile( noto, bodyType, MAX_DISTANCE, friendType, inflictionType, false,
|
|
previousMobile );
|
|
}
|
|
|
|
break;
|
|
case TargetDistance.Closest:
|
|
|
|
m = GetClosestMobile( noto, bodyType, friendType, inflictionType );
|
|
|
|
break;
|
|
case TargetDistance.Previous:
|
|
|
|
m = GetNextMobile( noto, bodyType, MAX_DISTANCE, friendType, inflictionType, true );
|
|
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException( nameof( targetDistance ), targetDistance, null );
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
private static Notoriety[] NotoFlagsToArray( TargetNotoriety notoFlags )
|
|
{
|
|
List<Notoriety> notos = new List<Notoriety>();
|
|
|
|
if ( notoFlags.HasFlag( TargetNotoriety.Criminal ) || notoFlags.HasFlag( TargetNotoriety.Any ) )
|
|
{
|
|
notos.Add( Notoriety.Criminal );
|
|
}
|
|
|
|
if ( notoFlags.HasFlag( TargetNotoriety.Enemy ) || notoFlags.HasFlag( TargetNotoriety.Any ) )
|
|
{
|
|
notos.Add( Notoriety.Enemy );
|
|
}
|
|
|
|
if ( notoFlags.HasFlag( TargetNotoriety.Gray ) || notoFlags.HasFlag( TargetNotoriety.Any ) )
|
|
{
|
|
notos.Add( Notoriety.Attackable );
|
|
}
|
|
|
|
if ( notoFlags.HasFlag( TargetNotoriety.Innocent ) || notoFlags.HasFlag( TargetNotoriety.Any ) )
|
|
{
|
|
notos.Add( Notoriety.Innocent );
|
|
}
|
|
|
|
if ( notoFlags.HasFlag( TargetNotoriety.Murderer ) || notoFlags.HasFlag( TargetNotoriety.Any ) )
|
|
{
|
|
notos.Add( Notoriety.Murderer );
|
|
}
|
|
|
|
if ( notoFlags.HasFlag( TargetNotoriety.Friend ) || notoFlags.HasFlag( TargetNotoriety.Any ) )
|
|
{
|
|
notos.Add( Notoriety.Ally );
|
|
}
|
|
|
|
return notos.ToArray();
|
|
}
|
|
}
|
|
|
|
public static class MobileEnumerableExtensionMethods
|
|
{
|
|
public static Mobile[] ByInflication( this IEnumerable<Mobile> mobiles, TargetInfliction inflictionType )
|
|
{
|
|
switch ( inflictionType )
|
|
{
|
|
case TargetInfliction.Any:
|
|
return mobiles.ToArray();
|
|
case TargetInfliction.Lowest:
|
|
return mobiles.Where( m => m.Hits < m.HitsMax && !m.IsDead ).OrderBy( m => m.Hits ).ToArray();
|
|
case TargetInfliction.Poisoned:
|
|
return mobiles.Where( m => m.IsPoisoned ).ToArray();
|
|
case TargetInfliction.Mortaled:
|
|
return mobiles.Where( m => m.IsYellowHits ).ToArray();
|
|
case TargetInfliction.Paralyzed:
|
|
return mobiles.Where( m => m.IsFrozen ).ToArray();
|
|
case TargetInfliction.Dead:
|
|
return mobiles.Where( m => m.IsDead ).ToArray();
|
|
case TargetInfliction.Unmounted:
|
|
return mobiles.Where( m => m.GetLayer( Layer.Mount ) == 0 ).ToArray();
|
|
default:
|
|
throw new ArgumentOutOfRangeException( nameof( inflictionType ), inflictionType, null );
|
|
}
|
|
}
|
|
}
|
|
} |