-
Notifications
You must be signed in to change notification settings - Fork 7
/
Pathfinding.cs
197 lines (175 loc) · 7.79 KB
/
Pathfinding.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
using Microsoft.Xna.Framework;
using StardewValley;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Starbot.Pathfinder
{
public class Location
{
public int X;
public int Y;
public int F;
public int G;
public int H;
public Location Parent;
public bool Preferable = false;
}
public class Pathfinder
{
public static List<Tuple<int,int>> FindPath(GameLocation location, int startX, int startY, int targetX, int targetY, int cutoff = -1)
{
Location current = null;
Location start = new Location { X = startX, Y = startY };
Location target = new Location { X = targetX, Y = targetY };
var openList = new List<Location>();
var closedList = new List<Location>();
int g = 0;
// start by adding the original position to the open list
openList.Add(start);
while (openList.Count > 0)
{
// get the square with the lowest F score
var lowest = openList.Min(l => l.F);
current = openList.First(l => l.F == lowest);
// add to closed, remove from open
closedList.Add(current);
openList.Remove(current);
// if closed contains destination, we're done
if (closedList.FirstOrDefault(l => l.X == target.X && l.Y == target.Y) != null) break;
// if closed has exceed cutoff, break out and fail
if (cutoff > 0 && closedList.Count > cutoff)
{
//Mod.instance.Monitor.Log("Breaking out of pathfinding, cutoff exceeded");
return null;
}
var adjacentSquares = GetWalkableAdjacentSquares(current.X, current.Y, location, openList);
g = current.G + 1;
foreach (var adjacentSquare in adjacentSquares)
{
// if closed, ignore
if (closedList.FirstOrDefault(l => l.X == adjacentSquare.X
&& l.Y == adjacentSquare.Y) != null)
continue;
// if it's not in open
if (openList.FirstOrDefault(l => l.X == adjacentSquare.X
&& l.Y == adjacentSquare.Y) == null)
{
// compute score, set parent
adjacentSquare.G = g;
adjacentSquare.H = ComputeHScore(adjacentSquare.Preferable, adjacentSquare.X, adjacentSquare.Y, target.X, target.Y);
adjacentSquare.F = adjacentSquare.G + adjacentSquare.H;
adjacentSquare.Parent = current;
// and add it to open
openList.Insert(0, adjacentSquare);
}
else
{
// test if using the current G score makes the adjacent square's F score lower
// if yes update the parent because it means it's a better path
if (g + adjacentSquare.H < adjacentSquare.F)
{
adjacentSquare.G = g;
adjacentSquare.F = adjacentSquare.G + adjacentSquare.H;
adjacentSquare.Parent = current;
}
}
}
}
//make sure path is complete
if (current == null) return null;
if (current.X != targetX || current.Y != targetY)
{
//Mod.instance.Monitor.Log("No path available.", StardewModdingAPI.LogLevel.Warn);
return null;
}
// if path exists, let's pack it up for return
var returnPath = new List<Tuple<int, int>>();
while (current != null)
{
returnPath.Add(new Tuple<int, int>(current.X, current.Y));
current = current.Parent;
}
returnPath.Reverse();
return returnPath;
}
static List<Location> GetWalkableAdjacentSquares(int x, int y, GameLocation map, List<Location> openList)
{
List<Location> list = new List<Location>();
if (IsPassable(map, x, y - 1))
{
Location node = openList.Find(l => l.X == x && l.Y == y - 1);
if (node == null) list.Add(new Location() { Preferable = IsPreferableWalkingSurface(map, x, y), X = x, Y = y - 1 });
else list.Add(node);
}
if (IsPassable(map, x, y + 1))
{
Location node = openList.Find(l => l.X == x && l.Y == y + 1);
if (node == null) list.Add(new Location() { Preferable = IsPreferableWalkingSurface(map, x, y), X = x, Y = y + 1 });
else list.Add(node);
}
if (IsPassable(map, x - 1, y))
{
Location node = openList.Find(l => l.X == x - 1 && l.Y == y);
if (node == null) list.Add(new Location() { Preferable = IsPreferableWalkingSurface(map, x, y), X = x - 1, Y = y });
else list.Add(node);
}
if (IsPassable(map, x + 1, y))
{
Location node = openList.Find(l => l.X == x + 1 && l.Y == y);
if (node == null) list.Add(new Location() { Preferable = IsPreferableWalkingSurface(map, x, y), X = x + 1, Y = y });
else list.Add(node);
}
return list;
}
static bool IsPreferableWalkingSurface(GameLocation location, int x, int y)
{
//todo, make roads more desireable
return false;
}
static bool IsPassable(GameLocation location, int x, int y)
{
var v = new Vector2(x, y);
bool isWarp = false;
foreach(var w in location.warps)
{
if (w.X == x && w.Y == y) isWarp = true;
}
bool isOnMap = location.isTileOnMap(v);
bool isOccupied = location.isTileOccupiedIgnoreFloors(v, "");
bool isPassable = location.isTilePassable(new xTile.Dimensions.Location((int)x, (int)y), Game1.viewport);
//check for bigresourceclumps on the farm
if(location is Farm)
{
var fff = location as Farm;
foreach(var brc in fff.largeTerrainFeatures)
{
var r = brc.getBoundingBox();
var xx = x;
var yy = y;
if (xx > r.X && xx < r.X + r.Width && yy > r.Y && yy < r.Y + r.Height) return false;
}
}
if (location is StardewValley.Locations.BuildableGameLocation)
{
var bgl = location as StardewValley.Locations.BuildableGameLocation;
foreach (var b in bgl.buildings)
{
if (!b.isTilePassable(v)) return false;
}
}
if(location is StardewValley.Locations.BuildableGameLocation || location is Farm)
{
//more aggressive test. doesn't like floors
if (location.isCollidingPosition(new Rectangle((x * 64) + 2, (y * 64) + 2, 60, 60), Game1.viewport, true, 0, false, null, false, false, true)) return false;
}
return (isWarp || (isOnMap && !isOccupied && isPassable)); //warps must be passable even off-map
}
static int ComputeHScore(bool preferable, int x, int y, int targetX, int targetY)
{
return (Math.Abs(targetX - x) + Math.Abs(targetY - y)) - (preferable ? 1 : 0);
}
}
}