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,509 @@
/* 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 "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