You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
8.7 KiB
280 lines
8.7 KiB
#include "control_chat_commands.hpp" |
|
#include "control.hpp" |
|
|
|
#include "diablo_msg.hpp" |
|
#include "engine/backbuffer_state.hpp" |
|
#include "inv.h" |
|
#include "levels/setmaps.h" |
|
#include "storm/storm_net.hpp" |
|
#include "utils/algorithm/container.hpp" |
|
#include "utils/log.hpp" |
|
#include "utils/parse_int.hpp" |
|
#include "utils/str_case.hpp" |
|
#include "utils/str_cat.hpp" |
|
|
|
#ifdef _DEBUG |
|
#include "debug.h" |
|
#endif |
|
|
|
namespace devilution { |
|
|
|
namespace { |
|
|
|
struct TextCmdItem { |
|
const std::string text; |
|
const std::string description; |
|
const std::string requiredParameter; |
|
std::string (*actionProc)(const std::string_view); |
|
}; |
|
|
|
extern std::vector<TextCmdItem> TextCmdList; |
|
|
|
std::string TextCmdHelp(const std::string_view parameter) |
|
{ |
|
if (parameter.empty()) { |
|
std::string ret; |
|
StrAppend(ret, _("Available Commands:")); |
|
for (const TextCmdItem &textCmd : TextCmdList) { |
|
StrAppend(ret, " ", _(textCmd.text)); |
|
} |
|
return ret; |
|
} |
|
auto textCmdIterator = c_find_if(TextCmdList, [&](const TextCmdItem &elem) { return elem.text == parameter; }); |
|
if (textCmdIterator == TextCmdList.end()) |
|
return StrCat(_("Command "), parameter, _(" is unknown.")); |
|
auto &textCmdItem = *textCmdIterator; |
|
if (textCmdItem.requiredParameter.empty()) |
|
return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: No additional parameter needed.")); |
|
return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: "), _(textCmdItem.requiredParameter)); |
|
} |
|
|
|
void AppendArenaOverview(std::string &ret) |
|
{ |
|
for (int arena = SL_FIRST_ARENA; arena <= SL_LAST; arena++) { |
|
StrAppend(ret, "\n", arena - SL_FIRST_ARENA + 1, " (", QuestLevelNames[arena], ")"); |
|
} |
|
} |
|
|
|
std::string TextCmdArena(const std::string_view parameter) |
|
{ |
|
std::string ret; |
|
if (!gbIsMultiplayer) { |
|
StrAppend(ret, _("Arenas are only supported in multiplayer.")); |
|
return ret; |
|
} |
|
|
|
if (parameter.empty()) { |
|
StrAppend(ret, _("What arena do you want to visit?")); |
|
AppendArenaOverview(ret); |
|
return ret; |
|
} |
|
|
|
const ParseIntResult<int> parsedParam = ParseInt<int>(parameter, /*min=*/0); |
|
const _setlevels arenaLevel = parsedParam.has_value() ? static_cast<_setlevels>(parsedParam.value() - 1 + SL_FIRST_ARENA) : _setlevels::SL_NONE; |
|
if (!IsArenaLevel(arenaLevel)) { |
|
StrAppend(ret, _("Invalid arena-number. Valid numbers are:")); |
|
AppendArenaOverview(ret); |
|
return ret; |
|
} |
|
|
|
if (!MyPlayer->isOnLevel(0) && !MyPlayer->isOnArenaLevel()) { |
|
StrAppend(ret, _("To enter a arena, you need to be in town or another arena.")); |
|
return ret; |
|
} |
|
|
|
setlvltype = GetArenaLevelType(arenaLevel); |
|
StartNewLvl(*MyPlayer, WM_DIABSETLVL, arenaLevel); |
|
return ret; |
|
} |
|
|
|
std::string TextCmdArenaPot(const std::string_view parameter) |
|
{ |
|
std::string ret; |
|
if (!gbIsMultiplayer) { |
|
StrAppend(ret, _("Arenas are only supported in multiplayer.")); |
|
return ret; |
|
} |
|
const int numPots = ParseInt<int>(parameter, /*min=*/1).value_or(1); |
|
|
|
Player &myPlayer = *MyPlayer; |
|
|
|
for (int potNumber = numPots; potNumber > 0; potNumber--) { |
|
Item item {}; |
|
InitializeItem(item, IDI_ARENAPOT); |
|
GenerateNewSeed(item); |
|
item.updateRequiredStatsCacheForPlayer(myPlayer); |
|
|
|
if (!AutoPlaceItemInBelt(myPlayer, item, true, true) && !AutoPlaceItemInInventory(myPlayer, item, true)) { |
|
break; // inventory is full |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
std::string TextCmdInspect(const std::string_view parameter) |
|
{ |
|
std::string ret; |
|
if (!gbIsMultiplayer) { |
|
StrAppend(ret, _("Inspecting only supported in multiplayer.")); |
|
return ret; |
|
} |
|
|
|
if (parameter.empty()) { |
|
StrAppend(ret, _("Stopped inspecting players.")); |
|
InspectPlayer = MyPlayer; |
|
return ret; |
|
} |
|
|
|
const std::string param = AsciiStrToLower(parameter); |
|
auto it = c_find_if(Players, [¶m](const Player &player) { |
|
return AsciiStrToLower(player._pName) == param; |
|
}); |
|
if (it == Players.end()) { |
|
it = c_find_if(Players, [¶m](const Player &player) { |
|
return AsciiStrToLower(player._pName).find(param) != std::string::npos; |
|
}); |
|
} |
|
if (it == Players.end()) { |
|
StrAppend(ret, _("No players found with such a name")); |
|
return ret; |
|
} |
|
|
|
Player &player = *it; |
|
InspectPlayer = &player; |
|
StrAppend(ret, _("Inspecting player: ")); |
|
StrAppend(ret, player._pName); |
|
OpenCharPanel(); |
|
if (!SpellbookFlag) |
|
invflag = true; |
|
RedrawEverything(); |
|
return ret; |
|
} |
|
|
|
bool IsQuestEnabled(const Quest &quest) |
|
{ |
|
switch (quest._qidx) { |
|
case Q_FARMER: |
|
return gbIsHellfire && !sgGameInitInfo.bCowQuest; |
|
case Q_JERSEY: |
|
return gbIsHellfire && sgGameInitInfo.bCowQuest; |
|
case Q_GIRL: |
|
return gbIsHellfire && sgGameInitInfo.bTheoQuest; |
|
case Q_CORNSTN: |
|
return gbIsHellfire && !gbIsMultiplayer; |
|
case Q_GRAVE: |
|
case Q_DEFILER: |
|
case Q_NAKRUL: |
|
return gbIsHellfire; |
|
case Q_TRADER: |
|
return false; |
|
default: |
|
return quest._qactive != QUEST_NOTAVAIL; |
|
} |
|
} |
|
|
|
std::string TextCmdLevelSeed(const std::string_view parameter) |
|
{ |
|
const std::string_view levelType = setlevel ? "set level" : "dungeon level"; |
|
|
|
char gameId[] = { |
|
static_cast<char>((sgGameInitInfo.programid >> 24) & 0xFF), |
|
static_cast<char>((sgGameInitInfo.programid >> 16) & 0xFF), |
|
static_cast<char>((sgGameInitInfo.programid >> 8) & 0xFF), |
|
static_cast<char>(sgGameInitInfo.programid & 0xFF), |
|
'\0' |
|
}; |
|
|
|
const std::string_view mode = gbIsMultiplayer ? "MP" : "SP"; |
|
const std::string_view questPool = UseMultiplayerQuests() ? "MP" : "Full"; |
|
|
|
uint32_t questFlags = 0; |
|
for (const Quest &quest : Quests) { |
|
questFlags <<= 1; |
|
if (IsQuestEnabled(quest)) |
|
questFlags |= 1; |
|
} |
|
|
|
return StrCat( |
|
"Seedinfo for ", levelType, " ", currlevel, "\n", |
|
"seed: ", DungeonSeeds[currlevel], "\n", |
|
#ifdef _DEBUG |
|
"Mid1: ", glMid1Seed[currlevel], "\n", |
|
"Mid2: ", glMid2Seed[currlevel], "\n", |
|
"Mid3: ", glMid3Seed[currlevel], "\n", |
|
"End: ", glEndSeed[currlevel], "\n", |
|
#endif |
|
"\n", |
|
gameId, " ", mode, "\n", |
|
questPool, " quests: ", questFlags, "\n", |
|
"Storybook: ", DungeonSeeds[16]); |
|
} |
|
|
|
std::string TextCmdPing(const std::string_view parameter) |
|
{ |
|
std::string ret; |
|
const std::string param = AsciiStrToLower(parameter); |
|
auto it = c_find_if(Players, [¶m](const Player &player) { |
|
return AsciiStrToLower(player._pName) == param; |
|
}); |
|
if (it == Players.end()) { |
|
it = c_find_if(Players, [¶m](const Player &player) { |
|
return AsciiStrToLower(player._pName).find(param) != std::string::npos; |
|
}); |
|
} |
|
if (it == Players.end()) { |
|
StrAppend(ret, _("No players found with such a name")); |
|
return ret; |
|
} |
|
|
|
Player &player = *it; |
|
DvlNetLatencies latencies = DvlNet_GetLatencies(player.getId()); |
|
|
|
StrAppend(ret, fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Character Name */ "Latency statistics for {:s}:")), player.name())); |
|
|
|
StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Echo latency: {:d} ms")), latencies.echoLatency)); |
|
|
|
if (latencies.providerLatency) { |
|
if (latencies.isRelayed && *latencies.isRelayed) { |
|
StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms (Relayed)")), *latencies.providerLatency)); |
|
} else { |
|
StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms")), *latencies.providerLatency)); |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
std::vector<TextCmdItem> TextCmdList = { |
|
{ "/help", N_("Prints help overview or help for a specific command."), N_("[command]"), &TextCmdHelp }, |
|
{ "/arena", N_("Enter a PvP Arena."), N_("<arena-number>"), &TextCmdArena }, |
|
{ "/arenapot", N_("Gives Arena Potions."), N_("<number>"), &TextCmdArenaPot }, |
|
{ "/inspect", N_("Inspects stats and equipment of another player."), N_("<player name>"), &TextCmdInspect }, |
|
{ "/seedinfo", N_("Show seed infos for current level."), "", &TextCmdLevelSeed }, |
|
{ "/ping", N_("Show latency statistics for another player."), N_("<player name>"), &TextCmdPing }, |
|
}; |
|
|
|
} // namespace |
|
|
|
bool CheckChatCommand(const std::string_view text) |
|
{ |
|
if (text.size() < 1 || text[0] != '/') |
|
return false; |
|
|
|
auto textCmdIterator = c_find_if(TextCmdList, [&](const TextCmdItem &elem) { return text.find(elem.text) == 0 && (text.length() == elem.text.length() || text[elem.text.length()] == ' '); }); |
|
if (textCmdIterator == TextCmdList.end()) { |
|
InitDiabloMsg(StrCat(_("Command "), "\"", text, "\"", _(" is unknown."))); |
|
return true; |
|
} |
|
|
|
const TextCmdItem &textCmd = *textCmdIterator; |
|
std::string_view parameter = ""; |
|
if (text.length() > (textCmd.text.length() + 1)) |
|
parameter = text.substr(textCmd.text.length() + 1); |
|
const std::string result = textCmd.actionProc(parameter); |
|
if (result != "") |
|
InitDiabloMsg(result); |
|
return true; |
|
} |
|
|
|
} // namespace devilution
|
|
|