console: fix targeting pickups

Resolves #1667.
This commit is contained in:
Marcin Kurczewski 2024-10-05 12:27:31 +02:00
parent 1e1ff3092b
commit 309efee3c6
No known key found for this signature in database
GPG key ID: CC65E6FD28CAE42A
11 changed files with 65 additions and 21 deletions

View file

@ -284,10 +284,10 @@
"O_KEY_OPTION_2": "Key Option 2",
"O_KEY_OPTION_3": "Key Option 3",
"O_KEY_OPTION_4": "Key Option 4",
"O_KEY_HOLE_1": "Key Hole 1",
"O_KEY_HOLE_2": "Key Hole 2",
"O_KEY_HOLE_3": "Key Hole 3",
"O_KEY_HOLE_4": "Key Hole 4",
"O_KEY_HOLE_1": "Keyhole 1",
"O_KEY_HOLE_2": "Keyhole 2",
"O_KEY_HOLE_3": "Keyhole 3",
"O_KEY_HOLE_4": "Keyhole 4",
"O_PICKUP_ITEM_1": "Pickup Item 1",
"O_PICKUP_ITEM_2": "Pickup Item 2",
"O_PICKUP_OPTION_1": "Pickup Option 1",

View file

@ -20,6 +20,7 @@
- fixed Story So Far feature looping cutscenes forever (#1551, regression from 4.4)
- improved object name matching in console commands to work like TR2X
- improved vertex movement when looking through water portals even more (#1493)
- improved console commands targeting creatures and pickups (#1667)
## [4.4](https://github.com/LostArtefacts/TRX/compare/tr1-4.3...tr1-4.4) - 2024-09-20
- added `/exit` command (#1462)

View file

@ -9,6 +9,7 @@
- fixed `/endlevel` displaying a success message in the title screen
- fixed very loud music volume set by default (#1614)
- improved vertex movement when looking through water portals (#1493)
- improved console commands targeting creatures and pickups (#1667)
## [0.3](https://github.com/LostArtefacts/TR2X/compare/0.2...0.3) - 2024-09-20
- added new console commands:

View file

@ -110,10 +110,10 @@ static bool M_SetCurrentValue(
assert(option->target != NULL);
switch (option->type) {
case COT_BOOL:
if (String_Match(new_value, "on|true|1")) {
if (String_Match(new_value, "^(on|true|1)$")) {
*(bool *)option->target = true;
return true;
} else if (String_Match(new_value, "off|false|0")) {
} else if (String_Match(new_value, "^(off|false|0)$")) {
*(bool *)option->target = false;
return true;
}

View file

@ -22,8 +22,9 @@ static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static bool M_CanTargetObjectCreature(const GAME_OBJECT_ID object_id)
{
return Object_IsObjectType(object_id, g_EnemyObjects)
|| Object_IsObjectType(object_id, g_AllyObjects);
return (Object_IsObjectType(object_id, g_EnemyObjects)
|| Object_IsObjectType(object_id, g_AllyObjects))
&& Object_GetObject(object_id)->loaded;
}
static COMMAND_RESULT M_KillAllEnemies(void)

View file

@ -25,11 +25,27 @@ static COMMAND_RESULT M_TeleportToObject(const char *user_input);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static bool M_ObjectCanBePickedUp(const GAME_OBJECT_ID object_id)
{
if (!Object_IsObjectType(object_id, g_PickupObjects)) {
return true;
}
for (int32_t item_num = 0; item_num < Item_GetTotalCount(); item_num++) {
const ITEM *const item = Item_Get(item_num);
if (item->object_id == object_id && item->status != IS_INVISIBLE) {
return true;
}
}
return false;
}
static bool M_CanTargetObject(const GAME_OBJECT_ID object_id)
{
return !Object_IsObjectType(object_id, g_NullObjects)
&& !Object_IsObjectType(object_id, g_AnimObjects)
&& !Object_IsObjectType(object_id, g_InvObjects);
&& !Object_IsObjectType(object_id, g_InvObjects)
&& Object_GetObject(object_id)->loaded
&& M_ObjectCanBePickedUp(object_id);
}
static inline bool M_IsFloatRound(const float num)

View file

@ -135,10 +135,10 @@ OBJ_NAME_DEFINE(O_KEY_OPTION_1, "Key Option 1")
OBJ_NAME_DEFINE(O_KEY_OPTION_2, "Key Option 2")
OBJ_NAME_DEFINE(O_KEY_OPTION_3, "Key Option 3")
OBJ_NAME_DEFINE(O_KEY_OPTION_4, "Key Option 4")
OBJ_NAME_DEFINE(O_KEY_HOLE_1, "Key Hole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_2, "Key Hole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_3, "Key Hole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_4, "Key Hole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_1, "Keyhole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_2, "Keyhole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_3, "Keyhole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_4, "Keyhole 1")
OBJ_NAME_DEFINE(O_PICKUP_ITEM_1, "Pickup Item 1")
OBJ_NAME_DEFINE(O_PICKUP_ITEM_2, "Pickup Item 2")
OBJ_NAME_DEFINE(O_SCION_ITEM_1, "Scion 1")

View file

@ -199,10 +199,10 @@ OBJ_NAME_DEFINE(O_KEY_OPTION_1, "Key Option 1")
OBJ_NAME_DEFINE(O_KEY_OPTION_2, "Key Option 2")
OBJ_NAME_DEFINE(O_KEY_OPTION_3, "Key Option 3")
OBJ_NAME_DEFINE(O_KEY_OPTION_4, "Key Option 4")
OBJ_NAME_DEFINE(O_KEY_HOLE_1, "Key Hole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_2, "Key Hole 2")
OBJ_NAME_DEFINE(O_KEY_HOLE_3, "Key Hole 3")
OBJ_NAME_DEFINE(O_KEY_HOLE_4, "Key Hole 4")
OBJ_NAME_DEFINE(O_KEY_HOLE_1, "Keyhole 1")
OBJ_NAME_DEFINE(O_KEY_HOLE_2, "Keyhole 2")
OBJ_NAME_DEFINE(O_KEY_HOLE_3, "Keyhole 3")
OBJ_NAME_DEFINE(O_KEY_HOLE_4, "Keyhole 4")
OBJ_NAME_DEFINE(O_PICKUP_ITEM_1, "Pickup Item 1")
OBJ_NAME_DEFINE(O_PICKUP_ITEM_2, "Pickup Item 2")
OBJ_NAME_DEFINE(O_PICKUP_OPTION_1, "Pickup Option 1")

View file

@ -13,6 +13,7 @@ typedef struct {
typedef struct {
bool is_full;
bool is_word;
int32_t score;
} STRING_FUZZY_SCORE;

View file

@ -95,9 +95,9 @@ bool String_Match(const char *const subject, const char *const pattern)
pcre2_match_data *const match_data =
pcre2_match_data_create(ovec_size, NULL);
const int flags = 0;
const int rc = pcre2_match(
re, usubject, PCRE2_ZERO_TERMINATED, 0,
PCRE2_ANCHORED | PCRE2_ENDANCHORED, match_data, NULL);
re, usubject, PCRE2_ZERO_TERMINATED, 0, flags, match_data, NULL);
pcre2_match_data_free(match_data);
pcre2_code_free(re);
@ -111,14 +111,14 @@ bool String_IsEmpty(const char *const value)
bool String_ParseBool(const char *const value, bool *const target)
{
if (String_Match(value, "0|false|off")) {
if (String_Match(value, "^(0|false|off)$")) {
if (target != NULL) {
*target = false;
}
return true;
}
if (String_Match(value, "1|true|on")) {
if (String_Match(value, "^(1|true|on)$")) {
if (target != NULL) {
*target = true;
}

View file

@ -14,6 +14,7 @@
static STRING_FUZZY_SCORE M_GetScore(
const char *user_input, const char *reference, int32_t weight);
static void M_DiscardNonFullMatches(VECTOR *matches);
static void M_DiscardNonWordMatches(VECTOR *matches);
static void M_SortMatches(VECTOR *matches);
static void M_DiscardDuplicateMatches(VECTOR *matches);
@ -32,6 +33,7 @@ static STRING_FUZZY_SCORE M_GetScore(
// Assume a partial match
bool is_full = false;
bool is_word = false;
int32_t score = letter_score + percent_score;
if (String_Match(reference, full_regex)) {
// Got a full match
@ -39,6 +41,7 @@ static STRING_FUZZY_SCORE M_GetScore(
score += FULL_MATCH_SCORE_BONUS;
} else if (String_Match(reference, word_regex)) {
// Got a word match
is_word = true;
score += WORD_MATCH_SCORE_BONUS;
} else if (String_CaseSubstring(reference, user_input) == NULL) {
// No match.
@ -50,6 +53,7 @@ static STRING_FUZZY_SCORE M_GetScore(
return (STRING_FUZZY_SCORE) {
.is_full = is_full,
.is_word = is_word,
.score = score * weight,
};
}
@ -73,6 +77,25 @@ static void M_DiscardNonFullMatches(VECTOR *const matches)
}
}
static void M_DiscardNonWordMatches(VECTOR *const matches)
{
bool has_word_match = false;
for (int32_t i = 0; i < matches->count; i++) {
const STRING_FUZZY_MATCH *const match = Vector_Get(matches, i);
if (match->score.is_word) {
has_word_match = true;
}
}
if (has_word_match) {
for (int32_t i = matches->count - 1; i >= 0; i--) {
const STRING_FUZZY_MATCH *const match = Vector_Get(matches, i);
if (!match->score.is_word) {
Vector_RemoveAt(matches, i);
}
}
}
}
static void M_SortMatches(VECTOR *const matches)
{
// sort by match length so that best-matching results appear first
@ -129,6 +152,7 @@ VECTOR *String_FuzzyMatch(const char *user_input, const VECTOR *const source)
}
M_DiscardNonFullMatches(matches);
M_DiscardNonWordMatches(matches);
M_DiscardDuplicateMatches(matches);
M_SortMatches(matches);