implement $item command on non-bb and on proxy

This commit is contained in:
Martin Michelsen
2022-07-30 23:03:46 -07:00
parent 286997188e
commit 442f33733d
11 changed files with 211 additions and 120 deletions
+42 -6
View File
@@ -803,23 +803,59 @@ static void server_command_item(shared_ptr<ServerState>, shared_ptr<Lobby> l,
string data = parse_data_string(encode_sjis(args));
if (data.size() < 2) {
send_text_message(c, u"$C6Item codes must be\n2 bytes or more.");
send_text_message(c, u"$C6Item codes must be\n2 bytes or more");
return;
}
if (data.size() > 16) {
send_text_message(c, u"$C6Item codes must be\n16 bytes or fewer.");
send_text_message(c, u"$C6Item codes must be\n16 bytes or fewer");
return;
}
ItemData item_data;
l->next_drop_item.clear();
if (data.size() <= 12) {
memcpy(&l->next_drop_item.data.data1, data.data(), data.size());
} else {
memcpy(&l->next_drop_item.data.data1, data.data(), 12);
memcpy(&l->next_drop_item.data.data2, data.data() + 12, 12 - data.size());
memcpy(&l->next_drop_item.data.data2, data.data() + 12, data.size() - 12);
}
send_text_message(c, u"$C6Next drop chosen.");
string name = name_for_item(l->next_drop_item.data, true);
send_text_message(c, u"$C7Next drop:\n" + decode_sjis(name));
}
static void proxy_command_item(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string& args) {
if (session.version == GameVersion::BB) {
send_text_message(session.client_channel,
u"$C6This command cannot\nbe used on the proxy\nserver in BB games");
return;
}
if (session.lobby_client_id != session.leader_client_id) {
send_text_message(session.client_channel,
u"$C6You must be the\nleader to use this\ncommand");
return;
}
string data = parse_data_string(encode_sjis(args));
if (data.size() < 2) {
send_text_message(session.client_channel, u"$C6Item codes must be\n2 bytes or more");
return;
}
if (data.size() > 16) {
send_text_message(session.client_channel, u"$C6Item codes must be\n16 bytes or fewer");
return;
}
session.next_drop_item.clear();
if (data.size() <= 12) {
memcpy(&session.next_drop_item.data.data1, data.data(), data.size());
} else {
memcpy(&session.next_drop_item.data.data1, data.data(), 12);
memcpy(&session.next_drop_item.data.data2, data.data() + 12, data.size() - 12);
}
string name = name_for_item(session.next_drop_item.data, true);
send_text_message(session.client_channel, u"$C7Next drop:\n" + decode_sjis(name));
}
@@ -852,7 +888,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
{u"$gc" , {server_command_get_self_card , nullptr , u"Usage:\ngc"}},
{u"$infhp" , {server_command_infinite_hp , proxy_command_infinite_hp , u"Usage:\ninfhp"}},
{u"$inftp" , {server_command_infinite_tp , proxy_command_infinite_tp , u"Usage:\ninftp"}},
{u"$item" , {server_command_item , nullptr , u"Usage:\nitem <item-code>"}},
{u"$item" , {server_command_item , proxy_command_item , u"Usage:\nitem <item-code>"}},
{u"$kick" , {server_command_kick , nullptr , u"Usage:\nkick <name-or-number>"}},
{u"$li" , {server_command_lobby_info , proxy_command_lobby_info , u"Usage:\nli"}},
{u"$maxlevel" , {server_command_max_level , nullptr , u"Usage:\nmax_level <level>"}},
+7 -3
View File
@@ -478,11 +478,11 @@ struct C_MenuItemInfoRequest_09 {
// softlocking the game.
struct S_Unknown_PC_0E {
PlayerLobbyDataPC lobby_data[4]; // This type is a guess
parray<uint8_t, 0x21> unknown_a1;
parray<uint8_t, 0x08> unknown_a1;
parray<uint8_t, 0x18> unknown_a2[4];
parray<uint8_t, 0x18> unknown_a3;
};
// TODO: Document XB format for this. It's probably the same as the GC format.
struct S_Unknown_GC_0E {
PlayerLobbyDataGC lobby_data[4]; // This type is a guess
struct UnknownA0 {
@@ -496,6 +496,10 @@ struct S_Unknown_GC_0E {
uint8_t unknown_a3[4];
};
struct S_Unknown_XB_0E {
parray<uint8_t, 0xE8> unknown_a1;
};
// 0F: Invalid command
// 10 (C->S): Menu selection
+23 -4
View File
@@ -626,6 +626,10 @@ void PlayerLobbyDataBB::clear() {
constexpr uint32_t MESETA_IDENTIFIER = 0x00040000;
ItemData::ItemData() {
this->clear();
}
void ItemData::clear() {
this->data1d[0] = 0;
this->data1d[1] = 0;
this->data1d[2] = 0;
@@ -643,22 +647,37 @@ uint32_t ItemData::primary_identifier() const {
}
}
PlayerInventoryItem::PlayerInventoryItem()
: equip_flags(0x0000), tech_flag(0x0000), game_flags(0x00000000), data() { }
PlayerInventoryItem::PlayerInventoryItem() {
this->clear();
}
PlayerInventoryItem::PlayerInventoryItem(const PlayerBankItem& src)
: tech_flag(0x0001), data(src.data) {
this->equip_flags = (this->data.data1[0] > 2) ? 0x0044 : 0x0050;
}
PlayerBankItem::PlayerBankItem()
: data(), amount(0), show_flags(0) { }
void PlayerInventoryItem::clear() {
this->equip_flags = 0x0000;
this->tech_flag = 0x0000;
this->game_flags = 0x00000000;
this->data.clear();
}
PlayerBankItem::PlayerBankItem() {
this->clear();
}
PlayerBankItem::PlayerBankItem(const PlayerInventoryItem& src)
: data(src.data),
amount(stack_size_for_item(this->data)),
show_flags(1) { }
void PlayerBankItem::clear() {
this->data.clear();
this->amount = 0;
this->show_flags = 0;
}
PlayerInventory::PlayerInventory()
+3
View File
@@ -28,6 +28,7 @@ struct ItemData { // 0x14 bytes
} __attribute__((packed));
ItemData();
void clear();
uint32_t primary_identifier() const;
} __attribute__((packed));
@@ -42,6 +43,7 @@ struct PlayerInventoryItem { // 0x1C bytes
PlayerInventoryItem();
PlayerInventoryItem(const PlayerBankItem&);
void clear();
} __attribute__((packed));
struct PlayerBankItem { // 0x18 bytes
@@ -51,6 +53,7 @@ struct PlayerBankItem { // 0x18 bytes
PlayerBankItem();
PlayerBankItem(const PlayerInventoryItem&);
void clear();
} __attribute__((packed));
struct PlayerInventory { // 0x34C bytes
+42 -1
View File
@@ -676,6 +676,30 @@ static HandlerResult process_server_60_62_6C_6D_C9_CB(shared_ptr<ServerState>,
}
}
if (!data.empty() &&
session.next_drop_item.data.data1d[0] &&
(session.version != GameVersion::BB)) {
if (data[0] == 0x60) {
const auto& cmd = check_size_t<G_EnemyDropItemRequest_6x60>(data);
session.next_drop_item.data.id = session.next_item_id++;
send_drop_item(session.server_channel, session.next_drop_item.data,
true, cmd.area, cmd.x, cmd.z, cmd.request_id);
send_drop_item(session.client_channel, session.next_drop_item.data,
true, cmd.area, cmd.x, cmd.z, cmd.request_id);
session.next_drop_item.clear();
return HandlerResult::Type::SUPPRESS;
} else if (data[0] == -0x5E) { // A2
const auto& cmd = check_size_t<G_BoxItemDropRequest_6xA2>(data);
session.next_drop_item.data.id = session.next_item_id++;
send_drop_item(session.server_channel, session.next_drop_item.data,
false, cmd.area, cmd.x, cmd.z, cmd.request_id);
send_drop_item(session.client_channel, session.next_drop_item.data,
false, cmd.area, cmd.x, cmd.z, cmd.request_id);
session.next_drop_item.clear();
return HandlerResult::Type::SUPPRESS;
}
}
return HandlerResult::Type::FORWARD;
}
@@ -766,6 +790,16 @@ static HandlerResult process_server_gc_B8(shared_ptr<ServerState>,
return HandlerResult::Type::FORWARD;
}
static void update_leader_id(ProxyServer::LinkedSession& session, uint8_t leader_id) {
if (session.leader_client_id != leader_id) {
session.leader_client_id = leader_id;
session.log.info("Changed room leader to %zu", session.leader_client_id);
if (session.leader_client_id == session.lobby_client_id) {
send_text_message(session.client_channel, u"$C6You are now the leader");
}
}
}
template <typename CmdT>
static HandlerResult process_server_65_67_68(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) {
@@ -789,6 +823,7 @@ static HandlerResult process_server_65_67_68(shared_ptr<ServerState>,
bool modified = false;
session.lobby_client_id = cmd.client_id;
update_leader_id(session, cmd.leader_id);
for (size_t x = 0; x < flag; x++) {
size_t index = cmd.entries[x].lobby_data.client_id;
if (index >= session.lobby_players.size()) {
@@ -840,6 +875,7 @@ static HandlerResult process_server_64(shared_ptr<ServerState>,
bool modified = false;
session.lobby_client_id = cmd->client_id;
update_leader_id(session, cmd->leader_id);
for (size_t x = 0; x < flag; x++) {
if (cmd->lobby_data[x].guild_card == session.remote_guild_card_number) {
cmd->lobby_data[x].guild_card = session.license->serial_number;
@@ -885,6 +921,7 @@ static HandlerResult process_server_66_69(shared_ptr<ServerState>,
session.lobby_players[index].name.clear();
session.log.info("Removed lobby player (%zu)", index);
}
update_leader_id(session, cmd.leader_id);
return HandlerResult::Type::FORWARD;
}
@@ -974,7 +1011,11 @@ static HandlerResult process_client_60_62_6C_6D_C9_CB(shared_ptr<ServerState> s,
if (cmd.guild_card_number == session.license->serial_number) {
cmd.guild_card_number = session.remote_guild_card_number;
}
} else if (data[0] == 0x2F || data[0] == 0x4C) {
}
}
if (!data.empty()) {
if (data[0] == 0x2F || data[0] == 0x4C) {
if (session.infinite_hp) {
vector<PSOSubcommand> subs;
for (size_t amount = 1020; amount > 0;) {
+3 -1
View File
@@ -445,11 +445,13 @@ ProxyServer::LinkedSession::LinkedSession(
infinite_tp(false),
save_files(false),
function_call_return_value(-1),
next_item_id(0x0F000000),
override_section_id(-1),
override_lobby_event(-1),
override_lobby_number(-1),
lobby_players(12),
lobby_client_id(0) {
lobby_client_id(0),
leader_client_id(0) {
this->last_switch_enabled_command.subcommand = 0;
memset(this->prev_server_command_bytes, 0, sizeof(this->prev_server_command_bytes));
}
+3
View File
@@ -66,6 +66,8 @@ public:
bool save_files;
int64_t function_call_return_value; // -1 = don't block function calls
G_SwitchStateChanged_6x05 last_switch_enabled_command;
PlayerInventoryItem next_drop_item;
uint32_t next_item_id;
int16_t override_section_id;
int16_t override_lobby_event;
int16_t override_lobby_number;
@@ -78,6 +80,7 @@ public:
};
std::vector<LobbyPlayer> lobby_players;
size_t lobby_client_id;
size_t leader_client_id;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
+77 -103
View File
@@ -671,63 +671,91 @@ static void process_subcommand_sort_inventory_bb(shared_ptr<ServerState>,
////////////////////////////////////////////////////////////////////////////////
// EXP/Drop Item commands
static void process_subcommand_enemy_drop_item_request(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
if (l->version == GameVersion::BB) {
const auto* cmd = check_size_sc<G_EnemyDropItemRequest_6x60>(data);
static bool drop_item(
std::shared_ptr<Lobby> l,
int64_t enemy_id,
uint8_t area,
float x,
float z,
uint16_t request_id) {
if (!l->is_game()) {
return;
}
if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) {
throw logic_error("item tracking not enabled in BB game");
}
PlayerInventoryItem item;
// If there's an override item set (via the $item command), use that item code
if (l->next_drop_item.data.data1d[0]) {
item = l->next_drop_item;
l->next_drop_item.clear();
// If the game is BB, run the rare + common drop logic
} else if (l->version == GameVersion::BB) {
if (!l->common_item_creator.get()) {
throw runtime_error("received box drop subcommand without item creator present");
}
PlayerInventoryItem item;
// TODO: Deduplicate this code with the box drop item request handler
bool is_rare = false;
if (l->next_drop_item.data.data1d[0]) {
item = l->next_drop_item;
l->next_drop_item.data.data1d[0] = 0;
} else {
if (l->rare_item_set) {
if (cmd->enemy_id <= 0x65) {
is_rare = sample_rare_item(*
l->random, l->rare_item_set->rares[cmd->enemy_id].probability);
}
}
if (is_rare) {
const auto& code = l->rare_item_set->rares[cmd->enemy_id].item_code;
item.data.data1[0] = code[0];
item.data.data1[1] = code[1];
item.data.data1[2] = code[2];
//RandPercentages();
if (item.data.data1d[0] == 0) {
item.data.data1[4] |= 0x80; // make it unidentified if it's a weapon
const RareItemDrop* rare_drop = nullptr;
if (l->rare_item_set) {
if (enemy_id < 0) {
for (size_t z = 0; z < 30; z++) {
if (l->rare_item_set->box_areas[z] != area) {
continue;
}
if (sample_rare_item(
*l->random, l->rare_item_set->box_rares[z].probability)) {
rare_drop = &l->rare_item_set->box_rares[z];
break;
}
}
} else {
try {
item.data = l->common_item_creator->create_drop_item(false, l->episode,
l->difficulty, cmd->area, l->section_id);
} catch (const out_of_range&) {
// create_common_item throws this when it doesn't want to make an item
return;
if ((enemy_id <= 0x65) &&
sample_rare_item(
*l->random, l->rare_item_set->rares[enemy_id].probability)) {
rare_drop = &l->rare_item_set->rares[enemy_id];
}
}
}
item.data.id = l->generate_item_id(0xFF);
l->add_item(item, cmd->area, cmd->x, cmd->z);
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z,
cmd->request_id);
if (rare_drop) {
item.data.data1[0] = rare_drop->item_code[0];
item.data.data1[1] = rare_drop->item_code[1];
item.data.data1[2] = rare_drop->item_code[2];
// TODO: Add random percentages
if (item.data.data1d[0] == 0) {
item.data.data1[4] |= 0x80; // make it unidentified if it's a weapon
}
} else {
try {
item.data = l->common_item_creator->create_drop_item(
false, l->episode, l->difficulty, area, l->section_id);
} catch (const out_of_range&) {
// create_common_item throws this when it doesn't want to make an item
return true;
}
}
// If the game is not BB and there's no override item, forward the request to
// the leader instead of generating the item drop command
} else {
return false;
}
item.data.id = l->generate_item_id(0xFF);
if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) {
l->add_item(item, area, x, z);
}
send_drop_item(l, item.data, (enemy_id >= 0), area, x, z, request_id);
return true;
}
static void process_subcommand_enemy_drop_item_request(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
if (!l->is_game()) {
return;
}
const auto* cmd = check_size_sc<G_EnemyDropItemRequest_6x60>(data);
if (!drop_item(l, cmd->enemy_id, cmd->area, cmd->x, cmd->z, cmd->request_id)) {
forward_subcommand(l, c, command, flag, data);
}
}
@@ -735,66 +763,12 @@ static void process_subcommand_enemy_drop_item_request(shared_ptr<ServerState>,
static void process_subcommand_box_drop_item_request(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
if (l->version == GameVersion::BB) {
const auto* cmd = check_size_sc<G_BoxItemDropRequest_6xA2>(data);
if (!l->is_game()) {
return;
}
if (!l->is_game()) {
return;
}
if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) {
throw logic_error("item tracking not enabled in BB game");
}
if (!l->common_item_creator.get()) {
throw runtime_error("received box drop subcommand without item creator present");
}
PlayerInventoryItem item;
bool is_rare = false;
if (l->next_drop_item.data.data1d[0]) {
item = l->next_drop_item;
l->next_drop_item.data.data1d[0] = 0;
} else {
size_t index;
if (l->rare_item_set) {
for (index = 0; index < 30; index++) {
if (l->rare_item_set->box_areas[index] != cmd->area) {
continue;
}
if (sample_rare_item(
*l->random, l->rare_item_set->box_rares[index].probability)) {
is_rare = true;
break;
}
}
}
if (is_rare) {
const auto& code = l->rare_item_set->box_rares[index].item_code;
item.data.data1[0] = code[0];
item.data.data1[1] = code[1];
item.data.data1[2] = code[2];
//RandPercentages();
if (item.data.data1d[0] == 0) {
item.data.data1[4] |= 0x80; // make it unidentified if it's a weapon
}
} else {
try {
item.data = l->common_item_creator->create_drop_item(true, l->episode,
l->difficulty, cmd->area, l->section_id);
} catch (const out_of_range&) {
// create_common_item throws this when it doesn't want to make an item
return;
}
}
}
item.data.id = l->generate_item_id(0xFF);
l->add_item(item, cmd->area, cmd->x, cmd->z);
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z,
cmd->request_id);
} else {
const auto* cmd = check_size_sc<G_BoxItemDropRequest_6xA2>(data);
if (!drop_item(l, -1, cmd->area, cmd->x, cmd->z, cmd->request_id)) {
forward_subcommand(l, c, command, flag, data);
}
}
+7
View File
@@ -1267,6 +1267,13 @@ void send_revive_player(shared_ptr<Lobby> l, shared_ptr<Client> c) {
////////////////////////////////////////////////////////////////////////////////
// BB game commands
void send_drop_item(Channel& ch, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) {
G_DropItem_6x5F cmd = {
0x5F, 0x0B, 0x0000, area, from_enemy, request_id, x, z, 0, item, 0};
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
}
void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) {
G_DropItem_6x5F cmd = {
+2
View File
@@ -208,6 +208,8 @@ void send_set_player_visibility(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, bool visible);
void send_revive_player(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c);
void send_drop_item(Channel& ch, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id);
void send_drop_item(std::shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id);
void send_drop_stacked_item(std::shared_ptr<Lobby> l, const ItemData& item,