1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "ultima/shared/std/containers.h"
24 #include "ultima/nuvie/misc/u6_misc.h"
25 #include "ultima/nuvie/actors/actor.h"
26 #include "ultima/nuvie/core/party.h"
27 #include "ultima/nuvie/pathfinder/seek_path.h"
28 #include "ultima/nuvie/pathfinder/actor_path_finder.h"
29 #include "ultima/nuvie/pathfinder/party_path_finder.h"
30
31 namespace Ultima {
32 namespace Nuvie {
33
34 using Std::vector;
35
PartyPathFinder(Party * p)36 PartyPathFinder::PartyPathFinder(Party *p) {
37 assert(p);
38 party = p;
39 }
40
~PartyPathFinder()41 PartyPathFinder::~PartyPathFinder() {
42
43 }
44
45 /* True if a member's target and leader are in roughly the same direction. */
is_behind_target(uint32 member_num)46 bool PartyPathFinder::is_behind_target(uint32 member_num) {
47 if (get_leader() < 0)
48 return false;
49 uint8 ldir = get_member(get_leader()).actor->get_direction(); // leader direciton
50 MapCoord from = party->get_location(member_num);
51 MapCoord to = party->get_formation_coords(member_num); // target
52 sint8 to_x = to.x - from.x, to_y = to.y - from.y;
53 return (((ldir == NUVIE_DIR_N && to_y < 0)
54 || (ldir == NUVIE_DIR_S && to_y > 0)
55 || (ldir == NUVIE_DIR_E && to_x > 0)
56 || (ldir == NUVIE_DIR_W && to_x < 0)));
57 }
58
is_at_target(uint32 p)59 bool PartyPathFinder::is_at_target(uint32 p) {
60 MapCoord target_loc = party->get_formation_coords(p);
61 MapCoord member_loc = party->get_location(p);
62 return (target_loc == member_loc);
63 }
64
65 /* Is anyone in front of `member_num' adjacent to `from'?
66 * (is_contiguous(member, member_loc) == "is member adjacent to another member
67 * whose following position is lower-numbered?") */
is_contiguous(uint32 member_num,MapCoord from)68 bool PartyPathFinder::is_contiguous(uint32 member_num, MapCoord from) {
69 for (uint32 q = 0; q < member_num; q++) { // check lower-numbered members
70 Actor *actor = get_member(q).actor;
71 if (actor && actor->is_immobile() == true) continue;
72
73 MapCoord loc = party->get_location(q);
74 if (from.distance(loc) <= 1)
75 return true;
76 }
77 return false;
78 }
79
80 /* Is member adjacent to another member whose following position is lower-numbered? */
is_contiguous(uint32 member_num)81 bool PartyPathFinder::is_contiguous(uint32 member_num) {
82 MapCoord member_loc = party->get_location(member_num);
83 return (is_contiguous(member_num, member_loc));
84 }
85
86 /* Returns in rel_x and rel_y the direction a character needs to move to get
87 * closer to their target. */
get_target_dir(uint32 p,sint8 & rel_x,sint8 & rel_y)88 void PartyPathFinder::get_target_dir(uint32 p, sint8 &rel_x, sint8 &rel_y) {
89 //MapCoord leader_loc = party->get_leader_location();
90 MapCoord target_loc = party->get_formation_coords(p);
91 MapCoord member_loc = party->get_location(p);
92
93 rel_x = get_wrapped_rel_dir(target_loc.x, member_loc.x, target_loc.z);
94 rel_y = get_wrapped_rel_dir(target_loc.y, member_loc.y, target_loc.z);
95 }
96
97 /* Returns in vec_x and vec_y the last direction the leader moved in. It's
98 * derived from his facing direction so it's not as precise as get_last_move(). */
get_forward_dir(sint8 & vec_x,sint8 & vec_y)99 void PartyPathFinder::get_forward_dir(sint8 &vec_x, sint8 &vec_y) {
100 // get_last_move(vec_x, vec_y);
101 vec_x = 0;
102 vec_y = 0;
103 uint8 dir = (get_leader() >= 0) ? get_member(get_leader()).actor->get_direction() : NUVIE_DIR_N;
104 if (dir == NUVIE_DIR_N) {
105 vec_x = 0;
106 vec_y = -1;
107 } else if (dir == NUVIE_DIR_S) {
108 vec_x = 0;
109 vec_y = 1;
110 } else if (dir == NUVIE_DIR_E) {
111 vec_x = 1;
112 vec_y = 0;
113 } else if (dir == NUVIE_DIR_W) {
114 vec_x = -1;
115 vec_y = 0;
116 }
117 }
118
119 /* Returns in vec_x and vec_y the last direction the leader moved in. */
get_last_move(sint8 & vec_x,sint8 & vec_y)120 void PartyPathFinder::get_last_move(sint8 &vec_x, sint8 &vec_y) {
121 MapCoord leader_loc = party->get_leader_location();
122
123 vec_x = get_wrapped_rel_dir(leader_loc.x, party->prev_leader_x, leader_loc.z);
124 vec_y = get_wrapped_rel_dir(leader_loc.y, party->prev_leader_y, leader_loc.z);
125 }
126
127 /* Returns true if the leader moved before the last call to follow(). */
leader_moved()128 bool PartyPathFinder::leader_moved() {
129 MapCoord leader_loc = party->get_leader_location();
130 return ((leader_loc.x - party->prev_leader_x)
131 || (leader_loc.y - party->prev_leader_y));
132 }
133
134 /* Returns true if the leader moved far enough away from follower to pull him. */
leader_moved_away(uint32 p)135 bool PartyPathFinder::leader_moved_away(uint32 p) {
136 MapCoord leader_loc = party->get_leader_location();
137 MapCoord target_loc = party->get_formation_coords(p);
138 MapCoord member_loc = party->get_location(p);
139 return (leader_loc.distance(member_loc) > leader_loc.distance(target_loc));
140 }
141
142 /* Compares leader position with last known position. */
leader_moved_diagonally()143 bool PartyPathFinder::leader_moved_diagonally() {
144 MapCoord leader_loc = party->get_leader_location();
145 return (party->prev_leader_x != leader_loc.x
146 && party->prev_leader_y != leader_loc.y);
147 }
148
follow_passA(uint32 p)149 bool PartyPathFinder::follow_passA(uint32 p) {
150 bool contiguous = is_contiguous(p);
151 bool try_again = false;
152 sint8 vec_x = 0, vec_y = 0; // previous direction of party leader's movement
153 sint8 rel_x = 0, rel_y = 0; // direction to target
154
155 get_target_dir(p, rel_x, rel_y);
156 if (contiguous) {
157 if (is_at_target(p))
158 return true;
159
160 // Move towards target, and see if we get to try again.
161 // If you always get an extra move, distant followers move towards the
162 // leader instead of forward. Only get an extra move if we stopped, or
163 // blocked square is in the same direction the leader moved.
164 get_last_move(vec_x, vec_y);
165 if (!leader_moved() && !try_moving_to_target(p))
166 try_again = true;
167 else if (leader_moved() && leader_moved_away(p) && !try_moving_to_target(p))
168 if (is_behind_target(p))
169 try_again = true;
170 } else {
171 if (!move_member(p, rel_x, rel_y))
172 try_again = true;
173 }
174
175 // get another move chance here
176 if (try_again) {
177 MapCoord target_loc = party->get_formation_coords(p);
178 if (!try_all_directions(p, target_loc)) { // turn towards target
179 if (contiguous)
180 return false;
181 if (!move_member(p, rel_x, rel_y, true)) // allow non-contiguous moves
182 return false;
183 }
184 }
185 return true;
186 }
187
follow_passB(uint32 p)188 bool PartyPathFinder::follow_passB(uint32 p) {
189 if (is_contiguous(p)) {
190 if (is_at_target(p))
191 return true;
192
193 if (leader_moved_away(p)) { // only move if leader walked away from us
194 // move forward (direction leader moved) if target is ahead of us
195 if (leader_moved() && is_behind_target(p))
196 try_moving_forward(p);
197 if (leader_moved_diagonally()) // extra move
198 try_moving_sideways(p);
199 }
200 } else {
201 if (!try_moving_forward(p)) {
202 sint8 vec_x, vec_y;
203 get_forward_dir(vec_x, vec_y);
204 MapCoord member_loc = party->get_location(p);
205 MapCoord forward_loc = member_loc.abs_coords(vec_x, vec_y);
206 try_all_directions(p, forward_loc); // turn towards forward
207 }
208 }
209
210 if (!is_contiguous(p)) // critical; still not contiguous
211 if (!try_moving_to_leader(p, true))
212 return false;
213
214 return true;
215 }
216
217 /* Follower moves up, down, left, or right. (to intercept a target who moved
218 * diagonally) Returns true if the character moved. */
try_moving_sideways(uint32 p)219 bool PartyPathFinder::try_moving_sideways(uint32 p) {
220 // prefer movement towards target direction
221 sint8 rel_x, rel_y;
222 get_target_dir(p, rel_x, rel_y);
223 if (!move_member(p, rel_x, 0)) // try two directions
224 if (!move_member(p, 0, rel_y))
225 return false;
226 return true;
227 }
228
229 /* Follower tries moving towards the leader, and then towards each adjacent
230 * direction if necessary. Returns true if the character moved. */
try_moving_to_leader(uint32 p,bool ignore_position)231 bool PartyPathFinder::try_moving_to_leader(uint32 p, bool ignore_position) {
232 // move towards leader (allow non-contiguous moves)
233 sint8 rel_x, rel_y;
234 get_target_dir(p, rel_x, rel_y);
235 if (move_member(p, rel_x, rel_y, ignore_position, true, false))
236 return true;
237 DirFinder::get_adjacent_dir(rel_x, rel_y, -1);
238 if (move_member(p, rel_x, rel_y, ignore_position, true, false))
239 return true;
240 DirFinder::get_adjacent_dir(rel_x, rel_y, 2);
241 if (move_member(p, rel_x, rel_y, ignore_position, true, false))
242 return true;
243 return false;
244 }
245
246 /* Try moving in a forward direction. (direction leader moved) */
try_moving_forward(uint32 p)247 bool PartyPathFinder::try_moving_forward(uint32 p) {
248 sint8 vec_x = 0, vec_y = 0;
249 get_forward_dir(vec_x, vec_y);
250 if (!move_member(p, vec_x, vec_y))
251 return false;
252 return true;
253 }
254
255 /* Follower moves in the direction of their target, trying both adjacent
256 * directions if necessary.
257 * Returns true if character moved, or doesn't need to. Returns false if he stil
258 * needs to try to move. */
try_moving_to_target(uint32 p,bool avoid_damage_tiles)259 bool PartyPathFinder::try_moving_to_target(uint32 p, bool avoid_damage_tiles) {
260 sint8 rel_x, rel_y;
261 get_target_dir(p, rel_x, rel_y);
262 if (!move_member(p, rel_x, rel_y, false, false)) { // don't ignore position, don't bump other followers
263 sint8 leader = get_leader();
264 if (leader >= 0) {
265 // try both adjacent directions, first the one which is
266 // perpendicular to the leader's facing direction
267 uint8 ldir = get_member(leader).actor->get_direction();
268 sint8 dx = (ldir == NUVIE_DIR_W) ? -1 : (ldir == NUVIE_DIR_E) ? 1 : 0;
269 sint8 dy = (ldir == NUVIE_DIR_N) ? -1 : (ldir == NUVIE_DIR_S) ? 1 : 0;
270 sint8 relx2 = rel_x, rely2 = rel_y; // adjacent directions, counter-clockwise
271 sint8 relx3 = rel_x, rely3 = rel_y; // clockwise
272 DirFinder::get_adjacent_dir(relx2, rely2, -1);
273 DirFinder::get_adjacent_dir(relx3, rely3, 1);
274 if (!(abs(relx2) == abs(dy) && abs(rely2) == abs(dx))) {
275 // first isn't perpendicular; swap directions
276 DirFinder::get_adjacent_dir(relx2, rely2, 2); // becomes clockwise
277 DirFinder::get_adjacent_dir(relx3, rely3, -2); // counter-clockwise
278 }
279 if (!move_member(p, relx2, rely2))
280 if (!move_member(p, relx3, rely3)) {
281 // this makes Iolo (follower 3) try to move around other party
282 // members when leader changes direction and they
283 // block him
284 return false;
285 }
286 }
287 }
288 return true;
289 }
290
291 /* Follower p will try moving in every direction, first towards the leader, and
292 * then in a circular order starting with the direction closest to target_loc.
293 * Returns true if character moved. */
try_all_directions(uint32 p,MapCoord target_loc)294 bool PartyPathFinder::try_all_directions(uint32 p, MapCoord target_loc) {
295 MapCoord leader_loc = party->get_leader_location();
296 MapCoord member_loc = party->get_location(p);
297 sint8 to_leader_x = get_wrapped_rel_dir(leader_loc.x, member_loc.x, leader_loc.z);
298 sint8 to_leader_y = get_wrapped_rel_dir(leader_loc.y, member_loc.y, leader_loc.z);
299 // rotate direction, towards target
300 sint8 rot = DirFinder::get_turn_towards_dir(to_leader_x, to_leader_y,
301 sint8(target_loc.x - member_loc.x),
302 sint8(target_loc.y - member_loc.y));
303 if (rot == 0) rot = 1; // default clockwise
304
305 // check all directions, first only those adjacent to the real target
306 MapCoord real_target = party->get_formation_coords(p);
307 for (uint32 dir = 0; dir < 8; dir++) {
308 MapCoord dest = member_loc.abs_coords(to_leader_x, to_leader_y);
309 if (dest.distance(real_target) == 1 && move_member(p, to_leader_x, to_leader_y))
310 return true;
311 DirFinder::get_adjacent_dir(to_leader_x, to_leader_y, rot);
312 }
313 // this time, don't allow any moves that take us further from the leader
314 // than our target position is (unless we're already that far away)
315 for (uint32 dir = 0; dir < 8; dir++) {
316 MapCoord dest = member_loc.abs_coords(to_leader_x, to_leader_y);
317 if ((dest.distance(leader_loc) <= real_target.distance(leader_loc)
318 || dest.distance(leader_loc) <= member_loc.distance(leader_loc))
319 && move_member(p, to_leader_x, to_leader_y))
320 return true;
321 DirFinder::get_adjacent_dir(to_leader_x, to_leader_y, rot);
322 }
323 // now try any move possible (don't bother if already contiguous)
324 if (!is_contiguous(p))
325 for (uint32 dir = 0; dir < 8; dir++) {
326 //MapCoord dest = member_loc.abs_coords(to_leader_x, to_leader_y);
327 if (move_member(p, to_leader_x, to_leader_y))
328 return true;
329 DirFinder::get_adjacent_dir(to_leader_x, to_leader_y, rot);
330 }
331 return false;
332 }
333
334 /* Returns a list(vector) of all locations adjacent to 'center', sorted by their
335 * distance to 'target'. (near to far)
336 */
337 vector<MapCoord>
get_neighbor_tiles(MapCoord & center,MapCoord & target)338 PartyPathFinder::get_neighbor_tiles(MapCoord ¢er, MapCoord &target) {
339 sint8 rel_x = get_wrapped_rel_dir(target.x, center.x, target.z);
340 sint8 rel_y = get_wrapped_rel_dir(target.y, center.y, target.z);
341 vector<MapCoord> neighbors;
342 for (uint32 dir = 0; dir < 8; dir++) {
343 MapCoord this_square = center.abs_coords(rel_x, rel_y); // initial square in first iteration
344 vector<MapCoord>::iterator i = neighbors.begin();
345 uint32 sorted = 0;
346 for (; sorted < neighbors.size(); sorted++, i++) {
347 MapCoord check_square = neighbors[sorted];
348 if (target.distance(this_square) < target.distance(check_square)
349 && !party->is_anyone_at(check_square)) { // exclude squares with any other party member from being at the front of the list
350 neighbors.insert(i, this_square);
351 break;
352 }
353 }
354 if (sorted == neighbors.size()) // place before end of the list
355 neighbors.insert(neighbors.end(), this_square);
356 DirFinder::get_adjacent_dir(rel_x, rel_y, 1);
357 }
358 return neighbors;
359 }
360
361 /* This is like Actor::push_actor and exchanges positions with an actor, or
362 * moves them to a neighboring location. This method doesn't move the original
363 * "bumper" actor. When exchanging positions, the bumped character loses their
364 * turn.
365 * Characters can only be "bumped" within one move of their ORIGINAL square. It
366 * is illegal to bump someone into a CRITICAL or IMPOSSIBLE state.
367 * Returns true if the party member moved successfully.
368 */
bump_member(uint32 bumped_member_num,uint32 member_num)369 bool PartyPathFinder::bump_member(uint32 bumped_member_num, uint32 member_num) {
370 if (member_num >= party->get_party_size())
371 return false;
372 Actor *actor = get_member(bumped_member_num).actor;
373 if (actor->is_immobile())
374 return false;
375 Actor *push_actor = get_member(member_num).actor;
376 MapCoord bump_from = party->get_location(bumped_member_num);
377 MapCoord bump_target = party->get_formation_coords(bumped_member_num); // initial direction
378 MapCoord member_loc = party->get_location(member_num);
379 sint8 to_member_x = get_wrapped_rel_dir(member_loc.x, bump_from.x, member_loc.z); // to push_actor
380 sint8 to_member_y = get_wrapped_rel_dir(member_loc.y, bump_from.y, member_loc.z);
381
382 // sort neighboring squares by distance to target (closest first)
383 vector<MapCoord> neighbors;
384 if (bump_target != bump_from)
385 neighbors = get_neighbor_tiles(bump_from, bump_target);
386 else { // sort by distance to leader
387 MapCoord leader_loc = party->get_leader_location();
388 neighbors = get_neighbor_tiles(bump_from, leader_loc);
389 }
390
391 for (uint32 dir = 0; dir < 8; dir++) {
392 sint8 rel_x = get_wrapped_rel_dir(neighbors[dir].x, bump_from.x, bump_from.z);
393 sint8 rel_y = get_wrapped_rel_dir(neighbors[dir].y, bump_from.y, bump_from.z);
394 // Since this direction is blocked, it will only be at the end of the
395 // sorted list.
396 if (rel_x == to_member_x && rel_y == to_member_y) {
397 // Use special push() that ignores actors, and reduces moves left.
398 actor->push(push_actor, ACTOR_PUSH_HERE);
399 return true;
400 } else if (move_member(bumped_member_num, rel_x, rel_y)) {
401 // Reduce moves left so actor can't move (or get pushed) again.
402 actor->set_moves_left(0);
403 return true;
404 }
405 }
406 return false;
407 }
408
409 /* "Try a move", only if target is contiguous. */
move_member(uint32 member_num,sint16 relx,sint16 rely,bool ignore_position,bool can_bump,bool avoid_danger_tiles)410 bool PartyPathFinder::move_member(uint32 member_num, sint16 relx, sint16 rely, bool ignore_position, bool can_bump, bool avoid_danger_tiles) {
411 /**Do not call with relx and rely set to 0.**/
412 if (relx == 0 && rely == 0)
413 return true;
414 MapCoord member_loc = party->get_location(member_num);
415 MapCoord target(member_loc);
416 target = member_loc.abs_coords(relx, rely);
417 Actor *actor = get_member(member_num).actor;
418 ActorMoveFlags flags = ACTOR_IGNORE_MOVES;
419 if (!avoid_danger_tiles)
420 flags = flags | ACTOR_IGNORE_DANGER;
421
422 if (is_contiguous(member_num, target) || ignore_position) {
423 if (actor->move(target.x, target.y, target.z, flags)) {
424 actor->set_direction(relx, rely);
425 return true;
426 }
427 if (actor->get_error()->err == ACTOR_BLOCKED_BY_ACTOR) {
428 Actor *blocking_actor = actor->get_error()->blocking_actor;
429 sint8 blocking_member_num = -1;
430 if (blocking_actor)
431 blocking_member_num = party->get_member_num(blocking_actor);
432 if (blocking_member_num < sint32(member_num))
433 return false; // blocked by an actor not in the party
434 if (bump_member(uint32(blocking_member_num), member_num)
435 && actor->move(target.x, target.y, target.z, flags | ACTOR_IGNORE_MOVES)) {
436 actor->set_direction(relx, rely);
437 return true;
438 }
439 }
440 }
441 return false; // target is not contiguous, or move is blocked
442 }
443
444 /* Use a better pathfinder to search for the leader. */
seek_leader(uint32 p)445 void PartyPathFinder::seek_leader(uint32 p) {
446 Actor *actor = get_member(p).actor;
447 MapCoord leader_loc = party->get_leader_location();
448 ActorPathFinder *df = actor->get_pathfinder();
449 if (!df) {
450 df = new ActorPathFinder(actor, leader_loc);
451 actor->set_pathfinder(df, new SeekPath);
452 } else if (leader_moved()) // update target
453 df->set_goal(leader_loc);
454 }
455
end_seek(uint32 p)456 void PartyPathFinder::end_seek(uint32 p) {
457 get_member(p).actor->delete_pathfinder();
458 }
459
460 } // End of namespace Nuvie
461 } // End of namespace Ultima
462