AGS: Engine: implement add_waypoint_direct(), fix AddWaypoint's resolution

This fixes Character.AddWaypoint not applying room<->mask resolution conversion,
which result in character's speed being different if room has non 1:1 mask resolution.

Hide calculate_move_stage() inside pathfinder, and expose add_waypoint_direct()
in the pathfinder interface instead.
From upstream 093354d78849dc3b4f13a244d4400c4e71f7d687
This commit is contained in:
Walter Agazzi 2024-02-28 13:47:33 +01:00
parent dacccc343e
commit 0b50067abb
10 changed files with 91 additions and 55 deletions

View File

@ -153,9 +153,8 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
return;
}
cmls->pos[cmls->numstage] = (x << 16) + y;
// They're already walking there anyway
if (cmls->pos[cmls->numstage] == cmls->pos[cmls->numstage - 1])
if (cmls->lastx == x && cmls->lasty == y)
return;
int move_speed_x, move_speed_y;
@ -164,8 +163,17 @@ void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
debug_script_warn("Character::AddWaypoint: called for '%s' with walk speed 0", chaa->scrname);
}
calculate_move_stage(cmls, cmls->numstage - 1, move_speed_x, move_speed_y);
cmls->numstage++;
// There's an issue: the existing movelist is converted to room resolution,
// so we do this trick: convert last step to mask resolution, before calling
// a pathfinder api, and then we'll convert old and new last step back.
// TODO: figure out a better way of processing this!
const int last_stage = cmls->numstage - 1;
cmls->pos[last_stage] = ((room_to_mask_coord(cmls->pos[last_stage] >> 16)) << 16) | ((room_to_mask_coord(cmls->pos[last_stage] & 0xFFFF)) & 0xFFFF);
const int dst_x = room_to_mask_coord(x);
const int dst_y = room_to_mask_coord(y);
if (add_waypoint_direct(cmls, dst_x, dst_y, move_speed_x, move_speed_y)) {
convert_move_path_to_room_resolution(cmls, last_stage, last_stage + 1);
}
}
void Character_AnimateEx(CharacterInfo *chaa, int loop, int delay, int repeat,
@ -1592,13 +1600,7 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
chin->flags &= ~CHF_MOVENOTWALK;
int toxPassedIn = tox, toyPassedIn = toy;
int charX = room_to_mask_coord(chin->x);
int charY = room_to_mask_coord(chin->y);
tox = room_to_mask_coord(tox);
toy = room_to_mask_coord(toy);
if ((tox == charX) && (toy == charY)) {
if ((tox == chin->x) && (toy == chin->y)) {
StopMoving(chac);
debug_script_log("%s already at destination, not moving", chin->scrname);
return;
@ -1626,7 +1628,7 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
chin->frame = oldframe;
// use toxPassedIn cached variable so the hi-res co-ordinates
// are still displayed as such
debug_script_log("%s: Start move to %d,%d", chin->scrname, toxPassedIn, toyPassedIn);
debug_script_log("%s: Start move to %d,%d", chin->scrname, tox, toy);
int move_speed_x, move_speed_y;
chin->get_effective_walkspeeds(move_speed_x, move_speed_y);
@ -1634,8 +1636,13 @@ void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims)
debug_script_warn("MoveCharacter: called for '%s' with walk speed 0", chin->scrname);
}
int mslot = find_route(charX, charY, tox, toy, move_speed_x, move_speed_y,
prepare_walkable_areas(chac), chac + CHMLSOFFS, 1, ignwal);
// Convert src and dest coords to the mask resolution, for pathfinder
const int src_x = room_to_mask_coord(chin->x);
const int src_y = room_to_mask_coord(chin->y);
const int dst_x = room_to_mask_coord(tox);
const int dst_y = room_to_mask_coord(toy);
int mslot = find_route(src_x, src_y, dst_x, dst_y, move_speed_x, move_speed_y, prepare_walkable_areas(chac), chac + CHMLSOFFS, 1, ignwal);
if (mslot > 0) {
chin->walking = mslot;

View File

@ -413,12 +413,13 @@ void move_object(int objj, int tox, int toy, int spee, int ignwal) {
debug_script_log("Object %d start move to %d,%d", objj, tox, toy);
int objX = room_to_mask_coord(_G(objs)[objj].x);
int objY = room_to_mask_coord(_G(objs)[objj].y);
tox = room_to_mask_coord(tox);
toy = room_to_mask_coord(toy);
// Convert src and dest coords to the mask resolution, for pathfinder
const int src_x = room_to_mask_coord(_G(objs)[objj].x);
const int src_y = room_to_mask_coord(_G(objs)[objj].y);
const int dst_x = room_to_mask_coord(tox);
const int dst_y = room_to_mask_coord(toy);
int mslot = find_route(objX, objY, tox, toy, spee, spee, prepare_walkable_areas(-1), objj + 1, 1, ignwal);
int mslot = find_route(src_x, src_y, dst_x, dst_y, spee, spee, prepare_walkable_areas(-1), objj + 1, 1, ignwal);
if (mslot > 0) {
_G(objs)[objj].moving = mslot;

View File

@ -1006,33 +1006,42 @@ AGS_INLINE int mask_to_room_coord(int coord) {
return coord * _GP(thisroom).MaskResolution / _GP(game).GetDataUpscaleMult();
}
void convert_move_path_to_room_resolution(MoveList *ml) {
void convert_move_path_to_room_resolution(MoveList *ml, int from_step, int to_step) {
if (to_step < 0)
to_step = ml->numstage;
to_step = CLIP(to_step, 0, ml->numstage - 1);
from_step = CLIP(from_step, 0, to_step);
// If speed is independent from MaskResolution...
if ((_GP(game).options[OPT_WALKSPEEDABSOLUTE] != 0) && _GP(game).GetDataUpscaleMult() > 1) {
// Speeds are independent from MaskResolution
for (int i = 0; i < ml->numstage; i++) {
// ...so they are not multiplied by MaskResolution factor when converted to room coords
for (int i = from_step; i <= to_step; i++) { // ...so they are not multiplied by MaskResolution factor when converted to room coords
ml->xpermove[i] = ml->xpermove[i] / _GP(game).GetDataUpscaleMult();
ml->ypermove[i] = ml->ypermove[i] / _GP(game).GetDataUpscaleMult();
}
}
// Skip the conversion if these are equal, as they are multiplier and divisor
if (_GP(thisroom).MaskResolution == _GP(game).GetDataUpscaleMult())
return;
ml->fromx = mask_to_room_coord(ml->fromx);
ml->fromy = mask_to_room_coord(ml->fromy);
ml->lastx = mask_to_room_coord(ml->lastx);
ml->lasty = mask_to_room_coord(ml->lasty);
if (from_step == 0) {
ml->fromx = mask_to_room_coord(ml->fromx);
ml->fromy = mask_to_room_coord(ml->fromy);
}
if (to_step == ml->numstage - 1) {
ml->lastx = mask_to_room_coord(ml->lastx);
ml->lasty = mask_to_room_coord(ml->lasty);
}
for (int i = 0; i < ml->numstage; i++) {
for (int i = from_step; i <= to_step; i++) {
uint16_t lowPart = mask_to_room_coord(ml->pos[i] & 0x0000ffff);
uint16_t highPart = mask_to_room_coord((ml->pos[i] >> 16) & 0x0000ffff);
ml->pos[i] = ((int)highPart << 16) | (lowPart & 0x0000ffff);
}
// If speed is scaling with MaskResolution...
if (_GP(game).options[OPT_WALKSPEEDABSOLUTE] == 0) {
// Speeds are scaling with MaskResolution
for (int i = 0; i < ml->numstage; i++) {
for (int i = from_step; i <= to_step; i++) {
ml->xpermove[i] = mask_to_room_coord(ml->xpermove[i]);
ml->ypermove[i] = mask_to_room_coord(ml->ypermove[i]);
}

View File

@ -70,8 +70,6 @@ void croom_ptr_clear();
// In legacy games with low-res data resolution there's additional conversion
// between data and room coordinates.
//
// gets multiplier for converting from room mask to data coordinate
extern AGS_INLINE int get_roommask_to_data_mul();
// coordinate conversion data ---> room ---> mask
extern AGS_INLINE int room_to_mask_coord(int coord);
// coordinate conversion mask ---> room ---> data
@ -79,7 +77,7 @@ extern AGS_INLINE int mask_to_room_coord(int coord);
struct MoveList;
// Convert move path from room's mask resolution to room resolution
void convert_move_path_to_room_resolution(MoveList *ml);
void convert_move_path_to_room_resolution(MoveList *ml, int from_step = 0, int to_step = -1);
} // namespace AGS3

View File

@ -51,8 +51,8 @@ public:
int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) override {
return AGS::Engine::RouteFinder::find_route(srcx, srcy, xx, yy, move_speed_x, move_speed_y, onscreen, movlst, nocross, ignore_walls);
}
void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y) override {
AGS::Engine::RouteFinder::calculate_move_stage(mlsp, aaa, move_speed_x, move_speed_y);
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) override {
return AGS::Engine::RouteFinder::add_waypoint_direct(mlsp, x, y, move_speed_x, move_speed_y);
}
};
@ -78,8 +78,8 @@ public:
int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) override {
return AGS::Engine::RouteFinderLegacy::find_route(srcx, srcy, xx, yy, move_speed_x, move_speed_y, onscreen, movlst, nocross, ignore_walls);
}
void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y) override {
AGS::Engine::RouteFinderLegacy::calculate_move_stage(mlsp, aaa, move_speed_x, move_speed_y);
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) override {
return AGS::Engine::RouteFinder::add_waypoint_direct(mlsp, x, y, move_speed_x, move_speed_y);
}
};
@ -116,8 +116,8 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
return _GP(route_finder_impl)->find_route(srcx, srcy, xx, yy, move_speed_x, move_speed_y, onscreen, movlst, nocross, ignore_walls);
}
void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y) {
_GP(route_finder_impl)->calculate_move_stage(mlsp, aaa, move_speed_x, move_speed_y);
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) {
return _GP(route_finder_impl)->add_waypoint_direct(mlsp, x, y, move_speed_x, move_speed_y);
}
} // namespace AGS3

View File

@ -46,7 +46,7 @@ public:
virtual int can_see_from(int x1, int y1, int x2, int y2) = 0;
virtual void get_lastcpos(int &lastcx, int &lastcy) = 0;
virtual int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, AGS::Shared::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) = 0;
virtual void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y) = 0;
virtual bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) = 0;
};
void init_pathfinder(GameDataVersion game_file_version);
@ -58,7 +58,8 @@ int can_see_from(int x1, int y1, int x2, int y2);
void get_lastcpos(int &lastcx, int &lastcy);
int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, AGS::Shared::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0);
void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y);
// Append a waypoint to the move list, skip pathfinding
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y);
} // namespace AGS3

View File

@ -117,7 +117,7 @@ inline fixed input_speed_to_fixed(int speed_val) {
}
// Calculates the X and Y per game loop, for this stage of the movelist
void calculate_move_stage_intern(MoveList *mlsp, int aaa, fixed move_speed_x, fixed move_speed_y) {
void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed move_speed_y) {
// work out the x & y per move. First, opp/adj=tan, so work out the angle
if (mlsp->pos[aaa] == mlsp->pos[aaa + 1]) {
mlsp->xpermove[aaa] = 0;
@ -189,10 +189,6 @@ void calculate_move_stage_intern(MoveList *mlsp, int aaa, fixed move_speed_x, fi
mlsp->ypermove[aaa] = newymove;
}
void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y) {
calculate_move_stage_intern(mlsp, aaa, input_speed_to_fixed(move_speed_x), input_speed_to_fixed(move_speed_y));
}
int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) {
_G(wallscreen) = onscreen;
@ -233,7 +229,7 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
for (int i = 0; i < _G(num_navpoints) - 1; i++) {
calculate_move_stage_intern(&_GP(mls)[mlist], i, fix_speed_x, fix_speed_y);
calculate_move_stage(&_GP(mls)[mlist], i, fix_speed_x, fix_speed_y);
}
_GP(mls)[mlist].fromx = srcx;
@ -246,6 +242,20 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
return mlist;
}
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) {
if (mlsp->numstage >= MAXNEEDSTAGES)
return false;
const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
mlsp->pos[mlsp->numstage] = MAKE_INTCOORD(x, y);
calculate_move_stage(mlsp, mlsp->numstage - 1, fix_speed_x, fix_speed_y);
mlsp->numstage++;
mlsp->lastx = x;
mlsp->lasty = y;
return true;
}
} // namespace RouteFinder
} // namespace Engine
} // namespace AGS

View File

@ -48,7 +48,7 @@ int can_see_from(int x1, int y1, int x2, int y2);
void get_lastcpos(int &lastcx, int &lastcy);
int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, AGS::Shared::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0);
void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y);
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y);
} // namespace RouteFinder
} // namespace Engine

View File

@ -637,7 +637,7 @@ inline fixed input_speed_to_fixed(int speed_val) {
}
// Calculates the X and Y per game loop, for this stage of the movelist
void calculate_move_stage_intern(MoveList *mlsp, int aaa, fixed move_speed_x, fixed move_speed_y) {
void calculate_move_stage(MoveList *mlsp, int aaa, fixed move_speed_x, fixed move_speed_y) {
assert(mlsp != nullptr);
// work out the x & y per move. First, opp/adj=tan, so work out the angle
@ -717,10 +717,6 @@ void calculate_move_stage_intern(MoveList *mlsp, int aaa, fixed move_speed_x, fi
#endif
}
void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y) {
calculate_move_stage_intern(mlsp, aaa, input_speed_to_fixed(move_speed_x), input_speed_to_fixed(move_speed_y));
}
#define MAKE_INTCOORD(x,y) (((unsigned short)x << 16) | ((unsigned short)y))
int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) {
@ -844,7 +840,7 @@ stage_again:
const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
for (aaa = 0; aaa < numstages - 1; aaa++) {
calculate_move_stage_intern(&_GP(mls)[mlist], aaa, fix_speed_x, fix_speed_y);
calculate_move_stage(&_GP(mls)[mlist], aaa, fix_speed_x, fix_speed_y);
}
_GP(mls)[mlist].fromx = orisrcx;
@ -868,6 +864,20 @@ stage_again:
#endif
}
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y) {
if (mlsp->numstage >= MAXNEEDSTAGES)
return false;
const fixed fix_speed_x = input_speed_to_fixed(move_speed_x);
const fixed fix_speed_y = input_speed_to_fixed(move_speed_y);
mlsp->pos[mlsp->numstage] = MAKE_INTCOORD(x, y);
calculate_move_stage(mlsp, mlsp->numstage - 1, fix_speed_x, fix_speed_y);
mlsp->numstage++;
mlsp->lastx = x;
mlsp->lasty = y;
return true;
}
void shutdown_pathfinder() {
if (pathbackx != nullptr) {
free(pathbackx);

View File

@ -46,7 +46,7 @@ int can_see_from(int x1, int y1, int x2, int y2);
void get_lastcpos(int &lastcx, int &lastcy);
int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int move_speed_y, AGS::Shared::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0);
void calculate_move_stage(MoveList *mlsp, int aaa, int move_speed_x, int move_speed_y);
bool add_waypoint_direct(MoveList *mlsp, short x, short y, int move_speed_x, int move_speed_y);
} // namespace RouteFinderLegacy
} // namespace Engine