2018-06-29 22:04:54 -04:00
using ACE.Entity ;
using ACE.Entity.Enum ;
2018-12-20 07:49:03 -05:00
using ACE.Server.Entity ;
2018-06-29 22:04:54 -04:00
using ACE.Server.Managers ;
using ACE.Server.Network ;
using ACE.Server.WorldObjects ;
2019-04-12 18:07:45 -04:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2022-02-08 00:03:42 -05:00
using System.Numerics ;
2019-04-12 18:07:45 -04:00
using System.Text.RegularExpressions ;
2018-06-29 22:04:54 -04:00
namespace ACE.Server.Command
{
/// <summary>
/// Command handler sanity preserving parameter parsing
/// </summary>
public class CommandParameterHelpers
{
/// <summary>
/// Types of parameter values
/// </summary>
public enum ACECommandParameterType
{
/// <summary>
/// don't use this one
/// </summary>
Invalid ,
/// <summary>
2019-04-12 18:07:45 -04:00
/// normal coordinates, example: 37.3s,67w<para/>
2018-06-29 22:04:54 -04:00
/// output is of type ACE.Entity.Position
/// </summary>
Location ,
/// <summary>
2019-04-12 18:07:45 -04:00
/// a character name of an online player, example: the fat bastard<para/>
/// output is of type ACE.Server.WorldObjects.Player<para/>
/// only one of OnlinePlayerName or OnlinePlayerNameOrIid or PlayerName parameter can be used for one command<para/>
/// must be the first parameter
2018-06-29 22:04:54 -04:00
/// </summary>
2019-04-12 18:07:45 -04:00
OnlinePlayerName ,
2018-06-29 22:04:54 -04:00
/// <summary>
2019-04-12 18:07:45 -04:00
/// a character name of an online player, example: the fat bastard or a decimal iid, example: 1342177281<para/>
/// output is of type ACE.Server.WorldObjects.Player<para/>
/// only one of OnlinePlayerName or OnlinePlayerNameOrIid or PlayerName parameter can be used for one command<para/>
/// must be the first parameter
/// </summary>
OnlinePlayerNameOrIid ,
/// <summary>
/// a decimal or hexadecimal iid of an online player, examples: 1342177292 or 0x5000000C<para/>
/// output is of type ACE.Server.WorldObjects.Player<para/>
/// </summary>
OnlinePlayerIid ,
/// <summary>
/// a character name, example: the fat bastard<para/>
/// output is of type string<para/>
/// only one of OnlinePlayerName or OnlinePlayerNameOrIid or PlayerName parameter can be used for one command<para/>
/// must be the first parameter
/// </summary>
PlayerName ,
/// <summary>
/// a url, example: http://someserver.net:4321/?get=blah <para/>
/// output is of type System.Uri
/// </summary>
Uri ,
/// <summary>
/// a number, example: 01231242<para/>
2018-06-29 22:04:54 -04:00
/// output is of type ulong: 1231242
/// </summary>
2018-12-19 13:07:18 -05:00
ULong ,
/// <summary>
2019-04-12 18:07:45 -04:00
/// a number, example: -01231242<para/>
2018-12-19 13:07:18 -05:00
/// output is of type long: -1231242
/// </summary>
Long ,
/// <summary>
2019-04-12 18:07:45 -04:00
/// a number, example: 01231242<para/>
2018-12-19 13:07:18 -05:00
/// output is of type long: 1231242
/// </summary>
2019-04-12 18:07:45 -04:00
PositiveLong ,
/// <summary>
/// some text enclosed in double quotes, example: "the problem is solved"<para/>
/// Note: To accept this kind of parameter IncludeRaw must be true for the command handler attribute decoration and RawIncluded argument must be true for the call to ResolveACEParameters
/// </summary>
DoubleQuoteEnclosedText ,
/// <summary>
/// some text preceeded by a comma, example (the part after char): /hug my girl, char, the reasons are plenty!<para/>
/// Note: To accept this kind of parameter IncludeRaw must be true for the command handler attribute decoration and RawIncluded argument must be true for the call to ResolveACEParameters<para/>
/// limit 1 per command, may not include commas
/// </summary>
CommaPrefixedText ,
/// <summary>
/// a word, example: boat<para/>
/// output is of type string<para/>
/// word may be one or more of case insensitive a-z, 0-9, and underscore<para/>
/// </summary>
SimpleWord ,
/// <summary>
/// an element of an enum<para/>
/// output is of enum type you supply as PossibleValues<para/>
/// set the PossibleValues object to the type of enum<para/>
/// </summary>
Enum ,
2018-06-29 22:04:54 -04:00
}
/// <summary>
/// A player supplied parameter
/// </summary>
public class ACECommandParameter
{
/// <summary>
/// The type of the parameter value
/// </summary>
public ACECommandParameterType Type { get ; set ; } = ACECommandParameterType . Invalid ;
/// <summary>
/// The value to use upon missing or invalid supplied parameter
/// </summary>
public object DefaultValue { get ; set ; } = null ;
/// <summary>
/// If this parameter is required
/// </summary>
public bool Required { get ; set ; } = false ;
/// <summary>
/// The resultant parsed Value (or the default value)
/// </summary>
public object Value { get ; set ; } = null ;
2019-04-12 18:07:45 -04:00
public Position AsPosition = > ( Position ) Value ;
public Player AsPlayer = > ( Player ) Value ;
public ulong AsULong = > ( ulong ) Value ;
public long AsLong = > ( long ) Value ;
public long AsInt = > ( int ) Value ;
public string AsString = > ( string ) Value ;
public Uri AsUri = > ( Uri ) Value ;
2018-06-29 22:04:54 -04:00
/// <summary>
/// The parameter either wasn't supplied or was invalid (doesn't parse, player doesn't exist, etc.)
/// </summary>
public bool Defaulted { get ; set ; } = true ;
/// <summary>
/// The broadcast message to send to the session when the parameter is required and didn't parse or wasn't supplied.
/// </summary>
public string ErrorMessage { get ; set ; } = null ;
/// <summary>
/// Automatically assigned during GetParameters procedure
/// </summary>
public int ParameterNo { get ; set ; } = - 1 ;
2019-04-12 18:07:45 -04:00
/// <summary>
/// if the type is enum set this to the enum that the parameter should be part of
/// </summary>
public Type PossibleValues { get ; set ; } = null ;
2018-06-29 22:04:54 -04:00
}
/// <summary>
/// Resolve the parameters supplied by the player into usable values.
/// </summary>
/// <param name="session">the session of the player who sent the command</param>
2019-04-12 18:07:45 -04:00
/// <param name="aceParsedParameters">the collection of parameters supplied by the default parameter parser</param>
2018-06-29 22:04:54 -04:00
/// <param name="parameters">the resolution details for every parameter</param>
2019-04-12 18:07:45 -04:00
/// <param name="rawIncluded">whether or not the raw unparsed command line minus the command name was included as the first parameter</param>
2018-06-29 22:04:54 -04:00
/// <returns>the parameters were successfully resolved or not</returns>
2019-04-12 18:07:45 -04:00
public static bool ResolveACEParameters ( Session session , IEnumerable < string > aceParsedParameters , IEnumerable < ACECommandParameter > parameters , bool rawIncluded = false )
2018-06-29 22:04:54 -04:00
{
2019-04-12 18:07:45 -04:00
string parameterBlob = "" ;
if ( rawIncluded )
{
parameterBlob = aceParsedParameters . First ( ) ;
}
else
{
parameterBlob = aceParsedParameters . Count ( ) > 0 ? aceParsedParameters . Aggregate ( ( a , b ) = > a + " " + b ) . Trim ( new char [ ] { ' ' , ',' } ) : string . Empty ;
}
int commaCount = parameterBlob . Count ( x = > x = = ',' ) ;
List < ACECommandParameter > acps = parameters . ToList ( ) ;
2018-06-29 22:04:54 -04:00
for ( int i = acps . Count - 1 ; i > - 1 ; i - - )
{
2019-04-12 18:07:45 -04:00
ACECommandParameter acp = acps [ i ] ;
2018-06-29 22:04:54 -04:00
acp . ParameterNo = i + 1 ;
if ( parameterBlob . Length > 0 )
{
try
{
switch ( acp . Type )
{
2018-12-19 13:07:18 -05:00
case ACECommandParameterType . PositiveLong :
2019-04-12 18:07:45 -04:00
Match match4 = Regex . Match ( parameterBlob , @"(-?\d+)$" , RegexOptions . IgnoreCase ) ;
2018-12-19 13:07:18 -05:00
if ( match4 . Success )
{
if ( ! long . TryParse ( match4 . Groups [ 1 ] . Value , out long val ) )
{
return false ;
}
if ( val < = 0 )
{
return false ;
}
acp . Value = val ;
acp . Defaulted = false ;
2019-04-12 18:07:45 -04:00
parameterBlob = ( match4 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , match4 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' } ) ;
2018-12-19 13:07:18 -05:00
}
break ;
case ACECommandParameterType . Long :
2019-04-12 18:07:45 -04:00
Match match3 = Regex . Match ( parameterBlob , @"(-?\d+)$" , RegexOptions . IgnoreCase ) ;
2018-12-19 13:07:18 -05:00
if ( match3 . Success )
{
if ( ! long . TryParse ( match3 . Groups [ 1 ] . Value , out long val ) )
{
return false ;
}
acp . Value = val ;
acp . Defaulted = false ;
parameterBlob = ( match3 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , match3 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' , ',' } ) ;
}
break ;
2018-06-29 22:04:54 -04:00
case ACECommandParameterType . ULong :
2019-04-12 18:07:45 -04:00
Match match2 = Regex . Match ( parameterBlob , @"(-?\d+)$" , RegexOptions . IgnoreCase ) ;
2018-06-29 22:04:54 -04:00
if ( match2 . Success )
{
2018-12-19 13:07:18 -05:00
if ( ! ulong . TryParse ( match2 . Groups [ 1 ] . Value , out ulong val ) )
{
return false ;
}
acp . Value = val ;
2018-06-29 22:04:54 -04:00
acp . Defaulted = false ;
parameterBlob = ( match2 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , match2 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' , ',' } ) ;
}
break ;
case ACECommandParameterType . Location :
Position position = null ;
2019-04-12 18:07:45 -04:00
Match match = Regex . Match ( parameterBlob , @"([\d\.]+[ns])[^\d\.]*([\d\.]+[ew])$" , RegexOptions . IgnoreCase ) ;
2018-06-29 22:04:54 -04:00
if ( match . Success )
{
2019-04-12 18:07:45 -04:00
string ns = match . Groups [ 1 ] . Value ;
string ew = match . Groups [ 2 ] . Value ;
2018-06-29 22:04:54 -04:00
if ( ! TryParsePosition ( new string [ ] { ns , ew } , out string errorMessage , out position ) )
{
2019-04-12 18:07:45 -04:00
if ( session ! = null )
{
ChatPacket . SendServerMessage ( session , errorMessage , ChatMessageType . Broadcast ) ;
}
else
{
Console . WriteLine ( errorMessage ) ;
}
2018-06-29 22:04:54 -04:00
return false ;
}
else
{
acp . Value = position ;
acp . Defaulted = false ;
2019-04-12 18:07:45 -04:00
int coordsStartPos = Math . Min ( match . Groups [ 1 ] . Index , match . Groups [ 2 ] . Index ) ;
2018-06-29 22:04:54 -04:00
parameterBlob = ( coordsStartPos = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , coordsStartPos ) . Trim ( new char [ ] { ' ' , ',' } ) ;
}
}
break ;
2019-04-12 18:07:45 -04:00
case ACECommandParameterType . OnlinePlayerName :
if ( i ! = 0 )
{
throw new Exception ( "Player parameter must be the first parameter, since it can contain spaces." ) ;
}
parameterBlob = parameterBlob . TrimEnd ( new char [ ] { ' ' , ',' } ) ;
Player targetPlayer = PlayerManager . GetOnlinePlayer ( parameterBlob ) ;
2018-12-02 13:41:16 -06:00
if ( targetPlayer = = null )
2018-06-29 22:04:54 -04:00
{
2019-04-12 18:07:45 -04:00
string errorMsg = $"Unable to find player {parameterBlob}" ;
if ( session ! = null )
{
ChatPacket . SendServerMessage ( session , errorMsg , ChatMessageType . Broadcast ) ;
}
else
{
Console . WriteLine ( errorMsg ) ;
}
2018-06-29 22:04:54 -04:00
return false ;
}
else
{
2018-12-02 13:41:16 -06:00
acp . Value = targetPlayer ;
2018-06-29 22:04:54 -04:00
acp . Defaulted = false ;
}
break ;
2019-04-12 18:07:45 -04:00
case ACECommandParameterType . OnlinePlayerNameOrIid :
if ( i ! = 0 )
{
throw new Exception ( "Player parameter must be the first parameter, since it can contain spaces." ) ;
}
if ( ! parameterBlob . Contains ( ' ' ) )
{
if ( uint . TryParse ( parameterBlob , out uint iid ) )
{
Player targetPlayer2 = PlayerManager . GetOnlinePlayer ( iid ) ;
if ( targetPlayer2 = = null )
{
string logMsg = $"Unable to find player with iid {iid}" ;
if ( session ! = null )
{
ChatPacket . SendServerMessage ( session , logMsg , ChatMessageType . Broadcast ) ;
}
else
{
Console . WriteLine ( logMsg ) ;
}
return false ;
}
else
{
acp . Value = targetPlayer2 ;
acp . Defaulted = false ;
break ;
}
}
}
Player targetPlayer3 = PlayerManager . GetOnlinePlayer ( parameterBlob ) ;
if ( targetPlayer3 = = null )
{
string logMsg = $"Unable to find player {parameterBlob}" ;
if ( session ! = null )
{
ChatPacket . SendServerMessage ( session , logMsg , ChatMessageType . Broadcast ) ;
}
else
{
Console . WriteLine ( logMsg ) ;
}
return false ;
}
else
{
acp . Value = targetPlayer3 ;
acp . Defaulted = false ;
}
break ;
case ACECommandParameterType . OnlinePlayerIid :
Match matcha5 = Regex . Match ( parameterBlob , /*((i == 0) ? "" : @"\s+") +*/ @"(\d{10})$|(0x[0-9a-f]{8})$" , RegexOptions . IgnoreCase ) ;
if ( matcha5 . Success )
{
string strIid = "" ;
if ( matcha5 . Groups [ 2 ] . Success )
{
strIid = matcha5 . Groups [ 2 ] . Value ;
}
else if ( matcha5 . Groups [ 1 ] . Success )
{
strIid = matcha5 . Groups [ 1 ] . Value ;
}
try
{
uint iid = 0 ;
if ( strIid . StartsWith ( "0x" ) )
{
iid = Convert . ToUInt32 ( strIid , 16 ) ;
}
else
{
iid = uint . Parse ( strIid ) ;
}
Player targetPlayer2 = PlayerManager . GetOnlinePlayer ( iid ) ;
if ( targetPlayer2 = = null )
{
string logMsg = $"Unable to find player with iid {strIid}" ;
if ( session ! = null )
{
ChatPacket . SendServerMessage ( session , logMsg , ChatMessageType . Broadcast ) ;
}
else
{
Console . WriteLine ( logMsg ) ;
}
return false ;
}
else
{
acp . Value = targetPlayer2 ;
acp . Defaulted = false ;
parameterBlob = ( matcha5 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , matcha5 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' , ',' } ) ;
}
}
catch ( Exception )
{
string errorMsg = $"Unable to parse {strIid} into a player iid" ;
if ( session ! = null )
{
ChatPacket . SendServerMessage ( session , errorMsg , ChatMessageType . Broadcast ) ;
}
else
{
Console . WriteLine ( errorMsg ) ;
}
return false ;
}
}
break ;
case ACECommandParameterType . PlayerName :
if ( i ! = 0 )
{
throw new Exception ( "Player name parameter must be the first parameter, since it can contain spaces." ) ;
}
parameterBlob = parameterBlob . TrimEnd ( new char [ ] { ' ' , ',' } ) ;
if ( string . IsNullOrWhiteSpace ( parameterBlob ) )
{
break ;
}
else
{
acp . Value = parameterBlob ;
acp . Defaulted = false ;
}
break ;
case ACECommandParameterType . Uri :
Match match5 = Regex . Match ( parameterBlob , @"(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*))$" , RegexOptions . IgnoreCase ) ;
if ( match5 . Success )
{
string strUri = match5 . Groups [ 1 ] . Value ;
try
{
Uri url = new Uri ( strUri ) ;
acp . Value = url ;
acp . Defaulted = false ;
parameterBlob = ( match5 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , match5 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' , ',' } ) ;
}
catch ( Exception )
{
return false ;
}
}
break ;
case ACECommandParameterType . DoubleQuoteEnclosedText :
Match match6 = Regex . Match ( parameterBlob . TrimEnd ( ) , @"(\"".*\"")$" , RegexOptions . IgnoreCase ) ;
if ( match6 . Success )
{
string txt = match6 . Groups [ 1 ] . Value ;
try
{
acp . Value = txt . Trim ( '"' ) ;
acp . Defaulted = false ;
parameterBlob = ( match6 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , match6 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' , ',' } ) ;
}
catch ( Exception )
{
return false ;
}
}
break ;
case ACECommandParameterType . CommaPrefixedText :
if ( i = = 0 )
{
throw new Exception ( "this parameter type is not appropriate as the first parameter" ) ;
}
if ( i = = acps . Count - 1 & & ! acp . Required & & commaCount < acps . Count - 1 )
{
break ;
}
Match match7 = Regex . Match ( parameterBlob . TrimEnd ( ) , @"\,\s*([^,]*)$" , RegexOptions . IgnoreCase ) ;
if ( match7 . Success )
{
string txt = match7 . Groups [ 1 ] . Value ;
try
{
acp . Value = txt . TrimStart ( new char [ ] { ' ' , ',' } ) ;
acp . Defaulted = false ;
parameterBlob = ( match7 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , match7 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' , ',' } ) ;
}
catch ( Exception )
{
return false ;
}
}
break ;
case ACECommandParameterType . SimpleWord :
Match match8 = Regex . Match ( parameterBlob . TrimEnd ( ) , @"([a-zA-Z1-9_]+)\s*$" , RegexOptions . IgnoreCase ) ;
if ( match8 . Success )
{
string txt = match8 . Groups [ 1 ] . Value ;
try
{
acp . Value = txt . TrimStart ( ' ' ) ;
acp . Defaulted = false ;
parameterBlob = ( match8 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , match8 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' , ',' } ) ;
}
catch ( Exception )
{
return false ;
}
}
break ;
case ACECommandParameterType . Enum :
if ( acp . PossibleValues = = null )
{
throw new Exception ( "The enum parameter type must be accompanied by the PossibleValues" ) ;
}
if ( ! acp . PossibleValues . IsEnum )
{
throw new Exception ( "PossibleValues must be an enum type" ) ;
}
Match match9 = Regex . Match ( parameterBlob . TrimEnd ( ) , @"([a-zA-Z1-9_]+)\s*$" , RegexOptions . IgnoreCase ) ;
if ( match9 . Success )
{
string txt = match9 . Groups [ 1 ] . Value ;
try
{
txt = txt . Trim ( new char [ ] { ' ' , ',' } ) ;
Array etvs = Enum . GetValues ( acp . PossibleValues ) ;
foreach ( object etv in etvs )
{
if ( etv . ToString ( ) . ToLower ( ) = = txt . ToLower ( ) )
{
acp . Value = etv ;
acp . Defaulted = false ;
parameterBlob = ( match9 . Groups [ 1 ] . Index = = 0 ) ? string . Empty : parameterBlob . Substring ( 0 , match9 . Groups [ 1 ] . Index ) . Trim ( new char [ ] { ' ' } ) ;
break ;
}
}
}
catch ( Exception )
{
return false ;
}
}
break ;
2018-06-29 22:04:54 -04:00
}
}
catch
{
return false ;
}
}
2019-04-12 18:07:45 -04:00
if ( acp . Defaulted )
{
acp . Value = acp . DefaultValue ;
}
2018-06-29 22:04:54 -04:00
if ( acp . Required & & acp . Defaulted )
{
if ( ! string . IsNullOrWhiteSpace ( acp . ErrorMessage ) )
2019-04-12 18:07:45 -04:00
{
if ( session ! = null )
{
ChatPacket . SendServerMessage ( session , acp . ErrorMessage , ChatMessageType . Broadcast ) ;
}
else
{
Console . WriteLine ( acp . ErrorMessage ) ;
}
}
2018-06-29 22:04:54 -04:00
return false ;
}
}
return true ;
}
/// <summary>
/// Try to parse a player supplied coordinate into a position
/// </summary>
/// <param name="parameters">parameter array containing 2 contiguous elements: first is north/south, second is east/west</param>
/// <param name="errorMessage">the problem encountered while trying to parse</param>
/// <param name="position">the resultant ACE.Entity.Position</param>
/// <param name="startingElement">the first zero based element index of the 2 contiguous elements in the parameter array</param>
/// <returns>the parsing was successful or not</returns>
public static bool TryParsePosition ( string [ ] parameters , out string errorMessage , out Position position , int startingElement = 0 )
{
errorMessage = string . Empty ;
position = null ;
if ( parameters . Length - startingElement - 1 < 1 )
{
errorMessage = "not enough parameters" ;
return false ;
}
string northSouth = parameters [ startingElement ] . ToLower ( ) . Replace ( "," , "" ) . Trim ( ) ;
string eastWest = parameters [ startingElement + 1 ] . ToLower ( ) . Replace ( "," , "" ) . Trim ( ) ;
if ( ! northSouth . EndsWith ( "n" ) & & ! northSouth . EndsWith ( "s" ) )
{
errorMessage = "Missing n or s indicator on first parameter" ;
return false ;
}
if ( ! eastWest . EndsWith ( "e" ) & & ! eastWest . EndsWith ( "w" ) )
{
errorMessage = "Missing e or w indicator on second parameter" ;
return false ;
}
2019-04-12 18:07:45 -04:00
if ( ! float . TryParse ( northSouth . Substring ( 0 , northSouth . Length - 1 ) , out float coordNS ) )
2018-06-29 22:04:54 -04:00
{
errorMessage = "North/South coordinate is not a valid number." ;
return false ;
}
2019-04-12 18:07:45 -04:00
if ( ! float . TryParse ( eastWest . Substring ( 0 , eastWest . Length - 1 ) , out float coordEW ) )
2018-06-29 22:04:54 -04:00
{
errorMessage = "East/West coordinate is not a valid number." ;
return false ;
}
if ( northSouth . EndsWith ( "s" ) )
2019-04-12 18:07:45 -04:00
{
2018-06-29 22:04:54 -04:00
coordNS * = - 1.0f ;
2019-04-12 18:07:45 -04:00
}
2018-06-29 22:04:54 -04:00
if ( eastWest . EndsWith ( "w" ) )
2019-04-12 18:07:45 -04:00
{
2018-06-29 22:04:54 -04:00
coordEW * = - 1.0f ;
2019-04-12 18:07:45 -04:00
}
2018-06-29 22:04:54 -04:00
try
{
2022-02-08 00:03:42 -05:00
position = new Position ( new Vector2 ( coordEW , coordNS ) ) ;
2018-12-20 07:49:03 -05:00
position . AdjustMapCoords ( ) ;
2018-06-29 22:04:54 -04:00
}
2018-12-20 07:49:03 -05:00
catch ( Exception e )
2018-06-29 22:04:54 -04:00
{
2018-12-20 07:49:03 -05:00
Console . WriteLine ( e ) ;
2018-06-29 22:04:54 -04:00
errorMessage = $"There was a problem with that location (bad coordinates?)." ;
return false ;
}
return true ;
}
}
}