summaryrefslogtreecommitdiffstats
path: root/src/Mobs/PathFinder.cpp
blob: 260044f3b2dc0e8cbf1ac41edd6b84445c876f11 (plain) (blame)
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#include "Globals.h"
#include "PathFinder.h"
#include "BlockType.h"
#include "../BlockInfo.h"
#include "../Chunk.h"





cPathFinder::cPathFinder(float a_MobWidth, float a_MobHeight) :
	m_Width(a_MobWidth),
	m_Height(a_MobHeight),
	m_GiveUpCounter(0),
	m_NotFoundCooldown(0)
{
}





ePathFinderStatus cPathFinder::GetNextWayPoint(cChunk & a_Chunk, const Vector3d & a_Source, Vector3d * a_Destination, Vector3d * a_OutputWaypoint, bool a_DontCare)
{
	m_FinalDestination = *a_Destination;
	m_Source = a_Source;

	// If a recent PATH_NOT_FOUND was returned, we rest for a few ticks.
	if (m_NotFoundCooldown > 0)
	{
		m_NotFoundCooldown -= 1;
		return ePathFinderStatus::CALCULATING;
	}

	// Tweak the destination. If something is wrong with the destination or the chunk, rest for a while.
	if (!(EnsureProperPoint(m_FinalDestination, a_Chunk) && EnsureProperPoint(m_Source, a_Chunk)))
	{
		m_NotFoundCooldown = 20;
		return ePathFinderStatus::PATH_NOT_FOUND;
	}

	/* printf("%d %d %d -> %d %d %d\n",
	static_cast<int>(m_Source.x),
	static_cast<int>(m_Source.y),
	static_cast<int>(m_Source.z),
	static_cast<int>(m_FinalDestination.x),
	static_cast<int>(m_FinalDestination.y),
	static_cast<int>(m_FinalDestination.z)); */

	// Rest is over. Prepare m_Path by calling ResetPathFinding.
	if (m_NotFoundCooldown == 0)
	{
		m_NotFoundCooldown = -1;
		ResetPathFinding(a_Chunk);
	}

	// If m_Path has not been initialized yet, initialize it.
	if (!m_Path->IsValid())
	{
		ResetPathFinding(a_Chunk);
	}

	switch (m_Path->CalculationStep(a_Chunk))
	{
		case ePathFinderStatus::NEARBY_FOUND:
		{
			m_NoPathToTarget = true;
			m_PathDestination = m_Path->AcceptNearbyPath();
			if (a_DontCare)
			{
				m_FinalDestination = m_PathDestination;
				*a_Destination = m_FinalDestination;  // Modify the mob's final destination because it doesn't care about reaching an exact spot
			}
			else
			{
				m_DeviationOrigin = m_FinalDestination;  // This is the only case in which m_DeviationOrigin != m_PathDestination
			}
			return ePathFinderStatus::CALCULATING;
			// The next call will trigger the PATH_FOUND case
		}

		case ePathFinderStatus::PATH_NOT_FOUND:
		{
			m_NotFoundCooldown = 20;
			return ePathFinderStatus::PATH_NOT_FOUND;
		}
		case ePathFinderStatus::CALCULATING:
		{
			return ePathFinderStatus::CALCULATING;
		}
		case ePathFinderStatus::PATH_FOUND:
		{
			m_GiveUpCounter -= 1;

			if (m_GiveUpCounter == 0)
			{
				if (a_DontCare)
				{
					// We're having trouble reaching the next waypoint but the mob
					// Doesn't care where to go, just tell him we got there ;)
					m_FinalDestination = m_Source;
					*a_Destination = m_FinalDestination;
					ResetPathFinding(a_Chunk);
					return ePathFinderStatus::CALCULATING;
				}
				else
				{
					ResetPathFinding(a_Chunk);
					return ePathFinderStatus::CALCULATING;
				}
			}

			if (PathIsTooOld())
			{
				ResetPathFinding(a_Chunk);
				return ePathFinderStatus::CALCULATING;
			}

			if (m_Path->NoMoreWayPoints())
			{
				// We're always heading towards m_PathDestination.
				// If m_PathDestination is exactly m_FinalDestination, then we're about to reach the destination.
				if (m_PathDestination == m_FinalDestination)
				{
					*a_OutputWaypoint = m_FinalDestination;
					return ePathFinderStatus::PATH_FOUND;

				}
				else
				{
					// Otherwise, we've finished our approximate path and time to recalc.
					ResetPathFinding(a_Chunk);
					return ePathFinderStatus::CALCULATING;
				}
			}

			Vector3d Waypoint(m_WayPoint);
			Vector3d Source(m_Source);
			Waypoint.y = 0;
			Source.y = 0;

			if (m_Path->IsFirstPoint() || (((Waypoint - Source).SqrLength() < WAYPOINT_RADIUS) && (m_Source.y >= m_WayPoint.y)))
			{
				// if the mob has just started or if the mob reached a waypoint, give them a new waypoint.
				m_WayPoint = m_Path->GetNextPoint();
				m_GiveUpCounter = 40;
				return ePathFinderStatus::PATH_FOUND;
			}
			else
			{
				// Otherwise, the mob is still walking towards its waypoint, we'll patiently wait. We won't update m_WayPoint.
				*a_OutputWaypoint = m_WayPoint;
				return ePathFinderStatus::PATH_FOUND;
			}
		}
	}
	UNREACHABLE("Unsupported path finder status");
}





void cPathFinder::ResetPathFinding(cChunk &a_Chunk)
{
	m_GiveUpCounter = 40;
	m_NoPathToTarget = false;
	m_PathDestination = m_FinalDestination;
	m_DeviationOrigin = m_PathDestination;
	m_Path.reset(new cPath(a_Chunk, m_Source, m_PathDestination, 20, m_Width, m_Height));
}





bool cPathFinder::EnsureProperPoint(Vector3d & a_Vector, cChunk & a_Chunk)
{
	cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(a_Vector.x), FloorC(a_Vector.z));
	BLOCKTYPE BlockType;
	NIBBLETYPE BlockMeta;

	if ((Chunk == nullptr) || !Chunk->IsValid())
	{
		return false;
	}

	// If destination in the air, first try to go 1 block north, or east, or west.
	// This fixes the player leaning issue.
	// If that failed, we instead go down to the lowest air block.
	auto Below = a_Vector.Floor().addedY(-1);
	if (!cChunkDef::IsValidHeight(Below))
	{
		return false;

	}
	auto BelowRel = cChunkDef::AbsoluteToRelative(Below);

	Chunk->GetBlockTypeMeta(BelowRel, BlockType, BlockMeta);
	if (!(IsWaterOrSolid(BlockType)))
	{
		constexpr std::array<Vector3i, 8> Offsets =
		{
			{
				{-1, 0, 0},
				{1, 0, 0},
				{0, 0, -1},
				{0, 0, 1},
				{-1, 0, -1},
				{-1, 0, 1},
				{1, 0, -1},
				{1, 0, 1},
			}
		};

		// Looks for a neighbouring block one block in x or z direction that is water or solid.
		bool InTheAir = true;
		for (const auto & Offset : Offsets)
		{
			auto InspectPos = Below + Offset;
			Chunk = a_Chunk.GetNeighborChunk(InspectPos.x, InspectPos.z);
			if ((Chunk == nullptr) || !Chunk->IsValid())
			{
				return false;
			}
			auto InspectRel = cChunkDef::AbsoluteToRelative(InspectPos);
			Chunk->GetBlockTypeMeta(InspectRel, BlockType, BlockMeta);
			if (IsWaterOrSolid((BlockType)))
			{
				BelowRel = InspectRel;
				InTheAir = false;
				break;
			}
		}

		// Go down to the lowest air block.
		if (InTheAir)
		{
			while (cChunkDef::IsValidHeight(a_Vector.addedY(-1)))
			{
				Chunk->GetBlockTypeMeta(BelowRel.addedY(-1), BlockType, BlockMeta);
				if (IsWaterOrSolid(BlockType))
				{
					break;
				}
				BelowRel.y -= 1;
			}
		}
	}

	// If destination in water or solid, go up to the first air block.
	while (BelowRel.y < cChunkDef::Height)
	{
		Chunk->GetBlockTypeMeta(BelowRel, BlockType, BlockMeta);
		if (!IsWaterOrSolid(BlockType))
		{
			break;
		}
		BelowRel.y += 1;
	}

	return true;
}





bool cPathFinder::IsWaterOrSolid(BLOCKTYPE a_BlockType)
{
	return ((a_BlockType == E_BLOCK_STATIONARY_WATER) || cBlockInfo::IsSolid(a_BlockType));
}





bool cPathFinder::PathIsTooOld() const
{
	size_t acceptableDeviation = m_Path->WayPointsLeft() / 2;
	if (acceptableDeviation == 0)
	{
		acceptableDeviation = 1;
	}
	const auto DeviationSqr = (m_FinalDestination - m_DeviationOrigin).SqrLength();
	return (DeviationSqr > (acceptableDeviation * acceptableDeviation));
}