use enums for difficulty and language; fix enemy state aliases; closes #694

This commit is contained in:
Martin Michelsen
2025-10-17 20:53:25 -07:00
parent 052dcf8c6e
commit 46c2260d0f
70 changed files with 790 additions and 671 deletions
+34 -27
View File
@@ -1720,7 +1720,7 @@ phosg::JSON MapDefinition::CameraSpec::json() const {
});
}
phosg::JSON MapDefinition::NPCDeck::json(uint8_t language) const {
phosg::JSON MapDefinition::NPCDeck::json(Language language) const {
phosg::JSON card_ids_json = phosg::JSON::list();
for (size_t z = 0; z < this->card_ids.size(); z++) {
if (this->card_ids[z] != 0xFFFF) {
@@ -1733,7 +1733,7 @@ phosg::JSON MapDefinition::NPCDeck::json(uint8_t language) const {
});
}
phosg::JSON MapDefinition::AIParams::json(uint8_t language) const {
phosg::JSON MapDefinition::AIParams::json(Language language) const {
phosg::JSON params_json = phosg::JSON::list();
for (size_t z = 0; z < this->params.size(); z++) {
params_json.emplace_back(this->params[z].load());
@@ -1745,7 +1745,7 @@ phosg::JSON MapDefinition::AIParams::json(uint8_t language) const {
});
}
phosg::JSON MapDefinition::DialogueSet::json(uint8_t language) const {
phosg::JSON MapDefinition::DialogueSet::json(Language language) const {
phosg::JSON strings_json = phosg::JSON::list();
for (size_t z = 0; z < this->strings.size(); z++) {
strings_json.emplace_back(this->strings[z].decode(language));
@@ -1818,7 +1818,7 @@ string MapDefinition::CameraSpec::str() const {
this->unknown_a2[1], this->unknown_a2[2]);
}
string MapDefinition::str(const CardIndex* card_index, uint8_t language) const {
string MapDefinition::str(const CardIndex* card_index, Language language) const {
deque<string> lines;
auto add_map = [&](const parray<parray<uint8_t, 0x10>, 0x10>& tiles) {
for (size_t y = 0; y < this->height; y++) {
@@ -2503,7 +2503,7 @@ CardIndex::CardIndex(
// Some cards intentionally have the same name, so we just leave them
// unindexed (they can still be looked up by ID, of course)
string name = entry->def.en_name.decode(1);
string name = entry->def.en_name.decode(Language::ENGLISH);
this->card_definitions_by_name.emplace(name, entry);
this->card_definitions_by_name_normalized.emplace(this->normalize_card_name(name), entry);
@@ -2620,11 +2620,11 @@ string CardIndex::normalize_card_name(const string& name) {
return ret;
}
MapIndex::VersionedMap::VersionedMap(shared_ptr<const MapDefinition> map, uint8_t language)
MapIndex::VersionedMap::VersionedMap(shared_ptr<const MapDefinition> map, Language language)
: map(map),
language(language) {}
MapIndex::VersionedMap::VersionedMap(std::string&& compressed_data, uint8_t language)
MapIndex::VersionedMap::VersionedMap(std::string&& compressed_data, Language language)
: language(language),
compressed_data(make_shared<string>(std::move(compressed_data))) {
string decompressed = prs_decompress(*this->compressed_data);
@@ -2673,33 +2673,39 @@ std::shared_ptr<const std::string> MapIndex::VersionedMap::trial_download() cons
MapIndex::Map::Map(shared_ptr<const VersionedMap> initial_version)
: map_number(initial_version->map->map_number),
initial_version(initial_version) {
this->versions.resize(this->initial_version->language + 1);
this->versions[this->initial_version->language] = initial_version;
size_t lang_index = static_cast<size_t>(this->initial_version->language);
this->versions.resize(lang_index + 1);
this->versions[lang_index] = initial_version;
}
void MapIndex::Map::add_version(std::shared_ptr<const VersionedMap> vm) {
if (this->versions.size() <= vm->language) {
this->versions.resize(vm->language + 1);
size_t lang_index = static_cast<size_t>(vm->language);
if (this->versions.size() <= lang_index) {
this->versions.resize(lang_index + 1);
}
if (this->versions[vm->language]) {
if (this->versions[lang_index]) {
throw runtime_error("map version already exists");
}
this->initial_version->map->assert_semantically_equivalent(*vm->map);
this->versions[vm->language] = vm;
this->versions[lang_index] = vm;
}
bool MapIndex::Map::has_version(uint8_t language) const {
return (this->versions.size() > language) && !!this->versions[language];
bool MapIndex::Map::has_version(Language language) const {
size_t lang_index = static_cast<size_t>(language);
return (this->versions.size() > lang_index) && !!this->versions[lang_index];
}
shared_ptr<const MapIndex::VersionedMap> MapIndex::Map::version(uint8_t language) const {
shared_ptr<const MapIndex::VersionedMap> MapIndex::Map::version(Language language) const {
size_t lang_index = static_cast<size_t>(language);
// If the requested language exists, return it
if ((language < this->versions.size()) && this->versions[language]) {
return this->versions[language];
if ((lang_index < this->versions.size()) && this->versions[lang_index]) {
return this->versions[lang_index];
}
// If English exists, return it
if ((1 < this->versions.size()) && this->versions[1]) {
return this->versions[1];
constexpr size_t english_lang_index = static_cast<size_t>(Language::ENGLISH);
if ((english_lang_index < this->versions.size()) && this->versions[english_lang_index]) {
return this->versions[english_lang_index];
}
// Return the first version that exists
for (const auto& vm : this->versions) {
@@ -2754,7 +2760,7 @@ MapIndex::MapIndex(const string& directory) {
if (base_filename[base_filename.size() - 2] != '-') {
throw runtime_error("language code not present");
}
uint8_t language = language_code_for_char(base_filename[base_filename.size() - 1]);
Language language = language_for_char(base_filename[base_filename.size() - 1]);
shared_ptr<VersionedMap> vm;
if (decompressed_data) {
@@ -2773,7 +2779,7 @@ MapIndex::MapIndex(const string& directory) {
static_game_data_log.debug_f("({}) Created Episode 3 map {:08X} {} ({}; {})",
filename,
vm->map->map_number,
char_for_language_code(vm->language),
char_for_language(vm->language),
vm->map->is_quest() ? "quest" : "free",
name);
} else {
@@ -2781,7 +2787,7 @@ MapIndex::MapIndex(const string& directory) {
static_game_data_log.debug_f("({}) Added Episode 3 map version {:08X} {} ({}; {})",
filename,
vm->map->map_number,
char_for_language_code(vm->language),
char_for_language(vm->language),
vm->map->is_quest() ? "quest" : "free",
name);
}
@@ -2794,7 +2800,7 @@ MapIndex::MapIndex(const string& directory) {
}
}
const string& MapIndex::get_compressed_list(size_t num_players, uint8_t language) const {
const string& MapIndex::get_compressed_list(size_t num_players, Language language) const {
if (num_players == 0) {
throw runtime_error("cannot generate map list for no players");
}
@@ -2802,10 +2808,11 @@ const string& MapIndex::get_compressed_list(size_t num_players, uint8_t language
throw logic_error("player count is too high in map list generation");
}
if (language >= this->compressed_map_lists.size()) {
this->compressed_map_lists.resize(language + 1);
size_t lang_index = static_cast<size_t>(language);
if (lang_index >= this->compressed_map_lists.size()) {
this->compressed_map_lists.resize(lang_index + 1);
}
string& compressed_map_list = this->compressed_map_lists[language].at(num_players - 1);
string& compressed_map_list = this->compressed_map_lists[lang_index].at(num_players - 1);
if (compressed_map_list.empty()) {
phosg::StringWriter entries_w;
phosg::StringWriter strings_w;
+11 -11
View File
@@ -1315,7 +1315,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 00 */ pstring<TextEncoding::MARKED, 0x18> deck_name;
/* 18 */ parray<be_uint16_t, 0x20> card_ids; // Last one appears to always be FFFF
/* 58 */
phosg::JSON json(uint8_t language) const;
phosg::JSON json(Language language) const;
} __packed_ws__(NPCDeck, 0x58);
/* 1FE8 */ parray<NPCDeck, 3> npc_decks; // Unused if name[0] == 0
@@ -1331,7 +1331,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// TODO: Figure out exactly how these are used and document here.
/* 0018 */ parray<be_uint16_t, 0x7E> params;
/* 0114 */
phosg::JSON json(uint8_t language) const;
phosg::JSON json(Language language) const;
} __packed_ws__(AIParams, 0x114);
/* 20F0 */ parray<AIParams, 3> npc_ai_params; // Unused if name[0] == 0
@@ -1384,7 +1384,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// strings, excluding any that are empty or begin with the character '^'.
/* 0004 */ parray<pstring<TextEncoding::MARKED, 0x40>, 4> strings;
/* 0104 */
phosg::JSON json(uint8_t language) const;
phosg::JSON json(Language language) const;
} __packed_ws__(DialogueSet, 0x104);
// There are up to 0x10 of these per valid NPC, but only the first 13 of them
@@ -1490,8 +1490,8 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// text may differ.
void assert_semantically_equivalent(const MapDefinition& other) const;
std::string str(const CardIndex* card_index, uint8_t language) const;
phosg::JSON json(uint8_t language) const;
std::string str(const CardIndex* card_index, Language language) const;
phosg::JSON json(Language language) const;
} __packed_ws__(MapDefinition, 0x5A18);
struct MapDefinitionTrial {
@@ -1592,10 +1592,10 @@ public:
class VersionedMap {
public:
std::shared_ptr<const MapDefinition> map;
uint8_t language;
Language language;
VersionedMap(std::shared_ptr<const MapDefinition> map, uint8_t language);
VersionedMap(std::string&& compressed_data, uint8_t language);
VersionedMap(std::shared_ptr<const MapDefinition> map, Language language);
VersionedMap(std::string&& compressed_data, Language language);
std::shared_ptr<const MapDefinitionTrial> trial() const;
std::shared_ptr<const std::string> compressed(bool trial) const;
@@ -1616,8 +1616,8 @@ public:
explicit Map(std::shared_ptr<const VersionedMap> initial_version);
void add_version(std::shared_ptr<const VersionedMap> vm);
bool has_version(uint8_t language) const;
std::shared_ptr<const VersionedMap> version(uint8_t language) const;
bool has_version(Language language) const;
std::shared_ptr<const VersionedMap> version(Language language) const;
inline const std::vector<std::shared_ptr<const VersionedMap>>& all_versions() const {
return this->versions;
}
@@ -1626,7 +1626,7 @@ public:
std::vector<std::shared_ptr<const VersionedMap>> versions;
};
const std::string& get_compressed_list(size_t num_players, uint8_t language) const;
const std::string& get_compressed_list(size_t num_players, Language language) const;
inline std::shared_ptr<const Map> get(uint32_t id) const {
return this->maps.at(id);
}
+1 -1
View File
@@ -320,7 +320,7 @@ void DeckState::print(FILE* stream, std::shared_ptr<const CardIndex> card_index)
}
}
if (ce) {
string name = ce->def.en_name.decode(1);
string name = ce->def.en_name.decode(Language::ENGLISH);
phosg::fwrite_fmt(stream, " ({:02}) index={:02X} ref=@{:04X} card_id=#{:04X} \"{}\" {}\n",
z, e.deck_index, this->card_refs[z], e.card_id, name, name_for_card_state(e.state));
} else {
+15 -14
View File
@@ -274,14 +274,14 @@ void Server::send_6xB4x46() const {
// NTE doesn't have the date_str2 field, but we send it anyway to make
// debugging easier.
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, 1);
cmd.date_str1.encode(std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()), 1);
cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, Language::ENGLISH);
cmd.date_str1.encode(std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()), Language::ENGLISH);
string build_date = phosg::format_time(BUILD_TIMESTAMP);
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), 1);
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH);
this->send(cmd);
}
string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> map, uint8_t language, bool is_nte) {
string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> map, Language language, bool is_nte) {
auto vm = map->version(language);
const auto& compressed = vm->compressed(is_nte);
@@ -306,7 +306,7 @@ void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) co
if (this->last_chosen_map) {
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch->language, this->options.is_nte());
this->log().info_f("Sending {} version of map {:08X}", char_for_language_code(ch->language), this->last_chosen_map->map_number);
this->log().info_f("Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number);
ch->send(0x6C, 0x00, data);
}
@@ -2137,7 +2137,7 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
// in the case of NTE, no values at all, since the Rules structure is
// smaller). So, use the values from the last chosen map if applicable, or
// the values from the $dicerange command if available.
uint8_t language = c ? c->language() : 1;
Language language = c ? c->language() : Language::ENGLISH;
const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(language)->map->default_rules : nullptr;
auto& server_rules = this->map_and_rules->rules;
// NTE can specify the DEF dice value range in its Rules struct, so we use
@@ -2526,7 +2526,7 @@ void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const st
}
size_t num_players = l ? l->count_clients() : 1;
uint8_t language = sender_c ? sender_c->language() : 1;
Language language = sender_c ? sender_c->language() : Language::ENGLISH;
const auto& list_data = this->options.map_index->get_compressed_list(num_players, language);
phosg::StringWriter w;
@@ -2553,15 +2553,16 @@ void Server::send_6xB6x41_to_all_clients() const {
if (!c) {
return;
}
if (map_commands_by_language.size() <= c->language()) {
map_commands_by_language.resize(c->language() + 1);
size_t lang_index = static_cast<size_t>(c->language());
if (map_commands_by_language.size() <= lang_index) {
map_commands_by_language.resize(lang_index + 1);
}
if (map_commands_by_language[c->language()].empty()) {
map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition(
if (map_commands_by_language[lang_index].empty()) {
map_commands_by_language[lang_index] = this->prepare_6xB6x41_map_definition(
this->last_chosen_map, c->language(), this->options.is_nte());
}
this->log().info_f("Sending {} version of map {:08X}", char_for_language_code(c->language()), this->last_chosen_map->map_number);
send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]);
this->log().info_f("Sending {} version of map {:08X}", name_for_language(c->language()), this->last_chosen_map->map_number);
send_command(c, 0x6C, 0x00, map_commands_by_language[lang_index]);
};
for (const auto& c : l->clients) {
send_to_client(c);
@@ -2586,7 +2587,7 @@ void Server::send_6xB6x41_to_all_clients() const {
}
} else {
auto out_data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, 1, false);
auto out_data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, Language::ENGLISH, false);
this->send(out_data.data(), out_data.size(), 0x6C, false);
}
}
+1 -1
View File
@@ -265,7 +265,7 @@ public:
G_UpdateDecks_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const;
G_SetPlayerNames_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const;
static std::string prepare_6xB6x41_map_definition(std::shared_ptr<const MapIndex::Map> map, uint8_t language, bool is_nte);
static std::string prepare_6xB6x41_map_definition(std::shared_ptr<const MapIndex::Map> map, Language language, bool is_nte);
void send_6xB6x41_to_all_clients() const;
G_SetTrapTileLocations_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const;
+1 -1
View File
@@ -737,7 +737,7 @@ string Tournament::bracket_str() const {
}
};
auto en_vm = this->map->version(1);
auto en_vm = this->map->version(Language::ENGLISH);
if (en_vm) {
string map_name = en_vm->map->name.decode(en_vm->language);
ret += std::format(" Map: {:08X} ({})\n", this->map->map_number, map_name);