1 #include "astar.h"
2 #include "factions.h"
3 #include "Being.h"
4 #include "map.h"
5 #include "place.h"
6 #include "session.h"
7 #include "log.h"
8 #include "terrain.h"
9 #include "kern_intvar.h"
10
11 // USE_CACHED_PATH works but it can cause some strange-seeming behavior. If a
12 // new, better route opens than the cached path then the being won't find it,
13 // but will blindly follow the cached path. Unless pathfinding becomes a
14 // performance issue leave this off.
15 //
16 // Addendum: actually, we need it on, because otherwise the member will
17 // "thrash" when, for example, a portcullis remains closed: pathfinding around
18 // it, then pathfinding through it and trying to open it, then around it again,
19 // etc...
20 #ifndef USE_CACHED_PATH
21 # define USE_CACHED_PATH true
22 #endif
23
Being()24 Being::Being()
25 {
26 setDefaults();
27 }
28
Being(class ObjectType * type)29 Being::Being(class ObjectType *type)
30 : Object(type)
31 {
32 setDefaults();
33 }
34
setDefaults()35 void Being::setDefaults()
36 {
37 name = NULL;
38 cachedPath = NULL;
39 cachedPathPlace = NULL;
40 setBaseFaction(INVALID_FACTION);
41 }
42
~Being()43 Being::~Being()
44 {
45 if (cachedPath) {
46 astar_path_destroy(cachedPath);
47 }
48 if (name) {
49 free(name);
50 }
51 }
52
setBaseFaction(int faction)53 void Being::setBaseFaction(int faction)
54 {
55 baseFaction = faction;
56 setCurrentFaction(faction);
57 }
58
getBaseFaction()59 int Being::getBaseFaction()
60 {
61 return baseFaction;
62 }
63
getCurrentFaction()64 int Being::getCurrentFaction()
65 {
66 return currentFaction;
67 }
68
getLayer()69 enum layer Being::getLayer()
70 {
71 return being_layer;
72 }
73
switchPlaces(class Being * occupant)74 void Being::switchPlaces(class Being *occupant)
75 {
76 int oldx = getX();
77 int oldy = getY();
78 int newx = occupant->getX();
79 int newy = occupant->getY();
80
81 struct place *oldPlace = getPlace();
82 occupant->relocate(oldPlace, oldx, oldy);
83 relocate(oldPlace, newx, newy);
84 decActionPoints(place_get_diagonal_movement_cost(getPlace(),
85 oldx, oldy,
86 newx, newy,
87 this, 0));
88 }
89
pathfindTo(struct place * destplace,int destx,int desty,int flags)90 bool Being::pathfindTo(struct place *destplace, int destx, int desty,
91 int flags)
92 {
93 struct astar_search_info as_info;
94 struct astar_node *pathPtr;
95
96 if (!flags)
97 flags = PFLAG_IGNORECOMPANIONS | PFLAG_IGNOREMECHS;
98
99 if (isStationary())
100 return false;
101
102 // For now, don't try to pathfind between places.
103 if (destplace != getPlace())
104 {
105 warn("%s in %s, can't pathfind to %s", getName(),
106 getPlace()->name, destplace->name);
107 return false;
108 }
109
110 //dbg("%s pathfind from (%d %d) to (%d %d)\n",
111 //getName(), getX(), getY(), destx, desty);
112
113 // Check the cachedPath
114 if (USE_CACHED_PATH && cachedPath)
115 {
116
117 //dbg("cachedPath: ");
118 //astar_dbg_dump_path(cachedPath);
119
120 // If the cached path is for a different place then we can't
121 // use it
122 if (getPlace() != cachedPathPlace)
123 {
124 //dbg("old place\n");
125 clearCachedPath();
126 }
127 else
128 {
129 pathPtr = cachedPath;
130
131
132 // If the cached path does not start from the current
133 // coordinates then we can't use it.
134 if (pathPtr->x != getX() || pathPtr->y != getY())
135 {
136 //dbg("old start\n");
137 clearCachedPath();
138 }
139 else if (pathPtr->x != destx || pathPtr->y != desty)
140 {
141 pathPtr = pathPtr->next;
142
143 // if we are about to hit nasty terrain, reevaluate our options
144 if (pathPtr && place_get_terrain(getPlace(),pathPtr->x,pathPtr->y)->effect)
145 {
146 dbg("recheck path (terrain)\n");
147 pathPtr = NULL;
148 }
149 if (pathPtr && place_get_object(getPlace(),pathPtr->x,pathPtr->y, field_layer) != NULL)
150 {
151 dbg("recheck path (field)\n");
152 pathPtr = NULL;
153 }
154
155 //dbg("tracing\n");
156 // Trace down the path until it ends or hits
157 // the target
158 while (pathPtr &&
159 (pathPtr->x != destx || pathPtr->y != desty))
160 pathPtr = pathPtr->next;
161
162 // If this path is no good then destroy it,
163 // we'll have to get a new one.
164 if (! pathPtr)
165 {
166 //dbg("won't reach\n");
167 clearCachedPath();
168 }
169 }
170 }
171 }
172
173 // If we don't have a valid path then try to find one, first by
174 // ignoring mechanisms.
175 if (! USE_CACHED_PATH || ! cachedPath) {
176 //dbg("searching\n");
177 memset(&as_info, 0, sizeof (as_info));
178 as_info.x0 = getX();
179 as_info.y0 = getY();
180 as_info.x1 = destx;
181 as_info.y1 = desty;
182 as_info.flags = flags;
183 cachedPath = place_find_path(getPlace(), &as_info, this);
184 }
185
186 // If we still don't have a valid path then give up
187 if (!cachedPath) {
188 //dbg("none found\n");
189 return false;
190 }
191
192 // If the path does not lead anywhere then we must be at our
193 // destination, so we can destroy it and return.
194 pathPtr = cachedPath->next;
195 if (! pathPtr) {
196 //dbg("already there\n");
197 clearCachedPath();
198 return true;
199 }
200
201 // Otherwise the path is good, so cache the place.
202 cachedPathPlace = getPlace();
203 //dbg("Found path: ");
204 //astar_dbg_dump_path(cachedPath);
205
206 enum MoveResult result;
207 result = move(pathPtr->x - getX(),
208 pathPtr->y - getY());
209
210 // Was the move blocked by an occupant?
211 if (result == WasOccupied) {
212
213 // Yes - are we supposed to ignore beings?
214 if (flags & PFLAG_IGNOREBEINGS) {
215
216 // Yes - try to switch. I don't know why I need to
217 // check for isOnMap() (when would we not be on a map?
218 // multi-place scehdules maybe?), but it looks like
219 // something we probably added to fix a corner case, so
220 // I'm leaving it in.
221 class Character *occupant;
222 if (isOnMap()
223 && (occupant = (class Character *) place_get_object(getPlace(),
224 pathPtr->x, pathPtr->y,
225 being_layer))) {
226 if (!are_hostile(this, occupant)
227 && occupant->isIncapacitated())
228 {
229 if (!place_is_passable(getPlace(), getX(), getY(),
230 occupant, 0))
231 {
232 relocate(getPlace(), pathPtr->x, pathPtr->y);
233 runHook(OBJ_HOOK_MOVE_DONE, "pdd", getPlace(),
234 pathPtr->x, pathPtr->y);
235 decActionPoints(place_get_diagonal_movement_cost
236 (
237 getPlace(),
238 getX(), getY(),
239 pathPtr->x, pathPtr->y,
240 this, PFLAG_IGNOREMECHS
241 ));
242 }
243 switchPlaces(occupant);
244 }
245 }
246 } else {
247 // No, we are not ignoring beings. We're probably using
248 // a cached path that was built when the tile was
249 // unoccupied. Let's just null out the cachedPath now
250 // and let the being try again on the next turn.
251 clearCachedPath();
252 }
253 }
254
255 // If the move failed because something impassable is there then check
256 // for a mech and try to handle it. This is good enough to get through
257 // the usual implementation of a door.
258 if (result == WasImpassable && isOnMap()) {
259
260 //dbg("impassable\n");
261 class Object *mech;
262 mech = place_get_object(getPlace(),
263 pathPtr->x,
264 pathPtr->y,
265 mech_layer);
266 if (mech && mech->getObjectType()->canHandle()) {
267 // workaround for [ 1114054 ] messages for off-screen
268 // NPC's getting printed: temporarily prevent the mech
269 // script from generating log messages
270 log_disable();
271 mech->getObjectType()->handle(mech, this);
272 log_enable();
273 mapSetDirty();
274
275 // Now try and move again.
276 result = move(pathPtr->x - getX(),
277 pathPtr->y - getY());
278
279 // Workaround an infinite loop in Character::exec()
280 // where a party member is trying to rendezvous on a
281 // path through a mech, and it keeps handling the mech,
282 // thus decrementing its action points, but in fact it
283 // can't move on any path.
284 if (MovedOk == result) {
285 this->decActionPoints(kern_intvar_get("AP_COST:handle_mechanism"));
286 }
287
288 }
289
290 if (WasImpassable == result && isOnMap()) {
291
292 //dbg("still impassable\n");
293 // If the move was still impassable then try and find a
294 // path that avoids mechanisms. Destroy this path
295 // first.
296 clearCachedPath();
297
298 // Redo the search
299 memset(&as_info, 0, sizeof (as_info));
300 as_info.x0 = getX();
301 as_info.y0 = getY();
302 as_info.x1 = destx;
303 as_info.y1 = desty;
304 as_info.flags = PFLAG_IGNORECOMPANIONS;
305 cachedPath = place_find_path(getPlace(), &as_info,
306 this);
307
308 // If we still don't have a valid path then give up
309 if (!cachedPath) {
310 //dbg("no path\n");
311 return false;
312 }
313
314 //dbg("New path: ");
315 //astar_dbg_dump_path(cachedPath);
316
317 // Otherwise the path is good, so cache the place.
318 cachedPathPlace = getPlace();
319 pathPtr = cachedPath->next;
320
321 // Check if already there (can happen if the target changes)
322 if (! pathPtr) {
323 //dbg("already there\n");
324 clearCachedPath();
325 return true;
326 }
327
328 // Try to take the next step along the path.
329 result = move(pathPtr->x - getX(),
330 pathPtr->y - getY());
331
332 }
333 }
334
335 // If the move worked (as evidenced by the fact that our location
336 // changed to the next node) then free the first node and make the next
337 // node the head of the path so we can continue using it next turn.
338 if (getX() == pathPtr->x &&
339 getY() == pathPtr->y) {
340 //dbg("ok\n");
341 if (USE_CACHED_PATH) {
342 astar_node_destroy(cachedPath);
343 cachedPath = pathPtr;
344 } else {
345 clearCachedPath();
346 }
347 return true;
348 }
349
350 return false;
351 }
352
getName()353 const char *Being::getName()
354 {
355 if (name)
356 return name;
357 return "<no name>";
358 }
359
setName(const char * val)360 void Being::setName(const char *val)
361 {
362 if (val)
363 name = strdup(val);
364 else if (name) {
365 free(name);
366 name = NULL;
367 }
368 }
369
setCurrentFaction(int faction)370 void Being::setCurrentFaction(int faction)
371 {
372 currentFaction = faction;
373 }
374
clearCachedPath()375 void Being::clearCachedPath()
376 {
377 if (cachedPath) {
378 astar_path_destroy(cachedPath);
379 cachedPath = NULL;
380 cachedPathPlace = NULL;
381 }
382 }
383