enable item tracking on NTE and 11/2000 and make $item work

This commit is contained in:
Martin Michelsen
2023-12-16 17:44:17 -08:00
parent 74604788c9
commit f14f7dd93b
10 changed files with 466 additions and 469 deletions
+3
View File
@@ -1870,6 +1870,9 @@ struct SplitCommand {
// command, and to execute the command and block the chat if it is.
void on_chat_command(std::shared_ptr<Client> c, const std::string& text) {
SplitCommand cmd(text);
if (!cmd.name.empty() && cmd.name[0] == '@') {
cmd.name[0] = '$';
}
const ChatCommandDefinition* def = nullptr;
try {
+22 -13
View File
@@ -3953,7 +3953,11 @@ struct G_DestroyNPC_6x1C {
// 6x1D: Invalid subcommand
// 6x1E: Invalid subcommand
// 6x1F: Set player floor
// 6x1F: Set player floor and request positions
struct G_SetPlayerFloor_DCNTE_6x1F {
G_ClientIDHeader header;
} __packed__;
struct G_SetPlayerFloor_6x1F {
G_ClientIDHeader header;
@@ -3961,8 +3965,8 @@ struct G_SetPlayerFloor_6x1F {
} __packed__;
// 6x20: Set position
// Existing clients send this when a new client joins a lobby/game, so the new
// client knows where to place them.
// Existing clients send this in response to a 6x1F command when a new client
// joins a lobby or game, so the new client knows where to place them.
struct G_SetPosition_6x20 {
G_ClientIDHeader header;
@@ -4565,7 +4569,13 @@ struct G_UseBossWarp_6x6A {
le_uint16_t unused = 0;
} __packed__;
// 6x6B: Sync enemy state (used while loading into game; same header format as 6E)
// 6x6B: Sync enemy state (used while loading into game)
struct G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E {
G_ExtendedHeader<G_UnusedHeader> header;
le_uint32_t decompressed_size = 0;
// BC0-compressed data follows here (see bc0_decompress)
} __packed__;
struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E {
G_ExtendedHeader<G_UnusedHeader> header;
@@ -4576,7 +4586,6 @@ struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E {
// Decompressed format is a list of these
struct G_SyncEnemyState_6x6B_Entry_Decompressed {
// TODO: Verify this format on DC and PC. It appears correct for GC and BB.
le_uint32_t flags = 0;
le_uint16_t last_attacker = 0;
le_uint16_t remaining_hp = 0;
@@ -4591,7 +4600,6 @@ struct G_SyncEnemyState_6x6B_Entry_Decompressed {
// Decompressed format is a list of these
struct G_SyncObjectState_6x6C_Entry_Decompressed {
// TODO: Verify this format on DC and PC. It appears correct for GC and BB.
le_uint16_t flags = 0;
le_uint16_t object_index = 0;
} __packed__;
@@ -4641,7 +4649,6 @@ struct G_SyncItemState_6x6D_Decompressed {
// Compressed format is the same as 6x6B.
struct G_SyncFlagState_6x6E_Decompressed {
// TODO: Verify this format on DC and PC. It appears correct for GC and BB.
// 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.
@@ -4663,9 +4670,11 @@ struct G_SetQuestFlags_6x6F {
// 6x70: Sync player disp data and inventory (used while loading into game)
// Annoyingly, they didn't use the same format as the 65/67/68 commands here,
// and instead rearranged a bunch of things.
// The format appears to be the same for all pre-BB PSO versions, although
// Episode 3 does not send this command at all since the relevant data is sent
// to the joining player in the 64 command instead.
// The format appears to be the same for all pre-BB PSO versions except DC NTE,
// although Episode 3 does not send this command at all since the relevant data
// is sent to the joining player in the 64 command instead.
// TODO: Document DC NTE format, and check if DC 11/2000 format is the same.
struct G_SyncPlayerDispAndInventory_DC_PC_GC_6x70 {
// Offsets in this struct are relative to the overall command header
@@ -4682,9 +4691,9 @@ struct G_SyncPlayerDispAndInventory_DC_PC_GC_6x70 {
/* 0024 */ le_uint32_t angle_y;
/* 0028 */ le_uint32_t angle_z;
/* 002C */ le_uint16_t unknown_a3a;
/* 002C */ le_uint16_t current_hp;
/* 002C */ le_uint16_t bonus_hp_from_materials;
/* 002C */ le_uint16_t bonus_tp_from_materials;
/* 002E */ le_uint16_t current_hp;
/* 0030 */ le_uint16_t bonus_hp_from_materials; // Missing on DC NTE
/* 0032 */ le_uint16_t bonus_tp_from_materials; // Missing on DC NTE
/* 0034 */ parray<parray<le_uint32_t, 3>, 5> unknown_a4;
/* 0070 */ le_uint32_t language = 0;
/* 0074 */ le_uint32_t player_tag = 0;
+6 -5
View File
@@ -479,10 +479,11 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
this->next_game_item_id = m.reassign_all_item_ids(this->next_game_item_id);
}
}
// We don't consume item IDs here because the 6F handler will do it for
// real; we just want to see what they would be when the join command is
// sent
this->assign_inventory_and_bank_item_ids(c, false);
// On DC NTE and 11/2000, the game assigns item IDs immediately when a
// player joins a game, then assigns them again after the 6x6D equivalent is
// received. For this reason, we consume item IDs here only if the client is
// NTE or 11/2000.
this->assign_inventory_and_bank_item_ids(c, is_pre_v1(c->version()));
}
// If the lobby is recording a battle record, add the player join event
@@ -713,7 +714,7 @@ void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consum
if (!consume_ids) {
this->next_item_id_for_client[c->lobby_client_id] = start_item_id;
}
if (c->log.info("Assigned inventory item IDs")) {
if (c->log.info("Assigned inventory item IDs%s", consume_ids ? "" : " but did not mark IDs as used")) {
p->print_inventory(stderr, c->version(), c->require_server_state()->item_name_index);
if (p->bank.num_items) {
p->bank.assign_ids(0x99000000 + (c->lobby_client_id << 20));
+5 -4
View File
@@ -1595,13 +1595,14 @@ static HandlerResult C_06(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
return HandlerResult::Type::SUPPRESS;
}
bool is_command = (text[0] == '$') ||
(text[0] == '\t' && text[1] != 'C' && text[2] == '$');
char command_sentinel = (ses->version() == Version::DC_V1_11_2000_PROTOTYPE) ? '@' : '$';
bool is_command = (text[0] == command_sentinel) ||
(text[0] == '\t' && text[1] != 'C' && text[2] == command_sentinel);
if (is_command && ses->config.check_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED)) {
size_t offset = ((text[0] & 0xF0) == 0x40) ? 1 : 0;
offset += (text[offset] == '$') ? 0 : 2;
offset += (text[offset] == command_sentinel) ? 0 : 2;
text = text.substr(offset);
if (text.size() >= 2 && text[1] == '$') {
if (text.size() >= 2 && text[1] == command_sentinel) {
if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) {
send_chat_message_from_client(ses->server_channel, add_color(text.substr(1)), private_flags);
} else {
+3 -2
View File
@@ -3084,8 +3084,9 @@ static void on_06(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
return;
}
if (text[0] == '$') {
if (text[1] == '$') {
char command_sentinel = (c->version() == Version::DC_V1_11_2000_PROTOTYPE) ? '@' : '$';
if (text[0] == command_sentinel) {
if (text[1] == command_sentinel) {
text = text.substr(1);
} else {
on_chat_command(c, text);
+381 -419
View File
File diff suppressed because it is too large Load Diff
+40 -22
View File
@@ -24,6 +24,16 @@ using namespace std;
extern const char* QUEST_BARRIER_DISCONNECT_HOOK_NAME;
inline uint8_t get_pre_v1_subcommand(Version v, uint8_t nte_subcommand, uint8_t proto_subcommand, uint8_t final_subcommand) {
if (v == Version::DC_NTE) {
return nte_subcommand;
} else if (v == Version::DC_V1_11_2000_PROTOTYPE) {
return proto_subcommand;
} else {
return final_subcommand;
}
}
const unordered_set<uint32_t> v2_crypt_initial_client_commands({
0x00260088, // (17) DCNTE license check
0x00B0008B, // (02) DCNTE login
@@ -2297,13 +2307,6 @@ void send_ep3_change_music(Channel& ch, uint32_t song) {
ch.send(0x60, 0x00, cmd);
}
void send_set_player_visibility(shared_ptr<Lobby> l, shared_ptr<Client> c, bool visible) {
uint8_t subcmd = visible ? 0x23 : 0x22;
uint16_t client_id = c->lobby_client_id;
G_SetPlayerVisibility_6x22_6x23 cmd = {{subcmd, 0x01, client_id}};
send_command_t(l, 0x60, 0x00, cmd);
}
void send_game_item_state(shared_ptr<Client> c) {
auto l = c->require_lobby();
auto s = c->require_server_state();
@@ -2340,16 +2343,25 @@ void send_game_item_state(shared_ptr<Client> c) {
string compressed_data = bc0_compress(decompressed_w.str());
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();
StringWriter w;
w.put(compressed_header);
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);
}
w.write(compressed_data);
while (w.size() & 3) {
w.put_u8(0x00);
@@ -2368,8 +2380,9 @@ void send_game_item_state(shared_ptr<Client> 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 = {
{{0x5F, 0x0B, 0x0000}, {floor, from_enemy, entity_id, x, z, 0, 0, item}}, 0};
{{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_version(ch.version));
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
}
@@ -2387,7 +2400,8 @@ void send_drop_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item,
void send_drop_stacked_item_to_channel(
shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z) {
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{0x5D, 0x0A, 0x0000}, floor, 0, x, z, item}, 0};
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};
cmd.item_data.encode_for_version(ch.version, s->item_parameter_table_for_version(ch.version));
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
}
@@ -2403,7 +2417,8 @@ void send_drop_stacked_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item,
}
void send_pick_up_item_to_client(shared_ptr<Client> c, uint8_t client_id, uint32_t item_id, uint8_t floor) {
G_PickUpItem_6x59 cmd = {{0x59, 0x03, client_id}, client_id, floor, item_id};
uint8_t subcommand = get_pre_v1_subcommand(c->version(), 0x4B, 0x52, 0x59);
G_PickUpItem_6x59 cmd = {{subcommand, 0x03, client_id}, client_id, floor, item_id};
send_command_t(c, 0x60, 0x00, cmd);
}
@@ -2433,7 +2448,8 @@ void send_create_inventory_item_to_lobby(shared_ptr<Client> c, uint8_t client_id
void send_destroy_item_to_lobby(shared_ptr<Client> c, uint32_t item_id, uint32_t amount, bool exclude_c) {
auto l = c->require_lobby();
uint16_t client_id = c->lobby_client_id;
G_DeleteInventoryItem_6x29 cmd = {{0x29, 0x03, client_id}, item_id, amount};
uint8_t subcommand = get_pre_v1_subcommand(c->version(), 0x25, 0x27, 0x29);
G_DeleteInventoryItem_6x29 cmd = {{subcommand, 0x03, client_id}, item_id, amount};
if (exclude_c) {
send_command_excluding_client(l, c, 0x60, 0x00, &cmd, sizeof(cmd));
} else {
@@ -2442,7 +2458,8 @@ void send_destroy_item_to_lobby(shared_ptr<Client> c, uint32_t item_id, uint32_t
}
void send_destroy_floor_item_to_client(shared_ptr<Client> c, uint32_t item_id, uint32_t floor) {
G_DestroyFloorItem_6x63 cmd = {{0x63, 0x03, 0x0000}, item_id, floor};
uint8_t subcommand = get_pre_v1_subcommand(c->version(), 0x55, 0x5C, 0x63);
G_DestroyFloorItem_6x63 cmd = {{subcommand, 0x03, 0x0000}, item_id, floor};
send_command_t(c, 0x60, 0x00, cmd);
}
@@ -2511,8 +2528,9 @@ void send_level_up(shared_ptr<Client> c) {
} catch (const out_of_range&) {
}
uint8_t subcommand = get_pre_v1_subcommand(c->version(), 0x2C, 0x2E, 0x30);
G_LevelUp_6x30 cmd = {
{0x30, sizeof(G_LevelUp_6x30) / 4, c->lobby_client_id},
{subcommand, sizeof(G_LevelUp_6x30) / 4, c->lobby_client_id},
stats.atp + (mag ? ((mag->data1w[3] / 100) * 2) : 0),
stats.mst + (mag ? ((mag->data1w[5] / 100) * 2) : 0),
stats.evp,
-1
View File
@@ -295,7 +295,6 @@ void send_warp(std::shared_ptr<Client> c, uint32_t floor, bool is_private);
void send_warp(std::shared_ptr<Lobby> l, uint32_t floor, bool is_private);
void send_ep3_change_music(Channel& ch, uint32_t song);
void send_set_player_visibility(std::shared_ptr<Client> c, bool visible);
void send_revive_player(std::shared_ptr<Client> c);
void send_game_item_state(std::shared_ptr<Client> c);
+3
View File
@@ -31,6 +31,9 @@ Version enum_for_name<Version>(const char* name);
inline bool is_patch(Version version) {
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
}
inline bool is_pre_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE);
}
inline bool is_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1);
}