|
--- a/Projects/CheatXtended/src/plugin.cpp |
|
+++ b/Projects/CheatXtended/src/plugin.cpp |
|
@@ -39,6 +39,7 @@ |
|
#include <cstdint> |
|
#include <cctype> |
|
#include <vector> |
|
+#include <string> |
|
|
|
using wh::xgenaimodule::C_InformationManager; |
|
using wh::xgenaimodule::S_InformationRecord; |
|
@@ -52,6 +53,19 @@ |
|
using wh::rpgmodule::E_RPGSkill; |
|
|
|
namespace { |
|
+ |
|
+// Helper to strip quotes and force exact matching |
|
+std::string CleanQuery(const char* arg, bool& outForceExact) |
|
+{ |
|
+ outForceExact = false; |
|
+ if (!arg) return ""; |
|
+ std::string s(arg); |
|
+ if (s.length() >= 2 && ((s.front() == '"' && s.back() == '"') || (s.front() == ''' && s.back() == '''))) { |
|
+ s = s.substr(1, s.length() - 2); |
|
+ outForceExact = true; |
|
+ } |
|
+ return s; |
|
+} |
|
|
|
// --------------------------------------------------------------------------- |
|
// Arg parsing |
|
@@ -238,9 +252,21 @@ |
|
// Find by substring match. Returns match count; fills outId on single match. |
|
int FindStat(const char* query, int& outId) |
|
{ |
|
+ bool forceExact = false; |
|
+ std::string clean = CleanQuery(query, forceExact); |
|
+ const char* q = clean.c_str(); |
|
+ |
|
+ for (int i = 0; i < kStatCount; ++i) { |
|
+ if (_stricmp(kStats[i].shortName, q) == 0 || _stricmp(kStats[i].longName, q) == 0) { |
|
+ outId = kStats[i].id; |
|
+ return 1; |
|
+ } |
|
+ } |
|
+ if (forceExact) return 0; |
|
+ |
|
int count = 0; |
|
for (int i = 0; i < kStatCount; ++i) { |
|
- if (MatchesName(kStats[i].shortName, query) || MatchesName(kStats[i].longName, query)) { |
|
+ if (MatchesName(kStats[i].shortName, q) || MatchesName(kStats[i].longName, q)) { |
|
outId = kStats[i].id; |
|
++count; |
|
} |
|
@@ -250,9 +276,21 @@ |
|
|
|
int FindSkill(const char* query, int& outId) |
|
{ |
|
+ bool forceExact = false; |
|
+ std::string clean = CleanQuery(query, forceExact); |
|
+ const char* q = clean.c_str(); |
|
+ |
|
+ for (int i = 0; i < kSkillCount; ++i) { |
|
+ if (_stricmp(kSkills[i].shortName, q) == 0 || _stricmp(kSkills[i].longName, q) == 0) { |
|
+ outId = kSkills[i].id; |
|
+ return 1; |
|
+ } |
|
+ } |
|
+ if (forceExact) return 0; |
|
+ |
|
int count = 0; |
|
for (int i = 0; i < kSkillCount; ++i) { |
|
- if (MatchesName(kSkills[i].shortName, query) || MatchesName(kSkills[i].longName, query)) { |
|
+ if (MatchesName(kSkills[i].shortName, q) || MatchesName(kSkills[i].longName, q)) { |
|
outId = kSkills[i].id; |
|
++count; |
|
} |
|
@@ -262,16 +300,20 @@ |
|
|
|
void ListStatMatches(Offsets::ILog* log, const char* query) |
|
{ |
|
+ bool forceExact = false; |
|
+ std::string clean = CleanQuery(query, forceExact); |
|
for (int i = 0; i < kStatCount; ++i) { |
|
- if (MatchesName(kStats[i].shortName, query) || MatchesName(kStats[i].longName, query)) |
|
+ if (MatchesName(kStats[i].shortName, clean.c_str()) || MatchesName(kStats[i].longName, clean.c_str())) |
|
log->LogAlways("[CheatX] '%s' (%s)", kStats[i].shortName, kStats[i].longName); |
|
} |
|
} |
|
|
|
void ListSkillMatches(Offsets::ILog* log, const char* query) |
|
{ |
|
+ bool forceExact = false; |
|
+ std::string clean = CleanQuery(query, forceExact); |
|
for (int i = 0; i < kSkillCount; ++i) { |
|
- if (MatchesName(kSkills[i].shortName, query) || MatchesName(kSkills[i].longName, query)) |
|
+ if (MatchesName(kSkills[i].shortName, clean.c_str()) || MatchesName(kSkills[i].longName, clean.c_str())) |
|
log->LogAlways("[CheatX] '%s' (%s)", kSkills[i].shortName, kSkills[i].longName); |
|
} |
|
} |
|
@@ -408,28 +450,42 @@ |
|
{ |
|
auto* log = SSystemGlobalEnvironment::GetInstance()->pLog; |
|
if (!log) return; |
|
- const char* name = ArgString(args, 1); |
|
- if (!name) { log->LogAlways("[CheatX] usage: cheatx_set_reputation <faction_name> <value>"); return; } |
|
+ const char* nameArg = ArgString(args, 1); |
|
+ if (!nameArg) { log->LogAlways("[CheatX] usage: cheatx_set_reputation <faction_name> <value>"); return; } |
|
float value = ArgFloat(args, 2, 0.0f); |
|
if (value < -1.0f) value = -1.0f; else if (value > 1.0f) value = 1.0f; |
|
|
|
auto* mgr = C_FactionManager::GetInstance(); |
|
if (!mgr) { log->LogAlways("[CheatX] no faction manager"); return; } |
|
|
|
+ bool forceExact = false; |
|
+ std::string cleanName = CleanQuery(nameArg, forceExact); |
|
+ const char* name = cleanName.c_str(); |
|
+ |
|
std::vector<C_Faction*> matches; |
|
+ C_Faction* exactMatch = nullptr; |
|
for (const auto& kv : mgr->m_factions) { |
|
C_Faction* f = kv.second.get(); |
|
if (!f || !f->m_pDef) continue; |
|
- if (MatchesName(f->m_pDef->name.c_str(), name)) |
|
+ if (_stricmp(f->m_pDef->name.c_str(), name) == 0) { |
|
+ exactMatch = f; |
|
+ break; |
|
+ } |
|
+ if (!forceExact && MatchesName(f->m_pDef->name.c_str(), name)) |
|
matches.push_back(f); |
|
} |
|
|
|
+ if (exactMatch) { |
|
+ matches.clear(); |
|
+ matches.push_back(exactMatch); |
|
+ } |
|
+ |
|
if (matches.empty()) { |
|
- log->LogAlways("[CheatX] no faction matching '%s'", name); |
|
+ log->LogAlways("[CheatX] no faction matching '%s'", nameArg); |
|
return; |
|
} |
|
if (matches.size() > 1) { |
|
- log->LogAlways("[CheatX] ambiguous -- %d factions match '%s':", static_cast<int>(matches.size()), name); |
|
+ log->LogAlways("[CheatX] ambiguous -- %d factions match '%s':", static_cast<int>(matches.size()), nameArg); |
|
for (C_Faction* f : matches) |
|
log->LogAlways("[CheatX] '%s' (rep=%.2f)", f->m_pDef->name.c_str(), f->m_reputation); |
|
return; |
|
@@ -445,24 +501,40 @@ |
|
{ |
|
auto* log = SSystemGlobalEnvironment::GetInstance()->pLog; |
|
if (!log) return; |
|
- const char* name = ArgString(args, 1); |
|
- if (!name) { log->LogAlways("[CheatX] usage: cheatx_set_opinion <npc_name> <value>"); return; } |
|
+ const char* nameArg = ArgString(args, 1); |
|
+ if (!nameArg) { log->LogAlways("[CheatX] usage: cheatx_set_opinion <npc_name> <value>"); return; } |
|
float value = ArgFloat(args, 2, 0.0f); |
|
if (value < -1.0f) value = -1.0f; else if (value > 1.0f) value = 1.0f; |
|
|
|
+ bool forceExact = false; |
|
+ std::string cleanName = CleanQuery(nameArg, forceExact); |
|
+ const char* name = cleanName.c_str(); |
|
+ |
|
struct Match { C_Soul* soul; float dist; }; |
|
std::vector<Match> matches; |
|
+ Match exactMatch = {nullptr, 0.0f}; |
|
+ bool foundExact = false; |
|
+ |
|
ForEachNpc(0.0f, [&](C_Soul* s, float dist) { |
|
- if (MatchesName(s->m_soulName.c_str(), name)) |
|
+ if (_stricmp(s->m_soulName.c_str(), name) == 0) { |
|
+ exactMatch = {s, dist}; |
|
+ foundExact = true; |
|
+ } |
|
+ if (!forceExact && MatchesName(s->m_soulName.c_str(), name)) |
|
matches.push_back({s, dist}); |
|
}); |
|
|
|
+ if (foundExact) { |
|
+ matches.clear(); |
|
+ matches.push_back(exactMatch); |
|
+ } |
|
+ |
|
if (matches.empty()) { |
|
- log->LogAlways("[CheatX] no loaded NPC matching '%s'", name); |
|
+ log->LogAlways("[CheatX] no loaded NPC matching '%s'", nameArg); |
|
return; |
|
} |
|
if (matches.size() > 1) { |
|
- log->LogAlways("[CheatX] ambiguous -- %d NPCs match '%s':", static_cast<int>(matches.size()), name); |
|
+ log->LogAlways("[CheatX] ambiguous -- %d NPCs match '%s':", static_cast<int>(matches.size()), nameArg); |
|
for (auto& m : matches) |
|
log->LogAlways("[CheatX] '%s' (opinion=%.2f, dist=%.1fm)", m.soul->m_soulName.c_str(), m.soul->GetPlayerOpinion(), m.dist); |
|
return; |
|
@@ -655,12 +727,28 @@ |
|
return; |
|
} |
|
|
|
+ bool forceExact = false; |
|
+ std::string cleanArg = CleanQuery(arg, forceExact); |
|
+ const char* name = cleanArg.c_str(); |
|
+ |
|
struct Match { C_Soul* soul; float dist; }; |
|
std::vector<Match> matches; |
|
+ Match exactMatch = {nullptr, 0.0f}; |
|
+ bool foundExact = false; |
|
+ |
|
ForEachNpc(0.0f, [&](C_Soul* s, float dist) { |
|
- if (MatchesName(s->m_soulName.c_str(), arg)) |
|
+ if (_stricmp(s->m_soulName.c_str(), name) == 0) { |
|
+ exactMatch = {s, dist}; |
|
+ foundExact = true; |
|
+ } |
|
+ if (!forceExact && MatchesName(s->m_soulName.c_str(), name)) |
|
matches.push_back({s, dist}); |
|
}); |
|
+ |
|
+ if (foundExact) { |
|
+ matches.clear(); |
|
+ matches.push_back(exactMatch); |
|
+ } |
|
|
|
if (matches.empty()) { |
|
log->LogAlways("[CheatX] no loaded NPC matching '%s'", arg); |