Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02b0bf622c | |||
| 39d1b338b7 | |||
| b27b458557 | |||
| f642e2f5a8 | |||
| 50ded155ed | |||
| eab453413e | |||
| 2304a17dd0 | |||
| be4837cccf | |||
| 2235103efe | |||
| 466eb49c55 |
@@ -123,7 +123,7 @@ After building newserv or downloading a release, do this to set it up and use it
|
||||
1. In the system/ directory, make a copy of config.example.json named config.json, and edit it appropriately.
|
||||
2. If you plan to play PSO Blue Burst on newserv, set up the patch directory. See the "Client patch directories" section below.
|
||||
3. Run `./newserv` in the newserv directory. This will start the game server and run the interactive shell. You may need `sudo` if newserv's built-in DNS server is enabled.
|
||||
4. Use the interactive shell to add a license. Run `help` in the shell to see how to do this.
|
||||
4. If you set AllowUnregisteredUsers to false in config.json, use the interactive shell to add your license. Run `help` in the shell to see how to do this.
|
||||
5. Set your client's network settings appropriately and start an online game. See the "Connecting local clients" or "Connecting remote clients" section to see how to get your game client to connect.
|
||||
|
||||
To use newserv in other ways (e.g. for translating data), see the end of this document.
|
||||
|
||||
@@ -155,3 +155,22 @@ TODO: Figure out more debug message conditionals (vars/functions) and add them h
|
||||
|
||||
(Episode 3 USA) Able to find VIP cards offline (but they're still rare)
|
||||
042C0B20 4800000C
|
||||
|
||||
(Ep3 USA) Hold L when starting battle to enter debug menu
|
||||
042C5460 4BD3AF78
|
||||
040003D8 3C60804A
|
||||
040003DC 60630518
|
||||
040003E0 3C800002
|
||||
040003E4 480C9F35
|
||||
040003E8 2C030000
|
||||
040003EC 4082000C
|
||||
040003F0 8801001A
|
||||
040003F4 48000008
|
||||
040003F8 3800001A
|
||||
040003FC 482C5068
|
||||
|
||||
(Ep3 USA) Dressing room always accessible
|
||||
041A16FC 38600001
|
||||
|
||||
(Ep3 USA) Replace Options menu with debug menu
|
||||
04149E70 38600019
|
||||
|
||||
+121
-144
@@ -1083,151 +1083,43 @@ void PlayerConfig::encrypt(uint8_t basis) {
|
||||
this->basis = basis;
|
||||
}
|
||||
|
||||
HPType hp_type_for_name(const char* name) {
|
||||
if (!strcmp(name, "DEFEAT_PLAYER")) {
|
||||
return HPType::DEFEAT_PLAYER;
|
||||
} else if (!strcmp(name, "DEFEAT_TEAM")) {
|
||||
return HPType::DEFEAT_TEAM;
|
||||
} else if (!strcmp(name, "COMMON_HP")) {
|
||||
return HPType::COMMON_HP;
|
||||
} else {
|
||||
throw out_of_range("invalid HP type name");
|
||||
}
|
||||
}
|
||||
const char* name_for_hp_type(HPType hp_type) {
|
||||
switch (hp_type) {
|
||||
case HPType::DEFEAT_PLAYER:
|
||||
return "DEFEAT_PLAYER";
|
||||
case HPType::DEFEAT_TEAM:
|
||||
return "DEFEAT_TEAM";
|
||||
case HPType::COMMON_HP:
|
||||
return "COMMON_HP";
|
||||
default:
|
||||
throw out_of_range("invalid HP type");
|
||||
}
|
||||
}
|
||||
|
||||
DiceExchangeMode dice_exchange_mode_for_name(const char* name) {
|
||||
if (!strcmp(name, "HIGH_ATK")) {
|
||||
return DiceExchangeMode::HIGH_ATK;
|
||||
} else if (!strcmp(name, "HIGH_DEF")) {
|
||||
return DiceExchangeMode::HIGH_DEF;
|
||||
} else if (!strcmp(name, "NONE")) {
|
||||
return DiceExchangeMode::NONE;
|
||||
} else {
|
||||
throw out_of_range("invalid dice exchange mode name");
|
||||
}
|
||||
}
|
||||
const char* name_for_dice_exchange_mode(DiceExchangeMode dice_exchange_mode) {
|
||||
switch (dice_exchange_mode) {
|
||||
case DiceExchangeMode::HIGH_ATK:
|
||||
return "HIGH_ATK";
|
||||
case DiceExchangeMode::HIGH_DEF:
|
||||
return "HIGH_DEF";
|
||||
case DiceExchangeMode::NONE:
|
||||
return "NONE";
|
||||
default:
|
||||
throw out_of_range("invalid dice exchange mode");
|
||||
}
|
||||
}
|
||||
|
||||
AllowedCards allowed_cards_for_name(const char* name) {
|
||||
if (!strcmp(name, "ALL")) {
|
||||
return AllowedCards::ALL;
|
||||
} else if (!strcmp(name, "N_ONLY")) {
|
||||
return AllowedCards::N_ONLY;
|
||||
} else if (!strcmp(name, "N_R_ONLY")) {
|
||||
return AllowedCards::N_R_ONLY;
|
||||
} else if (!strcmp(name, "N_R_S_ONLY")) {
|
||||
return AllowedCards::N_R_S_ONLY;
|
||||
} else {
|
||||
throw out_of_range("invalid allowed cards name");
|
||||
}
|
||||
}
|
||||
const char* name_for_allowed_cards(AllowedCards allowed_cards) {
|
||||
switch (allowed_cards) {
|
||||
case AllowedCards::ALL:
|
||||
return "ALL";
|
||||
case AllowedCards::N_ONLY:
|
||||
return "N_ONLY";
|
||||
case AllowedCards::N_R_ONLY:
|
||||
return "N_R_ONLY";
|
||||
case AllowedCards::N_R_S_ONLY:
|
||||
return "N_R_S_ONLY";
|
||||
default:
|
||||
throw out_of_range("invalid allowed cards");
|
||||
}
|
||||
}
|
||||
|
||||
Rules::Rules() {
|
||||
this->clear();
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void get_json_optional_int(const JSONObject::dict_type& dict, IntT& result, const char* key) {
|
||||
try {
|
||||
auto value_json = dict.at(key);
|
||||
if (value_json->is_int()) {
|
||||
result = value_json->as_int();
|
||||
} else if (value_json->is_bool()) {
|
||||
result = value_json->as_bool() ? 1 : 0;
|
||||
} else {
|
||||
throw runtime_error("int-valued field is not int or bool");
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename EnumT>
|
||||
void get_json_optional_enum(const JSONObject::dict_type& dict, EnumT& result, const char* key, EnumT (*parse_str)(const char*)) {
|
||||
try {
|
||||
auto value_json = dict.at(key);
|
||||
if (value_json->is_string()) {
|
||||
result = parse_str(value_json->as_string().c_str());
|
||||
} else if (value_json->is_int()) {
|
||||
result = static_cast<EnumT>(value_json->as_int());
|
||||
} else {
|
||||
throw runtime_error("enum-valued field is not int or string");
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
};
|
||||
|
||||
Rules::Rules(shared_ptr<const JSONObject> json) {
|
||||
Rules::Rules(const JSON& json) {
|
||||
this->clear();
|
||||
|
||||
auto dict = json->as_dict();
|
||||
get_json_optional_int(dict, this->overall_time_limit, "overall_time_limit");
|
||||
get_json_optional_int(dict, this->phase_time_limit, "phase_time_limit");
|
||||
get_json_optional_enum(dict, this->allowed_cards, "allowed_cards", allowed_cards_for_name);
|
||||
get_json_optional_int(dict, this->min_dice, "min_dice");
|
||||
get_json_optional_int(dict, this->max_dice, "max_dice");
|
||||
get_json_optional_int(dict, this->disable_deck_shuffle, "disable_deck_shuffle");
|
||||
get_json_optional_int(dict, this->disable_deck_loop, "disable_deck_loop");
|
||||
get_json_optional_int(dict, this->char_hp, "char_hp");
|
||||
get_json_optional_enum(dict, this->hp_type, "hp_type", hp_type_for_name);
|
||||
get_json_optional_int(dict, this->no_assist_cards, "no_assist_cards");
|
||||
get_json_optional_int(dict, this->disable_dialogue, "disable_dialogue");
|
||||
get_json_optional_enum(dict, this->dice_exchange_mode, "dice_exchange_mode", dice_exchange_mode_for_name);
|
||||
get_json_optional_int(dict, this->disable_dice_boost, "disable_dice_boost");
|
||||
this->overall_time_limit = json.get_int("overall_time_limit", this->overall_time_limit);
|
||||
this->phase_time_limit = json.get_int("phase_time_limit", this->phase_time_limit);
|
||||
this->allowed_cards = json.get_enum("allowed_cards", this->allowed_cards);
|
||||
this->min_dice = json.get_int("min_dice", this->min_dice);
|
||||
this->max_dice = json.get_int("max_dice", this->max_dice);
|
||||
this->disable_deck_shuffle = json.get_bool("disable_deck_shuffle", this->disable_deck_shuffle);
|
||||
this->disable_deck_loop = json.get_bool("disable_deck_loop", this->disable_deck_loop);
|
||||
this->char_hp = json.get_int("char_hp", this->char_hp);
|
||||
this->hp_type = json.get_enum("hp_type", this->hp_type);
|
||||
this->no_assist_cards = json.get_bool("no_assist_cards", this->no_assist_cards);
|
||||
this->disable_dialogue = json.get_bool("disable_dialogue", this->disable_dialogue);
|
||||
this->dice_exchange_mode = json.get_enum("dice_exchange_mode", this->dice_exchange_mode);
|
||||
this->disable_dice_boost = json.get_bool("disable_dice_boost", this->disable_dice_boost);
|
||||
}
|
||||
|
||||
shared_ptr<JSONObject> Rules::json() const {
|
||||
unordered_map<string, shared_ptr<JSONObject>> dict;
|
||||
dict.emplace("overall_time_limit", make_json_int(this->overall_time_limit));
|
||||
dict.emplace("phase_time_limit", make_json_int(this->phase_time_limit));
|
||||
dict.emplace("allowed_cards", make_json_str(name_for_allowed_cards(this->allowed_cards)));
|
||||
dict.emplace("min_dice", make_json_int(this->min_dice));
|
||||
dict.emplace("max_dice", make_json_int(this->max_dice));
|
||||
dict.emplace("disable_deck_shuffle", make_json_bool(this->disable_deck_shuffle));
|
||||
dict.emplace("disable_deck_loop", make_json_bool(this->disable_deck_loop));
|
||||
dict.emplace("char_hp", make_json_int(this->char_hp));
|
||||
dict.emplace("hp_type", make_json_str(name_for_hp_type(this->hp_type)));
|
||||
dict.emplace("no_assist_cards", make_json_bool(this->no_assist_cards));
|
||||
dict.emplace("disable_dialogue", make_json_bool(this->disable_dialogue));
|
||||
dict.emplace("dice_exchange_mode", make_json_str(name_for_dice_exchange_mode(this->dice_exchange_mode)));
|
||||
dict.emplace("disable_dice_boost", make_json_bool(this->disable_dice_boost));
|
||||
return shared_ptr<JSONObject>(new JSONObject(std::move(dict)));
|
||||
JSON Rules::json() const {
|
||||
return JSON::dict({
|
||||
{"overall_time_limit", this->overall_time_limit},
|
||||
{"phase_time_limit", this->phase_time_limit},
|
||||
{"allowed_cards", name_for_enum(this->allowed_cards)},
|
||||
{"min_dice", this->min_dice},
|
||||
{"max_dice", this->max_dice},
|
||||
{"disable_deck_shuffle", this->disable_deck_shuffle},
|
||||
{"disable_deck_loop", this->disable_deck_loop},
|
||||
{"char_hp", this->char_hp},
|
||||
{"hp_type", name_for_enum(this->hp_type)},
|
||||
{"no_assist_cards", this->no_assist_cards},
|
||||
{"disable_dialogue", this->disable_dialogue},
|
||||
{"dice_exchange_mode", name_for_enum(this->dice_exchange_mode)},
|
||||
{"disable_dice_boost", this->disable_dice_boost},
|
||||
});
|
||||
}
|
||||
|
||||
void Rules::set_defaults() {
|
||||
@@ -2106,15 +1998,15 @@ set<uint32_t> MapIndex::all_numbers() const {
|
||||
|
||||
COMDeckIndex::COMDeckIndex(const string& filename) {
|
||||
try {
|
||||
auto json = JSONObject::parse(load_file(filename));
|
||||
for (const auto& def_json : json->as_list()) {
|
||||
auto json = JSON::parse(load_file(filename));
|
||||
for (const auto& def_json : json.as_list()) {
|
||||
auto& def = this->decks.emplace_back(new COMDeckDefinition());
|
||||
def->index = this->decks.size() - 1;
|
||||
def->player_name = def_json->at(0)->as_string();
|
||||
def->deck_name = def_json->at(1)->as_string();
|
||||
auto card_ids_json = def_json->at(2)->as_list();
|
||||
def->player_name = def_json->at(0).as_string();
|
||||
def->deck_name = def_json->at(1).as_string();
|
||||
auto card_ids_json = def_json->at(2);
|
||||
for (size_t z = 0; z < 0x1F; z++) {
|
||||
def->card_ids[z] = card_ids_json.at(z)->as_int();
|
||||
def->card_ids[z] = card_ids_json.at(z).as_int();
|
||||
}
|
||||
if (!this->decks_by_name.emplace(def->deck_name, def).second) {
|
||||
throw runtime_error("duplicate COM deck name: " + def->deck_name);
|
||||
@@ -2142,3 +2034,88 @@ shared_ptr<const COMDeckDefinition> COMDeckIndex::random_deck() const {
|
||||
}
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
template <>
|
||||
Episode3::HPType enum_for_name<Episode3::HPType>(const char* name) {
|
||||
if (!strcmp(name, "DEFEAT_PLAYER")) {
|
||||
return Episode3::HPType::DEFEAT_PLAYER;
|
||||
} else if (!strcmp(name, "DEFEAT_TEAM")) {
|
||||
return Episode3::HPType::DEFEAT_TEAM;
|
||||
} else if (!strcmp(name, "COMMON_HP")) {
|
||||
return Episode3::HPType::COMMON_HP;
|
||||
} else {
|
||||
throw out_of_range("invalid HP type name");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::HPType>(Episode3::HPType hp_type) {
|
||||
switch (hp_type) {
|
||||
case Episode3::HPType::DEFEAT_PLAYER:
|
||||
return "DEFEAT_PLAYER";
|
||||
case Episode3::HPType::DEFEAT_TEAM:
|
||||
return "DEFEAT_TEAM";
|
||||
case Episode3::HPType::COMMON_HP:
|
||||
return "COMMON_HP";
|
||||
default:
|
||||
throw out_of_range("invalid HP type");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
Episode3::DiceExchangeMode enum_for_name<Episode3::DiceExchangeMode>(const char* name) {
|
||||
if (!strcmp(name, "HIGH_ATK")) {
|
||||
return Episode3::DiceExchangeMode::HIGH_ATK;
|
||||
} else if (!strcmp(name, "HIGH_DEF")) {
|
||||
return Episode3::DiceExchangeMode::HIGH_DEF;
|
||||
} else if (!strcmp(name, "NONE")) {
|
||||
return Episode3::DiceExchangeMode::NONE;
|
||||
} else {
|
||||
throw out_of_range("invalid dice exchange mode name");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::DiceExchangeMode>(Episode3::DiceExchangeMode dice_exchange_mode) {
|
||||
switch (dice_exchange_mode) {
|
||||
case Episode3::DiceExchangeMode::HIGH_ATK:
|
||||
return "HIGH_ATK";
|
||||
case Episode3::DiceExchangeMode::HIGH_DEF:
|
||||
return "HIGH_DEF";
|
||||
case Episode3::DiceExchangeMode::NONE:
|
||||
return "NONE";
|
||||
default:
|
||||
throw out_of_range("invalid dice exchange mode");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
Episode3::AllowedCards enum_for_name<Episode3::AllowedCards>(const char* name) {
|
||||
if (!strcmp(name, "ALL")) {
|
||||
return Episode3::AllowedCards::ALL;
|
||||
} else if (!strcmp(name, "N_ONLY")) {
|
||||
return Episode3::AllowedCards::N_ONLY;
|
||||
} else if (!strcmp(name, "N_R_ONLY")) {
|
||||
return Episode3::AllowedCards::N_R_ONLY;
|
||||
} else if (!strcmp(name, "N_R_S_ONLY")) {
|
||||
return Episode3::AllowedCards::N_R_S_ONLY;
|
||||
} else {
|
||||
throw out_of_range("invalid allowed cards name");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::AllowedCards>(Episode3::AllowedCards allowed_cards) {
|
||||
switch (allowed_cards) {
|
||||
case Episode3::AllowedCards::ALL:
|
||||
return "ALL";
|
||||
case Episode3::AllowedCards::N_ONLY:
|
||||
return "N_ONLY";
|
||||
case Episode3::AllowedCards::N_R_ONLY:
|
||||
return "N_R_ONLY";
|
||||
case Episode3::AllowedCards::N_R_S_ONLY:
|
||||
return "N_R_S_ONLY";
|
||||
default:
|
||||
throw out_of_range("invalid allowed cards");
|
||||
}
|
||||
}
|
||||
|
||||
+17
-11
@@ -7,6 +7,7 @@
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <phosg/Tools.hh>
|
||||
#include <phosg/Types.hh>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -659,18 +660,12 @@ enum class HPType : uint8_t {
|
||||
COMMON_HP = 2,
|
||||
};
|
||||
|
||||
HPType hp_type_for_name(const char* name);
|
||||
const char* name_for_hp_type(HPType hp_type);
|
||||
|
||||
enum class DiceExchangeMode : uint8_t {
|
||||
HIGH_ATK = 0,
|
||||
HIGH_DEF = 1,
|
||||
NONE = 2,
|
||||
};
|
||||
|
||||
DiceExchangeMode dice_exchange_mode_for_name(const char* name);
|
||||
const char* name_for_dice_exchange_mode(DiceExchangeMode dice_exchange_mode);
|
||||
|
||||
enum class AllowedCards : uint8_t {
|
||||
ALL = 0,
|
||||
N_ONLY = 1,
|
||||
@@ -678,9 +673,6 @@ enum class AllowedCards : uint8_t {
|
||||
N_R_S_ONLY = 3,
|
||||
};
|
||||
|
||||
AllowedCards allowed_cards_for_name(const char* name);
|
||||
const char* name_for_allowed_cards(AllowedCards allowed_cards);
|
||||
|
||||
struct Rules {
|
||||
// When this structure is used in a map/quest definition, FF in any of these
|
||||
// fields means the user is allowed to override it. Any non-FF fields are
|
||||
@@ -708,8 +700,8 @@ struct Rules {
|
||||
// likely be more work than it's worth.
|
||||
|
||||
Rules();
|
||||
explicit Rules(std::shared_ptr<const JSONObject> json);
|
||||
std::shared_ptr<JSONObject> json() const;
|
||||
explicit Rules(const JSON& json);
|
||||
JSON json() const;
|
||||
bool operator==(const Rules& other) const;
|
||||
bool operator!=(const Rules& other) const;
|
||||
void clear();
|
||||
@@ -1115,3 +1107,17 @@ private:
|
||||
};
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
// TODO: Figure out how to declare these inside the Episode3 namespace.
|
||||
template <>
|
||||
Episode3::HPType enum_for_name<Episode3::HPType>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::HPType>(Episode3::HPType hp_type);
|
||||
template <>
|
||||
Episode3::DiceExchangeMode enum_for_name<Episode3::DiceExchangeMode>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::DiceExchangeMode>(Episode3::DiceExchangeMode dice_exchange_mode);
|
||||
template <>
|
||||
Episode3::AllowedCards enum_for_name<Episode3::AllowedCards>(const char* name);
|
||||
template <>
|
||||
const char* name_for_enum<Episode3::AllowedCards>(Episode3::AllowedCards allowed_cards);
|
||||
|
||||
+43
-50
@@ -313,7 +313,7 @@ Tournament::Tournament(
|
||||
shared_ptr<const MapIndex> map_index,
|
||||
shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
uint8_t number,
|
||||
std::shared_ptr<const JSONObject> json)
|
||||
const JSON& json)
|
||||
: log(string_printf("[Tournament/%02hhX] ", number)),
|
||||
map_index(map_index),
|
||||
com_deck_index(com_deck_index),
|
||||
@@ -325,28 +325,25 @@ void Tournament::init() {
|
||||
vector<size_t> team_index_to_rounds_cleared;
|
||||
|
||||
bool is_registration_complete;
|
||||
if (this->source_json) {
|
||||
auto& dict = this->source_json->as_dict();
|
||||
this->name = dict.at("name")->as_string();
|
||||
this->map = this->map_index->definition_for_number(dict.at("map_number")->as_int());
|
||||
this->rules = Rules(dict.at("rules"));
|
||||
this->is_2v2 = dict.at("is_2v2")->as_bool();
|
||||
is_registration_complete = dict.at("is_registration_complete")->as_bool();
|
||||
if (!this->source_json.is_null()) {
|
||||
this->name = this->source_json.get_string("name");
|
||||
this->map = this->map_index->definition_for_number(this->source_json.get_int("map_number"));
|
||||
this->rules = Rules(this->source_json.at("rules"));
|
||||
this->is_2v2 = this->source_json.get_bool("is_2v2");
|
||||
is_registration_complete = this->source_json.get_bool("is_registration_complete");
|
||||
|
||||
for (const auto& team_json : dict.at("teams")->as_list()) {
|
||||
auto& team_dict = team_json->as_dict();
|
||||
for (const auto& team_json : this->source_json.get_list("teams")) {
|
||||
auto& team = this->teams.emplace_back(new Team(
|
||||
this->shared_from_this(),
|
||||
this->teams.size(),
|
||||
team_dict.at("max_players")->as_int()));
|
||||
team->name = team_dict.at("name")->as_string();
|
||||
team->password = team_dict.at("password")->as_string();
|
||||
team_index_to_rounds_cleared.emplace_back(team_dict.at("num_rounds_cleared")->as_int());
|
||||
for (const auto& player_json : team_dict.at("player_specs")->as_list()) {
|
||||
team_json->get_int("max_players")));
|
||||
team->name = team_json->get_string("name");
|
||||
team->password = team_json->get_string("password");
|
||||
team_index_to_rounds_cleared.emplace_back(team_json->get_int("num_rounds_cleared"));
|
||||
for (const auto& player_json : team_json->get_list("player_specs")) {
|
||||
if (player_json->is_int()) {
|
||||
uint32_t serial_number = player_json->as_int();
|
||||
team->players.emplace_back(serial_number);
|
||||
this->all_player_serial_numbers.emplace(serial_number);
|
||||
team->players.emplace_back(player_json->as_int());
|
||||
this->all_player_serial_numbers.emplace(player_json->as_int());
|
||||
} else {
|
||||
team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string()));
|
||||
}
|
||||
@@ -354,7 +351,7 @@ void Tournament::init() {
|
||||
}
|
||||
this->num_teams = this->teams.size();
|
||||
|
||||
this->source_json.reset();
|
||||
this->source_json = nullptr;
|
||||
|
||||
} else {
|
||||
// Create empty teams
|
||||
@@ -459,34 +456,33 @@ void Tournament::init() {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<JSONObject> Tournament::json() const {
|
||||
unordered_map<string, shared_ptr<JSONObject>> dict;
|
||||
dict.emplace("name", make_json_str(this->name));
|
||||
dict.emplace("map_number", make_json_int(this->map->map.map_number));
|
||||
dict.emplace("rules", this->rules.json());
|
||||
dict.emplace("is_2v2", make_json_bool(this->is_2v2));
|
||||
dict.emplace("is_registration_complete", make_json_bool(this->current_state != State::REGISTRATION));
|
||||
|
||||
vector<shared_ptr<JSONObject>> teams_list;
|
||||
JSON Tournament::json() const {
|
||||
auto teams_list = JSON::list();
|
||||
for (auto team : this->teams) {
|
||||
unordered_map<string, shared_ptr<JSONObject>> team_dict;
|
||||
team_dict.emplace("max_players", make_json_int(team->max_players));
|
||||
vector<shared_ptr<JSONObject>> player_jsons_list;
|
||||
auto players_list = JSON::list();
|
||||
for (const auto& player : team->players) {
|
||||
if (player.is_human()) {
|
||||
player_jsons_list.emplace_back(make_json_int(player.serial_number));
|
||||
players_list.emplace_back(player.serial_number);
|
||||
} else {
|
||||
player_jsons_list.emplace_back(make_json_str(player.com_deck->deck_name));
|
||||
players_list.emplace_back(player.com_deck->deck_name);
|
||||
}
|
||||
}
|
||||
team_dict.emplace("player_specs", make_json_list(std::move(player_jsons_list)));
|
||||
team_dict.emplace("name", make_json_str(team->name));
|
||||
team_dict.emplace("password", make_json_str(team->password));
|
||||
team_dict.emplace("num_rounds_cleared", make_json_int(team->num_rounds_cleared));
|
||||
teams_list.emplace_back(new JSONObject(std::move(team_dict)));
|
||||
teams_list.emplace_back(JSON::dict({
|
||||
{"max_players", team->max_players},
|
||||
{"player_specs", std::move(players_list)},
|
||||
{"name", team->name},
|
||||
{"password", team->password},
|
||||
{"num_rounds_cleared", team->num_rounds_cleared},
|
||||
}));
|
||||
}
|
||||
dict.emplace("teams", make_json_list(std::move(teams_list)));
|
||||
return shared_ptr<JSONObject>(new JSONObject(std::move(dict)));
|
||||
return JSON::dict({
|
||||
{"name", this->name},
|
||||
{"map_number", this->map->map.map_number.load()},
|
||||
{"rules", this->rules.json()},
|
||||
{"is_2v2", this->is_2v2},
|
||||
{"is_registration_complete", (this->current_state != State::REGISTRATION)},
|
||||
{"teams", std::move(teams_list)},
|
||||
});
|
||||
}
|
||||
|
||||
uint8_t Tournament::get_number() const {
|
||||
@@ -664,15 +660,13 @@ TournamentIndex::TournamentIndex(
|
||||
return;
|
||||
}
|
||||
|
||||
auto json = JSONObject::parse(load_file(this->state_filename));
|
||||
|
||||
auto& list = json->as_list();
|
||||
if (list.size() != 0x20) {
|
||||
auto json = JSON::parse(load_file(this->state_filename));
|
||||
if (json.size() != 0x20) {
|
||||
throw runtime_error("tournament JSON list length is incorrect");
|
||||
}
|
||||
for (size_t z = 0; z < 0x20; z++) {
|
||||
if (!list.at(z)->is_null()) {
|
||||
this->tournaments[z].reset(new Tournament(this->map_index, this->com_deck_index, z, list[z]));
|
||||
if (!json.at(z).is_null()) {
|
||||
this->tournaments[z].reset(new Tournament(this->map_index, this->com_deck_index, z, json.at(z)));
|
||||
this->tournaments[z]->init();
|
||||
}
|
||||
}
|
||||
@@ -683,16 +677,15 @@ void TournamentIndex::save() const {
|
||||
return;
|
||||
}
|
||||
|
||||
vector<shared_ptr<JSONObject>> list;
|
||||
auto list = JSON::list();
|
||||
for (size_t z = 0; z < 0x20; z++) {
|
||||
if (this->tournaments[z]) {
|
||||
list.emplace_back(this->tournaments[z]->json());
|
||||
} else {
|
||||
list.emplace_back(make_json_null());
|
||||
list.emplace_back(nullptr);
|
||||
}
|
||||
}
|
||||
auto json = make_json_list(std::move(list));
|
||||
save_file(this->state_filename, json->serialize(JSONObject::SerializeOption::FORMAT));
|
||||
save_file(this->state_filename, list.serialize(JSON::SerializeOption::FORMAT));
|
||||
}
|
||||
|
||||
vector<shared_ptr<Tournament>> TournamentIndex::all_tournaments() const {
|
||||
|
||||
@@ -104,11 +104,11 @@ public:
|
||||
std::shared_ptr<const MapIndex> map_index,
|
||||
std::shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
uint8_t number,
|
||||
std::shared_ptr<const JSONObject> json);
|
||||
const JSON& json);
|
||||
~Tournament() = default;
|
||||
void init();
|
||||
|
||||
std::shared_ptr<JSONObject> json() const;
|
||||
JSON json() const;
|
||||
|
||||
uint8_t get_number() const;
|
||||
const std::string& get_name() const;
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
|
||||
std::shared_ptr<const MapIndex> map_index;
|
||||
std::shared_ptr<const COMDeckIndex> com_deck_index;
|
||||
std::shared_ptr<const JSONObject> source_json;
|
||||
JSON source_json;
|
||||
uint8_t number;
|
||||
std::string name;
|
||||
std::shared_ptr<const MapIndex::MapEntry> map;
|
||||
|
||||
+5
-15
@@ -21,26 +21,16 @@ PrefixedLogger replay_log("[ReplaySession] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger server_log("[Server] ", LogLevel::USE_DEFAULT);
|
||||
PrefixedLogger static_game_data_log("[StaticGameData] ", LogLevel::USE_DEFAULT);
|
||||
|
||||
static LogLevel log_level_for_name(const string& name) {
|
||||
static const unordered_map<string, LogLevel> levels({
|
||||
{"debug", LogLevel::DEBUG},
|
||||
{"info", LogLevel::INFO},
|
||||
{"warning", LogLevel::WARNING},
|
||||
{"error", LogLevel::ERROR},
|
||||
{"disabled", LogLevel::DISABLED},
|
||||
});
|
||||
return levels.at(tolower(name));
|
||||
}
|
||||
|
||||
static void set_log_level_from_json(
|
||||
PrefixedLogger& log, shared_ptr<JSONObject> d, const char* json_key) {
|
||||
PrefixedLogger& log, const JSON& d, const char* json_key) {
|
||||
try {
|
||||
log.min_level = log_level_for_name(d->at(json_key)->as_string());
|
||||
} catch (const JSONObject::key_error&) {
|
||||
string name = toupper(d.at(json_key).as_string());
|
||||
log.min_level = enum_for_name<LogLevel>(name.c_str());
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
void set_log_levels_from_json(shared_ptr<JSONObject> json) {
|
||||
void set_log_levels_from_json(const JSON& json) {
|
||||
set_log_level_from_json(ax_messages_log, json, "AXMessages");
|
||||
set_log_level_from_json(channel_exceptions_log, json, "ChannelExceptions");
|
||||
set_log_level_from_json(client_log, json, "Clients");
|
||||
|
||||
+1
-1
@@ -20,4 +20,4 @@ extern PrefixedLogger replay_log;
|
||||
extern PrefixedLogger server_log;
|
||||
extern PrefixedLogger static_game_data_log;
|
||||
|
||||
void set_log_levels_from_json(std::shared_ptr<JSONObject> json);
|
||||
void set_log_levels_from_json(const JSON& json);
|
||||
|
||||
+21
-36
@@ -40,9 +40,9 @@ using namespace std;
|
||||
bool use_terminal_colors = false;
|
||||
|
||||
template <typename T>
|
||||
vector<T> parse_int_vector(shared_ptr<const JSONObject> o) {
|
||||
vector<T> parse_int_vector(const JSON& o) {
|
||||
vector<T> ret;
|
||||
for (const auto& x : o->as_list()) {
|
||||
for (const auto& x : o.as_list()) {
|
||||
ret.emplace_back(x->as_int());
|
||||
}
|
||||
return ret;
|
||||
@@ -1303,7 +1303,7 @@ int main(int argc, char** argv) {
|
||||
shared_ptr<string> data(new string(read_input_data()));
|
||||
shared_ptr<RareItemSet> rs;
|
||||
if (json) {
|
||||
rs.reset(new JSONRareItemSet(JSONObject::parse(read_input_data())));
|
||||
rs.reset(new JSONRareItemSet(JSON::parse(read_input_data())));
|
||||
} else {
|
||||
rs.reset(new RELRareItemSet(data));
|
||||
}
|
||||
@@ -1400,7 +1400,7 @@ int main(int argc, char** argv) {
|
||||
return ret;
|
||||
};
|
||||
|
||||
JSONObject::dict_type episodes_dict;
|
||||
auto episodes_dict = JSON::dict();
|
||||
static const array<pair<Episode, vector<vector<EnemyType>>>, 3> episodes = {
|
||||
make_pair(Episode::EP1, generate_table(Episode::EP1)),
|
||||
make_pair(Episode::EP2, generate_table(Episode::EP2)),
|
||||
@@ -1409,11 +1409,11 @@ int main(int argc, char** argv) {
|
||||
for (const auto& episode_it : episodes) {
|
||||
Episode episode = episode_it.first;
|
||||
const auto& rt_index_to_enemy_type = episode_it.second;
|
||||
JSONObject::dict_type difficulty_dict;
|
||||
auto difficulty_dict = JSON::dict();
|
||||
for (uint8_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
JSONObject::dict_type section_id_dict;
|
||||
auto section_id_dict = JSON::dict();
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
JSONObject::dict_type collection_dict;
|
||||
auto collection_dict = JSON::dict();
|
||||
|
||||
for (size_t rt_index = 0; rt_index < rt_index_to_enemy_type.size(); rt_index++) {
|
||||
const auto& enemy_types = rt_index_to_enemy_type[rt_index];
|
||||
@@ -1427,66 +1427,51 @@ int main(int argc, char** argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject::list_type spec_list;
|
||||
|
||||
auto frac = reduce_fraction<uint64_t>(spec.probability, 0x100000000);
|
||||
spec_list.emplace_back(make_json_str(string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second)));
|
||||
spec_list.emplace_back(make_json_int(primary_identifier));
|
||||
|
||||
JSONObject::list_type specs_list;
|
||||
specs_list.emplace_back(make_json_list(std::move(spec_list)));
|
||||
auto specs_json = make_json_list(std::move(specs_list));
|
||||
auto specs_json = JSON::list({JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), primary_identifier})});
|
||||
for (const auto& enemy_type : enemy_types) {
|
||||
if (enemy_type_valid_for_episode(episode, enemy_type)) {
|
||||
collection_dict.emplace(name_for_enum(enemy_type), specs_json);
|
||||
collection_dict.emplace(name_for_enum(enemy_type), std::move(specs_json));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t area = 0; area < 0x12; area++) {
|
||||
JSONObject::list_type area_list;
|
||||
auto area_list = JSON::list();
|
||||
|
||||
for (const auto& spec : rs.get_box_specs(GameMode::NORMAL, episode, difficulty, section_id, area)) {
|
||||
uint32_t primary_identifier = (spec.item_code[0] << 16) | (spec.item_code[1] << 8) | spec.item_code[2];
|
||||
if (primary_identifier == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject::list_type spec_list;
|
||||
|
||||
auto frac = reduce_fraction<uint64_t>(spec.probability, 0x100000000);
|
||||
spec_list.emplace_back(make_json_str(string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second)));
|
||||
spec_list.emplace_back(make_json_int(primary_identifier));
|
||||
|
||||
area_list.emplace_back(make_json_list(std::move(spec_list)));
|
||||
area_list.emplace_back(JSON::list({string_printf("%" PRIu64 "/%" PRIu64, frac.first, frac.second), primary_identifier}));
|
||||
}
|
||||
|
||||
if (!area_list.empty()) {
|
||||
collection_dict.emplace(
|
||||
string_printf("Box-%s", name_for_area(episode, area)),
|
||||
make_json_list(std::move(area_list)));
|
||||
std::move(area_list));
|
||||
}
|
||||
}
|
||||
|
||||
if (!collection_dict.empty()) {
|
||||
section_id_dict.emplace(name_for_section_id(section_id), make_json_dict(std::move(collection_dict)));
|
||||
section_id_dict.emplace(name_for_section_id(section_id), std::move(collection_dict));
|
||||
}
|
||||
}
|
||||
difficulty_dict.emplace(token_name_for_difficulty(difficulty), make_json_dict(std::move(section_id_dict)));
|
||||
difficulty_dict.emplace(token_name_for_difficulty(difficulty), std::move(section_id_dict));
|
||||
}
|
||||
episodes_dict.emplace(token_name_for_episode(episode), make_json_dict(std::move(difficulty_dict)));
|
||||
episodes_dict.emplace(token_name_for_episode(episode), std::move(difficulty_dict));
|
||||
}
|
||||
|
||||
JSONObject::dict_type root_dict;
|
||||
root_dict.emplace("Normal", make_json_dict(std::move(episodes_dict)));
|
||||
auto root_json = make_json_dict(std::move(root_dict));
|
||||
string json_data = root_json->serialize(
|
||||
JSONObject::SerializeOption::FORMAT |
|
||||
JSONObject::SerializeOption::HEX_INTEGERS |
|
||||
JSONObject::SerializeOption::SORT_DICT_KEYS);
|
||||
write_output_data(json_data.data(), json_data.size());
|
||||
auto root_json = JSON::dict({{"Normal", std::move(episodes_dict)}});
|
||||
string json_data = root_json.serialize(
|
||||
JSON::SerializeOption::FORMAT |
|
||||
JSON::SerializeOption::HEX_INTEGERS |
|
||||
JSON::SerializeOption::SORT_DICT_KEYS);
|
||||
|
||||
write_output_data(json_data.data(), json_data.size());
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+17
-24
@@ -33,20 +33,20 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
: root_dir(root_dir) {
|
||||
|
||||
string metadata_cache_filename = root_dir + "/.metadata-cache.json";
|
||||
shared_ptr<JSONObject> metadata_cache_json;
|
||||
JSON metadata_cache_json;
|
||||
try {
|
||||
string metadata_text = load_file(metadata_cache_filename);
|
||||
metadata_cache_json = JSONObject::parse(metadata_text);
|
||||
metadata_cache_json = JSON::parse(metadata_text);
|
||||
patch_index_log.info("Loaded patch metadata cache from %s", metadata_cache_filename.c_str());
|
||||
} catch (const exception& e) {
|
||||
metadata_cache_json = make_json_dict({});
|
||||
metadata_cache_json = JSON::dict();
|
||||
patch_index_log.warning("Cannot load patch metadata cache from %s: %s", metadata_cache_filename.c_str(), e.what());
|
||||
}
|
||||
|
||||
// Assuming it's rare for patch files to change, we skip writing the metadata
|
||||
// cache if no files were changed at all (which should usually be the case)
|
||||
bool should_write_metadata_cache = false;
|
||||
shared_ptr<JSONObject> new_metadata_cache_json = make_json_dict({});
|
||||
JSON new_metadata_cache_json = JSON::dict();
|
||||
|
||||
vector<string> path_directories;
|
||||
function<void(const string&)> collect_dir = [&](const string& dir) -> void {
|
||||
@@ -75,12 +75,11 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
f->name = item;
|
||||
|
||||
string compute_crc32s_message; // If not empty, should compute crc32s
|
||||
shared_ptr<JSONObject> cache_item_json;
|
||||
JSON cache_item_json;
|
||||
try {
|
||||
cache_item_json = metadata_cache_json->at(relative_item_path);
|
||||
auto cache_item = metadata_cache_json->at(relative_item_path)->as_list();
|
||||
uint64_t cached_size = cache_item.at(0)->as_int();
|
||||
uint64_t cached_mtime = cache_item.at(1)->as_int();
|
||||
cache_item_json = metadata_cache_json.at(relative_item_path);
|
||||
uint64_t cached_size = cache_item_json.get_int(0);
|
||||
uint64_t cached_mtime = cache_item_json.get_int(1);
|
||||
if (static_cast<uint64_t>(st.st_mtime) != cached_mtime) {
|
||||
throw runtime_error("file has been modified");
|
||||
}
|
||||
@@ -88,8 +87,8 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
throw runtime_error("file size has changed");
|
||||
}
|
||||
f->size = cached_size;
|
||||
f->crc32 = cache_item.at(2)->as_int();
|
||||
for (const auto& chunk_crc32_json : cache_item.at(3)->as_list()) {
|
||||
f->crc32 = cache_item_json.get_int(2);
|
||||
for (const auto& chunk_crc32_json : cache_item_json.get_list(3)) {
|
||||
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
|
||||
}
|
||||
|
||||
@@ -106,25 +105,19 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
}
|
||||
|
||||
// File was modified or cache item was missing; make a new cache item
|
||||
vector<shared_ptr<JSONObject>> chunk_crcs_item;
|
||||
auto chunk_crcs_item = JSON::list();
|
||||
for (uint32_t chunk_crc : f->chunk_crcs) {
|
||||
chunk_crcs_item.emplace_back(make_json_int(chunk_crc));
|
||||
chunk_crcs_item.emplace_back(chunk_crc);
|
||||
}
|
||||
vector<shared_ptr<JSONObject>> new_cache_item({
|
||||
make_json_int(f->size),
|
||||
make_json_int(st.st_mtime),
|
||||
make_json_int(f->crc32),
|
||||
make_json_list(std::move(chunk_crcs_item)),
|
||||
});
|
||||
new_metadata_cache_json->as_dict().emplace(
|
||||
relative_item_path, make_json_list(std::move(new_cache_item)));
|
||||
new_metadata_cache_json.emplace(
|
||||
relative_item_path, JSON::list({f->size, st.st_mtime, f->crc32, std::move(chunk_crcs_item)}));
|
||||
should_write_metadata_cache = true;
|
||||
|
||||
} else {
|
||||
// File was not modified and cache item was valid; just use the
|
||||
// existing cache item
|
||||
new_metadata_cache_json->as_dict().emplace(
|
||||
relative_item_path, cache_item_json);
|
||||
new_metadata_cache_json.emplace(
|
||||
relative_item_path, std::move(cache_item_json));
|
||||
}
|
||||
|
||||
this->files_by_patch_order.emplace_back(f);
|
||||
@@ -148,7 +141,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
|
||||
if (should_write_metadata_cache) {
|
||||
try {
|
||||
save_file(metadata_cache_filename, new_metadata_cache_json->serialize());
|
||||
save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
|
||||
patch_index_log.info("Saved patch metadata cache to %s", metadata_cache_filename.c_str());
|
||||
} catch (const exception& e) {
|
||||
patch_index_log.warning("Cannot save patch metadata cache to %s: %s", metadata_cache_filename.c_str(), e.what());
|
||||
|
||||
+9
-10
@@ -21,14 +21,13 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
QuestCategoryIndex::Category::Category(uint32_t category_id, std::shared_ptr<const JSONObject> json)
|
||||
QuestCategoryIndex::Category::Category(uint32_t category_id, const JSON& json)
|
||||
: category_id(category_id) {
|
||||
const auto& l = json->as_list();
|
||||
this->flags = l.at(0)->as_int();
|
||||
this->type = l.at(1)->as_string().at(0);
|
||||
this->short_token = l.at(2)->as_string();
|
||||
this->name = decode_sjis(l.at(3)->as_string());
|
||||
this->description = decode_sjis(l.at(4)->as_string());
|
||||
this->flags = json.get_int(0);
|
||||
this->type = json.get_string(1).at(0);
|
||||
this->short_token = json.get_string(2);
|
||||
this->name = decode_sjis(json.get_string(3));
|
||||
this->description = decode_sjis(json.get_string(4));
|
||||
}
|
||||
|
||||
bool QuestCategoryIndex::Category::matches_flags(uint8_t request) const {
|
||||
@@ -40,10 +39,10 @@ bool QuestCategoryIndex::Category::matches_flags(uint8_t request) const {
|
||||
return request & this->flags;
|
||||
}
|
||||
|
||||
QuestCategoryIndex::QuestCategoryIndex(std::shared_ptr<const JSONObject> json) {
|
||||
QuestCategoryIndex::QuestCategoryIndex(const JSON& json) {
|
||||
uint32_t next_category_id = 1;
|
||||
for (const auto& it : json->as_list()) {
|
||||
this->categories.emplace_back(next_category_id++, it);
|
||||
for (const auto& it : json.as_list()) {
|
||||
this->categories.emplace_back(next_category_id++, *it);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -30,14 +30,14 @@ struct QuestCategoryIndex {
|
||||
std::u16string name;
|
||||
std::u16string description;
|
||||
|
||||
explicit Category(uint32_t category_id, std::shared_ptr<const JSONObject> json);
|
||||
explicit Category(uint32_t category_id, const JSON& json);
|
||||
|
||||
bool matches_flags(uint8_t request) const;
|
||||
};
|
||||
|
||||
std::vector<Category> categories;
|
||||
|
||||
explicit QuestCategoryIndex(std::shared_ptr<const JSONObject> json);
|
||||
explicit QuestCategoryIndex(const JSON& json);
|
||||
|
||||
const Category& find(char type, const std::string& short_token) const;
|
||||
const Category& at(uint32_t category_id) const;
|
||||
|
||||
+12
-13
@@ -170,8 +170,8 @@ const RELRareItemSet::Table& RELRareItemSet::get_table(
|
||||
return tables[(ep_index * 10 * 4) + (difficulty * 10) + secid];
|
||||
}
|
||||
|
||||
JSONRareItemSet::JSONRareItemSet(std::shared_ptr<const JSONObject> json) {
|
||||
for (const auto& mode_it : json->as_dict()) {
|
||||
JSONRareItemSet::JSONRareItemSet(const JSON& json) {
|
||||
for (const auto& mode_it : json.as_dict()) {
|
||||
static const unordered_map<string, GameMode> mode_keys(
|
||||
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
|
||||
GameMode mode = mode_keys.at(mode_it.first);
|
||||
@@ -207,14 +207,13 @@ JSONRareItemSet::JSONRareItemSet(std::shared_ptr<const JSONObject> json) {
|
||||
}
|
||||
|
||||
for (const auto& spec_json : item_it.second->as_list()) {
|
||||
auto& spec_list = spec_json->as_list();
|
||||
auto& d = target->emplace_back();
|
||||
|
||||
auto prob_desc = spec_list.at(0);
|
||||
if (prob_desc->is_int()) {
|
||||
d.probability = spec_list.at(0)->as_int();
|
||||
} else if (prob_desc->is_string()) {
|
||||
auto tokens = split(prob_desc->as_string(), '/');
|
||||
auto prob_desc = spec_json->at(0);
|
||||
if (prob_desc.is_int()) {
|
||||
d.probability = prob_desc.as_int();
|
||||
} else if (prob_desc.is_string()) {
|
||||
auto tokens = split(prob_desc.as_string(), '/');
|
||||
if (tokens.size() != 2) {
|
||||
throw runtime_error("invalid probability specification");
|
||||
}
|
||||
@@ -227,14 +226,14 @@ JSONRareItemSet::JSONRareItemSet(std::shared_ptr<const JSONObject> json) {
|
||||
}
|
||||
}
|
||||
|
||||
auto item_desc = spec_list.at(1);
|
||||
if (item_desc->is_int()) {
|
||||
uint32_t item_code = spec_list.at(1)->as_int();
|
||||
auto item_desc = spec_json->at(1);
|
||||
if (item_desc.is_int()) {
|
||||
uint32_t item_code = item_desc.as_int();
|
||||
d.item_code[0] = (item_code >> 16) & 0xFF;
|
||||
d.item_code[1] = (item_code >> 8) & 0xFF;
|
||||
d.item_code[2] = item_code & 0xFF;
|
||||
} else if (item_desc->is_string()) {
|
||||
ItemData data(item_desc->as_string());
|
||||
} else if (item_desc.is_string()) {
|
||||
ItemData data(item_desc.as_string());
|
||||
d.item_code[0] = data.data1[0];
|
||||
d.item_code[1] = data.data1[1];
|
||||
d.item_code[2] = data.data1[2];
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ private:
|
||||
|
||||
class JSONRareItemSet : public RareItemSet {
|
||||
public:
|
||||
JSONRareItemSet(std::shared_ptr<const JSONObject> json);
|
||||
explicit JSONRareItemSet(const JSON& json);
|
||||
virtual ~JSONRareItemSet() = default;
|
||||
|
||||
virtual std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
|
||||
|
||||
+10
-3
@@ -231,6 +231,12 @@ static void send_main_menu(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
}
|
||||
|
||||
void on_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
if (c->flags & Client::Flag::IS_EPISODE_3) {
|
||||
auto team = s->ep3_tournament_index->team_for_serial_number(c->license->serial_number);
|
||||
auto tourn = team ? team->tournament.lock() : nullptr;
|
||||
c->ep3_tournament_team = team;
|
||||
}
|
||||
|
||||
// On the BB data server, this function is called only on the last connection
|
||||
// (when we should send the ship select menu).
|
||||
if ((c->server_behavior == ServerBehavior::LOGIN_SERVER) ||
|
||||
@@ -243,8 +249,10 @@ void on_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
} else if (s->pre_lobby_event) {
|
||||
send_change_event(c, s->pre_lobby_event);
|
||||
}
|
||||
send_ep3_rank_update(c);
|
||||
|
||||
send_ep3_rank_update(s, c);
|
||||
send_get_player_info(c);
|
||||
|
||||
} else if (s->pre_lobby_event) {
|
||||
send_change_event(c, s->pre_lobby_event);
|
||||
}
|
||||
@@ -2498,9 +2506,8 @@ static void on_61_98(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// login sequence.
|
||||
if ((c->flags & Client::Flag::IS_EPISODE_3) && !(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) {
|
||||
send_ep3_card_list_update(s, c);
|
||||
auto team = s->ep3_tournament_index->team_for_serial_number(c->license->serial_number);
|
||||
auto team = c->ep3_tournament_team.lock();
|
||||
auto tourn = team ? team->tournament.lock() : nullptr;
|
||||
c->ep3_tournament_team = team;
|
||||
if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) {
|
||||
send_ep3_confirm_tournament_entry(s, c, tourn);
|
||||
}
|
||||
|
||||
@@ -480,9 +480,6 @@ static void on_change_area(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<CmdT>(data, size);
|
||||
if (!l->is_game()) {
|
||||
return;
|
||||
}
|
||||
c->area = cmd.area;
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
}
|
||||
|
||||
+10
-10
@@ -2209,9 +2209,9 @@ void send_ep3_media_update(
|
||||
send_command(c, 0xB9, 0x00, w.str());
|
||||
}
|
||||
|
||||
void send_ep3_rank_update(shared_ptr<Client> c) {
|
||||
S_RankUpdate_GC_Ep3_B7 cmd = {
|
||||
0, "\0\0\0\0\0\0\0\0\0\0\0", 1000000, 1000000, 0xFFFFFFFF};
|
||||
void send_ep3_rank_update(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
uint32_t meseta = s->ep3_infinite_meseta ? 1000000 : 0;
|
||||
S_RankUpdate_GC_Ep3_B7 cmd = {0, "\0\0\0\0\0\0\0\0\0\0\0", meseta, meseta, 0xFFFFFFFF};
|
||||
send_command_t(c, 0xB7, 0x00, cmd);
|
||||
}
|
||||
|
||||
@@ -2805,8 +2805,8 @@ void send_card_auction_if_all_clients_ready(
|
||||
num_cards = min<uint16_t>(num_cards, 0x14);
|
||||
|
||||
uint64_t distribution_size = 0;
|
||||
for (const auto& it : s->ep3_card_auction_pool) {
|
||||
distribution_size += it.second.first;
|
||||
for (const auto& e : s->ep3_card_auction_pool) {
|
||||
distribution_size += e.probability;
|
||||
}
|
||||
|
||||
auto card_index = (l->flags & Lobby::Flag::IS_EP3_TRIAL)
|
||||
@@ -2817,12 +2817,12 @@ void send_card_auction_if_all_clients_ready(
|
||||
cmd.points_available = s->ep3_card_auction_points;
|
||||
for (size_t z = 0; z < num_cards; z++) {
|
||||
uint64_t v = random_object<uint64_t>() % distribution_size;
|
||||
for (const auto& it : s->ep3_card_auction_pool) {
|
||||
if (v >= it.second.first) {
|
||||
v -= it.second.first;
|
||||
for (const auto& e : s->ep3_card_auction_pool) {
|
||||
if (v >= e.probability) {
|
||||
v -= e.probability;
|
||||
} else {
|
||||
cmd.entries[z].card_id = card_index->definition_for_name(it.first)->def.card_id.load();
|
||||
cmd.entries[z].min_price = it.second.second;
|
||||
cmd.entries[z].card_id = e.card_id;
|
||||
cmd.entries[z].min_price = e.min_price;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -332,7 +332,7 @@ void send_ep3_media_update(
|
||||
uint32_t type,
|
||||
uint32_t which,
|
||||
const std::string& compressed_data);
|
||||
void send_ep3_rank_update(std::shared_ptr<Client> c);
|
||||
void send_ep3_rank_update(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void send_ep3_card_battle_table_state(std::shared_ptr<Lobby> l, uint16_t table_number);
|
||||
void send_ep3_set_context_token(std::shared_ptr<Client> c, uint32_t context_token);
|
||||
|
||||
|
||||
+77
-138
@@ -26,6 +26,7 @@ ServerState::ServerState(const char* config_filename, bool is_replay)
|
||||
drops_enabled(true),
|
||||
episode_3_send_function_call_enabled(false),
|
||||
catch_handler_exceptions(true),
|
||||
ep3_infinite_meseta(true),
|
||||
ep3_behavior_flags(0),
|
||||
run_shell_behavior(RunShellBehavior::DEFAULT),
|
||||
cheat_mode_behavior(CheatModeBehavior::OFF_BY_DEFAULT),
|
||||
@@ -384,9 +385,8 @@ void ServerState::set_port_configuration(
|
||||
}
|
||||
}
|
||||
|
||||
void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
void ServerState::create_menus(const JSON& json) {
|
||||
config_log.info("Creating menus");
|
||||
const auto& d = config_json->as_dict();
|
||||
|
||||
shared_ptr<Menu> information_menu_v2(new Menu(MenuID::INFORMATION, u"Information"));
|
||||
shared_ptr<Menu> information_menu_v3(new Menu(MenuID::INFORMATION, u"Information"));
|
||||
@@ -398,13 +398,12 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
u"Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU);
|
||||
{
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("InformationMenuContents")->as_list()) {
|
||||
auto& v = item->as_list();
|
||||
information_menu_v2->items.emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
decode_sjis(v.at(1)->as_string()), 0);
|
||||
information_menu_v3->items.emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
decode_sjis(v.at(1)->as_string()), MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
|
||||
information_contents->emplace_back(decode_sjis(v.at(2)->as_string()));
|
||||
for (const auto& item : json.at("InformationMenuContents").as_list()) {
|
||||
u16string name = decode_sjis(item->get_string(0));
|
||||
u16string short_desc = decode_sjis(item->get_string(1));
|
||||
information_menu_v2->items.emplace_back(item_id, name, short_desc, 0);
|
||||
information_menu_v3->items.emplace_back(item_id, name, short_desc, MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
|
||||
information_contents->emplace_back(decode_sjis(item->get_string(2)));
|
||||
item_id++;
|
||||
}
|
||||
}
|
||||
@@ -417,9 +416,9 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
ret_pds.clear();
|
||||
|
||||
try {
|
||||
map<string, shared_ptr<JSONObject>> sorted_jsons;
|
||||
for (const auto& it : d.at(key)->as_dict()) {
|
||||
sorted_jsons.emplace(it.first, it.second);
|
||||
map<string, const JSON&> sorted_jsons;
|
||||
for (const auto& it : json.at(key).as_dict()) {
|
||||
sorted_jsons.emplace(it.first, *it.second);
|
||||
}
|
||||
|
||||
ret->items.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back",
|
||||
@@ -429,10 +428,9 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : sorted_jsons) {
|
||||
const string& netloc_str = item.second->as_string();
|
||||
const string& netloc_str = item.second.as_string();
|
||||
const string& description = "$C7Remote server:\n$C6" + netloc_str;
|
||||
ret->items.emplace_back(item_id, decode_sjis(item.first),
|
||||
decode_sjis(description), 0);
|
||||
ret->items.emplace_back(item_id, decode_sjis(item.first), decode_sjis(description), 0);
|
||||
ret_pds.emplace_back(parse_netloc(netloc_str));
|
||||
item_id++;
|
||||
}
|
||||
@@ -451,7 +449,7 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
this->proxy_destinations_xb, "ProxyDestinations-XB");
|
||||
|
||||
try {
|
||||
const string& netloc_str = d.at("ProxyDestination-Patch")->as_string();
|
||||
const string& netloc_str = json.get_string("ProxyDestination-Patch");
|
||||
this->proxy_destination_patch = parse_netloc(netloc_str);
|
||||
config_log.info("Patch server proxy is enabled with destination %s", netloc_str.c_str());
|
||||
for (auto& it : this->name_to_port_config) {
|
||||
@@ -464,7 +462,7 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
this->proxy_destination_patch.second = 0;
|
||||
}
|
||||
try {
|
||||
const string& netloc_str = d.at("ProxyDestination-BB")->as_string();
|
||||
const string& netloc_str = json.get_string("ProxyDestination-BB");
|
||||
this->proxy_destination_bb = parse_netloc(netloc_str);
|
||||
config_log.info("BB proxy is enabled with destination %s", netloc_str.c_str());
|
||||
for (auto& it : this->name_to_port_config) {
|
||||
@@ -477,18 +475,9 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
this->proxy_destination_bb.second = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
this->welcome_message = decode_sjis(d.at("WelcomeMessage")->as_string());
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->pc_patch_server_message = decode_sjis(d.at("PCPatchServerMessage")->as_string());
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->bb_patch_server_message = decode_sjis(d.at("BBPatchServerMessage")->as_string());
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
this->welcome_message = decode_sjis(json.get_string("WelcomeMessage", ""));
|
||||
this->pc_patch_server_message = decode_sjis(json.get_string("PCPatchServerMessage", ""));
|
||||
this->bb_patch_server_message = decode_sjis(json.get_string("BBPatchServerMessage", ""));
|
||||
}
|
||||
|
||||
shared_ptr<const string> ServerState::load_bb_file(
|
||||
@@ -558,33 +547,31 @@ void ServerState::collect_network_addresses() {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<JSONObject> ServerState::load_config() const {
|
||||
JSON ServerState::load_config() const {
|
||||
config_log.info("Loading configuration");
|
||||
return JSONObject::parse(load_file(this->config_filename));
|
||||
return JSON::parse(load_file(this->config_filename));
|
||||
}
|
||||
|
||||
static vector<PortConfiguration> parse_port_configuration(
|
||||
shared_ptr<const JSONObject> json) {
|
||||
static vector<PortConfiguration> parse_port_configuration(const JSON& json) {
|
||||
vector<PortConfiguration> ret;
|
||||
for (const auto& item_json_it : json->as_dict()) {
|
||||
auto item_list = item_json_it.second->as_list();
|
||||
for (const auto& item_json_it : json.as_dict()) {
|
||||
const auto& item_list = item_json_it.second;
|
||||
PortConfiguration& pc = ret.emplace_back();
|
||||
pc.name = item_json_it.first;
|
||||
pc.port = item_list[0]->as_int();
|
||||
pc.version = version_for_name(item_list[1]->as_string().c_str());
|
||||
pc.behavior = server_behavior_for_name(item_list[2]->as_string().c_str());
|
||||
pc.port = item_list->at(0).as_int();
|
||||
pc.version = version_for_name(item_list->at(1).as_string().c_str());
|
||||
pc.behavior = server_behavior_for_name(item_list->at(2).as_string().c_str());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ServerState::parse_config(shared_ptr<const JSONObject> config_json) {
|
||||
void ServerState::parse_config(const JSON& json) {
|
||||
config_log.info("Parsing configuration");
|
||||
const auto& d = config_json->as_dict();
|
||||
|
||||
this->name = decode_sjis(d.at("ServerName")->as_string());
|
||||
this->name = decode_sjis(json.at("ServerName").as_string());
|
||||
|
||||
try {
|
||||
this->username = d.at("User")->as_string();
|
||||
this->username = json.at("User").as_string();
|
||||
if (this->username == "$SUDO_USER") {
|
||||
const char* user_from_env = getenv("SUDO_USER");
|
||||
if (!user_from_env) {
|
||||
@@ -595,9 +582,9 @@ void ServerState::parse_config(shared_ptr<const JSONObject> config_json) {
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
this->set_port_configuration(parse_port_configuration(d.at("PortConfiguration")));
|
||||
this->set_port_configuration(parse_port_configuration(json.at("PortConfiguration")));
|
||||
|
||||
auto local_address_str = d.at("LocalAddress")->as_string();
|
||||
auto local_address_str = json.at("LocalAddress").as_string();
|
||||
try {
|
||||
this->local_address = this->all_addresses.at(local_address_str);
|
||||
string addr_str = string_for_address(this->local_address);
|
||||
@@ -609,7 +596,7 @@ void ServerState::parse_config(shared_ptr<const JSONObject> config_json) {
|
||||
}
|
||||
this->all_addresses.emplace("<local>", this->local_address);
|
||||
|
||||
auto external_address_str = d.at("ExternalAddress")->as_string();
|
||||
auto external_address_str = json.at("ExternalAddress").as_string();
|
||||
try {
|
||||
this->external_address = this->all_addresses.at(external_address_str);
|
||||
string addr_str = string_for_address(this->external_address);
|
||||
@@ -621,106 +608,50 @@ void ServerState::parse_config(shared_ptr<const JSONObject> config_json) {
|
||||
}
|
||||
this->all_addresses.emplace("<external>", this->external_address);
|
||||
|
||||
try {
|
||||
this->dns_server_port = d.at("DNSServerPort")->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
this->dns_server_port = 0;
|
||||
}
|
||||
this->dns_server_port = json.get_int("DNSServerPort", this->dns_server_port);
|
||||
|
||||
try {
|
||||
for (const auto& item : d.at("IPStackListen")->as_list()) {
|
||||
for (const auto& item : json.at("IPStackListen").as_list()) {
|
||||
this->ip_stack_addresses.emplace_back(item->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->ip_stack_debug = d.at("IPStackDebug")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
this->ip_stack_debug = json.get_bool("IPStackDebug", this->ip_stack_debug);
|
||||
this->allow_unregistered_users = json.get_bool("AllowUnregisteredUsers", this->allow_unregistered_users);
|
||||
this->item_tracking_enabled = json.get_bool("EnableItemTracking", this->item_tracking_enabled);
|
||||
this->drops_enabled = json.get_bool("EnableDrops", this->drops_enabled);
|
||||
this->episode_3_send_function_call_enabled = json.get_bool("EnableEpisode3SendFunctionCall", this->episode_3_send_function_call_enabled);
|
||||
this->catch_handler_exceptions = json.get_bool("CatchHandlerExceptions", this->catch_handler_exceptions);
|
||||
this->ep3_infinite_meseta = json.get_bool("Episode3InfiniteMeseta", this->ep3_infinite_meseta);
|
||||
this->proxy_allow_save_files = json.get_bool("ProxyAllowSaveFiles", this->proxy_allow_save_files);
|
||||
this->proxy_enable_login_options = json.get_bool("ProxyEnableLoginOptions", this->proxy_enable_login_options);
|
||||
this->ep3_behavior_flags = json.get_int("Episode3BehaviorFlags", this->ep3_behavior_flags);
|
||||
this->ep3_card_auction_points = json.get_int("CardAuctionPoints", this->ep3_card_auction_points);
|
||||
|
||||
try {
|
||||
this->allow_unregistered_users = d.at("AllowUnregisteredUsers")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
this->allow_unregistered_users = true;
|
||||
}
|
||||
|
||||
try {
|
||||
this->item_tracking_enabled = d.at("EnableItemTracking")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
this->item_tracking_enabled = true;
|
||||
}
|
||||
|
||||
try {
|
||||
this->drops_enabled = d.at("EnableDrops")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
this->drops_enabled = true;
|
||||
}
|
||||
|
||||
try {
|
||||
this->episode_3_send_function_call_enabled = d.at("EnableEpisode3SendFunctionCall")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
this->episode_3_send_function_call_enabled = false;
|
||||
}
|
||||
|
||||
try {
|
||||
this->catch_handler_exceptions = d.at("CatchHandlerExceptions")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
this->catch_handler_exceptions = true;
|
||||
}
|
||||
|
||||
try {
|
||||
this->proxy_allow_save_files = d.at("ProxyAllowSaveFiles")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
this->proxy_allow_save_files = true;
|
||||
}
|
||||
try {
|
||||
this->proxy_enable_login_options = d.at("ProxyEnableLoginOptions")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
this->proxy_enable_login_options = false;
|
||||
}
|
||||
|
||||
try {
|
||||
this->ep3_behavior_flags = d.at("Episode3BehaviorFlags")->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
this->ep3_behavior_flags = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
this->ep3_card_auction_points = d.at("CardAuctionPoints")->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
this->ep3_card_auction_points = 0;
|
||||
}
|
||||
try {
|
||||
auto i = d.at("CardAuctionSize");
|
||||
if (i->is_int()) {
|
||||
this->ep3_card_auction_min_size = i->as_int();
|
||||
const auto& i = json.at("CardAuctionSize");
|
||||
if (i.is_int()) {
|
||||
this->ep3_card_auction_min_size = i.as_int();
|
||||
this->ep3_card_auction_max_size = this->ep3_card_auction_min_size;
|
||||
} else {
|
||||
this->ep3_card_auction_min_size = i->as_list().at(0)->as_int();
|
||||
this->ep3_card_auction_max_size = i->as_list().at(1)->as_int();
|
||||
this->ep3_card_auction_min_size = i.at(0).as_int();
|
||||
this->ep3_card_auction_max_size = i.at(1).as_int();
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
this->ep3_card_auction_min_size = 0;
|
||||
this->ep3_card_auction_max_size = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const auto& it : d.at("CardAuctionPool")->as_dict()) {
|
||||
const auto& card_name = it.first;
|
||||
const auto& card_cfg_json = it.second->as_list();
|
||||
this->ep3_card_auction_pool.emplace(card_name, make_pair(card_cfg_json.at(0)->as_int(), card_cfg_json.at(1)->as_int()));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
for (const auto& it : json.get("CardAuctionPool", JSON::dict()).as_dict()) {
|
||||
this->ep3_card_auction_pool.emplace_back(
|
||||
CardAuctionPoolEntry{
|
||||
.probability = static_cast<uint64_t>(it.second->at(0).as_int()),
|
||||
.card_id = 0,
|
||||
.min_price = static_cast<uint16_t>(it.second->at(1).as_int()),
|
||||
.card_name = it.first});
|
||||
}
|
||||
|
||||
shared_ptr<JSONObject> log_levels_json;
|
||||
try {
|
||||
log_levels_json = d.at("LogLevels");
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
if (log_levels_json.get()) {
|
||||
set_log_levels_from_json(log_levels_json);
|
||||
}
|
||||
set_log_levels_from_json(json.get("LogLevels", JSON::dict()));
|
||||
|
||||
for (const string& filename : list_directory("system/blueburst/keys")) {
|
||||
if (!ends_with(filename, ".nsk")) {
|
||||
@@ -733,13 +664,14 @@ void ServerState::parse_config(shared_ptr<const JSONObject> config_json) {
|
||||
config_log.info("%zu Blue Burst key file(s) loaded", this->bb_private_keys.size());
|
||||
|
||||
try {
|
||||
bool run_shell = d.at("RunInteractiveShell")->as_bool();
|
||||
this->run_shell_behavior = run_shell ? ServerState::RunShellBehavior::ALWAYS : ServerState::RunShellBehavior::NEVER;
|
||||
this->run_shell_behavior = json.at("RunInteractiveShell").as_bool()
|
||||
? ServerState::RunShellBehavior::ALWAYS
|
||||
: ServerState::RunShellBehavior::NEVER;
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
try {
|
||||
const string& behavior = d.at("CheatModeBehavior")->as_string();
|
||||
const string& behavior = json.at("CheatModeBehavior").as_string();
|
||||
if (behavior == "Off") {
|
||||
this->cheat_mode_behavior = CheatModeBehavior::OFF;
|
||||
} else if (behavior == "OffByDefault") {
|
||||
@@ -753,8 +685,8 @@ void ServerState::parse_config(shared_ptr<const JSONObject> config_json) {
|
||||
}
|
||||
|
||||
try {
|
||||
auto v = d.at("LobbyEvent");
|
||||
uint8_t event = v->is_int() ? v->as_int() : event_for_name(v->as_string());
|
||||
auto v = json.at("LobbyEvent");
|
||||
uint8_t event = v.is_int() ? v.as_int() : event_for_name(v.as_string());
|
||||
this->pre_lobby_event = event;
|
||||
for (const auto& l : this->all_lobbies()) {
|
||||
l->event = event;
|
||||
@@ -762,13 +694,10 @@ void ServerState::parse_config(shared_ptr<const JSONObject> config_json) {
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
try {
|
||||
this->ep3_menu_song = d.at("Episode3MenuSong")->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
this->ep3_menu_song = json.get_int("Episode3MenuSong", this->ep3_menu_song);
|
||||
|
||||
try {
|
||||
this->quest_category_index.reset(new QuestCategoryIndex(d.at("QuestCategories")));
|
||||
this->quest_category_index.reset(new QuestCategoryIndex(json.at("QuestCategories")));
|
||||
} catch (const exception& e) {
|
||||
throw runtime_error(string_printf(
|
||||
"QuestCategories is missing or invalid in config.json (%s) - see config.example.json for an example", e.what()));
|
||||
@@ -825,7 +754,7 @@ void ServerState::load_level_table() {
|
||||
void ServerState::load_item_tables() {
|
||||
try {
|
||||
config_log.info("Loading JSON rare item table");
|
||||
auto json = JSONObject::parse(load_file("system/blueburst/rare-table.json"));
|
||||
auto json = JSON::parse(load_file("system/blueburst/rare-table.json"));
|
||||
this->rare_item_set.reset(new JSONRareItemSet(json));
|
||||
} catch (const exception& e) {
|
||||
config_log.info("Failed to load JSON rare item table: %s", e.what());
|
||||
@@ -900,6 +829,16 @@ void ServerState::load_ep3_data() {
|
||||
this->ep3_tournament_index.reset(new Episode3::TournamentIndex(
|
||||
this->ep3_map_index, this->ep3_com_deck_index, tournament_state_filename, true));
|
||||
}
|
||||
|
||||
config_log.info("Resolving Episode 3 card auction pool");
|
||||
for (auto& e : this->ep3_card_auction_pool) {
|
||||
try {
|
||||
const auto& card = this->ep3_card_index->definition_for_name(e.card_name);
|
||||
e.card_id = card->def.card_id;
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" does not exist", e.card_name.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerState::load_quest_index() {
|
||||
|
||||
+11
-4
@@ -61,6 +61,7 @@ struct ServerState {
|
||||
bool drops_enabled;
|
||||
bool episode_3_send_function_call_enabled;
|
||||
bool catch_handler_exceptions;
|
||||
bool ep3_infinite_meseta;
|
||||
uint32_t ep3_behavior_flags;
|
||||
RunShellBehavior run_shell_behavior;
|
||||
CheatModeBehavior cheat_mode_behavior;
|
||||
@@ -92,7 +93,13 @@ struct ServerState {
|
||||
uint16_t ep3_card_auction_points;
|
||||
uint16_t ep3_card_auction_min_size;
|
||||
uint16_t ep3_card_auction_max_size;
|
||||
std::unordered_map<std::string, std::pair<uint64_t, uint16_t>> ep3_card_auction_pool;
|
||||
struct CardAuctionPoolEntry {
|
||||
uint64_t probability;
|
||||
uint16_t card_id;
|
||||
uint16_t min_price;
|
||||
std::string card_name;
|
||||
};
|
||||
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
|
||||
|
||||
std::shared_ptr<LicenseManager> license_manager;
|
||||
|
||||
@@ -169,9 +176,9 @@ struct ServerState {
|
||||
const std::string& gsl_filename = "",
|
||||
const std::string& bb_directory_filename = "") const;
|
||||
|
||||
std::shared_ptr<JSONObject> load_config() const;
|
||||
JSON load_config() const;
|
||||
void collect_network_addresses();
|
||||
void parse_config(std::shared_ptr<const JSONObject> config_json);
|
||||
void parse_config(const JSON& config_json);
|
||||
void load_licenses();
|
||||
void load_patch_indexes();
|
||||
void load_battle_params();
|
||||
@@ -181,5 +188,5 @@ struct ServerState {
|
||||
void load_quest_index();
|
||||
void compile_functions();
|
||||
void load_dol_files();
|
||||
void create_menus(std::shared_ptr<const JSONObject> config_json);
|
||||
void create_menus(const JSON& config_json);
|
||||
};
|
||||
|
||||
+57
-30
@@ -163,69 +163,70 @@
|
||||
// Specify which kinds of logging you want to be enabled. This allows you to
|
||||
// make the terminal more or less noisy when players are connected, so you can
|
||||
// see only the log messages you care about. The log levels are, in decreasing
|
||||
// order of verbosity, "debug", "info", "warning", "error", and "disabled".
|
||||
// order of verbosity, "DEBUG", "INFO", "WARNING", "ERROR", and "DISABLED".
|
||||
"LogLevels": {
|
||||
// AX messages are messages sent to the terminal with the $ax command.
|
||||
"AXMessages": "info",
|
||||
"AXMessages": "INFO",
|
||||
// Channel exceptions are messages about clients disconnecting unexpectedly,
|
||||
// or other unexpected network-level events.
|
||||
"ChannelExceptions": "info",
|
||||
"ChannelExceptions": "INFO",
|
||||
// Client messages describe events that are specific to a single client's
|
||||
// connection or game state.
|
||||
"Clients": "info",
|
||||
"Clients": "INFO",
|
||||
// Command data messages show the raw data for all commands sent and
|
||||
// received, on both the game server and proxy server. If stderr is a
|
||||
// terminal, these messages are colored as well; green is for commands sent
|
||||
// by the client, yellow is for commands sent by newserv, and red is for
|
||||
// commands sent by the remote server (for proxy server sessions).
|
||||
"CommandData": "info",
|
||||
// commands sent by the remote server (in proxy server sessions).
|
||||
"CommandData": "INFO",
|
||||
// Config messages describe server-wide events, and generally only occur
|
||||
// during the startup procedure.
|
||||
"Config": "info",
|
||||
"Config": "INFO",
|
||||
// DNS server messages describe erroneous queries that the DNS server does
|
||||
// not respond to. Normal DNS queries do not generate any log messages.
|
||||
"DNSServer": "info",
|
||||
"DNSServer": "INFO",
|
||||
// Function compiler messages describe PowerPC function call assembly
|
||||
// events, which generally only occur during startup.
|
||||
"FunctionCompiler": "info",
|
||||
"FunctionCompiler": "INFO",
|
||||
// IP stack simulator messages describe clients connecting and disconnecting
|
||||
// via the IP stack interface, and errors that occur at the simulated
|
||||
// network level within the simulator. This log is fairly verbose at the
|
||||
// info level, so by default we suppress those messages.
|
||||
"IPStackSimulator": "warning",
|
||||
"IPStackSimulator": "WARNING",
|
||||
// License manager messages describe the creation of new license files.
|
||||
"LicenseManager": "info",
|
||||
"LicenseManager": "INFO",
|
||||
// Lobby messages describe creation and deletion of lobbies and games, as
|
||||
// well as item tracking events within games.
|
||||
"Lobbies": "info",
|
||||
"Lobbies": "INFO",
|
||||
// Patch file index messages describe finding and preloading the patch files
|
||||
// available for download to BB and PC clients.
|
||||
"PatchFileIndex": "info",
|
||||
"PatchFileIndex": "INFO",
|
||||
// Player data messages describe the loading and saving of player and
|
||||
// account data files.
|
||||
"PlayerData": "info",
|
||||
"PlayerData": "INFO",
|
||||
// Proxy server messages describe clients connecting and disconnecting from
|
||||
// the proxy server, as well as events that occur in each session.
|
||||
"ProxyServer": "info",
|
||||
"ProxyServer": "INFO",
|
||||
// Replay messages are generated when replaying a session log (usually
|
||||
// during functional testing).
|
||||
"Replay": "info",
|
||||
"Replay": "INFO",
|
||||
// Game server messages describe clients connecting and disconnecting from
|
||||
// the game server.
|
||||
"GameServer": "info",
|
||||
"GameServer": "INFO",
|
||||
// Static game data messages describe the loading of any kind of game data.
|
||||
"StaticGameData": "info",
|
||||
"StaticGameData": "INFO",
|
||||
},
|
||||
|
||||
// By default, the server only allows users who are registered in the license
|
||||
// file to connect. By enabling this option, all users will be allowed to
|
||||
// connect. Each time a user connects with an unregistered license (serial
|
||||
// number / access key combination, or username/password combination on BB), a
|
||||
// temporary license is created for them, which lasts until the server is
|
||||
// shut down. This license is not saved to the license file. For BB, player
|
||||
// If this option is disabled, the server only allows users who are registered
|
||||
// in the license file to connect. If this is enabled, all users will be
|
||||
// allowed to connect even if they're not registered in the license file. Each
|
||||
// time a user connects with an unregistered license (serial number / access
|
||||
// key combination, or username/password combination on BB), a temporary
|
||||
// license is created for them, which lasts until the server is shut down.
|
||||
// These temporary licenses are not saved to the license file. For BB, player
|
||||
// and account data is still saved on the server, even for users with
|
||||
// temporary licenses.
|
||||
"AllowUnregisteredUsers": false,
|
||||
"AllowUnregisteredUsers": true,
|
||||
|
||||
// User to run the server as. If present, newserv will attempt to switch to
|
||||
// this user's permissions after loading its configuration and opening
|
||||
@@ -282,6 +283,13 @@
|
||||
// they are at the newserv main menu. If set, this value must be an integer.
|
||||
// "Episode3MenuSong": 0,
|
||||
|
||||
// Episode 3 Meseta behavior. If enabled (which is the default), all players
|
||||
// have infinite Meseta, which effectively makes jukebox songs and Pinz's Shop
|
||||
// free. If disabled, all players have no Meseta, which makes these features
|
||||
// inaccessible. Proper Meseta behavior will be implemented at some point in
|
||||
// the future.
|
||||
"Episode3InfiniteMeseta": true,
|
||||
|
||||
// Episode 3 battle behavior flags. When set to zero, battles behave as they
|
||||
// did on the original Sega servers. Combinations of behaviors can be enabled
|
||||
// by bitwise-OR'ing together the following values:
|
||||
@@ -315,13 +323,32 @@
|
||||
// enter an auction, the auctioned cards are drawn with replacement from the
|
||||
// distribution specified here.
|
||||
"CardAuctionPoints": 30,
|
||||
"CardAuctionSize": [2, 4],
|
||||
"CardAuctionSize": [6, 8],
|
||||
"CardAuctionPool": {
|
||||
// "CardName": [RelativeFrequency, MinPrice]
|
||||
"Red Sword": [500, 8],
|
||||
"Hildeblue": [400, 10],
|
||||
"Grants": [300, 15],
|
||||
"Megid": [700, 6],
|
||||
"Beat+": [500, 4],
|
||||
"Berserk+": [800, 7],
|
||||
"Biboo": [500, 6],
|
||||
"Chaos Bringer": [900, 3],
|
||||
"Charity+": [800, 4],
|
||||
"Counter+": [800, 9],
|
||||
"Dark Flow": [600, 11],
|
||||
"Dolmolm": [500, 8],
|
||||
"Egg Rappy": [800, 2],
|
||||
"Epsilon": [300, 7],
|
||||
"Gather+": [800, 8],
|
||||
"Gibbles+": [700, 6],
|
||||
"Grants": [200, 4],
|
||||
"Hallo Rappy+": [800, 3],
|
||||
"Heart of Poumn": [600, 5],
|
||||
"Heaven Punisher": [500, 6],
|
||||
"Lavis Cannon": [400, 7],
|
||||
"Piety": [800, 5],
|
||||
"Rag Rappy+": [800, 2],
|
||||
"Rich+": [700, 2],
|
||||
"Snail Pace": [900, 3],
|
||||
"Striker of Chao": [500, 5],
|
||||
"Thread+": [800, 8],
|
||||
},
|
||||
|
||||
// Quest category configuration. See README.md for information on how quest
|
||||
|
||||
+14
-14
@@ -58,20 +58,20 @@
|
||||
},
|
||||
|
||||
"LogLevels": {
|
||||
"AXMessages": "info",
|
||||
"ChannelExceptions": "info",
|
||||
"Clients": "info",
|
||||
"CommandData": "info",
|
||||
"Config": "info",
|
||||
"DNSServer": "info",
|
||||
"FunctionCompiler": "info",
|
||||
"IPStackSimulator": "info",
|
||||
"LicenseManager": "info",
|
||||
"Lobbies": "info",
|
||||
"PlayerData": "info",
|
||||
"ProxyServer": "info",
|
||||
"GameServer": "info",
|
||||
"StaticGameData": "info",
|
||||
"AXMessages": "INFO",
|
||||
"ChannelExceptions": "INFO",
|
||||
"Clients": "INFO",
|
||||
"CommandData": "INFO",
|
||||
"Config": "INFO",
|
||||
"DNSServer": "INFO",
|
||||
"FunctionCompiler": "INFO",
|
||||
"IPStackSimulator": "INFO",
|
||||
"LicenseManager": "INFO",
|
||||
"Lobbies": "INFO",
|
||||
"PlayerData": "INFO",
|
||||
"ProxyServer": "INFO",
|
||||
"GameServer": "INFO",
|
||||
"StaticGameData": "INFO",
|
||||
},
|
||||
|
||||
"AllowUnregisteredUsers": true,
|
||||
|
||||
Reference in New Issue
Block a user