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