make episode an enum class

This commit is contained in:
Martin Michelsen
2023-03-04 11:41:37 -08:00
parent a35d835f31
commit 159f80cce3
18 changed files with 341 additions and 183 deletions
+17 -30
View File
@@ -106,7 +106,7 @@ static void server_command_lobby_info(shared_ptr<ServerState>, shared_ptr<Lobby>
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<ServerState>, shared_ptr<Lobby>,
static void server_command_auction(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> 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<ServerState>, shared_ptr<Lobby>
}
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<ServerState> s, shared_ptr<Lobby>
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<ServerState> s, shared_ptr<Lobby>
shared_ptr<Episode3::BattleRecord> record(new Episode3::BattleRecord(data));
shared_ptr<Episode3::BattleRecordPlayer> 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<ServerState>, shared_ptr<Lobby> 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<ServerState>, shared_ptr<Lobby> 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<ServerState>,
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<ServerState>, shared_ptr<Lobby> 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<ServerState>,
@@ -976,7 +962,8 @@ static void proxy_command_next(shared_ptr<ServerState>,
static void server_command_what(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> 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)) {
+4 -4
View File
@@ -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;
}
}
+2 -1
View File
@@ -6,6 +6,7 @@
#include <random>
#include "Client.hh"
#include "StaticGameData.hh"
@@ -31,7 +32,7 @@ struct CommonItemCreator {
std::shared_ptr<std::mt19937> 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;
};
+3 -3
View File
@@ -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<uint32_t>()),
random(new mt19937(this->random_seed)),
@@ -140,7 +140,7 @@ void Lobby::add_client(shared_ptr<Client> 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<Client> 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) {
+17 -12
View File
@@ -11,21 +11,23 @@
#include <phosg/Encoding.hh>
#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<Lobby> {
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<Lobby> {
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<Lobby> {
// 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<Lobby> {
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;
+42 -17
View File
@@ -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<PSOEnemy> parse_map(
shared_ptr<const BattleParamsIndex> battle_params,
bool is_solo,
uint8_t episode,
Episode episode,
uint8_t difficulty,
shared_ptr<const string> data,
bool alt_enemies) {
@@ -185,7 +198,7 @@ vector<PSOEnemy> 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<PSOEnemy> 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<PSOEnemy> 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<PSOEnemy> 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<vector<vector<AreaMapFileIndex>>> map_file_info = {
{ // Episode 1
{ // Non-solo
@@ -643,12 +655,25 @@ static const vector<vector<vector<AreaMapFileIndex>>> map_file_info = {
},
};
const vector<vector<AreaMapFileIndex>>& 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<le_uint32_t, 0x20>& variations,
shared_ptr<mt19937> 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<string> 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_<name_token>[_VV][_VV][_off]<e|o>[_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<string> 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);
+5 -4
View File
@@ -8,6 +8,7 @@
#include <string>
#include <vector>
#include "StaticGameData.hh"
#include "Text.hh"
@@ -44,7 +45,7 @@ public:
std::shared_ptr<const std::string> data_off_ep2, // BattleParamEntry_lab.dat
std::shared_ptr<const std::string> 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<PSOEnemy> parse_map(
std::shared_ptr<const BattleParamsIndex> battle_params,
bool is_solo,
uint8_t episode,
Episode episode,
uint8_t difficulty,
std::shared_ptr<const std::string> data,
bool alt_enemies);
@@ -125,8 +126,8 @@ private:
void generate_variations(
parray<le_uint32_t, 0x20>& variations,
std::shared_ptr<std::mt19937> random,
uint8_t episode,
Episode episode,
bool is_solo);
std::vector<std::string> 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();
+24 -27
View File
@@ -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<const PSOQuestHeaderDC*>(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<const PSOQuestHeaderPC*>(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<const Episode3::MapDefinition*>(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<const PSOQuestHeaderGC*>(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<const PSOQuestHeaderBB*>(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");
}
}
+2 -1
View File
@@ -7,6 +7,7 @@
#include <string>
#include <vector>
#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;
+18 -5
View File
@@ -16,17 +16,30 @@ RareItemSet::RareItemSet(shared_ptr<const string> 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) {
+3 -1
View File
@@ -6,6 +6,8 @@
#include <string>
#include <random>
#include "StaticGameData.hh"
class RareItemSet {
@@ -28,7 +30,7 @@ public:
RareItemSet(std::shared_ptr<const std::string> 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);
+76 -55
View File
@@ -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"<BattleTable>";
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<ServerState> 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<ServerState> s,
shared_ptr<Client> c, uint16_t, uint32_t flag, const string& data) {
check_size_t<S_CardBattleTableConfirmation_GC_Ep3_E5>(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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> c,
}
}
bool is_ep3 = (q->episode == 0xFF);
bool is_ep3 = (q->episode == Episode::EP3);
string bin_basename = q->bin_filename();
shared_ptr<const string> bin_contents = q->bin_contents();
string dat_basename;
@@ -2178,7 +2171,7 @@ static void on_84(shared_ptr<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<Lobby> create_game_generic(
shared_ptr<Client> c,
const std::u16string& name,
const std::u16string& password,
uint8_t episode,
Episode episode,
uint8_t difficulty,
uint32_t flags,
shared_ptr<Lobby> watched_lobby,
shared_ptr<Episode3::BattleRecordPlayer> 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<Lobby> 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<Lobby> 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<Lobby> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<Client> 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) {
+1 -1
View File
@@ -11,7 +11,7 @@ std::shared_ptr<Lobby> create_game_generic(
std::shared_ptr<Client> c,
const std::u16string& name,
const std::u16string& password,
uint8_t episode,
Episode episode,
uint8_t difficulty,
uint32_t flags,
std::shared_ptr<Lobby> watched_lobby = nullptr,
+11 -10
View File
@@ -213,7 +213,7 @@ static void on_forward_check_size_ep3_lobby(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
check_size_sc<G_UnusedHeader>(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<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
check_size_sc<G_UnusedHeader>(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<ServerState> s,
const string& orig_data) {
const auto& header = check_size_sc<G_CardBattleCommandHeader>(
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<ServerState>,
static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> 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<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> 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<ServerState>,
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<ServerState>,
((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<ServerState>,
{
{0x60, 0x06, 0x0000},
static_cast<uint8_t>(c->area),
static_cast<uint8_t>((l->episode == 2) ? 0x4E : 0x2F),
static_cast<uint8_t>(is_ep2 ? 0x4E : 0x2F),
0x0B4F,
(l->episode == 2) ? -9999.0f : 10160.58984375f,
is_ep2 ? -9999.0f : 10160.58984375f,
0.0f,
2,
0,
+42 -9
View File
@@ -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<Client> c, shared_ptr<ServerState> 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<Client> c, shared_ptr<Lobby> 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<Client> c, shared_ptr<Lobby> 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<LobbyDataT, DispDataT>), '\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<Client> c, shared_ptr<Lobby> 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;
+5 -3
View File
@@ -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);
+54
View File
@@ -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<string> section_id_to_name({
"Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria",
"Oran", "Yellowboze", "Whitill"});
+15
View File
@@ -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);