rewrite map data model
This commit is contained in:
@@ -92,7 +92,7 @@ public:
|
||||
private:
|
||||
struct File {
|
||||
std::shared_ptr<const std::string> data;
|
||||
const Table* table;
|
||||
const Table* table = nullptr;
|
||||
};
|
||||
|
||||
// Indexed as [online/offline][episode]
|
||||
|
||||
+27
-20
@@ -465,7 +465,7 @@ static void server_command_swsetall(shared_ptr<Client> c, const std::string&) {
|
||||
auto& cmd = cmds[z];
|
||||
cmd.header.subcommand = 0x05;
|
||||
cmd.header.size = 0x03;
|
||||
cmd.header.object_id = 0xFFFF;
|
||||
cmd.header.entity_id = 0xFFFF;
|
||||
cmd.switch_flag_floor = c->floor;
|
||||
cmd.switch_flag_num = z;
|
||||
cmd.flags = 0x01;
|
||||
@@ -485,7 +485,7 @@ static void proxy_command_swsetall(shared_ptr<ProxyServer::LinkedSession> ses, c
|
||||
auto& cmd = cmds[z];
|
||||
cmd.header.subcommand = 0x05;
|
||||
cmd.header.size = 0x03;
|
||||
cmd.header.object_id = 0xFFFF;
|
||||
cmd.header.entity_id = 0xFFFF;
|
||||
cmd.switch_flag_floor = ses->floor;
|
||||
cmd.switch_flag_num = z;
|
||||
cmd.flags = 0x01;
|
||||
@@ -1117,7 +1117,7 @@ static void server_command_cheat(shared_ptr<Client> c, const std::string&) {
|
||||
if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) &&
|
||||
!c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE) &&
|
||||
s->cheat_flags.insufficient_minimum_level) {
|
||||
size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty);
|
||||
size_t default_min_level = s->default_min_level_for_game(c->version(), l->episode, l->difficulty);
|
||||
if (l->min_level < default_min_level) {
|
||||
l->min_level = default_min_level;
|
||||
send_text_message_printf(l, "$C6Minimum level set\nto %" PRIu32, l->min_level + 1);
|
||||
@@ -1312,7 +1312,7 @@ static void server_command_secid(shared_ptr<Client> c, const std::string& args)
|
||||
c->config.override_section_id = new_override_section_id;
|
||||
if (l->is_game() && (l->leader_id == c->lobby_client_id)) {
|
||||
l->override_section_id = new_override_section_id;
|
||||
l->change_section_id();
|
||||
l->create_item_creator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1343,11 +1343,17 @@ static void server_command_variations(shared_ptr<Client> c, const std::string& a
|
||||
check_is_game(l, false);
|
||||
check_cheats_allowed(s, c, s->cheat_flags.override_variations);
|
||||
|
||||
c->override_variations = make_unique<parray<le_uint32_t, 0x20>>();
|
||||
c->override_variations->clear(0);
|
||||
for (size_t z = 0; z < min<size_t>(c->override_variations->size(), args.size()); z++) {
|
||||
c->override_variations->at(z) = args[z] - '0';
|
||||
c->override_variations = make_unique<Variations>();
|
||||
for (size_t z = 0; z < min<size_t>(c->override_variations->entries.size() * 2, args.size()); z++) {
|
||||
auto& entry = c->override_variations->entries.at(z / 2);
|
||||
if (z & 1) {
|
||||
entry.entities = args[z] - '0';
|
||||
} else {
|
||||
entry.layout = args[z] - '0';
|
||||
}
|
||||
}
|
||||
auto vars_str = c->override_variations->str();
|
||||
c->log.info("Override variations set to %s", vars_str.c_str());
|
||||
}
|
||||
|
||||
static void server_command_rand(shared_ptr<Client> c, const std::string& args) {
|
||||
@@ -1441,7 +1447,7 @@ static void server_command_min_level(shared_ptr<Client> c, const std::string& ar
|
||||
bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) ||
|
||||
c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
|
||||
if (!cheats_allowed && s->cheat_flags.insufficient_minimum_level) {
|
||||
size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty);
|
||||
size_t default_min_level = s->default_min_level_for_game(c->version(), l->episode, l->difficulty);
|
||||
if (new_min_level < default_min_level) {
|
||||
send_text_message_printf(c, "$C6Cannot set minimum\nlevel below %zu", default_min_level + 1);
|
||||
return;
|
||||
@@ -2084,7 +2090,10 @@ static void proxy_command_next(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
static void server_command_where(shared_ptr<Client> c, const std::string&) {
|
||||
auto l = c->require_lobby();
|
||||
send_text_message_printf(c, "$C7%01" PRIX32 ":%s X:%" PRId32 " Z:%" PRId32,
|
||||
c->floor, short_name_for_floor(l->episode, c->floor), static_cast<int32_t>(c->x), static_cast<int32_t>(c->z));
|
||||
c->floor,
|
||||
short_name_for_floor(l->episode, c->floor),
|
||||
static_cast<int32_t>(c->pos.x.load()),
|
||||
static_cast<int32_t>(c->pos.z.load()));
|
||||
for (auto lc : l->clients) {
|
||||
if (lc && (lc != c)) {
|
||||
string name = lc->character()->disp.name.decode(lc->language());
|
||||
@@ -2108,9 +2117,7 @@ static void server_command_what(shared_ptr<Client> c, const std::string&) {
|
||||
if (!it.second->visible_to_client(c->lobby_client_id)) {
|
||||
continue;
|
||||
}
|
||||
float dx = it.second->x - c->x;
|
||||
float dz = it.second->z - c->z;
|
||||
float dist2 = (dx * dx) + (dz * dz);
|
||||
float dist2 = (it.second->pos - c->pos).norm2();
|
||||
if (!nearest_fi || (dist2 < min_dist2)) {
|
||||
nearest_fi = it.second;
|
||||
min_dist2 = dist2;
|
||||
@@ -2281,7 +2288,7 @@ static void server_command_dropmode(shared_ptr<Client> c, const std::string& arg
|
||||
return;
|
||||
}
|
||||
|
||||
l->set_drop_mode(new_mode);
|
||||
l->drop_mode = new_mode;
|
||||
switch (l->drop_mode) {
|
||||
case Lobby::DropMode::DISABLED:
|
||||
send_text_message(l, "Item drops disabled");
|
||||
@@ -2358,11 +2365,11 @@ static void server_command_item(shared_ptr<Client> c, const std::string& args) {
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
|
||||
if ((l->drop_mode == Lobby::DropMode::SERVER_PRIVATE) || (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE)) {
|
||||
l->add_item(c->floor, item, c->x, c->z, (1 << c->lobby_client_id));
|
||||
send_drop_stacked_item_to_channel(s, c->channel, item, c->floor, c->x, c->z);
|
||||
l->add_item(c->floor, item, c->pos, nullptr, nullptr, (1 << c->lobby_client_id));
|
||||
send_drop_stacked_item_to_channel(s, c->channel, item, c->floor, c->pos);
|
||||
} else {
|
||||
l->add_item(c->floor, item, c->x, c->z, 0x00F);
|
||||
send_drop_stacked_item_to_lobby(l, item, c->floor, c->x, c->z);
|
||||
l->add_item(c->floor, item, c->pos, nullptr, nullptr, 0x00F);
|
||||
send_drop_stacked_item_to_lobby(l, item, c->floor, c->pos);
|
||||
}
|
||||
|
||||
string name = s->describe_item(c->version(), item, true);
|
||||
@@ -2397,8 +2404,8 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
|
||||
|
||||
} else {
|
||||
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->x, ses->z);
|
||||
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->x, ses->z);
|
||||
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->pos);
|
||||
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->pos);
|
||||
|
||||
string name = s->describe_item(ses->version(), item, true);
|
||||
send_text_message(ses->client_channel, "$C7Item created:\n" + name);
|
||||
|
||||
+6
-2
@@ -195,8 +195,6 @@ Client::Client(
|
||||
bb_connection_phase(0xFF),
|
||||
ping_start_time(0),
|
||||
sub_version(-1),
|
||||
x(0.0f),
|
||||
z(0.0f),
|
||||
floor(0),
|
||||
lobby_client_id(0),
|
||||
lobby_arrow_color(0),
|
||||
@@ -407,6 +405,9 @@ bool Client::can_see_quest(
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const {
|
||||
if (!q->has_version_any_language(this->version())) {
|
||||
return false;
|
||||
}
|
||||
return this->evaluate_quest_availability_expression(q->available_expression, game, event, difficulty, num_players, v1_present);
|
||||
}
|
||||
|
||||
@@ -417,6 +418,9 @@ bool Client::can_play_quest(
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const {
|
||||
if (!q->has_version_any_language(this->version())) {
|
||||
return false;
|
||||
}
|
||||
return this->evaluate_quest_availability_expression(q->enabled_expression, game, event, difficulty, num_players, v1_present);
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -206,10 +206,9 @@ public:
|
||||
// Lobby/positioning
|
||||
Config config;
|
||||
Config synced_config;
|
||||
std::unique_ptr<parray<le_uint32_t, 0x20>> override_variations;
|
||||
std::unique_ptr<Variations> override_variations;
|
||||
int32_t sub_version;
|
||||
float x;
|
||||
float z;
|
||||
VectorXZF pos;
|
||||
uint32_t floor;
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
uint8_t lobby_client_id;
|
||||
|
||||
+131
-215
@@ -9,6 +9,7 @@
|
||||
#include "Episode3/DeckState.hh"
|
||||
#include "Episode3/MapState.hh"
|
||||
#include "Episode3/PlayerStateSubordinates.hh"
|
||||
#include "Map.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
@@ -1233,7 +1234,7 @@ struct S_JoinGameT_DC_PC {
|
||||
// field when sending this command to start an Episode 3 tournament game. This
|
||||
// can be misleading when reading old logs from those days, but the Episode 3
|
||||
// client really does ignore it.
|
||||
/* 0004 */ parray<le_uint32_t, 0x20> variations;
|
||||
/* 0004 */ Variations variations;
|
||||
// Unlike lobby join commands, these are filled in in their slot positions.
|
||||
// That is, if there's only one player in a game with ID 2, then the first two
|
||||
// of these are blank and the player's data is in the third entry here.
|
||||
@@ -1246,7 +1247,7 @@ struct S_JoinGameT_DC_PC {
|
||||
/* 0109 */ uint8_t event = 0;
|
||||
/* 010A */ uint8_t section_id = 0;
|
||||
/* 010B */ uint8_t challenge_mode = 0;
|
||||
/* 010C */ le_uint32_t rare_seed = 0;
|
||||
/* 010C */ le_uint32_t random_seed = 0;
|
||||
/* 0110 */
|
||||
} __packed__;
|
||||
|
||||
@@ -1255,7 +1256,7 @@ struct S_JoinGame_DCNTE_64 {
|
||||
uint8_t leader_id = 0;
|
||||
uint8_t disable_udp = 1;
|
||||
uint8_t unused = 0;
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
Variations variations;
|
||||
parray<PlayerLobbyDataDCGC, 4> lobby_data;
|
||||
} __packed_ws__(S_JoinGame_DCNTE_64, 0x104);
|
||||
using S_JoinGame_DC_64 = S_JoinGameT_DC_PC<PlayerLobbyDataDCGC>;
|
||||
@@ -2238,50 +2239,19 @@ struct S_ServerTime_B1 {
|
||||
|
||||
struct S_ExecuteCode_B2 {
|
||||
// If code_size == 0, no code is executed, but checksumming may still occur.
|
||||
// In that case, this structure is the entire body of the command (no footer
|
||||
// is sent).
|
||||
// In that case, this structure is the entire body of the command (no code
|
||||
// or footer is sent).
|
||||
le_uint32_t code_size = 0; // Size of code (following this struct) and footer
|
||||
le_uint32_t checksum_start = 0; // May be null if size is zero
|
||||
le_uint32_t checksum_size = 0; // If zero, no checksum is computed
|
||||
// The code immediately follows, ending with an S_ExecuteCode_Footer_B2
|
||||
// The code immediately follows, ending with a RELFileFooter. The REL's root
|
||||
// offset is relative to the start of the code object here, so an offset of 0
|
||||
// refers to the byte after checksum_size. The root offset points to the
|
||||
// offset ot the entrypoint (that is, the entrypoint offset is doubly
|
||||
// indirect). This is presumably done so the entrypoint can be optionally
|
||||
// relocated.
|
||||
} __packed_ws__(S_ExecuteCode_B2, 0x0C);
|
||||
|
||||
template <bool BE>
|
||||
struct S_ExecuteCode_FooterT_B2 {
|
||||
static constexpr bool IsBE = BE; // Needed by generate_client_command_t
|
||||
|
||||
// Relocations is a list of words (le_uint16_t on DC/PC/XB/BB, be_uint16_t on
|
||||
// GC) containing the number of doublewords (uint32_t) to skip for each
|
||||
// relocation. The relocation pointer starts immediately after the
|
||||
// checksum_size field in the header, and advances by the value of one
|
||||
// relocation word (times 4) before each relocation. At each relocated
|
||||
// doubleword, the address of the first byte of the code (after checksum_size)
|
||||
// is added to the existing value.
|
||||
// For example, if the code segment contains the following data (where R
|
||||
// specifies doublewords to relocate):
|
||||
// RR RR RR RR ?? ?? ?? ?? ?? ?? ?? ?? RR RR RR RR
|
||||
// RR RR RR RR ?? ?? ?? ?? RR RR RR RR
|
||||
// then the relocation words should be 0000, 0003, 0001, and 0002.
|
||||
// If there is a small number of relocations, they may be placed in the unused
|
||||
// fields of this structure to save space and/or confuse reverse engineers.
|
||||
// The game never accesses the last 12 bytes of this structure unless
|
||||
// relocations_offset points there, so those 12 bytes may also be omitted from
|
||||
// the command entirely (without changing code_size - so code_size would
|
||||
// technically extend beyond the end of the B2 command).
|
||||
U32T<BE> relocations_offset = 0; // Relative to code base (after checksum_size)
|
||||
U32T<BE> num_relocations = 0;
|
||||
parray<U32T<BE>, 2> unused1;
|
||||
// entrypoint_offset is doubly indirect - it points to a pointer to a 32-bit
|
||||
// value that itself is the actual entrypoint. This is presumably done so the
|
||||
// entrypoint can be optionally relocated.
|
||||
U32T<BE> entrypoint_addr_offset = 0; // Relative to code base (after checksum_size).
|
||||
parray<U32T<BE>, 3> unused2;
|
||||
} __packed__;
|
||||
using S_ExecuteCode_Footer_GC_B2 = S_ExecuteCode_FooterT_B2<true>;
|
||||
using S_ExecuteCode_Footer_DC_PC_XB_BB_B2 = S_ExecuteCode_FooterT_B2<false>;
|
||||
check_struct_size(S_ExecuteCode_Footer_GC_B2, 0x20);
|
||||
check_struct_size(S_ExecuteCode_Footer_DC_PC_XB_BB_B2, 0x20);
|
||||
|
||||
// B3 (C->S): Execute code and/or checksum memory result
|
||||
// Not used on versions that don't support the B2 command (see above).
|
||||
|
||||
@@ -3244,7 +3214,7 @@ struct SC_SyncSaveFiles_BB_E7 {
|
||||
// primary game's players should be the leader (this is what newserv does).
|
||||
|
||||
struct S_JoinSpectatorTeam_Ep3_E8 {
|
||||
/* 0000 */ parray<le_uint32_t, 0x20> variations; // unused
|
||||
/* 0000 */ Variations variations; // unused
|
||||
struct PlayerEntry {
|
||||
/* 0000 */ PlayerLobbyDataDCGC lobby_data;
|
||||
/* 0020 */ PlayerInventory inventory;
|
||||
@@ -3260,7 +3230,7 @@ struct S_JoinSpectatorTeam_Ep3_E8 {
|
||||
/* 1175 */ uint8_t event = 0;
|
||||
/* 1176 */ uint8_t section_id = 0;
|
||||
/* 1177 */ uint8_t challenge_mode = 0;
|
||||
/* 1178 */ le_uint32_t rare_seed = 0;
|
||||
/* 1178 */ le_uint32_t random_seed = 0;
|
||||
/* 117C */ uint8_t episode = 0;
|
||||
/* 117D */ parray<uint8_t, 3> unused;
|
||||
struct SpectatorEntry {
|
||||
@@ -3867,16 +3837,11 @@ struct G_ClientIDHeader {
|
||||
uint8_t size = 0;
|
||||
le_uint16_t client_id = 0; // <= 12
|
||||
} __packed_ws__(G_ClientIDHeader, 4);
|
||||
struct G_EnemyIDHeader {
|
||||
struct G_EntityIDHeader {
|
||||
uint8_t subcommand = 0;
|
||||
uint8_t size = 0;
|
||||
le_uint16_t enemy_id = 0; // In [0x1000, 0x4000); not the same as enemy_index!
|
||||
} __packed_ws__(G_EnemyIDHeader, 4);
|
||||
struct G_ObjectIDHeader {
|
||||
uint8_t subcommand = 0;
|
||||
uint8_t size = 0;
|
||||
le_uint16_t object_id = 0; // >= 0x4000, != 0xFFFF
|
||||
} __packed_ws__(G_ObjectIDHeader, 4);
|
||||
le_uint16_t entity_id = 0; // 0-B=client, 1000-3FFF=enemy, 4000-FFFF=object
|
||||
} __packed_ws__(G_EntityIDHeader, 4);
|
||||
struct G_ParameterHeader {
|
||||
uint8_t subcommand = 0;
|
||||
uint8_t size = 0;
|
||||
@@ -3916,9 +3881,9 @@ struct G_Unknown_6x02_6x03 {
|
||||
// TODO: This does something with TObjDoorKey objects
|
||||
|
||||
struct G_Unknown_6x04 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t door_key_token = 0;
|
||||
le_uint16_t unused = 0;
|
||||
G_ParameterHeader header; // param = door token (NOT entity ID or index)
|
||||
le_uint16_t unused1 = 0;
|
||||
le_uint16_t unused2 = 0;
|
||||
} __packed_ws__(G_Unknown_6x04, 8);
|
||||
|
||||
// 6x05: Switch state changed
|
||||
@@ -3930,7 +3895,7 @@ struct G_Unknown_6x04 {
|
||||
|
||||
struct G_SwitchStateChanged_6x05 {
|
||||
// Note: header.object_id is 0xFFFF for room clear when all enemies defeated
|
||||
G_ObjectIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
// TODO: Some of these might be big-endian on GC; it only byteswaps
|
||||
// switch_flag_num. Are the others actually uint16, or are they uint8[2]?
|
||||
le_uint16_t client_id = 0;
|
||||
@@ -3996,14 +3961,14 @@ struct G_SymbolChat_6x07 {
|
||||
// 6x09: Unknown
|
||||
|
||||
struct G_Unknown_6x09 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
} __packed_ws__(G_Unknown_6x09, 4);
|
||||
|
||||
// 6x0A: Update enemy state
|
||||
|
||||
template <bool BE>
|
||||
struct G_UpdateEnemyStateT_6x0A {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t enemy_index = 0; // [0, 0xB50)
|
||||
le_uint16_t total_damage = 0;
|
||||
// Flags:
|
||||
@@ -4019,7 +3984,7 @@ check_struct_size(G_UpdateEnemyState_DC_PC_XB_BB_6x0A, 0x0C);
|
||||
// 6x0B: Update object state
|
||||
|
||||
struct G_UpdateObjectState_6x0B {
|
||||
G_ClientIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint32_t flags = 0;
|
||||
le_uint32_t object_index = 0;
|
||||
} __packed_ws__(G_UpdateObjectState_6x0B, 0x0C);
|
||||
@@ -4069,12 +4034,12 @@ struct G_Unknown_6x0E {
|
||||
|
||||
// 6x10: Unknown (not valid on Episode 3)
|
||||
|
||||
struct G_Unknown_6x10_6x11_6x12_6x14 {
|
||||
G_EnemyIDHeader header;
|
||||
struct G_Unknown_6x10_6x11_6x14 {
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint32_t unknown_a4 = 0;
|
||||
} __packed_ws__(G_Unknown_6x10_6x11_6x12_6x14, 0x0C);
|
||||
} __packed_ws__(G_Unknown_6x10_6x11_6x14, 0x0C);
|
||||
|
||||
// 6x11: Unknown (not valid on Episode 3)
|
||||
// Same format as 6x10
|
||||
@@ -4083,7 +4048,7 @@ struct G_Unknown_6x10_6x11_6x12_6x14 {
|
||||
|
||||
template <bool BE>
|
||||
struct G_DragonBossActionsT_6x12 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint32_t unknown_a4 = 0;
|
||||
@@ -4098,7 +4063,7 @@ check_struct_size(G_DragonBossActions_GC_6x12, 0x14);
|
||||
// 6x13: De Rol Le boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_DeRolLeBossActions_6x13 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
} __packed_ws__(G_DeRolLeBossActions_6x13, 8);
|
||||
@@ -4109,7 +4074,7 @@ struct G_DeRolLeBossActions_6x13 {
|
||||
// 6x15: Vol Opt boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_VolOptBossActions_6x15 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint16_t unknown_a4 = 0;
|
||||
@@ -4119,7 +4084,7 @@ struct G_VolOptBossActions_6x15 {
|
||||
// 6x16: Vol Opt boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_VolOptBossActions_6x16 {
|
||||
G_UnusedHeader header;
|
||||
G_EntityIDHeader header;
|
||||
parray<uint8_t, 6> unknown_a2;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
} __packed_ws__(G_VolOptBossActions_6x16, 0x0C);
|
||||
@@ -4127,7 +4092,7 @@ struct G_VolOptBossActions_6x16 {
|
||||
// 6x17: Vol Opt phase 2 boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_VolOpt2BossActions_6x17 {
|
||||
G_ClientIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_float unknown_a2 = 0.0f;
|
||||
le_float unknown_a3 = 0.0f;
|
||||
le_float unknown_a4 = 0.0f;
|
||||
@@ -4137,14 +4102,14 @@ struct G_VolOpt2BossActions_6x17 {
|
||||
// 6x18: Vol Opt phase 2 boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_VolOpt2BossActions_6x18 {
|
||||
G_ClientIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
parray<le_uint16_t, 4> unknown_a2;
|
||||
} __packed_ws__(G_VolOpt2BossActions_6x18, 0x0C);
|
||||
|
||||
// 6x19: Dark Falz boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_DarkFalzActions_6x19 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint32_t unknown_a4 = 0;
|
||||
@@ -4186,9 +4151,7 @@ struct G_SetPlayerFloor_6x1F {
|
||||
struct G_SetPosition_6x20 {
|
||||
G_ClientIDHeader header;
|
||||
le_int32_t floor = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
} __packed_ws__(G_SetPosition_6x20, 0x18);
|
||||
|
||||
@@ -4212,9 +4175,7 @@ struct G_SetPlayerVisibility_6x22_6x23 {
|
||||
struct G_TeleportPlayer_6x24 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_TeleportPlayer_6x24, 0x14);
|
||||
|
||||
// 6x25: Equip item (protected on V3/V4)
|
||||
@@ -4266,9 +4227,7 @@ struct G_DropItem_6x2A {
|
||||
le_uint16_t amount = 0;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint32_t item_id = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_DropItem_6x2A, 0x18);
|
||||
|
||||
// 6x2B: Create item in inventory (e.g. via tekker or bank withdraw) (protected on V3/V4)
|
||||
@@ -4377,11 +4336,11 @@ struct G_PhotonBlast_6x37 {
|
||||
|
||||
// 6x38: Donate to photon blast (protected on V3/V4)
|
||||
|
||||
struct G_Unknown_6x38 {
|
||||
struct G_DonateToPhotonBlast_6x38 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unused = 0;
|
||||
} __packed_ws__(G_Unknown_6x38, 8);
|
||||
} __packed_ws__(G_DonateToPhotonBlast_6x38, 8);
|
||||
|
||||
// 6x39: Photon blast ready (protected on V3/V4)
|
||||
|
||||
@@ -4414,9 +4373,7 @@ struct G_StopAtPosition_6x3E {
|
||||
le_uint16_t angle = 0;
|
||||
le_int16_t floor = 0;
|
||||
le_int16_t room = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_StopAtPosition_6x3E, 0x18);
|
||||
|
||||
// 6x3F: Set position (protected on V3/V4)
|
||||
@@ -4427,9 +4384,7 @@ struct G_SetPosition_6x3F {
|
||||
le_uint16_t angle = 0;
|
||||
le_int16_t floor = 0;
|
||||
le_int16_t room = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_SetPosition_6x3F, 0x18);
|
||||
|
||||
// 6x40: Walk (protected on V3/V4)
|
||||
@@ -4437,28 +4392,19 @@ struct G_SetPosition_6x3F {
|
||||
|
||||
struct G_WalkToPosition_6x40 {
|
||||
G_ClientIDHeader header;
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXZF pos;
|
||||
le_uint32_t action = 0;
|
||||
} __packed_ws__(G_WalkToPosition_6x40, 0x10);
|
||||
|
||||
// 6x41: Unknown
|
||||
// This subcommand is completely ignored by v2 and later.
|
||||
|
||||
struct G_Unknown_6x41 {
|
||||
G_ClientIDHeader header;
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
} __packed_ws__(G_Unknown_6x41, 0x0C);
|
||||
|
||||
// 6x41: Move to position (v1)
|
||||
// 6x42: Run (protected on V3/V4)
|
||||
// This subcommand is completely ignored by v2 and later.
|
||||
// If UDP mode is enabled, this command is sent via UDP.
|
||||
|
||||
struct G_RunToPosition_6x42 {
|
||||
struct G_MoveToPosition_6x41_6x42 {
|
||||
G_ClientIDHeader header;
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
} __packed_ws__(G_RunToPosition_6x42, 0x0C);
|
||||
VectorXZF pos;
|
||||
} __packed_ws__(G_MoveToPosition_6x41_6x42, 0x0C);
|
||||
|
||||
// 6x43: First attack (protected on V3/V4)
|
||||
// 6x44: Second attack (protected on V3/V4)
|
||||
@@ -4484,7 +4430,7 @@ struct TargetEntry {
|
||||
|
||||
struct G_AttackFinished_6x46 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t count = 0;
|
||||
le_uint32_t target_count = 0;
|
||||
// The client may send a shorter command if not all of these are used.
|
||||
parray<TargetEntry, 10> targets;
|
||||
} __packed_ws__(G_AttackFinished_6x46, 0x30);
|
||||
@@ -4544,8 +4490,7 @@ struct G_HitByEnemy_6x4B_6x4C {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t angle = 0;
|
||||
le_uint16_t current_hp = 0;
|
||||
le_float x_velocity = 0.0f;
|
||||
le_float z_velocity = 0.0f;
|
||||
VectorXZF velocity;
|
||||
} __packed_ws__(G_HitByEnemy_6x4B_6x4C, 0x10);
|
||||
|
||||
// 6x4D: Player died (protected on V3/V4)
|
||||
@@ -4603,7 +4548,9 @@ struct G_Unknown_6x53 {
|
||||
} __packed_ws__(G_Unknown_6x53, 4);
|
||||
|
||||
// 6x54: Unknown
|
||||
// This subcommand is completely ignored (at least, by PSO GC).
|
||||
// This subcommand is completely ignored by DCv2 and later. On DC NTE, 11/2000,
|
||||
// and DCv1, the handler has some logic in it and it calls a virtual function
|
||||
// on TObjPlayer, but that codepath ends up doing nothing.
|
||||
|
||||
struct G_Unknown_6x54 {
|
||||
G_ClientIDHeader header;
|
||||
@@ -4614,23 +4561,17 @@ struct G_Unknown_6x54 {
|
||||
struct G_IntraMapWarp_6x55 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_float x1 = 0.0f;
|
||||
le_float y1 = 0.0f;
|
||||
le_float z1 = 0.0f;
|
||||
le_float x2 = 0.0f;
|
||||
le_float y2 = 0.0f;
|
||||
le_float z2 = 0.0f;
|
||||
VectorXYZF pos1;
|
||||
VectorXYZF pos2;
|
||||
} __packed_ws__(G_IntraMapWarp_6x55, 0x20);
|
||||
|
||||
// 6x56: Unknown (supported; game) (protected on V3/V4)
|
||||
// 6x56: Set player position and angle (protected on V3/V4)
|
||||
|
||||
struct G_Unknown_6x56 {
|
||||
struct G_SetPlayerPositionAndAngle_6x56 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
} __packed_ws__(G_Unknown_6x56, 0x14);
|
||||
le_uint32_t angle_y = 0;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_SetPlayerPositionAndAngle_6x56, 0x14);
|
||||
|
||||
// 6x57: Unknown (supported; lobby & game) (protected on V3/V4)
|
||||
|
||||
@@ -4680,8 +4621,7 @@ struct G_DropStackedItem_DC_6x5D {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t unknown_a2 = 0; // Corresponds to FloorItem::unknown_a2
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXZF pos;
|
||||
ItemData item_data;
|
||||
} __packed_ws__(G_DropStackedItem_DC_6x5D, 0x24);
|
||||
|
||||
@@ -4700,10 +4640,9 @@ struct G_BuyShopItem_6x5E {
|
||||
|
||||
struct FloorItem {
|
||||
/* 00 */ uint8_t floor = 0;
|
||||
/* 01 */ uint8_t from_enemy = 0;
|
||||
/* 02 */ le_uint16_t entity_id = 0; // < 0x0B50 if from_enemy != 0; otherwise < 0x0BA0
|
||||
/* 04 */ le_float x = 0.0f;
|
||||
/* 08 */ le_float z = 0.0f;
|
||||
/* 01 */ uint8_t source_type = 0; // 1 = enemy, 2 = box
|
||||
/* 02 */ le_uint16_t entity_index = 0; // < 0x0B50 if source_type == 1; otherwise < 0x0BA0
|
||||
/* 04 */ VectorXZF pos;
|
||||
/* 0C */ le_uint16_t unknown_a2 = 0;
|
||||
// The drop number is scoped to the floor and increments by 1 each time an
|
||||
// item is dropped. The last item dropped in each floor has drop_number equal
|
||||
@@ -4728,9 +4667,8 @@ struct G_StandardDropItemRequest_DC_6x60 {
|
||||
/* 00 */ G_UnusedHeader header;
|
||||
/* 04 */ uint8_t floor = 0;
|
||||
/* 05 */ uint8_t rt_index = 0;
|
||||
/* 06 */ le_uint16_t entity_id = 0;
|
||||
/* 08 */ le_float x = 0.0f;
|
||||
/* 0C */ le_float z = 0.0f;
|
||||
/* 06 */ le_uint16_t entity_index = 0;
|
||||
/* 08 */ VectorXZF pos;
|
||||
/* 10 */ le_uint16_t section = 0;
|
||||
/* 12 */ le_uint16_t ignore_def = 0;
|
||||
/* 14 */
|
||||
@@ -4794,9 +4732,7 @@ struct G_SetTelepipeState_6x68 {
|
||||
le_uint16_t unknown_b1 = 0;
|
||||
uint8_t unknown_b2 = 0;
|
||||
uint8_t unknown_b3 = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
le_uint32_t unknown_a3 = 0;
|
||||
} __packed_ws__(G_SetTelepipeState_6x68, 0x1C);
|
||||
|
||||
@@ -4829,16 +4765,21 @@ struct G_NPCControl_6x69 {
|
||||
le_uint16_t param3; // Commands 0/3: npc_template_index; commands 1/2: unused
|
||||
} __packed_ws__(G_NPCControl_6x69, 0x0C);
|
||||
|
||||
// 6x6A: Use boss warp (not valid on Episode 3)
|
||||
// 6x6A: Set boss warp flags (not valid on Episode 3)
|
||||
|
||||
struct G_UseBossWarp_6x6A {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unused = 0;
|
||||
} __packed_ws__(G_UseBossWarp_6x6A, 8);
|
||||
struct G_SetBossWarpFlags_6x6A {
|
||||
G_EntityIDHeader header; // entity_id = boss warp object ID
|
||||
le_uint16_t flags;
|
||||
le_uint16_t unused;
|
||||
} __packed_ws__(G_SetBossWarpFlags_6x6A, 8);
|
||||
|
||||
// 6x6B: Sync enemy state (used while loading into game)
|
||||
|
||||
// Note that DC NTE doesn't send the decompressed size in the header here. This
|
||||
// is a bug that can cause the client to write more entries than it should when
|
||||
// it receives one of these commands. All later versions, including 11/2000,
|
||||
// use the second header structure here, which prevents this issue.
|
||||
|
||||
struct G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E {
|
||||
G_ExtendedHeaderT<G_UnusedHeader> header;
|
||||
le_uint32_t decompressed_size = 0;
|
||||
@@ -4852,25 +4793,11 @@ struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E {
|
||||
// BC0-compressed data follows here (see bc0_decompress)
|
||||
} __packed_ws__(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E, 0x10);
|
||||
|
||||
// Decompressed format is a list of these
|
||||
struct G_SyncEnemyState_6x6B_Entry_Decompressed {
|
||||
le_uint32_t flags = 0; // Same as flags in 6x0A
|
||||
le_uint16_t item_drop_id = 0;
|
||||
le_uint16_t total_damage = 0; // Same as in 6x0A
|
||||
uint8_t red_buff_type = 0;
|
||||
uint8_t red_buff_level = 0;
|
||||
uint8_t blue_buff_type = 0;
|
||||
uint8_t blue_buff_level = 0;
|
||||
} __packed_ws__(G_SyncEnemyState_6x6B_Entry_Decompressed, 0x0C);
|
||||
// Decompressed format is a list of SyncEnemyStateEntry (from Map.hh).
|
||||
|
||||
// 6x6C: Sync object state (used while loading into game)
|
||||
// Compressed format is the same as 6x6B.
|
||||
|
||||
// Decompressed format is a list of these
|
||||
struct G_SyncObjectState_6x6C_Entry_Decompressed {
|
||||
le_uint16_t flags = 0;
|
||||
le_uint16_t item_drop_id = 0;
|
||||
} __packed_ws__(G_SyncObjectState_6x6C_Entry_Decompressed, 4);
|
||||
// Decompressed format is a list of SyncObjectStateEntry (from Map.hh).
|
||||
|
||||
// 6x6D: Sync item state (used while loading into game)
|
||||
// Internal name: RcvItemCondition
|
||||
@@ -4967,9 +4894,7 @@ struct G_6x70_Sub_Telepipe {
|
||||
/* 00 */ le_uint16_t owner_client_id = 0xFFFF;
|
||||
/* 02 */ le_uint16_t floor = 0;
|
||||
/* 04 */ le_uint32_t unknown_a1 = 0;
|
||||
/* 08 */ le_float x = 0.0f;
|
||||
/* 0C */ le_float y = 0.0f;
|
||||
/* 10 */ le_float z = 0.0f;
|
||||
/* 08 */ VectorXYZF pos;
|
||||
/* 14 */ le_uint32_t unknown_a3 = 0;
|
||||
/* 18 */ le_uint32_t unknown_a4 = 0x0000FFFF;
|
||||
/* 1C */
|
||||
@@ -4997,9 +4922,7 @@ struct G_6x70_Base_DCNTE {
|
||||
/* 0000 */ le_uint16_t client_id = 0;
|
||||
/* 0002 */ le_uint16_t room_id = 0;
|
||||
/* 0004 */ le_uint32_t flags1 = 0;
|
||||
/* 0008 */ le_float x = 0.0f;
|
||||
/* 000C */ le_float y = 0.0f;
|
||||
/* 0010 */ le_float z = 0.0f;
|
||||
/* 0008 */ VectorXYZF pos;
|
||||
/* 0014 */ le_uint32_t angle_x = 0;
|
||||
/* 0018 */ le_uint32_t angle_y = 0;
|
||||
/* 001C */ le_uint32_t angle_z = 0;
|
||||
@@ -5174,7 +5097,7 @@ struct G_UpdateQuestFlag_V3_BB_6x75 : G_UpdateQuestFlag_DC_PC_6x75 {
|
||||
// bitwise OR operation instead of a simple assignment.
|
||||
|
||||
struct G_SetEntitySetFlags_6x76 {
|
||||
G_EnemyIDHeader header; // 1000-3FFF = enemy, 4000-FFFF = object
|
||||
G_EntityIDHeader header; // 1000-3FFF = enemy, 4000-FFFF = object
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t flags = 0;
|
||||
} __packed_ws__(G_SetEntitySetFlags_6x76, 8);
|
||||
@@ -5289,8 +5212,10 @@ check_struct_size(G_BattleScoresBE_6x7F, 0x24);
|
||||
|
||||
struct G_TriggerTrap_6x80 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
// Traps set by players are numbered according to their type and who set
|
||||
// them. The trap number is computed as (client_id * 80) + (trap_type * 20).
|
||||
le_uint16_t trap_number = 0;
|
||||
le_uint16_t what = 0; // Must be 0, 1, or 2
|
||||
} __packed_ws__(G_TriggerTrap_6x80, 8);
|
||||
|
||||
// 6x81: Disable drop weapon on death (protected on V3/V4)
|
||||
@@ -5334,10 +5259,7 @@ struct G_Unknown_6x85 {
|
||||
|
||||
// 6x86: Hit destructible object (not valid on Episode 3)
|
||||
|
||||
struct G_HitDestructibleObject_6x86 {
|
||||
G_ObjectIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_uint32_t unknown_a2 = 0;
|
||||
struct G_HitDestructibleObject_6x86 : G_UpdateObjectState_6x0B {
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint16_t unknown_a4 = 0;
|
||||
} __packed_ws__(G_HitDestructibleObject_6x86, 0x10);
|
||||
@@ -5355,13 +5277,14 @@ struct G_RestoreShrunkenPlayer_6x88 {
|
||||
G_ClientIDHeader header;
|
||||
} __packed_ws__(G_RestoreShrunkenPlayer_6x88, 4);
|
||||
|
||||
// 6x89: Player killed by monster (protected on V3/V4)
|
||||
// 6x89: Set killer entity ID (protected on V3/V4)
|
||||
// This is used to specify which enemy killed a player.
|
||||
|
||||
struct G_PlayerKilledByMonster_6x89 {
|
||||
struct G_SetKillerEntityID_6x89 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t killer_entity_id = 0;
|
||||
le_uint16_t unused = 0;
|
||||
} __packed_ws__(G_PlayerKilledByMonster_6x89, 8);
|
||||
} __packed_ws__(G_SetKillerEntityID_6x89, 8);
|
||||
|
||||
// 6x8A: Show Challenge time records window (not valid on Episode 3)
|
||||
// The leader sends this command to tell other clients to show, hide, or update
|
||||
@@ -5400,34 +5323,31 @@ struct G_SetTechniqueLevelOverride_6x8D {
|
||||
// 6x8E: Unknown (not valid on Episode 3)
|
||||
// This command has a handler, but it does nothing.
|
||||
|
||||
// 6x8F: Unknown (not valid on Episode 3)
|
||||
// 6x8F: Add battle damage scores (not valid on Episode 3)
|
||||
|
||||
struct G_Unknown_6x8F {
|
||||
struct G_AddBattleDamageScores_6x8F {
|
||||
G_ClientIDHeader header; // client_id is the attacking player
|
||||
le_uint16_t target_entity_id = 0;
|
||||
le_uint16_t amount = 0;
|
||||
} __packed_ws__(G_AddBattleDamageScores_6x8F, 8);
|
||||
|
||||
// 6x90: Set player battle team (not valid on Episode 3) (protected on V3/V4)
|
||||
|
||||
struct G_SetPlayerBattleTeam_6x90 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t client_id2 = 0;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
} __packed_ws__(G_Unknown_6x8F, 8);
|
||||
|
||||
// 6x90: Unknown (not valid on Episode 3) (protected on V3/V4)
|
||||
|
||||
struct G_Unknown_6x90 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
} __packed_ws__(G_Unknown_6x90, 8);
|
||||
le_uint32_t team_number = 0; // 0 or 1
|
||||
} __packed_ws__(G_SetPlayerBattleTeam_6x90, 8);
|
||||
|
||||
// 6x91: Unknown (supported; game only)
|
||||
// TODO: Deals with TOAttackableCol objects. Figoure out exactly what it does.
|
||||
// TODO: Deals with TOAttackableCol objects. Figure out exactly what it does.
|
||||
|
||||
struct G_Unknown_6x91 {
|
||||
G_ObjectIDHeader header;
|
||||
le_uint32_t object_flags = 0;
|
||||
le_uint32_t object_index = 0; // < 0xBA0
|
||||
struct G_UpdateAttackableColState_6x91 : G_UpdateObjectState_6x0B {
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint16_t unknown_a4 = 0;
|
||||
le_uint16_t switch_flag_num = 0;
|
||||
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared
|
||||
uint8_t switch_flag_floor = 0;
|
||||
} __packed_ws__(G_Unknown_6x91, 0x14);
|
||||
uint8_t floor = 0;
|
||||
} __packed_ws__(G_UpdateAttackableColState_6x91, 0x14);
|
||||
|
||||
// 6x92: Unknown (not valid on Episode 3)
|
||||
// This does something with the TObjOnlineEndingHexMove object. TODO: Figure
|
||||
@@ -5522,7 +5442,7 @@ struct G_LevelUpAllTechniques_6x9B {
|
||||
// TODO: Figure out what this does.
|
||||
|
||||
struct G_Unknown_6x9C {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
} __packed_ws__(G_Unknown_6x9C, 8);
|
||||
|
||||
@@ -5547,7 +5467,7 @@ struct G_PlayerCameraShutterSound_6x9E {
|
||||
// 6x9F: Gal Gryphon boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_GalGryphonBossActions_6x9F {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_float unknown_a2 = 0.0f;
|
||||
le_float unknown_a3 = 0.0f;
|
||||
@@ -5556,10 +5476,8 @@ struct G_GalGryphonBossActions_6x9F {
|
||||
// 6xA0: Gal Gryphon boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_GalGryphonBossActions_6xA0 {
|
||||
G_EnemyIDHeader header;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
G_EntityIDHeader header;
|
||||
VectorXYZF pos;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
@@ -5586,7 +5504,7 @@ struct G_SpecializableItemDropRequest_6xA2 : G_StandardDropItemRequest_PC_V3_BB_
|
||||
// 6xA3: Olga Flow boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_OlgaFlowBossActions_6xA3 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
uint8_t unknown_a1 = 0;
|
||||
uint8_t unknown_a2 = 0;
|
||||
parray<uint8_t, 2> unknown_a3;
|
||||
@@ -5595,7 +5513,7 @@ struct G_OlgaFlowBossActions_6xA3 {
|
||||
// 6xA4: Olga Flow phase 1 boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_OlgaFlowPhase1BossActions_6xA4 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
uint8_t what = 0;
|
||||
parray<uint8_t, 3> unknown_a3;
|
||||
} __packed_ws__(G_OlgaFlowPhase1BossActions_6xA4, 8);
|
||||
@@ -5603,7 +5521,7 @@ struct G_OlgaFlowPhase1BossActions_6xA4 {
|
||||
// 6xA5: Olga Flow phase 2 boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_OlgaFlowPhase2BossActions_6xA5 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
uint8_t what = 0;
|
||||
parray<uint8_t, 3> unknown_a3;
|
||||
} __packed_ws__(G_OlgaFlowPhase2BossActions_6xA5, 8);
|
||||
@@ -5637,7 +5555,7 @@ struct G_ModifyTradeProposal_6xA6 {
|
||||
|
||||
template <bool BE>
|
||||
struct G_GolDragonBossActionsT_6xA8 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint32_t unknown_a4 = 0;
|
||||
@@ -5654,7 +5572,7 @@ check_struct_size(G_GolDragonBossActions_GC_6xA8, 0x18);
|
||||
// 6xA9: Barba Ray boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_BarbaRayBossActions_6xA9 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
} __packed_ws__(G_BarbaRayBossActions_6xA9, 8);
|
||||
@@ -5662,7 +5580,7 @@ struct G_BarbaRayBossActions_6xA9 {
|
||||
// 6xAA: Barba Ray boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_BarbaRayBossActions_6xAA {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint32_t unknown_a3 = 0;
|
||||
@@ -5673,7 +5591,7 @@ struct G_BarbaRayBossActions_6xAA {
|
||||
// It's not known what it does on GC NTE.
|
||||
|
||||
struct G_Unknown_GCNTE_6xAB {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
} __packed_ws__(G_Unknown_GCNTE_6xAB, 8);
|
||||
@@ -5685,12 +5603,11 @@ struct G_CreateLobbyChair_6xAB {
|
||||
} __packed_ws__(G_CreateLobbyChair_6xAB, 8);
|
||||
|
||||
// 6xAC: Unknown (not valid on pre-V3) (protected on V3/V4)
|
||||
// This command's appears to be different on GC NTE than on any other version.
|
||||
// It also seems that no version (other than perhaps GC NTE) ever sends this
|
||||
// command.
|
||||
// This command appears to be different on GC NTE than on any other version. It
|
||||
// also seems that no version (except perhaps GC NTE) ever sends this command.
|
||||
|
||||
struct G_Unknown_GCNTE_6xAC {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint32_t unknown_a3 = 0;
|
||||
@@ -6079,8 +5996,7 @@ struct G_SplitStackedItem_BB_6xC3 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t unused2 = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXZF pos;
|
||||
le_uint32_t item_id = 0;
|
||||
le_uint32_t amount = 0;
|
||||
} __packed_ws__(G_SplitStackedItem_BB_6xC3, 0x18);
|
||||
@@ -6098,7 +6014,7 @@ struct G_MedicalCenterUsed_BB_6xC5 {
|
||||
G_ClientIDHeader header;
|
||||
} __packed_ws__(G_MedicalCenterUsed_BB_6xC5, 4);
|
||||
|
||||
// 6xC6: Steal experience (BB)
|
||||
// 6xC6: Steal experience (BB; handled by the server)
|
||||
|
||||
struct G_StealEXP_BB_6xC6 {
|
||||
G_ClientIDHeader header;
|
||||
@@ -6118,7 +6034,7 @@ struct G_ChargeAttack_BB_6xC7 {
|
||||
// 6xC8: Enemy EXP request (BB; handled by the server)
|
||||
|
||||
struct G_EnemyEXPRequest_BB_6xC8 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t enemy_index = 0;
|
||||
le_uint16_t requesting_client_id = 0;
|
||||
uint8_t is_killer = 0;
|
||||
@@ -6182,8 +6098,7 @@ struct G_ChallengeModeGraveRecoveryItemRequest_BB_6xD1 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_float x = 0;
|
||||
le_float z = 0;
|
||||
VectorXZF pos;
|
||||
le_uint32_t item_type = 0; // Should be < 6
|
||||
} __packed_ws__(G_ChallengeModeGraveRecoveryItemRequest_BB_6xD1, 0x14);
|
||||
|
||||
@@ -6281,6 +6196,9 @@ struct G_UpgradeWeaponAttribute_BB_6xDA {
|
||||
|
||||
struct G_ExchangeItemInQuest_BB_6xDB {
|
||||
G_ClientIDHeader header;
|
||||
// If this is 0, the command is identical to 6x29. If this is 1, a function
|
||||
// similar to find_item_by_id is called instead of find_item_by_id, but I
|
||||
// don't yet know what exactly the logic differences are.
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_uint32_t item_id = 0;
|
||||
le_uint32_t amount = 0;
|
||||
@@ -6333,8 +6251,7 @@ struct G_RequestItemDropFromQuest_BB_6xE0 {
|
||||
uint8_t type = 0; // valueA
|
||||
uint8_t unknown_a3 = 0;
|
||||
uint8_t unused = 0;
|
||||
le_float x = 0.0f; // valueB
|
||||
le_float z = 0.0f; // valueC
|
||||
VectorXZF pos; // x = valueB, z = valueC
|
||||
} __packed_ws__(G_RequestItemDropFromQuest_BB_6xE0, 0x10);
|
||||
|
||||
// 6xE1: Exchange Photon Tickets (BB; handled by server)
|
||||
@@ -6359,8 +6276,7 @@ struct G_GetMesetaSlotPrize_BB_6xE2 {
|
||||
uint8_t floor;
|
||||
uint8_t unknown_a2;
|
||||
uint8_t unused;
|
||||
le_float x; // TODO: Verify this guess
|
||||
le_float z; // TODO: Verify this guess
|
||||
VectorXZF pos; // TODO: Verify this guess
|
||||
} __packed_ws__(G_GetMesetaSlotPrize_BB_6xE2, 0x10);
|
||||
|
||||
// 6xE3: Set Meseta slot prize result (BB)
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Vector.hh>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
struct VectorXZF {
|
||||
le_float x = 0.0;
|
||||
le_float z = 0.0;
|
||||
|
||||
inline VectorXZF operator-() const {
|
||||
return VectorXZF{-this->x, -this->z};
|
||||
}
|
||||
|
||||
inline VectorXZF operator+(const VectorXZF& other) const {
|
||||
return VectorXZF{this->x + other.x, this->z + other.z};
|
||||
}
|
||||
inline VectorXZF operator-(const VectorXZF& other) const {
|
||||
return VectorXZF{this->x - other.x, this->z - other.z};
|
||||
}
|
||||
|
||||
inline bool operator==(const VectorXZF& other) const {
|
||||
return ((this->x == other.x) && (this->z == other.z));
|
||||
}
|
||||
inline bool operator!=(const VectorXZF& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
inline double norm() const {
|
||||
return sqrt(this->norm2());
|
||||
}
|
||||
inline double norm2() const {
|
||||
return ((this->x * this->x) + (this->z * this->z));
|
||||
}
|
||||
|
||||
inline std::string str() const {
|
||||
return phosg::string_printf("[VectorXZF x=%g z=%g]", this->x.load(), this->z.load());
|
||||
}
|
||||
} __packed_ws__(VectorXZF, 0x08);
|
||||
|
||||
struct VectorXYZF {
|
||||
le_float x = 0.0;
|
||||
le_float y = 0.0;
|
||||
le_float z = 0.0;
|
||||
|
||||
inline operator VectorXZF() const {
|
||||
return VectorXZF{this->x, this->z};
|
||||
}
|
||||
|
||||
inline VectorXYZF operator-() const {
|
||||
return VectorXYZF{-this->x, -this->y, -this->z};
|
||||
}
|
||||
|
||||
inline VectorXYZF operator+(const VectorXYZF& other) const {
|
||||
return VectorXYZF{this->x + other.x, this->y + other.y, this->z + other.z};
|
||||
}
|
||||
inline VectorXYZF operator-(const VectorXYZF& other) const {
|
||||
return VectorXYZF{this->x - other.x, this->y - other.y, this->z - other.z};
|
||||
}
|
||||
|
||||
inline bool operator==(const VectorXYZF& other) const {
|
||||
return ((this->x == other.x) && (this->y == other.y) && (this->z == other.z));
|
||||
}
|
||||
inline bool operator!=(const VectorXYZF& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
inline double norm() const {
|
||||
return sqrt(this->norm2());
|
||||
}
|
||||
inline double norm2() const {
|
||||
return ((this->x * this->x) + (this->y * this->y) + (this->z * this->z));
|
||||
}
|
||||
|
||||
inline std::string str() const {
|
||||
return phosg::string_printf("[VectorXYZF x=%g y=%g z=%g]", this->x.load(), this->y.load(), this->z.load());
|
||||
}
|
||||
} __packed_ws__(VectorXYZF, 0x0C);
|
||||
|
||||
struct VectorXYZTF {
|
||||
le_float x = 0.0;
|
||||
le_float y = 0.0;
|
||||
le_float z = 0.0;
|
||||
le_float t = 0.0;
|
||||
} __packed_ws__(VectorXYZTF, 0x10);
|
||||
|
||||
struct VectorXYZI {
|
||||
le_uint32_t x = 0;
|
||||
le_uint32_t y = 0;
|
||||
le_uint32_t z = 0;
|
||||
} __packed_ws__(VectorXYZI, 0x0C);
|
||||
|
||||
template <bool BE>
|
||||
struct ArrayRefT {
|
||||
static constexpr bool IsBE = BE;
|
||||
/* 00 */ U32T<BE> count;
|
||||
/* 04 */ U32T<BE> offset;
|
||||
/* 08 */
|
||||
} __packed__;
|
||||
using ArrayRef = ArrayRefT<false>;
|
||||
using ArrayRefBE = ArrayRefT<true>;
|
||||
check_struct_size(ArrayRef, 8);
|
||||
check_struct_size(ArrayRefBE, 8);
|
||||
|
||||
template <bool BE>
|
||||
struct RELFileFooterT {
|
||||
static constexpr bool IsBE = BE;
|
||||
// Relocations is a list of words (le_uint16_t on DC/PC/XB/BB, be_uint16_t on
|
||||
// GC) containing the number of doublewords (uint32_t) to skip for each
|
||||
// relocation. The relocation pointer starts immediately after the
|
||||
// checksum_size field in the header, and advances by the value of one
|
||||
// relocation word (times 4) before each relocation. At each relocated
|
||||
// doubleword, the address of the first byte of the code (after checksum_size)
|
||||
// is added to the existing value.
|
||||
// For example, if the code segment contains the following data (where R
|
||||
// specifies doublewords to relocate):
|
||||
// RR RR RR RR ?? ?? ?? ?? ?? ?? ?? ?? RR RR RR RR
|
||||
// RR RR RR RR ?? ?? ?? ?? RR RR RR RR
|
||||
// then the relocation words should be 0000, 0003, 0001, and 0002.
|
||||
// If there is a small number of relocations, they may be placed in the unused
|
||||
// fields of this structure to save space and/or confuse reverse engineers.
|
||||
// The game never accesses the last 12 bytes of this structure unless
|
||||
// relocations_offset points there, so those 12 bytes may also be omitted
|
||||
// entirely in situations (e.g. in the B2 command, without changing code_size,
|
||||
// so code_size would technically extend beyond the end of the B2 command).
|
||||
U32T<BE> relocations_offset = 0;
|
||||
U32T<BE> num_relocations = 0;
|
||||
parray<U32T<BE>, 2> unused1;
|
||||
U32T<BE> root_offset = 0;
|
||||
parray<U32T<BE>, 3> unused2;
|
||||
} __attribute__((packed));
|
||||
using RELFileFooter = RELFileFooterT<false>;
|
||||
using RELFileFooterBE = RELFileFooterT<true>;
|
||||
check_struct_size(RELFileFooter, 0x20);
|
||||
check_struct_size(RELFileFooterBE, 0x20);
|
||||
@@ -1121,3 +1121,51 @@ bool enemy_type_is_rare(EnemyType type) {
|
||||
(type == EnemyType::DORPHON_ECLAIR) ||
|
||||
(type == EnemyType::KONDRIEU));
|
||||
}
|
||||
|
||||
EnemyType rare_type_for_enemy_type(EnemyType base_type, Episode episode, uint8_t event, uint8_t floor) {
|
||||
switch (base_type) {
|
||||
case EnemyType::HILDEBEAR:
|
||||
return EnemyType::HILDEBLUE;
|
||||
case EnemyType::RAG_RAPPY:
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
return EnemyType::AL_RAPPY;
|
||||
case Episode::EP2:
|
||||
switch (event) {
|
||||
case 0x01: // rappy_type 1
|
||||
return EnemyType::SAINT_RAPPY;
|
||||
case 0x04: // rappy_type 2
|
||||
return EnemyType::EGG_RAPPY;
|
||||
case 0x05: // rappy_type 3
|
||||
return EnemyType::HALLO_RAPPY;
|
||||
default:
|
||||
return EnemyType::LOVE_RAPPY;
|
||||
}
|
||||
case Episode::EP4:
|
||||
return (floor > 0x05) ? EnemyType::DEL_RAPPY_ALT : EnemyType::DEL_RAPPY;
|
||||
default:
|
||||
throw logic_error("invalid episode");
|
||||
}
|
||||
case EnemyType::POISON_LILY:
|
||||
return EnemyType::NAR_LILY;
|
||||
case EnemyType::POFUILLY_SLIME:
|
||||
return EnemyType::POUILLY_SLIME;
|
||||
case EnemyType::SAND_RAPPY:
|
||||
return EnemyType::DEL_RAPPY;
|
||||
case EnemyType::SAND_RAPPY_ALT:
|
||||
return EnemyType::DEL_RAPPY_ALT;
|
||||
case EnemyType::MERISSA_A:
|
||||
return EnemyType::MERISSA_AA;
|
||||
case EnemyType::ZU:
|
||||
return EnemyType::PAZUZU;
|
||||
case EnemyType::ZU_ALT:
|
||||
return EnemyType::PAZUZU_ALT;
|
||||
case EnemyType::DORPHON:
|
||||
return EnemyType::DORPHON_ECLAIR;
|
||||
case EnemyType::SAINT_MILLION:
|
||||
case EnemyType::SHAMBERTIN:
|
||||
return EnemyType::KONDRIEU;
|
||||
default:
|
||||
return base_type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,3 +148,4 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type)
|
||||
uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type);
|
||||
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
|
||||
bool enemy_type_is_rare(EnemyType type);
|
||||
EnemyType rare_type_for_enemy_type(EnemyType base_type, Episode episode, uint8_t event, uint8_t floor);
|
||||
|
||||
+22
-15
@@ -8,6 +8,7 @@
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "../CommonFileFormats.hh"
|
||||
#include "../Compression.hh"
|
||||
#include "../Loggers.hh"
|
||||
#include "../PSOEncryption.hh"
|
||||
@@ -2452,28 +2453,34 @@ CardIndex::CardIndex(
|
||||
this->compressed_card_definitions = phosg::load_file(filename);
|
||||
decompressed_data = prs_decompress(this->compressed_card_definitions);
|
||||
}
|
||||
|
||||
// The client can't handle files larger than this
|
||||
if (decompressed_data.size() > 0x36EC0) {
|
||||
throw runtime_error("decompressed card list data is too long");
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"decompressed card update file size %zX is not aligned with card definition size %zX (%zX extra bytes)",
|
||||
decompressed_data.size(), sizeof(CardDefinition), decompressed_data.size() % sizeof(CardDefinition)));
|
||||
// The card definitions file is a standard REL file; the root offset points
|
||||
// to an ArrayRef which specifies an array of CardDefinition structs
|
||||
phosg::StringReader r(decompressed_data);
|
||||
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
|
||||
uint32_t offset = r.pget_u32b(footer.root_offset);
|
||||
uint32_t count = r.pget_u32b(footer.root_offset + 4);
|
||||
if (offset > decompressed_data.size() ||
|
||||
((offset + count * sizeof(CardDefinition)) > decompressed_data.size())) {
|
||||
throw runtime_error("definitions array reference out of bounds");
|
||||
}
|
||||
auto* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data());
|
||||
size_t max_cards = decompressed_data.size() / sizeof(CardDefinition);
|
||||
for (size_t x = 0; x < max_cards; x++) {
|
||||
CardDefinition* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data() + offset);
|
||||
for (size_t x = 0; x < count; x++) {
|
||||
auto& def = defs[x];
|
||||
|
||||
// The last card entry has the build date and some other metadata (and
|
||||
// isn't a real card, obviously), so skip it. The game detects this by
|
||||
// checking for a negative value in type, which we also do here.
|
||||
if (static_cast<int8_t>(defs[x].type) < 0) {
|
||||
if (static_cast<int8_t>(def.type) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entry = make_shared<CardEntry>(CardEntry{defs[x], "", "", "", {}});
|
||||
auto entry = make_shared<CardEntry>(CardEntry{def, "", "", "", {}});
|
||||
if (!this->card_definitions.emplace(entry->def.card_id, entry).second) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"duplicate card id: %08" PRIX32, entry->def.card_id.load()));
|
||||
@@ -2493,17 +2500,17 @@ CardIndex::CardIndex(
|
||||
|
||||
if (!text_filename.empty() || !decompressed_text_filename.empty()) {
|
||||
try {
|
||||
entry->text = std::move(card_text.at(defs[x].card_id));
|
||||
entry->text = std::move(card_text.at(def.card_id));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
entry->debug_tags = std::move(card_tags.at(defs[x].card_id));
|
||||
entry->debug_tags = std::move(card_tags.at(def.card_id));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
if (!dice_text_filename.empty() || !decompressed_dice_text_filename.empty()) {
|
||||
try {
|
||||
auto& dice_text_it = card_dice_text.at(defs[x].card_id);
|
||||
auto& dice_text_it = card_dice_text.at(def.card_id);
|
||||
entry->dice_caption = std::move(dice_text_it.first);
|
||||
entry->dice_text = std::move(dice_text_it.second);
|
||||
} catch (const out_of_range&) {
|
||||
@@ -2523,7 +2530,7 @@ CardIndex::CardIndex(
|
||||
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 (0x%zX bytes); removing text", this->compressed_card_definitions.size());
|
||||
for (size_t x = 0; x < max_cards; x++) {
|
||||
for (size_t x = 0; x < count; x++) {
|
||||
if (static_cast<int8_t>(defs[x].type) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../CommonFileFormats.hh"
|
||||
#include "../PlayerSubordinates.hh"
|
||||
#include "../Text.hh"
|
||||
#include "../TextIndex.hh"
|
||||
@@ -814,18 +815,12 @@ struct CardDefinition {
|
||||
} __packed_ws__(CardDefinition, 0x128);
|
||||
|
||||
struct CardDefinitionsFooter {
|
||||
// Technically the card definitions file is a REL file, so the last 0x20 bytes
|
||||
// here should be a separate structure.
|
||||
/* 00 */ be_uint32_t num_cards1;
|
||||
/* 04 */ be_uint32_t cards_offset; // == 0
|
||||
/* 08 */ be_uint32_t num_cards2;
|
||||
/* 0C */ parray<be_uint32_t, 3> unknown_a2;
|
||||
/* 18 */ parray<be_uint16_t, 0x10> relocations;
|
||||
/* 38 */ be_uint32_t relocations_offset;
|
||||
/* 3C */ be_uint32_t num_relocations;
|
||||
/* 40 */ parray<be_uint32_t, 2> unused1;
|
||||
/* 48 */ be_uint32_t footer_offset;
|
||||
/* 4C */ parray<be_uint32_t, 3> unused2;
|
||||
/* 38 */ RELFileFooterBE rel_footer;
|
||||
/* 58 */
|
||||
} __packed_ws__(CardDefinitionsFooter, 0x58);
|
||||
|
||||
|
||||
@@ -2335,7 +2335,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
if (l) {
|
||||
// Note: Sega's implementation doesn't set EX results values here; they
|
||||
// did it at game join time instead. We do it here for code simplicity.
|
||||
if ((l->base_version != Version::GC_EP3_NTE) && l->ep3_ex_result_values) {
|
||||
if (!this->options.is_nte() && l->ep3_ex_result_values) {
|
||||
this->send(*l->ep3_ex_result_values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#endif
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
@@ -47,16 +48,18 @@ const char* name_for_architecture(CompiledFunctionCode::Architecture arch) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FooterT>
|
||||
template <bool BE>
|
||||
string CompiledFunctionCode::generate_client_command_t(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
using FooterT = RELFileFooterT<BE>;
|
||||
|
||||
FooterT footer;
|
||||
footer.num_relocations = this->relocation_deltas.size();
|
||||
footer.unused1.clear(0);
|
||||
footer.entrypoint_addr_offset = this->entrypoint_offset_offset;
|
||||
footer.root_offset = this->entrypoint_offset_offset;
|
||||
footer.unused2.clear(0);
|
||||
|
||||
phosg::StringWriter w;
|
||||
@@ -108,10 +111,10 @@ string CompiledFunctionCode::generate_client_command(
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
if (this->arch == Architecture::POWERPC) {
|
||||
return this->generate_client_command_t<S_ExecuteCode_Footer_GC_B2>(
|
||||
return this->generate_client_command_t<true>(
|
||||
label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else if ((this->arch == Architecture::X86) || (this->arch == Architecture::SH4)) {
|
||||
return this->generate_client_command_t<S_ExecuteCode_Footer_DC_PC_XB_BB_B2>(
|
||||
return this->generate_client_command_t<false>(
|
||||
label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else {
|
||||
throw logic_error("invalid architecture");
|
||||
|
||||
@@ -40,7 +40,7 @@ struct CompiledFunctionCode {
|
||||
|
||||
bool is_big_endian() const;
|
||||
|
||||
template <typename FooterT>
|
||||
template <bool BE>
|
||||
std::string generate_client_command_t(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data = nullptr,
|
||||
|
||||
+10
-14
@@ -550,8 +550,8 @@ phosg::JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c,
|
||||
{"SubVersion", c->sub_version},
|
||||
{"Config", HTTPServer::generate_client_config_json_st(c->config)},
|
||||
{"Language", name_for_language_code(c->language())},
|
||||
{"LocationX", c->x},
|
||||
{"LocationZ", c->z},
|
||||
{"LocationX", c->pos.x.load()},
|
||||
{"LocationZ", c->pos.z.load()},
|
||||
{"LocationFloor", c->floor},
|
||||
{"CanChat", c->can_chat},
|
||||
});
|
||||
@@ -735,8 +735,8 @@ phosg::JSON HTTPServer::generate_proxy_client_json_st(shared_ptr<const ProxyServ
|
||||
{"Language", name_for_language_code(ses->language())},
|
||||
{"LobbyClientID", ses->lobby_client_id},
|
||||
{"LeaderClientID", ses->leader_client_id},
|
||||
{"LocationX", ses->x},
|
||||
{"LocationZ", ses->z},
|
||||
{"LocationX", ses->pos.x.load()},
|
||||
{"LocationZ", ses->pos.z.load()},
|
||||
{"LocationFloor", ses->floor},
|
||||
{"IsInGame", ses->is_in_game},
|
||||
{"IsInQuest", ses->is_in_quest},
|
||||
@@ -787,7 +787,6 @@ phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared
|
||||
ret.emplace("CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED));
|
||||
ret.emplace("MinLevel", l->min_level + 1);
|
||||
ret.emplace("MaxLevel", l->max_level + 1);
|
||||
ret.emplace("BaseVersion", l->base_version);
|
||||
ret.emplace("Episode", name_for_episode(l->episode));
|
||||
ret.emplace("HasPassword", !l->password.empty());
|
||||
ret.emplace("Name", l->name);
|
||||
@@ -796,11 +795,7 @@ phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared
|
||||
ret.emplace("QuestSelectionInProgress", l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS));
|
||||
ret.emplace("QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS));
|
||||
ret.emplace("JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS));
|
||||
auto variations_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < l->variations.size(); z++) {
|
||||
variations_json.emplace_back(l->variations[z].load());
|
||||
}
|
||||
ret.emplace("Variations", std::move(variations_json));
|
||||
ret.emplace("Variations", l->variations.json());
|
||||
ret.emplace("SectionID", name_for_section_id(l->effective_section_id()));
|
||||
ret.emplace("Mode", name_for_mode(l->mode));
|
||||
ret.emplace("Difficulty", name_for_difficulty(l->difficulty));
|
||||
@@ -845,8 +840,8 @@ phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared
|
||||
const auto& item = it.second;
|
||||
auto item_dict = phosg::JSON::dict({
|
||||
{"LocationFloor", floor},
|
||||
{"LocationX", item->x},
|
||||
{"LocationZ", item->z},
|
||||
{"LocationX", item->pos.x.load()},
|
||||
{"LocationZ", item->pos.z.load()},
|
||||
{"DropNumber", item->drop_number},
|
||||
{"Flags", item->flags},
|
||||
{"Data", item->data.hex()},
|
||||
@@ -1016,7 +1011,9 @@ phosg::JSON HTTPServer::generate_lobbies_json() const {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
phosg::JSON res = phosg::JSON::list();
|
||||
for (const auto& it : this->state->id_to_lobby) {
|
||||
res.emplace_back(this->generate_lobby_json_st(it.second, this->state->item_name_index_opt(it.second->base_version)));
|
||||
auto leader = it.second->clients[it.second->leader_id];
|
||||
Version v = leader ? leader->version() : Version::BB_V4;
|
||||
res.emplace_back(this->generate_lobby_json_st(it.second, this->state->item_name_index_opt(v)));
|
||||
}
|
||||
return res;
|
||||
});
|
||||
@@ -1061,7 +1058,6 @@ phosg::JSON HTTPServer::generate_summary_json() const {
|
||||
auto game_json = phosg::JSON::dict({
|
||||
{"ID", l->lobby_id},
|
||||
{"Name", l->name},
|
||||
{"BaseVersion", phosg::name_for_enum(l->base_version)},
|
||||
{"Players", l->count_clients()},
|
||||
{"CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED)},
|
||||
{"Episode", name_for_episode(l->episode)},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "ItemParameterTable.hh"
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
ItemParameterTable::ItemParameterTable(shared_ptr<const string> data, Version version)
|
||||
@@ -416,15 +418,12 @@ ItemParameterTable::ToolV4 ItemParameterTable::ToolV3T<BE>::to_v4() const {
|
||||
|
||||
template <bool BE>
|
||||
size_t indirect_lookup_2d_count(const phosg::StringReader& r, size_t root_offset, size_t co_index) {
|
||||
using ArrayRefT = typename std::conditional_t<BE, ItemParameterTable::ArrayRefBE, ItemParameterTable::ArrayRef>;
|
||||
return r.pget<ArrayRefT>(root_offset + sizeof(ArrayRefT) * co_index).count;
|
||||
return r.pget<ArrayRefT<BE>>(root_offset + sizeof(ArrayRefT<BE>) * co_index).count;
|
||||
}
|
||||
|
||||
template <typename T, bool BE>
|
||||
const T& indirect_lookup_2d(const phosg::StringReader& r, size_t root_offset, size_t co_index, size_t item_index) {
|
||||
using ArrayRefT = typename std::conditional_t<BE, ItemParameterTable::ArrayRefBE, ItemParameterTable::ArrayRef>;
|
||||
|
||||
const auto& co = r.pget<ArrayRefT>(root_offset + sizeof(ArrayRefT) * co_index);
|
||||
const auto& co = r.pget<ArrayRefT<BE>>(root_offset + sizeof(ArrayRefT<BE>) * co_index);
|
||||
if (item_index >= co.count) {
|
||||
throw out_of_range("item ID out of range");
|
||||
}
|
||||
|
||||
@@ -19,18 +19,6 @@ public:
|
||||
// functions instead of manually branching on various offset table pointers
|
||||
// being null or not in each public function. Rewrite this and make it better.
|
||||
|
||||
template <bool BE>
|
||||
struct ArrayRefT {
|
||||
/* 00 */ U32T<BE> count;
|
||||
/* 04 */ U32T<BE> offset;
|
||||
/* 08 */
|
||||
} __packed__;
|
||||
|
||||
using ArrayRef = ArrayRefT<false>;
|
||||
using ArrayRefBE = ArrayRefT<true>;
|
||||
check_struct_size(ArrayRef, 8);
|
||||
check_struct_size(ArrayRefBE, 8);
|
||||
|
||||
template <bool BE>
|
||||
struct ItemBaseV2T {
|
||||
// id specifies several things; notably, it doubles as the index of the
|
||||
|
||||
+7
-2
@@ -189,9 +189,14 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
||||
item.data.data1.clear_after(3);
|
||||
should_delete_item = false;
|
||||
|
||||
// TODO: It seems that on non-BB, clients don't synchronize this at all
|
||||
// so they could end up thinking the unwrapped item is something
|
||||
// completely different. How does this actually work on console PSO?
|
||||
auto l = c->require_lobby();
|
||||
if (l->base_version == Version::BB_V4) {
|
||||
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item.data);
|
||||
for (const auto& lc : l->clients) {
|
||||
if (lc && (lc->version() == Version::BB_V4)) {
|
||||
send_create_inventory_item_to_client(c, c->lobby_client_id, item.data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+7
-3
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
@@ -63,7 +64,8 @@ LevelTableV2::LevelTableV2(const string& data, bool compressed) {
|
||||
r = phosg::StringReader(data);
|
||||
}
|
||||
|
||||
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& offsets = r.pget<Offsets>(footer.root_offset);
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 9; char_class++) {
|
||||
@@ -112,7 +114,8 @@ LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
|
||||
// u32 offset:
|
||||
// u32[12] offsets:
|
||||
// LevelStatsDeltaBE[200] level_deltas
|
||||
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(r.pget_u32b(r.size() - 0x10)));
|
||||
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
|
||||
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(footer.root_offset));
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
@@ -189,7 +192,8 @@ LevelTableV4::LevelTableV4(const string& data, bool compressed) {
|
||||
r = phosg::StringReader(data);
|
||||
}
|
||||
|
||||
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& offsets = r.pget<Offsets>(footer.root_offset);
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
|
||||
+70
-218
@@ -27,12 +27,18 @@ shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::find(uint32_t item_id) con
|
||||
return this->items.at(item_id);
|
||||
}
|
||||
|
||||
void Lobby::FloorItemManager::add(const ItemData& item, float x, float z, uint16_t flags) {
|
||||
void Lobby::FloorItemManager::add(
|
||||
const ItemData& item,
|
||||
const VectorXZF& pos,
|
||||
shared_ptr<const MapState::ObjectState> from_obj,
|
||||
shared_ptr<const MapState::EnemyState> from_ene,
|
||||
uint16_t flags) {
|
||||
auto fi = make_shared<FloorItem>();
|
||||
fi->data = item;
|
||||
fi->x = x;
|
||||
fi->z = z;
|
||||
fi->pos = pos;
|
||||
fi->drop_number = this->next_drop_number++;
|
||||
fi->from_obj = from_obj;
|
||||
fi->from_ene = from_ene;
|
||||
fi->flags = flags;
|
||||
this->add(fi);
|
||||
}
|
||||
@@ -52,7 +58,7 @@ void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
|
||||
}
|
||||
}
|
||||
this->log.info("Added floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX",
|
||||
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags);
|
||||
fi->data.id.load(), fi->pos.x.load(), fi->pos.z.load(), fi->drop_number, fi->flags);
|
||||
}
|
||||
|
||||
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
|
||||
@@ -71,7 +77,7 @@ std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_
|
||||
}
|
||||
this->items.erase(item_it);
|
||||
this->log.info("Removed floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX",
|
||||
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags);
|
||||
fi->data.id.load(), fi->pos.x.load(), fi->pos.z.load(), fi->drop_number, fi->flags);
|
||||
return fi;
|
||||
}
|
||||
|
||||
@@ -142,7 +148,6 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||
min_level(0),
|
||||
max_level(0xFFFFFFFF),
|
||||
next_game_item_id(0xCC000000),
|
||||
base_version(Version::GC_V3),
|
||||
allowed_versions(0x0000),
|
||||
override_section_id(0xFF),
|
||||
episode(Episode::NONE),
|
||||
@@ -196,24 +201,22 @@ shared_ptr<Lobby::ChallengeParameters> Lobby::require_challenge_params() const {
|
||||
return this->challenge_params;
|
||||
}
|
||||
|
||||
void Lobby::set_drop_mode(DropMode new_mode) {
|
||||
this->drop_mode = new_mode;
|
||||
|
||||
bool should_have_item_creator = (this->base_version == Version::BB_V4) ||
|
||||
((new_mode != DropMode::DISABLED) && (new_mode != DropMode::CLIENT));
|
||||
if (should_have_item_creator && !this->item_creator) {
|
||||
this->create_item_creator();
|
||||
} else if (!should_have_item_creator && this->item_creator) {
|
||||
void Lobby::create_item_creator(Version logic_version) {
|
||||
if (!this->is_game() || this->episode == Episode::EP3) {
|
||||
this->item_creator.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Lobby::create_item_creator() {
|
||||
auto s = this->require_server_state();
|
||||
|
||||
if (logic_version == Version::UNKNOWN) {
|
||||
auto leader_c = this->clients[this->leader_id];
|
||||
logic_version = leader_c ? leader_c->version() : Version::BB_V4;
|
||||
}
|
||||
|
||||
shared_ptr<const RareItemSet> rare_item_set;
|
||||
shared_ptr<const CommonItemSet> common_item_set;
|
||||
switch (this->base_version) {
|
||||
switch (logic_version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::GC_EP3_NTE:
|
||||
@@ -245,6 +248,7 @@ void Lobby::create_item_creator() {
|
||||
default:
|
||||
throw logic_error("invalid lobby base version");
|
||||
}
|
||||
|
||||
this->item_creator = make_shared<ItemCreator>(
|
||||
common_item_set,
|
||||
rare_item_set,
|
||||
@@ -252,8 +256,8 @@ void Lobby::create_item_creator() {
|
||||
s->tool_random_set,
|
||||
s->weapon_random_sets.at(this->difficulty),
|
||||
s->tekker_adjustment_set,
|
||||
s->item_parameter_table(this->base_version),
|
||||
s->item_stack_limits(this->base_version),
|
||||
s->item_parameter_table(logic_version),
|
||||
s->item_stack_limits(logic_version),
|
||||
this->episode,
|
||||
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
|
||||
this->difficulty,
|
||||
@@ -262,21 +266,6 @@ void Lobby::create_item_creator() {
|
||||
this->quest ? this->quest->battle_rules : nullptr);
|
||||
}
|
||||
|
||||
void Lobby::change_section_id() {
|
||||
if (this->item_creator) {
|
||||
uint8_t new_section_id = this->effective_section_id();
|
||||
if (this->item_creator->get_section_id() != new_section_id) {
|
||||
this->log.info("Changing section ID to %s", name_for_section_id(new_section_id));
|
||||
this->item_creator->set_section_id(new_section_id);
|
||||
for (const auto& c : this->clients) {
|
||||
if (c && c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_printf(c, "$C5Section ID changed\nto %s (%hhu)", name_for_section_id(new_section_id), new_section_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Lobby::effective_section_id() const {
|
||||
if (this->override_section_id != 0xFF) {
|
||||
return this->override_section_id;
|
||||
@@ -291,199 +280,55 @@ uint8_t Lobby::effective_section_id() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
shared_ptr<Map> Lobby::load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
shared_ptr<const string> quest_dat_contents_decompressed) {
|
||||
auto map = make_shared<Map>(version, lobby_id, random_seed, opt_rand_crypt);
|
||||
map->add_entities_from_quest_data(episode, difficulty, event, quest_dat_contents_decompressed, rare_rates);
|
||||
return map;
|
||||
}
|
||||
|
||||
shared_ptr<Map> Lobby::load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
shared_ptr<const SetDataTableBase> sdt,
|
||||
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
const phosg::PrefixedLogger* log) {
|
||||
auto enemy_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::ENEMIES);
|
||||
auto object_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::OBJECTS);
|
||||
auto event_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::EVENTS);
|
||||
return Lobby::load_maps(
|
||||
enemy_filenames,
|
||||
object_filenames,
|
||||
event_filenames,
|
||||
version,
|
||||
episode,
|
||||
mode,
|
||||
difficulty,
|
||||
event,
|
||||
lobby_id,
|
||||
get_file_data,
|
||||
rare_rates,
|
||||
random_seed,
|
||||
opt_rand_crypt,
|
||||
log);
|
||||
}
|
||||
|
||||
shared_ptr<Map> Lobby::load_maps(
|
||||
const vector<string>& enemy_filenames,
|
||||
const vector<string>& object_filenames,
|
||||
const vector<string>& event_filenames,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t rare_seed,
|
||||
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const phosg::PrefixedLogger* log) {
|
||||
auto map = make_shared<Map>(version, lobby_id, rare_seed, opt_rand_crypt);
|
||||
|
||||
// Don't load free-roam maps in Challenge mode, since players can't go to
|
||||
// Ragol without a quest loaded
|
||||
if (mode == GameMode::CHALLENGE) {
|
||||
return map;
|
||||
}
|
||||
|
||||
for (size_t floor = 0; floor < 0x12; floor++) {
|
||||
const auto& floor_enemy_filename = enemy_filenames.at(floor);
|
||||
if (!floor_enemy_filename.empty()) {
|
||||
auto map_data = get_file_data(version, floor_enemy_filename);
|
||||
if (map_data) {
|
||||
map->add_enemies_from_map_data(
|
||||
episode,
|
||||
difficulty,
|
||||
event,
|
||||
floor,
|
||||
map_data->data(),
|
||||
map_data->size(),
|
||||
rare_rates);
|
||||
if (log) {
|
||||
log->info("Loaded enemies map %s for floor %02zX", floor_enemy_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("Enemies map %s for floor %02zX cannot be used; skipping", floor_enemy_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("No enemies to load for floor %02zX", floor);
|
||||
}
|
||||
|
||||
const auto& floor_object_filename = object_filenames.at(floor);
|
||||
if (!floor_object_filename.empty()) {
|
||||
auto map_data = get_file_data(version, floor_object_filename);
|
||||
if (map_data) {
|
||||
map->add_objects_from_map_data(floor, map_data);
|
||||
if (log) {
|
||||
log->info("Loaded objects map %s for floor %02zX", floor_object_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("Objects map %s for floor %02zX cannot be used; skipping", floor_object_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("No objects to load for floor %02zX", floor);
|
||||
}
|
||||
|
||||
const auto& floor_event_filename = event_filenames.at(floor);
|
||||
if (!floor_event_filename.empty()) {
|
||||
auto map_data = get_file_data(version, floor_event_filename);
|
||||
if (map_data) {
|
||||
map->add_events_from_map_data(floor, map_data->data(), map_data->size());
|
||||
if (log) {
|
||||
log->info("Loaded events map %s for floor %02zX", floor_event_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("Events map %s for floor %02zX cannot be used; skipping", floor_event_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("No events to load for floor %02zX", floor);
|
||||
uint16_t Lobby::quest_version_flags() const {
|
||||
uint16_t ret = 0;
|
||||
for (auto lc : this->clients) {
|
||||
if (lc) {
|
||||
ret |= (1 << static_cast<size_t>(lc->version()));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Lobby::load_maps() {
|
||||
auto rare_rates = ((this->base_version == Version::BB_V4) && this->rare_enemy_rates)
|
||||
? this->rare_enemy_rates
|
||||
: Map::DEFAULT_RARE_ENEMIES;
|
||||
auto rare_rates = this->rare_enemy_rates ? this->rare_enemy_rates : MapState::DEFAULT_RARE_ENEMIES;
|
||||
|
||||
if (this->quest) {
|
||||
auto leader_c = this->clients.at(this->leader_id);
|
||||
if (!leader_c) {
|
||||
throw logic_error("lobby leader is missing");
|
||||
}
|
||||
|
||||
auto vq = this->quest->version(this->base_version, leader_c->language());
|
||||
if (!vq->dat_contents_decompressed) {
|
||||
throw runtime_error("quest does not have DAT data");
|
||||
}
|
||||
this->map = this->load_maps(
|
||||
this->base_version,
|
||||
this->episode,
|
||||
if (this->episode == Episode::EP3) {
|
||||
this->map_state = make_shared<MapState>();
|
||||
} else if (this->quest) {
|
||||
this->map_state = make_shared<MapState>(
|
||||
this->lobby_id,
|
||||
this->difficulty,
|
||||
this->event,
|
||||
this->lobby_id,
|
||||
rare_rates,
|
||||
this->random_seed,
|
||||
this->rare_enemy_rates,
|
||||
this->opt_rand_crypt,
|
||||
vq->dat_contents_decompressed);
|
||||
|
||||
} else if (this->mode != GameMode::CHALLENGE) {
|
||||
auto s = this->require_server_state();
|
||||
this->map = this->load_maps(
|
||||
this->base_version,
|
||||
this->episode,
|
||||
this->mode,
|
||||
this->difficulty,
|
||||
this->event,
|
||||
this->lobby_id,
|
||||
s->set_data_table(this->base_version, this->episode, this->mode, this->difficulty),
|
||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||
rare_rates,
|
||||
this->random_seed,
|
||||
this->opt_rand_crypt,
|
||||
this->variations,
|
||||
&this->log);
|
||||
|
||||
this->quest->get_supermap(this->random_seed));
|
||||
} else {
|
||||
this->map = make_shared<Map>(this->base_version, this->lobby_id, this->random_seed, this->opt_rand_crypt);
|
||||
auto s = this->require_server_state();
|
||||
this->map_state = make_shared<MapState>(
|
||||
this->lobby_id,
|
||||
this->difficulty,
|
||||
this->event,
|
||||
this->random_seed,
|
||||
this->rare_enemy_rates,
|
||||
this->opt_rand_crypt,
|
||||
s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations));
|
||||
}
|
||||
|
||||
this->log.info("Generated objects list (%zu entries):", this->map->objects.size());
|
||||
for (size_t z = 0; z < this->map->objects.size(); z++) {
|
||||
string o_str = this->map->objects[z].str();
|
||||
this->log.info("(K-%zX) %s", z, o_str.c_str());
|
||||
if (this->check_flag(Lobby::Flag::DEBUG)) {
|
||||
this->log.info("Generated map state:");
|
||||
this->map_state->print(stderr);
|
||||
}
|
||||
this->log.info("Generated enemies list (%zu entries):", this->map->enemies.size());
|
||||
for (size_t z = 0; z < this->map->enemies.size(); z++) {
|
||||
string e_str = this->map->enemies[z].str();
|
||||
this->log.info("(E-%zX) %s", z, e_str.c_str());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Lobby::is_ep3_nte() const {
|
||||
for (const auto& lc : this->clients) {
|
||||
if (lc && (lc->version() != Version::GC_EP3_NTE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this->log.info("Generated events list (%zu entries):", this->map->events.size());
|
||||
for (size_t z = 0; z < this->map->events.size(); z++) {
|
||||
string e_str = this->map->events[z].str();
|
||||
this->log.info("%s", e_str.c_str());
|
||||
}
|
||||
this->log.info("Loaded maps contain %zu object entries and %zu enemy entries overall (%zu as rares)",
|
||||
this->map->objects.size(), this->map->enemies.size(), this->map->rare_enemy_indexes.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lobby::create_ep3_server() {
|
||||
@@ -494,7 +339,8 @@ void Lobby::create_ep3_server() {
|
||||
this->log.info("Recreating Episode 3 server state");
|
||||
}
|
||||
auto tourn = this->tournament_match ? this->tournament_match->tournament.lock() : nullptr;
|
||||
bool is_nte = this->base_version == Version::GC_EP3_NTE;
|
||||
|
||||
bool is_nte = this->is_ep3_nte();
|
||||
Episode3::Server::Options options = {
|
||||
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index,
|
||||
.map_index = s->ep3_map_index,
|
||||
@@ -504,7 +350,7 @@ void Lobby::create_ep3_server() {
|
||||
.tournament = tourn,
|
||||
.trap_card_ids = s->ep3_trap_card_ids,
|
||||
};
|
||||
if (this->base_version == Version::GC_EP3_NTE) {
|
||||
if (is_nte) {
|
||||
options.behavior_flags |= Episode3::BehaviorFlag::IS_TRIAL_EDITION;
|
||||
} else {
|
||||
options.behavior_flags &= (~Episode3::BehaviorFlag::IS_TRIAL_EDITION);
|
||||
@@ -520,7 +366,7 @@ void Lobby::reassign_leader_on_client_departure(size_t leaving_client_index) {
|
||||
}
|
||||
if (this->clients[x]) {
|
||||
this->leader_id = x;
|
||||
this->change_section_id();
|
||||
this->create_item_creator();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -611,7 +457,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
}
|
||||
if (leader_index >= this->max_clients) {
|
||||
this->leader_id = c->lobby_client_id;
|
||||
this->change_section_id();
|
||||
this->create_item_creator();
|
||||
}
|
||||
|
||||
// If this is a lobby or no one was here before this, reassign all the floor
|
||||
@@ -850,9 +696,15 @@ shared_ptr<Lobby::FloorItem> Lobby::find_item(uint8_t floor, uint32_t item_id) c
|
||||
return this->floor_item_managers.at(floor).find(item_id);
|
||||
}
|
||||
|
||||
void Lobby::add_item(uint8_t floor, const ItemData& data, float x, float z, uint16_t flags) {
|
||||
void Lobby::add_item(
|
||||
uint8_t floor,
|
||||
const ItemData& data,
|
||||
const VectorXZF& pos,
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj,
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene,
|
||||
uint16_t flags) {
|
||||
auto& m = this->floor_item_managers.at(floor);
|
||||
m.add(data, x, z, flags);
|
||||
m.add(data, pos, from_obj, from_ene, flags);
|
||||
this->evict_items_from_floor(floor);
|
||||
}
|
||||
|
||||
|
||||
+28
-51
@@ -26,9 +26,11 @@ struct ServerState;
|
||||
struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
struct FloorItem {
|
||||
ItemData data;
|
||||
float x;
|
||||
float z;
|
||||
VectorXZF pos;
|
||||
uint64_t drop_number;
|
||||
// At most one of the following will be non-null
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj;
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene;
|
||||
// The low 12 bits of flags are visibility flags, specifying which clients
|
||||
// can see the item. (In practice, only the lowest 4 of these bits are used,
|
||||
// but the game has fields for 12 players so we do too.)
|
||||
@@ -52,7 +54,12 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
|
||||
bool exists(uint32_t item_id) const;
|
||||
std::shared_ptr<FloorItem> find(uint32_t item_id) const;
|
||||
void add(const ItemData& item, float x, float z, uint16_t flags);
|
||||
void add(
|
||||
const ItemData& item,
|
||||
const VectorXZF& pos,
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj,
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene,
|
||||
uint16_t flags);
|
||||
void add(std::shared_ptr<FloorItem> fi);
|
||||
std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id);
|
||||
std::unordered_set<std::shared_ptr<FloorItem>> evict();
|
||||
@@ -65,6 +72,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
// clang-format off
|
||||
GAME = 0x00000001,
|
||||
PERSISTENT = 0x00000002,
|
||||
DEBUG = 0x00000004,
|
||||
// Flags used only for games
|
||||
CHEATS_ENABLED = 0x00000100,
|
||||
QUEST_SELECTION_IN_PROGRESS = 0x00000200,
|
||||
@@ -103,15 +111,14 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
std::array<uint32_t, 12> next_item_id_for_client;
|
||||
uint32_t next_game_item_id;
|
||||
std::vector<FloorItemManager> floor_item_managers;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
|
||||
std::shared_ptr<Map> map;
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates;
|
||||
std::shared_ptr<MapState> map_state; // Always null for lobbies, never null for games
|
||||
Variations variations;
|
||||
std::unique_ptr<QuestFlags> quest_flags_known; // If null, ALL quest flags are known
|
||||
std::unique_ptr<QuestFlags> quest_flag_values;
|
||||
std::unique_ptr<SwitchFlags> switch_flags;
|
||||
|
||||
// Game config
|
||||
Version base_version;
|
||||
// Bits in allowed_versions specify who is allowed to join this game. The
|
||||
// bits are indexed as (1 << version), where version is a value from the
|
||||
// Version enum.
|
||||
@@ -131,7 +138,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
uint8_t allowed_drop_modes;
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<ItemCreator> item_creator; // Always null for lobbies, never null for games
|
||||
|
||||
struct ChallengeParameters {
|
||||
uint8_t stage_number = 0;
|
||||
@@ -204,55 +211,16 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
std::shared_ptr<ChallengeParameters> require_challenge_params() const;
|
||||
void set_drop_mode(DropMode new_mode);
|
||||
void create_item_creator();
|
||||
void change_section_id();
|
||||
void create_item_creator(Version logic_version = Version::UNKNOWN);
|
||||
uint8_t effective_section_id() const;
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const SetDataTableBase> sdt,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
const phosg::PrefixedLogger* log = nullptr);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
const std::vector<std::string>& enemy_filenames,
|
||||
const std::vector<std::string>& object_filenames,
|
||||
const std::vector<std::string>& event_filenames,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const phosg::PrefixedLogger* log = nullptr);
|
||||
uint16_t quest_version_flags() const;
|
||||
void load_maps();
|
||||
void create_ep3_server();
|
||||
|
||||
[[nodiscard]] inline bool is_game() const {
|
||||
return this->check_flag(Flag::GAME);
|
||||
}
|
||||
[[nodiscard]] bool is_ep3_nte() const;
|
||||
[[nodiscard]] inline bool is_ep3() const {
|
||||
return this->episode == Episode::EP3;
|
||||
}
|
||||
@@ -263,6 +231,9 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
inline void allow_version(Version v) {
|
||||
this->allowed_versions |= (1 << static_cast<size_t>(v));
|
||||
}
|
||||
inline void forbid_version(Version v) {
|
||||
this->allowed_versions &= ~(1 << static_cast<size_t>(v));
|
||||
}
|
||||
|
||||
void reassign_leader_on_client_departure(size_t leaving_client_id);
|
||||
size_t count_clients() const;
|
||||
@@ -297,7 +268,13 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
|
||||
bool item_exists(uint8_t floor, uint32_t item_id) const;
|
||||
std::shared_ptr<FloorItem> find_item(uint8_t floor, uint32_t item_id) const;
|
||||
void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t flags);
|
||||
void add_item(
|
||||
uint8_t floor,
|
||||
const ItemData& item,
|
||||
const VectorXZF& pos,
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj,
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene,
|
||||
uint16_t flags);
|
||||
void add_item(uint8_t floor, std::shared_ptr<FloorItem>);
|
||||
void evict_items_from_floor(uint8_t floor);
|
||||
std::shared_ptr<FloorItem> remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id);
|
||||
|
||||
+113
-233
@@ -1407,7 +1407,8 @@ Action a_encode_qst(
|
||||
pvr_data = make_shared<string>(phosg::load_file(pvr_filename));
|
||||
} catch (const phosg::cannot_open_file&) {
|
||||
}
|
||||
auto vq = make_shared<VersionedQuest>(0, 0, version, 0, bin_data, dat_data, pvr_data);
|
||||
|
||||
auto vq = make_shared<VersionedQuest>(0, 0, version, 0, bin_data, dat_data, nullptr, pvr_data);
|
||||
if (download) {
|
||||
vq = vq->create_download_quest();
|
||||
}
|
||||
@@ -1446,21 +1447,22 @@ Action a_disassemble_quest_script(
|
||||
});
|
||||
Action a_disassemble_quest_map(
|
||||
"disassemble-quest-map", "\
|
||||
disassemble-quest-map [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
|
||||
disassemble-quest-map [OPTIONS] [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
|
||||
Disassemble the input quest map (.dat file) into a text representation of\n\
|
||||
the data it contains.\n",
|
||||
the data it contains. If --decompressed is given, don\'t decompress before\n\
|
||||
disassembling.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
string data = read_input_data(args);
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
if (!args.get<bool>("decompressed")) {
|
||||
data = prs_decompress(data);
|
||||
*data = prs_decompress(*data);
|
||||
}
|
||||
string result = Map::disassemble_quest_data(data.data(), data.size());
|
||||
string result = MapFile(data).disassemble();
|
||||
write_output_data(args, result.data(), result.size(), "txt");
|
||||
});
|
||||
Action a_disassemble_free_map(
|
||||
"disassemble-free-map", "\
|
||||
disassemble-free-map INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
||||
Disassemble the input free-roam map (.dat or .evt file) into a text\n\
|
||||
Disassemble the input free-play map (.dat or .evt file) into a text\n\
|
||||
representation of the data it contains. Unlike othe disassembly actions,\n\
|
||||
this action expects its input to be already decompressed. If the input is\n\
|
||||
compressed, use the --compressed option. Also unlike other options, the\n\
|
||||
@@ -1475,18 +1477,19 @@ Action a_disassemble_free_map(
|
||||
throw runtime_error("cannot determine input file type");
|
||||
}
|
||||
|
||||
string data = read_input_data(args);
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
if (args.get<bool>("compressed")) {
|
||||
data = prs_decompress(data);
|
||||
*data = prs_decompress(*data);
|
||||
}
|
||||
|
||||
uint8_t floor = args.get<uint8_t>("floor", 0);
|
||||
string result;
|
||||
if (is_objects) {
|
||||
result = Map::disassemble_objects_data(data.data(), data.size());
|
||||
result = MapFile(floor, data, nullptr, nullptr).disassemble();
|
||||
} else if (is_enemies) {
|
||||
result = Map::disassemble_enemies_data(data.data(), data.size());
|
||||
result = MapFile(floor, nullptr, data, nullptr).disassemble();
|
||||
} else if (is_events) {
|
||||
result = Map::disassemble_wave_events_data(data.data(), data.size());
|
||||
result = MapFile(floor, nullptr, nullptr, data).disassemble();
|
||||
} else {
|
||||
throw logic_error("unhandled input type");
|
||||
}
|
||||
@@ -1912,7 +1915,7 @@ Action a_convert_rare_item_set(
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
shared_ptr<RareItemSet> rs;
|
||||
if (phosg::ends_with(input_filename, ".json")) {
|
||||
rs = make_shared<RareItemSet>(phosg::JSON::parse(*data), s->item_name_index(version));
|
||||
rs = make_shared<RareItemSet>(phosg::JSON::parse(*data), s->item_name_index_opt(version));
|
||||
} else if (phosg::ends_with(input_filename, ".gsl")) {
|
||||
rs = make_shared<RareItemSet>(GSLArchive(data, false), false);
|
||||
} else if (phosg::ends_with(input_filename, ".gslb")) {
|
||||
@@ -1931,9 +1934,9 @@ Action a_convert_rare_item_set(
|
||||
|
||||
string output_filename = args.get<string>(2, false);
|
||||
if (output_filename.empty() || (output_filename == "-")) {
|
||||
rs->print_all_collections(stdout, s->item_name_index(version));
|
||||
rs->print_all_collections(stdout, s->item_name_index_opt(version));
|
||||
} else if (phosg::ends_with(output_filename, ".json")) {
|
||||
auto json = rs->json(s->item_name_index(version));
|
||||
auto json = rs->json(s->item_name_index_opt(version));
|
||||
string data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::SORT_DICT_KEYS);
|
||||
write_output_data(args, data.data(), data.size(), nullptr);
|
||||
} else if (phosg::ends_with(output_filename, ".gsl")) {
|
||||
@@ -2088,23 +2091,21 @@ Action a_name_all_items(
|
||||
}
|
||||
|
||||
fprintf(stderr, "IDENT :");
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
Version version = static_cast<Version>(v_s);
|
||||
const auto& index = s->item_name_indexes.at(v_s);
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
if (index) {
|
||||
fprintf(stderr, " %30s ", phosg::name_for_enum(version));
|
||||
fprintf(stderr, " %30s ", phosg::name_for_enum(v));
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
|
||||
for (uint32_t primary_identifier : all_primary_identifiers) {
|
||||
fprintf(stderr, "%08" PRIX32 ":", primary_identifier);
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
const auto& index = s->item_name_indexes.at(v_s);
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
if (index) {
|
||||
Version version = static_cast<Version>(v_s);
|
||||
auto pmt = s->item_parameter_table(version);
|
||||
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(version), primary_identifier);
|
||||
auto pmt = s->item_parameter_table(v);
|
||||
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(v), primary_identifier);
|
||||
string name = index->describe_item(item);
|
||||
try {
|
||||
bool is_rare = pmt->is_item_rare(item);
|
||||
@@ -2212,10 +2213,9 @@ Action a_print_item_parameter_tables(
|
||||
s->load_text_index(false);
|
||||
s->load_item_definitions(false);
|
||||
s->load_item_name_indexes(false);
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
const auto& index = s->item_name_indexes.at(v_s);
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
if (index) {
|
||||
Version v = static_cast<Version>(v_s);
|
||||
fprintf(stdout, "======== %s\n", phosg::name_for_enum(v));
|
||||
index->print_table(stdout);
|
||||
}
|
||||
@@ -2569,224 +2569,104 @@ Action a_show_battle_params(
|
||||
s->battle_params->get_table(true, Episode::EP4).print(stdout);
|
||||
});
|
||||
|
||||
Action a_find_rare_enemy_seeds(
|
||||
"find-rare-enemy-seeds", "\
|
||||
find-rare-enemy-seeds OPTIONS...\n\
|
||||
Search all possible rare seeds to find those that produce one or more rare\n\
|
||||
enemies in any set of variations. A version option (e.g. --gc) is required;\n\
|
||||
an episode option (--ep1, --ep2, or --ep4) is also required. A difficulty\n\
|
||||
option (--normal, --hard, --very-hard, or --ultimate) may be given; this\n\
|
||||
affects which rare rates from config.json are used if --bb was given.\n\
|
||||
Similarly, --battle, --challenge, or --solo may also be given; this affects\n\
|
||||
which variations are used on all versions and which rare rates to use for\n\
|
||||
BB. --threads=COUNT controls the number of threads to use for the search\n\
|
||||
by default, one thread per CPU core is used. --min-count specifies how many\n\
|
||||
rare enemies must be found to output the seed. Finally, --quest=NAME may be\n\
|
||||
given to use that quest\'s map instead of the free-roam maps.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto version = get_cli_version(args);
|
||||
auto episode = get_cli_episode(args);
|
||||
auto difficulty = get_cli_difficulty(args);
|
||||
auto mode = get_cli_game_mode(args);
|
||||
size_t num_threads = args.get<size_t>("threads", 0);
|
||||
size_t min_count = args.get<size_t>("min-count", 1);
|
||||
string quest_name = args.get<string>("quest", false);
|
||||
|
||||
auto s = make_shared<ServerState>(get_config_filename(args));
|
||||
shared_ptr<const VersionedQuest> vq;
|
||||
if (!quest_name.empty()) {
|
||||
s->load_config_early();
|
||||
s->load_quest_index(false);
|
||||
auto q = s->quest_index(version)->get(quest_name);
|
||||
if (!q) {
|
||||
throw runtime_error("quest does not exist");
|
||||
}
|
||||
vq = q->version(version, 1);
|
||||
if (!vq) {
|
||||
throw runtime_error("quest version does not exist");
|
||||
}
|
||||
} else if (version == Version::BB_V4) {
|
||||
s->load_config_early();
|
||||
} else if (version == Version::PC_V2) {
|
||||
s->load_patch_indexes(false);
|
||||
} else {
|
||||
s->clear_file_caches(false);
|
||||
}
|
||||
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates;
|
||||
if (version != Version::BB_V4) {
|
||||
rare_rates = Map::DEFAULT_RARE_ENEMIES;
|
||||
} else if (mode == GameMode::CHALLENGE) {
|
||||
rare_rates = s->rare_enemy_rates_challenge;
|
||||
} else {
|
||||
rare_rates = s->rare_enemy_rates_by_difficulty[difficulty];
|
||||
}
|
||||
|
||||
mutex output_lock;
|
||||
auto thread_fn = [&](uint64_t seed, size_t) -> bool {
|
||||
auto random_crypt = make_shared<PSOV2Encryption>(seed);
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
|
||||
shared_ptr<Map> map;
|
||||
if (vq) {
|
||||
if (!vq->dat_contents_decompressed) {
|
||||
throw runtime_error("quest does not have DAT data");
|
||||
}
|
||||
map = Lobby::load_maps(
|
||||
version, episode, difficulty, 0, 0, rare_rates, seed, random_crypt, vq->dat_contents_decompressed);
|
||||
|
||||
} else {
|
||||
generate_variations_deprecated(variations, random_crypt, version, episode, (mode == GameMode::SOLO));
|
||||
map = Lobby::load_maps(
|
||||
version,
|
||||
episode,
|
||||
mode,
|
||||
difficulty,
|
||||
0,
|
||||
0,
|
||||
s->set_data_table(version, episode, mode, difficulty),
|
||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||
rare_rates,
|
||||
seed,
|
||||
random_crypt,
|
||||
variations);
|
||||
}
|
||||
|
||||
vector<size_t> rare_indexes;
|
||||
for (size_t z = 0; z < map->enemies.size(); z++) {
|
||||
if (enemy_type_is_rare(map->enemies[z].type)) {
|
||||
rare_indexes.emplace_back(z);
|
||||
}
|
||||
}
|
||||
|
||||
if (rare_indexes.size() >= min_count) {
|
||||
lock_guard g(output_lock);
|
||||
fprintf(stdout, "%08" PRIX64 ":", seed);
|
||||
for (size_t index : rare_indexes) {
|
||||
fprintf(stdout, " E-%zX:%s", index, phosg::name_for_enum(map->enemies[index].type));
|
||||
}
|
||||
fprintf(stdout, "\n");
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
phosg::parallel_range_blocks<uint64_t>(thread_fn, 0, 0x100000000, 0x1000, num_threads, nullptr);
|
||||
});
|
||||
|
||||
Action a_load_maps_test(
|
||||
"load-maps-test", nullptr, +[](phosg::Arguments& args) {
|
||||
using SDT = SetDataTable;
|
||||
bool save_disassembly = args.get<bool>("disassemble");
|
||||
|
||||
auto s = make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->clear_file_caches(false);
|
||||
s->load_patch_indexes(false);
|
||||
s->load_set_data_tables(false);
|
||||
s->load_quest_index(false);
|
||||
for (size_t v_s = NUM_PATCH_VERSIONS; v_s < NUM_VERSIONS; v_s++) {
|
||||
Version v = static_cast<Version>(v_s);
|
||||
if (is_ep3(v)) {
|
||||
continue;
|
||||
}
|
||||
const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
if (episode == Episode::EP4 && !is_v4(v)) {
|
||||
continue;
|
||||
}
|
||||
if (episode == Episode::EP2 && is_v1_or_v2(v) && (v != Version::GC_NTE)) {
|
||||
continue;
|
||||
}
|
||||
const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (GameMode mode : modes) {
|
||||
if ((mode == GameMode::BATTLE || mode == GameMode::CHALLENGE) && is_v1(v)) {
|
||||
continue;
|
||||
}
|
||||
if (mode == GameMode::SOLO && !is_v4(v)) {
|
||||
continue;
|
||||
}
|
||||
for (uint8_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
if (difficulty == 3 && is_v1(v)) {
|
||||
continue;
|
||||
}
|
||||
auto sdt = s->set_data_table(v, episode, mode, difficulty);
|
||||
for (uint8_t floor = 0; floor < 0x12; floor++) {
|
||||
auto variation_maxes = sdt->num_free_roam_variations_for_floor(episode, mode == GameMode::SOLO, floor);
|
||||
for (size_t var1 = 0; var1 < variation_maxes.first; var1++) {
|
||||
for (size_t var2 = 0; var2 < variation_maxes.second; var2++) {
|
||||
auto enemies_filename = sdt->map_filename_for_variation(
|
||||
floor, var1, var2, episode, mode, SDT::FilenameType::ENEMIES);
|
||||
auto objects_filename = sdt->map_filename_for_variation(
|
||||
floor, var1, var2, episode, mode, SDT::FilenameType::OBJECTS);
|
||||
auto events_filename = sdt->map_filename_for_variation(
|
||||
floor, var1, var2, episode, mode, SDT::FilenameType::EVENTS);
|
||||
s->load_maps(false);
|
||||
|
||||
fprintf(stderr, "... %s %s %s %s %02hhX %zX %zX",
|
||||
phosg::name_for_enum(v), name_for_episode(episode), name_for_mode(mode), name_for_difficulty(difficulty), floor, var1, var2);
|
||||
auto map = make_shared<Map>(v, 0, 0, nullptr);
|
||||
if (!enemies_filename.empty()) {
|
||||
fprintf(stderr, " [%s => ", enemies_filename.c_str());
|
||||
auto map_data = s->load_map_file(v, enemies_filename);
|
||||
if (map_data) {
|
||||
map->add_enemies_from_map_data(
|
||||
episode, difficulty, 0, 0, map_data->data(), map_data->size(), Map::DEFAULT_RARE_ENEMIES);
|
||||
fprintf(stderr, "%zu enemies, %zu sets]", map->enemies.size(), map->enemy_set_flags.size());
|
||||
} else {
|
||||
fprintf(stderr, "__MISSING__]");
|
||||
}
|
||||
}
|
||||
if (!objects_filename.empty()) {
|
||||
fprintf(stderr, " [%s => ", objects_filename.c_str());
|
||||
auto map_data = s->load_map_file(v, objects_filename);
|
||||
if (map_data) {
|
||||
map->add_objects_from_map_data(floor, map_data);
|
||||
fprintf(stderr, "%zu objects]", map->objects.size());
|
||||
} else {
|
||||
fprintf(stderr, "__MISSING__]");
|
||||
}
|
||||
}
|
||||
if (!events_filename.empty()) {
|
||||
fprintf(stderr, " [%s => ", events_filename.c_str());
|
||||
auto map_data = s->load_map_file(v, events_filename);
|
||||
if (map_data) {
|
||||
map->add_events_from_map_data(floor, map_data->data(), map_data->size());
|
||||
fprintf(stderr, "%zu events, %zu action bytes]", map->events.size(), map->event_action_stream.size());
|
||||
} else {
|
||||
fprintf(stderr, "__MISSING__]");
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& it : s->supermaps) {
|
||||
auto episode = static_cast<Episode>((it.first >> 28) & 7);
|
||||
auto mode = static_cast<GameMode>((it.first >> 26) & 3);
|
||||
uint8_t difficulty = (it.first >> 24) & 3;
|
||||
uint8_t floor = (it.first >> 16) & 0xFF;
|
||||
uint8_t layout = (it.first >> 8) & 0xFF;
|
||||
uint8_t entities = (it.first >> 0) & 0xFF;
|
||||
|
||||
fprintf(stderr, "FREE MAP: %08" PRIX32 " => %s %s %c floor=%02hhX layout=%02hhX entities=%02hhX\n",
|
||||
it.first,
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities);
|
||||
if (save_disassembly) {
|
||||
string filename = phosg::string_printf(
|
||||
"supermap_%s_%s_%c_%02hhX_%02hhx_%02hhX.txt",
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities);
|
||||
auto f = phosg::fopen_unique(filename, "wt");
|
||||
it.second->print(f.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& q_it : s->default_quest_index->quests_by_number) {
|
||||
for (const auto& vq_it : q_it.second->versions) {
|
||||
auto vq = vq_it.second;
|
||||
shared_ptr<const Map> map = Lobby::load_maps(
|
||||
vq->version,
|
||||
vq->episode,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
Map::DEFAULT_RARE_ENEMIES,
|
||||
0,
|
||||
nullptr,
|
||||
vq->dat_contents_decompressed);
|
||||
fprintf(stderr, "... %" PRIu32 " (%s) %s %s %s => %zu enemies (%zu sets), %zu objects, %zu events\n",
|
||||
vq->quest_number,
|
||||
vq->name.c_str(),
|
||||
name_for_episode(vq->episode),
|
||||
phosg::name_for_enum(vq->version),
|
||||
name_for_language_code(vq->language),
|
||||
map->enemies.size(),
|
||||
map->enemy_set_flags.size(),
|
||||
map->objects.size(),
|
||||
map->events.size());
|
||||
// Generate MapStates for a few random variations
|
||||
for (size_t z = 0; z < 0x20; z++) {
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
|
||||
Episode episode = episodes[phosg::random_object<uint32_t>() % episodes.size()];
|
||||
GameMode mode = modes[phosg::random_object<uint32_t>() % modes.size()];
|
||||
uint8_t difficulty = phosg::random_object<uint32_t>() % 4;
|
||||
uint8_t event = phosg::random_object<uint32_t>() % 8;
|
||||
uint32_t random_seed = phosg::random_object<uint32_t>();
|
||||
fprintf(stderr, "FREE MAP STATE TEST: %s %s %c\n",
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty));
|
||||
|
||||
auto sdt = s->set_data_table(Version::BB_V4, episode, mode, difficulty);
|
||||
auto variations = sdt->generate_variations(episode, (mode == GameMode::SOLO), nullptr);
|
||||
auto supermaps = s->supermaps_for_variations(episode, mode, difficulty, variations);
|
||||
auto map_state = make_shared<MapState>(
|
||||
0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, nullptr, supermaps);
|
||||
map_state->verify();
|
||||
|
||||
fprintf(stderr, " map state ok: 0x%zX objects, 0x%zX enemies, 0x%zX enemy sets, 0x%zX events\n",
|
||||
map_state->object_states.size(),
|
||||
map_state->enemy_states.size(),
|
||||
map_state->enemy_set_states.size(),
|
||||
map_state->event_states.size());
|
||||
}
|
||||
|
||||
s->load_quest_index(false);
|
||||
|
||||
uint32_t random_seed = args.get<uint32_t>("random-seed", 0, phosg::Arguments::IntFormat::HEX);
|
||||
for (const auto& it : s->default_quest_index->quests_by_number) {
|
||||
auto supermap = it.second->get_supermap(random_seed);
|
||||
if (!supermap) {
|
||||
fprintf(stderr, "QUEST MAP: %08" PRIX32 " => (no supermap)\n", it.first);
|
||||
} else {
|
||||
string filename = phosg::string_printf("supermap_quest_%" PRIu32 "_%08" PRIX32 ".txt", it.first, random_seed);
|
||||
fprintf(stderr, "QUEST MAP: %08" PRIX32 " => %s\n", it.first, filename.c_str());
|
||||
if (save_disassembly) {
|
||||
auto f = phosg::fopen_unique(filename, "wt");
|
||||
fprintf(f.get(), "QUEST %" PRIu32 " (%s)\n", it.first, it.second->name.c_str());
|
||||
supermap->print(f.get());
|
||||
}
|
||||
}
|
||||
|
||||
auto map_state = make_shared<MapState>(
|
||||
0,
|
||||
phosg::random_object<uint8_t>() & 3,
|
||||
0,
|
||||
phosg::random_object<uint32_t>(),
|
||||
MapState::DEFAULT_RARE_ENEMIES,
|
||||
nullptr,
|
||||
supermap);
|
||||
map_state->verify();
|
||||
|
||||
fprintf(stderr, " map state ok: 0x%zX objects, 0x%zX enemies, 0x%zX enemy sets, 0x%zX events\n",
|
||||
map_state->object_states.size(),
|
||||
map_state->enemy_states.size(),
|
||||
map_state->enemy_set_states.size(),
|
||||
map_state->event_states.size());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+3598
-2140
File diff suppressed because it is too large
Load Diff
+806
-406
File diff suppressed because it is too large
Load Diff
+66
-54
@@ -746,7 +746,7 @@ static HandlerResult S_B2(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
ses->log.info("Wrote code from server to file %s", output_filename.c_str());
|
||||
|
||||
#ifdef HAVE_RESOURCE_FILE
|
||||
using FooterT = S_ExecuteCode_FooterT_B2<BE>;
|
||||
using FooterT = RELFileFooterT<BE>;
|
||||
|
||||
// TODO: Support SH-4 disassembly too
|
||||
bool is_ppc = ::is_ppc(ses->version());
|
||||
@@ -770,9 +770,9 @@ static HandlerResult S_B2(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
reloc_offset += (r.get<U16T<BE>>() * 4);
|
||||
labels.emplace(reloc_offset, phosg::string_printf("reloc%zu", x));
|
||||
}
|
||||
labels.emplace(footer.entrypoint_addr_offset.load(), "entry_ptr");
|
||||
labels.emplace(footer.root_offset.load(), "entry_ptr");
|
||||
labels.emplace(footer_offset, "footer");
|
||||
labels.emplace(r.pget<U32T<BE>>(footer.entrypoint_addr_offset), "start");
|
||||
labels.emplace(r.pget<U32T<BE>>(footer.root_offset), "start");
|
||||
|
||||
string disassembly;
|
||||
if (is_ppc) {
|
||||
@@ -1002,9 +1002,9 @@ static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, co
|
||||
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data.data(), data.size());
|
||||
auto s = ses->require_server_state();
|
||||
ses->next_drop_item.id = ses->next_item_id++;
|
||||
bool is_box = (cmd.rt_index == 0x30);
|
||||
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
uint8_t source_type = (cmd.rt_index == 0x30) ? 2 : 1;
|
||||
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, source_type, cmd.floor, cmd.pos, cmd.entity_index);
|
||||
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, source_type, cmd.floor, cmd.pos, cmd.entity_index);
|
||||
ses->next_drop_item.clear();
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
@@ -1025,27 +1025,34 @@ static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, co
|
||||
ses->log.warning("Session is in INTERCEPT drop mode, but item creator is missing");
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
if (!ses->map) {
|
||||
ses->log.warning("Session is in INTERCEPT drop mode, but map is missing");
|
||||
if (!ses->map_state) {
|
||||
ses->log.warning("Session is in INTERCEPT drop mode, but map state is missing");
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
|
||||
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data.data(), data.size());
|
||||
auto rec = reconcile_drop_request_with_map(
|
||||
ses->log, ses->client_channel, cmd, ses->version(), ses->lobby_episode, ses->config, ses->map, false);
|
||||
ses->log,
|
||||
ses->client_channel,
|
||||
cmd,
|
||||
ses->lobby_episode,
|
||||
ses->lobby_event,
|
||||
ses->config,
|
||||
ses->map_state,
|
||||
false);
|
||||
|
||||
ItemCreator::DropResult res;
|
||||
if (rec.is_box) {
|
||||
if (rec.obj_st) {
|
||||
if (rec.ignore_def) {
|
||||
ses->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
ses->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_index.load(), cmd.effective_area);
|
||||
res = ses->item_creator->on_box_item_drop(cmd.effective_area);
|
||||
} else {
|
||||
ses->log.info("Creating item from box %04hX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")",
|
||||
cmd.entity_id.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
|
||||
cmd.entity_index.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
|
||||
res = ses->item_creator->on_specialized_box_item_drop(cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6);
|
||||
}
|
||||
} else {
|
||||
ses->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
ses->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_index.load(), cmd.effective_area);
|
||||
res = ses->item_creator->on_monster_item_drop(rec.effective_rt_index, cmd.effective_area);
|
||||
}
|
||||
|
||||
@@ -1054,12 +1061,12 @@ static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, co
|
||||
} else {
|
||||
auto s = ses->require_server_state();
|
||||
string name = s->describe_item(ses->version(), res.item, false);
|
||||
ses->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
ses->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_index.load(), cmd.effective_area, name.c_str());
|
||||
res.item.id = ses->next_item_id++;
|
||||
ses->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients",
|
||||
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load());
|
||||
send_drop_item_to_channel(s, ses->client_channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, ses->server_channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
res.item.id.load(), cmd.floor, cmd.pos.x.load(), cmd.pos.z.load());
|
||||
send_drop_item_to_channel(s, ses->client_channel, res.item, rec.obj_st ? 2 : 1, cmd.floor, cmd.pos, cmd.entity_index);
|
||||
send_drop_item_to_channel(s, ses->server_channel, res.item, rec.obj_st ? 2 : 1, cmd.floor, cmd.pos, cmd.entity_index);
|
||||
send_item_notification_if_needed(s, ses->client_channel, ses->config, res.item, res.is_from_rare_table);
|
||||
}
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
@@ -1144,7 +1151,7 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
const auto& cmd = check_size_t<G_AttackFinished_6x46>(data,
|
||||
offsetof(G_AttackFinished_6x46, targets),
|
||||
sizeof(G_AttackFinished_6x46));
|
||||
if (cmd.count > min<size_t>(cmd.header.size - 2, cmd.targets.size())) {
|
||||
if (cmd.target_count > min<size_t>(cmd.header.size - 2, cmd.targets.size())) {
|
||||
ses->log.warning("Blocking subcommand 6x46 with invalid count");
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
@@ -1426,18 +1433,25 @@ static HandlerResult S_13_A7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
|
||||
if (!sf->is_download && phosg::ends_with(sf->basename, ".dat")) {
|
||||
auto quest_dat_data = make_shared<std::string>(prs_decompress(sf->data));
|
||||
try {
|
||||
ses->map = Lobby::load_maps(
|
||||
ses->version(),
|
||||
ses->lobby_episode,
|
||||
auto map_file = make_shared<MapFile>(quest_dat_data);
|
||||
auto materialized_map_file = map_file->materialize_random_sections(ses->lobby_random_seed);
|
||||
|
||||
array<shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||
map_files.at(static_cast<size_t>(ses->version())) = materialized_map_file;
|
||||
auto supermap = make_shared<SuperMap>(ses->lobby_episode, map_files);
|
||||
|
||||
ses->map_state = make_shared<MapState>(
|
||||
ses->id,
|
||||
ses->lobby_difficulty,
|
||||
ses->lobby_event,
|
||||
ses->id,
|
||||
Map::DEFAULT_RARE_ENEMIES,
|
||||
ses->lobby_random_seed,
|
||||
MapState::DEFAULT_RARE_ENEMIES,
|
||||
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
||||
quest_dat_data);
|
||||
supermap);
|
||||
|
||||
} catch (const exception& e) {
|
||||
ses->log.warning("Failed to load quest map: %s", e.what());
|
||||
ses->map_state.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1578,7 +1592,7 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ProxyServer::LinkedSession> ses, u
|
||||
ses->lobby_episode = Episode::EP1;
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
ses->map_state.reset();
|
||||
|
||||
// This command can cause the client to no longer send D6 responses when
|
||||
// 1A/D5 large message boxes are closed. newserv keeps track of this
|
||||
@@ -1720,7 +1734,7 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
} else {
|
||||
ses->lobby_mode = GameMode::NORMAL;
|
||||
}
|
||||
ses->lobby_random_seed = cmd->rare_seed;
|
||||
ses->lobby_random_seed = cmd->random_seed;
|
||||
if (cmd_ep3) {
|
||||
ses->lobby_episode = Episode::EP3;
|
||||
} else {
|
||||
@@ -1730,27 +1744,24 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
if (ses->version() == Version::GC_NTE) {
|
||||
// GC NTE ignores the variations field entirely, so clear the array to
|
||||
// ensure we'll load the correct maps
|
||||
cmd->variations.clear(0);
|
||||
cmd->variations = Variations();
|
||||
}
|
||||
|
||||
// Recreate the item creator if needed, and load maps
|
||||
auto s = ses->require_server_state();
|
||||
ses->set_drop_mode(ses->drop_mode);
|
||||
if (!is_ep3(ses->version())) {
|
||||
ses->map = Lobby::load_maps(
|
||||
ses->version(),
|
||||
ses->lobby_episode,
|
||||
ses->lobby_mode,
|
||||
if (!is_ep3(ses->version()) && (ses->lobby_mode != GameMode::CHALLENGE)) {
|
||||
auto s = ses->require_server_state();
|
||||
ses->map_state = make_shared<MapState>(
|
||||
ses->id,
|
||||
ses->lobby_difficulty,
|
||||
ses->lobby_event,
|
||||
ses->id,
|
||||
s->set_data_table(ses->version(), ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty),
|
||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||
Map::DEFAULT_RARE_ENEMIES,
|
||||
ses->lobby_random_seed,
|
||||
MapState::DEFAULT_RARE_ENEMIES,
|
||||
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
||||
cmd->variations,
|
||||
&ses->log);
|
||||
s->supermaps_for_variations(ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty, cmd->variations));
|
||||
} else {
|
||||
ses->map_state.reset();
|
||||
}
|
||||
|
||||
bool modified = false;
|
||||
@@ -1785,7 +1796,7 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
modified = true;
|
||||
}
|
||||
if (ses->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||
cmd->rare_seed = ses->config.override_random_seed;
|
||||
cmd->random_seed = ses->config.override_random_seed;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
@@ -1812,7 +1823,7 @@ static HandlerResult S_E8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->lobby_episode = Episode::EP3;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
ses->map_state.reset();
|
||||
|
||||
bool modified = false;
|
||||
|
||||
@@ -1850,7 +1861,7 @@ static HandlerResult S_E8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
modified = true;
|
||||
}
|
||||
if (ses->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||
cmd.rare_seed = ses->config.override_random_seed;
|
||||
cmd.random_seed = ses->config.override_random_seed;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
@@ -1899,7 +1910,7 @@ static HandlerResult C_98(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
|
||||
ses->lobby_mode = GameMode::NORMAL;
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
ses->map_state.reset();
|
||||
|
||||
if (is_v3(ses->version()) || is_v4(ses->version())) {
|
||||
return C_GXB_61(ses, command, flag, data);
|
||||
@@ -1999,9 +2010,7 @@ constexpr on_command_t C_B_81 = &C_81<SC_SimpleMail_BB_81>;
|
||||
|
||||
template <typename CmdT>
|
||||
void C_6x_movement(shared_ptr<ProxyServer::LinkedSession> ses, const string& data) {
|
||||
const auto& cmd = check_size_t<CmdT>(data);
|
||||
ses->x = cmd.x;
|
||||
ses->z = cmd.z;
|
||||
ses->pos = check_size_t<CmdT>(data).pos;
|
||||
}
|
||||
|
||||
template <typename SendGuildCardCmdT>
|
||||
@@ -2030,19 +2039,22 @@ HandlerResult C_6x<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, u
|
||||
if (!data.empty()) {
|
||||
if ((data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
|
||||
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
|
||||
if (ses->map && (cmd.flags & 1) && (cmd.header.object_id != 0xFFFF)) {
|
||||
for (auto* door : ses->map->doors_for_switch_flag(cmd.switch_flag_floor, cmd.switch_flag_num)) {
|
||||
if (door->game_flags & 0x0001) {
|
||||
if (ses->map_state && (cmd.flags & 1) && (cmd.header.entity_id != 0xFFFF)) {
|
||||
auto door_states = ses->map_state->door_states_for_switch_flag(
|
||||
ses->version(), cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
for (auto& door_state : door_states) {
|
||||
if (door_state->game_flags & 0x0001) {
|
||||
continue;
|
||||
}
|
||||
door->game_flags |= 1;
|
||||
door_state->game_flags |= 1;
|
||||
|
||||
uint16_t object_index = ses->map_state->index_for_object_state(ses->version(), door_state);
|
||||
G_UpdateObjectState_6x0B cmd0B;
|
||||
cmd0B.header.subcommand = 0x0B;
|
||||
cmd0B.header.size = sizeof(cmd0B) / 4;
|
||||
cmd0B.header.client_id = door->object_id | 0x4000;
|
||||
cmd0B.flags = door->game_flags;
|
||||
cmd0B.object_index = door->object_id;
|
||||
cmd0B.header.entity_id = object_index | 0x4000;
|
||||
cmd0B.flags = door_state->game_flags;
|
||||
cmd0B.object_index = object_index;
|
||||
ses->client_channel.send(0x60, 0x00, &cmd0B, sizeof(cmd0B));
|
||||
ses->server_channel.send(0x60, 0x00, &cmd0B, sizeof(cmd0B));
|
||||
}
|
||||
@@ -2075,8 +2087,8 @@ HandlerResult C_6x<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, u
|
||||
} else if (data[0] == 0x40) {
|
||||
C_6x_movement<G_WalkToPosition_6x40>(ses, data);
|
||||
|
||||
} else if (data[0] == 0x42) {
|
||||
C_6x_movement<G_RunToPosition_6x42>(ses, data);
|
||||
} else if ((data[0] == 0x41) || (data[0] == 0x42)) {
|
||||
C_6x_movement<G_MoveToPosition_6x41_6x42>(ses, data);
|
||||
|
||||
} else if (data[0] == 0x48) {
|
||||
if (ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) {
|
||||
|
||||
@@ -547,8 +547,6 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
lobby_client_id(0),
|
||||
leader_client_id(0),
|
||||
floor(0),
|
||||
x(0.0),
|
||||
z(0.0),
|
||||
is_in_game(false),
|
||||
is_in_quest(false),
|
||||
lobby_event(0),
|
||||
|
||||
+2
-3
@@ -81,7 +81,7 @@ public:
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<std::string> quest_dat_data;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<Map> map;
|
||||
std::shared_ptr<MapState> map_state;
|
||||
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
@@ -95,8 +95,7 @@ public:
|
||||
size_t lobby_client_id;
|
||||
size_t leader_client_id;
|
||||
uint16_t floor;
|
||||
float x;
|
||||
float z;
|
||||
VectorXZF pos;
|
||||
bool is_in_game;
|
||||
bool is_in_quest;
|
||||
uint8_t lobby_event;
|
||||
|
||||
+98
-23
@@ -199,6 +199,7 @@ VersionedQuest::VersionedQuest(
|
||||
uint8_t language,
|
||||
std::shared_ptr<const std::string> bin_contents,
|
||||
std::shared_ptr<const std::string> dat_contents,
|
||||
std::shared_ptr<const MapFile> map_file,
|
||||
std::shared_ptr<const std::string> pvr_contents,
|
||||
std::shared_ptr<const BattleRules> battle_rules,
|
||||
ssize_t challenge_template_index,
|
||||
@@ -219,6 +220,7 @@ VersionedQuest::VersionedQuest(
|
||||
is_dlq_encoded(false),
|
||||
bin_contents(bin_contents),
|
||||
dat_contents(dat_contents),
|
||||
map_file(map_file),
|
||||
pvr_contents(pvr_contents),
|
||||
battle_rules(battle_rules),
|
||||
challenge_template_index(challenge_template_index),
|
||||
@@ -226,10 +228,6 @@ VersionedQuest::VersionedQuest(
|
||||
available_expression(available_expression),
|
||||
enabled_expression(enabled_expression) {
|
||||
|
||||
if (this->dat_contents) {
|
||||
this->dat_contents_decompressed = make_shared<string>(prs_decompress(*this->dat_contents));
|
||||
}
|
||||
|
||||
auto bin_decompressed = prs_decompress(*this->bin_contents);
|
||||
|
||||
switch (this->version) {
|
||||
@@ -390,12 +388,13 @@ Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
|
||||
joinable(initial_version->joinable),
|
||||
lock_status_register(initial_version->lock_status_register),
|
||||
name(initial_version->name),
|
||||
supermap(nullptr),
|
||||
battle_rules(initial_version->battle_rules),
|
||||
challenge_template_index(initial_version->challenge_template_index),
|
||||
description_flag(initial_version->description_flag),
|
||||
available_expression(initial_version->available_expression),
|
||||
enabled_expression(initial_version->enabled_expression) {
|
||||
this->versions.emplace(this->versions_key(initial_version->version, initial_version->language), initial_version);
|
||||
this->add_version(initial_version);
|
||||
}
|
||||
|
||||
uint32_t Quest::versions_key(Version v, uint8_t language) {
|
||||
@@ -449,6 +448,44 @@ void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
|
||||
this->versions.emplace(this->versions_key(vq->version, vq->language), vq);
|
||||
}
|
||||
|
||||
std::shared_ptr<const SuperMap> Quest::get_supermap(int64_t random_seed) const {
|
||||
if (this->supermap) {
|
||||
return this->supermap;
|
||||
}
|
||||
|
||||
bool save_to_cache = true;
|
||||
bool any_map_file_present = false;
|
||||
array<shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
|
||||
auto vq = this->version(v, 1);
|
||||
if (vq && vq->map_file) {
|
||||
auto map_file = vq->map_file;
|
||||
if (map_file->has_random_sections()) {
|
||||
if (random_seed < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
save_to_cache = false;
|
||||
map_file = map_file->materialize_random_sections(random_seed);
|
||||
}
|
||||
map_files.at(static_cast<size_t>(v)) = map_file;
|
||||
any_map_file_present = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!any_map_file_present) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto supermap = make_shared<SuperMap>(this->episode, map_files);
|
||||
if (save_to_cache) {
|
||||
this->supermap = supermap;
|
||||
}
|
||||
static_game_data_log.info("Constructed %s supermap for quest %" PRIu32 " (%s)",
|
||||
save_to_cache ? "cacheable" : "temporary", this->quest_number, this->name.c_str());
|
||||
|
||||
return supermap;
|
||||
}
|
||||
|
||||
bool Quest::has_version(Version v, uint8_t language) const {
|
||||
return this->versions.count(this->versions_key(v, language));
|
||||
}
|
||||
@@ -482,17 +519,22 @@ shared_ptr<const VersionedQuest> Quest::version(Version v, uint8_t language) con
|
||||
|
||||
QuestIndex::QuestIndex(
|
||||
const string& directory,
|
||||
std::shared_ptr<const QuestCategoryIndex> category_index,
|
||||
shared_ptr<const QuestCategoryIndex> category_index,
|
||||
bool is_ep3)
|
||||
: directory(directory),
|
||||
category_index(category_index) {
|
||||
|
||||
struct FileData {
|
||||
std::string filename;
|
||||
string filename;
|
||||
shared_ptr<const string> data;
|
||||
};
|
||||
struct DATFileData {
|
||||
string filename;
|
||||
shared_ptr<const string> data;
|
||||
shared_ptr<const MapFile> map_file;
|
||||
};
|
||||
map<string, FileData> bin_files;
|
||||
map<string, FileData> dat_files;
|
||||
map<string, DATFileData> dat_files;
|
||||
map<string, FileData> pvr_files;
|
||||
map<string, FileData> json_files;
|
||||
map<string, uint32_t> categories;
|
||||
@@ -519,6 +561,23 @@ QuestIndex::QuestIndex(
|
||||
}
|
||||
};
|
||||
|
||||
auto add_dat_file = [&](const string& basename, const string& filename, string&& value) {
|
||||
if (categories.emplace(basename, cat->category_id).first->second != cat->category_id) {
|
||||
throw runtime_error("file " + basename + " exists in multiple categories");
|
||||
}
|
||||
auto data_ptr = make_shared<string>(std::move(value));
|
||||
auto map_file = make_shared<MapFile>(make_shared<string>(prs_decompress(*data_ptr)));
|
||||
if (!dat_files.emplace(basename, DATFileData{filename, data_ptr, map_file}).second) {
|
||||
throw runtime_error("file " + basename + " already exists");
|
||||
}
|
||||
// There is a bug in the client that prevents quests from loading properly
|
||||
// if any file's size is a multiple of 0x400. See the comments on the 13
|
||||
// command in CommandFormats.hh for more details.
|
||||
if (!(data_ptr->size() & 0x3FF)) {
|
||||
data_ptr->push_back(0x00);
|
||||
}
|
||||
};
|
||||
|
||||
string cat_path = directory + "/" + cat->directory_name;
|
||||
if (!phosg::isdir(cat_path)) {
|
||||
static_game_data_log.warning("Quest category directory %s is missing; skipping it", cat_path.c_str());
|
||||
@@ -570,9 +629,9 @@ QuestIndex::QuestIndex(
|
||||
} else if (extension == "bind" || extension == "mnmd") {
|
||||
add_file(bin_files, file_basename, orig_filename, prs_compress_optimal(file_data), true);
|
||||
} else if (extension == "dat") {
|
||||
add_file(dat_files, file_basename, orig_filename, std::move(file_data), true);
|
||||
add_dat_file(file_basename, orig_filename, std::move(file_data));
|
||||
} else if (extension == "datd") {
|
||||
add_file(dat_files, file_basename, orig_filename, prs_compress_optimal(file_data), true);
|
||||
add_dat_file(file_basename, orig_filename, prs_compress_optimal(file_data));
|
||||
} else if (extension == "pvr") {
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(file_data), true);
|
||||
} else if (extension == "qst") {
|
||||
@@ -581,7 +640,7 @@ QuestIndex::QuestIndex(
|
||||
if (phosg::ends_with(it.first, ".bin")) {
|
||||
add_file(bin_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
} else if (phosg::ends_with(it.first, ".dat")) {
|
||||
add_file(dat_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
add_dat_file(file_basename, orig_filename, std::move(it.second));
|
||||
} else if (phosg::ends_with(it.first, ".pvr")) {
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
} else {
|
||||
@@ -655,7 +714,7 @@ QuestIndex::QuestIndex(
|
||||
uint8_t language = language_code_for_char(language_token[0]);
|
||||
|
||||
// Find the corresponding dat and pvr files
|
||||
const FileData* dat_filedata = nullptr;
|
||||
const DATFileData* dat_filedata = nullptr;
|
||||
const FileData* pvr_filedata = nullptr;
|
||||
if (!::is_ep3(version)) {
|
||||
// Look for dat and pvr files with the same basename as the bin file; if
|
||||
@@ -746,6 +805,7 @@ QuestIndex::QuestIndex(
|
||||
language,
|
||||
bin_filedata->data,
|
||||
dat_filedata ? dat_filedata->data : nullptr,
|
||||
dat_filedata ? dat_filedata->map_file : nullptr,
|
||||
pvr_filedata ? pvr_filedata->data : nullptr,
|
||||
battle_rules,
|
||||
challenge_template_index,
|
||||
@@ -796,6 +856,11 @@ QuestIndex::QuestIndex(
|
||||
static_game_data_log.warning("(%s) Failed to index quest file: (%s)", basename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// Create supermaps for all quests that need them (all non-Ep3 quests)
|
||||
for (const auto& it : this->quests_by_number) {
|
||||
it.second->get_supermap(-1);
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const Quest> QuestIndex::get(uint32_t quest_number) const {
|
||||
@@ -817,11 +882,11 @@ shared_ptr<const Quest> QuestIndex::get(const std::string& name) const {
|
||||
vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint16_t version_flags,
|
||||
IncludeCondition include_condition) const {
|
||||
vector<shared_ptr<const QuestCategoryIndex::Category>> ret;
|
||||
for (const auto& cat : this->category_index->categories) {
|
||||
if (cat->check_flag(menu_type) && !this->filter(episode, version, cat->category_id, include_condition, 1).empty()) {
|
||||
if (cat->check_flag(menu_type) && !this->filter(episode, version_flags, cat->category_id, include_condition, 1).empty()) {
|
||||
ret.emplace_back(cat);
|
||||
}
|
||||
}
|
||||
@@ -830,7 +895,7 @@ vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
||||
|
||||
vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filter(
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint16_t version_flags,
|
||||
uint32_t category_id,
|
||||
IncludeCondition include_condition,
|
||||
size_t limit) const {
|
||||
@@ -843,17 +908,27 @@ vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filt
|
||||
return ret;
|
||||
}
|
||||
for (auto it : category_it->second) {
|
||||
if (((effective_episode == Episode::NONE) || (it.second->episode == effective_episode)) &&
|
||||
it.second->has_version_any_language(version)) {
|
||||
IncludeState state = include_condition ? include_condition(it.second) : IncludeState::AVAILABLE;
|
||||
if (state == IncludeState::HIDDEN) {
|
||||
continue;
|
||||
}
|
||||
ret.emplace_back(make_pair(state, it.second));
|
||||
if (limit && (ret.size() >= limit)) {
|
||||
if ((effective_episode != Episode::NONE) && (it.second->episode != effective_episode)) {
|
||||
continue;
|
||||
}
|
||||
bool all_required_versions_present = true;
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
if ((version_flags & (1 << v_s)) && !it.second->has_version_any_language(static_cast<Version>(v_s))) {
|
||||
all_required_versions_present = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!all_required_versions_present) {
|
||||
continue;
|
||||
}
|
||||
IncludeState state = include_condition ? include_condition(it.second) : IncludeState::AVAILABLE;
|
||||
if (state == IncludeState::HIDDEN) {
|
||||
continue;
|
||||
}
|
||||
ret.emplace_back(make_pair(state, it.second));
|
||||
if (limit && (ret.size() >= limit)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
+19
-14
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "IntegralExpression.hh"
|
||||
#include "Map.hh"
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "StaticGameData.hh"
|
||||
@@ -63,25 +64,25 @@ struct QuestCategoryIndex {
|
||||
};
|
||||
|
||||
struct VersionedQuest {
|
||||
uint32_t quest_number;
|
||||
uint32_t category_id;
|
||||
Episode episode;
|
||||
bool allow_start_from_chat_command;
|
||||
bool joinable;
|
||||
int16_t lock_status_register;
|
||||
uint32_t quest_number = 0;
|
||||
uint32_t category_id = 0;
|
||||
Episode episode = Episode::NONE;
|
||||
bool allow_start_from_chat_command = false;
|
||||
bool joinable = false;
|
||||
int16_t lock_status_register = -1;
|
||||
std::string name;
|
||||
Version version;
|
||||
uint8_t language;
|
||||
bool is_dlq_encoded;
|
||||
Version version = Version::UNKNOWN;
|
||||
uint8_t language = 1;
|
||||
bool is_dlq_encoded = false;
|
||||
std::string short_description;
|
||||
std::string long_description;
|
||||
std::shared_ptr<const std::string> bin_contents;
|
||||
std::shared_ptr<const std::string> dat_contents;
|
||||
std::shared_ptr<const std::string> dat_contents_decompressed;
|
||||
std::shared_ptr<const MapFile> map_file;
|
||||
std::shared_ptr<const std::string> pvr_contents;
|
||||
std::shared_ptr<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
uint8_t description_flag;
|
||||
ssize_t challenge_template_index = -1;
|
||||
uint8_t description_flag = 0;
|
||||
std::shared_ptr<const IntegralExpression> available_expression;
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression;
|
||||
|
||||
@@ -92,6 +93,7 @@ struct VersionedQuest {
|
||||
uint8_t language,
|
||||
std::shared_ptr<const std::string> bin_contents,
|
||||
std::shared_ptr<const std::string> dat_contents,
|
||||
std::shared_ptr<const MapFile> map_file,
|
||||
std::shared_ptr<const std::string> pvr_contents,
|
||||
std::shared_ptr<const BattleRules> battle_rules = nullptr,
|
||||
ssize_t challenge_template_index = -1,
|
||||
@@ -120,6 +122,8 @@ public:
|
||||
Quest& operator=(const Quest&) = default;
|
||||
Quest& operator=(Quest&&) = default;
|
||||
|
||||
std::shared_ptr<const SuperMap> get_supermap(int64_t random_seed) const;
|
||||
|
||||
void add_version(std::shared_ptr<const VersionedQuest> vq);
|
||||
bool has_version(Version v, uint8_t language) const;
|
||||
bool has_version_any_language(Version v) const;
|
||||
@@ -134,6 +138,7 @@ public:
|
||||
bool joinable;
|
||||
int16_t lock_status_register;
|
||||
std::string name;
|
||||
mutable std::shared_ptr<const SuperMap> supermap;
|
||||
std::shared_ptr<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
uint8_t description_flag;
|
||||
@@ -165,11 +170,11 @@ struct QuestIndex {
|
||||
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint16_t version_flags,
|
||||
IncludeCondition include_condition = nullptr) const;
|
||||
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter(
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint16_t version_flags,
|
||||
uint32_t category_id,
|
||||
IncludeCondition include_condition = nullptr,
|
||||
size_t limit = 0) const;
|
||||
|
||||
+6
-13
@@ -59,13 +59,6 @@ using AttackData = BattleParamsIndex::AttackData;
|
||||
using ResistData = BattleParamsIndex::ResistData;
|
||||
using MovementData = BattleParamsIndex::MovementData;
|
||||
|
||||
struct Vector4F {
|
||||
le_float x;
|
||||
le_float y;
|
||||
le_float z;
|
||||
le_float t;
|
||||
} __packed_ws__(Vector4F, 0x10);
|
||||
|
||||
// bit_cast isn't in the standard place on macOS (it is apparently implicitly
|
||||
// included by resource_dasm, but newserv can be built without resource_dasm)
|
||||
// and I'm too lazy to go find the right header to include
|
||||
@@ -2213,7 +2206,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
|
||||
// valueD = loop flag (0 = no, 1 = yes)
|
||||
// regsE[0-2] = result point (x, y, z as floats)
|
||||
// regsE[3] = the result code (0 = failed, 1 = success)
|
||||
// labelF = control point entries (array of valueA Vector4F structures)
|
||||
// labelF = control point entries (array of valueA VectorXYZTF structures)
|
||||
{0xF8DB, "get_vector_from_path", "unknownF8DB", {INT32, FLOAT32, FLOAT32, INT32, {REG_SET_FIXED, 4}, SCRIPT16}, F_V3_V4 | F_ARGS},
|
||||
|
||||
// Same as npc_text, but only applies to a specific player slot.
|
||||
@@ -2309,7 +2302,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
|
||||
// valueD = loop flag (0 = no, 1 = yes)
|
||||
// regsE[0-2] = result point (x, y, z as floats)
|
||||
// regsE[3] = the result code (0 = failed, 1 = success)
|
||||
// labelF = control point entries (array of valueA Vector4F structures)
|
||||
// labelF = control point entries (array of valueA VectorXYZTF structures)
|
||||
{0xF8F2, "compute_bezier_curve_point", "load_unk_data", {INT32, FLOAT32, FLOAT32, INT32, {REG_SET_FIXED, 4}, {LABEL16, Arg::DataType::BEZIER_CONTROL_POINT_DATA}}, F_V3_V4 | F_ARGS},
|
||||
|
||||
// Creates a timed particle effect. Like the particle opcode, but the
|
||||
@@ -3626,11 +3619,11 @@ std::string disassemble_quest_script(
|
||||
}
|
||||
if (l->has_data_type(Arg::DataType::BEZIER_CONTROL_POINT_DATA)) {
|
||||
phosg::StringReader r = cmd_r.sub(l->offset, size);
|
||||
lines.emplace_back(" // As Vector4F");
|
||||
while (r.remaining() >= sizeof(Vector4F)) {
|
||||
lines.emplace_back(" // As VectorXYZTF");
|
||||
while (r.remaining() >= sizeof(VectorXYZTF)) {
|
||||
size_t offset = l->offset + cmd_r.where();
|
||||
const auto& e = r.get<Vector4F>();
|
||||
lines.emplace_back(phosg::string_printf(" %04zX vector4f x=%g, y=%g, z=%g, t=%g", offset, e.x.load(), e.y.load(), e.z.load(), e.t.load()));
|
||||
const auto& e = r.get<VectorXYZTF>();
|
||||
lines.emplace_back(phosg::string_printf(" %04zX vector x=%g, y=%g, z=%g, t=%g", offset, e.x.load(), e.y.load(), e.z.load(), e.t.load()));
|
||||
}
|
||||
if (r.remaining() > 0) {
|
||||
size_t struct_end_offset = l->offset + r.where();
|
||||
|
||||
+10
-10
@@ -5,6 +5,7 @@
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "BattleParamsIndex.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
@@ -94,8 +95,8 @@ RareItemSet::ExpandedDrop RareItemSet::ParsedRELData::PackedDrop::expand() const
|
||||
|
||||
template <bool BE>
|
||||
void RareItemSet::ParsedRELData::parse_t(phosg::StringReader r, bool is_v1) {
|
||||
uint32_t root_offset = r.pget<U32T<BE>>(r.size() - 0x10);
|
||||
const auto& root = r.pget<OffsetsT<BE>>(root_offset);
|
||||
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
|
||||
const auto& root = r.pget<OffsetsT<BE>>(footer.root_offset);
|
||||
|
||||
phosg::StringReader monsters_r = r.sub(root.monster_rares_offset);
|
||||
for (size_t z = 0; z < (is_v1 ? 0x33 : 0x65); z++) {
|
||||
@@ -160,14 +161,13 @@ std::string RareItemSet::ParsedRELData::serialize_t(bool is_v1) const {
|
||||
while (w.size() & 0x1F) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
w.put<U32T<BE>>(relocations_offset);
|
||||
w.put<U32T<BE>>(3); // num_relocations
|
||||
w.put<U32T<BE>>(1); // TODO: What is this used for?
|
||||
w.put<U32T<BE>>(0);
|
||||
w.put<U32T<BE>>(root_offset);
|
||||
w.put<U32T<BE>>(0);
|
||||
w.put<U32T<BE>>(0);
|
||||
w.put<U32T<BE>>(0);
|
||||
|
||||
RELFileFooterT<BE> footer;
|
||||
footer.relocations_offset = relocations_offset;
|
||||
footer.num_relocations = 3;
|
||||
footer.unused1[0] = 1; // TODO: What is this used for?
|
||||
footer.root_offset = root_offset;
|
||||
w.put<RELFileFooterT<BE>>(footer);
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
|
||||
+130
-142
@@ -1393,7 +1393,7 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr<Lobby> l, int16_t ta
|
||||
// Figure out which clients are at this table. If any client has declined, we
|
||||
// never start a match, but we may start a match even if all clients have not
|
||||
// yet accepted (in case of a tournament match).
|
||||
Version base_version = Version::UNKNOWN;
|
||||
Version game_version = Version::UNKNOWN;
|
||||
unordered_map<size_t, shared_ptr<Client>> table_clients;
|
||||
bool all_clients_accepted = true;
|
||||
for (const auto& c : l->clients) {
|
||||
@@ -1401,9 +1401,9 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr<Lobby> l, int16_t ta
|
||||
continue;
|
||||
}
|
||||
// Prevent match from starting unless all players are on the same version
|
||||
if (base_version == Version::UNKNOWN) {
|
||||
base_version = c->version();
|
||||
} else if (base_version != c->version()) {
|
||||
if (game_version == Version::UNKNOWN) {
|
||||
game_version = c->version();
|
||||
} else if (game_version != c->version()) {
|
||||
return false;
|
||||
}
|
||||
if (c->card_battle_table_seat_number >= 4) {
|
||||
@@ -1920,10 +1920,31 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
for (size_t x = 0; x < game->max_clients; x++) {
|
||||
const auto& game_c = game->clients[x];
|
||||
if (game_c.get()) {
|
||||
static_assert(NUM_VERSIONS == 14, "Don\'t forget to update the game player listing version tokens");
|
||||
static const array<const char*, NUM_VERSIONS> version_tokens = {
|
||||
" $C4P2$C7",
|
||||
" $C4P4$C7",
|
||||
" $C5DCN$C7",
|
||||
" $C5DCP$C7",
|
||||
" $C2DC1$C7",
|
||||
" $C2DC2$C7",
|
||||
" $C5PCN$C7",
|
||||
" $C2PC$C7",
|
||||
" $C5GCN$C7",
|
||||
" $C2GC$C7",
|
||||
" $C5Ep3N$C7",
|
||||
" $C2Ep3$C7",
|
||||
" $C2XB$C7",
|
||||
" $C2BB$C7",
|
||||
};
|
||||
const char* version_token = (game_c->version() != c->version())
|
||||
? version_tokens.at(static_cast<size_t>(game_c->version()))
|
||||
: "";
|
||||
auto player = game_c->character();
|
||||
string name = escape_player_name(player->disp.name.decode(game_c->language()));
|
||||
info += phosg::string_printf("%s\n %s Lv%" PRIu32 " %c\n",
|
||||
info += phosg::string_printf("%s%s\n %s Lv%" PRIu32 " %c\n",
|
||||
name.c_str(),
|
||||
version_token,
|
||||
name_for_char_class(player->disp.visual.char_class),
|
||||
player->disp.stats.level + 1,
|
||||
char_for_language_code(game_c->language()));
|
||||
@@ -1942,10 +1963,6 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
info += phosg::string_printf("Req. level: %" PRIu32 "+\n", game->min_level + 1);
|
||||
}
|
||||
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
info += phosg::string_printf("%s\n", phosg::name_for_enum(game->base_version));
|
||||
}
|
||||
|
||||
if (game->check_flag(Lobby::Flag::CHEATS_ENABLED)) {
|
||||
info += "$C6Cheats enabled$C7\n";
|
||||
}
|
||||
@@ -2080,15 +2097,10 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
|
||||
throw logic_error("on_quest_loaded called without a quest loaded");
|
||||
}
|
||||
|
||||
auto s = l->require_server_state();
|
||||
// Replace the free-play map with the quest's map
|
||||
l->load_maps();
|
||||
|
||||
// For BB Challenge quests, don't replace the map now - the leader will send
|
||||
// an 02DF command to create overlays, which also replaces the map. (We do
|
||||
// this because 02DF is also sent when a challenge is failed and retried,
|
||||
// which reloads the map and recreates character overlays anyway.)
|
||||
if ((l->base_version != Version::BB_V4) || (l->quest->challenge_template_index < 0)) {
|
||||
l->load_maps();
|
||||
}
|
||||
auto s = l->require_server_state();
|
||||
|
||||
// Delete all floor items
|
||||
for (auto& m : l->floor_item_managers) {
|
||||
@@ -2100,26 +2112,20 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((lc->version() == Version::BB_V4) && l->map) {
|
||||
send_rare_enemy_index_list(lc, l->map->rare_enemy_indexes);
|
||||
if (lc->version() == Version::BB_V4) {
|
||||
send_rare_enemy_index_list(lc, l->map_state->bb_rare_enemy_indexes);
|
||||
}
|
||||
|
||||
// On non-BB versions, overlays are created when the quest starts because
|
||||
// the server is not informed when the clients have replaced their player
|
||||
// data. On BB, this is instead done in the 6xCF handler (for battle) or
|
||||
// the 02DF handler (for challenge).
|
||||
if (l->base_version != Version::BB_V4) {
|
||||
lc->delete_overlay();
|
||||
if (l->quest->battle_rules) {
|
||||
lc->use_default_bank();
|
||||
lc->create_battle_overlay(l->quest->battle_rules, s->level_table(lc->version()));
|
||||
lc->log.info("Created battle overlay");
|
||||
} else if (l->quest->challenge_template_index >= 0) {
|
||||
lc->use_default_bank();
|
||||
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version()));
|
||||
lc->log.info("Created challenge overlay");
|
||||
l->assign_inventory_and_bank_item_ids(lc, true);
|
||||
}
|
||||
lc->delete_overlay();
|
||||
if (l->quest->battle_rules) {
|
||||
lc->use_default_bank();
|
||||
lc->create_battle_overlay(l->quest->battle_rules, s->level_table(lc->version()));
|
||||
lc->log.info("Created battle overlay");
|
||||
} else if (l->quest->challenge_template_index >= 0) {
|
||||
lc->use_default_bank();
|
||||
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version()));
|
||||
lc->log.info("Created challenge overlay");
|
||||
l->assign_inventory_and_bank_item_ids(lc, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2151,12 +2157,10 @@ void set_lobby_quest(shared_ptr<Lobby> l, shared_ptr<const Quest> q, bool substi
|
||||
l->clear_flag(Lobby::Flag::PERSISTENT);
|
||||
|
||||
l->quest = q;
|
||||
if (!is_ep3(l->base_version)) {
|
||||
if (l->episode != Episode::EP3) {
|
||||
l->episode = q->episode;
|
||||
}
|
||||
if (l->item_creator) {
|
||||
l->create_item_creator();
|
||||
}
|
||||
l->create_item_creator();
|
||||
|
||||
// There is no such thing as command AC on PSO V1 and V2 - quests just start
|
||||
// immediately when they're done downloading. (This is also the case on V3
|
||||
@@ -2296,9 +2300,10 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
// online quests, but they're served via a server data request
|
||||
// instead of the file download paradigm that other versions use.)
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
const auto& categories = quest_index->categories(menu_type, Episode::EP3, c->version());
|
||||
uint16_t version_flags = (1 << static_cast<size_t>(c->version()));
|
||||
const auto& categories = quest_index->categories(menu_type, Episode::EP3, version_flags);
|
||||
if (categories.size() == 1) {
|
||||
auto quests = quest_index->filter(Episode::EP3, c->version(), categories[0]->category_id);
|
||||
auto quests = quest_index->filter(Episode::EP3, version_flags, categories[0]->category_id);
|
||||
send_quest_menu(c, quests, true);
|
||||
break;
|
||||
}
|
||||
@@ -2618,12 +2623,13 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
|
||||
shared_ptr<Lobby> l = c->lobby.lock();
|
||||
Episode episode = l ? l->episode : Episode::NONE;
|
||||
uint16_t version_flags = (1 << static_cast<size_t>(c->version())) | (l ? l->quest_version_flags() : 0);
|
||||
QuestIndex::IncludeCondition include_condition = nullptr;
|
||||
if (l && !c->login->account->check_flag(Account::Flag::DISABLE_QUEST_REQUIREMENTS)) {
|
||||
include_condition = l->quest_include_condition();
|
||||
}
|
||||
|
||||
const auto& quests = quest_index->filter(episode, c->version(), item_id, include_condition);
|
||||
const auto& quests = quest_index->filter(episode, version_flags, item_id, include_condition);
|
||||
send_quest_menu(c, quests, !l);
|
||||
break;
|
||||
}
|
||||
@@ -3944,7 +3950,7 @@ static void on_DF_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
|
||||
}
|
||||
}
|
||||
|
||||
l->load_maps();
|
||||
l->map_state->reset();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4286,7 +4292,7 @@ static void on_C9_XB(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data
|
||||
|
||||
shared_ptr<Lobby> create_game_generic(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<Client> creator_c,
|
||||
const std::string& name,
|
||||
const std::string& password,
|
||||
Episode episode,
|
||||
@@ -4307,86 +4313,68 @@ shared_ptr<Lobby> create_game_generic(
|
||||
throw invalid_argument("incorrect difficulty level");
|
||||
}
|
||||
|
||||
auto current_lobby = c->require_lobby();
|
||||
auto current_lobby = creator_c->require_lobby();
|
||||
|
||||
size_t min_level = s->default_min_level_for_game(c->version(), episode, difficulty);
|
||||
size_t min_level = s->default_min_level_for_game(creator_c->version(), episode, difficulty);
|
||||
|
||||
auto p = c->character();
|
||||
if (!c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
|
||||
auto p = creator_c->character();
|
||||
if (!creator_c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
|
||||
// Note: We don't throw here because this is a situation players might
|
||||
// actually encounter while playing the game normally
|
||||
string msg = phosg::string_printf("You must be level %zu\nor above to play\nthis difficulty.", static_cast<size_t>(min_level + 1));
|
||||
send_lobby_message_box(c, msg);
|
||||
send_lobby_message_box(creator_c, msg);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<Lobby> game = s->create_lobby(true);
|
||||
game->name = name;
|
||||
game->base_version = c->version();
|
||||
game->allowed_versions = 0;
|
||||
switch (game->base_version) {
|
||||
case Version::DC_NTE:
|
||||
game->allow_version(Version::DC_NTE);
|
||||
break;
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
game->allow_version(Version::DC_V1_11_2000_PROTOTYPE);
|
||||
break;
|
||||
case Version::DC_V1:
|
||||
game->allow_version(Version::DC_V1);
|
||||
game->allow_version(Version::DC_V2);
|
||||
if (s->allow_dc_pc_games) {
|
||||
game->allow_version(Version::PC_V2);
|
||||
}
|
||||
break;
|
||||
case Version::DC_V2:
|
||||
if (allow_v1 && (difficulty <= 2) && (mode == GameMode::NORMAL)) {
|
||||
game->allow_version(Version::DC_V1);
|
||||
}
|
||||
game->allow_version(Version::DC_V2);
|
||||
if (s->allow_dc_pc_games) {
|
||||
game->allow_version(Version::PC_V2);
|
||||
}
|
||||
break;
|
||||
case Version::PC_NTE:
|
||||
game->allow_version(Version::PC_NTE);
|
||||
break;
|
||||
case Version::PC_V2:
|
||||
game->allow_version(Version::PC_V2);
|
||||
if (s->allow_dc_pc_games) {
|
||||
game->allow_version(Version::DC_V2);
|
||||
if (allow_v1 && (difficulty <= 2) && (mode == GameMode::NORMAL)) {
|
||||
game->allow_version(Version::DC_V1);
|
||||
game->episode = episode;
|
||||
game->mode = mode;
|
||||
game->difficulty = difficulty;
|
||||
game->allowed_versions = s->compatibility_groups.at(static_cast<size_t>(creator_c->version()));
|
||||
static_assert(NUM_VERSIONS == 14, "Don't forget to update the group compatibility restrictions");
|
||||
if (!allow_v1 || (difficulty > 2) || (mode != GameMode::NORMAL)) {
|
||||
game->forbid_version(Version::DC_NTE);
|
||||
game->forbid_version(Version::DC_V1_11_2000_PROTOTYPE);
|
||||
game->forbid_version(Version::DC_V1);
|
||||
}
|
||||
switch (game->episode) {
|
||||
case Episode::NONE:
|
||||
throw logic_error("game episode not set at creation time");
|
||||
case Episode::EP1:
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
if (is_ep3(v)) {
|
||||
game->forbid_version(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
game->allow_version(Version::GC_NTE);
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
game->allow_version(Version::GC_V3);
|
||||
if (s->allow_gc_xb_games) {
|
||||
game->allow_version(Version::XB_V3);
|
||||
case Episode::EP2:
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
if (!is_v3(v) || is_ep3(v)) {
|
||||
game->forbid_version(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
game->allow_version(Version::GC_EP3_NTE);
|
||||
break;
|
||||
case Version::GC_EP3:
|
||||
game->allow_version(Version::GC_EP3);
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
game->allow_version(Version::XB_V3);
|
||||
if (s->allow_gc_xb_games) {
|
||||
game->allow_version(Version::GC_V3);
|
||||
case Episode::EP3:
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
if (!is_ep3(v)) {
|
||||
game->forbid_version(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
game->allow_version(Version::BB_V4);
|
||||
case Episode::EP4:
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
if (!is_v4(v)) {
|
||||
game->forbid_version(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid quest script version");
|
||||
}
|
||||
if (c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION)) {
|
||||
|
||||
if (creator_c->login->account->check_flag(Account::Flag::DEBUG)) {
|
||||
game->set_flag(Lobby::Flag::DEBUG);
|
||||
}
|
||||
if (creator_c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION)) {
|
||||
game->set_flag(Lobby::Flag::IS_CLIENT_CUSTOMIZATION);
|
||||
}
|
||||
|
||||
@@ -4409,15 +4397,12 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->password = password;
|
||||
|
||||
game->creator_section_id = p->disp.visual.section_id;
|
||||
game->override_section_id = c->config.override_section_id;
|
||||
game->episode = episode;
|
||||
game->mode = mode;
|
||||
game->override_section_id = creator_c->config.override_section_id;
|
||||
if (game->mode == GameMode::CHALLENGE) {
|
||||
game->challenge_params = make_shared<Lobby::ChallengeParameters>();
|
||||
}
|
||||
game->difficulty = difficulty;
|
||||
if (c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||
game->random_seed = c->config.override_random_seed;
|
||||
if (creator_c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||
game->random_seed = creator_c->config.override_random_seed;
|
||||
game->opt_rand_crypt = make_shared<PSOV2Encryption>(game->random_seed);
|
||||
}
|
||||
if (battle_player) {
|
||||
@@ -4428,7 +4413,7 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->exp_share_multiplier = s->exp_share_multiplier;
|
||||
|
||||
const unordered_map<uint16_t, IntegralExpression>* quest_flag_rewrites;
|
||||
switch (game->base_version) {
|
||||
switch (creator_c->version()) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
@@ -4437,13 +4422,13 @@ shared_ptr<Lobby> create_game_generic(
|
||||
case Version::PC_V2:
|
||||
quest_flag_rewrites = &s->quest_flag_rewrites_v1_v2;
|
||||
if (game->mode == GameMode::BATTLE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v1_v2_battle);
|
||||
game->drop_mode = s->default_drop_mode_v1_v2_battle;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_battle;
|
||||
} else if (game->mode == GameMode::CHALLENGE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v1_v2_challenge);
|
||||
game->drop_mode = s->default_drop_mode_v1_v2_challenge;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_challenge;
|
||||
} else {
|
||||
game->set_drop_mode(s->default_drop_mode_v1_v2_normal);
|
||||
game->drop_mode = s->default_drop_mode_v1_v2_normal;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_normal;
|
||||
}
|
||||
break;
|
||||
@@ -4452,32 +4437,32 @@ shared_ptr<Lobby> create_game_generic(
|
||||
case Version::XB_V3:
|
||||
quest_flag_rewrites = &s->quest_flag_rewrites_v3;
|
||||
if (game->mode == GameMode::BATTLE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v3_battle);
|
||||
game->drop_mode = s->default_drop_mode_v3_battle;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v3_battle;
|
||||
} else if (game->mode == GameMode::CHALLENGE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v3_challenge);
|
||||
game->drop_mode = s->default_drop_mode_v3_challenge;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v3_challenge;
|
||||
} else {
|
||||
game->set_drop_mode(s->default_drop_mode_v3_normal);
|
||||
game->drop_mode = s->default_drop_mode_v3_normal;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v3_normal;
|
||||
}
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
quest_flag_rewrites = nullptr;
|
||||
game->set_drop_mode(Lobby::DropMode::DISABLED);
|
||||
game->drop_mode = Lobby::DropMode::DISABLED;
|
||||
game->allowed_drop_modes = (1 << static_cast<size_t>(game->drop_mode));
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
quest_flag_rewrites = &s->quest_flag_rewrites_v4;
|
||||
if (game->mode == GameMode::BATTLE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v4_battle);
|
||||
game->drop_mode = s->default_drop_mode_v4_battle;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v4_battle;
|
||||
} else if (game->mode == GameMode::CHALLENGE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v4_challenge);
|
||||
game->drop_mode = s->default_drop_mode_v4_challenge;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v4_challenge;
|
||||
} else {
|
||||
game->set_drop_mode(s->default_drop_mode_v4_normal);
|
||||
game->drop_mode = s->default_drop_mode_v4_normal;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v4_normal;
|
||||
}
|
||||
// Disallow CLIENT mode on BB
|
||||
@@ -4491,6 +4476,7 @@ shared_ptr<Lobby> create_game_generic(
|
||||
default:
|
||||
throw logic_error("invalid quest script version");
|
||||
}
|
||||
game->create_item_creator(creator_c->version());
|
||||
|
||||
game->event = current_lobby->event;
|
||||
game->block = 0xFF;
|
||||
@@ -4512,23 +4498,27 @@ shared_ptr<Lobby> create_game_generic(
|
||||
|
||||
if (game->episode != Episode::EP3) {
|
||||
// GC NTE ignores the passed-in variations and always uses all zeroes
|
||||
if (game->base_version == Version::GC_NTE) {
|
||||
game->variations.clear(0);
|
||||
} else if (c->override_variations) {
|
||||
game->variations = *c->override_variations;
|
||||
c->override_variations.reset();
|
||||
if (creator_c->version() == Version::GC_NTE) {
|
||||
game->variations = Variations();
|
||||
game->log.info("Base version is GC_NTE; using blank variations");
|
||||
} else if (creator_c->override_variations) {
|
||||
game->variations = *creator_c->override_variations;
|
||||
creator_c->override_variations.reset();
|
||||
auto vars_str = game->variations.str();
|
||||
game->log.info("Using variations from client override: %s", vars_str.c_str());
|
||||
} else {
|
||||
auto sdt = s->set_data_table(game->base_version, game->episode, game->mode, game->difficulty);
|
||||
auto sdt = s->set_data_table(creator_c->version(), game->episode, game->mode, game->difficulty);
|
||||
game->variations = sdt->generate_variations(game->episode, is_solo, game->opt_rand_crypt);
|
||||
auto vars_str = game->variations.str();
|
||||
game->log.info("Using random variations: %s", vars_str.c_str());
|
||||
}
|
||||
game->load_maps();
|
||||
} else {
|
||||
game->variations.clear(0);
|
||||
game->map = make_shared<Map>(game->base_version, game->lobby_id, game->random_seed, game->opt_rand_crypt);
|
||||
game->variations = Variations();
|
||||
}
|
||||
game->load_maps(); // Load free-play maps
|
||||
|
||||
// The game's quest flags are inherited from the creator, if known
|
||||
if (c->version() == Version::BB_V4) {
|
||||
if (creator_c->version() == Version::BB_V4) {
|
||||
game->quest_flag_values = make_unique<QuestFlags>(p->quest_flags);
|
||||
game->quest_flags_known = nullptr;
|
||||
} else {
|
||||
@@ -4540,10 +4530,10 @@ shared_ptr<Lobby> create_game_generic(
|
||||
IntegralExpression::Env env = {
|
||||
.flags = &p->quest_flags.data.at(difficulty),
|
||||
.challenge_records = &p->challenge_records,
|
||||
.team = c->team(),
|
||||
.team = creator_c->team(),
|
||||
.num_players = 1,
|
||||
.event = game->event,
|
||||
.v1_present = is_v1(game->base_version),
|
||||
.v1_present = is_v1(creator_c->version()),
|
||||
};
|
||||
for (const auto& it : *quest_flag_rewrites) {
|
||||
bool should_set = it.second.evaluate(env);
|
||||
@@ -4557,7 +4547,7 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->quest_flags_known->set(game->difficulty, it.first);
|
||||
}
|
||||
}
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
creator_c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
}
|
||||
|
||||
game->switch_flags = make_unique<SwitchFlags>();
|
||||
@@ -4761,11 +4751,11 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
|
||||
send_server_time(c);
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
string variations_str;
|
||||
for (size_t z = 0; z < l->variations.size(); z++) {
|
||||
variations_str += phosg::string_printf("%" PRIX32, l->variations[z].load());
|
||||
for (size_t z = 0; z < l->variations.entries.size(); z++) {
|
||||
const auto& e = l->variations.entries[z];
|
||||
variations_str += phosg::string_printf(" %" PRIX32 "%" PRIX32, e.layout.load(), e.entities.load());
|
||||
}
|
||||
send_text_message_printf(c, "Rare seed: %08" PRIX32 "\nRare enemies: %zu\nVariations: %s\n",
|
||||
l->random_seed, l->map->rare_enemy_indexes.size(), variations_str.c_str());
|
||||
send_text_message_printf(c, "Rare seed: %08" PRIX32 "\nVariations:%s\n", l->random_seed, variations_str.c_str());
|
||||
}
|
||||
|
||||
bool should_resume_game = true;
|
||||
@@ -4800,9 +4790,7 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
|
||||
c->config.clear_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
|
||||
c->log.info("LOADING_RUNNING_JOINABLE_QUEST flag cleared");
|
||||
}
|
||||
if (l->map) {
|
||||
send_rare_enemy_index_list(c, l->map->rare_enemy_indexes);
|
||||
}
|
||||
send_rare_enemy_index_list(c, l->map_state->bb_rare_enemy_indexes);
|
||||
}
|
||||
|
||||
// We should resume the game if:
|
||||
|
||||
+1569
-1270
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,11 @@
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Map.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_subcommand_multi(std::shared_ptr<Client> c, uint8_t command, uint8_t flag, std::string& data);
|
||||
bool subcommand_is_implemented(uint8_t which);
|
||||
|
||||
void send_item_notification_if_needed(
|
||||
std::shared_ptr<ServerState> s,
|
||||
@@ -21,8 +21,9 @@ void send_item_notification_if_needed(
|
||||
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
|
||||
|
||||
struct DropReconcileResult {
|
||||
std::shared_ptr<MapState::ObjectState> obj_st;
|
||||
std::shared_ptr<MapState::EnemyState> ene_st;
|
||||
uint8_t effective_rt_index;
|
||||
bool is_box;
|
||||
bool should_drop;
|
||||
bool ignore_def;
|
||||
};
|
||||
@@ -31,10 +32,10 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
phosg::PrefixedLogger& log,
|
||||
Channel& client_channel,
|
||||
G_SpecializableItemDropRequest_6xA2& cmd,
|
||||
Version version,
|
||||
Episode episode,
|
||||
uint8_t event,
|
||||
const Client::Config& config,
|
||||
std::shared_ptr<Map> map,
|
||||
std::shared_ptr<MapState> map,
|
||||
bool mark_drop);
|
||||
|
||||
class Parsed6x70Data {
|
||||
|
||||
+10
-10
@@ -286,19 +286,19 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
case 0x64:
|
||||
if ((version == Version::PC_NTE) || (version == Version::PC_V2)) {
|
||||
auto& mask = check_size_t<S_JoinGame_PC_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
mask.variations = Variations();
|
||||
mask.random_seed = 0;
|
||||
} else if (version == Version::XB_V3) {
|
||||
auto& mask = check_size_t<S_JoinGame_XB_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
mask.variations = Variations();
|
||||
mask.random_seed = 0;
|
||||
} else if (version == Version::DC_NTE || version == Version::DC_V1_11_2000_PROTOTYPE) {
|
||||
auto& mask = check_size_t<S_JoinGame_DCNTE_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
mask.variations = Variations();
|
||||
} else {
|
||||
auto& mask = check_size_t<S_JoinGame_DC_64>(mask_data, mask_size, sizeof(S_JoinGame_Ep3_64));
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
mask.variations = Variations();
|
||||
mask.random_seed = 0;
|
||||
for (size_t offset = sizeof(S_JoinGame_GC_64) +
|
||||
offsetof(S_JoinGame_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum);
|
||||
offset + 4 <= mask_size;
|
||||
@@ -348,7 +348,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
case 0xE8:
|
||||
if (is_gc(version)) {
|
||||
auto& mask = check_size_t<S_JoinSpectatorTeam_Ep3_E8>(mask_data, mask_size);
|
||||
mask.rare_seed = 0;
|
||||
mask.random_seed = 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
mask.players[z].disp.visual.name_color_checksum = 0;
|
||||
}
|
||||
@@ -430,8 +430,8 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
}
|
||||
case 0x0064: {
|
||||
auto& mask = check_size_t<S_JoinGame_BB_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
mask.variations = Variations();
|
||||
mask.random_seed = 0;
|
||||
break;
|
||||
}
|
||||
case 0x00B1: {
|
||||
|
||||
+166
-97
@@ -520,8 +520,7 @@ bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("CallProtectedHandler", c->config.specific_version);
|
||||
uint32_t size_label_value = is_big_endian(c->version()) ? data.size() : phosg::bswap32(data.size());
|
||||
send_function_call(c, fn, {{"size", size_label_value}}, data.data(), data.size());
|
||||
send_function_call(c, fn, {{"size", data.size()}}, data.data(), data.size());
|
||||
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
|
||||
if (echo_to_lobby) {
|
||||
auto l = c->lobby.lock();
|
||||
@@ -1627,8 +1626,14 @@ void send_quest_categories_menu_t(
|
||||
include_condition = l ? l->quest_include_condition() : nullptr;
|
||||
}
|
||||
|
||||
uint16_t version_flags = (1 << static_cast<size_t>(c->version()));
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
version_flags |= l->quest_version_flags();
|
||||
}
|
||||
|
||||
vector<EntryT> entries;
|
||||
for (const auto& cat : quest_index->categories(menu_type, episode, c->version(), include_condition)) {
|
||||
for (const auto& cat : quest_index->categories(menu_type, episode, version_flags, include_condition)) {
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = cat->use_ep2_icon() ? MenuID::QUEST_CATEGORIES_EP2 : MenuID::QUEST_CATEGORIES_EP1;
|
||||
e.item_id = cat->category_id;
|
||||
@@ -1806,11 +1811,11 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
||||
|
||||
S_JoinSpectatorTeam_Ep3_E8 cmd;
|
||||
|
||||
cmd.variations.clear(0);
|
||||
cmd.variations = Variations();
|
||||
cmd.client_id = c->lobby_client_id;
|
||||
cmd.event = l->event;
|
||||
cmd.section_id = l->effective_section_id();
|
||||
cmd.rare_seed = l->random_seed;
|
||||
cmd.random_seed = l->random_seed;
|
||||
cmd.episode = 0xFF;
|
||||
|
||||
uint8_t player_count = 0;
|
||||
@@ -1953,7 +1958,7 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
cmd.event = l->event;
|
||||
cmd.section_id = l->effective_section_id();
|
||||
cmd.challenge_mode = (l->mode == GameMode::CHALLENGE) ? 1 : 0;
|
||||
cmd.rare_seed = l->random_seed;
|
||||
cmd.random_seed = l->random_seed;
|
||||
return populate_lobby_data(cmd);
|
||||
};
|
||||
auto populate_v3_cmd = [&](auto& cmd) -> size_t {
|
||||
@@ -2568,7 +2573,6 @@ void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private)
|
||||
|
||||
void send_warp(shared_ptr<Client> c, uint32_t floor, bool is_private) {
|
||||
send_warp(c->channel, c->lobby_client_id, floor, is_private);
|
||||
c->floor = floor;
|
||||
}
|
||||
|
||||
void send_warp(shared_ptr<Lobby> l, uint32_t floor, bool is_private) {
|
||||
@@ -2587,6 +2591,10 @@ void send_ep3_change_music(Channel& ch, uint32_t song) {
|
||||
void send_game_join_sync_command(
|
||||
shared_ptr<Client> c, const void* data, size_t size, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc) {
|
||||
string compressed_data = bc0_compress(data, size);
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
c->log.info("Compressed sync data from (%zX -> %zX bytes):", size, compressed_data.size());
|
||||
phosg::print_data(stderr, data, size);
|
||||
}
|
||||
send_game_join_sync_command_compressed(c, compressed_data.data(), compressed_data.size(), size, dc_nte_sc, dc_11_2000_sc, sc);
|
||||
}
|
||||
|
||||
@@ -2639,6 +2647,9 @@ void send_game_join_sync_command_compressed(
|
||||
|
||||
void send_game_item_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
throw logic_error("cannot send item state in non-game lobby");
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
phosg::StringWriter floor_items_w;
|
||||
|
||||
@@ -2652,7 +2663,7 @@ void send_game_item_state(shared_ptr<Client> c) {
|
||||
decompressed_header.next_item_id_per_player[2].load(),
|
||||
decompressed_header.next_item_id_per_player[3].load());
|
||||
|
||||
for (size_t floor = 0; floor < 0x10; floor++) {
|
||||
for (size_t floor = 0; floor < 0x0F; floor++) {
|
||||
const auto& m = l->floor_item_managers.at(floor);
|
||||
// It's important that these are added in increasing order of item_id (hence
|
||||
// why items is a map and not an unordered_map), since the game uses binary
|
||||
@@ -2667,10 +2678,9 @@ void send_game_item_state(shared_ptr<Client> c) {
|
||||
|
||||
FloorItem fi;
|
||||
fi.floor = floor;
|
||||
fi.from_enemy = 0;
|
||||
fi.entity_id = 0xFFFF;
|
||||
fi.x = item->x;
|
||||
fi.z = item->z;
|
||||
fi.source_type = 0;
|
||||
fi.entity_index = 0xFFFF;
|
||||
fi.pos = item->pos;
|
||||
fi.unknown_a2 = 0;
|
||||
fi.drop_number = (floor == 0) ? 0xFFFF : (decompressed_header.next_drop_number_per_floor.at(floor - 1)++);
|
||||
fi.item = item->data;
|
||||
@@ -2686,55 +2696,84 @@ void send_game_item_state(shared_ptr<Client> c) {
|
||||
decompressed_w.write(floor_items_w.str());
|
||||
const auto& data = decompressed_w.str();
|
||||
send_game_join_sync_command(c, data.data(), data.size(), 0x5E, 0x65, 0x6D);
|
||||
}
|
||||
|
||||
void send_game_enemy_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->map) {
|
||||
return;
|
||||
// Items on floors 0x0F and above can't be sent in the 6x6D command, so we
|
||||
// manually send 6x5D commands to create them if needed
|
||||
phosg::StringWriter w;
|
||||
for (size_t floor = 0x0F; floor < l->floor_item_managers.size(); floor++) {
|
||||
const auto& m = l->floor_item_managers[floor];
|
||||
for (const auto& it : m.items) {
|
||||
const auto& item = it.second;
|
||||
if (!item->visible_to_client(c->lobby_client_id)) {
|
||||
continue;
|
||||
}
|
||||
uint8_t subcommand = get_pre_v1_subcommand(c->version(), 0x4F, 0x56, 0x5D);
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, item->pos, item->data}, 0};
|
||||
cmd.item_data.encode_for_version(c->version(), s->item_parameter_table_for_encode(c->version()));
|
||||
w.put(cmd);
|
||||
}
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
|
||||
vector<G_SyncEnemyState_6x6B_Entry_Decompressed> entries;
|
||||
entries.reserve(l->map->enemies.size());
|
||||
for (size_t z = 0; z < l->map->enemies.size(); z++) {
|
||||
const auto& enemy = l->map->enemies[z];
|
||||
auto& entry = entries.emplace_back();
|
||||
entry.flags = enemy.game_flags;
|
||||
entry.item_drop_id = (enemy.server_flags & Map::Enemy::Flag::ITEM_DROPPED) ? 0xFFFF : (0xCA0 + z);
|
||||
entry.total_damage = enemy.total_damage;
|
||||
if (!w.str().empty()) {
|
||||
send_command(c, 0x6D, c->lobby_client_id, w.str());
|
||||
}
|
||||
|
||||
send_game_join_sync_command(c, entries.data(), entries.size() * sizeof(entries[0]), 0x5C, 0x63, 0x6B);
|
||||
}
|
||||
|
||||
void send_game_object_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->map) {
|
||||
return;
|
||||
if (!l->is_game()) {
|
||||
throw logic_error("cannot send object state in non-game lobby");
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
|
||||
vector<G_SyncObjectState_6x6C_Entry_Decompressed> entries;
|
||||
entries.reserve(l->map->objects.size());
|
||||
for (size_t z = 0; z < l->map->objects.size(); z++) {
|
||||
const auto& obj = l->map->objects[z];
|
||||
vector<SyncObjectStateEntry> entries;
|
||||
for (auto obj_st : l->map_state->iter_object_states(c->version())) {
|
||||
auto& entry = entries.emplace_back();
|
||||
entry.flags = obj.game_flags;
|
||||
entry.item_drop_id = (obj.item_drop_checked) ? 0xFFFF : (0x100 + z);
|
||||
entry.flags = obj_st->game_flags;
|
||||
entry.item_drop_id = (obj_st->item_drop_checked)
|
||||
? 0xFFFF
|
||||
: (0x100 + l->map_state->index_for_object_state(c->version(), obj_st));
|
||||
}
|
||||
|
||||
send_game_join_sync_command(c, entries.data(), entries.size() * sizeof(entries[0]), 0x5D, 0x64, 0x6C);
|
||||
}
|
||||
|
||||
void send_game_set_state(shared_ptr<Client> c) {
|
||||
void send_game_enemy_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->map) {
|
||||
return;
|
||||
if (!l->is_game()) {
|
||||
throw logic_error("cannot send enemy state in non-game lobby");
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
|
||||
vector<SyncEnemyStateEntry> entries;
|
||||
for (auto ene_st : l->map_state->iter_enemy_states(c->version())) {
|
||||
auto& entry = entries.emplace_back();
|
||||
entry.flags = ene_st->game_flags;
|
||||
entry.item_drop_id = (ene_st->server_flags & MapState::EnemyState::Flag::ITEM_DROPPED)
|
||||
? 0xFFFF
|
||||
: (0xCA0 + l->map_state->index_for_enemy_state(c->version(), ene_st));
|
||||
entry.total_damage = ene_st->total_damage;
|
||||
}
|
||||
|
||||
size_t num_object_sets = l->map->objects.size();
|
||||
size_t num_enemy_sets = l->map->enemy_set_flags.size();
|
||||
send_game_join_sync_command(c, entries.data(), entries.size() * sizeof(entries[0]), 0x5C, 0x63, 0x6B);
|
||||
}
|
||||
|
||||
void send_game_set_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
throw logic_error("cannot send set state in non-game lobby");
|
||||
}
|
||||
|
||||
size_t num_object_sets = 0;
|
||||
size_t num_enemy_sets = 0;
|
||||
size_t num_events = 0;
|
||||
for (const auto& fc : l->map_state->floor_config_entries) {
|
||||
if (fc.super_map) {
|
||||
const auto& entities = fc.super_map->version(c->version());
|
||||
num_object_sets += entities.objects.size();
|
||||
num_enemy_sets += entities.enemy_sets.size();
|
||||
num_events += entities.events.size();
|
||||
}
|
||||
}
|
||||
|
||||
G_SyncSetFlagState_6x6E_Decompressed::EntitySetFlags entity_set_flags_header;
|
||||
entity_set_flags_header.object_set_flags_offset = sizeof(entity_set_flags_header);
|
||||
@@ -2744,22 +2783,47 @@ void send_game_set_state(shared_ptr<Client> c) {
|
||||
|
||||
G_SyncSetFlagState_6x6E_Decompressed header;
|
||||
header.entity_set_flags_size = sizeof(entity_set_flags_header) + (num_object_sets + num_enemy_sets) * sizeof(le_uint16_t);
|
||||
header.event_set_flags_size = sizeof(le_uint16_t) * l->map->events.size();
|
||||
header.event_set_flags_size = sizeof(le_uint16_t) * num_events;
|
||||
header.switch_flags_size = is_v1(c->version()) ? 0x200 : 0x240;
|
||||
header.total_size = header.entity_set_flags_size + header.event_set_flags_size + header.switch_flags_size;
|
||||
|
||||
phosg::StringWriter w;
|
||||
w.put(header);
|
||||
w.put(entity_set_flags_header);
|
||||
for (const auto& obj : l->map->objects) {
|
||||
w.put_u16l(obj.set_flags);
|
||||
|
||||
{
|
||||
size_t size_before = w.size();
|
||||
for (const auto& obj_st : l->map_state->iter_object_states(c->version())) {
|
||||
w.put_u16l(obj_st->set_flags);
|
||||
}
|
||||
size_t bytes_added = w.size() - size_before;
|
||||
if (bytes_added != num_object_sets * sizeof(le_uint16_t)) {
|
||||
throw logic_error("incorrect number of object set flags added");
|
||||
}
|
||||
}
|
||||
for (uint16_t enemy_set_flags : l->map->enemy_set_flags) {
|
||||
w.put_u16l(enemy_set_flags);
|
||||
|
||||
{
|
||||
size_t size_before = w.size();
|
||||
for (const auto& ene_st : l->map_state->iter_enemy_set_states(c->version())) {
|
||||
w.put_u16l(ene_st->set_flags);
|
||||
}
|
||||
size_t bytes_added = w.size() - size_before;
|
||||
if (bytes_added != num_enemy_sets * sizeof(le_uint16_t)) {
|
||||
throw logic_error("incorrect number of enemy set flags added");
|
||||
}
|
||||
}
|
||||
for (const auto& event : l->map->events) {
|
||||
w.put_u16l(event.flags);
|
||||
|
||||
{
|
||||
size_t size_before = w.size();
|
||||
for (const auto& ev_st : l->map_state->iter_event_states(c->version())) {
|
||||
w.put_u16l(ev_st->flags);
|
||||
}
|
||||
size_t bytes_added = w.size() - size_before;
|
||||
if (bytes_added != num_events * sizeof(le_uint16_t)) {
|
||||
throw logic_error("incorrect number of event flags added");
|
||||
}
|
||||
}
|
||||
|
||||
if (l->switch_flags) {
|
||||
static_assert(sizeof(SwitchFlags) == 0x240, "switch_flags size is incorrect");
|
||||
w.write(l->switch_flags->data.data(), header.switch_flags_size);
|
||||
@@ -2869,16 +2933,15 @@ void send_game_player_state(shared_ptr<Client> to_c, shared_ptr<Client> from_c,
|
||||
to_send.telepipe.owner_client_id = from_c->telepipe_state.client_id2;
|
||||
to_send.telepipe.floor = from_c->telepipe_state.floor;
|
||||
to_send.telepipe.unknown_a1 = from_c->telepipe_state.unknown_b3;
|
||||
to_send.telepipe.x = from_c->telepipe_state.x;
|
||||
to_send.telepipe.y = from_c->telepipe_state.y;
|
||||
to_send.telepipe.z = from_c->telepipe_state.z;
|
||||
to_send.telepipe.pos = from_c->telepipe_state.pos;
|
||||
to_send.telepipe.unknown_a3 = from_c->telepipe_state.unknown_a3;
|
||||
}
|
||||
|
||||
if (apply_overrides) {
|
||||
auto from_p = from_c->character();
|
||||
to_send.base.x = from_c->x;
|
||||
to_send.base.z = from_c->z;
|
||||
to_send.base.pos.x = from_c->pos.x;
|
||||
to_send.base.pos.y = 0.0;
|
||||
to_send.base.pos.z = from_c->pos.z;
|
||||
to_send.bonus_hp_from_materials = from_p->inventory.hp_from_materials;
|
||||
to_send.bonus_tp_from_materials = from_p->inventory.tp_from_materials;
|
||||
to_send.language = from_c->language();
|
||||
@@ -2928,41 +2991,44 @@ void send_game_player_state(shared_ptr<Client> to_c, shared_ptr<Client> from_c,
|
||||
}
|
||||
}
|
||||
|
||||
void send_drop_item_to_channel(shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t entity_id) {
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x51, 0x58, 0x5F);
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{{subcommand, 0x0B, 0x0000}, {floor, from_enemy, entity_id, x, z, 0, 0, item}}, 0};
|
||||
cmd.item.item.encode_for_version(ch.version, s->item_parameter_table_for_encode(ch.version));
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
void send_drop_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t entity_id) {
|
||||
auto s = l->require_server_state();
|
||||
for (auto& c : l->clients) {
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
send_drop_item_to_channel(s, c->channel, item, from_enemy, floor, x, z, entity_id);
|
||||
void send_drop_item_to_channel(
|
||||
shared_ptr<ServerState> s,
|
||||
Channel& ch,
|
||||
const ItemData& item,
|
||||
uint8_t source_type,
|
||||
uint8_t floor,
|
||||
const VectorXZF& pos,
|
||||
uint16_t entity_index) {
|
||||
if (entity_index == 0xFFFF) {
|
||||
send_drop_stacked_item_to_channel(s, ch, item, floor, pos);
|
||||
} else {
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x51, 0x58, 0x5F);
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{{subcommand, 0x0B, 0x0000}, {floor, source_type, entity_index, pos, 0, 0, item}}, 0};
|
||||
cmd.item.item.encode_for_version(ch.version, s->item_parameter_table_for_encode(ch.version));
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void send_drop_stacked_item_to_channel(
|
||||
shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z) {
|
||||
shared_ptr<ServerState> s,
|
||||
Channel& ch,
|
||||
const ItemData& item,
|
||||
uint8_t floor,
|
||||
const VectorXZF& pos) {
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x4F, 0x56, 0x5D);
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, x, z, item}, 0};
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, pos, item}, 0};
|
||||
cmd.item_data.encode_for_version(ch.version, s->item_parameter_table_for_encode(ch.version));
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
void send_drop_stacked_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z) {
|
||||
void send_drop_stacked_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, const VectorXZF& pos) {
|
||||
auto s = l->require_server_state();
|
||||
for (auto& c : l->clients) {
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
send_drop_stacked_item_to_channel(s, c->channel, item, floor, x, z);
|
||||
send_drop_stacked_item_to_channel(s, c->channel, item, floor, pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3644,18 +3710,17 @@ void send_ep3_update_game_metadata(shared_ptr<Lobby> l) {
|
||||
{
|
||||
G_SetGameMetadata_Ep3_6xB4x52 cmd;
|
||||
cmd.total_spectators = total_spectators;
|
||||
if ((l->base_version != Version::GC_EP3_NTE) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
|
||||
}
|
||||
// Note: We can't use send_command_t(l, ...) here because that would send
|
||||
// the same command to l and to all watcher lobbies. The commands should
|
||||
// have different values depending on who's in each watcher lobby, so we
|
||||
// have to manually send to each client here.
|
||||
for (auto c : l->clients) {
|
||||
if (c) {
|
||||
send_command_t(c, 0xC9, 0x00, cmd);
|
||||
if ((c->version() == Version::GC_EP3) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
G_SetGameMetadata_Ep3_6xB4x52 masked_cmd = cmd;
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(&masked_cmd, sizeof(masked_cmd), mask_key);
|
||||
send_command_t(c, 0xC9, 0x00, masked_cmd);
|
||||
} else {
|
||||
send_command_t(c, 0xC9, 0x00, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3683,12 +3748,19 @@ void send_ep3_update_game_metadata(shared_ptr<Lobby> l) {
|
||||
cmd.total_spectators = total_spectators;
|
||||
cmd.text_size = text.size();
|
||||
cmd.text.encode(text, 1);
|
||||
if ((watcher_l->base_version != Version::GC_EP3_NTE) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
|
||||
for (auto c : watcher_l->clients) {
|
||||
if (c) {
|
||||
if ((c->version() == Version::GC_EP3) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
G_SetGameMetadata_Ep3_6xB4x52 masked_cmd = cmd;
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(&masked_cmd, sizeof(masked_cmd), mask_key);
|
||||
send_command_t(c, 0xC9, 0x00, masked_cmd);
|
||||
} else {
|
||||
send_command_t(c, 0xC9, 0x00, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
send_command_t(watcher_l, 0xC9, 0x00, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3958,15 +4030,13 @@ void send_ep3_card_auction(shared_ptr<Lobby> l) {
|
||||
}
|
||||
num_cards = min<uint16_t>(num_cards, 0x14);
|
||||
|
||||
auto card_index = l->is_ep3_nte() ? s->ep3_card_index_trial : s->ep3_card_index;
|
||||
|
||||
uint64_t distribution_size = 0;
|
||||
for (const auto& e : s->ep3_card_auction_pool) {
|
||||
distribution_size += e.probability;
|
||||
}
|
||||
|
||||
auto card_index = (l->base_version == Version::GC_EP3_NTE)
|
||||
? s->ep3_card_index_trial
|
||||
: s->ep3_card_index;
|
||||
|
||||
S_StartCardAuction_Ep3_EF cmd;
|
||||
cmd.points_available = s->ep3_card_auction_points;
|
||||
for (size_t z = 0; z < num_cards; z++) {
|
||||
@@ -4002,10 +4072,9 @@ void send_server_time(shared_ptr<Client> c) {
|
||||
gmtime_r(&t_secs, &t_parsed);
|
||||
|
||||
string time_str(128, 0);
|
||||
size_t len = strftime(time_str.data(), time_str.size(),
|
||||
"%Y:%m:%d: %H:%M:%S.000", &t_parsed);
|
||||
size_t len = strftime(time_str.data(), time_str.size(), "%Y:%m:%d: %H:%M:%S.000", &t_parsed);
|
||||
if (len == 0) {
|
||||
throw runtime_error("phosg::format_time buffer too short");
|
||||
throw logic_error("strftime buffer too short");
|
||||
}
|
||||
time_str.resize(len);
|
||||
|
||||
|
||||
+3
-5
@@ -354,13 +354,11 @@ void send_game_set_state(std::shared_ptr<Client> c);
|
||||
void send_game_flag_state(std::shared_ptr<Client> c);
|
||||
void send_game_player_state(std::shared_ptr<Client> to_c, std::shared_ptr<Client> from_c, bool apply_overrides);
|
||||
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
uint8_t source_type, uint8_t floor, const VectorXZF& pos, uint16_t entity_index);
|
||||
void send_drop_stacked_item_to_channel(
|
||||
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
|
||||
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, const VectorXZF& pos);
|
||||
void send_drop_stacked_item_to_lobby(
|
||||
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z);
|
||||
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, const VectorXZF& pos);
|
||||
void send_pick_up_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, uint32_t id, uint8_t floor);
|
||||
void send_create_inventory_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item);
|
||||
void send_create_inventory_item_to_lobby(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item, bool exclude_c = false);
|
||||
|
||||
+4
-2
@@ -256,6 +256,8 @@ CommandDefinition c_reload(
|
||||
args.s->load_bb_private_keys(true);
|
||||
} else if (type == "accounts") {
|
||||
args.s->load_accounts(true);
|
||||
} else if (type == "maps") {
|
||||
args.s->load_maps(true);
|
||||
} else if (type == "caches") {
|
||||
args.s->clear_file_caches(true);
|
||||
} else if (type == "patch-files") {
|
||||
@@ -1175,8 +1177,8 @@ void fn_create_item(CommandArgs& args) {
|
||||
send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
|
||||
|
||||
} else {
|
||||
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->x, ses->z);
|
||||
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->x, ses->z);
|
||||
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->pos);
|
||||
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->pos);
|
||||
|
||||
string name = s->describe_item(ses->version(), ses->next_drop_item, true);
|
||||
send_text_message(ses->client_channel, "$C7Item created:\n" + name);
|
||||
|
||||
+200
-17
@@ -3,6 +3,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Image.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Platform.hh>
|
||||
@@ -563,11 +564,6 @@ shared_ptr<const string> ServerState::load_bb_file(
|
||||
}
|
||||
|
||||
shared_ptr<const string> ServerState::load_map_file(Version version, const string& filename) const {
|
||||
auto& cache = this->map_file_caches.at(static_cast<size_t>(version));
|
||||
return cache->get(filename, bind(&ServerState::load_map_file_uncached, this, version, placeholders::_1));
|
||||
}
|
||||
|
||||
shared_ptr<const string> ServerState::load_map_file_uncached(Version version, const string& filename) const {
|
||||
if (version == Version::BB_V4) {
|
||||
try {
|
||||
return this->load_bb_file(filename);
|
||||
@@ -1044,8 +1040,32 @@ void ServerState::load_config_early() {
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
this->allow_dc_pc_games = this->config_json->get_bool("AllowDCPCGames", true);
|
||||
this->allow_gc_xb_games = this->config_json->get_bool("AllowGCXBGames", true);
|
||||
try {
|
||||
const auto& groups = this->config_json->get_list("CompatibilityGroups");
|
||||
this->compatibility_groups.fill(0);
|
||||
for (size_t v_s = 0; v_s < groups.size(); v_s++) {
|
||||
this->compatibility_groups[v_s] = groups[v_s]->as_int();
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
static_assert(NUM_VERSIONS == 14, "Don't forget to update the default compatibility groups");
|
||||
this->compatibility_groups = {
|
||||
0x0000, // PC_PATCH
|
||||
0x0000, // BB_PATCH
|
||||
0x0004, // DC_NTE compatible only with itself
|
||||
0x0008, // DC_V1_11_2000_PROTOTYPE compatible only with itself
|
||||
0x00B0, // DC_V1 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x00B0, // DC_V2 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x0040, // PC_NTE compatible only with itself
|
||||
0x00B0, // PC_V2 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x0100, // GC_NTE compatible only with itself
|
||||
0x1200, // GC_V3 compatible with GC_V3 and XB_V3
|
||||
0x0400, // GC_EP3_NTE compatible only with itself
|
||||
0x0800, // GC_EP3 compatible only with itself
|
||||
0x1200, // XB_V3 compatible with GC_V3 and XB_V3
|
||||
0x2000, // BB_V4 compatible only with itself
|
||||
};
|
||||
}
|
||||
|
||||
this->enable_chat_commands = this->config_json->get_bool("EnableChatCommands", true);
|
||||
|
||||
this->version_name_colors.reset();
|
||||
@@ -1216,20 +1236,20 @@ void ServerState::load_config_early() {
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
shared_ptr<const Map::RareEnemyRates> prev = Map::DEFAULT_RARE_ENEMIES;
|
||||
shared_ptr<const MapState::RareEnemyRates> prev = MapState::DEFAULT_RARE_ENEMIES;
|
||||
try {
|
||||
string key = "RareEnemyRates-";
|
||||
key += token_name_for_difficulty(z);
|
||||
this->rare_enemy_rates_by_difficulty[z] = make_shared<Map::RareEnemyRates>(this->config_json->at(key));
|
||||
this->rare_enemy_rates_by_difficulty[z] = make_shared<MapState::RareEnemyRates>(this->config_json->at(key));
|
||||
prev = this->rare_enemy_rates_by_difficulty[z];
|
||||
} catch (const out_of_range&) {
|
||||
this->rare_enemy_rates_by_difficulty[z] = prev;
|
||||
}
|
||||
}
|
||||
try {
|
||||
this->rare_enemy_rates_challenge = make_shared<Map::RareEnemyRates>(this->config_json->at("RareEnemyRates-Challenge"));
|
||||
this->rare_enemy_rates_challenge = make_shared<MapState::RareEnemyRates>(this->config_json->at("RareEnemyRates-Challenge"));
|
||||
} catch (const out_of_range&) {
|
||||
this->rare_enemy_rates_challenge = Map::DEFAULT_RARE_ENEMIES;
|
||||
this->rare_enemy_rates_challenge = MapState::DEFAULT_RARE_ENEMIES;
|
||||
}
|
||||
|
||||
this->min_levels_v4[0] = DEFAULT_MIN_LEVELS_V4_EP1;
|
||||
@@ -1398,7 +1418,7 @@ void ServerState::load_config_late() {
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
auto parse_primary_identifier_list = [&](const char* key, Version base_version) -> unordered_set<uint32_t> {
|
||||
auto parse_primary_identifier_list = [&](const char* key, Version v) -> unordered_set<uint32_t> {
|
||||
unordered_set<uint32_t> ret;
|
||||
try {
|
||||
for (const auto& pi_json : this->config_json->get_list(key)) {
|
||||
@@ -1406,7 +1426,7 @@ void ServerState::load_config_late() {
|
||||
ret.emplace(pi_json->as_int());
|
||||
} else {
|
||||
try {
|
||||
auto item = this->parse_item_description(base_version, pi_json->as_string());
|
||||
auto item = this->parse_item_description(v, pi_json->as_string());
|
||||
ret.emplace(item.primary_identifier());
|
||||
} catch (const exception& e) {
|
||||
config_log.warning("Cannot parse item description \"%s\": %s (skipping entry)", pi_json->as_string().c_str(), e.what());
|
||||
@@ -1532,12 +1552,174 @@ void ServerState::load_patch_indexes(bool from_non_event_thread) {
|
||||
this->forward_or_call(from_non_event_thread, std::move(set));
|
||||
}
|
||||
|
||||
void ServerState::load_maps(bool from_non_event_thread) {
|
||||
using SDT = SetDataTable;
|
||||
|
||||
config_log.info("Loading free play map files");
|
||||
|
||||
unordered_map<uint64_t, shared_ptr<const MapFile>> map_file_for_source_hash;
|
||||
map<uint32_t, array<shared_ptr<const MapFile>, NUM_VERSIONS>> map_files;
|
||||
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
|
||||
const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
if ((episode == Episode::EP2 && is_v1_or_v2(v) && (v != Version::GC_NTE)) ||
|
||||
(episode == Episode::EP4 && !is_v4(v))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (GameMode mode : modes) {
|
||||
if (((mode == GameMode::BATTLE || mode == GameMode::CHALLENGE) && is_v1(v)) ||
|
||||
(mode == GameMode::SOLO && !is_v4(v))) {
|
||||
continue;
|
||||
}
|
||||
for (uint8_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
if ((difficulty == 3) && is_v1(v)) {
|
||||
continue;
|
||||
}
|
||||
auto sdt = this->set_data_table(v, episode, mode, difficulty);
|
||||
for (uint8_t floor = 0; floor < 0x12; floor++) {
|
||||
auto variation_maxes = sdt->num_free_play_variations_for_floor(episode, mode == GameMode::SOLO, floor);
|
||||
for (size_t var_layout = 0; var_layout < variation_maxes.layout; var_layout++) {
|
||||
for (size_t var_entities = 0; var_entities < variation_maxes.entities; var_entities++) {
|
||||
uint32_t supermap_key = this->supermap_key(episode, mode, difficulty, floor, var_layout, var_entities);
|
||||
|
||||
auto objects_filename = sdt->map_filename_for_variation(
|
||||
episode, mode, floor, var_layout, var_entities, SDT::FilenameType::OBJECT_SETS);
|
||||
auto enemies_filename = sdt->map_filename_for_variation(
|
||||
episode, mode, floor, var_layout, var_entities, SDT::FilenameType::ENEMY_SETS);
|
||||
auto events_filename = sdt->map_filename_for_variation(
|
||||
episode, mode, floor, var_layout, var_entities, SDT::FilenameType::EVENTS);
|
||||
auto objects_data = objects_filename.empty() ? nullptr : this->load_map_file(v, objects_filename);
|
||||
auto enemies_data = enemies_filename.empty() ? nullptr : this->load_map_file(v, enemies_filename);
|
||||
auto events_data = enemies_filename.empty() ? nullptr : this->load_map_file(v, events_filename);
|
||||
|
||||
if (objects_data || enemies_data || events_data) {
|
||||
// TODO: This is ugly; the hash computation probably should be factored into MapFile
|
||||
uint64_t source_hash = ((objects_data ? phosg::fnv1a64(*objects_data) : 0) ^
|
||||
(enemies_data ? phosg::fnv1a64(*enemies_data) : 0) ^
|
||||
(events_data ? phosg::fnv1a64(*events_data) : 0));
|
||||
shared_ptr<const MapFile> map_file;
|
||||
try {
|
||||
map_file = map_file_for_source_hash.at(source_hash);
|
||||
} catch (const out_of_range&) {
|
||||
map_file = make_shared<MapFile>(floor, objects_data, enemies_data, events_data);
|
||||
if (map_file->source_hash() != source_hash) {
|
||||
throw logic_error("incorrect source hash");
|
||||
}
|
||||
map_file_for_source_hash.emplace(map_file->source_hash(), map_file);
|
||||
}
|
||||
|
||||
// Uncomment for debugging
|
||||
// config_log.info("Maps for %s %s %s %s %02hhX %02zu %02zu (%08" PRIX32 " => %016" PRIX64 "): objects=%s(%s)+0x%zX enemies=%s(%s)+0x%zX events=%s(%s)+0x%zX",
|
||||
// phosg::name_for_enum(v),
|
||||
// name_for_episode(episode),
|
||||
// name_for_mode(mode),
|
||||
// name_for_difficulty(difficulty),
|
||||
// floor,
|
||||
// var_layout,
|
||||
// var_entities,
|
||||
// supermap_key,
|
||||
// map_file->source_hash(),
|
||||
// objects_filename.empty() ? "(none)" : objects_filename.c_str(),
|
||||
// objects_data ? "present" : "missing",
|
||||
// map_file->count_object_sets(),
|
||||
// enemies_filename.empty() ? "(none)" : enemies_filename.c_str(),
|
||||
// enemies_data ? "present" : "missing",
|
||||
// map_file->count_enemy_sets(),
|
||||
// events_filename.empty() ? "(none)" : events_filename.c_str(),
|
||||
// events_data ? "present" : "missing",
|
||||
// map_file->count_events());
|
||||
|
||||
map_files[supermap_key].at(static_cast<size_t>(v)) = map_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config_log.info("Constructing free play supermaps");
|
||||
|
||||
unordered_map<uint64_t, shared_ptr<const SuperMap>> supermap_for_source_hash_sum;
|
||||
unordered_map<uint32_t, shared_ptr<const SuperMap>> new_supermaps;
|
||||
for (const auto& it : map_files) {
|
||||
uint64_t source_hash_sum = 0;
|
||||
for (auto map_file : it.second) {
|
||||
source_hash_sum += map_file ? map_file->source_hash() : 0;
|
||||
}
|
||||
|
||||
Episode episode = static_cast<Episode>((it.first >> 28) & 7);
|
||||
// Uncomment for debugging
|
||||
// auto mode = static_cast<GameMode>((it.first >> 26) & 3);
|
||||
// uint8_t difficulty = (it.first >> 24) & 3;
|
||||
// uint8_t floor = (it.first >> 16) & 0xFF;
|
||||
// uint8_t layout = (it.first >> 8) & 0xFF;
|
||||
// uint8_t entities = (it.first >> 0) & 0xFF;
|
||||
// fprintf(stderr, "SuperMap for %s %s %s %02hhX %02hhX %02hhX (%08" PRIX32 "): %016" PRIX64 " from",
|
||||
// name_for_episode(episode),
|
||||
// name_for_mode(mode),
|
||||
// name_for_difficulty(difficulty),
|
||||
// floor,
|
||||
// layout,
|
||||
// entities,
|
||||
// it.first,
|
||||
// source_hash_sum);
|
||||
// for (const auto& map_file : it.second) {
|
||||
// if (map_file) {
|
||||
// fprintf(stderr, " %016" PRIX64, map_file->source_hash());
|
||||
// } else {
|
||||
// fprintf(stderr, " ----------------");
|
||||
// }
|
||||
// }
|
||||
// fputc('\n', stderr);
|
||||
|
||||
shared_ptr<const SuperMap> supermap;
|
||||
try {
|
||||
supermap = supermap_for_source_hash_sum.at(source_hash_sum);
|
||||
static_game_data_log.info("Linking existing free play supermap %016" PRIX64 " for key %08" PRIX32, source_hash_sum, it.first);
|
||||
} catch (const out_of_range&) {
|
||||
supermap = make_shared<SuperMap>(episode, it.second);
|
||||
supermap_for_source_hash_sum.emplace(source_hash_sum, supermap);
|
||||
static_game_data_log.info("Constructed free play supermap %016" PRIX64 " for key %08" PRIX32, source_hash_sum, it.first);
|
||||
}
|
||||
new_supermaps.emplace(it.first, supermap);
|
||||
}
|
||||
|
||||
auto set = [s = this->shared_from_this(), new_supermaps = std::move(new_supermaps)]() {
|
||||
s->supermaps = std::move(new_supermaps);
|
||||
};
|
||||
this->forward_or_call(from_non_event_thread, std::move(set));
|
||||
}
|
||||
|
||||
vector<shared_ptr<const SuperMap>> ServerState::supermaps_for_variations(
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
const Variations& variations) const {
|
||||
vector<shared_ptr<const SuperMap>> ret;
|
||||
for (size_t floor = 0; floor < 0x12; floor++) {
|
||||
Variations::Entry e;
|
||||
if (floor < variations.entries.size()) {
|
||||
e = variations.entries[floor];
|
||||
}
|
||||
ret.push_back(this->get_supermap(episode, mode, difficulty, floor, e.layout, e.entities));
|
||||
if (ret.back()) {
|
||||
static_game_data_log.info("Using supermap %08" PRIX32 " for floor %02zX layout %" PRIX32 " entities %" PRIX32,
|
||||
this->supermap_key(episode, mode, difficulty, floor, e.layout, e.entities),
|
||||
floor, e.layout.load(), e.entities.load());
|
||||
} else {
|
||||
static_game_data_log.info("No supermap available for floor %02zX layout %" PRIX32 " entities %" PRIX32,
|
||||
floor, e.layout.load(), e.entities.load());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ServerState::clear_file_caches(bool from_non_event_thread) {
|
||||
auto set = [s = this->shared_from_this()]() {
|
||||
config_log.info("Clearing map file caches");
|
||||
for (auto& cache : s->map_file_caches) {
|
||||
cache = make_shared<ThreadSafeFileCache>();
|
||||
}
|
||||
config_log.info("Clearing BB stream file cache");
|
||||
s->bb_stream_files_cache.reset(new FileContentsCache(3600000000ULL));
|
||||
config_log.info("Clearing BB system cache");
|
||||
@@ -2041,6 +2223,7 @@ void ServerState::load_all() {
|
||||
this->load_dol_files(false);
|
||||
this->create_default_lobbies();
|
||||
this->load_set_data_tables(false);
|
||||
this->load_maps(false);
|
||||
this->load_battle_params(false);
|
||||
this->load_level_tables(false);
|
||||
this->load_text_index(false);
|
||||
|
||||
+28
-5
@@ -115,8 +115,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
bool allow_unregistered_users = false;
|
||||
bool allow_pc_nte = false;
|
||||
bool use_temp_accounts_for_prototypes = true;
|
||||
bool allow_dc_pc_games = true;
|
||||
bool allow_gc_xb_games = true;
|
||||
std::array<uint16_t, NUM_VERSIONS> compatibility_groups = {};
|
||||
bool enable_chat_commands = true;
|
||||
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
|
||||
uint32_t client_customization_name_color = 0x00000000;
|
||||
@@ -173,7 +172,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const FunctionCodeIndex> function_code_index;
|
||||
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
|
||||
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
|
||||
std::array<std::shared_ptr<ThreadSafeFileCache>, NUM_VERSIONS> map_file_caches;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<const SuperMap>> supermaps; // Keyed by supermap_key
|
||||
std::shared_ptr<FileContentsCache> bb_stream_files_cache;
|
||||
std::shared_ptr<FileContentsCache> bb_system_cache;
|
||||
std::shared_ptr<FileContentsCache> gba_files_cache;
|
||||
@@ -210,8 +209,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
|
||||
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::shared_ptr<const MapState::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
|
||||
std::vector<std::string> bb_required_patches;
|
||||
CheatFlags cheat_flags;
|
||||
@@ -401,6 +400,29 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const;
|
||||
void update_dependent_server_configs() const;
|
||||
|
||||
static constexpr uint32_t supermap_key(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t floor, uint32_t layout, uint32_t entities) {
|
||||
return (static_cast<uint32_t>(episode) << 28) |
|
||||
(static_cast<uint32_t>(mode) << 26) |
|
||||
(static_cast<uint32_t>(difficulty) << 24) |
|
||||
(static_cast<uint32_t>(floor) << 16) |
|
||||
(static_cast<uint32_t>(layout) << 8) |
|
||||
(static_cast<uint32_t>(entities) << 0);
|
||||
}
|
||||
inline std::shared_ptr<const SuperMap> get_supermap(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t floor, uint32_t layout, uint32_t entities) const {
|
||||
try {
|
||||
return this->supermaps.at(this->supermap_key(episode, mode, difficulty, floor, layout, entities));
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
std::vector<std::shared_ptr<const SuperMap>> supermaps_for_variations(
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
const Variations& variations) const;
|
||||
|
||||
// The following functions may only be called from a non-event thread if they
|
||||
// take a from_non_event_thread argument; any function that does not have this
|
||||
// argument must be called only from the event thread.
|
||||
@@ -413,6 +435,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
void load_accounts(bool from_non_event_thread);
|
||||
void load_teams(bool from_non_event_thread);
|
||||
void load_patch_indexes(bool from_non_event_thread);
|
||||
void load_maps(bool from_non_event_thread);
|
||||
void clear_file_caches(bool from_non_event_thread);
|
||||
void load_battle_params(bool from_non_event_thread);
|
||||
void load_level_tables(bool from_non_event_thread);
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
@@ -178,7 +179,7 @@ BinaryTextSet::BinaryTextSet(const std::string& pr2_data, size_t collection_coun
|
||||
// offsets in the file instead.
|
||||
::set<uint32_t> used_offsets;
|
||||
size_t root_offset = has_rel_footer
|
||||
? r.pget_u32l(r.size() - 0x10)
|
||||
? r.pget<RELFileFooter>(r.size() - 0x20).root_offset.load()
|
||||
: (r.size() - collection_count * sizeof(le_uint32_t));
|
||||
|
||||
phosg::StringReader collection_offsets_r = r.sub(root_offset, collection_count * sizeof(le_uint32_t));
|
||||
|
||||
+63
-20
@@ -24,7 +24,50 @@ enum class Version {
|
||||
UNKNOWN = 15,
|
||||
};
|
||||
|
||||
constexpr size_t NUM_VERSIONS = static_cast<size_t>(Version::BB_V4) + 1;
|
||||
constexpr std::array<Version, 14> ALL_VERSIONS = {
|
||||
Version::PC_PATCH,
|
||||
Version::BB_PATCH,
|
||||
Version::DC_NTE,
|
||||
Version::DC_V1_11_2000_PROTOTYPE,
|
||||
Version::DC_V1,
|
||||
Version::DC_V2,
|
||||
Version::PC_NTE,
|
||||
Version::PC_V2,
|
||||
Version::GC_NTE,
|
||||
Version::GC_V3,
|
||||
Version::GC_EP3_NTE,
|
||||
Version::GC_EP3,
|
||||
Version::XB_V3,
|
||||
Version::BB_V4,
|
||||
};
|
||||
constexpr std::array<Version, 12> ALL_NON_PATCH_VERSIONS = {
|
||||
Version::DC_NTE,
|
||||
Version::DC_V1_11_2000_PROTOTYPE,
|
||||
Version::DC_V1,
|
||||
Version::DC_V2,
|
||||
Version::PC_NTE,
|
||||
Version::PC_V2,
|
||||
Version::GC_NTE,
|
||||
Version::GC_V3,
|
||||
Version::GC_EP3_NTE,
|
||||
Version::GC_EP3,
|
||||
Version::XB_V3,
|
||||
Version::BB_V4,
|
||||
};
|
||||
constexpr std::array<Version, 10> ALL_ARPG_SEMANTIC_VERSIONS = {
|
||||
Version::DC_NTE,
|
||||
Version::DC_V1_11_2000_PROTOTYPE,
|
||||
Version::DC_V1,
|
||||
Version::DC_V2,
|
||||
Version::PC_NTE,
|
||||
Version::PC_V2,
|
||||
Version::GC_NTE,
|
||||
Version::GC_V3,
|
||||
Version::XB_V3,
|
||||
Version::BB_V4,
|
||||
};
|
||||
|
||||
constexpr size_t NUM_VERSIONS = ALL_VERSIONS.size();
|
||||
constexpr size_t NUM_PATCH_VERSIONS = static_cast<size_t>(Version::BB_PATCH) + 1;
|
||||
constexpr size_t NUM_NON_PATCH_VERSIONS = NUM_VERSIONS - NUM_PATCH_VERSIONS;
|
||||
|
||||
@@ -35,7 +78,7 @@ const char* phosg::name_for_enum<Version>(Version v);
|
||||
template <>
|
||||
Version phosg::enum_for_name<Version>(const char* name);
|
||||
|
||||
inline bool is_any_nte(Version version) {
|
||||
constexpr bool is_any_nte(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
@@ -43,55 +86,55 @@ inline bool is_any_nte(Version version) {
|
||||
(version == Version::GC_EP3_NTE);
|
||||
}
|
||||
|
||||
inline bool is_patch(Version version) {
|
||||
constexpr bool is_patch(Version version) {
|
||||
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
|
||||
}
|
||||
inline bool is_pre_v1(Version version) {
|
||||
constexpr bool is_pre_v1(Version version) {
|
||||
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE);
|
||||
}
|
||||
inline bool is_v1(Version version) {
|
||||
constexpr bool is_v1(Version version) {
|
||||
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1);
|
||||
}
|
||||
inline bool is_v2(Version version) {
|
||||
constexpr bool is_v2(Version version) {
|
||||
return (version == Version::DC_V2) || (version == Version::PC_NTE) || (version == Version::PC_V2) || (version == Version::GC_NTE);
|
||||
}
|
||||
inline bool is_v1_or_v2(Version version) {
|
||||
constexpr bool is_v1_or_v2(Version version) {
|
||||
return is_v1(version) || is_v2(version);
|
||||
}
|
||||
inline bool is_v3(Version version) {
|
||||
constexpr bool is_v3(Version version) {
|
||||
return (version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3) ||
|
||||
(version == Version::XB_V3);
|
||||
}
|
||||
inline bool is_v4(Version version) {
|
||||
constexpr bool is_v4(Version version) {
|
||||
return (version == Version::BB_V4);
|
||||
}
|
||||
|
||||
inline bool is_ep3(Version version) {
|
||||
constexpr bool is_ep3(Version version) {
|
||||
return (version == Version::GC_EP3_NTE) || (version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_dc(Version version) {
|
||||
constexpr bool is_dc(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2);
|
||||
}
|
||||
inline bool is_gc(Version version) {
|
||||
constexpr bool is_gc(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_sh4(Version version) {
|
||||
constexpr bool is_sh4(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2);
|
||||
}
|
||||
inline bool is_x86(Version version) {
|
||||
constexpr bool is_x86(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
@@ -99,20 +142,20 @@ inline bool is_x86(Version version) {
|
||||
(version == Version::XB_V3) ||
|
||||
(version == Version::BB_V4);
|
||||
}
|
||||
inline bool is_ppc(Version version) {
|
||||
constexpr bool is_ppc(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_big_endian(Version version) {
|
||||
constexpr bool is_big_endian(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
inline bool uses_v2_encryption(Version version) {
|
||||
constexpr bool uses_v2_encryption(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::DC_NTE) ||
|
||||
@@ -122,17 +165,17 @@ inline bool uses_v2_encryption(Version version) {
|
||||
(version == Version::PC_V2) ||
|
||||
(version == Version::GC_NTE);
|
||||
}
|
||||
inline bool uses_v3_encryption(Version version) {
|
||||
constexpr bool uses_v3_encryption(Version version) {
|
||||
return (version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3) ||
|
||||
(version == Version::XB_V3);
|
||||
}
|
||||
inline bool uses_v4_encryption(Version version) {
|
||||
constexpr bool uses_v4_encryption(Version version) {
|
||||
return (version == Version::BB_V4);
|
||||
}
|
||||
|
||||
inline bool uses_utf16(Version version) {
|
||||
constexpr bool uses_utf16(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
|
||||
@@ -70,8 +70,8 @@ struct BBRoot {
|
||||
template <bool BE, size_t StringTableCount, size_t TokenCount>
|
||||
void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) {
|
||||
phosg::StringReader r(data);
|
||||
uint32_t root_offset = r.pget<U32T<BE>>(r.size() - 0x10);
|
||||
const auto& root = r.pget<NonWindowsRootT<BE>>(root_offset);
|
||||
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
|
||||
const auto& root = r.pget<NonWindowsRootT<BE>>(footer.root_offset);
|
||||
|
||||
{
|
||||
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T<BE>) * StringTableCount);
|
||||
@@ -96,8 +96,8 @@ void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<s
|
||||
}
|
||||
|
||||
phosg::StringReader r(data);
|
||||
uint32_t root_offset = r.pget<le_uint32_t>(r.size() - 0x10);
|
||||
const auto& root = r.pget<RootT>(root_offset);
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& root = r.pget<RootT>(footer.root_offset);
|
||||
this->strings = *unitxt_collection;
|
||||
// this->table1 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table1, Table1Count);
|
||||
// this->table2 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table2, Table2Count);
|
||||
@@ -325,7 +325,5 @@ WordSelectMessage WordSelectTable::translate(
|
||||
}
|
||||
|
||||
WordSelectTable::Token::Token() {
|
||||
for (size_t z = 0; z < this->values_by_version.size(); z++) {
|
||||
this->values_by_version[z] = 0xFFFF;
|
||||
}
|
||||
this->values_by_version.fill(0xFFFF);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user