split Episode3::DataIndex into multiple structures

This commit is contained in:
Martin Michelsen
2023-08-13 23:12:27 -07:00
parent 7e55719983
commit 87118049ab
36 changed files with 416 additions and 328 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ add_executable(newserv
src/Episode3/BattleRecord.cc src/Episode3/BattleRecord.cc
src/Episode3/Card.cc src/Episode3/Card.cc
src/Episode3/CardSpecial.cc src/Episode3/CardSpecial.cc
src/Episode3/DataIndex.cc src/Episode3/DataIndexes.cc
src/Episode3/DeckState.cc src/Episode3/DeckState.cc
src/Episode3/MapState.cc src/Episode3/MapState.cc
src/Episode3/PlayerState.cc src/Episode3/PlayerState.cc
+4 -2
View File
@@ -68,6 +68,7 @@ Current known issues / missing features / things to do:
- Implement the C5 (battle/challenge records) command. - Implement the C5 (battle/challenge records) command.
- Implement choice search. - Implement choice search.
- Episode 3 bugs - Episode 3 bugs
- Trial Edition can't select maps in battle setup. Fix this.
- Fix behavior when joining a spectator team after the beginning of a battle. - Fix behavior when joining a spectator team after the beginning of a battle.
- Disconnecting during a match turns you into a COM if there are other humans in the match, even if the match is part of a tournament. This may be incorrect behavior for tournaments. - Disconnecting during a match turns you into a COM if there are other humans in the match, even if the match is part of a tournament. This may be incorrect behavior for tournaments.
- Disconnecting during a tournament when there are no other humans in the match simply cancels the match (so it can be replayed) instead of forfeiting, which is almost certainly incorrect behavior. (Then again, no one likes losing tournaments to COMs...) - Disconnecting during a tournament when there are no other humans in the match simply cancels the match (so it can be replayed) instead of forfeiting, which is almost certainly incorrect behavior. (Then again, no one likes losing tournaments to COMs...)
@@ -89,7 +90,7 @@ newserv supports several versions of PSO. Specifically:
| GC Ep1&2 Trial | Untested (2) | Untested (2) | Untested (2) | Untested (2) | | GC Ep1&2 Trial | Untested (2) | Untested (2) | Untested (2) | Untested (2) |
| GC Ep1&2 | Yes | Yes | Yes | Yes | | GC Ep1&2 | Yes | Yes | Yes | Yes |
| GC Ep1&2 Plus | Yes | Yes | Yes | Yes | | GC Ep1&2 Plus | Yes | Yes | Yes | Yes |
| GC Ep3 Trial | Yes | Yes | Yes | Yes | | GC Ep3 Trial | Yes | Yes | Partial (5) | Yes |
| GC Ep3 | Yes | Yes | Yes | Yes | | GC Ep3 | Yes | Yes | Yes | Yes |
| XBOX Ep1&2 | Untested (2) | Untested (2) | Untested (2) | Untested (2) | | XBOX Ep1&2 | Untested (2) | Untested (2) | Untested (2) | Untested (2) |
| BB (vanilla) | Yes | Yes | Yes (3) | Yes | | BB (vanilla) | Yes | Yes | Yes (3) | Yes |
@@ -100,6 +101,7 @@ newserv supports several versions of PSO. Specifically:
2. *newserv's implementations of these versions are based on disassembly of the client executables and have never been tested.* 2. *newserv's implementations of these versions are based on disassembly of the client executables and have never been tested.*
3. *BB games are mostly playable, but there are still some unimplemented features (for example, some quests that use rare commands may not work). Please submit a GitHub issue if you find something that doesn't work.* 3. *BB games are mostly playable, but there are still some unimplemented features (for example, some quests that use rare commands may not work). Please submit a GitHub issue if you find something that doesn't work.*
4. *Support for PSO Dreamcast Trial Edition and the December 2000 prototype is somewhat incomplete and probably never will be complete. These versions are rather unstable and seem to crash often, but it's not obvious whether it's because they're prototypes or because newserv sends data they can't handle.* 4. *Support for PSO Dreamcast Trial Edition and the December 2000 prototype is somewhat incomplete and probably never will be complete. These versions are rather unstable and seem to crash often, but it's not obvious whether it's because they're prototypes or because newserv sends data they can't handle.*
5. *Creating a game works, but choosing a map during battle setup causes the Trial Edition client to crash. This is likely due to the trial version's map format being slightly different from the final version's map format.*
## Setup ## Setup
@@ -201,7 +203,7 @@ Episode 3 state and game data is stored in the system/ep3 directory. The files i
* maps-free/ and maps-quest/: Online free battle and quest maps (.mnm/.bin/.mnmd/.bind files). Free battle and quest files have exactly the same format; the only difference between the files in these directories is which section of the menu they will appear in on the client. * maps-free/ and maps-quest/: Online free battle and quest maps (.mnm/.bin/.mnmd/.bind files). Free battle and quest files have exactly the same format; the only difference between the files in these directories is which section of the menu they will appear in on the client.
* tournament-state.json: State of all active tournaments. This file is automatically written when any tournament changes state for any reason (e.g. a tournament is created/started/deleted or a match is resolved). * tournament-state.json: State of all active tournaments. This file is automatically written when any tournament changes state for any reason (e.g. a tournament is created/started/deleted or a match is resolved).
There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndex.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available. There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndexes.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available.
Like quests, Episode 3 card definitions, maps, and quests are cached in memory. If you've changed any of these files, you can run `reload ep3` in the interactive shell to make the changes take effect without restarting the server. Like quests, Episode 3 card definitions, maps, and quests are cached in memory. If you've changed any of these files, you can run `reload ep3` in the interactive shell to make the changes take effect without restarting the server.
+3 -3
View File
@@ -5,7 +5,7 @@
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include "Episode3/DataIndex.hh" #include "Episode3/DataIndexes.hh"
#include "Episode3/DeckState.hh" #include "Episode3/DeckState.hh"
#include "Episode3/MapState.hh" #include "Episode3/MapState.hh"
#include "Episode3/PlayerStateSubordinates.hh" #include "Episode3/PlayerStateSubordinates.hh"
@@ -5178,8 +5178,8 @@ struct G_MapList_GC_Ep3_6xB6x40 {
le_uint16_t compressed_data_size; le_uint16_t compressed_data_size;
le_uint16_t unused; le_uint16_t unused;
// PRS-compressed map list data follows here. newserv generates this from the // PRS-compressed map list data follows here. newserv generates this from the
// map index at startup time; see the MapList struct in Episode3/DataIndex.hh // map index at startup; see the MapList struct in Episode3/DataIndexes.hh
// and Episode3::DataIndex::get_compressed_map_list for details on the format. // and Episode3::MapIndex::get_compressed_map_list for details on the format.
} __packed__; } __packed__;
struct G_MapData_GC_Ep3_6xB6x41 { struct G_MapData_GC_Ep3_6xB6x41 {
+1 -1
View File
@@ -133,7 +133,7 @@ uint16_t AssistServer::card_id_for_card_ref(uint16_t card_ref) const {
return this->server()->card_id_for_card_ref(card_ref); return this->server()->card_id_for_card_ref(card_ref);
} }
shared_ptr<const DataIndex::CardEntry> AssistServer::definition_for_card_id( shared_ptr<const CardIndex::CardEntry> AssistServer::definition_for_card_id(
uint16_t card_id) const { uint16_t card_id) const {
return this->server()->definition_for_card_id(card_id); return this->server()->definition_for_card_id(card_id);
} }
+4 -4
View File
@@ -5,7 +5,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "DataIndex.hh" #include "DataIndexes.hh"
#include "DeckState.hh" #include "DeckState.hh"
#include "PlayerState.hh" #include "PlayerState.hh"
@@ -24,7 +24,7 @@ public:
std::shared_ptr<const Server> server() const; std::shared_ptr<const Server> server() const;
uint16_t card_id_for_card_ref(uint16_t card_ref) const; uint16_t card_id_for_card_ref(uint16_t card_ref) const;
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_id(uint16_t card_id) const; std::shared_ptr<const CardIndex::CardEntry> definition_for_card_id(uint16_t card_id) const;
uint32_t compute_num_assist_effects_for_client(uint16_t client_id); uint32_t compute_num_assist_effects_for_client(uint16_t client_id);
uint32_t compute_num_assist_effects_for_team(uint32_t team_id); uint32_t compute_num_assist_effects_for_team(uint32_t team_id);
@@ -40,11 +40,11 @@ private:
public: public:
parray<AssistEffect, 4> assist_effects; parray<AssistEffect, 4> assist_effects;
std::shared_ptr<const DataIndex::CardEntry> assist_card_defs[4]; std::shared_ptr<const CardIndex::CardEntry> assist_card_defs[4];
uint32_t num_assist_cards_set; uint32_t num_assist_cards_set;
parray<uint8_t, 4> client_ids_with_assists; parray<uint8_t, 4> client_ids_with_assists;
parray<AssistEffect, 4> active_assist_effects; parray<AssistEffect, 4> active_assist_effects;
std::shared_ptr<const DataIndex::CardEntry> active_assist_card_defs[4]; std::shared_ptr<const CardIndex::CardEntry> active_assist_card_defs[4];
uint32_t num_active_assists; uint32_t num_active_assists;
std::shared_ptr<HandAndEquipState> hand_and_equip_states[4]; std::shared_ptr<HandAndEquipState> hand_and_equip_states[4];
std::shared_ptr<parray<CardShortStatus, 0x10>> card_short_statuses[4]; std::shared_ptr<parray<CardShortStatus, 0x10>> card_short_statuses[4];
+1 -1
View File
@@ -507,7 +507,7 @@ bool Card::get_attack_condition_value(
cond_type, card_ref, def_effect_index, value, out_value); cond_type, card_ref, def_effect_index, value, out_value);
} }
shared_ptr<const DataIndex::CardEntry> Card::get_definition() const { shared_ptr<const CardIndex::CardEntry> Card::get_definition() const {
return this->def_entry; return this->def_entry;
} }
+4 -4
View File
@@ -6,7 +6,7 @@
#include "../CommandFormats.hh" #include "../CommandFormats.hh"
#include "../Text.hh" #include "../Text.hh"
#include "DataIndex.hh" #include "DataIndexes.hh"
namespace Episode3 { namespace Episode3 {
@@ -58,7 +58,7 @@ public:
uint8_t def_effect_index, uint8_t def_effect_index,
uint16_t value, uint16_t value,
uint16_t* out_value) const; uint16_t* out_value) const;
std::shared_ptr<const DataIndex::CardEntry> get_definition() const; std::shared_ptr<const CardIndex::CardEntry> get_definition() const;
uint16_t get_card_ref() const; uint16_t get_card_ref() const;
uint8_t get_client_id() const; uint8_t get_client_id() const;
uint8_t get_current_hp() const; uint8_t get_current_hp() const;
@@ -99,12 +99,12 @@ private:
public: public:
int16_t max_hp; int16_t max_hp;
int16_t current_hp; int16_t current_hp;
std::shared_ptr<const DataIndex::CardEntry> def_entry; std::shared_ptr<const CardIndex::CardEntry> def_entry;
uint8_t client_id; uint8_t client_id;
uint16_t card_id; uint16_t card_id;
uint16_t card_ref; uint16_t card_ref;
uint16_t sc_card_ref; uint16_t sc_card_ref;
std::shared_ptr<const DataIndex::CardEntry> sc_def_entry; std::shared_ptr<const CardIndex::CardEntry> sc_def_entry;
CardType sc_card_type; CardType sc_card_type;
uint8_t team_id; uint8_t team_id;
uint32_t card_flags; uint32_t card_flags;
+14 -14
View File
@@ -816,7 +816,7 @@ shared_ptr<Card> CardSpecial::compute_replaced_target_based_on_conditions(
// the Gifoie card's ID (00D9) for compute_effective_range. // the Gifoie card's ID (00D9) for compute_effective_range.
// TODO: We should fix this so it doesn't rely on a fixed card definition. // TODO: We should fix this so it doesn't rely on a fixed card definition.
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, 0x00D9, target_card_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, 0x00D9, target_card_loc, this->server()->base()->map_and_rules1);
auto card_refs_in_parry_range = target_ps->get_all_cards_within_range( auto card_refs_in_parry_range = target_ps->get_all_cards_within_range(
range, target_card_loc, 0xFF); range, target_card_loc, 0xFF);
@@ -2526,7 +2526,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
if (ce && ps) { if (ce && ps) {
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
add_card_refs(ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM)); add_card_refs(ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM));
} }
} }
@@ -2566,7 +2566,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
if (ce && ps) { if (ce && ps) {
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id())); add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()));
} }
} }
@@ -2631,7 +2631,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// should fix this eventually. // should fix this eventually.
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref); auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2655,7 +2655,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Again with the Gifoie hardcoding... // TODO: Again with the Gifoie hardcoding...
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref); auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2721,7 +2721,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Again with the Gifoie hardcoding... // TODO: Again with the Gifoie hardcoding...
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref); auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2775,7 +2775,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Yet another Gifoie hardcode location :( // TODO: Yet another Gifoie hardcode location :(
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref); auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2802,7 +2802,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Sigh. Gifoie again. // TODO: Sigh. Gifoie again.
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref); auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2898,7 +2898,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// Slay instead of Gifoie // Slay instead of Gifoie
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x009C, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x009C, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref); auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2927,7 +2927,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: Sigh. Gifoie. Sigh. // TODO: Sigh. Gifoie. Sigh.
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref); auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -2965,7 +2965,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
// TODO: One more Gifoie here. // TODO: One more Gifoie here.
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1);
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = this->server()->card_for_set_card_ref(result_card_ref); auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
@@ -3383,7 +3383,7 @@ void CardSpecial::check_for_defense_interference(
shared_ptr<Card> target_card, shared_ptr<Card> target_card,
int16_t* inout_unknown_p4) { int16_t* inout_unknown_p4) {
// Note: This check is not part of the original implementation. // Note: This check is not part of the original implementation.
if (this->server()->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { if (this->server()->base()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
return; return;
} }
@@ -4126,7 +4126,7 @@ vector<shared_ptr<const Card>> CardSpecial::filter_cards_by_range(
// TODO: Remove hardcoded card ID here (Earthquake) // TODO: Remove hardcoded card ID here (Earthquake)
uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2); uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2);
parray<uint8_t, 9 * 9> range; parray<uint8_t, 9 * 9> range;
compute_effective_range(range, this->server()->base()->data_index, card_id, card1_loc, this->server()->base()->map_and_rules1); compute_effective_range(range, this->server()->base()->card_index, card_id, card1_loc, this->server()->base()->map_and_rules1);
auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM); auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM);
for (auto card : cards) { for (auto card : cards) {
@@ -4342,7 +4342,7 @@ void CardSpecial::unknown_8024A9D8(const ActionState& pa, uint16_t action_card_r
void CardSpecial::check_for_attack_interference(shared_ptr<Card> unknown_p2) { void CardSpecial::check_for_attack_interference(shared_ptr<Card> unknown_p2) {
// Note: This check is not part of the original implementation. // Note: This check is not part of the original implementation.
if (this->server()->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { if (this->server()->base()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
return; return;
} }
+1 -1
View File
@@ -5,7 +5,7 @@
#include <memory> #include <memory>
#include "../Text.hh" #include "../Text.hh"
#include "DataIndex.hh" #include "DataIndexes.hh"
namespace Episode3 { namespace Episode3 {
@@ -1,4 +1,4 @@
#include "DataIndex.hh" #include "DataIndexes.hh"
#include <stdint.h> #include <stdint.h>
@@ -1056,6 +1056,33 @@ Card: %04" PRIX32 " \"%s\"\n\
} }
} }
void PlayerConfig::decrypt() {
if (!this->is_encrypted) {
return;
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
this->basis);
this->is_encrypted = 0;
this->basis = 0;
}
void PlayerConfig::encrypt(uint8_t basis) {
if (this->is_encrypted) {
if (this->basis == basis) {
return;
}
this->decrypt();
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
basis);
this->is_encrypted = 1;
this->basis = basis;
}
HPType hp_type_for_name(const char* name) { HPType hp_type_for_name(const char* name) {
if (!strcmp(name, "DEFEAT_PLAYER")) { if (!strcmp(name, "DEFEAT_PLAYER")) {
return HPType::DEFEAT_PLAYER; return HPType::DEFEAT_PLAYER;
@@ -1351,7 +1378,7 @@ void StateFlags::clear_FF() {
this->client_sc_card_types.clear(CardType::INVALID_FF); this->client_sc_card_types.clear(CardType::INVALID_FF);
} }
string MapDefinition::str(const DataIndex* data_index) const { string MapDefinition::str(const CardIndex* card_index) const {
deque<string> lines; deque<string> lines;
auto add_map = [&](const parray<parray<uint8_t, 0x10>, 0x10>& tiles) { auto add_map = [&](const parray<parray<uint8_t, 0x10>, 0x10>& tiles) {
for (size_t y = 0; y < 0x10; y++) { for (size_t y = 0; y < 0x10; y++) {
@@ -1456,10 +1483,10 @@ string MapDefinition::str(const DataIndex* data_index) const {
lines.emplace_back(" name: " + string(this->npc_decks[z].name)); lines.emplace_back(" name: " + string(this->npc_decks[z].name));
for (size_t w = 0; w < 0x20; w++) { for (size_t w = 0; w < 0x20; w++) {
uint16_t card_id = this->npc_decks[z].card_ids[w]; uint16_t card_id = this->npc_decks[z].card_ids[w];
shared_ptr<const DataIndex::CardEntry> entry; shared_ptr<const CardIndex::CardEntry> entry;
if (data_index) { if (card_index) {
try { try {
entry = data_index->definition_for_card_id(card_id); entry = card_index->definition_for_id(card_id);
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
} }
@@ -1496,10 +1523,10 @@ string MapDefinition::str(const DataIndex* data_index) const {
} }
for (size_t z = 0; z < 0x10; z++) { for (size_t z = 0; z < 0x10; z++) {
uint16_t card_id = this->reward_card_ids[z]; uint16_t card_id = this->reward_card_ids[z];
shared_ptr<const DataIndex::CardEntry> entry; shared_ptr<const CardIndex::CardEntry> entry;
if (data_index) { if (card_index) {
try { try {
entry = data_index->definition_for_card_id(card_id); entry = card_index->definition_for_id(card_id);
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
} }
@@ -1671,14 +1698,12 @@ bool Rules::check_and_reset_invalid_fields() {
return ret; return ret;
} }
DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) CardIndex::CardIndex(const string& filename, const string& decompressed_filename, const string& text_filename) {
: behavior_flags(behavior_flags) {
unordered_map<uint32_t, vector<string>> card_tags; unordered_map<uint32_t, vector<string>> card_tags;
unordered_map<uint32_t, string> card_text; unordered_map<uint32_t, string> card_text;
if (this->behavior_flags & BehaviorFlag::LOAD_CARD_TEXT) { if (!text_filename.empty()) {
try { try {
string data = prs_decompress(load_file(directory + "/card-text.mnr")); string data = prs_decompress(load_file(text_filename));
StringReader r(data); StringReader r(data);
while (!r.eof()) { while (!r.eof()) {
@@ -1769,20 +1794,20 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
try { try {
string decompressed_data; string decompressed_data;
if (isfile(directory + "/card-definitions.mnrd")) { this->mtime_for_card_definitions = stat(filename).st_mtime;
this->mtime_for_card_definitions = stat(directory + "/card-definitions.mnrd").st_mtime; try {
decompressed_data = load_file(directory + "/card-definitions.mnrd"); decompressed_data = load_file(decompressed_filename);
this->compressed_card_definitions.clear(); this->compressed_card_definitions.clear();
} else { } catch (const cannot_open_file&) {
this->mtime_for_card_definitions = stat(directory + "/card-definitions.mnr").st_mtime; this->compressed_card_definitions = load_file(filename);
this->compressed_card_definitions = load_file(directory + "/card-definitions.mnr");
decompressed_data = prs_decompress(this->compressed_card_definitions); decompressed_data = prs_decompress(this->compressed_card_definitions);
} }
if (decompressed_data.size() > 0x36EC0) { if (decompressed_data.size() > 0x36EC0) {
throw runtime_error("decompressed card list data is too long"); throw runtime_error("decompressed card list data is too long");
} }
// There's a footer after the card definitions, but we ignore it // There's a footer after the card definitions (it's a standard-format REL
// file), but we ignore it
if (decompressed_data.size() % sizeof(CardDefinition) != sizeof(CardDefinitionsFooter)) { if (decompressed_data.size() % sizeof(CardDefinition) != sizeof(CardDefinitionsFooter)) {
throw runtime_error(string_printf( throw runtime_error(string_printf(
"decompressed card update file size %zX is not aligned with card definition size %zX (%zX extra bytes)", "decompressed card update file size %zX is not aligned with card definition size %zX (%zX extra bytes)",
@@ -1792,10 +1817,9 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
size_t max_cards = decompressed_data.size() / sizeof(CardDefinition); size_t max_cards = decompressed_data.size() / sizeof(CardDefinition);
for (size_t x = 0; x < max_cards; x++) { for (size_t x = 0; x < max_cards; x++) {
// The last card entry has the build date and some other metadata (and // The last card entry has the build date and some other metadata (and
// isn't a real card, obviously), so skip it. Seems like the card ID is // isn't a real card, obviously), so skip it. The game detects this by
// always a large number that won't fit in a uint16_t, so we use that to // checking for a negative value in type, which we also do here.
// determine if the entry is a real card or not. if (static_cast<int8_t>(defs[x].type) < 0) {
if (defs[x].card_id & 0xFFFF0000) {
continue; continue;
} }
@@ -1816,7 +1840,7 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
entry->def.mv.decode_code(); entry->def.mv.decode_code();
entry->def.decode_range(); entry->def.decode_range();
if (this->behavior_flags & BehaviorFlag::LOAD_CARD_TEXT) { if (!text_filename.empty()) {
try { try {
entry->text = std::move(card_text.at(defs[x].card_id)); entry->text = std::move(card_text.at(defs[x].card_id));
} catch (const out_of_range&) { } catch (const out_of_range&) {
@@ -1836,6 +1860,25 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
"Compressed card definitions (%zu bytes -> %zu bytes) in %" PRIu64 "ms", "Compressed card definitions (%zu bytes -> %zu bytes) in %" PRIu64 "ms",
decompressed_data.size(), this->compressed_card_definitions.size(), diff); decompressed_data.size(), this->compressed_card_definitions.size(), diff);
} }
if (this->compressed_card_definitions.size() > 0x7BF8) {
// Try to reduce the compressed size by clearing out text
static_game_data_log.info("Compressed card list data is too long; removing text");
for (size_t x = 0; x < max_cards; x++) {
if (static_cast<int8_t>(defs[x].type) < 0) {
continue;
}
defs[x].jp_name.clear();
}
uint64_t start = now();
this->compressed_card_definitions = prs_compress(decompressed_data);
uint64_t diff = now() - start;
static_game_data_log.info(
"Compressed card definitions (%zu bytes -> %zu bytes) in %" PRIu64 "ms",
decompressed_data.size(), this->compressed_card_definitions.size(), diff);
print_data(stderr, this->compressed_card_definitions);
}
if (this->compressed_card_definitions.size() > 0x7BF8) { if (this->compressed_card_definitions.size() > 0x7BF8) {
throw runtime_error("compressed card list data is too long"); throw runtime_error("compressed card list data is too long");
} }
@@ -1844,7 +1887,36 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
} catch (const exception& e) { } catch (const exception& e) {
static_game_data_log.warning("Failed to load Episode 3 card update: %s", e.what()); static_game_data_log.warning("Failed to load Episode 3 card update: %s", e.what());
} }
}
const string& CardIndex::get_compressed_definitions() const {
if (this->compressed_card_definitions.empty()) {
throw runtime_error("card definitions are not available");
}
return this->compressed_card_definitions;
}
shared_ptr<const CardIndex::CardEntry> CardIndex::definition_for_id(uint32_t id) const {
return this->card_definitions.at(id);
}
shared_ptr<const CardIndex::CardEntry> CardIndex::definition_for_name(const string& name) const {
return this->card_definitions_by_name.at(name);
}
set<uint32_t> CardIndex::all_ids() const {
set<uint32_t> ret;
for (const auto& it : this->card_definitions) {
ret.emplace(it.first);
}
return ret;
}
uint64_t CardIndex::definitions_mtime() const {
return this->mtime_for_card_definitions;
}
MapIndex::MapIndex(const string& directory) {
auto add_maps_from_dir = [&](const string& dir, bool is_quest) -> void { auto add_maps_from_dir = [&](const string& dir, bool is_quest) -> void {
for (const auto& filename : list_directory(dir)) { for (const auto& filename : list_directory(dir)) {
try { try {
@@ -1879,32 +1951,13 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
}; };
add_maps_from_dir(directory + "/maps-free", false); add_maps_from_dir(directory + "/maps-free", false);
add_maps_from_dir(directory + "/maps-quest", true); add_maps_from_dir(directory + "/maps-quest", true);
try {
auto json = JSONObject::parse(load_file(directory + "/com-decks.json"));
for (const auto& def_json : json->as_list()) {
auto& def = this->com_decks.emplace_back(new COMDeckDefinition());
def->index = this->com_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();
for (size_t z = 0; z < 0x1F; z++) {
def->card_ids[z] = card_ids_json.at(z)->as_int();
}
if (!this->com_decks_by_name.emplace(def->deck_name, def).second) {
throw runtime_error("duplicate COM deck name: " + def->deck_name);
}
}
} catch (const exception& e) {
static_game_data_log.warning("Failed to load Episode 3 COM decks: %s", e.what());
}
} }
DataIndex::MapEntry::MapEntry(const MapDefinition& map, bool is_quest) MapIndex::MapEntry::MapEntry(const MapDefinition& map, bool is_quest)
: map(map), : map(map),
is_quest(is_quest) {} is_quest(is_quest) {}
DataIndex::MapEntry::MapEntry(const string& compressed, bool is_quest) MapIndex::MapEntry::MapEntry(const string& compressed, bool is_quest)
: is_quest(is_quest), : is_quest(is_quest),
compressed_data(compressed) { compressed_data(compressed) {
string decompressed = prs_decompress(this->compressed_data); string decompressed = prs_decompress(this->compressed_data);
@@ -1916,43 +1969,14 @@ DataIndex::MapEntry::MapEntry(const string& compressed, bool is_quest)
this->map = *reinterpret_cast<const MapDefinition*>(decompressed.data()); this->map = *reinterpret_cast<const MapDefinition*>(decompressed.data());
} }
string DataIndex::MapEntry::compressed() const { string MapIndex::MapEntry::compressed() const {
if (this->compressed_data.empty()) { if (this->compressed_data.empty()) {
this->compressed_data = prs_compress(&this->map, sizeof(this->map)); this->compressed_data = prs_compress(&this->map, sizeof(this->map));
} }
return this->compressed_data; return this->compressed_data;
} }
const string& DataIndex::get_compressed_card_definitions() const { const string& MapIndex::get_compressed_list() const {
if (this->compressed_card_definitions.empty()) {
throw runtime_error("card definitions are not available");
}
return this->compressed_card_definitions;
}
shared_ptr<const DataIndex::CardEntry> DataIndex::definition_for_card_id(
uint32_t id) const {
return this->card_definitions.at(id);
}
shared_ptr<const DataIndex::CardEntry> DataIndex::definition_for_card_name(
const string& name) const {
return this->card_definitions_by_name.at(name);
}
set<uint32_t> DataIndex::all_card_ids() const {
set<uint32_t> ret;
for (const auto& it : this->card_definitions) {
ret.emplace(it.first);
}
return ret;
}
uint64_t DataIndex::card_definitions_mtime() const {
return this->mtime_for_card_definitions;
}
const string& DataIndex::get_compressed_map_list() const {
if (this->compressed_map_list.empty()) { if (this->compressed_map_list.empty()) {
StringWriter entries_w; StringWriter entries_w;
StringWriter strings_w; StringWriter strings_w;
@@ -2012,16 +2036,15 @@ const string& DataIndex::get_compressed_map_list() const {
return this->compressed_map_list; return this->compressed_map_list;
} }
shared_ptr<const DataIndex::MapEntry> DataIndex::definition_for_map_number(uint32_t id) const { shared_ptr<const MapIndex::MapEntry> MapIndex::definition_for_number(uint32_t id) const {
return this->maps.at(id); return this->maps.at(id);
} }
shared_ptr<const DataIndex::MapEntry> DataIndex::definition_for_map_name( shared_ptr<const MapIndex::MapEntry> MapIndex::definition_for_name(const string& name) const {
const string& name) const {
return this->maps_by_name.at(name); return this->maps_by_name.at(name);
} }
set<uint32_t> DataIndex::all_map_ids() const { set<uint32_t> MapIndex::all_numbers() const {
set<uint32_t> ret; set<uint32_t> ret;
for (const auto& it : this->maps) { for (const auto& it : this->maps) {
ret.emplace(it.first); ret.emplace(it.first);
@@ -2029,47 +2052,41 @@ set<uint32_t> DataIndex::all_map_ids() const {
return ret; return ret;
} }
size_t DataIndex::num_com_decks() const { COMDeckIndex::COMDeckIndex(const string& filename) {
return this->com_decks.size(); try {
} auto json = JSONObject::parse(load_file(filename));
for (const auto& def_json : json->as_list()) {
shared_ptr<const COMDeckDefinition> DataIndex::com_deck(size_t which) const { auto& def = this->decks.emplace_back(new COMDeckDefinition());
return this->com_decks.at(which); def->index = this->decks.size() - 1;
} def->player_name = def_json->at(0)->as_string();
def->deck_name = def_json->at(1)->as_string();
shared_ptr<const COMDeckDefinition> DataIndex::com_deck(const string& which) const { auto card_ids_json = def_json->at(2)->as_list();
return this->com_decks_by_name.at(which); for (size_t z = 0; z < 0x1F; z++) {
} def->card_ids[z] = card_ids_json.at(z)->as_int();
}
shared_ptr<const COMDeckDefinition> DataIndex::random_com_deck() const { if (!this->decks_by_name.emplace(def->deck_name, def).second) {
return this->com_decks[random_object<size_t>() % this->com_decks.size()]; throw runtime_error("duplicate COM deck name: " + def->deck_name);
} }
void PlayerConfig::decrypt() {
if (!this->is_encrypted) {
return;
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
this->basis);
this->is_encrypted = 0;
this->basis = 0;
}
void PlayerConfig::encrypt(uint8_t basis) {
if (this->is_encrypted) {
if (this->basis == basis) {
return;
} }
this->decrypt(); } catch (const exception& e) {
static_game_data_log.warning("Failed to load Episode 3 COM decks: %s", e.what());
} }
decrypt_trivial_gci_data( }
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), size_t COMDeckIndex::num_decks() const {
basis); return this->decks.size();
this->is_encrypted = 1; }
this->basis = basis;
shared_ptr<const COMDeckDefinition> COMDeckIndex::deck_for_index(size_t which) const {
return this->decks.at(which);
}
shared_ptr<const COMDeckDefinition> COMDeckIndex::deck_for_name(const string& which) const {
return this->decks_by_name.at(which);
}
shared_ptr<const COMDeckDefinition> COMDeckIndex::random_deck() const {
return this->decks[random_object<size_t>() % this->decks.size()];
} }
} // namespace Episode3 } // namespace Episode3
@@ -15,21 +15,22 @@
namespace Episode3 { namespace Episode3 {
// The comment in Server.hh does not apply to this file (and DataIndex.cc). // The comment in Server.hh does not apply to this file (and DataIndexes.cc).
// Except for the Location structure, these structures and functions are not // Except for the Location structure, these structures and functions are not
// based on Sega's original implementation. // based on Sega's original implementation.
class DataIndex; class CardIndex;
class MapIndex;
class COMDeckIndex;
const char* name_for_link_color(uint8_t color); const char* name_for_link_color(uint8_t color);
enum BehaviorFlag { enum BehaviorFlag : uint32_t {
SKIP_DECK_VERIFY = 0x00000001, SKIP_DECK_VERIFY = 0x00000001,
IGNORE_CARD_COUNTS = 0x00000002, IGNORE_CARD_COUNTS = 0x00000002,
SKIP_D1_D2_REPLACE = 0x00000004, SKIP_D1_D2_REPLACE = 0x00000004,
DISABLE_TIME_LIMITS = 0x00000008, DISABLE_TIME_LIMITS = 0x00000008,
ENABLE_STATUS_MESSAGES = 0x00000010, ENABLE_STATUS_MESSAGES = 0x00000010,
LOAD_CARD_TEXT = 0x00000020,
ENABLE_RECORDING = 0x00000040, ENABLE_RECORDING = 0x00000040,
DISABLE_MASKING = 0x00000080, DISABLE_MASKING = 0x00000080,
DISABLE_INTERFERENCE = 0x00000100, DISABLE_INTERFERENCE = 0x00000100,
@@ -952,10 +953,9 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
uint8_t deck_type; uint8_t deck_type;
} __attribute__((packed)); } __attribute__((packed));
/* 5A10 */ parray<EntryState, 4> entry_states; /* 5A10 */ parray<EntryState, 4> entry_states;
/* 5A18 */ /* 5A18 */
std::string str(const DataIndex* data_index = nullptr) const; std::string str(const CardIndex* card_index = nullptr) const;
} __attribute__((packed)); } __attribute__((packed));
struct COMDeckDefinition { struct COMDeckDefinition {
@@ -965,9 +965,9 @@ struct COMDeckDefinition {
parray<le_uint16_t, 0x1F> card_ids; parray<le_uint16_t, 0x1F> card_ids;
}; };
class DataIndex { class CardIndex {
public: public:
DataIndex(const std::string& directory, uint32_t behavior_flags); CardIndex(const std::string& filename, const std::string& decompressed_filename, const std::string& text_filename = "");
struct CardEntry { struct CardEntry {
CardDefinition def; CardDefinition def;
@@ -975,6 +975,23 @@ public:
std::vector<std::string> debug_tags; // Empty unless debug == true std::vector<std::string> debug_tags; // Empty unless debug == true
}; };
const std::string& get_compressed_definitions() const;
std::shared_ptr<const CardEntry> definition_for_id(uint32_t id) const;
std::shared_ptr<const CardEntry> definition_for_name(const std::string& name) const;
std::set<uint32_t> all_ids() const;
uint64_t definitions_mtime() const;
private:
std::string compressed_card_definitions;
std::unordered_map<uint32_t, std::shared_ptr<CardEntry>> card_definitions;
std::unordered_map<std::string, std::shared_ptr<CardEntry>> card_definitions_by_name;
uint64_t mtime_for_card_definitions;
};
class MapIndex {
public:
MapIndex(const std::string& directory);
class MapEntry { class MapEntry {
public: public:
MapDefinition map; MapDefinition map;
@@ -989,42 +1006,33 @@ public:
mutable std::string compressed_data; mutable std::string compressed_data;
}; };
const std::string& get_compressed_card_definitions() const; const std::string& get_compressed_list() const;
std::shared_ptr<const CardEntry> definition_for_card_id(uint32_t id) const; std::shared_ptr<const MapEntry> definition_for_number(uint32_t id) const;
std::shared_ptr<const CardEntry> definition_for_card_name( std::shared_ptr<const MapEntry> definition_for_name(const std::string& name) const;
const std::string& name) const; std::set<uint32_t> all_numbers() const;
std::set<uint32_t> all_card_ids() const;
uint64_t card_definitions_mtime() const;
const std::string& get_compressed_map_list() const;
std::shared_ptr<const MapEntry> definition_for_map_number(uint32_t id) const;
std::shared_ptr<const MapEntry> definition_for_map_name(
const std::string& name) const;
std::set<uint32_t> all_map_ids() const;
size_t num_com_decks() const;
std::shared_ptr<const COMDeckDefinition> com_deck(size_t which) const;
std::shared_ptr<const COMDeckDefinition> com_deck(const std::string& name) const;
std::shared_ptr<const COMDeckDefinition> random_com_deck() const;
const uint32_t behavior_flags;
private: private:
std::string compressed_card_definitions;
std::unordered_map<uint32_t, std::shared_ptr<CardEntry>> card_definitions;
std::unordered_map<std::string, std::shared_ptr<CardEntry>> card_definitions_by_name;
uint64_t mtime_for_card_definitions;
// The compressed map list is generated on demand from the maps map below. // The compressed map list is generated on demand from the maps map below.
// It's marked mutable because the logical consistency of the DataIndex object // It's marked mutable because the logical consistency of the MapIndex object
// is not violated from the caller's perspective even if we don't generate the // is not violated from the caller's perspective even if we don't generate the
// compressed map list at load time. // compressed map list at load time.
mutable std::string compressed_map_list; mutable std::string compressed_map_list;
std::map<uint32_t, std::shared_ptr<MapEntry>> maps; std::map<uint32_t, std::shared_ptr<MapEntry>> maps;
std::unordered_map<std::string, std::shared_ptr<MapEntry>> maps_by_name; std::unordered_map<std::string, std::shared_ptr<MapEntry>> maps_by_name;
};
std::vector<std::shared_ptr<COMDeckDefinition>> com_decks; class COMDeckIndex {
std::unordered_map<std::string, std::shared_ptr<COMDeckDefinition>> com_decks_by_name; public:
COMDeckIndex(const std::string& filename);
size_t num_decks() const;
std::shared_ptr<const COMDeckDefinition> deck_for_index(size_t which) const;
std::shared_ptr<const COMDeckDefinition> deck_for_name(const std::string& name) const;
std::shared_ptr<const COMDeckDefinition> random_deck() const;
private:
std::vector<std::shared_ptr<COMDeckDefinition>> decks;
std::unordered_map<std::string, std::shared_ptr<COMDeckDefinition>> decks_by_name;
}; };
} // namespace Episode3 } // namespace Episode3
+1 -1
View File
@@ -5,7 +5,7 @@
#include <memory> #include <memory>
#include "../Text.hh" #include "../Text.hh"
#include "DataIndex.hh" #include "DataIndexes.hh"
namespace Episode3 { namespace Episode3 {
+1 -1
View File
@@ -6,7 +6,7 @@
#include "../Text.hh" #include "../Text.hh"
#include "Card.hh" #include "Card.hh"
#include "DataIndex.hh" #include "DataIndexes.hh"
#include "DeckState.hh" #include "DeckState.hh"
#include "PlayerStateSubordinates.hh" #include "PlayerStateSubordinates.hh"
+1 -1
View File
@@ -5,7 +5,7 @@
#include <memory> #include <memory>
#include "../Text.hh" #include "../Text.hh"
#include "DataIndex.hh" #include "DataIndexes.hh"
namespace Episode3 { namespace Episode3 {
+8 -9
View File
@@ -8,7 +8,7 @@ namespace Episode3 {
void compute_effective_range( void compute_effective_range(
parray<uint8_t, 9 * 9>& ret, parray<uint8_t, 9 * 9>& ret,
shared_ptr<const DataIndex> data_index, shared_ptr<const CardIndex> card_index,
uint16_t card_id, uint16_t card_id,
const Location& loc, const Location& loc,
shared_ptr<const MapAndRulesState> map_and_rules) { shared_ptr<const MapAndRulesState> map_and_rules) {
@@ -19,9 +19,9 @@ void compute_effective_range(
// Heavy Fog: one tile directly in front // Heavy Fog: one tile directly in front
range_def[3] = 0x00000100; range_def[3] = 0x00000100;
} else { } else {
shared_ptr<const DataIndex::CardEntry> ce; shared_ptr<const CardIndex::CardEntry> ce;
try { try {
ce = data_index->definition_for_card_id(card_id); ce = card_index->definition_for_id(card_id);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return; return;
} }
@@ -126,9 +126,9 @@ void compute_effective_range(
} }
bool card_linkage_is_valid( bool card_linkage_is_valid(
shared_ptr<const DataIndex::CardEntry> right_ce, shared_ptr<const CardIndex::CardEntry> right_ce,
shared_ptr<const DataIndex::CardEntry> left_ce, shared_ptr<const CardIndex::CardEntry> left_ce,
shared_ptr<const DataIndex::CardEntry> sc_ce, shared_ptr<const CardIndex::CardEntry> sc_ce,
bool has_permission_effect) { bool has_permission_effect) {
if (!right_ce) { if (!right_ce) {
return false; return false;
@@ -1613,7 +1613,7 @@ bool RulerServer::defense_card_matches_any_attack_card_top_color(
return false; return false;
} }
shared_ptr<const DataIndex::CardEntry> RulerServer::definition_for_card_ref(uint16_t card_ref) const { shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_ref(uint16_t card_ref) const {
uint16_t card_id = this->card_id_for_card_ref(card_ref); uint16_t card_id = this->card_id_for_card_ref(card_ref);
if (card_id == 0xFFFF) { if (card_id == 0xFFFF) {
return nullptr; return nullptr;
@@ -1972,8 +1972,7 @@ uint16_t RulerServer::get_ally_sc_card_ref(uint16_t card_ref) const {
return 0xFFFF; return 0xFFFF;
} }
shared_ptr<const DataIndex::CardEntry> RulerServer::definition_for_card_id( shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_id(uint32_t card_id) const {
uint32_t card_id) const {
return this->server()->definition_for_card_id(card_id); return this->server()->definition_for_card_id(card_id);
} }
+7 -7
View File
@@ -5,7 +5,7 @@
#include <memory> #include <memory>
#include "AssistServer.hh" #include "AssistServer.hh"
#include "DataIndex.hh" #include "DataIndexes.hh"
#include "DeckState.hh" #include "DeckState.hh"
#include "PlayerState.hh" #include "PlayerState.hh"
@@ -15,15 +15,15 @@ class Server;
void compute_effective_range( void compute_effective_range(
parray<uint8_t, 9 * 9>& ret, parray<uint8_t, 9 * 9>& ret,
std::shared_ptr<const DataIndex> data_index, std::shared_ptr<const CardIndex> card_index,
uint16_t card_id, uint16_t card_id,
const Location& loc, const Location& loc,
std::shared_ptr<const MapAndRulesState> map_and_rules); std::shared_ptr<const MapAndRulesState> map_and_rules);
bool card_linkage_is_valid( bool card_linkage_is_valid(
std::shared_ptr<const DataIndex::CardEntry> right_def, std::shared_ptr<const CardIndex::CardEntry> right_def,
std::shared_ptr<const DataIndex::CardEntry> left_def, std::shared_ptr<const CardIndex::CardEntry> left_def,
std::shared_ptr<const DataIndex::CardEntry> sc_def, std::shared_ptr<const CardIndex::CardEntry> sc_def,
bool has_permission_effect); bool has_permission_effect);
class RulerServer { class RulerServer {
@@ -130,7 +130,7 @@ public:
uint16_t attacker_sc_card_ref) const; uint16_t attacker_sc_card_ref) const;
bool defense_card_matches_any_attack_card_top_color( bool defense_card_matches_any_attack_card_top_color(
const ActionState& pa) const; const ActionState& pa) const;
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const; std::shared_ptr<const CardIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const;
int32_t error_code_for_client_setting_card( int32_t error_code_for_client_setting_card(
uint8_t client_id, uint8_t client_id,
uint16_t card_ref, uint16_t card_ref,
@@ -156,7 +156,7 @@ public:
size_t num_occupied_tiles, size_t num_occupied_tiles,
size_t num_vacant_tiles) const; size_t num_vacant_tiles) const;
uint16_t get_ally_sc_card_ref(uint16_t card_ref) const; uint16_t get_ally_sc_card_ref(uint16_t card_ref) const;
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_id( std::shared_ptr<const CardIndex::CardEntry> definition_for_card_id(
uint32_t card_id) const; uint32_t card_id) const;
uint32_t get_card_id_with_effective_range( uint32_t get_card_id_with_effective_range(
uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const; uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const;
+22 -20
View File
@@ -26,11 +26,15 @@ void ServerBase::PresenceEntry::clear() {
ServerBase::ServerBase( ServerBase::ServerBase(
shared_ptr<Lobby> lobby, shared_ptr<Lobby> lobby,
shared_ptr<const DataIndex> data_index, shared_ptr<const CardIndex> card_index,
shared_ptr<const MapIndex> map_index,
uint32_t behavior_flags,
shared_ptr<PSOLFGEncryption> random_crypt, shared_ptr<PSOLFGEncryption> random_crypt,
shared_ptr<const DataIndex::MapEntry> map_if_tournament) shared_ptr<const MapIndex::MapEntry> map_if_tournament)
: lobby(lobby), : lobby(lobby),
data_index(data_index), card_index(card_index),
map_index(map_index),
behavior_flags(behavior_flags),
log(lobby->log.prefix + "[Ep3::Server] "), log(lobby->log.prefix + "[Ep3::Server] "),
random_crypt(random_crypt), random_crypt(random_crypt),
is_tournament(!!map_if_tournament), is_tournament(!!map_if_tournament),
@@ -182,7 +186,7 @@ void Server::send(const void* data, size_t size) const {
} }
string masked_data; string masked_data;
if (!(this->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_MASKING)) { if (!(this->base()->behavior_flags & BehaviorFlag::DISABLE_MASKING)) {
if (size >= 8) { if (size >= 8) {
masked_data.assign(reinterpret_cast<const char*>(data), size); masked_data.assign(reinterpret_cast<const char*>(data), size);
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1; uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
@@ -216,13 +220,12 @@ void Server::send_6xB4x46() const {
G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46; G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46;
cmd46.version_signature = VERSION_SIGNATURE; cmd46.version_signature = VERSION_SIGNATURE;
cmd46.date_str1 = format_time(this->base()->data_index->card_definitions_mtime() * 1000000); cmd46.date_str1 = format_time(this->base()->card_index->definitions_mtime() * 1000000);
cmd46.date_str2 = string_printf("Lobby/%08" PRIX32, l->lobby_id); cmd46.date_str2 = string_printf("Lobby/%08" PRIX32, l->lobby_id);
this->send(cmd46); this->send(cmd46);
} }
string Server::prepare_6xB6x41_map_definition( string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::MapEntry> map) {
shared_ptr<const DataIndex::MapEntry> map) {
const auto& compressed = map->compressed(); const auto& compressed = map->compressed();
StringWriter w; StringWriter w;
@@ -260,7 +263,7 @@ void Server::send_commands_for_joining_spectator(Channel& c) const {
__attribute__((format(printf, 2, 3))) void Server::log_debug(const char* fmt, ...) const { __attribute__((format(printf, 2, 3))) void Server::log_debug(const char* fmt, ...) const {
auto l = this->base()->lobby.lock(); auto l = this->base()->lobby.lock();
if (l && (this->base()->data_index->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { if (l && (this->base()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
va_list va; va_list va;
va_start(va, fmt); va_start(va, fmt);
this->base()->log.info_v(fmt, va); this->base()->log.info_v(fmt, va);
@@ -270,7 +273,7 @@ __attribute__((format(printf, 2, 3))) void Server::log_debug(const char* fmt, ..
__attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(const char* fmt, ...) const { __attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(const char* fmt, ...) const {
auto l = this->base()->lobby.lock(); auto l = this->base()->lobby.lock();
if (l && (this->base()->data_index->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { if (l && (this->base()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
va_list va; va_list va;
va_start(va, fmt); va_start(va, fmt);
std::string buf = string_vprintf(fmt, va); std::string buf = string_vprintf(fmt, va);
@@ -367,10 +370,9 @@ void Server::draw_phase_before() {
} }
} }
shared_ptr<const DataIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const { shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const {
try { try {
return this->base()->data_index->definition_for_card_id( return this->base()->card_index->definition_for_id(this->card_id_for_card_ref(card_ref));
this->card_id_for_card_ref(card_ref));
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
@@ -581,9 +583,9 @@ void Server::copy_player_states_to_prev_states() {
} }
} }
shared_ptr<const DataIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const { shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const {
try { try {
return this->base()->data_index->definition_for_card_id(card_id); return this->base()->card_index->definition_for_id(card_id);
} catch (const out_of_range&) { } catch (const out_of_range&) {
return nullptr; return nullptr;
} }
@@ -1839,7 +1841,7 @@ void Server::handle_6xB3x13_update_map_during_setup(const string& data) {
*b->map_and_rules1 = in_cmd.map_and_rules_state; *b->map_and_rules1 = in_cmd.map_and_rules_state;
*b->map_and_rules2 = in_cmd.map_and_rules_state; *b->map_and_rules2 = in_cmd.map_and_rules_state;
b->overlay_state = in_cmd.overlay_state; b->overlay_state = in_cmd.overlay_state;
if (b->data_index->behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) { if (b->behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) {
b->map_and_rules1->rules.overall_time_limit = 0; b->map_and_rules1->rules.overall_time_limit = 0;
b->map_and_rules1->rules.phase_time_limit = 0; b->map_and_rules1->rules.phase_time_limit = 0;
b->map_and_rules2->rules.overall_time_limit = 0; b->map_and_rules2->rules.overall_time_limit = 0;
@@ -1869,9 +1871,9 @@ void Server::handle_6xB3x14_update_deck_during_setup(const string& data) {
} }
DeckEntry entry = in_cmd.entry; DeckEntry entry = in_cmd.entry;
int32_t verify_error = 0; int32_t verify_error = 0;
if (!(this->base()->data_index->behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) { if (!(this->base()->behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) {
// Note: Sega's original implementation doesn't use the card counts here // Note: Sega's original implementation doesn't use the card counts here
if (this->base()->data_index->behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) { if (this->base()->behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) {
verify_error = this->ruler_server->verify_deck(entry.card_ids); verify_error = this->ruler_server->verify_deck(entry.card_ids);
} else { } else {
verify_error = this->ruler_server->verify_deck(entry.card_ids, verify_error = this->ruler_server->verify_deck(entry.card_ids,
@@ -1881,7 +1883,7 @@ void Server::handle_6xB3x14_update_deck_during_setup(const string& data) {
if (verify_error) { if (verify_error) {
throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error)); throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error));
} }
if (!(this->base()->data_index->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) { if (!(this->base()->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
this->ruler_server->replace_D1_D2_rarity_cards_with_Attack(entry.card_ids); this->ruler_server->replace_D1_D2_rarity_cards_with_Attack(entry.card_ids);
} }
*this->base()->deck_entries[in_cmd.client_id] = in_cmd.entry; *this->base()->deck_entries[in_cmd.client_id] = in_cmd.entry;
@@ -2135,7 +2137,7 @@ void Server::handle_6xB3x40_map_list_request(const string& data) {
throw runtime_error("lobby is deleted"); throw runtime_error("lobby is deleted");
} }
const auto& list_data = this->base()->data_index->get_compressed_map_list(); const auto& list_data = this->base()->map_index->get_compressed_list();
StringWriter w; StringWriter w;
uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3); uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3);
@@ -2164,7 +2166,7 @@ void Server::handle_6xB3x41_map_request(const string& data) {
throw runtime_error("lobby is deleted"); throw runtime_error("lobby is deleted");
} }
base->last_chosen_map = base->data_index->definition_for_map_number(cmd.map_number); base->last_chosen_map = base->map_index->definition_for_number(cmd.map_number);
auto out_cmd = this->prepare_6xB6x41_map_definition(base->last_chosen_map); auto out_cmd = this->prepare_6xB6x41_map_definition(base->last_chosen_map);
send_command(l, 0x6C, 0x00, out_cmd); send_command(l, 0x6C, 0x00, out_cmd);
for (auto watcher_l : l->watcher_lobbies) { for (auto watcher_l : l->watcher_lobbies) {
+13 -9
View File
@@ -19,7 +19,7 @@ namespace Episode3 {
/** /**
* This implementation of Episode 3 battles (contained in all files in the * This implementation of Episode 3 battles (contained in all files in the
* src/Episode3 directory, except for DataIndex.hh/cc) is derived from Sega's * src/Episode3 directory, except for DataIndexes.hh/cc) is derived from Sega's
* original server implementation, reverse-engineered from the Episode 3 client * original server implementation, reverse-engineered from the Episode 3 client
* executable. The control flow, function breakdown, and structure definitions * executable. The control flow, function breakdown, and structure definitions
* in these files map very closely to how their server implementation was * in these files map very closely to how their server implementation was
@@ -49,7 +49,7 @@ namespace Episode3 {
// - - - - - - - DeckState // - - - - - - - DeckState
// - - - - - - - HandAndEquipState // - - - - - - - HandAndEquipState
// - - - - - - - MapAndRulesState / OverlayState // - - - - - - - MapAndRulesState / OverlayState
// - - - - - - - - Everything within DataIndex // - - - - - - - - Everything within DataIndexes
class Server; class Server;
@@ -57,9 +57,11 @@ class ServerBase : public std::enable_shared_from_this<ServerBase> {
public: public:
ServerBase( ServerBase(
std::shared_ptr<Lobby> lobby, std::shared_ptr<Lobby> lobby,
std::shared_ptr<const DataIndex> data_index, std::shared_ptr<const CardIndex> card_index,
std::shared_ptr<const MapIndex> map_index,
uint32_t behavior_flags,
std::shared_ptr<PSOLFGEncryption> random_crypt, std::shared_ptr<PSOLFGEncryption> random_crypt,
std::shared_ptr<const DataIndex::MapEntry> map_if_tournament); std::shared_ptr<const MapIndex::MapEntry> map_if_tournament);
void init(); void init();
void reset(); void reset();
void recreate_server(); void recreate_server();
@@ -73,11 +75,13 @@ public:
} __attribute__((packed)); } __attribute__((packed));
std::weak_ptr<Lobby> lobby; std::weak_ptr<Lobby> lobby;
std::shared_ptr<const DataIndex> data_index; std::shared_ptr<const CardIndex> card_index;
std::shared_ptr<const MapIndex> map_index;
uint32_t behavior_flags;
PrefixedLogger log; PrefixedLogger log;
std::shared_ptr<PSOLFGEncryption> random_crypt; std::shared_ptr<PSOLFGEncryption> random_crypt;
bool is_tournament; bool is_tournament;
std::shared_ptr<const DataIndex::MapEntry> last_chosen_map; std::shared_ptr<const MapIndex::MapEntry> last_chosen_map;
std::shared_ptr<MapAndRulesState> map_and_rules1; std::shared_ptr<MapAndRulesState> map_and_rules1;
std::shared_ptr<MapAndRulesState> map_and_rules2; std::shared_ptr<MapAndRulesState> map_and_rules2;
@@ -135,7 +139,7 @@ public:
bool advance_battle_phase(); bool advance_battle_phase();
void action_phase_after(); void action_phase_after();
void draw_phase_before(); void draw_phase_before();
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const; std::shared_ptr<const CardIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const;
std::shared_ptr<Card> card_for_set_card_ref(uint16_t card_ref); std::shared_ptr<Card> card_for_set_card_ref(uint16_t card_ref);
std::shared_ptr<const Card> card_for_set_card_ref(uint16_t card_ref) const; std::shared_ptr<const Card> card_for_set_card_ref(uint16_t card_ref) const;
uint16_t card_id_for_card_ref(uint16_t card_ref) const; uint16_t card_id_for_card_ref(uint16_t card_ref) const;
@@ -147,7 +151,7 @@ public:
void compute_all_map_occupied_bits(); void compute_all_map_occupied_bits();
void compute_team_dice_boost(uint8_t team_id); void compute_team_dice_boost(uint8_t team_id);
void copy_player_states_to_prev_states(); void copy_player_states_to_prev_states();
std::shared_ptr<const DataIndex::CardEntry> definition_for_card_id(uint16_t card_id) const; std::shared_ptr<const CardIndex::CardEntry> definition_for_card_id(uint16_t card_id) const;
void destroy_cards_with_zero_hp(); void destroy_cards_with_zero_hp();
void determine_first_team_turn(); void determine_first_team_turn();
void dice_phase_after(); void dice_phase_after();
@@ -229,7 +233,7 @@ public:
G_UpdateDecks_GC_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const; G_UpdateDecks_GC_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const;
G_SetPlayerNames_GC_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const; G_SetPlayerNames_GC_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const;
static std::string prepare_6xB6x41_map_definition( static std::string prepare_6xB6x41_map_definition(
std::shared_ptr<const DataIndex::MapEntry> map); std::shared_ptr<const MapIndex::MapEntry> map);
G_SetTrapTileLocations_GC_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const; G_SetTrapTileLocations_GC_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const;
std::vector<std::shared_ptr<Card>> const_cast_set_cards_v( std::vector<std::shared_ptr<Card>> const_cast_set_cards_v(
+21 -20
View File
@@ -280,15 +280,17 @@ shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(
} }
Tournament::Tournament( Tournament::Tournament(
shared_ptr<const DataIndex> data_index, shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number, uint8_t number,
const string& name, const string& name,
shared_ptr<const DataIndex::MapEntry> map, shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules, const Rules& rules,
size_t num_teams, size_t num_teams,
bool is_2v2) bool is_2v2)
: log(string_printf("[Tournament/%02hhX] ", number)), : log(string_printf("[Tournament/%02hhX] ", number)),
data_index(data_index), map_index(map_index),
com_deck_index(com_deck_index),
number(number), number(number),
name(name), name(name),
map(map), map(map),
@@ -308,11 +310,13 @@ Tournament::Tournament(
} }
Tournament::Tournament( Tournament::Tournament(
std::shared_ptr<const DataIndex> data_index, shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number, uint8_t number,
std::shared_ptr<const JSONObject> json) std::shared_ptr<const JSONObject> json)
: log(string_printf("[Tournament/%02hhX] ", number)), : log(string_printf("[Tournament/%02hhX] ", number)),
data_index(data_index), map_index(map_index),
com_deck_index(com_deck_index),
source_json(json), source_json(json),
number(number), number(number),
current_state(State::REGISTRATION) {} current_state(State::REGISTRATION) {}
@@ -324,7 +328,7 @@ void Tournament::init() {
if (this->source_json) { if (this->source_json) {
auto& dict = this->source_json->as_dict(); auto& dict = this->source_json->as_dict();
this->name = dict.at("name")->as_string(); this->name = dict.at("name")->as_string();
this->map = this->data_index->definition_for_map_number(dict.at("map_number")->as_int()); this->map = this->map_index->definition_for_number(dict.at("map_number")->as_int());
this->rules = Rules(dict.at("rules")); this->rules = Rules(dict.at("rules"));
this->is_2v2 = dict.at("is_2v2")->as_bool(); this->is_2v2 = dict.at("is_2v2")->as_bool();
is_registration_complete = dict.at("is_registration_complete")->as_bool(); is_registration_complete = dict.at("is_registration_complete")->as_bool();
@@ -344,8 +348,7 @@ void Tournament::init() {
team->players.emplace_back(serial_number); team->players.emplace_back(serial_number);
this->all_player_serial_numbers.emplace(serial_number); this->all_player_serial_numbers.emplace(serial_number);
} else { } else {
team->players.emplace_back(this->data_index->com_deck( team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string()));
player_json->as_string()));
} }
} }
} }
@@ -486,10 +489,6 @@ std::shared_ptr<JSONObject> Tournament::json() const {
return shared_ptr<JSONObject>(new JSONObject(std::move(dict))); return shared_ptr<JSONObject>(new JSONObject(std::move(dict)));
} }
std::shared_ptr<const DataIndex> Tournament::get_data_index() const {
return this->data_index;
}
uint8_t Tournament::get_number() const { uint8_t Tournament::get_number() const {
return this->number; return this->number;
} }
@@ -498,7 +497,7 @@ const string& Tournament::get_name() const {
return this->name; return this->name;
} }
shared_ptr<const DataIndex::MapEntry> Tournament::get_map() const { shared_ptr<const MapIndex::MapEntry> Tournament::get_map() const {
return this->map; return this->map;
} }
@@ -593,13 +592,13 @@ void Tournament::start() {
throw logic_error("non-human player on team before tournament start"); throw logic_error("non-human player on team before tournament start");
} }
} }
if (this->data_index->num_com_decks() < t->max_players - t->players.size()) { if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
throw runtime_error("not enough COM decks to complete team"); throw runtime_error("not enough COM decks to complete team");
} }
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the same // TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the same
// team // team
while (t->players.size() < t->max_players) { while (t->players.size() < t->max_players) {
t->players.emplace_back(this->data_index->random_com_deck()); t->players.emplace_back(this->com_deck_index->random_deck());
} }
} }
@@ -654,10 +653,12 @@ void Tournament::print_bracket(FILE* stream) const {
} }
TournamentIndex::TournamentIndex( TournamentIndex::TournamentIndex(
shared_ptr<const DataIndex> data_index, shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
const string& state_filename, const string& state_filename,
bool skip_load_state) bool skip_load_state)
: data_index(data_index), : map_index(map_index),
com_deck_index(com_deck_index),
state_filename(state_filename) { state_filename(state_filename) {
if (this->state_filename.empty() || skip_load_state) { if (this->state_filename.empty() || skip_load_state) {
return; return;
@@ -671,7 +672,7 @@ TournamentIndex::TournamentIndex(
} }
for (size_t z = 0; z < 0x20; z++) { for (size_t z = 0; z < 0x20; z++) {
if (!list.at(z)->is_null()) { if (!list.at(z)->is_null()) {
this->tournaments[z].reset(new Tournament(this->data_index, z, list[z])); this->tournaments[z].reset(new Tournament(this->map_index, this->com_deck_index, z, list[z]));
this->tournaments[z]->init(); this->tournaments[z]->init();
} }
} }
@@ -706,7 +707,7 @@ vector<shared_ptr<Tournament>> TournamentIndex::all_tournaments() const {
shared_ptr<Tournament> TournamentIndex::create_tournament( shared_ptr<Tournament> TournamentIndex::create_tournament(
const string& name, const string& name,
shared_ptr<const DataIndex::MapEntry> map, shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules, const Rules& rules,
size_t num_teams, size_t num_teams,
bool is_2v2) { bool is_2v2) {
@@ -722,7 +723,7 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
} }
auto t = make_shared<Tournament>( auto t = make_shared<Tournament>(
this->data_index, number, name, map, rules, num_teams, is_2v2); this->map_index, this->com_deck_index, number, name, map, rules, num_teams, is_2v2);
t->init(); t->init();
this->tournaments[number] = t; this->tournaments[number] = t;
return t; return t;
+14 -10
View File
@@ -92,15 +92,17 @@ public:
}; };
Tournament( Tournament(
std::shared_ptr<const DataIndex> data_index, std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number, uint8_t number,
const std::string& name, const std::string& name,
std::shared_ptr<const DataIndex::MapEntry> map, std::shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules, const Rules& rules,
size_t num_teams, size_t num_teams,
bool is_2v2); bool is_2v2);
Tournament( Tournament(
std::shared_ptr<const DataIndex> data_index, std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
uint8_t number, uint8_t number,
std::shared_ptr<const JSONObject> json); std::shared_ptr<const JSONObject> json);
~Tournament() = default; ~Tournament() = default;
@@ -108,10 +110,9 @@ public:
std::shared_ptr<JSONObject> json() const; std::shared_ptr<JSONObject> json() const;
std::shared_ptr<const DataIndex> get_data_index() const;
uint8_t get_number() const; uint8_t get_number() const;
const std::string& get_name() const; const std::string& get_name() const;
std::shared_ptr<const DataIndex::MapEntry> get_map() const; std::shared_ptr<const MapIndex::MapEntry> get_map() const;
const Rules& get_rules() const; const Rules& get_rules() const;
bool get_is_2v2() const; bool get_is_2v2() const;
State get_state() const; State get_state() const;
@@ -131,11 +132,12 @@ public:
private: private:
PrefixedLogger log; PrefixedLogger log;
std::shared_ptr<const DataIndex> data_index; std::shared_ptr<const MapIndex> map_index;
std::shared_ptr<const COMDeckIndex> com_deck_index;
std::shared_ptr<const JSONObject> source_json; std::shared_ptr<const JSONObject> source_json;
uint8_t number; uint8_t number;
std::string name; std::string name;
std::shared_ptr<const DataIndex::MapEntry> map; std::shared_ptr<const MapIndex::MapEntry> map;
Rules rules; Rules rules;
size_t num_teams; size_t num_teams;
bool is_2v2; bool is_2v2;
@@ -160,7 +162,8 @@ private:
class TournamentIndex { class TournamentIndex {
public: public:
explicit TournamentIndex( explicit TournamentIndex(
std::shared_ptr<const DataIndex> data_index, std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
const std::string& state_filename, const std::string& state_filename,
bool skip_load_state = false); bool skip_load_state = false);
~TournamentIndex() = default; ~TournamentIndex() = default;
@@ -171,7 +174,7 @@ public:
std::shared_ptr<Tournament> create_tournament( std::shared_ptr<Tournament> create_tournament(
const std::string& name, const std::string& name,
std::shared_ptr<const DataIndex::MapEntry> map, std::shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules, const Rules& rules,
size_t num_teams, size_t num_teams,
bool is_2v2); bool is_2v2);
@@ -183,7 +186,8 @@ public:
uint32_t serial_number) const; uint32_t serial_number) const;
private: private:
std::shared_ptr<const DataIndex> data_index; std::shared_ptr<const MapIndex> map_index;
std::shared_ptr<const COMDeckIndex> com_deck_index;
std::string state_filename; std::string state_filename;
std::shared_ptr<Tournament> tournaments[0x20]; std::shared_ptr<Tournament> tournaments[0x20];
}; };
+13 -1
View File
@@ -195,7 +195,7 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
isdigit(filename[filename.size() - 12]) && isdigit(filename[filename.size() - 12]) &&
(filename[filename.size() - 11] == 'O' || filename[filename.size() - 11] == 'S') && (filename[filename.size() - 11] == 'O' || filename[filename.size() - 11] == 'S') &&
(filename[filename.size() - 10] == 'E' || filename[filename.size() - 10] == 'J' || filename[filename.size() - 10] == 'P') && (filename[filename.size() - 10] == 'E' || filename[filename.size() - 10] == 'J' || filename[filename.size() - 10] == 'P') &&
isdigit(filename[filename.size() - 9])) { (isdigit(filename[filename.size() - 9]) || filename[filename.size() - 9] == 'T')) {
specific_version = 0x33000000 | (filename[filename.size() - 11] << 16) | (filename[filename.size() - 10] << 8) | filename[filename.size() - 9]; specific_version = 0x33000000 | (filename[filename.size() - 11] << 16) | (filename[filename.size() - 10] << 8) | filename[filename.size() - 9];
patch_name = filename.substr(0, filename.size() - 13); patch_name = filename.substr(0, filename.size() - 13);
} }
@@ -375,6 +375,18 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
} }
} }
} }
{
// Generate entries for Trial Editions
data.region_code = 'J';
data.system_code = 'D';
data.version_code = 0;
uint32_t checksum = crc32(&data, sizeof(data));
uint32_t specific_version = 0x33004A54 | (*game_code2 << 16);
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
throw logic_error("multiple specific_versions have same header checksum");
}
data.system_code = 'G';
}
} }
} }
return checksum_to_specific_version.at(header_checksum); return checksum_to_specific_version.at(header_checksum);
+1
View File
@@ -37,6 +37,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
SPECTATORS_FORBIDDEN = 0x00004000, SPECTATORS_FORBIDDEN = 0x00004000,
START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000, START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000,
DROPS_ENABLED = 0x00010000, // Does not affect BB DROPS_ENABLED = 0x00010000, // Does not affect BB
IS_EP3_TRIAL = 0x00020000,
// Flags used only for lobbies // Flags used only for lobbies
PUBLIC = 0x01000000, PUBLIC = 0x01000000,
+23 -22
View File
@@ -1478,32 +1478,33 @@ int main(int argc, char** argv) {
break; break;
} }
case Behavior::SHOW_EP3_MAPS:
case Behavior::SHOW_EP3_CARDS: { case Behavior::SHOW_EP3_CARDS: {
config_log.info("Collecting Episode 3 data"); Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr");
Episode3::DataIndex index("system/ep3", Episode3::BehaviorFlag::LOAD_CARD_TEXT);
if (behavior == Behavior::SHOW_EP3_MAPS) { auto card_ids = card_index.all_ids();
auto map_ids = index.all_map_ids(); log_info("%zu card definitions", card_ids.size());
log_info("%zu maps", map_ids.size()); for (uint32_t card_id : card_ids) {
for (uint32_t map_id : map_ids) { auto entry = card_index.definition_for_id(card_id);
auto map = index.definition_for_map_number(map_id); string s = entry->def.str(false);
string s = map->map.str(&index); string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", ");
fprintf(stdout, "%s\n", s.c_str()); string text = entry->text.empty() ? "(No text available)" : str_replace_all(entry->text, "\n", "\n ");
} fprintf(stdout, "%s\n Tags: %s\n Text:\n %s\n\n", s.c_str(), tags.c_str(), text.c_str());
} else {
auto card_ids = index.all_card_ids();
log_info("%zu card definitions", card_ids.size());
for (uint32_t card_id : card_ids) {
auto entry = index.definition_for_card_id(card_id);
string s = entry->def.str(false);
string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", ");
string text = entry->text.empty() ? "(No text available)" : str_replace_all(entry->text, "\n", "\n ");
fprintf(stdout, "%s\n Tags: %s\n Text:\n %s\n\n", s.c_str(), tags.c_str(), text.c_str());
}
} }
break;
}
case Behavior::SHOW_EP3_MAPS: {
config_log.info("Collecting Episode 3 data");
Episode3::MapIndex map_index("system/ep3");
Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr");
auto map_ids = map_index.all_numbers();
log_info("%zu maps", map_ids.size());
for (uint32_t map_id : map_ids) {
auto map = map_index.definition_for_number(map_id);
string s = map->map.str(&card_index);
fprintf(stdout, "%s\n", s.c_str());
}
break; break;
} }
+1 -1
View File
@@ -9,7 +9,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "Episode3/DataIndex.hh" #include "Episode3/DataIndexes.hh"
#include "ItemData.hh" #include "ItemData.hh"
#include "LevelTable.hh" #include "LevelTable.hh"
#include "Text.hh" #include "Text.hh"
+24 -10
View File
@@ -303,7 +303,7 @@ static void set_console_client_flags(
c->log.info("Game version changed to DC"); c->log.info("Game version changed to DC");
} else if (c->version() == GameVersion::GC) { } else if (c->version() == GameVersion::GC) {
c->flags |= Client::Flag::IS_GC_TRIAL_EDITION; c->flags |= Client::Flag::IS_GC_TRIAL_EDITION;
c->log.info("Trial edition flag set"); c->log.info("GC Trial Edition flag set");
} }
} }
c->flags |= flags_for_version(c->version(), sub_version); c->flags |= flags_for_version(c->version(), sub_version);
@@ -930,7 +930,7 @@ static bool add_next_game_client(
state_cmd.state.first_team_turn = 0xFF; state_cmd.state.first_team_turn = 0xFF;
state_cmd.state.tournament_flag = 0x01; state_cmd.state.tournament_flag = 0x01;
state_cmd.state.client_sc_card_types.clear(Episode3::CardType::INVALID_FF); state_cmd.state.client_sc_card_types.clear(Episode3::CardType::INVALID_FF);
if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1; uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&state_cmd, sizeof(state_cmd), mask_key); set_mask_for_ep3_game_command(&state_cmd, sizeof(state_cmd), mask_key);
} }
@@ -958,7 +958,7 @@ static bool add_next_game_client(
ex_cmd.lose_entries[z].threshold = lose_entries[z].first; ex_cmd.lose_entries[z].threshold = lose_entries[z].first;
ex_cmd.lose_entries[z].value = lose_entries[z].second; ex_cmd.lose_entries[z].value = lose_entries[z].second;
} }
if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1; uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&ex_cmd, sizeof(ex_cmd), mask_key); set_mask_for_ep3_game_command(&ex_cmd, sizeof(ex_cmd), mask_key);
} }
@@ -1239,7 +1239,7 @@ static void on_DC_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
if (l->tournament_match) { if (l->tournament_match) {
auto tourn = l->tournament_match->tournament.lock(); auto tourn = l->tournament_match->tournament.lock();
if (tourn) { if (tourn) {
send_ep3_set_tournament_player_decks(l, c, l->tournament_match); send_ep3_set_tournament_player_decks(s, l, c, l->tournament_match);
string data = Episode3::Server::prepare_6xB6x41_map_definition( string data = Episode3::Server::prepare_6xB6x41_map_definition(
tourn->get_map()); tourn->get_map());
c->channel.send(0x6C, 0x00, data); c->channel.send(0x6C, 0x00, data);
@@ -1312,8 +1312,14 @@ static void on_CA_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
l->log.info("Recreating Episode 3 server state"); l->log.info("Recreating Episode 3 server state");
} }
auto tourn = l->tournament_match ? l->tournament_match->tournament.lock() : nullptr; auto tourn = l->tournament_match ? l->tournament_match->tournament.lock() : nullptr;
bool is_trial = (l->flags & Lobby::Flag::IS_EP3_TRIAL);
l->ep3_server_base = make_shared<Episode3::ServerBase>( l->ep3_server_base = make_shared<Episode3::ServerBase>(
l, s->ep3_data_index, l->random_crypt, tourn ? tourn->get_map() : nullptr); l,
is_trial ? s->ep3_card_index_trial : s->ep3_card_index,
s->ep3_map_index,
s->ep3_behavior_flags,
l->random_crypt,
tourn ? tourn->get_map() : nullptr);
l->ep3_server_base->init(); l->ep3_server_base->init();
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
@@ -1366,7 +1372,7 @@ static void on_CA_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
} else { } else {
throw logic_error("invalid winner team id"); throw logic_error("invalid winner team id");
} }
send_ep3_tournament_match_result(l, l->tournament_match); send_ep3_tournament_match_result(s, l, l->tournament_match);
on_tournament_bracket_updated(s, tourn); on_tournament_bracket_updated(s, tourn);
l->ep3_server_base->server->tournament_match_result_sent = true; l->ep3_server_base->server->tournament_match_result_sent = true;
@@ -1866,6 +1872,7 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
} }
if ((game->version != c->version()) || if ((game->version != c->version()) ||
(!game->is_ep3() != !(c->flags & Client::Flag::IS_EPISODE_3)) || (!game->is_ep3() != !(c->flags & Client::Flag::IS_EPISODE_3)) ||
(!(game->flags & Lobby::Flag::IS_EP3_TRIAL) != !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) ||
((game->flags & Lobby::Flag::NON_V1_ONLY) && (c->flags & Client::Flag::IS_DC_V1))) { ((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."); send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO.");
break; break;
@@ -2141,6 +2148,11 @@ static void on_84(shared_ptr<ServerState> s, shared_ptr<Client> c,
return; return;
} }
if (new_lobby->is_game()) {
send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The specified lobby\nis a game.");
return;
}
if (new_lobby->is_ep3() && !(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."); send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby is for\nEpisode 3 only.");
return; return;
@@ -3203,10 +3215,11 @@ shared_ptr<Lobby> create_game_generic(
(c->version() == GameVersion::BB) || (c->version() == GameVersion::BB) ||
(s->item_tracking_enabled && (mode == GameMode::NORMAL || mode == GameMode::SOLO)); (s->item_tracking_enabled && (mode == GameMode::NORMAL || mode == GameMode::SOLO));
// only disable drops if the config flag is set and are playing regualr multi-mode. // Only disable drops if the config flag is set and are playing regular
// drops are still enabled for battle and challenge modes. // multi-mode. Drops are still enabled for battle and challenge modes.
bool drops_enabled = bool drops_enabled = (s->drops_enabled || (mode != GameMode::NORMAL));
(s->drops_enabled || (mode != GameMode::NORMAL));
bool is_ep3_trial = (c->version() == GameVersion::GC) && (c->flags & Client::Flag::IS_EPISODE_3) && (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION);
shared_ptr<Lobby> game = s->create_lobby(); shared_ptr<Lobby> game = s->create_lobby();
game->name = name; game->name = name;
@@ -3214,6 +3227,7 @@ shared_ptr<Lobby> create_game_generic(
Lobby::Flag::GAME | Lobby::Flag::GAME |
(item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) | (item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) |
(drops_enabled ? Lobby::Flag::DROPS_ENABLED : 0) | (drops_enabled ? Lobby::Flag::DROPS_ENABLED : 0) |
(is_ep3_trial ? Lobby::Flag::IS_EP3_TRIAL : 0) |
((s->cheat_mode_behavior == ServerState::CheatModeBehavior::ON_BY_DEFAULT) ? Lobby::Flag::CHEATS_ENABLED : 0); ((s->cheat_mode_behavior == ServerState::CheatModeBehavior::ON_BY_DEFAULT) ? Lobby::Flag::CHEATS_ENABLED : 0);
game->password = password; game->password = password;
game->version = c->version(); game->version = c->version();
+1 -1
View File
@@ -380,7 +380,7 @@ static void on_ep3_battle_subs(shared_ptr<ServerState> s,
} }
} }
if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = 0; uint8_t mask_key = 0;
while (!mask_key) { while (!mask_key) {
mask_key = random_object<uint8_t>(); mask_key = random_object<uint8_t>();
+13 -6
View File
@@ -2180,7 +2180,9 @@ void send_rare_enemy_index_list(shared_ptr<Client> c, const vector<size_t>& inde
void send_ep3_card_list_update(shared_ptr<ServerState> s, shared_ptr<Client> c) { void send_ep3_card_list_update(shared_ptr<ServerState> s, shared_ptr<Client> c) {
if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) { if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) {
const auto& data = s->ep3_data_index->get_compressed_card_definitions(); const auto& data = (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)
? s->ep3_card_index_trial->get_compressed_definitions()
: s->ep3_card_index->get_compressed_definitions();
StringWriter w; StringWriter w;
w.put_u32l(data.size()); w.put_u32l(data.size());
@@ -2486,6 +2488,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
} }
void send_ep3_set_tournament_player_decks( void send_ep3_set_tournament_player_decks(
shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Lobby> l,
shared_ptr<Client> c, shared_ptr<Client> c,
shared_ptr<const Episode3::Tournament::Match> match) { shared_ptr<const Episode3::Tournament::Match> match) {
@@ -2532,7 +2535,7 @@ void send_ep3_set_tournament_player_decks(
add_entries_for_team(match->preceding_a->winner_team, 0); add_entries_for_team(match->preceding_a->winner_team, 0);
add_entries_for_team(match->preceding_b->winner_team, 2); add_entries_for_team(match->preceding_b->winner_team, 2);
if (!(tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1; uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
} }
@@ -2543,7 +2546,7 @@ void send_ep3_set_tournament_player_decks(
} }
void send_ep3_tournament_match_result( void send_ep3_tournament_match_result(
shared_ptr<Lobby> l, shared_ptr<const Episode3::Tournament::Match> match) { shared_ptr<ServerState> s, shared_ptr<Lobby> l, shared_ptr<const Episode3::Tournament::Match> match) {
auto tourn = match->tournament.lock(); auto tourn = match->tournament.lock();
if (!tourn) { if (!tourn) {
return; return;
@@ -2585,13 +2588,13 @@ void send_ep3_tournament_match_result(
// the player 1000000 and never charge for anything. // the player 1000000 and never charge for anything.
cmd.meseta_amount = 100; cmd.meseta_amount = 100;
cmd.meseta_reward_text = "You got %s meseta!"; cmd.meseta_reward_text = "You got %s meseta!";
if (!(tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1; uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
} }
send_command_t(l, 0xC9, 0x00, cmd); send_command_t(l, 0xC9, 0x00, cmd);
if (tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
send_text_message_printf(l, "$C5TOURN/%02hhX/%zu WIN %c", send_text_message_printf(l, "$C5TOURN/%02hhX/%zu WIN %c",
tourn->get_number(), match->round_num, tourn->get_number(), match->round_num,
match->winner_team == match->preceding_a->winner_team ? 'A' : 'B'); match->winner_team == match->preceding_a->winner_team ? 'A' : 'B');
@@ -2806,6 +2809,10 @@ void send_card_auction_if_all_clients_ready(
distribution_size += it.second.first; distribution_size += it.second.first;
} }
auto card_index = (l->flags & Lobby::Flag::IS_EP3_TRIAL)
? s->ep3_card_index_trial
: s->ep3_card_index;
S_StartCardAuction_GC_Ep3_EF cmd; S_StartCardAuction_GC_Ep3_EF cmd;
cmd.points_available = s->ep3_card_auction_points; cmd.points_available = s->ep3_card_auction_points;
for (size_t z = 0; z < num_cards; z++) { for (size_t z = 0; z < num_cards; z++) {
@@ -2814,7 +2821,7 @@ void send_card_auction_if_all_clients_ready(
if (v >= it.second.first) { if (v >= it.second.first) {
v -= it.second.first; v -= it.second.first;
} else { } else {
cmd.entries[z].card_id = s->ep3_data_index->definition_for_card_name(it.first)->def.card_id.load(); 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].min_price = it.second.second;
break; break;
} }
+2
View File
@@ -352,10 +352,12 @@ void send_ep3_tournament_info(
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t); std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_set_tournament_player_decks( void send_ep3_set_tournament_player_decks(
std::shared_ptr<ServerState> s,
std::shared_ptr<Lobby> l, std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament::Match> match); std::shared_ptr<const Episode3::Tournament::Match> match);
void send_ep3_tournament_match_result( void send_ep3_tournament_match_result(
std::shared_ptr<ServerState> s,
std::shared_ptr<Lobby> l, std::shared_ptr<Lobby> l,
std::shared_ptr<const Episode3::Tournament::Match> match); std::shared_ptr<const Episode3::Tournament::Match> match);
+1 -1
View File
@@ -451,7 +451,7 @@ Proxy session commands:\n\
} else if (command_name == "create-tournament") { } else if (command_name == "create-tournament") {
string name = get_quoted_string(command_args); string name = get_quoted_string(command_args);
string map_name = get_quoted_string(command_args); string map_name = get_quoted_string(command_args);
auto map = this->state->ep3_data_index->definition_for_map_name(map_name); auto map = this->state->ep3_map_index->definition_for_name(map_name);
uint32_t num_teams = stoul(get_quoted_string(command_args), nullptr, 0); uint32_t num_teams = stoul(get_quoted_string(command_args), nullptr, 0);
Episode3::Rules rules; Episode3::Rules rules;
rules.set_defaults(); rules.set_defaults();
+10 -5
View File
@@ -888,19 +888,24 @@ void ServerState::load_item_tables() {
} }
void ServerState::load_ep3_data() { void ServerState::load_ep3_data() {
config_log.info("Collecting Episode 3 data"); config_log.info("Collecting Episode 3 maps");
this->ep3_data_index.reset(new Episode3::DataIndex( this->ep3_map_index.reset(new Episode3::MapIndex("system/ep3"));
"system/ep3", this->ep3_behavior_flags)); config_log.info("Loading Episode 3 card definitions");
this->ep3_card_index.reset(new Episode3::CardIndex("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr"));
config_log.info("Loading Episode 3 trial card definitions");
this->ep3_card_index_trial.reset(new Episode3::CardIndex("system/ep3/card-definitions-trial.mnr", "system/ep3/card-definitions-trial.mnrd", "system/ep3/card-text-trial.mnr"));
config_log.info("Loading Episode 3 COM decks");
this->ep3_com_deck_index.reset(new Episode3::COMDeckIndex("system/ep3/com-decks.json"));
const string& tournament_state_filename = "system/ep3/tournament-state.json"; const string& tournament_state_filename = "system/ep3/tournament-state.json";
try { try {
this->ep3_tournament_index.reset(new Episode3::TournamentIndex( this->ep3_tournament_index.reset(new Episode3::TournamentIndex(
this->ep3_data_index, tournament_state_filename)); this->ep3_map_index, this->ep3_com_deck_index, tournament_state_filename));
config_log.info("Loaded Episode 3 tournament state"); config_log.info("Loaded Episode 3 tournament state");
} catch (const exception& e) { } catch (const exception& e) {
config_log.warning("Cannot load Episode 3 tournament state: %s", e.what()); config_log.warning("Cannot load Episode 3 tournament state: %s", e.what());
this->ep3_tournament_index.reset(new Episode3::TournamentIndex( this->ep3_tournament_index.reset(new Episode3::TournamentIndex(
this->ep3_data_index, tournament_state_filename, true)); this->ep3_map_index, this->ep3_com_deck_index, tournament_state_filename, true));
} }
} }
+5 -2
View File
@@ -11,7 +11,7 @@
#include "Client.hh" #include "Client.hh"
#include "CommonItemSet.hh" #include "CommonItemSet.hh"
#include "Episode3/DataIndex.hh" #include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh" #include "Episode3/Tournament.hh"
#include "FunctionCompiler.hh" #include "FunctionCompiler.hh"
#include "GSLArchive.hh" #include "GSLArchive.hh"
@@ -70,7 +70,10 @@ struct ServerState {
std::shared_ptr<const PatchFileIndex> pc_patch_file_index; std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
std::shared_ptr<const PatchFileIndex> bb_patch_file_index; std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
std::shared_ptr<const DOLFileIndex> dol_file_index; std::shared_ptr<const DOLFileIndex> dol_file_index;
std::shared_ptr<const Episode3::DataIndex> ep3_data_index; std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
std::shared_ptr<const QuestCategoryIndex> quest_category_index; std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> quest_index; std::shared_ptr<const QuestIndex> quest_index;
std::shared_ptr<const LevelTable> level_table; std::shared_ptr<const LevelTable> level_table;
-1
View File
@@ -87,7 +87,6 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) {
return Client::Flag::NO_D6_AFTER_LOBBY | return Client::Flag::NO_D6_AFTER_LOBBY |
Client::Flag::IS_EPISODE_3 | Client::Flag::IS_EPISODE_3 |
Client::Flag::IS_EP3_TRIAL_EDITION | Client::Flag::IS_EP3_TRIAL_EDITION |
Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL |
Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
case 0x42: // GC Ep3 JP case 0x42: // GC Ep3 JP
return Client::Flag::NO_D6_AFTER_LOBBY | return Client::Flag::NO_D6_AFTER_LOBBY |
-1
View File
@@ -279,7 +279,6 @@
// 0x0008 => Disable overall and per-phase battle time limits, regardless of // 0x0008 => Disable overall and per-phase battle time limits, regardless of
// the values chosen during battle rules setup // the values chosen during battle rules setup
// 0x0010 => Enable debug messages in Episode 3 games and battles // 0x0010 => Enable debug messages in Episode 3 games and battles
// 0x0020 => This flag is used internally and has no effect on battles
// 0x0040 => Enable battle recording (after a battle, players can save the // 0x0040 => Enable battle recording (after a battle, players can save the
// recording with the $saverec <filename> command) // recording with the $saverec <filename> command)
// 0x0080 => Disable command masking during battles // 0x0080 => Disable command masking during battles
Binary file not shown.
BIN
View File
Binary file not shown.
+21 -13
View File
@@ -3,9 +3,10 @@
# The returned value has the format SSGGRRVV, where: # The returned value has the format SSGGRRVV, where:
# S = 33 (which represents PSO GC) # S = 33 (which represents PSO GC)
# G = game (4F (O) = Episodes 1&2, 53 (S) = Episode 3) # G = game (4F (O) = Ep1&2, 53 (S) = Ep3)
# R = region (45 (E), 4A (J), or 50 (P)) # R = region (45 (E), 4A (J), or 50 (P))
# V = minor version | 30 (30 = 1.00, 31 = 1.01, 32 = 1.02, etc.) # V = minor version | 30 (30 = 1.00, 31 = 1.01, 32 = 1.02, etc.), or 54 for
# Trial Edition
# This results in a 4-character ASCII-printable version code which encodes all # This results in a 4-character ASCII-printable version code which encodes all
# of the above information. This value is called specific_version in the places # of the above information. This value is called specific_version in the places
# where it's used by the server. # where it's used by the server.
@@ -19,16 +20,23 @@ reloc0:
start: start:
lis r3, 0x8000 lis r3, 0x8000
lwz r4, [r3] lwz r4, [r3]
lbz r5, [r3 + 7]
li r3, -1
rlwinm r0, r4, 16, 16, 31
cmplwi r0, 0x4750
bnelr
lis r3, 0x3300
rlwimi r3, r4, 8, 8, 23
rlwimi r3, r5, 0, 24, 31
ori r3, r3, 0x0030
# For Trial Editions, set the V field to 54; for other versions, set it to
# 0x30 | disc_version
rlwinm r0, r4, 8, 24, 31
cmplwi r0, 0x47 # Check if high byte of game ID is 'G'
beq not_trial
cmplwi r0, 0x44 # Check if high byte of game ID is 'D'
beq is_trial
li r3, 0
blr
is_trial:
li r3, 0x0054
b end_trial_check
not_trial:
lbz r3, [r3 + 7]
ori r3, r3, 0x0030
end_trial_check:
oris r3, r3, 0x3300 # Set high byte ('3')
rlwimi r3, r4, 8, 8, 23 # Set middle two bytes to last to bytes of game ID
blr blr