You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
452 lines
14 KiB
452 lines
14 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Mapper.Scripting;
|
|
|
|
namespace Mapper
|
|
{
|
|
public partial class Mapper
|
|
{
|
|
private List<Exit>[] _pGrid = new List<Exit>[4096];
|
|
private uint _pGridMax = 0;
|
|
private List<Exit>[] _pExits = new[] { new List<Exit>(), new List<Exit>() };
|
|
|
|
private void Path_Init()
|
|
{
|
|
for(int i = 0; i < _pGrid.Length; i++)
|
|
_pGrid[i] = new List<Exit>(512);
|
|
}
|
|
|
|
private bool CanUsePortal(Exit p, Pathfinder x)
|
|
{
|
|
if(!p.HasFlag("recallmechanic"))
|
|
{
|
|
if(!x.CanUsePortals)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(!x.CanUseRecalls)
|
|
return false;
|
|
}
|
|
|
|
if(p.MinLevel > x.CharacterLevel + x.CharacterTier * 10 ||
|
|
p.MaxLevel < x.CharacterLevel)
|
|
return false;
|
|
|
|
if(p.HasFlag("nogq") && x.IsGlobalQuest)
|
|
return false;
|
|
|
|
if(x.SkipPortals != null && x.SkipPortals.Contains(p))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static AreaScript GetScript(Room r)
|
|
{
|
|
return AreaScriptMgr.GetScript(r.Area.Entry);
|
|
}
|
|
|
|
public PathfindResult Get(Pathfinder p)
|
|
{
|
|
if(p.StartRooms != null)
|
|
p.StartRooms = p.StartRooms.Where(val => val != null).ToArray();
|
|
if(p.StartRooms == null || p.StartRooms.Length == 0)
|
|
return new PathfindResult() { Success = false };
|
|
|
|
for(int i = 0; i < _pGridMax; i++)
|
|
_pGrid[i].Clear();
|
|
_pExits[0].Clear();
|
|
_pExits[1].Clear();
|
|
_pGridMax = 0;
|
|
foreach(KeyValuePair<uint, Room> x in IRooms)
|
|
{
|
|
x.Value.Mapper_OpenBy = null;
|
|
x.Value.Mapper_OpenCost = 0;
|
|
}
|
|
|
|
p.OnStartedPathfind();
|
|
|
|
if(p.OverridePortals == null)
|
|
{
|
|
foreach(KeyValuePair<uint, Exit> x in IPortals)
|
|
{
|
|
if(!x.Value.HasFlag("disabled") && CanUsePortal(x.Value, p))
|
|
_pExits[!x.Value.HasFlag("recallmechanic") ? 0 : 1].Add(x.Value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach(Exit x in p.OverridePortals)
|
|
{
|
|
if(!x.HasFlag("disabled") && CanUsePortal(x, p))
|
|
_pExits[!x.HasFlag("recallmechanic") ? 0 : 1].Add(x);
|
|
}
|
|
}
|
|
|
|
Room Finish = null;
|
|
if(p.StartRooms != null)
|
|
{
|
|
foreach(Room r in p.StartRooms)
|
|
{
|
|
r.Mapper_OpenCost = uint.MaxValue;
|
|
if(OpenRoom(r, null, 0, p))
|
|
{
|
|
Finish = r;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int itr1 = 0, itr2 = 0;
|
|
if(Finish == null)
|
|
{
|
|
while(itr1 < _pGrid.Length && itr1 < _pGridMax)
|
|
{
|
|
if(itr2 >= _pGrid[itr1].Count)
|
|
{
|
|
itr2 = 0;
|
|
itr1++;
|
|
continue;
|
|
}
|
|
|
|
if(OpenRoom(_pGrid[itr1][itr2].To, _pGrid[itr1][itr2], (uint)itr1, p))
|
|
{
|
|
Finish = _pGrid[itr1][itr2].To;
|
|
break;
|
|
}
|
|
|
|
itr2++;
|
|
}
|
|
}
|
|
|
|
PathfindResult pr = new PathfindResult();
|
|
pr.Success = Finish != null;
|
|
pr.Cost = itr1;
|
|
pr.Target = Finish;
|
|
while(Finish != null && Finish.Mapper_OpenBy != null)
|
|
{
|
|
pr.Path.Insert(0, Finish.Mapper_OpenBy);
|
|
Finish = Finish.Mapper_OpenBy.From;
|
|
}
|
|
pr.Start = pr.Path.Count != 0 ? pr.Path[0].From : Finish;
|
|
|
|
p.OnEndedPathFind(pr);
|
|
return pr;
|
|
}
|
|
|
|
private bool OpenRoom(Room r, Exit from, uint cost, Pathfinder p)
|
|
{
|
|
if(r.Mapper_OpenBy != null || (from != null && p.StartRooms.Contains(r)) || r.Area.Entry == uint.MaxValue)
|
|
return false;
|
|
|
|
r.Mapper_OpenBy = from;
|
|
r.Mapper_OpenCost = cost;
|
|
|
|
if(p.IsTargetRoom(r))
|
|
return true;
|
|
|
|
if(_pExits[0].Count > 0 && !r.HasFlag("prison"))
|
|
{
|
|
for(int i = _pExits[0].Count - 1; i >= 0; i--)
|
|
{
|
|
if(_pExits[0][i].To.Mapper_OpenBy != null)
|
|
_pExits[0].RemoveAt(i);
|
|
else if(GetScript(r).CanUsePortal(_pExits[0][i], r) && p.CanUsePortal(_pExits[0][i], r))
|
|
{
|
|
_pExits[0][i].From = r;
|
|
AddExit(_pExits[0][i], p, true);
|
|
_pExits[0].RemoveAt(i);
|
|
}
|
|
}
|
|
}
|
|
if(_pExits[1].Count > 0 && !r.HasFlag("norecall"))
|
|
{
|
|
for(int i = _pExits[1].Count - 1; i >= 0; i--)
|
|
{
|
|
if(_pExits[1][i].To.Mapper_OpenBy != null)
|
|
_pExits[1].RemoveAt(i);
|
|
else if(GetScript(r).CanUsePortal(_pExits[1][i], r) && p.CanUsePortal(_pExits[1][i], r))
|
|
{
|
|
_pExits[1][i].From = r;
|
|
AddExit(_pExits[1][i], p, true);
|
|
_pExits[1].RemoveAt(i);
|
|
}
|
|
}
|
|
}
|
|
foreach(Exit e in r.exits)
|
|
{
|
|
if(e.To.Mapper_OpenBy != null)
|
|
continue;
|
|
if(e.To.Area.Entry == uint.MaxValue)
|
|
continue;
|
|
if(e.HasFlag("disabled"))
|
|
continue;
|
|
if(!GetScript(r).CanUseExit(e))
|
|
continue;
|
|
if(e.MinLevel > p.CharacterLevel)
|
|
continue;
|
|
if(e.MaxLevel < p.CharacterLevel)
|
|
continue;
|
|
if(!p.CanUseExit(e))
|
|
continue;
|
|
AddExit(e, p, false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void AddExit(Exit e, Pathfinder p, bool isPortal)
|
|
{
|
|
uint cost = e.From.Mapper_OpenCost + GetScript(e.From).GetLeaveRoomCost(e.From, e) +
|
|
(!isPortal ? GetScript(e.From).GetExitCost(e) : GetScript(e.From).GetPortalCost(e)) + GetScript(e.To).GetEnterRoomCost(e.To, e) +
|
|
p.GetLeaveRoomCost(e.From, e) +
|
|
(!isPortal ? p.GetExitCost(e) : p.GetPortalCost(e)) + p.GetEnterRoomCost(e.To, e);
|
|
|
|
uint o = p.GetOverrideCost(e, isPortal);
|
|
if(o != uint.MaxValue)
|
|
cost = e.From.Mapper_OpenCost + o;
|
|
|
|
if(cost >= _pGrid.Length)
|
|
return;
|
|
_pGrid[cost].Add(e);
|
|
cost++;
|
|
if(cost > _pGridMax)
|
|
_pGridMax = cost;
|
|
}
|
|
}
|
|
|
|
public class PathfindResult
|
|
{
|
|
internal PathfindResult()
|
|
{
|
|
}
|
|
|
|
public List<Exit> Path = new List<Exit>();
|
|
public int Cost = 0;
|
|
public bool Success = false;
|
|
public Room Target;
|
|
public Room Start;
|
|
|
|
/// <summary>
|
|
/// Generate a speedwalk from exit list.
|
|
/// </summary>
|
|
/// <param name="Exits">List of exits to generate a speedwalk from.</param>
|
|
/// <returns></returns>
|
|
public static string Speedwalk(IEnumerable<Exit> Exits)
|
|
{
|
|
List<string> sPath = new List<string>();
|
|
|
|
foreach(Exit x in Exits)
|
|
{
|
|
string Door = Mapper.GetScript(x.From).GetDoorCommand(x);
|
|
string Command = Mapper.GetScript(x.From).GetExitCommand(x);
|
|
if(!string.IsNullOrEmpty(Door))
|
|
sPath.AddRange(Door.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries));
|
|
if(Command == null)
|
|
break;
|
|
sPath.AddRange(Command.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries));
|
|
}
|
|
|
|
StringBuilder strSW = new StringBuilder();
|
|
StringBuilder strCur = new StringBuilder();
|
|
foreach(string x in sPath)
|
|
{
|
|
if(IsDirectionCommand(x) != 'x')
|
|
{
|
|
strCur.Append(char.ToLower(x[0]));
|
|
}
|
|
else
|
|
{
|
|
if(strCur.Length > 0)
|
|
{
|
|
if(strSW.Length > 0)
|
|
strSW.Append(';');
|
|
if(strCur.Length > 1)
|
|
strSW.Append("run ");
|
|
strSW.Append(CompressPath(strCur.ToString()));
|
|
strCur.Remove(0, strCur.Length);
|
|
}
|
|
|
|
if(strSW.Length > 0)
|
|
strSW.Append(';');
|
|
|
|
strSW.Append(x);
|
|
}
|
|
}
|
|
|
|
if(strCur.Length > 0)
|
|
{
|
|
if(strSW.Length > 0)
|
|
strSW.Append(';');
|
|
if(strCur.Length > 1)
|
|
strSW.Append("run ");
|
|
strSW.Append(CompressPath(strCur.ToString()));
|
|
strCur.Remove(0, strCur.Length);
|
|
}
|
|
|
|
return strSW.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if command is direction command. For example "west" -> 'w'. If it's not a direction
|
|
/// then return 'x'.
|
|
/// </summary>
|
|
/// <param name="cmd">Command to check.</param>
|
|
/// <returns></returns>
|
|
public static char IsDirectionCommand(string cmd)
|
|
{
|
|
cmd = cmd.ToLower().Trim();
|
|
|
|
if(cmd.Length == 0)
|
|
return 'x';
|
|
|
|
if(cmd.Length <= "north".Length && "north".StartsWith(cmd))
|
|
return 'n';
|
|
if(cmd.Length <= "west".Length && "west".StartsWith(cmd))
|
|
return 'w';
|
|
if(cmd.Length <= "south".Length && "south".StartsWith(cmd))
|
|
return 's';
|
|
if(cmd.Length <= "east".Length && "east".StartsWith(cmd))
|
|
return 'e';
|
|
if(cmd.Length <= "down".Length && "down".StartsWith(cmd))
|
|
return 'd';
|
|
if(cmd.Length <= "up".Length && "up".StartsWith(cmd))
|
|
return 'u';
|
|
return 'x';
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compresses "wwssse" into "2w3se".
|
|
/// </summary>
|
|
/// <param name="path">Path to compress.</param>
|
|
/// <returns></returns>
|
|
public static string CompressPath(string path)
|
|
{
|
|
char dir = 'x';
|
|
int c = 0;
|
|
StringBuilder str = new StringBuilder();
|
|
for(int i = 0; i < path.Length; i++)
|
|
{
|
|
if(path[i] == dir)
|
|
{
|
|
c++;
|
|
continue;
|
|
}
|
|
|
|
if(dir == 'x')
|
|
{
|
|
dir = path[i];
|
|
c = 1;
|
|
continue;
|
|
}
|
|
|
|
if(c > 1)
|
|
str.Append(c.ToString());
|
|
str.Append(dir.ToString());
|
|
dir = path[i];
|
|
c = 1;
|
|
}
|
|
|
|
if(dir != 'x')
|
|
{
|
|
if(c > 1)
|
|
str.Append(c.ToString());
|
|
str.Append(dir.ToString());
|
|
}
|
|
|
|
return str.ToString();
|
|
}
|
|
}
|
|
|
|
public class Pathfinder
|
|
{
|
|
protected Pathfinder()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the character level for this path find. It is used in level lock areas and portals.
|
|
/// </summary>
|
|
public int CharacterLevel = 1;
|
|
public int CharacterTier = 0;
|
|
public bool IsGlobalQuest = false;
|
|
public bool CanUsePortals = true;
|
|
public bool CanUseRecalls = true;
|
|
public bool IsSingleClassTier0 = false;
|
|
public Room[] SkipRooms;
|
|
public Exit[] SkipExits;
|
|
public Exit[] SkipPortals;
|
|
public Room[] StartRooms;
|
|
public Exit[] OverridePortals;
|
|
|
|
public virtual void CopyFrom(Pathfinder pf)
|
|
{
|
|
if(pf == null)
|
|
return;
|
|
|
|
CharacterLevel = pf.CharacterLevel;
|
|
CharacterTier = pf.CharacterTier;
|
|
IsGlobalQuest = pf.IsGlobalQuest;
|
|
CanUsePortals = pf.CanUsePortals;
|
|
CanUseRecalls = pf.CanUseRecalls;
|
|
IsSingleClassTier0 = pf.IsSingleClassTier0;
|
|
SkipRooms = pf.SkipRooms;
|
|
SkipExits = pf.SkipExits;
|
|
SkipPortals = pf.SkipPortals;
|
|
StartRooms = pf.StartRooms;
|
|
OverridePortals = pf.OverridePortals;
|
|
}
|
|
|
|
public virtual uint GetOverrideCost(Exit e, bool isPortal)
|
|
{
|
|
return uint.MaxValue;
|
|
}
|
|
|
|
public virtual void OnStartedPathfind()
|
|
{
|
|
}
|
|
|
|
public virtual bool IsTargetRoom(Room r)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public virtual void OnEndedPathFind(PathfindResult pr)
|
|
{
|
|
}
|
|
|
|
public virtual bool CanUseExit(Exit e)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public virtual bool CanUsePortal(Exit p, Room r)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public virtual uint GetLeaveRoomCost(Room r, Exit e)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public virtual uint GetExitCost(Exit e)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public virtual uint GetPortalCost(Exit e)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public virtual uint GetEnterRoomCost(Room r, Exit e)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|