diff --git a/src/Bin/CodeGen/CMakeLists.txt b/src/Bin/CodeGen/CMakeLists.txt index c70a359040f7..9b107493a943 100644 --- a/src/Bin/CodeGen/CMakeLists.txt +++ b/src/Bin/CodeGen/CMakeLists.txt @@ -8,6 +8,7 @@ set(BIN_CODEGEN_SOURCES set(BIN_CODEGEN_HEADERS CodeGenOptions.h CodeGenEnums.h + CodeGenFunctions.h CodeGenMap.h) if(NOT BUILD_PLATFORM STREQUAL "android") diff --git a/src/Bin/CodeGen/CodeGen.cpp b/src/Bin/CodeGen/CodeGen.cpp index 37d54e48f0bf..b2fb4032a878 100644 --- a/src/Bin/CodeGen/CodeGen.cpp +++ b/src/Bin/CodeGen/CodeGen.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -14,7 +15,10 @@ #include "Engine/GameResourceManager.h" #include "Engine/MapInfo.h" +#include "GUI/UI/Houses/TownHall.h" + #include "Library/Lod/LodReader.h" +#include "Library/Random/Random.h" #include "Library/Serialization/EnumSerialization.h" #include "Utility/Format.h" @@ -30,19 +34,6 @@ static auto contains = [](const std::string &haystack, const std::string &needle return haystack.find(needle) != std::string::npos; }; -static std::string toUpperCaseEnum(const std::string &string) { - std::string result; - for (char c : trim(string)) { - if (isalnum(c)) { - result += static_cast(toupper(c)); - } else if (isspace(c) || c == '/' || c == '-') { - if (!result.ends_with('_')) - result += '_'; - } - } - return result; -} - int runItemIdCodeGen(CodeGenOptions options, GameResourceManager *resourceManager) { ItemTable itemTable; itemTable.Initialize(resourceManager); @@ -256,7 +247,19 @@ int runHouseIdCodeGen(CodeGenOptions options, GameResourceManager *resourceManag return 0; } -std::string cleanupMonsterEnumName(std::string enumName) { +MonsterStats loadMonsterStats(GameResourceManager *resourceManager) { + TriBlob dmonBlobs; + dmonBlobs.mm7 = resourceManager->getEventsFile("dmonlist.bin"); + + pMonsterList = new MonsterList; + deserialize(dmonBlobs, pMonsterList); + + MonsterStats result; + result.Initialize(resourceManager->getEventsFile("monsters.txt")); + return result; +} + +std::string cleanupMonsterIdEnumName(std::string enumName) { for (const char *prefix : {"ZBLASTERGUY", "ZULTRA_DRAGON", }) if (enumName.starts_with(prefix)) enumName = enumName.substr(1); @@ -273,14 +276,7 @@ std::string cleanupMonsterEnumName(std::string enumName) { } int runMonsterIdCodeGen(CodeGenOptions options, GameResourceManager *resourceManager) { - TriBlob dmonBlobs; - dmonBlobs.mm7 = resourceManager->getEventsFile("dmonlist.bin"); - - pMonsterList = new MonsterList; - deserialize(dmonBlobs, pMonsterList); - - MonsterStats monsterStats; - monsterStats.Initialize(resourceManager->getEventsFile("monsters.txt")); + MonsterStats monsterStats = loadMonsterStats(resourceManager); CodeGenMap map; map.insert(MONSTER_INVALID, "INVALID", ""); @@ -288,7 +284,7 @@ int runMonsterIdCodeGen(CodeGenOptions options, GameResourceManager *resourceMan for (const MonsterId i : allMonsters()) { const MonsterDesc &desc = pMonsterList->pMonsters[i]; const MonsterInfo &info = monsterStats.pInfos[i]; - std::string enumName = cleanupMonsterEnumName(toUpperCaseEnum(desc.pMonsterName)); + std::string enumName = cleanupMonsterIdEnumName(toUpperCaseEnum(desc.pMonsterName)); std::string comment = info.pName; if (comment == "peasant") @@ -303,12 +299,20 @@ int runMonsterIdCodeGen(CodeGenOptions options, GameResourceManager *resourceMan return 0; } -int runMonsterTypeCodeGen(CodeGenOptions options, GameResourceManager *resourceManager) { - TriBlob dmonBlobs; - dmonBlobs.mm7 = resourceManager->getEventsFile("dmonlist.bin"); +std::string cleanupMonsterTypeEnumName(std::string enumName) { + enumName = cleanupMonsterIdEnumName(enumName); - pMonsterList = new MonsterList; - deserialize(dmonBlobs, pMonsterList); + if (enumName.ends_with("_A")) { + enumName.resize(enumName.size() - 2); + } else if (!enumName.empty()) { + throw Exception("Invalid monster id name"); + } + + return enumName; +} + +int runMonsterTypeCodeGen(CodeGenOptions options, GameResourceManager *resourceManager) { + MonsterStats monsterStats = loadMonsterStats(resourceManager); CodeGenMap map; map.insert(MONSTER_TYPE_INVALID, "INVALID", ""); @@ -319,13 +323,7 @@ int runMonsterTypeCodeGen(CodeGenOptions options, GameResourceManager *resourceM continue; const MonsterDesc &desc = pMonsterList->pMonsters[i]; - std::string enumName = cleanupMonsterEnumName(toUpperCaseEnum(desc.pMonsterName)); - - if (enumName.ends_with("_A")) { - enumName.resize(enumName.size() - 2); - } else if (!enumName.empty()) { - throw Exception("Invalid monster id name"); - } + std::string enumName = cleanupMonsterTypeEnumName(toUpperCaseEnum(desc.pMonsterName)); map.insert(monsterTypeForMonsterId(i), enumName, ""); } @@ -334,6 +332,57 @@ int runMonsterTypeCodeGen(CodeGenOptions options, GameResourceManager *resourceM return 0; } +int runBountyHuntCodegen(CodeGenOptions options, GameResourceManager *resourceManager) { + // Fill bounty hunt map. + grng = RandomEngine::create(RANDOM_ENGINE_SEQUENTIAL); + IndexedArray, HOUSE_FIRST_TOWN_HALL, HOUSE_LAST_TOWN_HALL> monstersByTownHall; + for (const HouseId townHall : allTownhallHouses()) { + grng->seed(0); + while(true) { + MonsterId monsterId = GUIWindow_TownHall::randomMonsterForHunting(townHall); + if (!monstersByTownHall[townHall].insert(monsterId).second) + break; + } + } + + // Invert the map. + std::unordered_map> townHallsByMonster; + for (const HouseId townHall : allTownhallHouses()) + for (const MonsterId monsterId : monstersByTownHall[townHall]) + townHallsByMonster[monsterId].insert(townHall); + + // Reduce the map to monster types & check that it's actually reducible. + std::unordered_map> townHallsByMonsterType; + for (const MonsterId monsterId : allMonsters()) { + MonsterType monsterType = monsterTypeForMonsterId(monsterId); + if (townHallsByMonsterType.contains(monsterType) && townHallsByMonsterType[monsterType] != townHallsByMonster[monsterId]) + throw Exception("Invalid bounty hunt record"); + + townHallsByMonsterType[monsterType] = townHallsByMonster[monsterId]; + } + + // Prepare output table. + std::vector> table; + for (const MonsterType monsterType : allMonsterTypes()) { + auto &line = table.emplace_back(); + line[0] = fmt::format("{{{}, ", toString(monsterType)); + line[1] = "{"; + for (const HouseId townHall : townHallsByMonsterType[monsterType]) + line[2 + std::to_underlying(townHall) - std::to_underlying(HOUSE_FIRST_TOWN_HALL)] = toString(townHall) + ", "; + for (size_t i = 6; i >= 2; i--) { + if (!line[i].empty()) { + line[i].resize(line[i].size() - 2); // Drop the last ", ". + break; + } + } + line[7] = "}},"; + } + + // Dump! + dumpAligned(stdout, " ", table); + return 0; +} + int platformMain(int argc, char **argv) { try { CodeGenOptions options = CodeGenOptions::parse(argc, argv); @@ -352,6 +401,7 @@ int platformMain(int argc, char **argv) { case CodeGenOptions::SUBCOMMAND_HOUSE_ID: return runHouseIdCodeGen(std::move(options), &resourceManager); case CodeGenOptions::SUBCOMMAND_MONSTER_ID: return runMonsterIdCodeGen(std::move(options), &resourceManager); case CodeGenOptions::SUBCOMMAND_MONSTER_TYPE: return runMonsterTypeCodeGen(std::move(options), &resourceManager); + case CodeGenOptions::SUBCOMMAND_BOUNTY_HUNT: return runBountyHuntCodegen(std::move(options), &resourceManager); default: assert(false); return 1; diff --git a/src/Bin/CodeGen/CodeGenEnums.cpp b/src/Bin/CodeGen/CodeGenEnums.cpp index e760407bab09..7463b3412125 100644 --- a/src/Bin/CodeGen/CodeGenEnums.cpp +++ b/src/Bin/CodeGen/CodeGenEnums.cpp @@ -39,3 +39,6 @@ MM_DEFINE_ENUM_SERIALIZATION_FUNCTIONS(BuildingType, CASE_INSENSITIVE, { {BUILDING_SHADOW_GUILD, "SHADOW_GUILD"}, {BUILDING_ADVENTURERS_INN, "ADVENTURERS_INN"}, }) + +MM_DEFINE_ENUM_MAGIC_SERIALIZATION_FUNCTIONS(HouseId) +MM_DEFINE_ENUM_MAGIC_SERIALIZATION_FUNCTIONS(MonsterType) diff --git a/src/Bin/CodeGen/CodeGenEnums.h b/src/Bin/CodeGen/CodeGenEnums.h index 50180bb9dfe4..6f7f7db4292e 100644 --- a/src/Bin/CodeGen/CodeGenEnums.h +++ b/src/Bin/CodeGen/CodeGenEnums.h @@ -1,7 +1,10 @@ #pragma once #include "Engine/Tables/BuildingTable.h" +#include "Engine/Objects/MonsterEnums.h" #include "Library/Serialization/SerializationFwd.h" MM_DECLARE_SERIALIZATION_FUNCTIONS(BuildingType) +MM_DECLARE_SERIALIZATION_FUNCTIONS(HouseId) +MM_DECLARE_SERIALIZATION_FUNCTIONS(MonsterType) diff --git a/src/Bin/CodeGen/CodeGenFunctions.h b/src/Bin/CodeGen/CodeGenFunctions.h new file mode 100644 index 000000000000..d7e06cbc7e37 --- /dev/null +++ b/src/Bin/CodeGen/CodeGenFunctions.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Utility/Format.h" + +inline std::string toUpperCaseEnum(const std::string &string) { + std::string result; + for (char c : trim(string)) { + if (std::isalnum(c)) { + result += static_cast(toupper(c)); + } else if (std::isspace(c) || c == '/' || c == '-') { + if (!result.ends_with('_')) + result += '_'; + } + } + return result; +} + +template +void dumpAligned(FILE *file, std::string_view prefix, const std::vector> &table) { + std::array maxLengths; + maxLengths.fill(0); + + for (const auto &line : table) + for (size_t i = 0; i < line.size(); i++) + maxLengths[i] = std::max(maxLengths[i], line[i].size()); + + for (const auto &line : table) { + std::string output = std::string(prefix); + for (size_t i = 0; i < line.size(); i++) { + output += line[i]; + output += std::string(maxLengths[i] - line[i].size(), ' '); + } + while (output.ends_with(' ')) // Note that we don't trim front. + output.pop_back(); + fmt::println(file, "{}", output); + } +} diff --git a/src/Bin/CodeGen/CodeGenMap.h b/src/Bin/CodeGen/CodeGenMap.h index d3149e993f67..323483b88723 100644 --- a/src/Bin/CodeGen/CodeGenMap.h +++ b/src/Bin/CodeGen/CodeGenMap.h @@ -11,6 +11,8 @@ #include "Utility/Workaround/ToUnderlying.h" #include "Utility/Format.h" +#include "CodeGenFunctions.h" + class CodeGenMap { public: template @@ -40,25 +42,16 @@ class CodeGenMap { } void dump(FILE *file, const std::string &prefix) { - std::vector> linesAndComments; + std::vector> linesAndComments; for (const auto &[value, name] : _nameByValue) { std::string comment = _commentByValue[value]; if (!comment.empty()) - comment = " // " + comment; + comment = "// " + comment; - linesAndComments.emplace_back(fmt::format("{}{} = {}", prefix, name, value), comment); + linesAndComments.push_back({fmt::format("{}{} = {}, ", prefix, name, value), comment}); } - size_t maxLineLen = 0; - for (const auto &[line, _] : linesAndComments) - maxLineLen = std::max(maxLineLen, line.size()); - - for (const auto &[line, comment] : linesAndComments) { - std::string padding; - if (!comment.empty()) - padding = std::string(maxLineLen - line.size(), ' '); - fmt::println(file, " {},{}{}", line, padding, comment); - } + dumpAligned(file, " ", linesAndComments); } private: diff --git a/src/Bin/CodeGen/CodeGenOptions.cpp b/src/Bin/CodeGen/CodeGenOptions.cpp index e7dcabfe9720..54105cd8bc78 100644 --- a/src/Bin/CodeGen/CodeGenOptions.cpp +++ b/src/Bin/CodeGen/CodeGenOptions.cpp @@ -36,16 +36,21 @@ CodeGenOptions CodeGenOptions::parse(int argc, char **argv) { CLI::App *monsterTypes = app->add_subcommand("monster_types", "Generate monster types enum")->fallthrough(); monsterTypes->callback([&] { result.subcommand = SUBCOMMAND_MONSTER_TYPE; }); + CLI::App *bountyHunt = app->add_subcommand("bounty_hunt", "Generate monster type / town hall table for bounty hunts")->fallthrough(); + bountyHunt->callback([&] { result.subcommand = SUBCOMMAND_BOUNTY_HUNT; }); + try { app->parse(argc, argv); } catch (const CLI::ParseError &e) { + // TODO(captainurist): this is getting out of hand. bool isHelp = app->get_help_ptr()->as() || items->get_help_ptr()->as() || maps->get_help_ptr()->as() || beacons->get_help_ptr()->as() || monsters->get_help_ptr()->as() || - monsterTypes->get_help_ptr()->as(); + monsterTypes->get_help_ptr()->as() || + bountyHunt->get_help_ptr()->as(); if (isHelp) { app->exit(e); result.helpPrinted = true; diff --git a/src/Bin/CodeGen/CodeGenOptions.h b/src/Bin/CodeGen/CodeGenOptions.h index 00bf8f191432..bf172725c481 100644 --- a/src/Bin/CodeGen/CodeGenOptions.h +++ b/src/Bin/CodeGen/CodeGenOptions.h @@ -11,6 +11,7 @@ struct CodeGenOptions : GameStarterOptions { SUBCOMMAND_HOUSE_ID, SUBCOMMAND_MONSTER_ID, SUBCOMMAND_MONSTER_TYPE, + SUBCOMMAND_BOUNTY_HUNT, }; using enum Subcommand; diff --git a/src/Engine/Objects/MonsterEnums.cpp b/src/Engine/Objects/MonsterEnums.cpp index 81e8119897f2..9ae97d9fd680 100644 --- a/src/Engine/Objects/MonsterEnums.cpp +++ b/src/Engine/Objects/MonsterEnums.cpp @@ -115,3 +115,114 @@ CharacterSex sexForMonsterType(MonsterType monsterType) { Race raceForMonsterType(MonsterType monsterType) { return sexAndRaceByMonsterType[monsterType].race; } + +struct BountyHuntableMask : IndexedArray { + constexpr BountyHuntableMask() { + fill(false); + } + + constexpr BountyHuntableMask(std::initializer_list townHalls) { + fill(false); + for (const HouseId townHall : townHalls) + (*this)[townHall] = true; + } +}; + +static constexpr IndexedArray bountyHuntableMaskByMonsterType = { + {MONSTER_TYPE_ANGEL, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ARCHER, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_BAT, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_BEHEMOTH, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_BEHOLDER, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_CLERIC_MOON, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_CLERIC_SUN, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_DEVIL, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_DRAGON, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_DRAGONFLY, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_DWARF, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ELEMENTAL_AIR, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ELEMENTAL_EARTH, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ELEMENTAL_FIRE, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ELEMENTAL_LIGHT, { }}, + {MONSTER_TYPE_ELEMENTAL_WATER, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ELF_ARCHER, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ELF_SPEARMAN, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_FIGHTER_CHAIN, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_FIGHTER_LEATHER, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_FIGHTER_PLATE, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_GARGOYLE, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_GENIE, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_GHOST, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_GOBLIN, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_GOG, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_GOLEM, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_GRIFFIN, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_HARPY, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_HYDRA, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_LICH, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_MAGE, { HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_MANTICORE, { }}, + {MONSTER_TYPE_MEDUSA, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_MINOTAUR, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_MONK, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_NECROMANCER, { HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_OOZE, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_PEASANT_DWARF_FEMALE_A, { }}, + {MONSTER_TYPE_PEASANT_DWARF_FEMALE_B, { }}, + {MONSTER_TYPE_PEASANT_DWARF_FEMALE_C, { }}, + {MONSTER_TYPE_PEASANT_DWARF_MALE_A, { }}, + {MONSTER_TYPE_PEASANT_DWARF_MALE_B, { }}, + {MONSTER_TYPE_PEASANT_DWARF_MALE_C, { }}, + {MONSTER_TYPE_PEASANT_ELF_FEMALE_A, { }}, + {MONSTER_TYPE_PEASANT_ELF_FEMALE_B, { }}, + {MONSTER_TYPE_PEASANT_ELF_FEMALE_C, { }}, + {MONSTER_TYPE_PEASANT_ELF_MALE_A, { }}, + {MONSTER_TYPE_PEASANT_ELF_MALE_B, { }}, + {MONSTER_TYPE_PEASANT_ELF_MALE_C, { }}, + {MONSTER_TYPE_PEASANT_HUMAN1_FEMALE_A, { }}, + {MONSTER_TYPE_PEASANT_HUMAN1_FEMALE_B, { }}, + {MONSTER_TYPE_PEASANT_HUMAN1_FEMALE_C, { }}, + {MONSTER_TYPE_PEASANT_HUMAN1_MALE_A, { }}, + {MONSTER_TYPE_PEASANT_HUMAN1_MALE_B, { }}, + {MONSTER_TYPE_PEASANT_HUMAN1_MALE_C, { }}, + {MONSTER_TYPE_PEASANT_HUMAN2_MALE_A, { }}, + {MONSTER_TYPE_PEASANT_HUMAN2_MALE_B, { }}, + {MONSTER_TYPE_PEASANT_HUMAN2_MALE_C, { }}, + {MONSTER_TYPE_PEASANT_HUMAN2_FEMALE_A, { }}, + {MONSTER_TYPE_PEASANT_HUMAN2_FEMALE_B, { }}, + {MONSTER_TYPE_PEASANT_HUMAN2_FEMALE_C, { }}, + {MONSTER_TYPE_RAT, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ROBOT, { }}, + {MONSTER_TYPE_ROC, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_SEA_MONSTER, { }}, + {MONSTER_TYPE_SKELETON_WARRIOR, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_SPIDER, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_SWORDSMAN, { HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_THIEF, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_TITAN, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_TROGLODYTE, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_VAMPIRE, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_WARLOCK, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_WIGHT, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_WYVERN, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_ZOMBIE, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_PEASANT_GOBLIN_FEMALE_A, {HOUSE_TOWN_HALL_HARMONDALE }}, + {MONSTER_TYPE_PEASANT_GOBLIN_FEMALE_B, { }}, + {MONSTER_TYPE_PEASANT_GOBLIN_FEMALE_C, { }}, + {MONSTER_TYPE_PEASANT_GOBLIN_MALE_A, { }}, + {MONSTER_TYPE_PEASANT_GOBLIN_MALE_B, { }}, + {MONSTER_TYPE_PEASANT_GOBLIN_MALE_C, { }}, + {MONSTER_TYPE_TROLL, { HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE, HOUSE_TOWN_HALL_PIT}}, + {MONSTER_TYPE_TREANT, { }}, + {MONSTER_TYPE_GHOUL, {HOUSE_TOWN_HALL_HARMONDALE, HOUSE_TOWN_HALL_ERATHIA, HOUSE_TOWN_HALL_TULAREAN_FOREST, HOUSE_TOWN_HALL_CELESTE }}, + {MONSTER_TYPE_BLASTERGUY, { }}, + {MONSTER_TYPE_ULTRA_DRAGON, { }}, + {MONSTER_TYPE_UNUSED_CAT, { }}, + {MONSTER_TYPE_UNUSED_CHICKEN, { }}, + {MONSTER_TYPE_UNUSED_DOG, { }}, + {MONSTER_TYPE_UNUSED_RAT, { }}, +}; + +bool isBountyHuntable(MonsterType monsterType, HouseId townHall) { + return bountyHuntableMaskByMonsterType[monsterType][townHall]; +} diff --git a/src/Engine/Objects/MonsterEnums.h b/src/Engine/Objects/MonsterEnums.h index 38683f6e34b0..34d2ad229132 100644 --- a/src/Engine/Objects/MonsterEnums.h +++ b/src/Engine/Objects/MonsterEnums.h @@ -3,6 +3,7 @@ #include #include "Engine/Objects/CharacterEnums.h" +#include "GUI/UI/UIHouseEnums.h" #include "Utility/Workaround/ToUnderlying.h" #include "Utility/Segment.h" @@ -431,6 +432,10 @@ enum class MonsterType { }; using enum MonsterType; +inline Segment allMonsterTypes() { + return {MONSTER_TYPE_FIRST, MONSTER_TYPE_LAST}; +} + inline MonsterType monsterTypeForMonsterId(MonsterId monsterId) { return static_cast((std::to_underlying(monsterId) - 1) / 3 + 1); } @@ -447,6 +452,8 @@ CharacterSex sexForMonsterType(MonsterType monsterType); Race raceForMonsterType(MonsterType monsterType); +bool isBountyHuntable(MonsterType monsterType, HouseId townHall); + /* 335 */ enum class MONSTER_SPECIAL_ABILITY_TYPE { MONSTER_SPECIAL_ABILITY_NONE = 0x0, @@ -519,3 +526,4 @@ enum class MonsterHostility { HOSTILITY_LAST = HOSTILITY_LONG }; using enum MonsterHostility; + diff --git a/src/GUI/UI/Houses/TownHall.h b/src/GUI/UI/Houses/TownHall.h index abc992daab61..91c9e78e5967 100644 --- a/src/GUI/UI/Houses/TownHall.h +++ b/src/GUI/UI/Houses/TownHall.h @@ -22,14 +22,14 @@ class GUIWindow_TownHall : public GUIWindow_House { */ std::string bountyHuntingText(); + static MonsterId randomMonsterForHunting(HouseId townhall); + protected: void mainDialogue(); void bountyHuntDialogue(); void payFineDialogue(); private: - MonsterId randomMonsterForHunting(HouseId townhall); - /** * Handler for the "Bounty Hunt" dialogue option in a town hall. *