describe 6x6B and 6x6C more completely
This commit is contained in:
+37
-30
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user