From 159f80cce3071e35ad4c629f2f284f5dc50d3d6d Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 4 Mar 2023 11:41:37 -0800 Subject: [PATCH] make episode an enum class --- src/ChatCommands.cc | 47 +++++--------- src/Items.cc | 8 +-- src/Items.hh | 3 +- src/Lobby.cc | 6 +- src/Lobby.hh | 29 +++++---- src/Map.cc | 59 ++++++++++++----- src/Map.hh | 9 +-- src/Quest.cc | 51 +++++++-------- src/Quest.hh | 3 +- src/RareItemSet.cc | 23 +++++-- src/RareItemSet.hh | 4 +- src/ReceiveCommands.cc | 131 ++++++++++++++++++++++---------------- src/ReceiveCommands.hh | 2 +- src/ReceiveSubcommands.cc | 21 +++--- src/SendCommands.cc | 51 ++++++++++++--- src/ServerState.cc | 8 ++- src/StaticGameData.cc | 54 ++++++++++++++++ src/StaticGameData.hh | 15 +++++ 18 files changed, 341 insertions(+), 183 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 2d85f63e..14aa5670 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -106,7 +106,7 @@ static void server_command_lobby_info(shared_ptr, shared_ptr if (l->is_game()) { lines.emplace_back(string_printf("Game ID: $C6%08X$C7", l->lobby_id)); - if (!(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (!l->is_ep3()) { if (l->max_level == 0xFFFFFFFF) { lines.emplace_back(string_printf("Levels: $C6%d+$C7", l->min_level + 1)); } else { @@ -244,7 +244,7 @@ static void server_command_debug(shared_ptr, shared_ptr, static void server_command_auction(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_privileges(c, Privilege::DEBUG); - if (l->is_game() && (l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (l->is_game() && l->is_ep3()) { G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd; send_command_t(l, 0xC9, 0x00, cmd); } @@ -469,7 +469,7 @@ static void server_command_lobby_type(shared_ptr, shared_ptr } l->type = new_type; - if (l->type < ((l->flags & Lobby::Flag::EPISODE_3_ONLY) ? 20 : 15)) { + if (l->type < (l->is_ep3() ? 20 : 15)) { l->type = l->block - 1; } @@ -511,7 +511,7 @@ static void server_command_playrec(shared_ptr s, shared_ptr if (l->battle_player) { l->battle_player->start(); } else { - uint32_t flags = Lobby::Flag::NON_V1_ONLY | Lobby::Flag::EPISODE_3_ONLY | Lobby::Flag::IS_SPECTATOR_TEAM; + uint32_t flags = Lobby::Flag::NON_V1_ONLY | Lobby::Flag::IS_SPECTATOR_TEAM; string filename = encode_sjis(args); if (filename[0] == '!') { flags |= Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY; @@ -521,7 +521,7 @@ static void server_command_playrec(shared_ptr s, shared_ptr shared_ptr record(new Episode3::BattleRecord(data)); shared_ptr battle_player( new Episode3::BattleRecordPlayer(record, s->game_server->get_base())); - create_game_generic(s, c, args.c_str(), u"", 0xFF, 0, flags, nullptr, battle_player); + create_game_generic(s, c, args.c_str(), u"", Episode::EP3, 0, flags, nullptr, battle_player); } } @@ -608,7 +608,7 @@ static void server_command_spec(shared_ptr, shared_ptr l, check_is_game(l, true); check_is_leader(l, c); check_is_ep3(c, true); - if (!(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (!l->is_ep3()) { throw logic_error("Episode 3 client in non-Episode 3 game"); } @@ -909,23 +909,15 @@ static void server_command_warp(shared_ptr, shared_ptr l, check_cheats_enabled(l); uint32_t area = stoul(encode_sjis(args), nullptr, 0); - if (!l->episode || (l->episode > 3)) { - return; - } if (c->area == area) { return; } - if ((l->episode == 1) && (area > 17)) { - send_text_message(c, u"$C6Area numbers must be\n17 or less."); + size_t limit = area_limit_for_episode(l->episode); + if (limit == 0) { return; - } - if ((l->episode == 2) && (area > 17)) { - send_text_message(c, u"$C6Area numbers must be\n17 or less."); - return; - } - if ((l->episode == 3) && (area > 10)) { - send_text_message(c, u"$C6Area numbers must be\n10 or less."); + } else if (area > limit) { + send_text_message_printf(c, "$C6Area numbers must\nbe %zu or less.", limit); return; } @@ -939,6 +931,7 @@ static void proxy_command_warp(shared_ptr, return; } uint32_t area = stoul(encode_sjis(args), nullptr, 0); + // TODO: Add limit check here like in the server command implementation send_warp(session.client_channel, session.lobby_client_id, area); session.area = area; } @@ -948,18 +941,11 @@ static void server_command_next(shared_ptr, shared_ptr l, check_is_game(l, true); check_cheats_enabled(l); - if (!l->episode || (l->episode > 3)) { - throw runtime_error("invalid episode number"); + size_t limit = area_limit_for_episode(l->episode); + if (limit == 0) { + return; } - - uint8_t new_area = c->area + 1; - if (((l->episode == 1) && (new_area > 17)) || - ((l->episode == 2) && (new_area > 17)) || - ((l->episode == 3) && (new_area > 10))) { - new_area = 0; - } - - send_warp(c, new_area); + send_warp(c, (c->area + 1) % limit); } static void proxy_command_next(shared_ptr, @@ -976,7 +962,8 @@ static void proxy_command_next(shared_ptr, static void server_command_what(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); - if (!l->episode || (l->episode > 3)) { + + if (!episode_has_arpg_semantics(l->episode)) { return; } if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { diff --git a/src/Items.cc b/src/Items.cc index 04e87f2f..9ab7549d 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -298,12 +298,12 @@ int32_t CommonItemCreator::decide_item_type(bool is_box) const { return -1; } -ItemData CommonItemCreator::create_drop_item(bool is_box, uint8_t episode, +ItemData CommonItemCreator::create_drop_item(bool is_box, Episode episode, uint8_t difficulty, uint8_t area, uint8_t) const { // TODO: use the section ID (last argument) to vary drop frequencies appropriately // change the area if it's invalid (data for the bosses are actually in other areas) if (area > 10) { - if (episode == 1) { + if (episode == Episode::EP1) { if (area == 11) { area = 3; // dragon } else if (area == 12) { @@ -315,7 +315,7 @@ ItemData CommonItemCreator::create_drop_item(bool is_box, uint8_t episode, } else { area = 1; // unknown area -> forest 1 } - } else if (episode == 2) { + } else if (episode == Episode::EP2) { if (area == 12) { area = 9; // gal gryphon } else if (area == 13) { @@ -327,7 +327,7 @@ ItemData CommonItemCreator::create_drop_item(bool is_box, uint8_t episode, } else { area = 10; // tower } - } else if (episode == 3) { + } else if (episode == Episode::EP4) { area = 1; } } diff --git a/src/Items.hh b/src/Items.hh index a1dc749d..19418fe9 100644 --- a/src/Items.hh +++ b/src/Items.hh @@ -6,6 +6,7 @@ #include #include "Client.hh" +#include "StaticGameData.hh" @@ -31,7 +32,7 @@ struct CommonItemCreator { std::shared_ptr random); int32_t decide_item_type(bool is_box) const; - ItemData create_drop_item(bool is_box, uint8_t episode, uint8_t difficulty, + ItemData create_drop_item(bool is_box, Episode episode, uint8_t difficulty, uint8_t area, uint8_t section_id) const; ItemData create_shop_item(uint8_t difficulty, uint8_t shop_type) const; }; diff --git a/src/Lobby.cc b/src/Lobby.cc index 88315ee8..f68774af 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -20,7 +20,7 @@ Lobby::Lobby(uint32_t id) next_game_item_id(0x00810000), version(GameVersion::GC), section_id(0), - episode(1), + episode(Episode::NONE), difficulty(0), random_seed(random_object()), random(new mt19937(this->random_seed)), @@ -140,7 +140,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { } // Send spectator count notifications if needed - if (this->is_game() && (this->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (this->is_game() && this->is_ep3()) { if (this->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { auto watched_l = this->watched_lobby.lock(); if (watched_l) { @@ -178,7 +178,7 @@ void Lobby::remove_client(shared_ptr c) { } // If the lobby is Episode 3, update the appropriate spectator counts - if (this->is_game() && (this->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (this->is_game() && this->is_ep3()) { if (this->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { auto watched_l = this->watched_lobby.lock(); if (watched_l) { diff --git a/src/Lobby.hh b/src/Lobby.hh index e38bc6c0..de2cb3f1 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -11,21 +11,23 @@ #include #include "Client.hh" -#include "Player.hh" -#include "Map.hh" -#include "RareItemSet.hh" -#include "Text.hh" -#include "Quest.hh" -#include "Items.hh" #include "Episode3/BattleRecord.hh" #include "Episode3/Server.hh" +#include "Items.hh" +#include "Map.hh" +#include "Player.hh" +#include "Quest.hh" +#include "RareItemSet.hh" +#include "StaticGameData.hh" +#include "Text.hh" + + struct Lobby : public std::enable_shared_from_this { enum Flag { GAME = 0x00000001, - EPISODE_3_ONLY = 0x00000002, - NON_V1_ONLY = 0x00000004, // DC NTE and DCv1 not allowed - PERSISTENT = 0x00000008, + NON_V1_ONLY = 0x00000002, // DC NTE and DCv1 not allowed + PERSISTENT = 0x00000004, // Flags used only for games CHEATS_ENABLED = 0x00000100, @@ -33,7 +35,7 @@ struct Lobby : public std::enable_shared_from_this { BATTLE_IN_PROGRESS = 0x00000400, JOINABLE_QUEST_IN_PROGRESS = 0x00000800, ITEM_TRACKING_ENABLED = 0x00001000, - IS_SPECTATOR_TEAM = 0x00002000, // EPISODE_3_ONLY must also be set + IS_SPECTATOR_TEAM = 0x00002000, // episode must be EP3 also SPECTATORS_FORBIDDEN = 0x00004000, BATTLE_MODE = 0x00008000, CHALLENGE_MODE = 0x00010000, @@ -68,8 +70,8 @@ struct Lobby : public std::enable_shared_from_this { // Game config GameVersion version; uint8_t section_id; - uint8_t episode; // 1 = Ep1, 2 = Ep2, 3 = Ep4, 0xFF = Ep3 - uint8_t difficulty; + Episode episode; + uint8_t difficulty; // 0-3 std::u16string password; std::u16string name; // This seed is also sent to the client for rare enemy generation @@ -113,6 +115,9 @@ struct Lobby : public std::enable_shared_from_this { inline bool is_game() const { return this->flags & Flag::GAME; } + inline bool is_ep3() const { + return this->episode == Episode::EP3; + } void reassign_leader_on_client_departure(size_t leaving_client_id); size_t count_clients() const; diff --git a/src/Map.cc b/src/Map.cc index fc1e3654..df82412d 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -86,17 +86,30 @@ BattleParamsIndex::BattleParamsIndex( } const BattleParamsIndex::Entry& BattleParamsIndex::get( - bool solo, uint8_t episode, uint8_t difficulty, uint8_t monster_type) const { - if (episode > 3) { - throw invalid_argument("incorrect episode"); - } + bool solo, Episode episode, uint8_t difficulty, uint8_t monster_type) const { if (difficulty > 4) { throw invalid_argument("incorrect difficulty"); } if (monster_type > 0x60) { throw invalid_argument("incorrect monster type"); } - return this->files[!!solo][episode].table->difficulty[difficulty][monster_type]; + + uint8_t ep_index; + switch (episode) { + case Episode::EP1: + ep_index = 0; + break; + case Episode::EP2: + ep_index = 1; + break; + case Episode::EP4: + ep_index = 2; + break; + default: + throw invalid_argument("invalid episode"); + } + + return this->files[!!solo][ep_index].table->difficulty[difficulty][monster_type]; } @@ -143,7 +156,7 @@ static uint64_t next_enemy_id = 1; vector parse_map( shared_ptr battle_params, bool is_solo, - uint8_t episode, + Episode episode, uint8_t difficulty, shared_ptr data, bool alt_enemies) { @@ -185,7 +198,7 @@ vector parse_map( create_enemy(e, 0x49 + (e.skin & 0x01), 0x01 + (e.skin & 0x01), "Hilde(bear|torr)"); break; case 0x41: // Rappies - if (episode == 3) { // Del Rappy and Sand Rappy + if (episode == Episode::EP4) { // Del Rappy and Sand Rappy if (alt_enemies) { create_enemy(e, 0x17 + (e.skin & 0x01), 17 + (e.skin & 0x01), "(Del|Sand) Rappy"); } else { @@ -217,7 +230,7 @@ vector parse_map( create_enemy(e, 0x4E, 12, "Grass Assassin"); break; case 0x61: // Del Lily, Poison Lily, Nar Lily - if ((episode == 2) && (alt_enemies)) { + if ((episode == Episode::EP2) && (alt_enemies)) { create_enemy(e, 0x25, 83, "Del Lily"); } else { create_enemy(e, 0x04 + ((e.reserved[10] & 0x800000) ? 1 : 0), @@ -303,9 +316,9 @@ vector parse_map( create_enemy(e, 0x20, 38, "Claw"); break; case 0xC0: // Dragon or Gal Gryphon - if (episode == 1) { + if (episode == Episode::EP1) { create_enemy(e, 0x12, 44, "Dragon"); - } else if (episode == 2) { + } else if (episode == Episode::EP2) { create_enemy(e, 0x1E, 77, "Gal Gryphon"); } break; @@ -386,7 +399,7 @@ vector parse_map( } break; case 0xE0: // Epsilon, Sinow Zoa and Zele - if ((episode == 2) && (alt_enemies)) { + if ((episode == Episode::EP2) && (alt_enemies)) { create_enemy(e, 0x23, 84, "Epsilon"); create_clones(4); } else { @@ -524,8 +537,7 @@ struct AreaMapFileIndex { variation2_values(variation2_values) { } }; -// These are indexed as [episode][is_solo][area] -// (Note that Lobby::episode is 1-3, so we actually use episode - 1) +// These are indexed as [episode][is_solo][area], where episode is 0-2 static const vector>> map_file_info = { { // Episode 1 { // Non-solo @@ -643,12 +655,25 @@ static const vector>> map_file_info = { }, }; +const vector>& map_file_info_for_episode(Episode ep) { + switch (ep) { + case Episode::EP1: + return map_file_info.at(0); + case Episode::EP2: + return map_file_info.at(1); + case Episode::EP4: + return map_file_info.at(2); + default: + throw invalid_argument("episode has no maps"); + } +} + void generate_variations( parray& variations, shared_ptr random, - uint8_t episode, + Episode episode, bool is_solo) { - const auto& ep_index = map_file_info.at(episode - 1); + const auto& ep_index = map_file_info_for_episode(episode); for (size_t z = 0; z < 0x10; z++) { const AreaMapFileIndex* a = nullptr; if (is_solo) { @@ -670,7 +695,7 @@ void generate_variations( } vector map_filenames_for_variation( - uint8_t episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2) { + Episode episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2) { // Map filenames are like map_[_VV][_VV][_off][_s].dat // name_token comes from AreaMapFileIndex // _VV are the values from the variation<1|2>_values vector (in contrast to @@ -678,7 +703,7 @@ vector map_filenames_for_variation( // _off or _s are used for solo mode (try both - city uses _s whereas levels // use _off apparently) // e|o specifies what kind of data: e = enemies, o = objects - const auto& ep_index = map_file_info.at(episode - 1); + const auto& ep_index = map_file_info_for_episode(episode); const AreaMapFileIndex* a = nullptr; if (is_solo) { a = &ep_index.at(true).at(area); diff --git a/src/Map.hh b/src/Map.hh index 43804f45..8dcfbeb9 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -8,6 +8,7 @@ #include #include +#include "StaticGameData.hh" #include "Text.hh" @@ -44,7 +45,7 @@ public: std::shared_ptr data_off_ep2, // BattleParamEntry_lab.dat std::shared_ptr data_off_ep4); // BattleParamEntry_ep4.dat - const Entry& get(bool solo, uint8_t episode, uint8_t difficulty, + const Entry& get(bool solo, Episode episode, uint8_t difficulty, uint8_t monster_type) const; private: @@ -84,7 +85,7 @@ struct PSOEnemy { std::vector parse_map( std::shared_ptr battle_params, bool is_solo, - uint8_t episode, + Episode episode, uint8_t difficulty, std::shared_ptr data, bool alt_enemies); @@ -125,8 +126,8 @@ private: void generate_variations( parray& variations, std::shared_ptr random, - uint8_t episode, + Episode episode, bool is_solo); std::vector map_filenames_for_variation( - uint8_t episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2); + Episode episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2); void load_map_files(); diff --git a/src/Quest.cc b/src/Quest.cc index 0e688f8e..adee40f7 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -299,11 +299,11 @@ const char* name_for_category(QuestCategory category) { case QuestCategory::TOWER: return "Tower"; case QuestCategory::GOVERNMENT_EPISODE_1: - return "GovernmentEpisode1"; + return "GovernmentEp1"; case QuestCategory::GOVERNMENT_EPISODE_2: - return "GovernmentEpisode2"; + return "GovernmentEp2"; case QuestCategory::GOVERNMENT_EPISODE_4: - return "GovernmentEpisode4"; + return "GovernmentEp4"; case QuestCategory::DOWNLOAD: return "Download"; case QuestCategory::BATTLE: @@ -319,21 +319,6 @@ const char* name_for_category(QuestCategory category) { } } -static const char* name_for_episode(uint8_t episode) { - switch (episode) { - case 0: - return "Ep1"; - case 1: - return "Ep2"; - case 2: - return "Ep4"; - case 0xFF: - return "Ep3"; - default: - return "InvalidEpisode"; - } -} - struct PSOQuestHeaderDC { // Same format for DC v1 and v2, thankfully @@ -400,7 +385,7 @@ Quest::Quest(const string& bin_filename) : internal_id(-1), menu_item_id(0), category(QuestCategory::UNKNOWN), - episode(0), + episode(Episode::NONE), is_dcv1(false), joinable(false), file_format(FileFormat::BIN_DAT), @@ -523,7 +508,7 @@ Quest::Quest(const string& bin_filename) } auto* header = reinterpret_cast(bin_decompressed.data()); this->joinable = false; - this->episode = 0; + this->episode = Episode::EP1; this->name = decode_sjis(header->name); this->short_description = decode_sjis(header->short_description); this->long_description = decode_sjis(header->long_description); @@ -537,7 +522,7 @@ Quest::Quest(const string& bin_filename) } auto* header = reinterpret_cast(bin_decompressed.data()); this->joinable = false; - this->episode = 0; + this->episode = Episode::EP1; this->name = header->name; this->short_description = header->short_description; this->long_description = header->long_description; @@ -552,7 +537,7 @@ Quest::Quest(const string& bin_filename) } auto* header = reinterpret_cast(bin_decompressed.data()); this->joinable = false; - this->episode = 0xFF; + this->episode = Episode::EP3; this->name = decode_sjis(header->name); this->short_description = decode_sjis(header->quest_name); this->long_description = decode_sjis(header->description); @@ -562,7 +547,7 @@ Quest::Quest(const string& bin_filename) } auto* header = reinterpret_cast(bin_decompressed.data()); this->joinable = false; - this->episode = (header->episode == 1); + this->episode = (header->episode == 1) ? Episode::EP2 : Episode::EP1; this->name = decode_sjis(header->name); this->short_description = decode_sjis(header->short_description); this->long_description = decode_sjis(header->long_description); @@ -576,16 +561,28 @@ Quest::Quest(const string& bin_filename) } auto* header = reinterpret_cast(bin_decompressed.data()); this->joinable = header->joinable_in_progress; - this->episode = header->episode; + switch (header->episode) { + case 0: + this->episode = Episode::EP1; + break; + case 1: + this->episode = Episode::EP2; + break; + case 2: + this->episode = Episode::EP4; + break; + default: + throw runtime_error("invalid episode number"); + } this->name = header->name; this->short_description = header->short_description; this->long_description = header->long_description; if (this->category == QuestCategory::GOVERNMENT_EPISODE_1) { - if (this->episode == 1) { + if (this->episode == Episode::EP2) { this->category = QuestCategory::GOVERNMENT_EPISODE_2; - } else if (this->episode == 2) { + } else if (this->episode == Episode::EP4) { this->category = QuestCategory::GOVERNMENT_EPISODE_4; - } else if (this->episode != 0) { + } else if (this->episode != Episode::EP1) { throw invalid_argument("government quest has invalid episode number"); } } diff --git a/src/Quest.hh b/src/Quest.hh index aef50ace..ed9c82ea 100644 --- a/src/Quest.hh +++ b/src/Quest.hh @@ -7,6 +7,7 @@ #include #include +#include "StaticGameData.hh" #include "Version.hh" @@ -47,7 +48,7 @@ public: int64_t internal_id; uint32_t menu_item_id; QuestCategory category; - uint8_t episode; // 0 = ep1, 1 = ep2, 2 = ep4, 0xFF = ep3 + Episode episode; bool is_dcv1; bool joinable; GameVersion version; diff --git a/src/RareItemSet.cc b/src/RareItemSet.cc index 81302787..74f9628c 100644 --- a/src/RareItemSet.cc +++ b/src/RareItemSet.cc @@ -16,17 +16,30 @@ RareItemSet::RareItemSet(shared_ptr data) : data(data) { } const RareItemSet::Table& RareItemSet::get_table( - uint8_t episode, uint8_t difficulty, uint8_t secid) const { - if (episode > 2) { - throw logic_error("incorrect episode number"); - } + Episode episode, uint8_t difficulty, uint8_t secid) const { if (difficulty > 3) { throw logic_error("incorrect difficulty"); } if (secid > 10) { throw logic_error("incorrect section id"); } - return this->tables[(episode * 10 * 4) + (difficulty * 10) + secid]; + + size_t ep_index; + switch (episode) { + case Episode::EP1: + ep_index = 0; + break; + case Episode::EP2: + ep_index = 1; + break; + case Episode::EP4: + ep_index = 2; + break; + default: + throw invalid_argument("incorrect episode"); + } + + return this->tables[(ep_index * 10 * 4) + (difficulty * 10) + secid]; } bool RareItemSet::sample(mt19937& random, uint8_t pc) { diff --git a/src/RareItemSet.hh b/src/RareItemSet.hh index bd79fafb..3342b8fb 100644 --- a/src/RareItemSet.hh +++ b/src/RareItemSet.hh @@ -6,6 +6,8 @@ #include #include +#include "StaticGameData.hh" + class RareItemSet { @@ -28,7 +30,7 @@ public: RareItemSet(std::shared_ptr data); - const Table& get_table(uint8_t episode, uint8_t difficulty, uint8_t secid) const; + const Table& get_table(Episode episode, uint8_t difficulty, uint8_t secid) const; static bool sample(std::mt19937& rand, uint8_t probability); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 83341ce8..e4b8bf15 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1079,10 +1079,10 @@ static bool start_ep3_battle_table_game_if_ready( // begin, but create_game_generic can still return null if an internal // precondition fails (though this should never happen for Episode 3 games). - uint32_t flags = Lobby::Flag::NON_V1_ONLY | Lobby::Flag::EPISODE_3_ONLY; + uint32_t flags = Lobby::Flag::NON_V1_ONLY; u16string name = tourn ? decode_sjis(tourn->get_name()) : u""; auto game = create_game_generic( - s, game_clients.begin()->second, name, u"", 0xFF, 0, flags); + s, game_clients.begin()->second, name, u"", Episode::EP3, 0, flags); if (!game) { return false; } @@ -1144,7 +1144,7 @@ static void on_E4_Ep3(shared_ptr s, } if (flag) { - if (l->is_game() || !(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (l->is_game() || !l->is_ep3()) { throw runtime_error("battle table join command sent in non-CARD lobby"); } c->card_battle_table_number = cmd.table_number; @@ -1180,7 +1180,7 @@ static void on_E5_Ep3(shared_ptr s, shared_ptr c, uint16_t, uint32_t flag, const string& data) { check_size_t(data); auto l = s->find_lobby(c->lobby_id); - if (l->is_game() || !(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (l->is_game() || !l->is_ep3()) { throw runtime_error("battle table command sent in non-CARD lobby"); } @@ -1271,7 +1271,7 @@ static void on_CA_Ep3(shared_ptr s, shared_ptr c, // command when it's not in any lobby at all. We just ignore such commands. return; } - if (!(l->flags & Lobby::Flag::EPISODE_3_ONLY) || !l->is_game()) { + if (!l->is_game() || !l->is_ep3()) { throw runtime_error("Episode 3 server data request sent outside of Episode 3 game"); } @@ -1420,7 +1420,7 @@ static void on_09(shared_ptr s, shared_ptr c, if (l->is_game()) { num_games++; if (l->version == c->version() && - (!(l->flags & Lobby::Flag::EPISODE_3_ONLY) == !(c->flags & Client::Flag::IS_EPISODE_3))) { + (!l->is_ep3() == !(c->flags & Client::Flag::IS_EPISODE_3))) { num_compatible_games++; } } @@ -1514,8 +1514,7 @@ static void on_09(shared_ptr s, shared_ptr c, if (!game->is_game()) { send_ship_info(c, u"$C4Incorrect game ID"); - } else if ((c->flags & Client::Flag::IS_EPISODE_3) && - (game->flags & Lobby::Flag::EPISODE_3_ONLY)) { + } else if ((c->flags & Client::Flag::IS_EPISODE_3) && game->is_ep3()) { send_ep3_game_details(c, game); } else { @@ -1525,7 +1524,7 @@ static void on_09(shared_ptr s, shared_ptr c, if (game_c.get()) { auto player = game_c->game_data.player(); auto name = encode_sjis(player->disp.name); - if (game->flags & Lobby::Flag::EPISODE_3_ONLY) { + if (game->is_ep3()) { info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n", x + 1, name.c_str(), player->disp.level + 1); } else { @@ -1537,12 +1536,6 @@ static void on_09(shared_ptr s, shared_ptr c, } } - int episode = game->episode; - if (episode == 3) { - episode = 4; - } else if (episode == 0xFF) { - episode = 3; - } string secid_str = name_for_section_id(game->section_id); const char* mode_abbrev = "Nml"; if (game->flags & Lobby::Flag::BATTLE_MODE) { @@ -1552,8 +1545,8 @@ static void on_09(shared_ptr s, shared_ptr c, } else if (game->flags & Lobby::Flag::SOLO_MODE) { mode_abbrev = "Solo"; } - info += string_printf("Ep%d %c %s %s\n", - episode, + info += string_printf("%s %c %s %s\n", + abbreviation_for_episode(game->episode), abbreviation_for_difficulty(game->difficulty), mode_abbrev, secid_str.c_str()); @@ -1904,7 +1897,7 @@ static void on_10(shared_ptr s, shared_ptr c, break; } if ((game->version != c->version()) || - (!(game->flags & Lobby::Flag::EPISODE_3_ONLY) != !(c->flags & Client::Flag::IS_EPISODE_3)) || + (!game->is_ep3() != !(c->flags & Client::Flag::IS_EPISODE_3)) || ((game->flags & Lobby::Flag::NON_V1_ONLY) && (c->flags & Client::Flag::IS_DC_V1))) { send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO."); break; @@ -1986,7 +1979,7 @@ static void on_10(shared_ptr s, shared_ptr c, } } - bool is_ep3 = (q->episode == 0xFF); + bool is_ep3 = (q->episode == Episode::EP3); string bin_basename = q->bin_filename(); shared_ptr bin_contents = q->bin_contents(); string dat_basename; @@ -2178,7 +2171,7 @@ static void on_84(shared_ptr s, shared_ptr c, return; } - if ((new_lobby->flags & Lobby::Flag::EPISODE_3_ONLY) && !(c->flags & Client::Flag::IS_EPISODE_3)) { + if (new_lobby->is_ep3() && !(c->flags & Client::Flag::IS_EPISODE_3)) { send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby is for\nEpisode 3 only."); return; } @@ -2573,7 +2566,7 @@ static void on_chat_generic(shared_ptr s, shared_ptr c, char private_flags = 0; u16string processed_text; - if ((text[0] != '\t') && (l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if ((text[0] != '\t') && l->is_ep3()) { private_flags = text[0]; processed_text = remove_language_marker(text.substr(1)); } else { @@ -3111,24 +3104,16 @@ shared_ptr create_game_generic( shared_ptr c, const std::u16string& name, const std::u16string& password, - uint8_t episode, + Episode episode, uint8_t difficulty, uint32_t flags, shared_ptr watched_lobby, shared_ptr battle_player) { - // A player's actual level is their displayed level - 1, so the minimums for - // Episode 1 (for example) are actually 1, 20, 40, 80. - static const uint32_t default_minimum_levels[3][4] = { - {0, 19, 39, 79}, // Episode 1 - {0, 29, 49, 89}, // Episode 2 - {0, 39, 79, 109}}; // Episode 4 - - bool is_ep3 = (flags & Lobby::Flag::EPISODE_3_ONLY); - if (episode == 0) { - episode = 0xFF; - } - if (((episode != 0xFF) && (episode > 3)) || (episode == 0)) { + if ((episode != Episode::EP1) && + (episode != Episode::EP2) && + (episode != Episode::EP3) && + (episode != Episode::EP4)) { throw invalid_argument("incorrect episode number"); } @@ -3141,7 +3126,32 @@ shared_ptr create_game_generic( throw invalid_argument("cannot make a game from outside any lobby"); } - uint8_t min_level = ((episode == 0xFF) ? 0 : default_minimum_levels[episode - 1][difficulty]); + uint8_t min_level; + // A player's actual level is their displayed level - 1, so the minimums for + // Episode 1 (for example) are actually 1, 20, 40, 80. + switch (episode) { + case Episode::EP1: { + static const uint32_t min_levels[4] = {0, 19, 39, 79}; + min_level = min_levels[difficulty]; + break; + } + case Episode::EP2: { + static const uint32_t min_levels[4] = {0, 29, 49, 89}; + min_level = min_levels[difficulty]; + break; + } + case Episode::EP3: + min_level = 0; + break; + case Episode::EP4: { + static const uint32_t min_levels[4] = {0, 39, 79, 109}; + min_level = min_levels[difficulty]; + break; + } + default: + throw runtime_error("invalid episode"); + } + if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES) && (min_level > c->game_data.player()->disp.level)) { // Note: We don't throw here because this is a situation players might @@ -3163,7 +3173,6 @@ shared_ptr create_game_generic( game->name = name; game->flags = flags | Lobby::Flag::GAME | - (is_ep3 ? Lobby::Flag::EPISODE_3_ONLY : 0) | (item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0); game->password = password; game->version = c->version(); @@ -3194,7 +3203,7 @@ shared_ptr create_game_generic( bool is_solo = (game->flags & Lobby::Flag::SOLO_MODE); // Generate the map variations - if (is_ep3) { + if (game->is_ep3()) { game->variations.clear(0); } else { generate_variations(game->variations, game->random, game->episode, is_solo); @@ -3268,7 +3277,8 @@ static void on_C1_PC(shared_ptr s, shared_ptr c, if (cmd.challenge_mode) { flags |= Lobby::Flag::CHALLENGE_MODE; } - auto game = create_game_generic(s, c, cmd.name, cmd.password, 1, cmd.difficulty, flags); + auto game = create_game_generic( + s, c, cmd.name, cmd.password, Episode::EP1, cmd.difficulty, flags); if (game) { s->change_client_lobby(c, game); c->flags |= Client::Flag::LOADING; @@ -3282,21 +3292,22 @@ static void on_0C_C1_E7_EC(shared_ptr s, shared_ptr c, // Only allow E7/EC from Ep3 clients bool client_is_ep3 = !!(c->flags & Client::Flag::IS_EPISODE_3); if (((command & 0xF0) == 0xE0) != client_is_ep3) { - return; + throw runtime_error("invalid command"); } - uint8_t episode = cmd.episode; + Episode episode = Episode::NONE; uint32_t flags = 0; if (c->version() == GameVersion::DC) { - if (episode) { + if (cmd.episode) { flags |= Lobby::Flag::NON_V1_ONLY; } - episode = 1; + episode = Episode::EP1; } else if (client_is_ep3) { - flags |= (Lobby::Flag::NON_V1_ONLY | Lobby::Flag::EPISODE_3_ONLY); - episode = 0xFF; + flags |= Lobby::Flag::NON_V1_ONLY; + episode = Episode::EP3; } else { // XB/GC non-Ep3 flags |= Lobby::Flag::NON_V1_ONLY; + episode = cmd.episode == 2 ? Episode::EP2 : Episode::EP1; } u16string name = decode_sjis(cmd.name); @@ -3348,8 +3359,24 @@ static void on_C1_BB(shared_ptr s, shared_ptr c, if (cmd.solo_mode) { flags |= Lobby::Flag::SOLO_MODE; } + + Episode episode; + switch (cmd.episode) { + case 1: + episode = Episode::EP1; + break; + case 2: + episode = Episode::EP2; + break; + case 3: + episode = Episode::EP4; + break; + default: + throw runtime_error("invalid episode number"); + } + auto game = create_game_generic( - s, c, cmd.name, cmd.password, cmd.episode, cmd.difficulty, flags); + s, c, cmd.name, cmd.password, episode, cmd.difficulty, flags); if (game) { s->change_client_lobby(c, game); c->flags |= Client::Flag::LOADING; @@ -3518,11 +3545,8 @@ static void on_EE_Ep3(shared_ptr s, shared_ptr c, throw runtime_error("non-Ep3 client sent card trade command"); } auto l = s->find_lobby(c->lobby_id); - if (!(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { - throw runtime_error("client sent card trade command outside of Ep3 lobby"); - } - if (!l->is_game()) { - throw runtime_error("client sent card trade command in non-game lobby"); + if (!l->is_game() || !l->is_ep3()) { + throw runtime_error("client sent card trade command outside of Ep3 game"); } if (flag == 0xD0) { @@ -3626,11 +3650,8 @@ static void on_EF_Ep3(shared_ptr s, shared_ptr c, throw runtime_error("non-Ep3 client sent card auction join command"); } auto l = s->find_lobby(c->lobby_id); - if (!(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { - throw runtime_error("client sent card auction join command outside of Ep3 lobby"); - } - if (!l->is_game()) { - throw runtime_error("client sent card auction join command in non-game lobby"); + if (!l->is_game() || !l->is_ep3()) { + throw runtime_error("client sent card auction join command outside of Ep3 game"); } if (c->flags & Client::Flag::AWAITING_CARD_AUCTION) { diff --git a/src/ReceiveCommands.hh b/src/ReceiveCommands.hh index e70ff77f..b7ed0755 100644 --- a/src/ReceiveCommands.hh +++ b/src/ReceiveCommands.hh @@ -11,7 +11,7 @@ std::shared_ptr create_game_generic( std::shared_ptr c, const std::u16string& name, const std::u16string& password, - uint8_t episode, + Episode episode, uint8_t difficulty, uint32_t flags, std::shared_ptr watched_lobby = nullptr, diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 665efe45..a6b727bd 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -213,7 +213,7 @@ static void on_forward_check_size_ep3_lobby(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { check_size_sc(data, sizeof(G_UnusedHeader), 0xFFFF); - if (l->is_game() || !(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (l->is_game() || !l->is_ep3()) { return; } forward_subcommand(l, c, command, flag, data); @@ -223,7 +223,7 @@ static void on_forward_check_size_ep3_game(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { check_size_sc(data, sizeof(G_UnusedHeader), 0xFFFF); - if (!l->is_game() || !(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (!l->is_game() || !l->is_ep3()) { return; } forward_subcommand(l, c, command, flag, data); @@ -239,7 +239,7 @@ static void on_ep3_battle_subs(shared_ptr s, const string& orig_data) { const auto& header = check_size_sc( orig_data, sizeof(G_CardBattleCommandHeader), 0xFFFF); - if (!l->is_game() || !(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (!l->is_game() || !l->is_ep3()) { return; } @@ -822,7 +822,7 @@ static void on_use_item(shared_ptr, static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { - if (l->flags & Lobby::Flag::EPISODE_3_ONLY) { + if (l->is_ep3()) { on_ep3_battle_subs(s, l, c, command, flag, data); } else if (!l->common_item_creator.get()) { @@ -858,7 +858,7 @@ static void on_open_bank_bb_or_card_trade_counter_ep3(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { if ((l->version == GameVersion::BB) && l->is_game()) { send_bank(c); - } else if (l->version == GameVersion::GC && l->flags & Lobby::Flag::EPISODE_3_ONLY) { + } else if ((l->version == GameVersion::GC) && l->is_ep3()) { forward_subcommand(l, c, command, flag, data); } } @@ -965,7 +965,7 @@ static bool drop_item( const RareItemSet::Table::Drop* drop = nullptr; if (s->rare_item_set) { const auto& table = s->rare_item_set->get_table( - l->episode - 1, l->difficulty, l->section_id); + l->episode, l->difficulty, l->section_id); if (enemy_id < 0) { for (size_t z = 0; z < 30; z++) { if (table.box_areas[z] != area) { @@ -1060,8 +1060,9 @@ static void on_phase_setup(shared_ptr, forward_subcommand(l, c, command, flag, data); bool should_send_boss_drop_req = false; + bool is_ep2 = (l->episode == Episode::EP2); if (cmd.difficulty == l->difficulty) { - if ((l->episode == 1) && (c->area == 0x0E)) { + if ((l->episode == Episode::EP1) && (c->area == 0x0E)) { // On Normal, Dark Falz does not have a third phase, so send the drop // request after the end of the second phase. On all other difficulty // levels, send it after the third phase. @@ -1069,7 +1070,7 @@ static void on_phase_setup(shared_ptr, ((l->difficulty != 0) && (cmd.basic_cmd.phase == 0x00000037))) { should_send_boss_drop_req = true; } - } else if ((l->episode == 2) && (cmd.basic_cmd.phase == 0x00000057) && (c->area == 0x0D)) { + } else if (is_ep2 && (cmd.basic_cmd.phase == 0x00000057) && (c->area == 0x0D)) { should_send_boss_drop_req = true; } } @@ -1081,9 +1082,9 @@ static void on_phase_setup(shared_ptr, { {0x60, 0x06, 0x0000}, static_cast(c->area), - static_cast((l->episode == 2) ? 0x4E : 0x2F), + static_cast(is_ep2 ? 0x4E : 0x2F), 0x0B4F, - (l->episode == 2) ? -9999.0f : 10160.58984375f, + is_ep2 ? -9999.0f : 10160.58984375f, 0.0f, 2, 0, diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 87e63d7c..7c15797a 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -894,7 +894,7 @@ void send_card_search_result_t( string encoded_lobby_name = encode_sjis(result_lobby->name); location_string = string_printf("%s,BLOCK01,%s", encoded_lobby_name.c_str(), encoded_server_name.c_str()); - } else if (result_lobby->flags & Lobby::Flag::EPISODE_3_ONLY) { + } else if (result_lobby->is_ep3()) { location_string = string_printf("BLOCK01-C%02" PRIu32 ",BLOCK01,%s", result_lobby->lobby_id - 15, encoded_server_name.c_str()); } else { @@ -1126,7 +1126,7 @@ void send_game_menu_t( continue; } - bool l_is_ep3 = !!(l->flags & Lobby::Flag::EPISODE_3_ONLY); + bool l_is_ep3 = l->is_ep3(); bool c_is_ep3 = !!(c->flags & Client::Flag::IS_EPISODE_3); if (l_is_ep3 != c_is_ep3) { continue; @@ -1143,6 +1143,24 @@ void send_game_menu_t( continue; } + uint8_t episode_num; + switch (l->episode) { + case Episode::EP1: + episode_num = 1; + break; + case Episode::EP2: + episode_num = 2; + break; + case Episode::EP3: + episode_num = 0; + break; + case Episode::EP4: + episode_num = 3; + break; + default: + throw runtime_error("lobby has incorrect episode number"); + } + auto& e = entries.emplace_back(); e.menu_id = MenuID::GAME; e.game_id = l->lobby_id; @@ -1151,12 +1169,12 @@ void send_game_menu_t( if (c->version() == GameVersion::DC) { e.episode = (l->flags & Lobby::Flag::NON_V1_ONLY) ? 1 : 0; } else { - e.episode = ((c->version() == GameVersion::BB) ? (l->max_clients << 4) : 0) | l->episode; + e.episode = ((c->version() == GameVersion::BB) ? (l->max_clients << 4) : 0) | episode_num; } - if (l->flags & Lobby::Flag::EPISODE_3_ONLY) { + if (l->is_ep3()) { e.flags = (l->password.empty() ? 0 : 2) | ((l->flags & Lobby::Flag::BATTLE_IN_PROGRESS) ? 4 : 0); } else { - e.flags = ((l->episode << 6) | (l->password.empty() ? 0 : 2)); + e.flags = ((episode_num << 6) | (l->password.empty() ? 0 : 2)); if (l->flags & Lobby::Flag::BATTLE_MODE) { e.flags |= 0x10; } @@ -1280,7 +1298,7 @@ void send_lobby_list(shared_ptr c, shared_ptr s) { if ((l->flags & Lobby::Flag::NON_V1_ONLY) && (c->flags & Client::Flag::IS_DC_V1)) { continue; } - if ((l->flags & Lobby::Flag::EPISODE_3_ONLY) && !(c->flags & Client::Flag::IS_EPISODE_3)) { + if (l->is_ep3() && !(c->flags & Client::Flag::IS_EPISODE_3)) { continue; } auto& e = entries.emplace_back(); @@ -1301,7 +1319,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) if (!(c->flags & Client::Flag::IS_EPISODE_3)) { throw runtime_error("lobby is not Episode 3"); } - if (!(l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (!l->is_ep3()) { throw runtime_error("lobby is not Episode 3"); } if (!(l->flags & Lobby::Flag::IS_SPECTATOR_TEAM)) { @@ -1405,7 +1423,7 @@ void send_join_game_t(shared_ptr c, shared_ptr l) { return; } - bool is_ep3 = (l->flags & Lobby::Flag::EPISODE_3_ONLY); + bool is_ep3 = l->is_ep3(); string data(is_ep3 ? sizeof(S_JoinGame_GC_Ep3_64) : sizeof(S_JoinGame), '\0'); // TODO: This is a terrible way to handle the different Ep3 format within the @@ -1448,7 +1466,22 @@ void send_join_game_t(shared_ptr c, shared_ptr l) { cmd->section_id = l->section_id; cmd->challenge_mode = (l->flags & Lobby::Flag::CHALLENGE_MODE) ? 1 : 0; cmd->rare_seed = l->random_seed; - cmd->episode = l->episode; + switch (l->episode) { + case Episode::EP1: + cmd->episode = 1; + break; + case Episode::EP2: + cmd->episode = 2; + break; + case Episode::EP3: + cmd->episode = 0xFF; + break; + case Episode::EP4: + cmd->episode = 3; + break; + default: + throw logic_error("invalid episode number in game"); + } cmd->unused2 = 0x01; cmd->solo_mode = (l->flags & Lobby::Flag::SOLO_MODE) ? 1 : 0; cmd->unused3 = 0x00; diff --git a/src/ServerState.cc b/src/ServerState.cc index 7cfcb38a..71d51767 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -49,12 +49,14 @@ ServerState::ServerState() Lobby::Flag::PUBLIC | Lobby::Flag::DEFAULT | Lobby::Flag::PERSISTENT | - (is_non_v1_only ? Lobby::Flag::NON_V1_ONLY : 0) | - (is_ep3_only ? Lobby::Flag::EPISODE_3_ONLY : 0); + (is_non_v1_only ? Lobby::Flag::NON_V1_ONLY : 0); l->block = x + 1; l->type = x; l->name = lobby_name; l->max_clients = 12; + if (is_ep3_only) { + l->episode = Episode::EP3; + } if (!is_non_v1_only) { this->public_lobby_search_order_v1.emplace_back(l); @@ -221,7 +223,7 @@ void ServerState::remove_lobby(uint32_t lobby_id) { } else { // Tell all players in all spectator teams to go back to the lobby for (auto watcher_l : l->watcher_lobbies) { - if (!(watcher_l->flags & Lobby::Flag::EPISODE_3_ONLY)) { + if (!watcher_l->is_ep3()) { throw logic_error("spectator team is not an Episode 3 lobby"); } l->log.info("Disbanding watcher lobby %" PRIX32, watcher_l->lobby_id); diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index 20aecf96..984eeacc 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -6,6 +6,60 @@ using namespace std; +size_t area_limit_for_episode(Episode ep) { + switch (ep) { + case Episode::EP1: + case Episode::EP2: + return 17; + break; + case Episode::EP4: + return 10; + break; + default: + return 0; + } +} + +bool episode_has_arpg_semantics(Episode ep) { + return (ep == Episode::EP1) || (ep == Episode::EP2) || (ep == Episode::EP4); +} + +const char* name_for_episode(Episode ep) { + switch (ep) { + case Episode::NONE: + return "No episode"; + case Episode::EP1: + return "Episode 1"; + case Episode::EP2: + return "Episode 2"; + case Episode::EP3: + return "Episode 3"; + case Episode::EP4: + return "Episode 4"; + default: + return "Unknown episode"; + } +} + +const char* abbreviation_for_episode(Episode ep) { + switch (ep) { + case Episode::NONE: + return "None"; + case Episode::EP1: + return "Ep1"; + case Episode::EP2: + return "Ep2"; + case Episode::EP3: + return "Ep3"; + case Episode::EP4: + return "Ep4"; + default: + return "UnkEp"; + } +} + + + const vector section_id_to_name({ "Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria", "Oran", "Yellowboze", "Whitill"}); diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index 5d05543c..a9056930 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -9,6 +9,21 @@ +enum class Episode { + NONE = 0, + EP1 = 1, + EP2 = 2, + EP3 = 3, + EP4 = 4, +}; + +size_t area_limit_for_episode(Episode ep); +bool episode_has_arpg_semantics(Episode ep); +const char* name_for_episode(Episode ep); +const char* abbreviation_for_episode(Episode ep); + + + size_t stack_size_for_item(uint8_t data0, uint8_t data1); size_t stack_size_for_item(const ItemData& item);