add $what command

This commit is contained in:
Martin Michelsen
2022-04-30 16:40:33 -07:00
parent f7c7dda765
commit b8f1b04bee
18 changed files with 2034 additions and 534 deletions
+1
View File
@@ -59,6 +59,7 @@ add_executable(newserv
src/ServerShell.cc
src/ServerState.cc
src/Shell.cc
src/StaticGameData.cc
src/Text.cc
src/Version.cc
)
+34 -297
View File
@@ -13,307 +13,11 @@
#include "Client.hh"
#include "SendCommands.hh"
#include "Text.hh"
#include "StaticGameData.hh"
using namespace std;
////////////////////////////////////////////////////////////////////////////////
const vector<string> section_id_to_name({
"Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria",
"Oran", "Yellowboze", "Whitill"});
const unordered_map<string, uint8_t> name_to_section_id({
{"viridia", 0},
{"greennill", 1},
{"skyly", 2},
{"bluefull", 3},
{"purplenum", 4},
{"pinkal", 5},
{"redria", 6},
{"oran", 7},
{"yellowboze", 8},
{"whitill", 9}});
const vector<string> lobby_event_to_name({
"none", "xmas", "none", "val", "easter", "hallo", "sonic", "newyear",
"summer", "white", "wedding", "fall", "s-spring", "s-summer", "spring"});
const unordered_map<string, uint8_t> name_to_lobby_event({
{"none", 0},
{"xmas", 1},
{"val", 3},
{"easter", 4},
{"hallo", 5},
{"sonic", 6},
{"newyear", 7},
{"summer", 8},
{"white", 9},
{"wedding", 10},
{"fall", 11},
{"s-spring", 12},
{"s-summer", 13},
{"spring", 14},
});
const unordered_map<uint8_t, string> lobby_type_to_name({
{0x00, "normal"},
{0x0F, "inormal"},
{0x10, "ipc"},
{0x11, "iball"},
{0x67, "cave2u"},
{0xD4, "cave1"},
{0xE9, "planet"},
{0xEA, "clouds"},
{0xED, "cave"},
{0xEE, "jungle"},
{0xEF, "forest2-2"},
{0xF0, "forest2-1"},
{0xF1, "windpower"},
{0xF2, "overview"},
{0xF3, "seaside"},
{0xF4, "some?"},
{0xF5, "dmorgue"},
{0xF6, "caelum"},
{0xF8, "digital"},
{0xF9, "boss1"},
{0xFA, "boss2"},
{0xFB, "boss3"},
{0xFC, "dragon"},
{0xFD, "derolle"},
{0xFE, "volopt"},
{0xFF, "darkfalz"},
});
const unordered_map<string, uint8_t> name_to_lobby_type({
{"normal", 0x00},
{"inormal", 0x0F},
{"ipc", 0x10},
{"iball", 0x11},
{"cave1", 0xD4},
{"cave2u", 0x67},
{"dragon", 0xFC},
{"derolle", 0xFD},
{"volopt", 0xFE},
{"darkfalz", 0xFF},
{"planet", 0xE9},
{"clouds", 0xEA},
{"cave", 0xED},
{"jungle", 0xEE},
{"forest2-2", 0xEF},
{"forest2-1", 0xF0},
{"windpower", 0xF1},
{"overview", 0xF2},
{"seaside", 0xF3},
{"some?", 0xF4},
{"dmorgue", 0xF5},
{"caelum", 0xF6},
{"digital", 0xF8},
{"boss1", 0xF9},
{"boss2", 0xFA},
{"boss3", 0xFB},
{"knight", 0xFC},
{"sky", 0xFE},
{"morgue", 0xFF},
});
const vector<string> tech_id_to_name({
"foie", "gifoie", "rafoie",
"barta", "gibarta", "rabarta",
"zonde", "gizonde", "razonde",
"grants", "deband", "jellen", "zalure", "shifta",
"ryuker", "resta", "anti", "reverser", "megid"});
const unordered_map<string, uint8_t> name_to_tech_id({
{"foie", 0},
{"gifoie", 1},
{"rafoie", 2},
{"barta", 3},
{"gibarta", 4},
{"rabarta", 5},
{"zonde", 6},
{"gizonde", 7},
{"razonde", 8},
{"grants", 9},
{"deband", 10},
{"jellen", 11},
{"zalure", 12},
{"shifta", 13},
{"ryuker", 14},
{"resta", 15},
{"anti", 16},
{"reverser", 17},
{"megid", 18},
});
const vector<string> npc_id_to_name({
"ninja", "rico", "sonic", "knuckles", "tails", "flowen", "elly"});
const unordered_map<string, uint8_t> name_to_npc_id({
{"ninja", 0},
{"rico", 1},
{"sonic", 2},
{"knuckles", 3},
{"tails", 4},
{"flowen", 5},
{"elly", 6}});
const string& name_for_section_id(uint8_t section_id) {
if (section_id < section_id_to_name.size()) {
return section_id_to_name[section_id];
} else {
static const string ret = "<Unknown section id>";
return ret;
}
}
u16string u16name_for_section_id(uint8_t section_id) {
return decode_sjis(name_for_section_id(section_id));
}
uint8_t section_id_for_name(const string& name) {
try {
return name_to_section_id.at(name);
} catch (const out_of_range&) { }
try {
uint64_t x = stoul(name);
if (x < section_id_to_name.size()) {
return x;
}
} catch (const invalid_argument&) {
} catch (const out_of_range&) { }
return 0xFF;
}
uint8_t section_id_for_name(const u16string& name) {
return section_id_for_name(encode_sjis(name));
}
const string& name_for_event(uint8_t event) {
if (event < lobby_event_to_name.size()) {
return lobby_event_to_name[event];
} else {
static const string ret = "<Unknown lobby event>";
return ret;
}
}
u16string u16name_for_event(uint8_t event) {
return decode_sjis(name_for_event(event));
}
uint8_t event_for_name(const string& name) {
try {
return name_to_lobby_event.at(name);
} catch (const out_of_range&) { }
try {
uint64_t x = stoul(name);
if (x < lobby_event_to_name.size()) {
return x;
}
} catch (const invalid_argument&) {
} catch (const out_of_range&) { }
return 0xFF;
}
uint8_t event_for_name(const u16string& name) {
return event_for_name(encode_sjis(name));
}
const string& name_for_lobby_type(uint8_t type) {
try {
return lobby_type_to_name.at(type);
} catch (const out_of_range&) {
static const string ret = "<Unknown lobby type>";
return ret;
}
}
u16string u16name_for_lobby_type(uint8_t type) {
return decode_sjis(name_for_lobby_type(type));
}
uint8_t lobby_type_for_name(const string& name) {
try {
return name_to_lobby_type.at(name);
} catch (const out_of_range&) { }
try {
uint64_t x = stoul(name);
if (x < lobby_type_to_name.size()) {
return x;
}
} catch (const invalid_argument&) {
} catch (const out_of_range&) { }
return 0x80;
}
uint8_t lobby_type_for_name(const u16string& name) {
return lobby_type_for_name(encode_sjis(name));
}
const string& name_for_technique(uint8_t tech) {
try {
return tech_id_to_name.at(tech);
} catch (const out_of_range&) {
static const string ret = "<Unknown technique>";
return ret;
}
}
u16string u16name_for_technique(uint8_t tech) {
return decode_sjis(name_for_technique(tech));
}
uint8_t technique_for_name(const string& name) {
try {
return name_to_tech_id.at(name);
} catch (const out_of_range&) { }
try {
uint64_t x = stoul(name);
if (x < tech_id_to_name.size()) {
return x;
}
} catch (const invalid_argument&) {
} catch (const out_of_range&) { }
return 0xFF;
}
uint8_t technique_for_name(const u16string& name) {
return technique_for_name(encode_sjis(name));
}
const string& name_for_npc(uint8_t npc) {
try {
return npc_id_to_name.at(npc);
} catch (const out_of_range&) {
static const string ret = "<Unknown NPC>";
return ret;
}
}
u16string u16name_for_npc(uint8_t npc) {
return decode_sjis(name_for_npc(npc));
}
uint8_t npc_for_name(const string& name) {
try {
return name_to_npc_id.at(name);
} catch (const out_of_range&) { }
try {
uint64_t x = stoul(name);
if (x < npc_id_to_name.size()) {
return x;
}
} catch (const invalid_argument&) {
} catch (const out_of_range&) { }
return 0xFF;
}
uint8_t npc_for_name(const u16string& name) {
return npc_for_name(encode_sjis(name));
}
////////////////////////////////////////////////////////////////////////////////
// Checks
@@ -861,6 +565,38 @@ static void command_next(shared_ptr<ServerState>, shared_ptr<Lobby> l,
send_warp(c, new_area);
}
static void command_what(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_is_game(l, true);
if (!l->episode || (l->episode > 3)) {
return;
}
float min_dist2 = 0.0f;
uint32_t nearest_item_id = 0xFFFFFFFF;
for (const auto& it : l->item_id_to_floor_item) {
if (it.second.area != c->area) {
continue;
}
float dx = it.second.x - c->x;
float dz = it.second.z - c->z;
float dist2 = (dx * dx) + (dz * dz);
if ((nearest_item_id == 0xFFFFFFFF) || (dist2 < min_dist2)) {
nearest_item_id = it.first;
min_dist2 = dist2;
}
}
if (nearest_item_id == 0xFFFFFFFF) {
send_text_message(c, u"No items are near you");
} else {
const auto& item = l->item_id_to_floor_item.at(nearest_item_id);
string name = name_for_item(item.inv_item.data);
send_text_message_printf(c, "$C6%s\n$C7ID: %08" PRIX32,
name.c_str(), item.inv_item.data.item_id.load());
}
}
static void command_song(shared_ptr<ServerState>, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string& args) {
check_is_ep3(c, true);
@@ -961,6 +697,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
{u"$swa" , {command_switch_assist , u"Usage:\nswa"}},
{u"$type" , {command_lobby_type , u"Usage:\ntype <name>"}},
{u"$warp" , {command_warp , u"Usage:\nwarp <area-number>"}},
{u"$what" , {command_what , u"Usage:\nwhat"}},
});
// This function is called every time any player sends a chat beginning with a
-25
View File
@@ -9,30 +9,5 @@
#include "Lobby.hh"
#include "Client.hh"
const std::string& name_for_section_id(uint8_t section_id);
std::u16string u16name_for_section_id(uint8_t section_id);
uint8_t section_id_for_name(const std::string& name);
uint8_t section_id_for_name(const std::u16string& name);
const std::string& name_for_event(uint8_t event);
std::u16string u16name_for_event(uint8_t event);
uint8_t event_for_name(const std::string& name);
uint8_t event_for_name(const std::u16string& name);
const std::string& name_for_lobby_type(uint8_t type);
std::u16string u16name_for_lobby_type(uint8_t type);
uint8_t lobby_type_for_name(const std::string& name);
uint8_t lobby_type_for_name(const std::u16string& name);
const std::string& name_for_technique(uint8_t tech);
std::u16string u16name_for_technique(uint8_t tech);
uint8_t technique_for_name(const std::string& name);
uint8_t technique_for_name(const std::u16string& name);
const std::string& name_for_npc(uint8_t npc);
std::u16string u16name_for_npc(uint8_t npc);
uint8_t npc_for_name(const std::string& name);
uint8_t npc_for_name(const std::u16string& name);
void process_chat_command(std::shared_ptr<ServerState> s, std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, const std::u16string& text);
+2
View File
@@ -33,6 +33,8 @@ Client::Client(
play_time_begin(now()),
last_recv_time(this->play_time_begin),
last_send_time(0),
x(0.0f),
z(0.0f),
area(0),
lobby_id(0),
lobby_client_id(0),
+2
View File
@@ -94,6 +94,8 @@ struct Client {
uint64_t last_send_time; // time of last data sent
// Lobby/positioning
float x;
float z;
uint32_t area; // which area is the client in?
uint32_t lobby_id; // which lobby is this person in?
uint8_t lobby_client_id; // which client number is this person?
+47 -28
View File
@@ -453,6 +453,11 @@ struct S_OpenFile_BB_44_A6 {
// 61 (C->S): Player data
// See PSOPlayerDataPC, PSOPlayerDataGC, PSOPlayerDataBB in Player.hh for this
// command's format.
// Note: If the client is in a game, the inventory sent by the client only
// includes items that would not disappear if the client was disconnected!
// Upon joining a game, the client assigns inventory item IDs sequentially as
// (0x00010000 + (0x00200000 * lobby_client_id) + x). So, for example, player
// 3's 8th item's ID would become 0x00610007.
// 62: Target command
// When a client sends this command, the server should forward it to the player
@@ -1434,7 +1439,7 @@ struct G_EnemyHitByPlayer_6x0A {
struct G_SetPlayerVisibility_6x22_6x23 {
uint8_t subcommand; // 22 = invisible, 23 = visible
uint8_t subsize;
uint8_t size;
le_uint16_t client_id;
};
@@ -1463,27 +1468,29 @@ struct G_UnequipItem_6x26 {
};
// 27: Use item
// Format is G_ItemSubcommand
struct G_UseItem_6x27 {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
le_uint32_t item_id;
};
// 28: Feed MAG
struct G_FeedMAG_6x28 {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t client_id;
le_uint32_t mag_item_id;
le_uint32_t fed_item_id;
};
// 29: Delete item (via bank deposit / sale / feeding MAG)
struct G_DestroyItem_6x29 {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t client_id;
le_uint32_t item_id;
le_uint32_t amount;
};
// This subcommand is also used for reducing the size of stacks - if amount is
// less than the stack count, the item is not deleted; its item ID remains valid
// Format is G_ItemSubcommand
// 2A: Drop item
@@ -1549,22 +1556,32 @@ struct G_LevelUp_6x30 {
struct G_StopAtPosition_6x3E {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t client_id;
uint64_t unknown;
le_float x;
le_float y;
le_float z;
uint32_t unused;
};
// 3F: Unknown (supported; lobby & game)
// 3F: Set position
struct G_SetPosition_6x3F {
uint8_t subcommand;
uint8_t size;
le_uint16_t client_id;
le_uint32_t unknown;
le_uint32_t area;
le_float x;
le_float y;
le_float z;
};
// 40: Walk
struct G_WalkToPosition_6x40 {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t client_id;
le_float x;
le_float z;
@@ -1577,7 +1594,7 @@ struct G_WalkToPosition_6x40 {
struct G_RunToPosition_6x42 {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t client_id;
le_float x;
le_float z;
@@ -1610,7 +1627,7 @@ struct G_RunToPosition_6x42 {
struct G_PickUpItem_6x59 {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t client_id;
le_uint16_t client_id2;
le_uint16_t area;
@@ -1636,21 +1653,22 @@ struct G_PickUpItemRequest_6x5A {
struct G_DropStackedItem_6x5D {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t unused;
uint8_t size;
uint8_t client_id; // TODO: verify this
uint8_t unused;
le_uint16_t area;
le_uint16_t unused2;
le_float x;
le_float y;
le_uint32_t unused3;
le_float z;
ItemData data;
le_uint32_t unused3;
};
// 5E: Buy item at shop
struct G_BuyShopItem_6x5E {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
uint8_t client_id;
uint8_t unused;
ItemData item;
@@ -1660,15 +1678,16 @@ struct G_BuyShopItem_6x5E {
struct G_DropItem_6x5F {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t unused;
uint8_t area;
uint8_t enemy_instance;
le_uint16_t request_id;
le_float x;
le_float y;
le_float z;
le_uint32_t unused2;
ItemData data;
le_uint32_t unused3;
};
// 60: Request for item drop (handled by the server on BB)
@@ -1681,7 +1700,7 @@ struct G_EnemyDropItemRequest_6x60 {
uint8_t enemy_id;
le_uint16_t request_id;
le_float x;
le_float y;
le_float z;
le_uint64_t unknown;
};
@@ -1761,7 +1780,7 @@ struct G_BoxItemDropRequest_6xA2 {
uint8_t unused2;
le_uint16_t request_id;
le_float x;
le_float y;
le_float z;
le_uint32_t unknown[6];
};
@@ -1847,7 +1866,7 @@ struct G_BankAction_BB_6xBD {
struct G_CreateInventoryItem_BB_6xBE {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t client_id;
ItemData item;
le_uint32_t unused;
@@ -1877,7 +1896,7 @@ struct G_SplitStackedItem_6xC3 {
le_uint16_t area;
le_uint16_t unused2;
le_float x;
le_float y;
le_float z;
le_uint32_t item_id;
le_uint32_t amount;
};
+1 -1
View File
@@ -219,7 +219,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
}
if (should_delete_item) {
c->player.remove_item(item.data.item_id, 1, nullptr);
c->player.remove_item(item.data.item_id, 1);
}
}
+19 -10
View File
@@ -96,6 +96,16 @@ void Lobby::add_client(shared_ptr<Client> c, bool reverse_indexes) {
this->leader_id = c->lobby_client_id;
}
}
// If the lobby is a game, assign the inventory's item IDs
if (this->is_game()) {
auto& inv = c->player.inventory;
size_t count = max<uint8_t>(inv.num_items, 30);
for (size_t x = 0; x < count; x++) {
inv.items[x].data.item_id = 0x00010000 + 0x00200000 * c->lobby_client_id + x;
}
c->player.print_inventory(stderr);
}
}
void Lobby::remove_client(shared_ptr<Client> c) {
@@ -170,17 +180,22 @@ uint8_t Lobby::game_event_for_lobby_event(uint8_t lobby_event) {
void Lobby::add_item(const PlayerInventoryItem& item) {
this->item_id_to_floor_item.emplace(item.data.item_id, item);
void Lobby::add_item(const PlayerInventoryItem& item, uint8_t area, float x, float z) {
auto& fi = this->item_id_to_floor_item[item.data.item_id];
fi.inv_item = item;
fi.area = area;
fi.x = x;
fi.z = z;
}
void Lobby::remove_item(uint32_t item_id, PlayerInventoryItem* item) {
PlayerInventoryItem Lobby::remove_item(uint32_t item_id) {
auto item_it = this->item_id_to_floor_item.find(item_id);
if (item_it == this->item_id_to_floor_item.end()) {
throw out_of_range("item not present");
}
*item = move(item_it->second);
PlayerInventoryItem ret = move(item_it->second.inv_item);
this->item_id_to_floor_item.erase(item_it);
return ret;
}
uint32_t Lobby::generate_item_id(uint8_t client_id) {
@@ -189,9 +204,3 @@ uint32_t Lobby::generate_item_id(uint8_t client_id) {
}
return this->next_game_item_id++;
}
void Lobby::assign_item_ids_for_player(uint32_t client_id, PlayerInventory& inv) {
for (size_t x = 0; x < inv.num_items; x++) {
inv.items[x].data.item_id = this->generate_item_id(client_id);
}
}
+9 -5
View File
@@ -33,12 +33,18 @@ struct Lobby {
uint32_t max_level;
// item info
struct FloorItem {
PlayerInventoryItem inv_item;
float x;
float z;
uint8_t area;
};
std::vector<PSOEnemy> enemies;
std::shared_ptr<const RareItemSet> rare_item_set;
std::array<uint32_t, 12> next_item_id;
uint32_t next_game_item_id;
PlayerInventoryItem next_drop_item;
std::unordered_map<uint32_t, PlayerInventoryItem> item_id_to_floor_item;
std::unordered_map<uint32_t, FloorItem> item_id_to_floor_item;
parray<le_uint32_t, 0x20> variations;
// game config
@@ -83,12 +89,10 @@ struct Lobby {
const std::u16string* identifier = nullptr,
uint64_t serial_number = 0);
void add_item(const PlayerInventoryItem& item);
void remove_item(uint32_t item_id, PlayerInventoryItem* item);
void add_item(const PlayerInventoryItem& item, uint8_t area, float x, float z);
PlayerInventoryItem remove_item(uint32_t item_id);
size_t find_item(uint32_t item_id);
uint32_t generate_item_id(uint8_t client_id);
void assign_item_ids_for_player(uint32_t client_id, PlayerInventory& inv);
static uint8_t game_event_for_lobby_event(uint8_t lobby_event);
};
+45 -55
View File
@@ -9,6 +9,7 @@
#include "Text.hh"
#include "Version.hh"
#include "StaticGameData.hh"
using namespace std;
@@ -439,29 +440,16 @@ PlayerLobbyDataBB::PlayerLobbyDataBB() noexcept
////////////////////////////////////////////////////////////////////////////////
static const unordered_map<uint32_t, uint32_t> combine_item_to_max({
{0x030000, 10},
{0x030001, 10},
{0x030002, 10},
{0x030100, 10},
{0x030101, 10},
{0x030102, 10},
{0x030300, 10},
{0x030400, 10},
{0x030500, 10},
{0x030600, 10},
{0x030601, 10},
{0x030700, 10},
{0x030800, 10},
{0x031000, 99},
{0x031001, 99},
{0x031002, 99},
});
const uint32_t meseta_identifier = 0x00000004;
const uint32_t meseta_identifier = 0x00040000;
uint32_t ItemData::primary_identifier() const {
return (this->item_data1[0] << 16) | (this->item_data1[1] << 8) | this->item_data1[2];
if (this->item_data1[0] == 0x03 && this->item_data1[1] == 0x02) {
return 0x00030200; // Tech disk (data1[2] is level, so omit it)
} else if (this->item_data1[0] == 0x02) {
return 0x00020000 | (this->item_data1[1] << 8); // Mag
} else {
return (this->item_data1[0] << 16) | (this->item_data1[1] << 8) | this->item_data1[2];
}
}
PlayerBankItem PlayerInventoryItem::to_bank_item() const {
@@ -577,21 +565,20 @@ void PlayerBank::add_item(const PlayerBankItem& item) {
this->num_items++;
}
void Player::remove_item(uint32_t item_id, uint32_t amount,
PlayerInventoryItem* item) {
PlayerInventoryItem Player::remove_item(uint32_t item_id, uint32_t amount) {
PlayerInventoryItem ret;
// are we removing meseta? then create a meseta item
if (item_id == 0xFFFFFFFF) {
if (amount > this->disp.meseta) {
throw out_of_range("player does not have enough meseta");
}
if (item) {
memset(item, 0, sizeof(*item));
item->data.item_data1[0] = 0x04;
item->data.item_data2d = amount;
}
memset(&ret, 0, sizeof(ret));
ret.data.item_data1[0] = 0x04;
ret.data.item_data2d = amount;
this->disp.meseta -= amount;
return;
return ret;
}
// find this item
@@ -602,40 +589,35 @@ void Player::remove_item(uint32_t item_id, uint32_t amount,
// (amount == 0 means remove all of it)
if (amount && (amount < inventory_item.data.item_data1[5]) &&
combine_item_to_max.count(inventory_item.data.primary_identifier())) {
if (item) {
*item = inventory_item;
item->data.item_data1[5] = amount;
item->data.item_id = 0xFFFFFFFF;
}
ret = inventory_item;
ret.data.item_data1[5] = amount;
ret.data.item_id = 0xFFFFFFFF;
inventory_item.data.item_data1[5] -= amount;
return;
return ret;
}
// not a combine item, or we're removing the whole stack? then just remove the item
if (item) {
*item = inventory_item;
}
ret = inventory_item;
this->inventory.num_items--;
memcpy(&this->inventory.items[index], &this->inventory.items[index + 1],
sizeof(PlayerInventoryItem) * (this->inventory.num_items - index));
return ret;
}
// removes an item from a bank. works just like RemoveItem for inventories; I won't comment it
void PlayerBank::remove_item(uint32_t item_id, uint32_t amount,
PlayerBankItem* item) {
PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) {
PlayerBankItem ret;
// are we removing meseta? then create a meseta item
if (item_id == 0xFFFFFFFF) {
if (amount > this->meseta) {
throw out_of_range("player does not have enough meseta");
}
if (item) {
memset(item, 0, sizeof(*item));
item->data.item_data1[0] = 0x04;
item->data.item_data2d = amount;
}
memset(&ret, 0, sizeof(ret));
ret.data.item_data1[0] = 0x04;
ret.data.item_data2d = amount;
this->meseta -= amount;
return;
return ret;
}
// find this item
@@ -646,23 +628,20 @@ void PlayerBank::remove_item(uint32_t item_id, uint32_t amount,
// (amount == 0 means remove all of it)
if (amount && (amount < bank_item.data.item_data1[5]) &&
combine_item_to_max.count(bank_item.data.primary_identifier())) {
if (item) {
*item = bank_item;
item->data.item_data1[5] = amount;
item->amount = amount;
}
ret = bank_item;
ret.data.item_data1[5] = amount;
ret.amount = amount;
bank_item.data.item_data1[5] -= amount;
bank_item.amount -= amount;
return;
return ret;
}
// not a combine item, or we're removing the whole stack? then just remove the item
if (item) {
*item = bank_item;
}
ret = bank_item;
this->num_items--;
memcpy(&this->items[index], &this->items[index + 1],
sizeof(PlayerBankItem) * (this->num_items - index));
return ret;
}
size_t PlayerInventory::find_item(uint32_t item_id) {
@@ -683,6 +662,17 @@ size_t PlayerBank::find_item(uint32_t item_id) {
throw out_of_range("item not present");
}
void Player::print_inventory(FILE* stream) const {
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.meseta.load());
fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items);
for (size_t x = 0; x < this->inventory.num_items; x++) {
const auto& item = this->inventory.items[x];
string name = name_for_item(item.data);
fprintf(stream, "[PlayerInventory] %zu (%08" PRIX32 "): %08" PRIX32 " (%s)\n",
x, item.data.item_id.load(), item.data.primary_identifier(), name.c_str());
}
}
string filename_for_player_bb(const string& username, uint8_t player_index) {
return string_printf("system/players/player_%s_%hhu.nsc", username.c_str(),
static_cast<uint8_t>(player_index + 1));
+4 -2
View File
@@ -75,7 +75,7 @@ struct PlayerBank {
const std::string& load_filename);
void add_item(const PlayerBankItem& item);
void remove_item(uint32_t item_id, uint32_t amount, PlayerBankItem* item);
PlayerBankItem remove_item(uint32_t item_id, uint32_t amount);
size_t find_item(uint32_t item_id);
} __attribute__((packed));
@@ -430,8 +430,10 @@ struct Player {
PlayerBB export_bb_player_data() const;
void add_item(const PlayerInventoryItem& item);
void remove_item(uint32_t item_id, uint32_t amount, PlayerInventoryItem* item);
PlayerInventoryItem remove_item(uint32_t item_id, uint32_t amount);
size_t find_item(uint32_t item_id);
void print_inventory(FILE* stream) const;
};
+15 -14
View File
@@ -29,17 +29,18 @@ extern FileContentsCache file_cache;
enum ClientStateBB {
// initial connection. server will redirect client to another port.
// Initial connection; server will redirect client to another port
INITIAL_LOGIN = 0x00,
// second connection. server will send client game data and account data.
// Second connection; server will send client game data and account data
DOWNLOAD_DATA = 0x01,
// third connection. choose character menu
// Third connection; client will show the choose character menu
CHOOSE_PLAYER = 0x02,
// fourth connection, used for saving characters only. if you do not create a
// character, server sets this state in order to skip it.
SAVE_PLAYER = 0x03,
// last connection. redirects client to login server.
SHIP_SELECT = 0x04,
// Fourth connection; used for saving characters only. If you do not create a
// character, the server sets this state during the third connection so this
// connection is effectively skipped.
SAVE_PLAYER = 0x03,
// Last connection; redirects client to login server
SHIP_SELECT = 0x04,
};
@@ -816,9 +817,6 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
s->change_client_lobby(c, game);
c->flags |= Client::Flag::LOADING;
if (c->version == GameVersion::BB) {
game->assign_item_ids_for_player(c->lobby_client_id, c->player.inventory);
}
break;
}
@@ -1696,8 +1694,6 @@ void process_create_game_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
s->add_lobby(game);
s->change_client_lobby(c, game);
c->flags |= Client::Flag::LOADING;
game->assign_item_ids_for_player(c->lobby_client_id, c->player.inventory);
}
void process_lobby_name_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -1723,7 +1719,12 @@ void process_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_resume_game(l, c);
send_server_time(c);
send_get_player_info(c);
// Only get player info again on BB, since on other versions the returned info
// only includes items that would be saved if the client disconnects
// unexpectedly (that is, only equipped items are included).
if (c->version == GameVersion::BB) {
send_get_player_info(c);
}
}
////////////////////////////////////////////////////////////////////////////////
+245 -88
View File
@@ -37,7 +37,7 @@ const CmdT* check_size_sc(
max_size = min_size;
}
const auto* cmd = &check_size_t<CmdT>(data, min_size, max_size);
if (check_size_field && cmd->size) {
if (check_size_field && (cmd->size != data.size() / 4)) {
throw runtime_error("invalid subcommand size field");
}
return cmd;
@@ -227,28 +227,102 @@ static void process_subcommand_switch_state_changed(shared_ptr<ServerState>,
}
////////////////////////////////////////////////////////////////////////////////
// BB Item commands
// player drops an item
static void process_subcommand_drop_item(shared_ptr<ServerState>,
template <typename CmdT>
void process_subcommand_movement(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_PlayerDropItem_6x2A>(data);
const auto* cmd = check_size_sc<CmdT>(data);
if ((cmd->client_id != c->lobby_client_id)) {
return;
}
PlayerInventoryItem item;
c->player.remove_item(cmd->item_id, 0, &item);
l->add_item(item);
if (cmd->client_id != c->lobby_client_id) {
return;
}
c->x = cmd->x;
c->z = cmd->z;
forward_subcommand(l, c, command, flag, data);
}
// player splits a stack and drops part of it
static void process_subcommand_drop_stacked_item(shared_ptr<ServerState>,
////////////////////////////////////////////////////////////////////////////////
// Item commands
static void process_subcommand_player_drop_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
const auto* cmd = check_size_sc<G_PlayerDropItem_6x2A>(data);
if ((cmd->client_id != c->lobby_client_id)) {
return;
}
l->add_item(c->player.remove_item(cmd->item_id, 0), cmd->area, cmd->x, cmd->z);
log(INFO, "[Items/%08" PRIX32 "] Player %hhu dropped item %08" PRIX32 " at %hu:(%g, %g)",
l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_create_inventory_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
const auto* cmd = check_size_sc<G_PlayerCreateInventoryItem_6x2B>(data);
if ((cmd->client_id != c->lobby_client_id)) {
return;
}
if (c->version == GameVersion::BB) {
// BB should never send this command - inventory items should only be
// created by the server in response to shop buy / bank withdraw / etc. reqs
return;
}
PlayerInventoryItem item;
item.equip_flags = 0; // TODO: Use the right default flags here
item.tech_flag = 0;
item.game_flags = 0;
item.data = cmd->item;
c->player.add_item(item);
log(INFO, "[Items/%08" PRIX32 "] Player %hhu created inventory item %08" PRIX32,
l->lobby_id, cmd->client_id, cmd->item.item_id.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_drop_partial_stack(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
const auto* cmd = check_size_sc<G_DropStackedItem_6x5D>(data);
// TODO: Should we check the client ID here too?
if (!l->is_game()) {
return;
}
if (l->version == GameVersion::BB) {
return;
}
// TODO: Should we delete anything from the inventory here? Does the client
// send an appropriate 6x29 alongside this?
PlayerInventoryItem item;
item.equip_flags = 0; // TODO: Use the right default flags here
item.tech_flag = 0;
item.game_flags = 0;
item.data = cmd->data;
l->add_item(item, cmd->area, cmd->x, cmd->z);
log(INFO, "[Items/%08" PRIX32 "] Player %hhu split stack to create ground item %08" PRIX32 " at %hu:(%g, %g)",
l->lobby_id, cmd->client_id, item.data.item_id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_drop_partial_stack_bb(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) {
@@ -258,8 +332,7 @@ static void process_subcommand_drop_stacked_item(shared_ptr<ServerState>,
return;
}
PlayerInventoryItem item;
c->player.remove_item(cmd->item_id, cmd->amount, &item);
auto item = c->player.remove_item(cmd->item_id, cmd->amount);
// if a stack was split, the original item still exists, so the dropped item
// needs a new ID. remove_item signals this by returning an item with id=-1
@@ -267,19 +340,98 @@ static void process_subcommand_drop_stacked_item(shared_ptr<ServerState>,
item.data.item_id = l->generate_item_id(c->lobby_client_id);
}
l->add_item(item);
l->add_item(item, cmd->area, cmd->x, cmd->z);
send_drop_stacked_item(l, item.data, cmd->area, cmd->x, cmd->y);
log(INFO, "[Items/%08" PRIX32 "] Player %hhu split stack %08" PRIX32 " (%" PRIu32 " of them) at %hu:(%g, %g)",
l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->amount.load(),
cmd->area.load(), cmd->x.load(), cmd->z.load());
// c->player.print_inventory(stderr);
send_drop_stacked_item(l, item.data, cmd->area, cmd->x, cmd->z);
} else {
forward_subcommand(l, c, command, flag, data);
}
}
// player requests to pick up an item
static void process_subcommand_buy_shop_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
const auto* cmd = check_size_sc<G_BuyShopItem_6x5E>(data);
if (!l->is_game() || (cmd->client_id != c->lobby_client_id)) {
return;
}
if (l->version == GameVersion::BB) {
return;
}
PlayerInventoryItem item;
item.equip_flags = 0; // TODO: Use the right default flags here
item.tech_flag = 0;
item.game_flags = 0;
item.data = cmd->item;
c->player.add_item(item);
log(INFO, "[Items/%08" PRIX32 "] Player %hhu bought item %08" PRIX32 " from shop",
l->lobby_id, cmd->client_id, item.data.item_id.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_box_or_enemy_item_drop(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
const auto* cmd = check_size_sc<G_DropItem_6x5F>(data);
if (!l->is_game() || (c->lobby_client_id != l->leader_id)) {
return;
}
if (l->version == GameVersion::BB) {
return;
}
PlayerInventoryItem item;
item.equip_flags = 0; // TODO: Use the right default flags here
item.tech_flag = 0;
item.game_flags = 0;
item.data = cmd->data;
l->add_item(item, cmd->area, cmd->x, cmd->z);
log(INFO, "[Items/%08" PRIX32 "] Leader created ground item %08" PRIX32 " at %hhu:(%g, %g)",
l->lobby_id, item.data.item_id.load(), cmd->area, cmd->x.load(), cmd->z.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_pick_up_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
auto* cmd = check_size_sc<G_PickUpItem_6x59>(data);
if (!l->is_game() || (cmd->client_id != c->lobby_client_id)) {
return;
}
if (l->version == GameVersion::BB) {
// BB clients should never send this; only the server should send this
return;
}
c->player.add_item(l->remove_item(cmd->item_id));
log(INFO, "[Items/%08" PRIX32 "] Player %hu picked up %08" PRIX32,
l->lobby_id, cmd->client_id.load(), cmd->item_id.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_pick_up_item_request(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
// This is handled by the server on BB, and by the leader on other versions
if (l->version == GameVersion::BB) {
auto* cmd = check_size_sc<G_PickUpItemRequest_6x5A>(data);
@@ -287,21 +439,19 @@ static void process_subcommand_pick_up_item(shared_ptr<ServerState>,
return;
}
PlayerInventoryItem item;
l->remove_item(cmd->item_id, &item);
c->player.add_item(item);
c->player.add_item(l->remove_item(cmd->item_id));
send_pick_up_item(l, c, item.data.item_id, cmd->area);
send_pick_up_item(l, c, cmd->item_id, cmd->area);
} else {
forward_subcommand(l, c, command, flag, data);
}
}
// player equips an item
static void process_subcommand_equip_unequip_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
// We don't track equip state on non-BB versions
if (l->version == GameVersion::BB) {
const auto* cmd = check_size_sc<G_ItemSubcommand>(data);
@@ -324,27 +474,23 @@ static void process_subcommand_equip_unequip_item(shared_ptr<ServerState>,
static void process_subcommand_use_item(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_ItemSubcommand>(data);
const auto* cmd = check_size_sc<G_UseItem_6x27>(data);
if (cmd->client_id != c->lobby_client_id) {
return;
}
size_t index = c->player.inventory.find_item(cmd->item_id);
if (cmd->command == 0x25) {
c->player.inventory.items[index].game_flags |= 0x00000008; // equip
} else {
c->player.inventory.items[index].game_flags &= 0xFFFFFFF7; // unequip
}
player_use_item(c, index);
if (cmd->client_id != c->lobby_client_id) {
return;
}
size_t index = c->player.inventory.find_item(cmd->item_id);
player_use_item(c, index);
log(INFO, "[Items/%08" PRIX32 "] Player used item %hhu:%08" PRIX32,
l->lobby_id, cmd->client_id, cmd->item_id.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_open_shop_or_ep3_unknown(shared_ptr<ServerState> s,
static void process_subcommand_open_shop_bb_or_unknown_ep3(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
if (l->flags & Lobby::Flag::EPISODE_3_ONLY) {
@@ -379,14 +525,14 @@ static void process_subcommand_open_shop_or_ep3_unknown(shared_ptr<ServerState>
}
}
static void process_subcommand_open_bank(shared_ptr<ServerState>,
static void process_subcommand_open_bank_bb(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t, const string&) {
if ((l->version == GameVersion::BB) && l->is_game()) {
send_bank(c);
}
}
static void process_subcommand_bank_action(shared_ptr<ServerState>,
static void process_subcommand_bank_action_bb(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t, const string& data) {
if (l->version == GameVersion::BB) {
const auto* cmd = check_size_sc<G_BankAction_BB_6xBD>(data);
@@ -406,8 +552,7 @@ static void process_subcommand_bank_action(shared_ptr<ServerState>,
c->player.bank.meseta += cmd->meseta_amount;
c->player.disp.meseta -= cmd->meseta_amount;
} else { // item
PlayerInventoryItem item;
c->player.remove_item(cmd->item_id, cmd->item_amount, &item);
auto item = c->player.remove_item(cmd->item_id, cmd->item_amount);
c->player.bank.add_item(item.to_bank_item());
send_destroy_item(l, c, cmd->item_id, cmd->item_amount);
}
@@ -422,8 +567,7 @@ static void process_subcommand_bank_action(shared_ptr<ServerState>,
c->player.bank.meseta -= cmd->meseta_amount;
c->player.disp.meseta += cmd->meseta_amount;
} else { // item
PlayerBankItem bank_item;
c->player.bank.remove_item(cmd->item_id, cmd->item_amount, &bank_item);
auto bank_item = c->player.bank.remove_item(cmd->item_id, cmd->item_amount);
PlayerInventoryItem item = bank_item.to_inventory_item();
item.data.item_id = l->generate_item_id(0xFF);
c->player.add_item(item);
@@ -434,7 +578,7 @@ static void process_subcommand_bank_action(shared_ptr<ServerState>,
}
// player sorts the items in their inventory
static void process_subcommand_sort_inventory(shared_ptr<ServerState>,
static void process_subcommand_sort_inventory_bb(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t,
const string& data) {
if (l->version == GameVersion::BB) {
@@ -461,10 +605,9 @@ static void process_subcommand_sort_inventory(shared_ptr<ServerState>,
}
////////////////////////////////////////////////////////////////////////////////
// BB EXP/Drop Item commands
// EXP/Drop Item commands
// enemy killed; leader sends drop item request
static void process_subcommand_enemy_drop_item(shared_ptr<ServerState> s,
static void process_subcommand_enemy_drop_item_request(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
if (l->version == GameVersion::BB) {
@@ -506,8 +649,8 @@ static void process_subcommand_enemy_drop_item(shared_ptr<ServerState> s,
}
item.data.item_id = l->generate_item_id(0xFF);
l->add_item(item);
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->y,
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 {
@@ -515,8 +658,7 @@ static void process_subcommand_enemy_drop_item(shared_ptr<ServerState> s,
}
}
// box broken; leader sends drop item request
static void process_subcommand_box_drop_item(shared_ptr<ServerState> s,
static void process_subcommand_box_drop_item_request(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
if (l->version == GameVersion::BB) {
@@ -565,8 +707,8 @@ static void process_subcommand_box_drop_item(shared_ptr<ServerState> s,
}
item.data.item_id = l->generate_item_id(0xFF);
l->add_item(item);
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->y,
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 {
@@ -704,23 +846,38 @@ static void process_subcommand_enemy_killed(shared_ptr<ServerState> s,
}
}
// destroy item (sent when there are too many items on the ground)
static void process_subcommand_destroy_item(shared_ptr<ServerState>,
static void process_subcommand_destroy_inventory_item(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_ItemSubcommand>(data);
if (!l->is_game()) {
return;
}
l->remove_item(cmd->item_id, nullptr);
const auto* cmd = check_size_sc<G_ItemSubcommand>(data);
if (!l->is_game()) {
return;
}
if (cmd->client_id != c->lobby_client_id) {
return;
}
c->player.remove_item(cmd->item_id, cmd->amount);
log(INFO, "[Items/%08" PRIX32 "] Inventory item %hhu:%08" PRIX32 " destroyed (%" PRIX32 " of them)",
l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->amount.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
// player requests to tekk an item
static void process_subcommand_identify_item(shared_ptr<ServerState>,
static void process_subcommand_destroy_ground_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const string& data) {
const auto* cmd = check_size_sc<G_ItemSubcommand>(data);
if (!l->is_game()) {
return;
}
l->remove_item(cmd->item_id);
log(INFO, "[Items/%08" PRIX32 "] Ground item %08" PRIX32 " destroyed (%" PRIX32 " of them)",
l->lobby_id, cmd->item_id.load(), cmd->amount.load());
// c->player.print_inventory(stderr);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_identify_item_bb(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) {
@@ -905,9 +1062,9 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 26 */ process_subcommand_equip_unequip_item, // Unequip item
/* 27 */ process_subcommand_use_item,
/* 28 */ process_subcommand_forward_check_size_game, // Feed MAG
/* 29 */ process_subcommand_forward_check_size_game, // Delete item (via bank deposit / sale / feeding MAG)
/* 2A */ process_subcommand_drop_item,
/* 2B */ process_subcommand_forward_check_size_game, // Create inventory item (e.g. from tekker or bank withdrawal)
/* 29 */ process_subcommand_destroy_inventory_item, // Delete item (via bank deposit / sale / feeding MAG)
/* 2A */ process_subcommand_player_drop_item,
/* 2B */ process_subcommand_create_inventory_item, // Create inventory item (e.g. from tekker or bank withdrawal)
/* 2C */ process_subcommand_forward_check_size, // Talk to NPC
/* 2D */ process_subcommand_forward_check_size, // Done talking to NPC
/* 2E */ process_subcommand_unimplemented,
@@ -926,11 +1083,11 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 3B */ process_subcommand_forward_check_size,
/* 3C */ process_subcommand_unimplemented,
/* 3D */ process_subcommand_unimplemented,
/* 3E */ process_subcommand_forward_check_size, // Stop moving
/* 3F */ process_subcommand_forward_check_size,
/* 40 */ process_subcommand_forward_check_size, // Walk
/* 3E */ process_subcommand_movement<G_StopAtPosition_6x3E>, // Stop moving
/* 3F */ process_subcommand_movement<G_SetPosition_6x3F>, // Set position (e.g. when materializing after warp)
/* 40 */ process_subcommand_movement<G_WalkToPosition_6x40>, // Walk
/* 41 */ process_subcommand_unimplemented,
/* 42 */ process_subcommand_forward_check_size, // Run
/* 42 */ process_subcommand_movement<G_RunToPosition_6x42>, // Run
/* 43 */ process_subcommand_forward_check_size_client,
/* 44 */ process_subcommand_forward_check_size_client,
/* 45 */ process_subcommand_forward_check_size_client,
@@ -953,17 +1110,17 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 56 */ process_subcommand_forward_check_size_client,
/* 57 */ process_subcommand_forward_check_size_client,
/* 58 */ process_subcommand_forward_check_size_game,
/* 59 */ process_subcommand_forward_check_size_game, // Item picked up
/* 5A */ process_subcommand_pick_up_item, // Request to pick up item
/* 59 */ process_subcommand_pick_up_item, // Item picked up
/* 5A */ process_subcommand_pick_up_item_request, // Request to pick up item
/* 5B */ process_subcommand_unimplemented,
/* 5C */ process_subcommand_unimplemented,
/* 5D */ process_subcommand_forward_check_size_game, // Drop meseta or stacked item
/* 5E */ process_subcommand_forward_check_size_game, // Buy item at shop
/* 5F */ process_subcommand_forward_check_size_game, // Drop item from box/enemy
/* 60 */ process_subcommand_enemy_drop_item, // Request for item drop (handled by the server on BB)
/* 5D */ process_subcommand_drop_partial_stack, // Drop meseta or stacked item
/* 5E */ process_subcommand_buy_shop_item, // Buy item at shop
/* 5F */ process_subcommand_box_or_enemy_item_drop, // Drop item from box/enemy
/* 60 */ process_subcommand_enemy_drop_item_request, // Request for item drop (handled by the server on BB)
/* 61 */ process_subcommand_forward_check_size_game, // Feed mag
/* 62 */ process_subcommand_unimplemented,
/* 63 */ process_subcommand_destroy_item, // Destroy an item on the ground (used when too many items have been dropped)
/* 63 */ process_subcommand_destroy_ground_item, // Destroy an item on the ground (used when too many items have been dropped)
/* 64 */ process_subcommand_unimplemented,
/* 65 */ process_subcommand_unimplemented,
/* 66 */ process_subcommand_forward_check_size_game, // Use star atomizer
@@ -1026,7 +1183,7 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 9F */ process_subcommand_forward_check_size_game, // Episode 2 boss actions
/* A0 */ process_subcommand_forward_check_size_game, // Episode 2 boss actions
/* A1 */ process_subcommand_unimplemented,
/* A2 */ process_subcommand_box_drop_item, // Request for item drop from box (handled by server on BB)
/* A2 */ process_subcommand_box_drop_item_request, // Request for item drop from box (handled by server on BB)
/* A3 */ process_subcommand_forward_check_size_game, // Episode 2 boss actions
/* A4 */ process_subcommand_unimplemented,
/* A5 */ process_subcommand_forward_check_size_game, // Episode 2 boss actions
@@ -1045,22 +1202,22 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* B2 */ process_subcommand_unimplemented,
/* B3 */ process_subcommand_unimplemented,
/* B4 */ process_subcommand_unimplemented,
/* B5 */ process_subcommand_open_shop_or_ep3_unknown, // BB shop request
/* B5 */ process_subcommand_open_shop_bb_or_unknown_ep3, // BB shop request
/* B6 */ process_subcommand_unimplemented, // BB shop contents (server->client only)
/* B7 */ process_subcommand_unimplemented, // TODO: BB buy shop item
/* B8 */ process_subcommand_identify_item, // Accept tekker result
/* B8 */ process_subcommand_identify_item_bb, // Accept tekker result
/* B9 */ process_subcommand_unimplemented,
/* BA */ process_subcommand_unimplemented,
/* BB */ process_subcommand_open_bank, // BB Bank request
/* BB */ process_subcommand_open_bank_bb, // BB Bank request
/* BC */ process_subcommand_unimplemented, // BB bank contents (server->client only)
/* BD */ process_subcommand_bank_action,
/* BD */ process_subcommand_bank_action_bb,
/* BE */ process_subcommand_unimplemented, // BB create inventory item (server->client only)
/* BF */ process_subcommand_forward_check_size_ep3_lobby, // Ep3 change music, also BB give EXP (BB usage is server->client only)
/* C0 */ process_subcommand_unimplemented,
/* C1 */ process_subcommand_unimplemented,
/* C2 */ process_subcommand_unimplemented,
/* C3 */ process_subcommand_drop_stacked_item, // Split stacked item - not sent if entire stack is dropped
/* C4 */ process_subcommand_sort_inventory,
/* C3 */ process_subcommand_drop_partial_stack_bb, // Split stacked item - not sent if entire stack is dropped
/* C4 */ process_subcommand_sort_inventory_bb,
/* C5 */ process_subcommand_unimplemented,
/* C6 */ process_subcommand_unimplemented,
/* C7 */ process_subcommand_unimplemented,
+8 -6
View File
@@ -1130,17 +1130,19 @@ void send_revive_player(shared_ptr<Lobby> l, shared_ptr<Client> c) {
// notifies other players of a dropped item from a box or enemy
void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t area, float x, float y, uint16_t request_id) {
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) {
G_DropItem_6x5F cmd = {
0x5F, 0x0A, 0x0000, area, from_enemy, request_id, x, y, 0, item};
0x5F, 0x0A, 0x0000, area, from_enemy, request_id, x, z, 0, item, 0};
send_command(l, 0x60, 0x00, cmd);
}
// notifies other players that a stack was split and part of it dropped (a new item was created)
void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item,
uint8_t area, float x, float y) {
uint8_t area, float x, float z) {
// TODO: Is this order correct? The original code sent {item, 0}, but it seems
// GC sends {0, item} (the last two fields in the struct are switched).
G_DropStackedItem_6x5D cmd = {
0x5D, 0x09, 0x0000, area, 0, x, y, 0, item};
0x5D, 0x09, 0x00, 0x00, area, 0, x, z, item, 0};
send_command(l, 0x60, 0x00, cmd);
}
@@ -1163,8 +1165,8 @@ void send_create_inventory_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
// destroys an item
void send_destroy_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t item_id, uint32_t amount) {
G_DestroyItem_6x29 cmd = {
0x29, 0x03, c->lobby_client_id, item_id, amount};
G_ItemSubcommand cmd = {
0x29, 0x03, c->lobby_client_id, 0x00, item_id, amount};
send_command(l, 0x60, 0x00, cmd);
}
+2 -2
View File
@@ -186,9 +186,9 @@ void send_set_player_visibility(std::shared_ptr<Lobby> l,
void send_revive_player(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c);
void send_drop_item(std::shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t area, float x, float y, uint16_t request_id);
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,
uint8_t area, float x, float y);
uint8_t area, float x, float z);
void send_pick_up_item(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c, uint32_t id,
uint8_t area);
void send_create_inventory_item(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c,
+1 -1
View File
@@ -6,9 +6,9 @@
#include <phosg/Strings.hh>
#include "ChatCommands.hh"
#include "ServerState.hh"
#include "SendCommands.hh"
#include "StaticGameData.hh"
using namespace std;
File diff suppressed because it is too large Load Diff
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <stdint.h>
#include <unordered_map>
#include "Player.hh"
extern const std::unordered_map<uint32_t, uint32_t> combine_item_to_max;
extern const std::unordered_map<uint8_t, const char*> name_for_weapon_special;
extern const std::unordered_map<uint8_t, const char*> name_for_s_rank_special;
extern const std::unordered_map<uint32_t, const char*> name_for_primary_identifier;
const std::string& name_for_technique(uint8_t tech);
std::u16string u16name_for_technique(uint8_t tech);
uint8_t technique_for_name(const std::string& name);
uint8_t technique_for_name(const std::u16string& name);
const std::string& name_for_section_id(uint8_t section_id);
std::u16string u16name_for_section_id(uint8_t section_id);
uint8_t section_id_for_name(const std::string& name);
uint8_t section_id_for_name(const std::u16string& name);
const std::string& name_for_event(uint8_t event);
std::u16string u16name_for_event(uint8_t event);
uint8_t event_for_name(const std::string& name);
uint8_t event_for_name(const std::u16string& name);
const std::string& name_for_lobby_type(uint8_t type);
std::u16string u16name_for_lobby_type(uint8_t type);
uint8_t lobby_type_for_name(const std::string& name);
uint8_t lobby_type_for_name(const std::u16string& name);
const std::string& name_for_npc(uint8_t npc);
std::u16string u16name_for_npc(uint8_t npc);
uint8_t npc_for_name(const std::string& name);
uint8_t npc_for_name(const std::u16string& name);
std::string name_for_item(const ItemData& item);