add $what command
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
Reference in New Issue
Block a user