describe 6x6B and 6x6C more completely

This commit is contained in:
Martin Michelsen
2024-02-28 19:41:16 -08:00
parent bb560c1153
commit 1f10d03923
7 changed files with 182 additions and 90 deletions
+37 -30
View File
@@ -3890,27 +3890,27 @@ struct G_Unknown_6x09 {
G_EnemyIDHeader header;
} __packed__;
// 6x0A: Enemy hit
// 6x0A: Update enemy state
template <bool IsBigEndian>
struct G_EnemyHitByPlayer_6x0A {
struct G_UpdateEnemyState_6x0A {
G_EnemyIDHeader header;
le_uint16_t enemy_index = 0; // [0, 0xB50)
le_uint16_t total_damage = 0;
// Flags:
// 00000400 - should play hit animation
// 00000800 - should die
// 00000800 - is dead
typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type flags = 0;
} __packed__;
struct G_EnemyHitByPlayer_GC_6x0A : G_EnemyHitByPlayer_6x0A<true> {
struct G_UpdateEnemyState_GC_6x0A : G_UpdateEnemyState_6x0A<true> {
} __packed__;
struct G_EnemyHitByPlayer_DC_PC_XB_BB_6x0A : G_EnemyHitByPlayer_6x0A<false> {
struct G_UpdateEnemyState_DC_PC_XB_BB_6x0A : G_UpdateEnemyState_6x0A<false> {
} __packed__;
// 6x0B: Box destroyed
// 6x0B: Update object state
struct G_BoxDestroyed_6x0B {
struct G_UpdateObjectState_6x0B {
G_ClientIDHeader header;
le_uint32_t flags = 0;
le_uint32_t object_index = 0;
@@ -4626,15 +4626,13 @@ struct G_UseStarAtomizer_6x66 {
parray<le_uint16_t, 4> target_client_ids;
} __packed__;
// 6x67: Create enemy set
// 6x67: Trigger wave event
struct G_CreateEnemySet_6x67 {
struct G_TriggerWaveEvent_6x67 {
G_UnusedHeader header;
// unused1 could be floor; the client checks this against a global but the
// logic is the same in both branches
le_uint32_t unused1 = 0;
le_uint32_t unknown_a1 = 0;
le_uint32_t unused2 = 0;
le_uint32_t floor = 0;
le_uint32_t event_id = 0; // NOT event index
le_uint32_t unused = 0;
} __packed__;
// 6x68: Create telepipe / cast Ryuker
@@ -4687,9 +4685,9 @@ struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E {
// Decompressed format is a list of these
struct G_SyncEnemyState_6x6B_Entry_Decompressed {
le_uint32_t flags = 0;
le_uint16_t last_attacker = 0;
le_uint16_t total_damage = 0;
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;
@@ -4702,7 +4700,7 @@ struct G_SyncEnemyState_6x6B_Entry_Decompressed {
// Decompressed format is a list of these
struct G_SyncObjectState_6x6C_Entry_Decompressed {
le_uint16_t flags = 0;
le_uint16_t object_index = 0;
le_uint16_t item_drop_id = 0;
} __packed__;
// 6x6D: Sync item state (used while loading into game)
@@ -4746,19 +4744,28 @@ struct G_SyncItemState_6x6D_Decompressed {
// FloorItem items[sum(floor_item_count_per_floor)];
} __packed__;
// 6x6E: Sync flag state (used while loading into game)
// 6x6E: Sync set flag state (used while loading into game)
// Compressed format is the same as 6x6B.
struct G_SyncFlagState_6x6E_Decompressed {
// The three unknowns here are the sizes (in bytes) of three fields
// immediately following this structure. It is currently unknown what these
// fields represent. The three unknown fields always sum to the size field.
le_uint16_t size = 0;
le_uint16_t unknown_a1 = 0;
le_uint16_t unknown_a2 = 0;
le_uint16_t unknown_a3 = 0;
// Three variable-length fields follow here. They are in the same order as the
// unknown fields above.
struct G_SyncSetFlagState_6x6E_Decompressed {
le_uint16_t total_size = 0; // == sum of the following 3 fields
le_uint16_t entity_set_flags_size = 0;
le_uint16_t event_set_flags_size = 0;
le_uint16_t unused_size = 0;
// Variable-length fields follow here:
// EntitySetFlags entity_set_flags; // Total size is set_flags_size
// le_uint16_t event_set_flags[event_set_flags_size / 2]; // Same order as in map files (NOT sorted by event_id)
// uint8_t unused[is_v1 ? 0x200 : 0x240]; // Possibly an early implementation of 6x6F; unused even in DC NTE
struct EntitySetFlags {
le_uint32_t object_set_flags_offset = 0;
le_uint32_t num_object_sets = 0;
le_uint32_t enemy_set_flags_offset = 0;
le_uint32_t num_enemy_sets = 0;
// Variable-length fields follow here:
// le_uint16_t object_set_flags[num_object_sets];
// le_uint16_t enemy_set_flags[num_enemy_sets];
} __packed__;
} __packed__;
// 6x6F: Set quest flags (used while loading into game)
@@ -4773,7 +4780,7 @@ struct G_SetQuestFlags_6x6F {
// and instead rearranged a bunch of things.
struct Telepipe {
/* 00 */ le_uint16_t client_id = 0xFFFF;
/* 00 */ le_uint16_t owner_client_id = 0xFFFF;
/* 02 */ le_uint16_t unknown_a1 = 0;
/* 04 */ le_uint32_t unknown_a2 = 0;
/* 08 */ le_float x = 0.0f;
+1 -1
View File
@@ -272,7 +272,7 @@ shared_ptr<Map> Lobby::load_maps(
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_enemies_and_objects_from_quest_data(
map->add_entities_from_quest_data(
episode,
difficulty,
event,
+14 -6
View File
@@ -655,9 +655,12 @@ string Map::EnemyEntry::str() const {
this->unused.load());
}
Map::Enemy::Enemy(uint16_t enemy_id, size_t source_index, uint8_t floor, EnemyType type)
Map::Enemy::Enemy(uint16_t enemy_id, size_t source_index, size_t set_index, uint8_t floor, EnemyType type)
: source_index(source_index),
set_index(set_index),
enemy_id(enemy_id),
total_damage(0),
game_flags(0),
type(type),
floor(floor),
state_flags(0),
@@ -721,6 +724,8 @@ void Map::add_objects_from_map_data(uint8_t floor, const void* data, size_t size
.param4 = objects[z].param4,
.param5 = objects[z].param5,
.param6 = objects[z].param6,
.game_flags = 0,
.set_flags = 0,
.item_drop_checked = false,
});
}
@@ -763,12 +768,15 @@ void Map::add_enemy(
uint8_t difficulty,
uint8_t event,
uint8_t floor,
size_t index,
size_t source_index,
const EnemyEntry& e,
std::shared_ptr<const RareEnemyRates> rare_rates) {
size_t set_index = this->enemy_set_flags.size();
this->enemy_set_flags.emplace_back(0);
auto add = [&](EnemyType type) -> void {
uint16_t enemy_id = this->enemies.size();
this->enemies.emplace_back(enemy_id, index, floor, type);
this->enemies.emplace_back(enemy_id, source_index, set_index, floor, type);
};
EnemyType child_type = EnemyType::UNKNOWN;
@@ -1186,14 +1194,14 @@ void Map::add_enemy(
add(EnemyType::UNKNOWN);
this->log.warning(
"(Entry %zu, offset %zX in file) Unknown enemy type %04hX",
index, index * sizeof(EnemyEntry), e.base_type.load());
source_index, source_index * sizeof(EnemyEntry), e.base_type.load());
break;
default:
add(EnemyType::UNKNOWN);
this->log.warning(
"(Entry %zu, offset %zX in file) Invalid enemy type %04hX",
index, index * sizeof(EnemyEntry), e.base_type.load());
source_index, source_index * sizeof(EnemyEntry), e.base_type.load());
break;
}
@@ -1492,7 +1500,7 @@ vector<Map::DATSectionsForFloor> Map::collect_quest_map_data_sections(const void
return ret;
}
void Map::add_enemies_and_objects_from_quest_data(
void Map::add_entities_from_quest_data(
Episode episode,
uint8_t difficulty,
uint8_t event,
+14 -6
View File
@@ -104,18 +104,18 @@ struct Map {
struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0
/* 00 */ le_uint32_t event_id;
/* 04 */ le_uint16_t flags;
/* 06 */ le_uint16_t unknown_a2;
/* 06 */ le_uint16_t event_type;
/* 08 */ le_uint16_t section;
/* 0A */ le_uint16_t wave_number;
/* 0C */ le_uint32_t delay;
/* 10 */ le_uint32_t clear_events_index;
/* 10 */ le_uint32_t action_stream_offset;
/* 14 */
} __attribute__((packed));
struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2'
/* 00 */ le_uint32_t event_id;
/* 04 */ le_uint16_t flags;
/* 06 */ le_uint16_t unknown_a2;
/* 06 */ le_uint16_t event_type;
/* 08 */ le_uint16_t section;
/* 0A */ le_uint16_t wave_number;
/* 0C */ le_uint16_t min_delay;
@@ -123,7 +123,7 @@ struct Map {
/* 10 */ uint8_t min_enemies;
/* 11 */ uint8_t max_enemies;
/* 12 */ le_uint16_t max_waves;
/* 14 */ le_uint32_t clear_events_index;
/* 14 */ le_uint32_t action_stream_offset;
/* 18 */
} __attribute__((packed));
@@ -217,6 +217,10 @@ struct Map {
uint32_t param4;
uint32_t param5;
uint32_t param6;
uint16_t game_flags;
// Technically set_flags shouldn't be part of the Object struct, but all
// object entries always generate exactly one object, so we store it here.
uint16_t set_flags;
bool item_drop_checked;
std::string str() const;
@@ -231,13 +235,16 @@ struct Map {
ITEM_DROPPED = 0x10,
};
size_t source_index;
size_t set_index;
uint16_t enemy_id;
uint16_t total_damage;
uint32_t game_flags; // From 6x0A
EnemyType type;
uint8_t floor;
uint8_t state_flags;
uint8_t last_hit_by_client_id;
Enemy(uint16_t enemy_id, size_t source_index, uint8_t floor, EnemyType type);
Enemy(uint16_t enemy_id, size_t source_index, size_t set_index, uint8_t floor, EnemyType type);
std::string str() const;
} __attribute__((packed));
@@ -300,7 +307,7 @@ struct Map {
};
static std::vector<DATSectionsForFloor> collect_quest_map_data_sections(const void* data, size_t size);
void add_enemies_and_objects_from_quest_data(
void add_entities_from_quest_data(
Episode episode,
uint8_t difficulty,
uint8_t event,
@@ -319,6 +326,7 @@ struct Map {
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
std::vector<Object> objects;
std::vector<Enemy> enemies;
std::vector<uint16_t> enemy_set_flags;
std::vector<size_t> rare_enemy_indexes;
};
+39 -16
View File
@@ -2283,6 +2283,12 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
return;
}
// Note: We always call reconcile_drop_request_with_map, even in client drop
// mode, so that we can correctly mark enemies and objects as having dropped
// their items in persistent games.
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data, size);
auto rec = reconcile_drop_request_with_map(c->log, c->channel, cmd, c->version(), l->episode, c->config, l->map, true);
switch (l->drop_mode) {
case Lobby::DropMode::CLIENT:
forward_subcommand(c, command, flag, data, size);
@@ -2297,9 +2303,6 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
throw logic_error("invalid drop mode");
}
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data, size);
auto rec = reconcile_drop_request_with_map(c->log, c->channel, cmd, c->version(), l->episode, c->config, l->map, true);
if (rec.should_drop) {
auto generate_item = [&]() -> ItemCreator::DropResult {
if (rec.is_box) {
@@ -2570,8 +2573,8 @@ static void on_gol_dragon_actions(shared_ptr<Client> c, uint8_t command, uint8_t
}
}
static void on_enemy_hit(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
const auto& cmd = check_size_t<G_EnemyHitByPlayer_DC_PC_XB_BB_6x0A>(data, size);
static void on_update_enemy_state(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
const auto& cmd = check_size_t<G_UpdateEnemyState_DC_PC_XB_BB_6x0A>(data, size);
if (command_is_private(command)) {
return;
@@ -2581,22 +2584,21 @@ static void on_enemy_hit(shared_ptr<Client> c, uint8_t command, uint8_t, void* d
return;
}
if (l->base_version == Version::BB_V4) {
if (c->lobby_client_id > 3) {
throw logic_error("client ID is above 3");
}
if (!l->map) {
throw runtime_error("game does not have a map loaded");
}
if (c->lobby_client_id > 3) {
throw logic_error("client ID is above 3");
}
if (l->map) {
if (cmd.enemy_index >= l->map->enemies.size()) {
return;
}
auto& enemy = l->map->enemies[cmd.enemy_index];
enemy.last_hit_by_client_id = c->lobby_client_id;
enemy.game_flags = is_big_endian(c->version()) ? bswap32(cmd.flags) : cmd.flags.load();
enemy.total_damage = cmd.total_damage;
l->log.info("E-%hX updated to damage=%hu game_flags=%08" PRIX32, cmd.enemy_index.load(), enemy.total_damage, enemy.game_flags);
}
G_EnemyHitByPlayer_GC_6x0A sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.total_damage, cmd.flags.load()}};
G_UpdateEnemyState_GC_6x0A sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.total_damage, cmd.flags.load()}};
bool sender_is_be = is_big_endian(c->version());
for (auto lc : l->clients) {
if (lc && (lc != c)) {
@@ -2609,6 +2611,27 @@ static void on_enemy_hit(shared_ptr<Client> c, uint8_t command, uint8_t, void* d
}
}
static void on_update_object_state(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
const auto& cmd = check_size_t<G_UpdateObjectState_6x0B>(data, size);
if (command_is_private(command)) {
return;
}
auto l = c->require_lobby();
if (!l->is_game()) {
return;
}
if (l->map) {
if (cmd.object_index >= l->map->objects.size()) {
return;
}
l->map->objects[cmd.object_index].game_flags = cmd.flags;
}
forward_subcommand(c, command, flag, data, size);
}
static void on_charge_attack_bb(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
auto l = c->require_lobby();
if (l->base_version != Version::BB_V4) {
@@ -3809,8 +3832,8 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
/* 6x07 */ {0x07, 0x07, 0x07, on_symbol_chat, SDF::ALWAYS_FORWARD_TO_WATCHERS},
/* 6x08 */ {0x08, 0x08, 0x08, on_invalid},
/* 6x09 */ {0x09, 0x09, 0x09, forward_subcommand_m},
/* 6x0A */ {0x0A, 0x0A, 0x0A, on_enemy_hit},
/* 6x0B */ {0x0B, 0x0B, 0x0B, on_forward_check_game},
/* 6x0A */ {0x0A, 0x0A, 0x0A, on_update_enemy_state},
/* 6x0B */ {0x0B, 0x0B, 0x0B, on_update_object_state},
/* 6x0C */ {0x0C, 0x0C, 0x0C, on_received_condition},
/* 6x0D */ {0x00, 0x00, 0x0D, on_forward_check_game},
/* 6x0E */ {0x00, 0x00, 0x0E, on_forward_check_game},
+75 -31
View File
@@ -2359,6 +2359,45 @@ void send_ep3_change_music(Channel& ch, uint32_t song) {
ch.send(0x60, 0x00, cmd);
}
static 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);
StringWriter w;
if (is_pre_v1(c->version())) {
G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E compressed_header;
compressed_header.header.basic_header.subcommand = (c->version() == Version::DC_NTE) ? dc_nte_sc : dc_11_2000_sc;
compressed_header.header.basic_header.size = 0x00;
compressed_header.header.basic_header.unused = 0x0000;
compressed_header.header.size = (compressed_data.size() + sizeof(G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E) + 3) & (~3);
compressed_header.decompressed_size = size;
w.put(compressed_header);
} else {
G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E compressed_header;
compressed_header.header.basic_header.subcommand = sc;
compressed_header.header.basic_header.size = 0x00;
compressed_header.header.basic_header.unused = 0x0000;
compressed_header.header.size = (compressed_data.size() + sizeof(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E) + 3) & (~3);
compressed_header.decompressed_size = size;
compressed_header.compressed_size = compressed_data.size();
w.put(compressed_header);
}
w.write(compressed_data);
while (w.size() & 3) {
w.put_u8(0x00);
}
if (c->game_join_command_queue) {
c->log.info("Client not ready to receive join commands; adding to queue");
auto& cmd = c->game_join_command_queue->emplace_back();
cmd.command = 0x6D;
cmd.flag = c->lobby_client_id;
cmd.data = std::move(w.str());
} else {
send_command(c, 0x6D, c->lobby_client_id, w.str());
}
}
void send_game_item_state(shared_ptr<Client> c) {
auto l = c->require_lobby();
auto s = c->require_server_state();
@@ -2406,42 +2445,47 @@ void send_game_item_state(shared_ptr<Client> c) {
StringWriter decompressed_w;
decompressed_w.put(decompressed_header);
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);
}
string compressed_data = bc0_compress(decompressed_w.str());
StringWriter w;
if (is_pre_v1(c->version())) {
G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E compressed_header;
compressed_header.header.basic_header.subcommand = (c->version() == Version::DC_NTE) ? 0x5E : 0x65;
compressed_header.header.basic_header.size = 0x00;
compressed_header.header.basic_header.unused = 0x0000;
compressed_header.header.size = (compressed_data.size() + sizeof(G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E) + 3) & (~3);
compressed_header.decompressed_size = decompressed_w.size();
w.put(compressed_header);
} else {
G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E compressed_header;
compressed_header.header.basic_header.subcommand = 0x6D;
compressed_header.header.basic_header.size = 0x00;
compressed_header.header.basic_header.unused = 0x0000;
compressed_header.header.size = (compressed_data.size() + sizeof(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E) + 3) & (~3);
compressed_header.decompressed_size = decompressed_w.size();
compressed_header.compressed_size = compressed_data.size();
w.put(compressed_header);
void send_game_enemy_state(shared_ptr<Client> c) {
auto l = c->require_lobby();
if (!l->map) {
return;
}
w.write(compressed_data);
while (w.size() & 3) {
w.put_u8(0x00);
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.state_flags & Map::Enemy::Flag::ITEM_DROPPED) ? 0xFFFF : (0xCA0 + z);
entry.total_damage = enemy.total_damage;
}
if (c->game_join_command_queue) {
c->log.info("Client not ready to receive join commands; adding to queue");
auto& cmd = c->game_join_command_queue->emplace_back();
cmd.command = 0x6D;
cmd.flag = c->lobby_client_id;
cmd.data = std::move(w.str());
} else {
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;
}
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];
auto& entry = entries.emplace_back();
entry.flags = obj.game_flags;
entry.item_drop_id = (obj.item_drop_checked) ? 0xFFFF : (0x100 + z);
}
send_game_join_sync_command(c, entries.data(), entries.size() * sizeof(entries[0]), 0x5D, 0x64, 0x6C);
}
void send_game_flag_state(shared_ptr<Client> c) {
+2
View File
@@ -305,6 +305,8 @@ void send_ep3_change_music(Channel& ch, uint32_t song);
void send_revive_player(std::shared_ptr<Client> c);
void send_game_item_state(std::shared_ptr<Client> c);
void send_game_enemy_state(std::shared_ptr<Client> c);
void send_game_object_state(std::shared_ptr<Client> c);
void send_game_flag_state(std::shared_ptr<Client> c);
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);