/* 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 . * */ #include "m4/adv_r/adv_walk.h" #include "m4/adv_r/adv_trigger.h" #include "m4/core/errors.h" #include "m4/core/imath.h" #include "m4/graphics/gr_series.h" #include "m4/wscript/wst_regs.h" #include "m4/vars.h" namespace M4 { void set_walker_scaling(SceneDef *rdef) { _G(globals)[GLB_MIN_Y] = rdef->back_y << 16; _G(globals)[GLB_MAX_Y] = rdef->front_y << 16; _G(globals)[GLB_MIN_SCALE] = FixedDiv(rdef->back_scale << 16, 100 << 16); _G(globals)[GLB_MAX_SCALE] = FixedDiv(rdef->front_scale << 16, 100 << 16); if (_G(globals)[GLB_MIN_Y] == _G(globals)[GLB_MAX_Y]) _G(globals)[GLB_SCALER] = 0; else _G(globals)[GLB_SCALER] = FixedDiv(_G(globals)[GLB_MAX_SCALE] - _G(globals)[GLB_MIN_SCALE], _G(globals)[GLB_MAX_Y] - _G(globals)[GLB_MIN_Y]); } static void ws_walkto_node(machine *myWalker, railNode *destNode, bool firstTime) { // Parameter verification if (!myWalker) { error_show(FL, 'W:-('); return; } if (!destNode) { error_show(FL, 'WNDN'); return; } // Calculate the destination values x, y, s const frac16 x = destNode->x << 16; const frac16 y = destNode->y << 16; const frac16 s = _G(globals)[GLB_MIN_SCALE] + FixedMul(y - _G(globals)[GLB_MIN_Y], _G(globals)[GLB_SCALER]); // Plug in the destination x, y, and s _G(globals)[GLB_TEMP_1] = x; _G(globals)[GLB_TEMP_2] = y; _G(globals)[GLB_TEMP_3] = s; // Final direction (GLB_TEMP_4) and trigger (GLB_TEMP_5) are set in ws_walk() below if (firstTime) { if (_G(completeWalk)) { _G(globals)[GLB_TEMP_6] = 0x10000; // so the feet will come together } else { _G(globals)[GLB_TEMP_6] = 0; // so the walker will freeze when he reaches the last node } sendWSMessage(STARTWALK << 16, 0, myWalker, 0, nullptr, 1); } else { sendWSMessage(WALKSEQ << 16, 0, myWalker, 0, nullptr, 1); } } bool walker_has_walk_finished(machine *sender) { // Parameter verification if ((!sender) || (!sender->myAnim8)) { error_show(FL, 'W:-('); return false; } // Remove the node we just arrived at from the sender's walkPath if (sender->walkPath) { railNode *tempNode = sender->walkPath; sender->walkPath = sender->walkPath->shortPath; mem_free((void *)tempNode); } // If no more nodes to traverse (a canadian word), check if he's standing. // If not standing, let him finish standing, then when he finishes standing if (!sender->walkPath) { return true; } else { // Else there are more nodes, so keep walking ws_walkto_node(sender, sender->walkPath, false); return false; } } /** * Called by player_walk */ void ws_walk(machine *myWalker, int32 x, int32 y, GrBuff **, int16 trigger, int32 finalFacing, bool complete_walk) { int8 directions[14] = { 0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 9 }; int32 currNodeID, destNodeID; if (!myWalker || !myWalker->myAnim8) error_show(FL, 'W:-('); // Get walker's current location const int32 currX = myWalker->myAnim8->myRegs[IDX_X] >> 16; const int32 currY = myWalker->myAnim8->myRegs[IDX_Y] >> 16; // Add the walker's current location and the destination to the rail nodes... Buffer *walkerCodes = nullptr; if (_G(screenCodeBuff)) walkerCodes = _G(screenCodeBuff)->get_buffer(); if ((currNodeID = AddRailNode(currX, currY, walkerCodes, true)) < 0) { error_show(FL, 'WCAN', "Walker's curr posn: %d %d", currX, currY); } if ((destNodeID = AddRailNode(x, y, walkerCodes, true)) < 0) { error_show(FL, 'WCAN', "Trying to walk to: %d %d", x, y); } // Dispose of the current path myWalker is following if (myWalker->walkPath) { DisposePath(myWalker->walkPath); } // Find the shortest path between currNodeID, and destNodeID const bool result = GetShortestPath(currNodeID, destNodeID, &(myWalker->walkPath)); // Now that a path has been found, remove the two extra added nodes RemoveRailNode(currNodeID, walkerCodes, true); RemoveRailNode(destNodeID, walkerCodes, true); if (_G(screenCodeBuff)) _G(screenCodeBuff)->release(); // Check the success of GetShortestPath if (!result) { term_message("Player: Can't walk there!!!"); _G(player).waiting_for_walk = false; return; } // If the result was true, but no path was returned, we are already there if (!myWalker->walkPath) { _G(player).need_to_walk = false; //we can only turn to face the final direction ws_turn_to_face(myWalker, finalFacing, trigger); } else { // Otherwise we have a path to follow, so get going... // Verify that finalFacing is valid or set -1 if (finalFacing > 0 && finalFacing < 13) { _G(globals)[GLB_TEMP_4] = directions[finalFacing] << 16; } else { _G(globals)[GLB_TEMP_4] = (frac16)-1 & ~0xffff; } // Set the trigger to be returned when the walk is finished _G(globals)[GLB_TEMP_5] = kernel_trigger_create(trigger); _G(completeWalk) = complete_walk; ws_walkto_node(myWalker, myWalker->walkPath, true); } if (_G(hyperwalk)) adv_hyperwalk_to_final_destination(nullptr, nullptr); } bool adv_walker_path_exists(machine *myWalker, int32 x, int32 y) { int32 currNodeID, destNodeID; if (!myWalker || !myWalker->myAnim8) { error_show(FL, 'W:-('); return false; } // Get walker's current location const int32 currX = myWalker->myAnim8->myRegs[IDX_X] >> 16; const int32 currY = myWalker->myAnim8->myRegs[IDX_Y] >> 16; // Add the walker's current location and the destination to the rail nodes... Buffer *walkerCodes = nullptr; if (_G(screenCodeBuff)) { walkerCodes = _G(screenCodeBuff)->get_buffer(); } if ((currNodeID = AddRailNode(currX, currY, walkerCodes, true)) < 0) { error_show(FL, 'WCAN', "Walker's curr posn: %d %d", currX, currY); } if ((destNodeID = AddRailNode(x, y, walkerCodes, true)) < 0) { error_show(FL, 'WCAN', "Trying to walk to: %d %d", x, y); } // Dispose of the current path myWalker is following if (myWalker->walkPath) { DisposePath(myWalker->walkPath); } // Find the shortest path between currNodeID, and destNodeID const bool result = GetShortestPath(currNodeID, destNodeID, &(myWalker->walkPath)); // Now that a path has been attempted, remove the two extra added nodes RemoveRailNode(currNodeID, walkerCodes, true); RemoveRailNode(destNodeID, walkerCodes, true); if (_G(screenCodeBuff)) _G(screenCodeBuff)->release(); return result; } void ws_custom_walk(machine *myWalker, int32 finalFacing, int32 trigger, bool complete_walk) { const int8 directions[14] = { 0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 9 }; // Verify parameters if ((!myWalker) || (!myWalker->walkPath)) { return; } // Verify that finalFacing is valid or set -1 if (finalFacing > 0 && finalFacing < 13) { _G(globals)[GLB_TEMP_4] = directions[finalFacing] << 16; } else { _G(globals)[GLB_TEMP_4] = (frac16)-1 & ~0xffff; } // Set the trigger to be returned when the walk is finished _G(globals)[GLB_TEMP_5] = kernel_trigger_create(trigger); // Begin the walk... _G(completeWalk) = complete_walk; ws_walkto_node(myWalker, myWalker->walkPath, true); } void ws_demand_facing(machine *myWalker, int32 facing) { const int8 directions[13] = { 0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 9 }; if ((!myWalker) || (!myWalker->myAnim8)) { term_message("demand facing, but no walker"); return; } if (facing > 0 && facing < 13) { _G(globals)[GLB_TEMP_4] = directions[facing] << 16; sendWSMessage(DEMAND_FACING << 16, 0, myWalker, 0, nullptr, 1); } } void ws_demand_location(machine *myWalker, int32 x, int32 y, int facing) { if (!myWalker || !myWalker->myAnim8) { term_message("demand locn, no walker"); return; } frac16 s = _G(globals)[GLB_MIN_SCALE] + FixedMul((y << 16) - _G(globals)[GLB_MIN_Y], _G(globals)[GLB_SCALER]); _G(globals)[GLB_TEMP_1] = x << 16; _G(globals)[GLB_TEMP_2] = y << 16; _G(globals)[GLB_TEMP_3] = s; sendWSMessage(DEMAND_LOCATION << 16, 0, myWalker, 0, nullptr, 1); if (facing != -1) ws_demand_facing(myWalker, facing); } static void ws_demand_location_and_facing(machine *myWalker, int32 x, int32 y, int32 facing) { if ((!myWalker) || (!myWalker->myAnim8)) { term_message("demand f & l, no walker"); return; } frac16 s = _G(globals)[GLB_MIN_SCALE] + FixedMul((y << 16) - _G(globals)[GLB_MIN_Y], _G(globals)[GLB_SCALER]); _G(globals)[GLB_TEMP_1] = x << 16; _G(globals)[GLB_TEMP_2] = y << 16; _G(globals)[GLB_TEMP_3] = s; if (facing > 0 && facing < 13) // WORKAROUND: The original's hyperwalk didn't work. By doing // the facing set separately, this is fixed ws_demand_facing(facing); sendWSMessage(DEMAND_LOCATION << 16, 0, myWalker, 0, nullptr, 1); _G(player).waiting_for_walk = false; // lets parse code get called when there is no facing set (from scenedit) } void ws_turn_to_face(machine *myWalker, int32 facing, int32 trigger) { int8 directions[13] = { 0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 9 }; if (!myWalker || !myWalker->myAnim8) { error_show(FL, 'W:-(', "demand facing: %d", facing); return; } // Verify that facing is valid or set -1 if (facing > 0 && facing < 13) { _G(globals)[GLB_TEMP_4] = directions[facing] << 16; } else { _G(globals)[GLB_TEMP_4] = (frac16)-1 & ~0xffff; } // Set the trigger to be returned when the walk is finished _G(globals)[GLB_TEMP_5] = kernel_trigger_create(trigger); // Make sure the _G(completeWalk) flag is set _G(globals)[GLB_TEMP_6] = 0x10000; sendWSMessage(TURN_TO_FACE << 16, 0, myWalker, 0, nullptr, 1); } void ws_demand_location(int32 x, int32 y, int facing) { ws_demand_location(_G(my_walker), x, y, facing); } void ws_hide_walker(machine *myWalker) { if (!myWalker) { error_show(FL, 'W:-('); return; } _G(player).walker_visible = false; sendWSMessage(PLAYER_HIDE << 16, 0, myWalker, 0, nullptr, 1); } void ws_unhide_walker(machine *myWalker) { if (!myWalker) { error_show(FL, 'W:-('); return; } _G(player).walker_visible = true; sendWSMessage(PLAYER_UNHIDE << 16, 0, myWalker, 0, nullptr, 1); } void ws_demand_facing(int32 newFacing) { ws_demand_facing(_G(my_walker), newFacing); } void ws_turn_to_face(int32 facing, int32 trigger) { ws_turn_to_face(_G(my_walker), facing, trigger); } void ws_hide_walker() { ws_hide_walker(_G(my_walker)); } void ws_unhide_walker() { ws_unhide_walker(_G(my_walker)); } void ws_walk(int32 x, int32 y, GrBuff **buffer, int16 trigger, int32 finalFacing, bool complete_walk) { ws_walk(_G(my_walker), x, y, buffer, trigger, finalFacing, complete_walk); } void ws_get_walker_info(machine *myWalker, int32 *x, int32 *y, int32 *s, int32 *layer, int32 *facing) { const int8 facings[10] = { 1, 2, 3, 4, 5, 7, 8, 9, 10, 11 }; if (!myWalker || !myWalker->myAnim8) { error_show(FL, 'W:-('); return; } Anim8 *myAnim8 = myWalker->myAnim8; if (x) { *x = myAnim8->myRegs[IDX_X] >> 16; } if (y) { *y = myAnim8->myRegs[IDX_Y] >> 16; } if (s) { *s = MulSF16(100 << 16, myAnim8->myRegs[IDX_S]) >> 16; } if (layer) { *layer = myAnim8->myRegs[IDX_LAYER] >> 16; } if (facing) { int index = myAnim8->myRegs[IDX_CELS_HASH] >> 24; // WORKAROUND: At the very least, Mei Chen in Riddle room 201 // produces a negative index value. This ensures indexes are valid if (index < 0 || index > 9) index = 0; if (myAnim8->myRegs[IDX_W] < 0) index = 9 - index; *facing = facings[index]; } } bool ws_walk_init_system() { // Initialize walker _G(globals)[GLB_MIN_Y] = _G(currentSceneDef).back_y << 16; _G(globals)[GLB_MAX_Y] = _G(currentSceneDef).front_y << 16; _G(globals)[GLB_MIN_SCALE] = FixedDiv(_G(currentSceneDef).back_scale << 16, 100 << 16); _G(globals)[GLB_MAX_SCALE] = FixedDiv(_G(currentSceneDef).front_scale << 16, 100 << 16); if (_G(globals)[GLB_MIN_Y] == _G(globals)[GLB_MAX_Y]) { _G(globals)[GLB_SCALER] = 0; } else { _G(globals)[GLB_SCALER] = FixedDiv(_G(globals)[GLB_MAX_SCALE] - _G(globals)[GLB_MIN_SCALE], _G(globals)[GLB_MAX_Y] - _G(globals)[GLB_MIN_Y]); } _G(my_walker) = _GW().walk_initialize_walker(); if (!_G(my_walker)) { error_show(FL, 'W:-('); return false; } return true; } bool ws_walk_load_series(const int16 *dir_array, const char *name_array[], bool shadow_flag, bool load_palette) { int32 i = 0; while (dir_array[i] >= 0) { const int32 result = AddWSAssetCELS(name_array[i], dir_array[i], (load_palette && !shadow_flag) ? _G(master_palette) : nullptr); if (result < 0) error_show(FL, 'W:-('); i++; } return true; } bool ws_walk_load_walker_series(const int16 *dir_array, const char *name_array[], bool load_palette) { return (ws_walk_load_series(dir_array, name_array, false, load_palette)); } bool ws_walk_load_shadow_series(const int16 *dir_array, const char *name_array[]) { return (ws_walk_load_series(dir_array, name_array, true, false)); } void ws_walk_dump_series(int16 num_directions, int16 start_hash) { for (int32 i = 0; i < num_directions; i++) { series_unload(start_hash++); } } void adv_get_walker_destination(machine *my_walker, int32 *x, int32 *y, int32 *final_facing) { int8 directions[11] = { 1, 2, 3, 4, 5, 7, 8, 9, 10, 11 }; // If there is no walker, or the walker is not on a walk path, return if (!my_walker || !my_walker->walkPath) { *x = 0; *y = 0; *final_facing = 0; return; } // Find the end of the path railNode *current_node = my_walker->walkPath; while (current_node->shortPath) { current_node = current_node->shortPath; } // Set the destination coords *x = current_node->x; *y = current_node->y; // Get final facing from l.v.6 = myRegs[6 + IDX_COUNT] int32 face = my_walker->myAnim8->myRegs[6 + IDX_COUNT] >> 16; // FIXME: Riddle room 608 Twelvetrees cutscene has face -1. Not sure if happens in original if (face == -1) face = 0; *final_facing = directions[face]; } void adv_hyperwalk_to_final_destination(void *, void *) { int32 x, y; int32 facing; _G(i_just_hyperwalked) = true; // Make sure we have a walker, that it can walk in this scene, and that we can hyperwalk if ((!_G(my_walker)) || (!_G(player).walker_in_this_scene) || _G(player).disable_hyperwalk) { return; } // If the walker is not currently walking anywhere, return if (!_G(my_walker)->walkPath) { return; } //get the final direction and facing adv_get_walker_destination(_G(my_walker), &x, &y, &facing); // Nuke the rail node path DisposePath(_G(my_walker)->walkPath); _G(my_walker)->walkPath = nullptr; // This will make player goto x,y,facing. when that happens, trigger will return ws_demand_location_and_facing(_G(my_walker), x, y, facing); } } // End of namespace M4