Skip to content

Commit

Permalink
Merge pull request #2658 from ivan-mogilko/362--fixstuckinnonwalkable
Browse files Browse the repository at this point in the history
Refactor Character and Object Move functions, optimize WalkStraight
  • Loading branch information
ivan-mogilko authored Jan 19, 2025
2 parents 35b029b + 2626124 commit f4e48dc
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 108 deletions.
160 changes: 108 additions & 52 deletions Engine/ac/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,10 @@ void Character_StopMovingEx(CharacterInfo *chi, bool force_walkable_area)
// end on a non-walkable area and gets stuck.
if (force_walkable_area && (chi->walking < TURNING_AROUND) && (chi->room == displayed_room))
{
// TODO: don't use PlaceOnWalkable, as that may result in moving character
// into the random direction. Instead, consider writing a "backtracing"
// function that runs along MoveList's current stage back, looking for
// the first pixel on a walkable area.
Character_PlaceOnWalkableArea(chi);
}

Expand Down Expand Up @@ -1060,31 +1064,63 @@ void Character_UnlockViewEx(CharacterInfo *chaa, int stopMoving) {
}


void Character_Walk(CharacterInfo *chaa, int x, int y, int blocking, int direct)
// Tests if the given character is permitted to start a move in the room
bool ValidateCharForMove(CharacterInfo *chaa, const char *api_name)
{
walk_or_move_character(chaa, x, y, blocking, direct, true);
if (chaa->room != displayed_room)
{
debug_script_warn("%s: specified character %s not in current room (is in %d, current room %d)",
api_name, chaa->scrname, chaa->room, displayed_room);
return false;
}
if (chaa->on != 1)
{
debug_script_warn("%s: character %s is turned off and cannot be moved", api_name, chaa->scrname);
return false;
}
return true;
}

void Character_Move(CharacterInfo *chaa, int x, int y, int blocking, int direct)
// Character_DoMove converts and validates script parameters, and calls corresponding internal character move function
void Character_DoMove(CharacterInfo *chaa, const char *api_name,
int x, int y, bool walk_straight, int blocking, int ignwal, bool walk_anim)
{
walk_or_move_character(chaa, x, y, blocking, direct, false);
}
if (!ValidateCharForMove(chaa, api_name))
return;

void Character_WalkStraight(CharacterInfo *chaa, int xx, int yy, int blocking) {
ValidateMoveParams(api_name, blocking, ignwal);

if (chaa->room != displayed_room)
quitprintf("!MoveCharacterStraight: character %s is not in current room %d (it is in room %d)",
chaa->scrname, displayed_room, chaa->room);
if (walk_straight)
{
move_character_straight(chaa, x, y, walk_anim);
}
else
{
move_character(chaa, x, y, ignwal != 0, walk_anim);
}

walk_or_move_character_straight(chaa, xx, yy, blocking, 1 /* use ANYWHERE */, true /* walk */);
if (blocking)
GameLoopUntilNotMoving(&chaa->walking);
}

void Character_MoveStraight(CharacterInfo *chaa, int xx, int yy, int blocking) {
void Character_Walk(CharacterInfo *chaa, int x, int y, int blocking, int ignwal)
{
Character_DoMove(chaa, "Character.Walk", x, y, false /* not straight */, blocking, ignwal, true /* walk anim */);
}

if (chaa->room != displayed_room)
quit("!MoveCharacterStraight: specified character not in current room");
void Character_Move(CharacterInfo *chaa, int x, int y, int blocking, int ignwal)
{
Character_DoMove(chaa, "Character.Move", x, y, false /* not straight */, blocking, ignwal, false /* no anim */);
}

walk_or_move_character_straight(chaa, xx, yy, blocking, 1 /* use ANYWHERE */, false /* move */);
void Character_WalkStraight(CharacterInfo *chaa, int x, int y, int blocking)
{
Character_DoMove(chaa, "Character.WalkStraight", x, y, true /* straight */, blocking, WALKABLE_AREAS, true /* walk anim */);
}

void Character_MoveStraight(CharacterInfo *chaa, int x, int y, int blocking)
{
Character_DoMove(chaa, "Character.MoveStraight", x, y, true /* straight */, blocking, WALKABLE_AREAS, false /* no anim */);
}

void Character_RunInteraction(CharacterInfo *chaa, int mood) {
Expand Down Expand Up @@ -1724,21 +1760,34 @@ int Character_GetSpeakingFrame(CharacterInfo *chaa) {
//=============================================================================

// order of loops to turn character in circle from down to down
int turnlooporder[8] = {0, 6, 1, 7, 3, 5, 2, 4};
const int turnlooporder[8] = {0, 6, 1, 7, 3, 5, 2, 4};

void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims) {
CharacterInfo*chin=&game.chars[chac];
if (chin->room!=displayed_room)
quitprintf("!MoveCharacter: character %s is not in current room %d (it is in room %d)",
chin->scrname, displayed_room, chin->room);
// Core character move implementation:
// uses a provided path or searches for a path to a given destination;
// starts a move or walk (with automatic animation).
void move_character_impl(CharacterInfo *chin, const std::vector<Point> *path, int tox, int toy, bool ignwal, bool walk_anim)
{
const int chac = chin->index_id;
if (!ValidateCharForMove(chin, "MoveCharacter"))
return;

if ((tox == chin->x) && (toy == chin->y)) {
if (path && path->empty() || !path && (tox == chin->x) && (toy == chin->y))
{
StopMoving(chac);
debug_script_log("%s already at destination, not moving", chin->scrname);
debug_script_log("MoveCharacter: %s move path is empty, or is already at destination, not moving", chin->scrname);
return;
}

if ((chin->animating) && (autoWalkAnims))
if (path)
{
// Jump character to the path's start
chin->x = path->front().X;
chin->y = path->front().Y;
tox = path->back().X;
toy = path->back().Y;
}

if ((chin->animating) && (walk_anim))
stop_character_anim(chin);
// Stop idling anim
stop_character_idling(chin);
Expand Down Expand Up @@ -1779,10 +1828,20 @@ void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims) {
const int dst_y = data_to_game_coord(toy);

const int mslot = chac + CHMLSOFFS;
MaskRouteFinder *pathfind = get_room_pathfinder();
pathfind->SetWalkableArea(prepare_walkable_areas(chac), thisroom.MaskResolution);
if (Pathfinding::FindRoute(mls[mslot], pathfind, src_x, src_y, dst_x, dst_y,
move_speed_x, move_speed_y, false, ignwal != 0))
bool path_result = false;
if (path)
{
path_result = Pathfinding::CalculateMoveList(mls[mslot], *path, move_speed_x, move_speed_y);
}
else
{
MaskRouteFinder *pathfind = get_room_pathfinder();
pathfind->SetWalkableArea(prepare_walkable_areas(chac), thisroom.MaskResolution);
path_result = Pathfinding::FindRoute(mls[mslot], pathfind, src_x, src_y, dst_x, dst_y, move_speed_x, move_speed_y, false, ignwal);
}

// If successful, then start moving
if (path_result)
{
chin->walking = mslot;
mls[mslot].direct = ignwal;
Expand All @@ -1797,7 +1856,7 @@ void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims) {
// or if they were already moving, keep the current wait -
// this prevents a glitch if MoveCharacter is called when they
// are already moving
if (autoWalkAnims)
if (walk_anim)
{
chin->walkwait = waitWas;
charextra[chac].animwait = animWaitWas;
Expand All @@ -1812,7 +1871,7 @@ void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims) {
chin->flags |= CHF_MOVENOTWALK;
}
}
else if (autoWalkAnims)
else if (walk_anim)
{
// pathfinder couldn't get a route, stand them still
chin->frame = 0;
Expand Down Expand Up @@ -2091,46 +2150,43 @@ void FindReasonableLoopForCharacter(CharacterInfo *chap) {

}

void walk_or_move_character(CharacterInfo *chaa, int x, int y, int blocking, int direct, bool isWalk)
void move_character(CharacterInfo *chaa, int tox, int toy, bool ignwal, bool walk_anim)
{
if (chaa->on != 1)
{
debug_script_warn("MoveCharacterBlocking: character is turned off and cannot be moved");
return;
}

if ((direct == ANYWHERE) || (direct == 1))
walk_character(chaa->index_id, x, y, 1, isWalk);
else if ((direct == WALKABLE_AREAS) || (direct == 0))
walk_character(chaa->index_id, x, y, 0, isWalk);
else
quit("!Character.Walk: Direct must be ANYWHERE or WALKABLE_AREAS");

if ((blocking == BLOCKING) || (blocking == 1))
GameLoopUntilNotMoving(&chaa->walking);
else if ((blocking != IN_BACKGROUND) && (blocking != 0))
quit("!Character.Walk: Blocking must be BLOCKING or IN_BACKGROUND");
move_character_impl(chaa, nullptr, tox, toy, ignwal, walk_anim);
}

void walk_or_move_character_straight(CharacterInfo *chaa, int x, int y, int blocking, int direct, bool isWalk)
void move_character_straight(CharacterInfo *chaa, int x, int y, bool walk_anim)
{
// NOTE: for old games we assume the input coordinates are in the "data" coordinate system
const int src_x = data_to_game_coord(chaa->x);
const int src_y = data_to_game_coord(chaa->y);
const int dst_x = data_to_game_coord(x);
const int dst_y = data_to_game_coord(y);

int movetox = x, movetoy = y;
int lastcx, lastcy;
MaskRouteFinder *pathfind = get_room_pathfinder();
pathfind->SetWalkableArea(prepare_walkable_areas(chaa->index_id), thisroom.MaskResolution);
if (!pathfind->CanSeeFrom(src_x, src_y, dst_x, dst_y, &lastcx, &lastcy))

int movetox = x, movetoy = y;
int lastcx = chaa->x, lastcy = chaa->y;
if (!pathfind->CanSeeFrom(chaa->x, chaa->y, x, y, &lastcx, &lastcy))
{
// move_character_impl assumes all coordinates in "data" system
movetox = game_to_data_coord(lastcx);
movetoy = game_to_data_coord(lastcy);
}

walk_or_move_character(chaa, movetox, movetoy, blocking, direct, isWalk);
std::vector<Point> path = { {chaa->x, chaa->y}, {movetox, movetoy} };
move_character_impl(chaa, &path, movetox, movetoy, false /* walkable areas */, walk_anim);
}

void walk_character(CharacterInfo *chaa, int tox, int toy, bool ignwal)
{
move_character(chaa, tox, toy, ignwal, true /* animate */);
}

void walk_character_straight(CharacterInfo *chaa, int x, int y)
{
move_character_straight(chaa, x, y, true /* animate */);
}

int wantMoveNow (CharacterInfo *chi, CharacterExtras *chex) {
Expand Down
24 changes: 15 additions & 9 deletions Engine/ac/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ void Character_Tint(CharacterInfo *chaa, int red, int green, int blue, int op
void Character_Think(CharacterInfo *chaa, const char *text);
void Character_UnlockView(CharacterInfo *chaa);
void Character_UnlockViewEx(CharacterInfo *chaa, int stopMoving);
void Character_Walk(CharacterInfo *chaa, int x, int y, int blocking, int direct);
void Character_Move(CharacterInfo *chaa, int x, int y, int blocking, int direct);
void Character_Walk(CharacterInfo *chaa, int x, int y, int blocking, int ignwal);
void Character_Move(CharacterInfo *chaa, int x, int y, int blocking, int ignwal);
void Character_WalkStraight(CharacterInfo *chaa, int xx, int yy, int blocking);

void Character_RunInteraction(CharacterInfo *chaa, int mood);
Expand Down Expand Up @@ -174,7 +174,6 @@ void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept,
int direction = 0, int sframe = 0, int volume = 100);
// Clears up animation parameters
void stop_character_anim(CharacterInfo *chap);
void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims);
int find_looporder_index (int curloop);
// returns 0 to use diagonal, 1 to not
int useDiagonal (CharacterInfo *char1);
Expand All @@ -189,12 +188,15 @@ int doNextCharMoveStep(CharacterInfo *chi, CharacterExtras *chex);
bool is_char_walking_ndirect(CharacterInfo *chi);
int find_nearest_walkable_area_within(int *xx, int *yy, int range, int step);
void find_nearest_walkable_area (int *xx, int *yy);
void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims);
void FindReasonableLoopForCharacter(CharacterInfo *chap);
// Start character walk or move
void walk_or_move_character(CharacterInfo *chaa, int x, int y, int blocking, int direct, bool isWalk);
// Start character walk or move along the straight line without pathfinding, until any non-passable area is met
void walk_or_move_character_straight(CharacterInfo *chaa, int x, int y, int blocking, int direct, bool isWalk);
// Start character walk or move; calculate path using destination and optionally "ignore walls" flag
void move_character(CharacterInfo *chaa, int tox, int toy, bool ignwal, bool walk_anim);
// Start character walk or move along the straight line until any non-passable area is met
void move_character_straight(CharacterInfo *chaa, int x, int y, bool walk_anim);
// Start character walk; calculate path using destination and optionally "ignore walls" flag
void walk_character(CharacterInfo *chaa, int tox, int toy, bool ignwal);
// Start character walk the straight line until any non-passable area is met
void walk_character_straight(CharacterInfo *chaa, int tox, int toy);
int wantMoveNow (CharacterInfo *chi, CharacterExtras *chex);
void setup_player_character(int charid);
Common::Bitmap *GetCharacterImage(int charid, bool *is_original = nullptr);
Expand Down Expand Up @@ -229,6 +231,10 @@ Rect GetCharacterRoomBBox(int charid, bool use_frame_0 = false);
// or the one that is least far away from its camera; calculated as a perpendicular distance between two AABBs.
PViewport FindNearestViewport(int charid);

// Character_DoMove converts and validates script parameters, and calls corresponding internal character move function
void Character_DoMove(CharacterInfo *chaa, const char *api_name,
int x, int y, bool walk_straight, int blocking, int ignwal, bool walk_anim);

//
// Character update functions
// TODO: move these into a runtime Character class, when there will be a proper one,
Expand All @@ -246,6 +252,6 @@ extern CharacterInfo*playerchar;
extern int32_t _sc_PlayerCharPtr;

// order of loops to turn character in circle from down to down
extern int turnlooporder[8];
extern const int turnlooporder[8];

#endif // __AGS_EE_AC__CHARACTER_H
7 changes: 4 additions & 3 deletions Engine/ac/characterinfo_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ void UpdateCharacterFollower(CharacterInfo *chi, std::vector<int> &followingAsSh
chi->room = -play.follow_change_room_timer;
}
if (chi->room >= 0) {
walk_character(chi->index_id, play.entered_at_x,play.entered_at_y,1, true);
walk_character(chi, play.entered_at_x, play.entered_at_y, true /* ignwal */);
doing_nothing = 0;
}
}
Expand All @@ -437,8 +437,9 @@ void UpdateCharacterFollower(CharacterInfo *chi, std::vector<int> &followingAsSh
// make sure he's not standing on top of the other man
if (goxoffs < 0) goxoffs-=distaway;
else goxoffs+=distaway;
walk_character(chi->index_id, game.chars[following].x + goxoffs,
game.chars[following].y + (Random(50)-25),0, true);
walk_character(chi, game.chars[following].x + goxoffs,
game.chars[following].y + (Random(50) - 25), false /* walk areas */);

doing_nothing = 0;
}
}
Expand Down
Loading

0 comments on commit f4e48dc

Please sign in to comment.