Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/pathfinder/path.h"
#include "ultima/nuvie/pathfinder/actor_path_finder.h"
namespace Ultima {
namespace Nuvie {
ActorPathFinder::ActorPathFinder(Actor *a, MapCoord g)
: PathFinder(a->get_location(), g), actor(a) {
}
ActorPathFinder::~ActorPathFinder() {
}
bool ActorPathFinder::get_next_move(MapCoord &step) {
MapCoord rel_step;
if (have_path()) {
step = search->get_first_step();
return check_loc(step);
}
get_closest_dir(rel_step);
if (check_dir(loc, rel_step)) {
step = loc.abs_coords(rel_step.sx, rel_step.sy);
return true;
}
if (search_towards_target(goal, rel_step)) {
step = loc.abs_coords(rel_step.sx, rel_step.sy);
return true;
}
if (find_path()) {
step = search->get_first_step();
return check_loc(step);
}
return false;
}
/* Get relative direction from Loc to Goal and place in Rel_step. */
void ActorPathFinder::get_closest_dir(MapCoord &rel_step) {
rel_step.sx = clamp(goal.x - loc.x, -1, 1);
rel_step.sy = clamp(goal.y - loc.y, -1, 1);
rel_step.z = loc.z;
uint16 dx = loc.xdistance(goal), dy = loc.ydistance(goal);
if (dx > dy) rel_step.sy = 0;
else if (dx < dy) rel_step.sx = 0;
}
bool ActorPathFinder::check_loc(const MapCoord &mapLoc) {
return actor->check_move(mapLoc.x, mapLoc.y, mapLoc.z);
}
/* Find a move from actor to g, starting with rel_step. Replace
* rel_step with the result. */
bool ActorPathFinder::search_towards_target(const MapCoord &g, MapCoord &rel_step) {
MapCoord mapLoc = actor->get_location();
MapCoord ccw_rel_step = rel_step, cw_rel_step = rel_step;
if (check_dir(mapLoc, rel_step)) // check original direction
return true;
bool try_ccw = check_dir_and_distance(mapLoc, g, ccw_rel_step, -1); // check adjacent directions
bool try_cw = check_dir_and_distance(mapLoc, g, cw_rel_step, 1);
if (!try_ccw) try_ccw = check_dir_and_distance(mapLoc, g, ccw_rel_step, -2); // check perpendicular directions
if (!try_cw) try_cw = check_dir_and_distance(mapLoc, g, cw_rel_step, 2);
if (!try_ccw && !try_cw)
return false;
rel_step = ccw_rel_step;
if (!try_ccw) rel_step = cw_rel_step;
else if (!try_cw) rel_step = ccw_rel_step;
else { // both valid, use closest
MapCoord ccw_step = mapLoc.abs_coords(ccw_rel_step.sx, ccw_rel_step.sy);
MapCoord cw_step = mapLoc.abs_coords(cw_rel_step.sx, cw_rel_step.sy);
MapCoord target(g);
if (cw_step.distance(target) < ccw_step.distance(target))
rel_step = cw_rel_step;
}
return true;
}
// check rotated dir, and copy results to rel_step if neighbor is passable
bool ActorPathFinder::check_dir_and_distance(const MapCoord &mapLoc, const MapCoord &g, MapCoord &rel_step, sint8 rotate) {
MapCoord rel_step_2 = rel_step;
if (check_dir(mapLoc, rel_step_2, rotate)) {
MapCoord neighbor = mapLoc.abs_coords(rel_step_2.sx, rel_step_2.sy);
if (neighbor.distance(g) <= mapLoc.distance(g)) {
rel_step = rel_step_2;
return true;
}
}
return false;
}
// new direction is copied to rel if true
bool ActorPathFinder::check_dir(const MapCoord &mapLoc, MapCoord &rel, sint8 rot) {
sint8 xdir = rel.sx, ydir = rel.sy;
get_adjacent_dir(xdir, ydir, rot);
MapCoord new_loc = MapCoord(mapLoc).abs_coords(xdir, ydir);
if (check_loc(new_loc)) {
rel.sx = xdir;
rel.sy = ydir;
return true;
}
return false;
}
void ActorPathFinder::actor_moved() {
update_location();
// pop step
if (have_path())
search->remove_first_step();
}
void ActorPathFinder::set_actor(Actor *a) {
actor = a;
}
bool ActorPathFinder::update_location() {
if (!actor)
return false;
actor->get_location(&loc.x, &loc.y, &loc.z);
return true;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_ACTOR_PATH_FINDER_H
#define NUVIE_PATHFINDER_ACTOR_PATH_FINDER_H
#include "ultima/nuvie/pathfinder/path_finder.h"
#include "ultima/nuvie/pathfinder/dir_finder.h"
namespace Ultima {
namespace Nuvie {
class Actor;
class ActorPathFinder: public PathFinder, public DirFinder {
protected:
Actor *actor;
public:
ActorPathFinder(Actor *a, MapCoord g);
~ActorPathFinder() override;
void set_actor(Actor *a);
virtual bool update_location(); /* get location from actor (use any time) */
virtual void actor_moved(); /* the actor moved ON PATH...
(use after get_next_move()) */
bool check_loc(const MapCoord &loc) override;
void get_closest_dir(MapCoord &rel_step); // relative dir loc->goal
bool get_next_move(MapCoord &step) override;
protected:
bool search_towards_target(const MapCoord &g, MapCoord &rel_step);
bool check_dir(const MapCoord &loc, MapCoord &rel, sint8 rot = 0) override;
bool check_dir_and_distance(const MapCoord &loc, const MapCoord &g, MapCoord &rel_step, sint8 rotate);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,218 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/shared/std/containers.h"
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/pathfinder/dir_finder.h"
#include "ultima/nuvie/pathfinder/astar_path.h"
namespace Ultima {
namespace Nuvie {
AStarPath::AStarPath() : final_node(0) {
}
void AStarPath::create_path() {
astar_node *i = final_node; // iterator through steps, from back
delete_path();
Std::vector<astar_node *> reverse_list;
while (i) {
reverse_list.push_back(i);
i = i->parent;
}
while (!reverse_list.empty()) {
i = reverse_list.back();
add_step(i->loc);
reverse_list.pop_back();
}
set_path_size(step_count);
}/* Get a new neighbor to nnode and score it, returning true if it's usable. */
bool AStarPath::score_to_neighbor(sint8 dir, astar_node *nnode, astar_node *neighbor,
sint32 &nnode_to_neighbor) {
sint8 sx = -1, sy = -1;
DirFinder::get_adjacent_dir(sx, sy, dir); // sx,sy = neighbor -1,-1 + dir
// get neighbor of nnode towards sx,sy, and cost to that neighbor
neighbor->loc = nnode->loc.abs_coords(sx, sy);
nnode_to_neighbor = step_cost(nnode->loc, neighbor->loc);
if (nnode_to_neighbor == -1) {
delete neighbor; // this neighbor is blocked
return false;
}
return true;
}/* Compare a node's score to the start node to already scored neighbors. */
bool AStarPath::compare_neighbors(astar_node *nnode, astar_node *neighbor,
sint32 nnode_to_neighbor, astar_node *in_open,
astar_node *in_closed) {
neighbor->to_start = nnode->to_start + nnode_to_neighbor;
// ignore this neighbor if already checked and closer to start
if ((in_open && in_open->to_start <= neighbor->to_start)
|| (in_closed && in_closed->to_start <= neighbor->to_start)) {
delete neighbor;
return false;
}
return true;
}/* Check all neighbors of a node (location) and save them to the "seen" list. */
bool AStarPath::search_node_neighbors(astar_node *nnode, const MapCoord &goal,
const uint32 max_score) {
for (uint32 dir = 1; dir < 8; dir += 2) {
astar_node *neighbor = new astar_node;
sint32 nnode_to_neighbor = -1;
if (!score_to_neighbor(dir, nnode, neighbor, nnode_to_neighbor))
continue; // this neighbor is blocked
astar_node *in_open = find_open_node(neighbor),
*in_closed = find_closed_node(neighbor);
if (!compare_neighbors(nnode, neighbor, nnode_to_neighbor, in_open, in_closed))
continue;
neighbor->parent = nnode;
neighbor->to_goal = path_cost_est(neighbor->loc, goal);
neighbor->score = neighbor->to_start + neighbor->to_goal;
neighbor->len = nnode->len + 1;
if (neighbor->score > max_score) {
delete neighbor; // too far away
continue;
}
// take neighbor out of closed list and put into open list
if (in_closed)
remove_closed_node(in_closed);
if (!in_open)
push_open_node(neighbor);
}
return true;
}
/* Do A* search of tiles to create a path from `start' to `goal'.
* Don't search past nodes with a score over the max. score.
* Create a partial path to low-score nodes with a distance-to-start over the
* max_steps count, defined here. Actor may perform another search when needed.
* Returns true if a path is created
*/
bool AStarPath::path_search(const MapCoord &start, const MapCoord &goal) {
//DEBUG(0,LEVEL_DEBUGGING,"SEARCH: %d: %d,%d -> %d,%d\n",actor->get_actor_num(),start.x,start.y,goal.x,goal.y);
astar_node *start_node = new astar_node;
start_node->loc = start;
start_node->to_start = 0;
start_node->to_goal = path_cost_est(start, goal);
start_node->score = start_node->to_start + start_node->to_goal;
start_node->len = 0;
push_open_node(start_node);
const uint32 max_score = get_max_score(start_node->to_goal);
const uint32 max_steps = 8 * 2 * 4; // walk up to four screen lengths before searching again
while (!open_nodes.empty()) {
astar_node *nnode = pop_open_node(); // next closest
if (nnode->loc == goal || nnode->len >= max_steps) {
if (nnode->loc != goal)
DEBUG(0, LEVEL_DEBUGGING, "out of steps, making partial path (nnode->len=%d)\n", nnode->len);
//DEBUG(0,LEVEL_DEBUGGING,"GOAL\n");
final_node = nnode;
create_path();
delete_nodes();
return true; // reached goal - success
}
// check cardinal neighbors (starting at top going clockwise)
search_node_neighbors(nnode, goal, max_score);
// node and neighbors checked, put into closed
closed_nodes.push_back(nnode);
}
//DEBUG(0,LEVEL_DEBUGGING,"FAIL\n");
delete_nodes();
return false; // out of open nodes - failure
}
/* Return the cost of moving one step from `c1' to `c2', which is always 1. This
* isn't very helpful, so subclasses should provide their own function.
* Returns -1 if c2 is blocked. */
sint32 AStarPath::step_cost(const MapCoord &c1, const MapCoord &c2) {
if (!pf->check_loc(c2.x, c2.y, c2.z)
|| c2.distance(c1) > 1)
return -1;
return 1;
}
/* Return an item in the list of closed nodes whose location matches `ncmp'.
*/
astar_node *AStarPath::find_closed_node(astar_node *ncmp) {
for (astar_node *n : closed_nodes)
if (n->loc == ncmp->loc)
return n;
return nullptr;
}
/* Return an item in the list of open nodes whose location matches `ncmp'.
*/
astar_node *AStarPath::find_open_node(astar_node *ncmp) {
for (astar_node *n : open_nodes)
if (n->loc == ncmp->loc)
return n;
return nullptr;
}
/* Add new node pointer to the list of open nodes (sorting by score).
*/
void AStarPath::push_open_node(astar_node *node) {
if (open_nodes.empty()) {
open_nodes.push_front(node);
return;
}
Std::list<astar_node *>::iterator n = open_nodes.begin();
// get to end of list or to a node with equal or greater score
while (n != open_nodes.end() && (*n++)->score < node->score);
open_nodes.insert(n, node); // and add before that location
}
/* Return pointer to the highest priority node from the list of open nodes, and
* remove it.
*/
astar_node *AStarPath::pop_open_node() {
astar_node *best = open_nodes.front();
open_nodes.pop_front(); // remove it
return best;
}
/* Find item in the list of closed nodes whose location matched `ncmp', and
* remove it from the list.
*/
void AStarPath::remove_closed_node(astar_node *ncmp) {
Std::list<astar_node *>::iterator n;
for (n = closed_nodes.begin(); n != closed_nodes.end(); n++)
if ((*n)->loc == ncmp->loc) {
closed_nodes.erase(n);
return;
}
}
/* Delete nodes dereferenced from pointers in the lists.
*/
void AStarPath::delete_nodes() {
while (!open_nodes.empty()) {
astar_node *delnode = open_nodes.front();
open_nodes.pop_front();
delete delnode;
}
while (!closed_nodes.empty()) {
astar_node *delnode = closed_nodes.front();
closed_nodes.pop_front();
delete delnode;
}
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,82 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_ASTAR_PATH_H
#define NUVIE_PATHFINDER_ASTAR_PATH_H
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/pathfinder/path.h"
namespace Ultima {
namespace Nuvie {
typedef struct astar_node_s {
MapCoord loc; // location
uint32 to_start; // costs from this node to start and to goal
uint32 to_goal;
uint32 score; // node score
uint32 len; // number of nodes before this one, regardless of score
struct astar_node_s *parent;
astar_node_s() : loc(0, 0, 0), to_start(0), to_goal(0), score(0), len(0),
parent(nullptr) { }
} astar_node;
/* Provides A* search and cost methods for PathFinder and subclasses.
*/class AStarPath: public Path {
protected:
Std::list<astar_node *> open_nodes, closed_nodes; // nodes seen
astar_node *final_node; // last node in path search, used by create_path()
/* Forms a usable path from results of a search. */
void create_path();
/* Search routine. */
bool search_node_neighbors(astar_node *nnode, const MapCoord &goal, const uint32 max_score);
bool compare_neighbors(astar_node *nnode, astar_node *neighbor,
sint32 nnode_to_neighbor, astar_node *in_open,
astar_node *in_closed);
bool score_to_neighbor(sint8 dir, astar_node *nnode, astar_node *neighbor,
sint32 &nnode_to_neighbor);
public:
AStarPath();
~AStarPath() override { }
bool path_search(const MapCoord &start, const MapCoord &goal) override;
uint32 path_cost_est(const MapCoord &s, const MapCoord &g) override {
return Path::path_cost_est(s, g);
}
uint32 get_max_score(uint32 cost) override {
return Path::get_max_score(cost);
}
uint32 path_cost_est(const astar_node &n1, const astar_node &n2) {
return Path::path_cost_est(n1.loc, n2.loc);
}
sint32 step_cost(const MapCoord &c1, const MapCoord &c2) override;
protected:
/* FIXME: These node functions can be replaced with a priority_queue and a list. */
astar_node *find_open_node(astar_node *ncmp);
void push_open_node(astar_node *node);
astar_node *pop_open_node();
astar_node *find_closed_node(astar_node *ncmp);
void remove_closed_node(astar_node *ncmp);
void delete_nodes();
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,95 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/pathfinder/combat_path_finder.h"
namespace Ultima {
namespace Nuvie {
CombatPathFinder::CombatPathFinder(Actor *a)
: ActorPathFinder(a, a->get_location()), target_mode(PATHFINDER_NONE),
max_dist(0), target(nullptr) {
}
/* Without a mode set, CombatPathFinder is identical to ActorPathFinder. */
CombatPathFinder::CombatPathFinder(Actor *a, Actor *t)
: ActorPathFinder(a, t->get_location()), target_mode(PATHFINDER_CHASE),
target(t), max_dist(0) {
}
CombatPathFinder::~CombatPathFinder() {
}
bool CombatPathFinder::reached_goal() {
if (target_mode == PATHFINDER_CHASE)
return (loc.distance(goal) <= 1);
if (target_mode == PATHFINDER_FLEE)
return (max_dist != 0 && loc.distance(goal) > max_dist);
return true;
}
bool CombatPathFinder::set_flee_mode(Actor *targetActor) {
target_mode = PATHFINDER_FLEE;
target = targetActor;
update_location();
return true;
}
bool CombatPathFinder::set_chase_mode(Actor *targetActor) {
target_mode = PATHFINDER_CHASE;
target = targetActor;
update_location();
return true;
}
bool CombatPathFinder::set_mode(CombatPathFinderMode mode, Actor *targetActor) {
target_mode = mode;
target = targetActor;
return true;
}
bool CombatPathFinder::update_location() {
ActorPathFinder::update_location();
set_goal(target->get_location());
if (max_dist != 0 && loc.distance(goal) > max_dist)
target_mode = PATHFINDER_NONE;
return true;
}
bool CombatPathFinder::get_next_move(MapCoord &step) {
if (target_mode == PATHFINDER_CHASE)
return ActorPathFinder::get_next_move(step);
if (target_mode == PATHFINDER_FLEE) {
get_closest_dir(step);
step.sx = -step.sx;
step.sy = -step.sy;
if (check_dir(loc, step)) {
step = loc.abs_coords(step.sx, step.sy);
return true;
}
}
return false;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,63 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_COMBAT_PATH_FINDER_H
#define NUVIE_PATHFINDER_COMBAT_PATH_FINDER_H
#include "ultima/nuvie/pathfinder/actor_path_finder.h"
namespace Ultima {
namespace Nuvie {
typedef enum {
PATHFINDER_NONE,
PATHFINDER_CHASE,
PATHFINDER_FLEE
} CombatPathFinderMode;
class CombatPathFinder: public ActorPathFinder {
protected:
Actor *target;
CombatPathFinderMode target_mode;
bool update_location() override;
uint8 max_dist;
public:
CombatPathFinder(Actor *a);
CombatPathFinder(Actor *a, Actor *t);
~CombatPathFinder() override;
bool set_flee_mode(Actor *actor);
bool set_chase_mode(Actor *actor);
bool set_mode(CombatPathFinderMode mode, Actor *actor);
void set_distance(uint8 dist) {
max_dist = dist;
}
bool get_next_move(MapCoord &step) override;
bool reached_goal() override;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,115 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/pathfinder/dir_finder.h"
namespace Ultima {
namespace Nuvie {
/** STATIC FUNCTIONS **/
/* From a normalized direction xdir,ydir as base, get the normalized direction
* towards one of the seven nearby tiles to the left or right of base.
* For rotate: -n = left n tiles, n = right n tiles
*/
void DirFinder::get_adjacent_dir(sint8 &xdir, sint8 &ydir, sint8 rotate) {
struct {
sint8 x, y;
} neighbors[8] = { { -1, -1}, { +0, -1}, { +1, -1},
{ +1, +0},/*ACTOR*/ { +1, +1},
{ +0, +1}, { -1, +1}, { -1, +0}
};
for (uint32 start = 0; start < 8; start++)
if (neighbors[start].x == xdir && neighbors[start].y == ydir) {
sint32 dest = start + rotate;
while (dest < 0 || dest > 7)
dest += (dest < 0) ? 8 : -8;
xdir = neighbors[dest].x;
ydir = neighbors[dest].y;
break;
}
}
NuvieDir DirFinder::get_nuvie_dir(sint16 xrel, sint16 yrel) {
NuvieDir direction = NUVIE_DIR_N; // default
if (xrel == 0 && yrel == 0) // nowhere
return direction;
if (xrel == 0) // up or down
direction = (yrel < 0) ? NUVIE_DIR_N : NUVIE_DIR_S;
else if (yrel == 0) // left or right
direction = (xrel < 0) ? NUVIE_DIR_W : NUVIE_DIR_E;
else if (xrel < 0 && yrel < 0)
direction = NUVIE_DIR_NW;
else if (xrel > 0 && yrel < 0)
direction = NUVIE_DIR_NE;
else if (xrel < 0 && yrel > 0)
direction = NUVIE_DIR_SW;
else if (xrel > 0 && yrel > 0)
direction = NUVIE_DIR_SE;
return direction;
}
NuvieDir DirFinder::get_nuvie_dir(uint16 sx, uint16 sy, uint16 tx, uint16 ty, uint8 z) {
return DirFinder::get_nuvie_dir(get_wrapped_rel_dir(tx, sx, z), get_wrapped_rel_dir(ty, sy, z));
}
// oxdir = original xdir, txdir = to xdir
sint8 DirFinder::get_turn_towards_dir(sint16 oxdir, sint16 oydir, sint8 txdir, sint8 tydir) {
oxdir = clamp(oxdir, -1, 1);
oydir = clamp(oydir, -1, 1);
txdir = clamp(txdir, -1, 1);
tydir = clamp(tydir, -1, 1);
struct {
sint8 x, y;
} dirs[8] = {{ -1, -1}, { +0, -1}, { +1, -1}, { +1, +0}, { +1, +1}, { +0, +1}, { -1, +1}, { -1, +0}};
uint8 t = 0, o = 0;
for (uint8 d = 0; d < 8; d++) {
if (dirs[d].x == oxdir && dirs[d].y == oydir)
o = d;
if (dirs[d].x == txdir && dirs[d].y == tydir)
t = d;
}
sint8 turn = t - o;
if (turn > 4)
turn = -(8 - turn);
return clamp(turn, -1, 1);
}
// xdir,ydir = normal direction from->to (simple method)
void DirFinder::get_normalized_dir(const MapCoord &from, const MapCoord &to, sint8 &xdir, sint8 &ydir) {
xdir = clamp(to.x - from.x, -1, 1);
ydir = clamp(to.y - from.y, -1, 1);
/* uint16 dx = from.xdistance(to), dy = from.ydistance(to);
if(dx > 0 && dy > 0)
{
if(dx > dy) xdir = 0;
else if(dx < dy) ydir = 0;
}*/
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,44 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_DIR_FINDER_H
#define NUVIE_PATHFINDER_DIR_FINDER_H
namespace Ultima {
namespace Nuvie {
class MapCoord;
class DirFinder {
public:
DirFinder() { }
static void get_adjacent_dir(sint8 &xdir, sint8 &ydir, sint8 rotate);
static NuvieDir get_nuvie_dir(sint16 xrel, sint16 yrel);
static NuvieDir get_nuvie_dir(uint16 sx, uint16 sy, uint16 tx, uint16 ty, uint8 z);
static sint8 get_turn_towards_dir(sint16 oxdir, sint16 oydir, sint8 txdir, sint8 tydir);
static void get_normalized_dir(const MapCoord &from, const MapCoord &to, sint8 &xdir, sint8 &ydir);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,474 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/shared/std/containers.h"
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/pathfinder/seek_path.h"
#include "ultima/nuvie/pathfinder/actor_path_finder.h"
#include "ultima/nuvie/pathfinder/party_path_finder.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/u6_objects.h"
namespace Ultima {
namespace Nuvie {
using Std::vector;
PartyPathFinder::PartyPathFinder(Party *p) : party(p) {
assert(p);
}
PartyPathFinder::~PartyPathFinder() {
}
/* True if a member's target and leader are in roughly the same direction. */
bool PartyPathFinder::is_behind_target(uint32 member_num) {
if (get_leader() < 0)
return false;
uint8 ldir = get_member(get_leader()).actor->get_direction(); // leader direciton
MapCoord from = party->get_location(member_num);
MapCoord to = party->get_formation_coords(member_num); // target
sint8 to_x = to.x - from.x, to_y = to.y - from.y;
return (((ldir == NUVIE_DIR_N && to_y < 0)
|| (ldir == NUVIE_DIR_S && to_y > 0)
|| (ldir == NUVIE_DIR_E && to_x > 0)
|| (ldir == NUVIE_DIR_W && to_x < 0)));
}
bool PartyPathFinder::is_at_target(uint32 p) {
MapCoord target_loc = party->get_formation_coords(p);
MapCoord member_loc = party->get_location(p);
return (target_loc == member_loc);
}
/* Is anyone in front of `member_num' adjacent to `from'?
* (is_contiguous(member, member_loc) == "is member adjacent to another member
* whose following position is lower-numbered?") */
bool PartyPathFinder::is_contiguous(uint32 member_num, const MapCoord &from) {
const bool isU6 = Game::get_game()->get_game_type() == NUVIE_GAME_U6;
const Actor *inActor = get_member(member_num).actor;
const bool isMouse = isU6 && inActor->get_obj_n() == OBJ_U6_MOUSE;
bool retVal = false;
for (uint32 q = 0; q < member_num; q++) { // check lower-numbered members
const Actor *actor = get_member(q).actor;
const bool otherIsMouse = isU6 && actor->get_obj_n() == OBJ_U6_MOUSE;
if (actor && actor->is_immobile() == true) continue;
MapCoord loc = party->get_location(q);
if (!isMouse && !otherIsMouse && from.distance(loc) == 0)
return false; // Do not allow stacking (except for Sherry)
if (from.distance(loc) <= 1) {
retVal = true;
continue; // Stay in the loop to check if other party members are on the same tile
}
}
return retVal;
}
/* Is member adjacent to another member whose following position is lower-numbered? */
bool PartyPathFinder::is_contiguous(uint32 member_num) {
MapCoord member_loc = party->get_location(member_num);
return (is_contiguous(member_num, member_loc));
}
/* Returns in rel_x and rel_y the direction a character needs to move to get
* closer to their target. */
void PartyPathFinder::get_target_dir(uint32 p, sint8 &rel_x, sint8 &rel_y) {
//MapCoord leader_loc = party->get_leader_location();
MapCoord target_loc = party->get_formation_coords(p);
MapCoord member_loc = party->get_location(p);
rel_x = get_wrapped_rel_dir(target_loc.x, member_loc.x, target_loc.z);
rel_y = get_wrapped_rel_dir(target_loc.y, member_loc.y, target_loc.z);
}
/* Returns in vec_x and vec_y the last direction the leader moved in. It's
* derived from his facing direction so it's not as precise as get_last_move(). */
void PartyPathFinder::get_forward_dir(sint8 &vec_x, sint8 &vec_y) {
// get_last_move(vec_x, vec_y);
vec_x = 0;
vec_y = 0;
NuvieDir dir = (get_leader() >= 0) ? get_member(get_leader()).actor->get_direction() : NUVIE_DIR_N;
if (dir == NUVIE_DIR_N) {
vec_x = 0;
vec_y = -1;
} else if (dir == NUVIE_DIR_S) {
vec_x = 0;
vec_y = 1;
} else if (dir == NUVIE_DIR_E) {
vec_x = 1;
vec_y = 0;
} else if (dir == NUVIE_DIR_W) {
vec_x = -1;
vec_y = 0;
}
}
/* Returns in vec_x and vec_y the last direction the leader moved in. */
void PartyPathFinder::get_last_move(sint8 &vec_x, sint8 &vec_y) {
MapCoord leader_loc = party->get_leader_location();
vec_x = get_wrapped_rel_dir(leader_loc.x, party->prev_leader_x, leader_loc.z);
vec_y = get_wrapped_rel_dir(leader_loc.y, party->prev_leader_y, leader_loc.z);
}
/* Returns true if the leader moved before the last call to follow(). */
bool PartyPathFinder::leader_moved() {
MapCoord leader_loc = party->get_leader_location();
return ((leader_loc.x - party->prev_leader_x)
|| (leader_loc.y - party->prev_leader_y));
}
/* Returns true if the leader moved far enough away from follower to pull him. */
bool PartyPathFinder::leader_moved_away(uint32 p) {
MapCoord leader_loc = party->get_leader_location();
MapCoord target_loc = party->get_formation_coords(p);
MapCoord member_loc = party->get_location(p);
return (leader_loc.distance(member_loc) > leader_loc.distance(target_loc));
}
/* Compares leader position with last known position. */
bool PartyPathFinder::leader_moved_diagonally() {
MapCoord leader_loc = party->get_leader_location();
return (party->prev_leader_x != leader_loc.x
&& party->prev_leader_y != leader_loc.y);
}
bool PartyPathFinder::follow_passA(uint32 p) {
bool contiguous = is_contiguous(p);
bool try_again = false;
sint8 vec_x = 0, vec_y = 0; // previous direction of party leader's movement
sint8 rel_x = 0, rel_y = 0; // direction to target
get_target_dir(p, rel_x, rel_y);
if (contiguous) {
if (is_at_target(p))
return true;
// Move towards target, and see if we get to try again.
// If you always get an extra move, distant followers move towards the
// leader instead of forward. Only get an extra move if we stopped, or
// blocked square is in the same direction the leader moved.
get_last_move(vec_x, vec_y);
if (!leader_moved() && !try_moving_to_target(p))
try_again = true;
else if (leader_moved() && leader_moved_away(p) && !try_moving_to_target(p))
if (is_behind_target(p))
try_again = true;
} else {
if (!move_member(p, rel_x, rel_y))
try_again = true;
}
// get another move chance here
if (try_again) {
MapCoord target_loc = party->get_formation_coords(p);
if (!try_all_directions(p, target_loc)) { // turn towards target
if (contiguous)
return false;
if (!move_member(p, rel_x, rel_y, true)) // allow non-contiguous moves
return false;
}
}
return true;
}
bool PartyPathFinder::follow_passB(uint32 p) {
if (is_contiguous(p)) {
if (is_at_target(p))
return true;
if (leader_moved_away(p)) { // only move if leader walked away from us
// move forward (direction leader moved) if target is ahead of us
if (leader_moved() && is_behind_target(p))
try_moving_forward(p);
if (leader_moved_diagonally()) // extra move
try_moving_sideways(p);
}
} else {
if (!try_moving_forward(p)) {
sint8 vec_x, vec_y;
get_forward_dir(vec_x, vec_y);
MapCoord member_loc = party->get_location(p);
MapCoord forward_loc = member_loc.abs_coords(vec_x, vec_y);
try_all_directions(p, forward_loc); // turn towards forward
}
}
if (!is_contiguous(p)) // critical; still not contiguous
if (!try_moving_to_leader(p, true))
return false;
return true;
}
/* Follower moves up, down, left, or right. (to intercept a target who moved
* diagonally) Returns true if the character moved. */
bool PartyPathFinder::try_moving_sideways(uint32 p) {
// prefer movement towards target direction
sint8 rel_x, rel_y;
get_target_dir(p, rel_x, rel_y);
if (!move_member(p, rel_x, 0)) // try two directions
if (!move_member(p, 0, rel_y))
return false;
return true;
}
/* Follower tries moving towards the leader, and then towards each adjacent
* direction if necessary. Returns true if the character moved. */
bool PartyPathFinder::try_moving_to_leader(uint32 p, bool ignore_position) {
// move towards leader (allow non-contiguous moves)
sint8 rel_x, rel_y;
get_target_dir(p, rel_x, rel_y);
if (move_member(p, rel_x, rel_y, ignore_position, true, false))
return true;
DirFinder::get_adjacent_dir(rel_x, rel_y, -1);
if (move_member(p, rel_x, rel_y, ignore_position, true, false))
return true;
DirFinder::get_adjacent_dir(rel_x, rel_y, 2);
if (move_member(p, rel_x, rel_y, ignore_position, true, false))
return true;
return false;
}
/* Try moving in a forward direction. (direction leader moved) */
bool PartyPathFinder::try_moving_forward(uint32 p) {
sint8 vec_x = 0, vec_y = 0;
get_forward_dir(vec_x, vec_y);
if (!move_member(p, vec_x, vec_y))
return false;
return true;
}
/* Follower moves in the direction of their target, trying both adjacent
* directions if necessary.
* Returns true if character moved, or doesn't need to. Returns false if he stil
* needs to try to move. */
bool PartyPathFinder::try_moving_to_target(uint32 p, bool avoid_damage_tiles) {
sint8 rel_x, rel_y;
get_target_dir(p, rel_x, rel_y);
if (!move_member(p, rel_x, rel_y, false, false)) { // don't ignore position, don't bump other followers
sint8 leader = get_leader();
if (leader >= 0) {
// try both adjacent directions, first the one which is
// perpendicular to the leader's facing direction
uint8 ldir = get_member(leader).actor->get_direction();
sint8 dx = (ldir == NUVIE_DIR_W) ? -1 : (ldir == NUVIE_DIR_E) ? 1 : 0;
sint8 dy = (ldir == NUVIE_DIR_N) ? -1 : (ldir == NUVIE_DIR_S) ? 1 : 0;
sint8 relx2 = rel_x, rely2 = rel_y; // adjacent directions, counter-clockwise
sint8 relx3 = rel_x, rely3 = rel_y; // clockwise
DirFinder::get_adjacent_dir(relx2, rely2, -1);
DirFinder::get_adjacent_dir(relx3, rely3, 1);
if (!(abs(relx2) == abs(dy) && abs(rely2) == abs(dx))) {
// first isn't perpendicular; swap directions
DirFinder::get_adjacent_dir(relx2, rely2, 2); // becomes clockwise
DirFinder::get_adjacent_dir(relx3, rely3, -2); // counter-clockwise
}
if (!move_member(p, relx2, rely2))
if (!move_member(p, relx3, rely3)) {
// this makes Iolo (follower 3) try to move around other party
// members when leader changes direction and they
// block him
return false;
}
}
}
return true;
}
/* Follower p will try moving in every direction, first towards the leader, and
* then in a circular order starting with the direction closest to target_loc.
* Returns true if character moved. */
bool PartyPathFinder::try_all_directions(uint32 p, MapCoord target_loc) {
MapCoord leader_loc = party->get_leader_location();
MapCoord member_loc = party->get_location(p);
sint8 to_leader_x = get_wrapped_rel_dir(leader_loc.x, member_loc.x, leader_loc.z);
sint8 to_leader_y = get_wrapped_rel_dir(leader_loc.y, member_loc.y, leader_loc.z);
// rotate direction, towards target
sint8 rot = DirFinder::get_turn_towards_dir(to_leader_x, to_leader_y,
sint8(target_loc.x - member_loc.x),
sint8(target_loc.y - member_loc.y));
if (rot == 0) rot = 1; // default clockwise
// check all directions, first only those adjacent to the real target
MapCoord real_target = party->get_formation_coords(p);
for (uint32 dir = 0; dir < 8; dir++) {
MapCoord dest = member_loc.abs_coords(to_leader_x, to_leader_y);
if (dest.distance(real_target) == 1 && move_member(p, to_leader_x, to_leader_y))
return true;
DirFinder::get_adjacent_dir(to_leader_x, to_leader_y, rot);
}
// this time, don't allow any moves that take us further from the leader
// than our target position is (unless we're already that far away)
for (uint32 dir = 0; dir < 8; dir++) {
MapCoord dest = member_loc.abs_coords(to_leader_x, to_leader_y);
if ((dest.distance(leader_loc) <= real_target.distance(leader_loc)
|| dest.distance(leader_loc) <= member_loc.distance(leader_loc))
&& move_member(p, to_leader_x, to_leader_y))
return true;
DirFinder::get_adjacent_dir(to_leader_x, to_leader_y, rot);
}
// now try any move possible (don't bother if already contiguous)
if (!is_contiguous(p))
for (uint32 dir = 0; dir < 8; dir++) {
//MapCoord dest = member_loc.abs_coords(to_leader_x, to_leader_y);
if (move_member(p, to_leader_x, to_leader_y))
return true;
DirFinder::get_adjacent_dir(to_leader_x, to_leader_y, rot);
}
return false;
}
/* Returns a list(vector) of all locations adjacent to 'center', sorted by their
* distance to 'target'. (near to far)
*/
vector<MapCoord>
PartyPathFinder::get_neighbor_tiles(const MapCoord &center, const MapCoord &target) {
sint8 rel_x = get_wrapped_rel_dir(target.x, center.x, target.z);
sint8 rel_y = get_wrapped_rel_dir(target.y, center.y, target.z);
vector<MapCoord> neighbors;
for (uint32 dir = 0; dir < 8; dir++) {
MapCoord this_square = center.abs_coords(rel_x, rel_y); // initial square in first iteration
vector<MapCoord>::iterator i = neighbors.begin();
uint32 sorted = 0;
for (; sorted < neighbors.size(); sorted++, i++) {
MapCoord check_square = neighbors[sorted];
if (target.distance(this_square) < target.distance(check_square)
&& !party->is_anyone_at(check_square)) { // exclude squares with any other party member from being at the front of the list
neighbors.insert(i, this_square);
break;
}
}
if (sorted == neighbors.size()) // place before end of the list
neighbors.insert(neighbors.end(), this_square);
DirFinder::get_adjacent_dir(rel_x, rel_y, 1);
}
return neighbors;
}
/* This is like Actor::push_actor and exchanges positions with an actor, or
* moves them to a neighboring location. This method doesn't move the original
* "bumper" actor. When exchanging positions, the bumped character loses their
* turn.
* Characters can only be "bumped" within one move of their ORIGINAL square. It
* is illegal to bump someone into a CRITICAL or IMPOSSIBLE state.
* Returns true if the party member moved successfully.
*/
bool PartyPathFinder::bump_member(uint32 bumped_member_num, uint32 member_num) {
if (member_num >= party->get_party_size())
return false;
Actor *actor = get_member(bumped_member_num).actor;
if (actor->is_immobile())
return false;
Actor *push_actor = get_member(member_num).actor;
MapCoord bump_from = party->get_location(bumped_member_num);
MapCoord bump_target = party->get_formation_coords(bumped_member_num); // initial direction
MapCoord member_loc = party->get_location(member_num);
sint8 to_member_x = get_wrapped_rel_dir(member_loc.x, bump_from.x, member_loc.z); // to push_actor
sint8 to_member_y = get_wrapped_rel_dir(member_loc.y, bump_from.y, member_loc.z);
// sort neighboring squares by distance to target (closest first)
vector<MapCoord> neighbors;
if (bump_target != bump_from)
neighbors = get_neighbor_tiles(bump_from, bump_target);
else { // sort by distance to leader
MapCoord leader_loc = party->get_leader_location();
neighbors = get_neighbor_tiles(bump_from, leader_loc);
}
for (uint32 dir = 0; dir < 8; dir++) {
sint8 rel_x = get_wrapped_rel_dir(neighbors[dir].x, bump_from.x, bump_from.z);
sint8 rel_y = get_wrapped_rel_dir(neighbors[dir].y, bump_from.y, bump_from.z);
// Since this direction is blocked, it will only be at the end of the
// sorted list.
if (rel_x == to_member_x && rel_y == to_member_y) {
// Use special push() that ignores actors, and reduces moves left.
actor->push(push_actor, ACTOR_PUSH_HERE);
return true;
} else if (move_member(bumped_member_num, rel_x, rel_y)) {
// Reduce moves left so actor can't move (or get pushed) again.
actor->set_moves_left(0);
return true;
}
}
return false;
}
/* "Try a move", only if target is contiguous. */
bool PartyPathFinder::move_member(uint32 member_num, sint16 relx, sint16 rely, bool ignore_position, bool can_bump, bool avoid_danger_tiles) {
/**Do not call with relx and rely set to 0.**/
if (relx == 0 && rely == 0)
return true;
MapCoord member_loc = party->get_location(member_num);
MapCoord target(member_loc);
target = member_loc.abs_coords(relx, rely);
Actor *actor = get_member(member_num).actor;
ActorMoveFlags flags = ACTOR_IGNORE_MOVES | ACTOR_IGNORE_PARTY_MEMBERS;
if (!avoid_danger_tiles)
flags = flags | ACTOR_IGNORE_DANGER;
if (is_contiguous(member_num, target) || ignore_position) {
if (actor->move(target.x, target.y, target.z, flags)) {
actor->set_direction(relx, rely);
return true;
}
// Block commented out: It seems that, at least in Ultima 6, party members do not
// bump each other, so we now ignore them via ACTOR_IGNORE_PARTY_MEMBERS.
// Instead, is_contiguous() prevents them from stacking.
// if (actor->get_error()->err == ACTOR_BLOCKED_BY_ACTOR) {
// const Actor *blocking_actor = actor->get_error()->blocking_actor;
// sint8 blocking_member_num = -1;
// if (blocking_actor)
// blocking_member_num = party->get_member_num(blocking_actor);
// if (blocking_member_num < sint32(member_num))
// return false; // blocked by an actor not in the party
// if (bump_member(uint32(blocking_member_num), member_num)
// && actor->move(target.x, target.y, target.z, flags | ACTOR_IGNORE_MOVES)) {
// actor->set_direction(relx, rely);
// return true;
// }
// }
}
return false; // target is not contiguous, or move is blocked
}
/* Use a better pathfinder to search for the leader. */
void PartyPathFinder::seek_leader(uint32 p) {
Actor *actor = get_member(p).actor;
MapCoord leader_loc = party->get_leader_location();
ActorPathFinder *df = actor->get_pathfinder();
if (!df) {
df = new ActorPathFinder(actor, leader_loc);
actor->set_pathfinder(df, new SeekPath);
} else if (leader_moved()) // update target
df->set_goal(leader_loc);
}
void PartyPathFinder::end_seek(uint32 p) {
get_member(p).actor->delete_pathfinder();
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,101 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_PARTY_PATH_FINDER_H
#define NUVIE_PATHFINDER_PARTY_PATH_FINDER_H
#include "ultima/nuvie/core/party.h"
#include "ultima/nuvie/core/map.h"
namespace Ultima {
namespace Nuvie {
/* PartyPathFinder moves the entire party at once.
* FIXME: Move to target if one square away and unblocked. Shamino isn't in the
* correct square after moving through a doorway.
* FIXME: Perhaps is_contiguous() should require everyone in front of a follower
* to also be contiguous. If more than one followers are lost, they stay
* together more than look for the leader.
* FIXME: When walking along the wall, the last follower doesn't move up when
* there is a closer free square. (it sometimes behaves the same way in U6)
* FIXME: If a higher-number follower is closer to the leader than a lower-numbered
* one, the one with higher priority bumps him out of the way, and he loses a
* move. This causes him to become non-contiguous. Followers should NEVER be
* moved to non-contiguous squares. Strangely, this only happens when moving in
* certain directions. (disable SEEK mode to check)
* FIXME: When changing directions, followers on opposite sides of the party
* (perpendicular to the forward direction) shouldn't exchange positions until
* the leader stops.
*/
#define AVOID_DAMAGE_TILES true
class PartyPathFinder {
Party *party; // friend
public:
PartyPathFinder(Party *p);
~PartyPathFinder();
bool follow_passA(uint32 p); // returns true if party member p moved
bool follow_passB(uint32 p);
void seek_leader(uint32 p);
void end_seek(uint32 p);
bool move_member(uint32 member_num, sint16 relx, sint16 rely, bool ignore_position = false, bool can_bump = true, bool avoid_danger_tiles = true);
bool bump_member(uint32 bumped_member_num, uint32 member_num);
bool is_seeking(uint32 member_num) {
return (get_member(member_num).actor->get_pathfinder() != 0);
}
bool is_contiguous(uint32 member_num, const MapCoord &from);
bool is_contiguous(uint32 member_num);
bool is_behind_target(uint32 member_num);
bool is_at_target(uint32 p);
void get_target_dir(uint32 p, sint8 &rel_x, sint8 &rel_y);
void get_forward_dir(sint8 &vec_x, sint8 &vec_y);
void get_last_move(sint8 &vec_x, sint8 &vec_y);
protected:
bool try_moving_to_leader(uint32 p, bool ignore_position);
bool try_moving_forward(uint32 p);
bool try_moving_to_target(uint32 p, bool avoid_damage_tiles = false);
bool try_all_directions(uint32 p, MapCoord target_loc);
bool try_moving_sideways(uint32 p);
bool leader_moved_away(uint32 p);
bool leader_moved_diagonally();
bool leader_moved();
Std::vector<MapCoord> get_neighbor_tiles(const MapCoord &center, const MapCoord &target);
// use party
struct PartyMember get_member(uint32 p) {
return (party->member[p]);
}
sint8 get_leader() {
return (party->get_leader());
}
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,128 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/misc/u6_misc.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/pathfinder/path.h"
namespace Ultima {
namespace Nuvie {
Path::Path()
: path(0), step_count(0), path_size(0), pf(0) {
}
Path::~Path() {
delete_path();
}
void Path::set_path_size(int alloc_size) {
path_size = alloc_size;
path = (MapCoord *)nuvie_realloc(path, path_size * sizeof(MapCoord));
}
/* Take estimate of a path, and return the highest allowed score of any nodes
* in the search of that path.
*/
uint32 Path::get_max_score(uint32 cost) {
uint32 max_score = cost * 2;
// search at least this far (else short paths will have too
// low of a maximum score to move around walls)
if (max_score < 8 * 2 * 3)
max_score = 8 * 2 * 3;
return max_score;
}
/* Return a weighted estimate of the highest cost from location `s' to `g'.
*/
uint32 Path::path_cost_est(const MapCoord &s, const MapCoord &g) {
uint32 major = (s.xdistance(g) >= s.ydistance(g))
? s.xdistance(g) : s.ydistance(g);
uint32 minor = (s.xdistance(g) >= s.ydistance(g))
? s.ydistance(g) : s.xdistance(g);
return (2 * major + minor);
}
/* Free and zero path.
*/
void Path::delete_path() {
if (path)
free(path);
path = nullptr;
step_count = 0;
path_size = 0;
}
const MapCoord &Path::get_first_step() {
return Path::get_step(0);
}
const MapCoord &Path::get_last_step() {
return Path::get_step(step_count - 1);
}
const MapCoord &Path::get_step(uint32 step_index) {
return path[step_index];
}
bool Path::have_path() {
return (path && step_count > 0);
}
void Path::get_path(MapCoord **path_start, uint32 &pathSize) {
if (path_start)
*path_start = path;
pathSize = step_count;
}
/* Increases path size in blocks and adds a step to the end of the path. */
void Path::add_step(const MapCoord &loc) {
const int path_block_size = 8;
if (step_count >= path_size) {
path_size += path_block_size;
path = (MapCoord *)nuvie_realloc(path, path_size * sizeof(MapCoord));
}
path[step_count++] = loc;
}
bool Path::remove_first_step() {
if (have_path()) {
step_count -= 1;
path_size = step_count;
MapCoord *new_path = (MapCoord *)malloc(path_size * sizeof(MapCoord));
memcpy(new_path, &(path[1]), step_count * sizeof(MapCoord));
free(path);
path = new_path;
return true;
}
return false;
}
bool Path::check_dir(const MapCoord &loc, MapCoord &rel) {
return pf->check_dir(loc, rel);
}
bool Path::check_loc(const MapCoord &loc) {
return pf->check_loc(loc);
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,84 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_PATH_H
#define NUVIE_PATHFINDER_PATH_H
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/pathfinder/path_finder.h"
namespace Ultima {
namespace Nuvie {
class MapCoord;
/* Abstract for a class that provides the path-search routines and cost methods
* to a PathFinder. This includes useful default methods, and path handling.
*/
class Path {
protected:
MapCoord *path; // list of tiles in the path, set by create_path()
uint32 step_count; // number of locations in the path
uint32 path_size; // allocated elements in list
PathFinder *pf;
void add_step(const MapCoord &loc);
bool check_dir(const MapCoord &loc, MapCoord &rel);
bool check_loc(const MapCoord &loc);
void set_path_size(int alloc_size);
public:
Path();
virtual ~Path();
void set_pathfinder(PathFinder *pathfinder) {
pf = pathfinder;
}
/* The pathfinding routine. Can return success or failure of a search. */
virtual bool path_search(const MapCoord &start, const MapCoord &goal) = 0;
void delete_path();
virtual bool have_path();
/* Returns the real cost of moving from a node (or location) to a
neighboring node. (a single step) */
virtual sint32 step_cost(const MapCoord &c1, const MapCoord &c2) = 0;
/* Estimate highest possible cost from s to g */
virtual uint32 path_cost_est(const MapCoord &s, const MapCoord &g);
/* Returns maximum score of any single node in the search of a path with
a certain estimated cost.*/
virtual uint32 get_max_score(uint32 cost);
virtual const MapCoord &get_first_step();
virtual const MapCoord &get_last_step();
virtual const MapCoord &get_step(uint32 step_index);
virtual void get_path(MapCoord **path_start, uint32 &path_size);
uint32 get_num_steps() {
return step_count;
}
virtual bool remove_first_step();
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,91 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/pathfinder/path.h"
#include "ultima/nuvie/pathfinder/path_finder.h"
namespace Ultima {
namespace Nuvie {
PathFinder::PathFinder() : start(0, 0, 0), goal(0, 0, 0), loc(0, 0, 0), search(0) {
}
PathFinder::PathFinder(const MapCoord &s, const MapCoord &g)
: start(s), goal(g), loc(0, 0, 0), search(0) {
}
PathFinder::~PathFinder() {
delete search;
}
bool PathFinder::check_dir(const MapCoord &from, MapCoord &rel, sint8) {
return check_loc(MapCoord(from.x + rel.sx, from.y + rel.sy, from.z));
}
bool PathFinder::check_loc(uint16 x, uint16 y, uint8 z) {
return check_loc(MapCoord(x, y, z));
}
void PathFinder::new_search(Path *new_path) {
delete search;
search = new_path;
search->set_pathfinder(this);
}
bool PathFinder::find_path() {
if (search) {
if (search->have_path())
search->delete_path();
return (search->path_search(loc, goal));
}
return false; // no path-search object
}
bool PathFinder::have_path() {
return (search && search->have_path());
}
void PathFinder::set_goal(const MapCoord &g) {
goal = g;
if (have_path())
search->delete_path();
}
void PathFinder::set_start(const MapCoord &s) {
start = s;
if (have_path())
search->delete_path();
}
bool PathFinder::is_path_clear() {
uint32 num_steps = search->get_num_steps();
for (unsigned int n = 0; n < num_steps; n++) {
const MapCoord &pos = search->get_step(n);
if (!check_loc(pos))
return false;
}
return true;
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,80 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_PATH_FINDER_H
#define NUVIE_PATHFINDER_PATH_FINDER_H
#include "ultima/nuvie/core/map.h"
namespace Ultima {
namespace Nuvie {
class Path;
class PathFinder {
protected:
MapCoord start, goal, loc; /* source, destination, current location */
Path *search; /* contains path-search algorithms, and
game-specific step costs */
void new_search(Path *new_path);
// bool is_hazardous(MapCoord &loc); will have to check objects and tiles
public:
PathFinder();
PathFinder(const MapCoord &s, const MapCoord &g);
virtual ~PathFinder();
void set_search(Path *new_path) {
new_search(new_path);
}
virtual void set_start(const MapCoord &s);
virtual void set_goal(const MapCoord &g);
virtual void set_location(const MapCoord &l) {
loc = l;
}
virtual MapCoord get_location() {
return loc;
}
virtual MapCoord get_goal() {
return goal;
}
virtual bool reached_goal() {
return (loc.x == goal.x && loc.y == goal.y
&& loc.z == goal.z);
}
virtual bool check_dir(const MapCoord &from, MapCoord &rel, sint8 unused = 0);
virtual bool check_loc(const MapCoord &loc) = 0;
bool check_loc(uint16 x, uint16 y, uint8 z);
virtual bool find_path(); /* get path to goal if one doesn't already exist */
virtual bool have_path(); /* a working path exists */
virtual bool is_path_clear(); /* recheck each location in path */
virtual bool get_next_move(MapCoord &step) = 0;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,102 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/actors/actor.h"
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/pathfinder/path.h"
#include "ultima/nuvie/pathfinder/sched_path_finder.h"
namespace Ultima {
namespace Nuvie {
/* NOTE: Path_type must always be valid. */
SchedPathFinder::SchedPathFinder(Actor *a, MapCoord g, Path *path_type)
: ActorPathFinder(a, g), prev_step_i(0), next_step_i(0) {
new_search(path_type);
assert(search && actor);
}
SchedPathFinder::~SchedPathFinder() {
}
bool SchedPathFinder::get_next_move(MapCoord &step) {
// jump to goal if both locations are off-screen
if (!goal.is_visible() && !loc.is_visible()) {
if (check_loc(goal)) {
search->delete_path();
step = goal;
return true;
}
}
if (!search->have_path())
if (!find_path())
return false;
step = search->get_step(next_step_i); // have a path, take a step
return true;
}
bool SchedPathFinder::find_path() {
if (search->have_path())
search->delete_path();
if (!search->path_search(loc, goal)) {
DEBUG(0, LEVEL_WARNING, "actor %d failed to find a path to %x,%x\n", actor->get_actor_num(), goal.x, goal.y);
return false;
}
prev_step_i = next_step_i = 0;
incr_step(); // the first step is the start location, so skip it
return true;
}
/* Returns true if actor location is correct. */
bool SchedPathFinder::is_location_in_path() {
const MapCoord &prev_step = search->get_step(prev_step_i);
return (loc == prev_step);
}
/* Update previous and next steps in path. */
void SchedPathFinder::incr_step() {
const MapCoord &prev_loc = search->get_step(prev_step_i);
const MapCoord &next_loc = search->get_step(next_step_i);
const MapCoord &last_loc = search->get_last_step();
if (prev_loc != last_loc) {
if (prev_loc != next_loc) // prev_step is going to stay behind next_step
++prev_step_i;
if (next_loc != last_loc)
++next_step_i;
}
}
void SchedPathFinder::actor_moved() {
update_location();
if (search->have_path())
incr_step();
}
/* Don't bother moving around other actors. They will probably move soon. */
bool SchedPathFinder::check_loc(const MapCoord &locPos) {
return actor->check_move(locPos.x, locPos.y, locPos.z, ACTOR_IGNORE_OTHERS);
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,55 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_SCHED_PATH_FINDER_H
#define NUVIE_PATHFINDER_SCHED_PATH_FINDER_H
#include "ultima/nuvie/pathfinder/actor_path_finder.h"
namespace Ultima {
namespace Nuvie {
/* Long-range pathfinder for NPCs.
*/
class SchedPathFinder: public ActorPathFinder {
protected:
uint32 prev_step_i, next_step_i; /* step counters */
public:
/* Pass 'path_type' to define search rules and methods to be used. The
PathFinder is responsible for deleting it when finished. */
SchedPathFinder(Actor *a, MapCoord g, Path *path_type);
~SchedPathFinder() override;
bool get_next_move(MapCoord &step) override; /* returns the next step in the path */
bool find_path() override; /* gets a NEW path from location->goal */
void actor_moved() override; /* update location and step counters */
bool check_loc(const MapCoord &loc) override; // ignores other actors
protected:
bool is_location_in_path();
void incr_step();
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,195 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/map.h"
#include "ultima/nuvie/pathfinder/dir_finder.h"
#include "ultima/nuvie/pathfinder/seek_path.h"
namespace Ultima {
namespace Nuvie {
using Std::vector;
SeekPath::SeekPath() {
}
SeekPath::~SeekPath() {
}
/* Get two relative directions that a line can travel to trace around an
obstacle towards `xdir',`ydir'. */
bool SeekPath::get_obstacle_tracer(const MapCoord &start, sint32 xdir, sint32 ydir,
sint32 &Axdir, sint32 &Aydir,
sint32 &Bxdir, sint32 &Bydir) {
if (xdir && ydir) { // original direction is diagonal
MapCoord checkA(start.x + xdir, start.y, start.z);
MapCoord checkB(start.x, start.y + ydir, start.z);
if (check_loc(checkA)) { // can go in X
Axdir = xdir;
Aydir = 0; // Horizontal; in X direction
} else { // X is blocked, must go in Y
Axdir = 0;
Aydir = -ydir; // Vertical; opposite Y direction
}
if (check_loc(checkB)) { // can go in Y
Bxdir = 0;
Bydir = ydir; // Vertical; in Y direction
} else { // Y is blocked, must go in X
Bxdir = -xdir;
Bydir = 0; // Horizontal; opposite X direction
}
} else { // orthagonal
// scan in perpendicular straight line
Axdir = ydir;
Aydir = xdir;
Bxdir = -Axdir;
Bydir = -Aydir;
}
return false;
}
/* Returns true if an opening is found along the original line. */
bool SeekPath::trace_check_obstacle(bool &turned, MapCoord &line, sint32 &deltax, sint32 &deltay, sint32 &xdir, sint32 &ydir, Std::vector<MapCoord> *scan) {
MapCoord obstacle(line.x + xdir, line.y + ydir, line.z);
if (check_loc(obstacle)) { // no obstacle here; able to move closer
if (scan->empty() || scan->back() != line)
scan->push_back(line); // *ADD TRACE NODE*
if (!turned) {
scan->push_back(obstacle); // *ADD TRACE NODE*
return true;
}
// bend line TOWARDS obstacle
line.x += xdir;
line.y += ydir; // step forward
sint32 old_deltax = deltax, old_deltay = deltay;
deltax = xdir;
deltay = ydir; // now moving in that direction
xdir = -old_deltax;
ydir = -old_deltay; // and looking away from old delta
turned = false;
}
return false;
}
void SeekPath::trace_around_corner(MapCoord &line, sint32 &deltax, sint32 &deltay, sint32 &xdir, sint32 &ydir, Std::vector<MapCoord> *scan) {
line.x -= deltax;
line.y -= deltay; // step back
if (scan->empty() || scan->back() != line)
scan->push_back(line); // *ADD TRACE NODE*
sint8 old_xdir = xdir, old_ydir = ydir;
xdir = deltax;
ydir = deltay; // now looking in that direction
deltax = -old_xdir;
deltay = -old_ydir; // and moving scan away from old obstacle
}
/* Trace an obstacle from 'start' towards the direction 'deltax' and 'deltay',
looking for openings towards 'xdir' and 'ydir'. The scan can bend 90 degrees
to get around walls. Returns true if something that looks like an opening
has been found. Trace nodes are placed at turns and where the scan ends. */
bool SeekPath::trace_obstacle(MapCoord line, sint32 deltax, sint32 deltay, sint32 xdir, sint32 ydir, Std::vector<MapCoord> *scan) {
const uint32 scan_max = 8; // number of squares to check before giving up
bool bend = false; // true if the scanning line is rotated 90 degrees
uint32 s = 0;
do {
line.x += deltax;
line.y += deltay;
if (!check_loc(line)) {
if (!bend) { // bend line AWAY from obstacle
trace_around_corner(line, deltax, deltay, xdir, ydir, scan);
bend = true;
} else // blocked (we only allow one turn)
break;
} else if (trace_check_obstacle(bend, line, deltax, deltay, xdir, ydir, scan))
return true;
} while (++s < scan_max);
scan->resize(0);
return false;
}
// choose which set of nodes traced around an obstacle should be used for a path
Std::vector<MapCoord> *SeekPath::get_best_scan(const MapCoord &start, const MapCoord &goal) {
if (A_scan.empty() && B_scan.empty())
return 0;
if (A_scan.empty())
return &B_scan;
if (B_scan.empty())
return &A_scan;
if (B_scan.back().distance(goal) < A_scan.back().distance(goal))
return &B_scan;
return &A_scan;
}
// copy A or B nodes to the path
void SeekPath::create_path(const MapCoord &start, const MapCoord &goal) {
vector<MapCoord> *nodes = get_best_scan(start, goal); // points to line A or B
MapCoord prev_node(start);
// these nodes are only at certain locations in the path, so all steps in
// between have to be added
while (nodes && !nodes->empty()) {
// create steps from prev_node to this_node
MapCoord this_node = nodes->front();
nodes->erase(nodes->begin());
if (this_node == start) // start is the first prev_node, which results in duplicate steps
continue;
sint16 dx = clamp(this_node.x - prev_node.x, -1, 1), dy = clamp(this_node.y - prev_node.y, -1, 1);
do {
prev_node = prev_node.abs_coords(dx, dy); // add dx & dy
add_step(prev_node);
} while (prev_node != this_node);
prev_node = this_node;
}
}
/* Returns true if a path is found around the obstacle between locations. */
bool SeekPath::path_search(const MapCoord &start, const MapCoord &goal) {
sint8 xdir = 0, ydir = 0; // direction start->goal
DirFinder::get_normalized_dir(start, goal, xdir, ydir); // init xdir & ydir
// confirm that goal is more than one square away
if ((start.x + xdir) == goal.x && (start.y + ydir) == goal.y)
return false;
// determine if each line (A and B) will be vertical or horizontal
sint32 Ax = 0, Ay = 0, Bx = 0, By = 0; // vector of line segments to scan
get_obstacle_tracer(start, xdir, ydir, Ax, Ay, Bx, By);
// direction from line to scan is perpendicular to line, towards obstacle
delete_nodes();
bool successA = trace_obstacle(start, Ax, Ay, (Ay) ? xdir : 0, (Ax) ? ydir : 0, &A_scan);
bool successB = trace_obstacle(start, Bx, By, (By) ? xdir : 0, (Bx) ? ydir : 0, &B_scan);
if (successA || successB)
create_path(start, goal); // create path from available nodes
delete_nodes();
return (successA || successB);
}
void SeekPath::delete_nodes() {
A_scan.clear();
B_scan.clear();
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,69 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_SEEK_PATH_H
#define NUVIE_PATHFINDER_SEEK_PATH_H
#include "ultima/shared/std/containers.h"
#include "ultima/nuvie/pathfinder/path.h"
namespace Ultima {
namespace Nuvie {
/* Provides routines for building short paths around obstacles and seeking a
* target. Much of the work doesn't involve finding a path at all, but instead
* finding the direction closest to the target.
*/
class SeekPath: public Path {
protected:
Std::vector<MapCoord> A_scan, B_scan; // nodes of a line scanned by trace_obstacle()
void create_path(const MapCoord &start, const MapCoord &goal);
Std::vector<MapCoord> *get_best_scan(const MapCoord &start, const MapCoord &goal);
void delete_nodes();
bool trace_check_obstacle(bool &turned, MapCoord &line, sint32 &deltax, sint32 &deltay, sint32 &xdir, sint32 &ydir, Std::vector<MapCoord> *scan);
void trace_around_corner(MapCoord &line, sint32 &deltax, sint32 &deltay, sint32 &xdir, sint32 &ydir, Std::vector<MapCoord> *scan);
public:
SeekPath();
~SeekPath() override;
sint32 step_cost(const MapCoord &c1, const MapCoord &c2) override {
return -1;
}
bool path_search(const MapCoord &start, const MapCoord &goal) override;
void delete_path() {
Path::delete_path();
delete_nodes();
}
/* Trace obstacle towards xdir,ydir for a possible opening. */
bool trace_obstacle(MapCoord line, sint32 deltax, sint32 deltay, sint32 xdir, sint32 ydir, Std::vector<MapCoord> *scan);
/* Get two relative directions that a line can travel to trace around an
obstacle towards `xdir',`ydir'. */
bool get_obstacle_tracer(const MapCoord &start, sint32 xdir, sint32 ydir,
sint32 &Axdir, sint32 &Aydir,
sint32 &Bxdir, sint32 &Bydir);
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,66 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/nuvie/core/nuvie_defs.h"
#include "ultima/nuvie/core/game.h"
#include "ultima/nuvie/core/obj_manager.h"
#include "ultima/nuvie/usecode/usecode.h"
#include "ultima/nuvie/pathfinder/u6_astar_path.h"
namespace Ultima {
namespace Nuvie {
/* Return the cost of moving one step from `c1' to `c2'.
* Blocking objects are checked for, and doors may be passable
* Returns -1 if c2 is blocked.
*/
sint32 U6AStarPath::step_cost(const MapCoord &c1, const MapCoord &c2) {
Game *game = Game::get_game();
sint32 c = 1; // final cost is not necessarily the actual move cost
// FIXME: need an actor->check_move(loc2, loc1) to check one step only
if (c2.distance(c1) > 1)
return -1;
if (!pf->check_loc(c2.x, c2.y, c2.z)) {
// check for door
// Door objects consist of a wall and the actual door tile.
// We use get_objBasedAt() here since we are only interested in the latter.
Obj *block = game->get_obj_manager()->get_objBasedAt(c2.x, c2.y, c2.z, true, false);
if (block && game->get_usecode()->is_unlocked_door(block))
c += 2; // cost for opening door
else
return -1;
}
// add cost of *original* step
// c += game->get_game_map()->get_impedance(c1.x, c1.y, c1.z);
if (c1.x != c2.x && c1.y != c2.y) // prefer non-diagonal
c *= 2;
return c;
}
// Possible step cost is 1 to 16.
uint32 U6AStarPath::path_cost_est(const MapCoord &s, const MapCoord &g) {
return Path::path_cost_est(s, g);
}
} // End of namespace Nuvie
} // End of namespace Ultima

View File

@@ -0,0 +1,40 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef NUVIE_PATHFINDER_U6_ASTAR_PATH_H
#define NUVIE_PATHFINDER_U6_ASTAR_PATH_H
#include "ultima/nuvie/pathfinder/astar_path.h"
namespace Ultima {
namespace Nuvie {
/* This provides a U6-specific step_cost() method. */
class U6AStarPath: public AStarPath {
public:
sint32 step_cost(const MapCoord &c1, const MapCoord &c2) override;
uint32 path_cost_est(const MapCoord &s, const MapCoord &g) override;
};
} // End of namespace Nuvie
} // End of namespace Ultima
#endif