console/cmd/tp: add round-robin item teleports

This commit is contained in:
Marcin Kurczewski 2024-11-06 10:49:20 +01:00
parent 09c55b8b6e
commit 2fc6e5d0bc
5 changed files with 106 additions and 38 deletions

View file

@ -39,6 +39,8 @@
- `\{ammo magnums}`
- `\{ammo uzis}`
- changed the `/pos` command to include the level number and title
- changed the `/tp` command to teleport to items in a round-robin fashion
The first call will teleport Lara to the object that's the closest to her; repeated calls will cycle through all matching objects in the object placement order.
- changed the music timestamp loading behaviour and config option to support ambient tracks (#1769)
- fixed missing pushblock SFX in Natla's Mines (#1714)
- fixed crash reports not working in certain circumstances (#1738)

View file

@ -34,6 +34,8 @@
- changed text backend to accept named sequences (eg. "\{arrow up}" and similar)
- changed inventory to pause the music rather than muting it (#1707)
- changed the `/pos` command to include the level number and title
- changed the `/tp` command to teleport to items in a round-robin fashion
The first call will teleport Lara to the object that's the closest to her; repeated calls will cycle through all matching objects in the object placement order.
- improved FMV mode appearance - removed black scanlines (#1729)
- improved FMV mode behavior - stopped switching screen resolutions (#1729)
- improved screenshots: now saved in the screenshots/ directory with level titles and timestamps as JPG or PNG, similar to TR1X (#1773)

View file

@ -11,12 +11,18 @@
#include "game/objects/vars.h"
#include "game/random.h"
#include "game/rooms.h"
#include "memory.h"
#include "strings.h"
#include <math.h>
#include <stdio.h>
static int16_t m_LastTeleportedItemNum = NO_ITEM;
static bool M_CanTargetObject(GAME_OBJECT_ID object_id);
static bool M_CanTargetItem(
const ITEM *item, const GAME_OBJECT_ID *matching_objs, int32_t match_count);
static const ITEM *M_GetItemToTeleporTo(const char *user_input);
static bool M_IsFloatRound(float num);
static COMMAND_RESULT M_TeleportToXYZ(float x, float y, float z);
@ -48,6 +54,95 @@ static bool M_CanTargetObject(const GAME_OBJECT_ID object_id)
&& M_ObjectCanBePickedUp(object_id);
}
static bool M_CanTargetItem(
const ITEM *const item, const GAME_OBJECT_ID *const matching_objs,
const int32_t match_count)
{
// Collected pickups
if (Object_IsObjectType(item->object_id, g_PickupObjects)
&& (item->status == IS_INVISIBLE || item->status == IS_DEACTIVATED
|| item->room_num == NO_ROOM)) {
return false;
}
// Killed enemies and removed items
if (item->flags & IF_KILLED) {
return false;
}
// Non-matches to user input
bool is_matched = false;
for (int32_t j = 0; j < match_count; j++) {
if (matching_objs[j] == item->object_id) {
is_matched = true;
break;
}
}
if (!is_matched) {
return false;
}
return true;
}
static const ITEM *M_GetItemToTeleporTo(const char *const user_input)
{
int32_t match_count = 0;
GAME_OBJECT_ID *matching_objs =
Object_IdsFromName(user_input, &match_count, M_CanTargetObject);
const ITEM *const lara_item = Lara_GetItem();
int16_t best_item_num = NO_ITEM;
// Choose the matching item closest to Lara.
const int32_t near_distance = WALL_L;
int16_t closest_item_num = NO_ITEM;
int32_t closest_distance = INT32_MAX;
for (int32_t item_num = 0; item_num < Item_GetTotalCount(); item_num++) {
const ITEM *const item = Item_Get(item_num);
if (!M_CanTargetItem(item, matching_objs, match_count)) {
continue;
}
const int32_t distance = Item_GetDistance(item, &lara_item->pos);
if (distance < closest_distance) {
closest_distance = distance;
closest_item_num = item_num;
}
}
if (closest_distance > near_distance) {
best_item_num = closest_item_num;
} else {
// If Lara's already very close to a matching item, choose the next
// matching item in a round-robin fashion.
const int16_t start_idx = (closest_item_num + 1) % Item_GetTotalCount();
for (int32_t i = 0; i < Item_GetTotalCount(); i++) {
int16_t item_num = (start_idx + i) % Item_GetTotalCount();
if (item_num == closest_item_num) {
continue;
}
const ITEM *const item = Item_Get(item_num);
if (!M_CanTargetItem(item, matching_objs, match_count)) {
continue;
}
const int32_t distance = Item_GetDistance(item, &lara_item->pos);
if (distance < near_distance) {
continue;
}
best_item_num = item_num;
break;
}
}
Memory_FreePointer(&matching_objs);
return Item_Get(best_item_num);
}
static inline bool M_IsFloatRound(const float num)
{
return (fabsf(num) - roundf(num)) < 0.0001f;
@ -113,44 +208,7 @@ static COMMAND_RESULT M_TeleportToObject(const char *const user_input)
return CR_BAD_INVOCATION;
}
int32_t match_count = 0;
GAME_OBJECT_ID *matching_objs =
Object_IdsFromName(user_input, &match_count, M_CanTargetObject);
const ITEM *const lara_item = Lara_GetItem();
const ITEM *best_item = NULL;
int32_t best_distance = INT32_MAX;
for (int16_t item_num = 0; item_num < Item_GetTotalCount(); item_num++) {
const ITEM *const item = Item_Get(item_num);
if (Object_IsObjectType(item->object_id, g_PickupObjects)
&& (item->status == IS_INVISIBLE || item->status == IS_DEACTIVATED
|| item->room_num == NO_ROOM)) {
continue;
}
if (item->flags & IF_KILLED) {
continue;
}
bool is_matched = false;
for (int32_t i = 0; i < match_count; i++) {
if (matching_objs[i] == item->object_id) {
is_matched = true;
break;
}
}
if (!is_matched) {
continue;
}
const int32_t distance = Item_GetDistance(item, &lara_item->pos);
if (distance < best_distance) {
best_distance = distance;
best_item = item;
}
}
const ITEM *const best_item = M_GetItemToTeleporTo(user_input);
if (best_item == NULL) {
Console_Log(GS(OSD_POS_SET_ITEM_FAIL), user_input);
return CR_FAILURE;

View file

@ -837,5 +837,8 @@ bool Item_TestFrameRange(ITEM *item, int16_t start, int16_t end)
ITEM *Item_Get(const int16_t item_num)
{
if (item_num == NO_ITEM) {
return NULL;
}
return &g_Items[item_num];
}

View file

@ -731,5 +731,8 @@ int32_t Item_GetDistance(const ITEM *const item, const XYZ_32 *const target)
ITEM *Item_Get(const int16_t item_num)
{
if (item_num == NO_ITEM) {
return NULL;
}
return &g_Items[item_num];
}