refactor player/account data handling
This commit is contained in:
+48
-34
@@ -305,57 +305,57 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tokens[0] == "atp") {
|
if (tokens[0] == "atp") {
|
||||||
c->player.disp.stats.atp = stoul(tokens[1]);
|
c->game_data.player()->disp.stats.atp = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "mst") {
|
} else if (tokens[0] == "mst") {
|
||||||
c->player.disp.stats.mst = stoul(tokens[1]);
|
c->game_data.player()->disp.stats.mst = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "evp") {
|
} else if (tokens[0] == "evp") {
|
||||||
c->player.disp.stats.evp = stoul(tokens[1]);
|
c->game_data.player()->disp.stats.evp = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "hp") {
|
} else if (tokens[0] == "hp") {
|
||||||
c->player.disp.stats.hp = stoul(tokens[1]);
|
c->game_data.player()->disp.stats.hp = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "dfp") {
|
} else if (tokens[0] == "dfp") {
|
||||||
c->player.disp.stats.dfp = stoul(tokens[1]);
|
c->game_data.player()->disp.stats.dfp = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "ata") {
|
} else if (tokens[0] == "ata") {
|
||||||
c->player.disp.stats.ata = stoul(tokens[1]);
|
c->game_data.player()->disp.stats.ata = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "lck") {
|
} else if (tokens[0] == "lck") {
|
||||||
c->player.disp.stats.lck = stoul(tokens[1]);
|
c->game_data.player()->disp.stats.lck = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "meseta") {
|
} else if (tokens[0] == "meseta") {
|
||||||
c->player.disp.meseta = stoul(tokens[1]);
|
c->game_data.player()->disp.meseta = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "exp") {
|
} else if (tokens[0] == "exp") {
|
||||||
c->player.disp.experience = stoul(tokens[1]);
|
c->game_data.player()->disp.experience = stoul(tokens[1]);
|
||||||
} else if (tokens[0] == "level") {
|
} else if (tokens[0] == "level") {
|
||||||
c->player.disp.level = stoul(tokens[1]) - 1;
|
c->game_data.player()->disp.level = stoul(tokens[1]) - 1;
|
||||||
} else if (tokens[0] == "namecolor") {
|
} else if (tokens[0] == "namecolor") {
|
||||||
uint32_t new_color;
|
uint32_t new_color;
|
||||||
sscanf(tokens[1].c_str(), "%8X", &new_color);
|
sscanf(tokens[1].c_str(), "%8X", &new_color);
|
||||||
c->player.disp.name_color = new_color;
|
c->game_data.player()->disp.name_color = new_color;
|
||||||
} else if (tokens[0] == "secid") {
|
} else if (tokens[0] == "secid") {
|
||||||
uint8_t secid = section_id_for_name(decode_sjis(tokens[1]));
|
uint8_t secid = section_id_for_name(decode_sjis(tokens[1]));
|
||||||
if (secid == 0xFF) {
|
if (secid == 0xFF) {
|
||||||
send_text_message(c, u"$C6No such section ID.");
|
send_text_message(c, u"$C6No such section ID.");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
c->player.disp.section_id = secid;
|
c->game_data.player()->disp.section_id = secid;
|
||||||
}
|
}
|
||||||
} else if (tokens[0] == "name") {
|
} else if (tokens[0] == "name") {
|
||||||
c->player.disp.name = add_language_marker(tokens[1], 'J');
|
c->game_data.player()->disp.name = add_language_marker(tokens[1], 'J');
|
||||||
} else if (tokens[0] == "npc") {
|
} else if (tokens[0] == "npc") {
|
||||||
if (tokens[1] == "none") {
|
if (tokens[1] == "none") {
|
||||||
c->player.disp.extra_model = 0;
|
c->game_data.player()->disp.extra_model = 0;
|
||||||
c->player.disp.v2_flags &= 0xFD;
|
c->game_data.player()->disp.v2_flags &= 0xFD;
|
||||||
} else {
|
} else {
|
||||||
uint8_t npc = npc_for_name(decode_sjis(tokens[1]));
|
uint8_t npc = npc_for_name(decode_sjis(tokens[1]));
|
||||||
if (npc == 0xFF) {
|
if (npc == 0xFF) {
|
||||||
send_text_message(c, u"$C6No such NPC.");
|
send_text_message(c, u"$C6No such NPC.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c->player.disp.extra_model = npc;
|
c->game_data.player()->disp.extra_model = npc;
|
||||||
c->player.disp.v2_flags |= 0x02;
|
c->game_data.player()->disp.v2_flags |= 0x02;
|
||||||
}
|
}
|
||||||
} else if ((tokens[0] == "tech") && (tokens.size() > 2)) {
|
} else if ((tokens[0] == "tech") && (tokens.size() > 2)) {
|
||||||
uint8_t level = stoul(tokens[2]) - 1;
|
uint8_t level = stoul(tokens[2]) - 1;
|
||||||
if (tokens[1] == "all") {
|
if (tokens[1] == "all") {
|
||||||
for (size_t x = 0; x < 0x14; x++) {
|
for (size_t x = 0; x < 0x14; x++) {
|
||||||
c->player.disp.technique_levels.data()[x] = level;
|
c->game_data.player()->disp.technique_levels.data()[x] = level;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint8_t tech_id = technique_for_name(decode_sjis(tokens[1]));
|
uint8_t tech_id = technique_for_name(decode_sjis(tokens[1]));
|
||||||
@@ -363,7 +363,7 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
|||||||
send_text_message(c, u"$C6No such technique.");
|
send_text_message(c, u"$C6No such technique.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c->player.disp.technique_levels.data()[tech_id] = level;
|
c->game_data.player()->disp.technique_levels.data()[tech_id] = level;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
send_text_message(c, u"$C6Unknown field.");
|
send_text_message(c, u"$C6Unknown field.");
|
||||||
@@ -376,13 +376,14 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
|||||||
s->send_lobby_join_notifications(l, c);
|
s->send_lobby_join_notifications(l, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void command_change_bank(shared_ptr<ServerState>, shared_ptr<Lobby>,
|
// TODO: implement this
|
||||||
|
// TODO: make sure the bank name is filesystem-safe
|
||||||
|
/* static void command_change_bank(shared_ptr<ServerState>, shared_ptr<Lobby>,
|
||||||
shared_ptr<Client> c, const std::u16string&) {
|
shared_ptr<Client> c, const std::u16string&) {
|
||||||
check_version(c, GameVersion::BB);
|
check_version(c, GameVersion::BB);
|
||||||
|
|
||||||
// TODO: implement this
|
TODO
|
||||||
// TODO: make sure the bank name is filesystem-safe
|
} */
|
||||||
}
|
|
||||||
|
|
||||||
static void command_convert_char_to_bb(shared_ptr<ServerState> s,
|
static void command_convert_char_to_bb(shared_ptr<ServerState> s,
|
||||||
shared_ptr<Lobby> l, shared_ptr<Client> c, const std::u16string& args) {
|
shared_ptr<Lobby> l, shared_ptr<Client> c, const std::u16string& args) {
|
||||||
@@ -419,6 +420,19 @@ static void command_convert_char_to_bb(shared_ptr<ServerState> s,
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Administration commands
|
// Administration commands
|
||||||
|
|
||||||
|
static string name_for_client(shared_ptr<Client> c) {
|
||||||
|
auto player = c->game_data.player(false);
|
||||||
|
if (player.get()) {
|
||||||
|
return encode_sjis(player->disp.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c->license.get()) {
|
||||||
|
return string_printf("SN:%" PRIu32, c->license->serial_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Player";
|
||||||
|
}
|
||||||
|
|
||||||
static void command_silence(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
static void command_silence(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
||||||
shared_ptr<Client> c, const std::u16string& args) {
|
shared_ptr<Client> c, const std::u16string& args) {
|
||||||
check_privileges(c, Privilege::SILENCE_USER);
|
check_privileges(c, Privilege::SILENCE_USER);
|
||||||
@@ -436,8 +450,8 @@ static void command_silence(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
|||||||
}
|
}
|
||||||
|
|
||||||
target->can_chat = !target->can_chat;
|
target->can_chat = !target->can_chat;
|
||||||
string target_name_sjis = encode_sjis(target->player.disp.name);
|
string target_name = name_for_client(target);
|
||||||
send_text_message_printf(l, "$C6%s %ssilenced", target_name_sjis.c_str(),
|
send_text_message_printf(l, "$C6%s %ssilenced", target_name.c_str(),
|
||||||
target->can_chat ? "un" : "");
|
target->can_chat ? "un" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,8 +473,8 @@ static void command_kick(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
|||||||
|
|
||||||
send_message_box(target, u"$C6You were kicked off by a moderator.");
|
send_message_box(target, u"$C6You were kicked off by a moderator.");
|
||||||
target->should_disconnect = true;
|
target->should_disconnect = true;
|
||||||
string target_name_sjis = encode_sjis(target->player.disp.name);
|
string target_name = name_for_client(target);
|
||||||
send_text_message_printf(l, "$C6%s kicked off", target_name_sjis.c_str());
|
send_text_message_printf(l, "$C6%s kicked off", target_name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void command_ban(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
static void command_ban(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
||||||
@@ -510,8 +524,8 @@ static void command_ban(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
|||||||
s->license_manager->ban_until(target->license->serial_number, now() + usecs);
|
s->license_manager->ban_until(target->license->serial_number, now() + usecs);
|
||||||
send_message_box(target, u"$C6You were banned by a moderator.");
|
send_message_box(target, u"$C6You were banned by a moderator.");
|
||||||
target->should_disconnect = true;
|
target->should_disconnect = true;
|
||||||
auto encoded_name = encode_sjis(target->player.disp.name);
|
string target_name = name_for_client(target);
|
||||||
send_text_message_printf(l, "$C6%s banned", encoded_name.c_str());
|
send_text_message_printf(l, "$C6%s banned", target_name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -648,11 +662,11 @@ static void command_item(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
|||||||
|
|
||||||
ItemData item_data;
|
ItemData item_data;
|
||||||
memset(&item_data, 0, sizeof(item_data));
|
memset(&item_data, 0, sizeof(item_data));
|
||||||
if (data.size() < 12) {
|
if (data.size() <= 12) {
|
||||||
memcpy(&l->next_drop_item.data.item_data1, data.data(), data.size());
|
memcpy(&l->next_drop_item.data.data1, data.data(), data.size());
|
||||||
} else {
|
} else {
|
||||||
memcpy(&l->next_drop_item.data.item_data1, data.data(), 12);
|
memcpy(&l->next_drop_item.data.data1, data.data(), 12);
|
||||||
memcpy(&l->next_drop_item.data.item_data2, data.data() + 12, 12 - data.size());
|
memcpy(&l->next_drop_item.data.data2, data.data() + 12, 12 - data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
send_text_message(c, u"$C6Next drop chosen.");
|
send_text_message(c, u"$C6Next drop chosen.");
|
||||||
@@ -677,7 +691,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
|
|||||||
{u"$ax" , {command_ax , u"Usage:\nax <message>"}},
|
{u"$ax" , {command_ax , u"Usage:\nax <message>"}},
|
||||||
{u"$ban" , {command_ban , u"Usage:\nban <name-or-number>"}},
|
{u"$ban" , {command_ban , u"Usage:\nban <name-or-number>"}},
|
||||||
{u"$bbchar" , {command_convert_char_to_bb, u"Usage:\nbbchar <user> <pass> <1-4>"}},
|
{u"$bbchar" , {command_convert_char_to_bb, u"Usage:\nbbchar <user> <pass> <1-4>"}},
|
||||||
{u"$changebank", {command_change_bank , u"Usage:\nchangebank <bank name>"}},
|
// {u"$bank", {command_bank , u"Usage:\nbank <bank name>"}},
|
||||||
{u"$cheat" , {command_cheat , u"Usage:\ncheat"}},
|
{u"$cheat" , {command_cheat , u"Usage:\ncheat"}},
|
||||||
{u"$edit" , {command_edit , u"Usage:\nedit <stat> <value>"}},
|
{u"$edit" , {command_edit , u"Usage:\nedit <stat> <value>"}},
|
||||||
{u"$event" , {command_lobby_event , u"Usage:\nevent <name>"}},
|
{u"$event" , {command_lobby_event , u"Usage:\nevent <name>"}},
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ Client::Client(
|
|||||||
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
|
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Client::set_license(shared_ptr<const License> l) {
|
||||||
|
this->license = l;
|
||||||
|
this->game_data.serial_number = this->license->serial_number;
|
||||||
|
if (this->version == GameVersion::BB) {
|
||||||
|
this->game_data.bb_username = this->license->username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ClientConfig Client::export_config() const {
|
ClientConfig Client::export_config() const {
|
||||||
ClientConfig cc;
|
ClientConfig cc;
|
||||||
cc.magic = CLIENT_CONFIG_MAGIC;
|
cc.magic = CLIENT_CONFIG_MAGIC;
|
||||||
|
|||||||
+3
-1
@@ -100,7 +100,7 @@ struct Client {
|
|||||||
uint32_t lobby_id; // which lobby is this person in?
|
uint32_t lobby_id; // which lobby is this person in?
|
||||||
uint8_t lobby_client_id; // which client number is this person?
|
uint8_t lobby_client_id; // which client number is this person?
|
||||||
uint8_t lobby_arrow_color; // lobby arrow color ID
|
uint8_t lobby_arrow_color; // lobby arrow color ID
|
||||||
Player player;
|
ClientGameData game_data;
|
||||||
|
|
||||||
// Miscellaneous (used by chat commands)
|
// Miscellaneous (used by chat commands)
|
||||||
uint32_t next_exp_value; // next EXP value to give
|
uint32_t next_exp_value; // next EXP value to give
|
||||||
@@ -116,6 +116,8 @@ struct Client {
|
|||||||
Client(struct bufferevent* bev, GameVersion version,
|
Client(struct bufferevent* bev, GameVersion version,
|
||||||
ServerBehavior server_behavior);
|
ServerBehavior server_behavior);
|
||||||
|
|
||||||
|
void set_license(std::shared_ptr<const License> l);
|
||||||
|
|
||||||
ClientConfig export_config() const;
|
ClientConfig export_config() const;
|
||||||
ClientConfigBB export_config_bb() const;
|
ClientConfigBB export_config_bb() const;
|
||||||
void import_config(const ClientConfig& cc);
|
void import_config(const ClientConfig& cc);
|
||||||
|
|||||||
+128
-127
@@ -142,23 +142,24 @@ using namespace std;
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||||
|
auto player = c->game_data.player();
|
||||||
|
|
||||||
ssize_t equipped_weapon = -1;
|
ssize_t equipped_weapon = -1;
|
||||||
// ssize_t equipped_armor = -1;
|
// ssize_t equipped_armor = -1;
|
||||||
// ssize_t equipped_shield = -1;
|
// ssize_t equipped_shield = -1;
|
||||||
// ssize_t equipped_mag = -1;
|
// ssize_t equipped_mag = -1;
|
||||||
for (size_t y = 0; y < c->player.inventory.num_items; y++) {
|
for (size_t y = 0; y < c->game_data.player()->inventory.num_items; y++) {
|
||||||
if (c->player.inventory.items[y].equip_flags & 0x0008) {
|
if (c->game_data.player()->inventory.items[y].equip_flags & 0x0008) {
|
||||||
if (c->player.inventory.items[y].data.item_data1[0] == 0) {
|
if (c->game_data.player()->inventory.items[y].data.data1[0] == 0) {
|
||||||
equipped_weapon = y;
|
equipped_weapon = y;
|
||||||
}
|
}
|
||||||
// else if ((c->player.inventory.items[y].data.item_data1[0] == 1) &&
|
// else if ((c->game_data.player()->inventory.items[y].data.data1[0] == 1) &&
|
||||||
// (c->player.inventory.items[y].data.item_data1[1] == 1)) {
|
// (c->game_data.player()->inventory.items[y].data.data1[1] == 1)) {
|
||||||
// equipped_armor = y;
|
// equipped_armor = y;
|
||||||
// } else if ((c->player.inventory.items[y].data.item_data1[0] == 1) &&
|
// } else if ((c->game_data.player()->inventory.items[y].data.data1[0] == 1) &&
|
||||||
// (c->player.inventory.items[y].data.item_data1[1] == 2)) {
|
// (c->game_data.player()->inventory.items[y].data.data1[1] == 2)) {
|
||||||
// equipped_shield = y;
|
// equipped_shield = y;
|
||||||
// } else if (c->player.inventory.items[y].data.item_data1[0] == 2) {
|
// } else if (c->game_data.player()->inventory.items[y].data.data1[0] == 2) {
|
||||||
// equipped_mag = y;
|
// equipped_mag = y;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
@@ -166,42 +167,42 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
|||||||
|
|
||||||
bool should_delete_item = true;
|
bool should_delete_item = true;
|
||||||
|
|
||||||
auto& item = c->player.inventory.items[item_index];
|
auto& item = c->game_data.player()->inventory.items[item_index];
|
||||||
if (item.data.item_data1w[0] == 0x0203) { // technique disk
|
if (item.data.data1w[0] == 0x0203) { // technique disk
|
||||||
c->player.disp.technique_levels.data()[item.data.item_data1[4]] = item.data.item_data1[2];
|
c->game_data.player()->disp.technique_levels.data()[item.data.data1[4]] = item.data.data1[2];
|
||||||
|
|
||||||
} else if (item.data.item_data1w[0] == 0x0A03) { // grinder
|
} else if (item.data.data1w[0] == 0x0A03) { // grinder
|
||||||
if (equipped_weapon < 0) {
|
if (equipped_weapon < 0) {
|
||||||
throw invalid_argument("grinder used with no weapon equipped");
|
throw invalid_argument("grinder used with no weapon equipped");
|
||||||
}
|
}
|
||||||
if (item.data.item_data1[2] > 2) {
|
if (item.data.data1[2] > 2) {
|
||||||
throw invalid_argument("incorrect grinder value");
|
throw invalid_argument("incorrect grinder value");
|
||||||
}
|
}
|
||||||
c->player.inventory.items[equipped_weapon].data.item_data1[3] += (item.data.item_data1[2] + 1);
|
c->game_data.player()->inventory.items[equipped_weapon].data.data1[3] += (item.data.data1[2] + 1);
|
||||||
// TODO: we should check for max grind here
|
// TODO: we should check for max grind here
|
||||||
|
|
||||||
} else if (item.data.item_data1w[0] == 0x0B03) { // material
|
} else if (item.data.data1w[0] == 0x0B03) { // material
|
||||||
switch (item.data.item_data1[2]) {
|
switch (item.data.data1[2]) {
|
||||||
case 0: // Power Material
|
case 0: // Power Material
|
||||||
c->player.disp.stats.atp += 2;
|
c->game_data.player()->disp.stats.atp += 2;
|
||||||
break;
|
break;
|
||||||
case 1: // Mind Material
|
case 1: // Mind Material
|
||||||
c->player.disp.stats.mst += 2;
|
c->game_data.player()->disp.stats.mst += 2;
|
||||||
break;
|
break;
|
||||||
case 2: // Evade Material
|
case 2: // Evade Material
|
||||||
c->player.disp.stats.evp += 2;
|
c->game_data.player()->disp.stats.evp += 2;
|
||||||
break;
|
break;
|
||||||
case 3: // HP Material
|
case 3: // HP Material
|
||||||
c->player.inventory.hp_materials_used += 2;
|
c->game_data.player()->inventory.hp_materials_used += 2;
|
||||||
break;
|
break;
|
||||||
case 4: // TP Material
|
case 4: // TP Material
|
||||||
c->player.inventory.tp_materials_used += 2;
|
c->game_data.player()->inventory.tp_materials_used += 2;
|
||||||
break;
|
break;
|
||||||
case 5: // Def Material
|
case 5: // Def Material
|
||||||
c->player.disp.stats.dfp += 2;
|
c->game_data.player()->disp.stats.dfp += 2;
|
||||||
break;
|
break;
|
||||||
case 6: // Luck Material
|
case 6: // Luck Material
|
||||||
c->player.disp.stats.lck += 2;
|
c->game_data.player()->disp.stats.lck += 2;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("unknown material used");
|
throw invalid_argument("unknown material used");
|
||||||
@@ -209,17 +210,17 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// default item action is to unwrap the item if it's a present
|
// default item action is to unwrap the item if it's a present
|
||||||
if ((item.data.item_data1[0] == 2) && (item.data.item_data2[2] & 0x40)) {
|
if ((item.data.data1[0] == 2) && (item.data.data2[2] & 0x40)) {
|
||||||
item.data.item_data2[2] &= 0xBF;
|
item.data.data2[2] &= 0xBF;
|
||||||
should_delete_item = false;
|
should_delete_item = false;
|
||||||
} else if ((item.data.item_data1[0] != 2) && (item.data.item_data1[4] & 0x40)) {
|
} else if ((item.data.data1[0] != 2) && (item.data.data1[4] & 0x40)) {
|
||||||
item.data.item_data1[4] &= 0xBF;
|
item.data.data1[4] &= 0xBF;
|
||||||
should_delete_item = false;
|
should_delete_item = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (should_delete_item) {
|
if (should_delete_item) {
|
||||||
c->player.remove_item(item.data.item_id, 1);
|
c->game_data.player()->remove_item(item.data.id, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,56 +324,56 @@ ItemData CommonItemCreator::create_drop_item(bool is_box, uint8_t episode,
|
|||||||
int32_t type = this->decide_item_type(is_box);
|
int32_t type = this->decide_item_type(is_box);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 0x00: // material
|
case 0x00: // material
|
||||||
item.item_data1[0] = 0x03;
|
item.data1[0] = 0x03;
|
||||||
item.item_data1[1] = 0x0B;
|
item.data1[1] = 0x0B;
|
||||||
item.item_data1[2] = random_int(0, 6);
|
item.data1[2] = random_int(0, 6);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x01: // equipment
|
case 0x01: // equipment
|
||||||
switch (random_int(0, 3)) {
|
switch (random_int(0, 3)) {
|
||||||
case 0x00: // weapon
|
case 0x00: // weapon
|
||||||
item.item_data1[1] = random_int(1, 12); // random normal class
|
item.data1[1] = random_int(1, 12); // random normal class
|
||||||
item.item_data1[2] = difficulty + random_int(0, 2); // special type
|
item.data1[2] = difficulty + random_int(0, 2); // special type
|
||||||
if ((item.item_data1[1] > 0x09) && (item.item_data1[2] > 0x04)) {
|
if ((item.data1[1] > 0x09) && (item.data1[2] > 0x04)) {
|
||||||
item.item_data1[2] = 0x04; // no special classes above 4
|
item.data1[2] = 0x04; // no special classes above 4
|
||||||
}
|
}
|
||||||
item.item_data1[4] = 0x80; // untekked
|
item.data1[4] = 0x80; // untekked
|
||||||
if (item.item_data1[2] < 0x04) {
|
if (item.data1[2] < 0x04) {
|
||||||
item.item_data1[4] |= random_int(0, 40); // give a special
|
item.data1[4] |= random_int(0, 40); // give a special
|
||||||
}
|
}
|
||||||
for (size_t x = 0, y = 0; (x < 5) && (y < 3); x++) { // percentages
|
for (size_t x = 0, y = 0; (x < 5) && (y < 3); x++) { // percentages
|
||||||
if (random_int(0, 10) == 1) { // 1/11 chance of getting each type of percentage
|
if (random_int(0, 10) == 1) { // 1/11 chance of getting each type of percentage
|
||||||
item.item_data1[6 + (y * 2)] = x + 1;
|
item.data1[6 + (y * 2)] = x + 1;
|
||||||
item.item_data1[7 + (y * 2)] = random_int(0, 10) * 5;
|
item.data1[7 + (y * 2)] = random_int(0, 10) * 5;
|
||||||
y++;
|
y++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x01: // armor
|
case 0x01: // armor
|
||||||
item.item_data1[0] = 0x01;
|
item.data1[0] = 0x01;
|
||||||
item.item_data1[1] = 0x01;
|
item.data1[1] = 0x01;
|
||||||
item.item_data1[2] = (6 * difficulty) + random_int(0, ((area / 2) + 2) - 1); // standard type based on difficulty and area
|
item.data1[2] = (6 * difficulty) + random_int(0, ((area / 2) + 2) - 1); // standard type based on difficulty and area
|
||||||
if (item.item_data1[2] > 0x17) {
|
if (item.data1[2] > 0x17) {
|
||||||
item.item_data1[2] = 0x17; // no standard types above 0x17
|
item.data1[2] = 0x17; // no standard types above 0x17
|
||||||
}
|
}
|
||||||
if (random_int(0, 10) == 0) { // +/-
|
if (random_int(0, 10) == 0) { // +/-
|
||||||
item.item_data1[4] = random_int(0, 5);
|
item.data1[4] = random_int(0, 5);
|
||||||
item.item_data1[6] = random_int(0, 2);
|
item.data1[6] = random_int(0, 2);
|
||||||
}
|
}
|
||||||
item.item_data1[5] = random_int(0, 4); // slots
|
item.data1[5] = random_int(0, 4); // slots
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x02: // shield
|
case 0x02: // shield
|
||||||
item.item_data1[0] = 0x01;
|
item.data1[0] = 0x01;
|
||||||
item.item_data1[1] = 0x02;
|
item.data1[1] = 0x02;
|
||||||
item.item_data1[2] = (5 * difficulty) + random_int(0, ((area / 2) + 2) - 1); // standard type based on difficulty and area
|
item.data1[2] = (5 * difficulty) + random_int(0, ((area / 2) + 2) - 1); // standard type based on difficulty and area
|
||||||
if (item.item_data1[2] > 0x14) {
|
if (item.data1[2] > 0x14) {
|
||||||
item.item_data1[2] = 0x14; // no standard types above 0x14
|
item.data1[2] = 0x14; // no standard types above 0x14
|
||||||
}
|
}
|
||||||
if (random_int(0, 10) == 0) { // +/-
|
if (random_int(0, 10) == 0) { // +/-
|
||||||
item.item_data1[4] = random_int(0, 5);
|
item.data1[4] = random_int(0, 5);
|
||||||
item.item_data1[6] = random_int(0, 5);
|
item.data1[6] = random_int(0, 5);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -382,81 +383,81 @@ ItemData CommonItemCreator::create_drop_item(bool is_box, uint8_t episode,
|
|||||||
if (type == 0xFF) {
|
if (type == 0xFF) {
|
||||||
throw out_of_range("no item dropped"); // 0xFF -> no item drops
|
throw out_of_range("no item dropped"); // 0xFF -> no item drops
|
||||||
}
|
}
|
||||||
item.item_data1[0] = 0x01;
|
item.data1[0] = 0x01;
|
||||||
item.item_data1[1] = 0x03;
|
item.data1[1] = 0x03;
|
||||||
item.item_data1[2] = type;
|
item.data1[2] = type;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x02: // technique
|
case 0x02: // technique
|
||||||
item.item_data1[0] = 0x03;
|
item.data1[0] = 0x03;
|
||||||
item.item_data1[1] = 0x02;
|
item.data1[1] = 0x02;
|
||||||
item.item_data1[4] = random_int(0, 18); // tech type
|
item.data1[4] = random_int(0, 18); // tech type
|
||||||
if ((item.item_data1[4] != 14) && (item.item_data1[4] != 17)) { // if not ryuker or reverser, give it a level
|
if ((item.data1[4] != 14) && (item.data1[4] != 17)) { // if not ryuker or reverser, give it a level
|
||||||
if (item.item_data1[4] == 16) { // if not anti, give it a level between 1 and 30
|
if (item.data1[4] == 16) { // if not anti, give it a level between 1 and 30
|
||||||
if (area > 3) {
|
if (area > 3) {
|
||||||
item.item_data1[2] = difficulty + random_int(0, ((area - 1) / 2) - 1);
|
item.data1[2] = difficulty + random_int(0, ((area - 1) / 2) - 1);
|
||||||
} else {
|
} else {
|
||||||
item.item_data1[2] = difficulty;
|
item.data1[2] = difficulty;
|
||||||
}
|
}
|
||||||
if (item.item_data1[2] > 6) {
|
if (item.data1[2] > 6) {
|
||||||
item.item_data1[2] = 6;
|
item.data1[2] = 6;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item.item_data1[2] = (5 * difficulty) + random_int(0, ((area * 3) / 2) - 1); // else between 1 and 7
|
item.data1[2] = (5 * difficulty) + random_int(0, ((area * 3) / 2) - 1); // else between 1 and 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x03: // scape doll
|
case 0x03: // scape doll
|
||||||
item.item_data1[0] = 0x03;
|
item.data1[0] = 0x03;
|
||||||
item.item_data1[1] = 0x09;
|
item.data1[1] = 0x09;
|
||||||
item.item_data1[2] = 0x00;
|
item.data1[2] = 0x00;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x04: // grinder
|
case 0x04: // grinder
|
||||||
item.item_data1[0] = 0x03;
|
item.data1[0] = 0x03;
|
||||||
item.item_data1[1] = 0x0A;
|
item.data1[1] = 0x0A;
|
||||||
item.item_data1[2] = random_int(0, 2); // mono, di, tri
|
item.data1[2] = random_int(0, 2); // mono, di, tri
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x05: // consumable
|
case 0x05: // consumable
|
||||||
item.item_data1[0] = 0x03;
|
item.data1[0] = 0x03;
|
||||||
item.item_data1[5] = 0x01;
|
item.data1[5] = 0x01;
|
||||||
switch (random_int(0, 2)) {
|
switch (random_int(0, 2)) {
|
||||||
case 0: // antidote / antiparalysis
|
case 0: // antidote / antiparalysis
|
||||||
item.item_data1[1] = 6;
|
item.data1[1] = 6;
|
||||||
item.item_data1[2] = random_int(0, 1);
|
item.data1[2] = random_int(0, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1: // telepipe / trap vision
|
case 1: // telepipe / trap vision
|
||||||
item.item_data1[1] = 7 + random_int(0, 1);
|
item.data1[1] = 7 + random_int(0, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2: // sol / moon / star atomizer
|
case 2: // sol / moon / star atomizer
|
||||||
item.item_data1[1] = 3 + random_int(0, 2);
|
item.data1[1] = 3 + random_int(0, 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x06: // consumable
|
case 0x06: // consumable
|
||||||
item.item_data1[0] = 0x03;
|
item.data1[0] = 0x03;
|
||||||
item.item_data1[5] = 0x01;
|
item.data1[5] = 0x01;
|
||||||
item.item_data1[1] = random_int(0, 1); // mate or fluid
|
item.data1[1] = random_int(0, 1); // mate or fluid
|
||||||
if (difficulty == 0) {
|
if (difficulty == 0) {
|
||||||
item.item_data1[2] = random_int(0, 1); // only mono and di on normal
|
item.data1[2] = random_int(0, 1); // only mono and di on normal
|
||||||
} else if (difficulty == 3) {
|
} else if (difficulty == 3) {
|
||||||
item.item_data1[2] = random_int(1, 2); // only di and tri on ultimate
|
item.data1[2] = random_int(1, 2); // only di and tri on ultimate
|
||||||
} else {
|
} else {
|
||||||
item.item_data1[2] = random_int(0, 2); // else, any of the three
|
item.data1[2] = random_int(0, 2); // else, any of the three
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x07: // meseta
|
case 0x07: // meseta
|
||||||
item.item_data1[0] = 0x04;
|
item.data1[0] = 0x04;
|
||||||
item.item_data2d = (90 * difficulty) + (random_int(0, 20) * (area * 2)); // meseta amount
|
item.data2d = (90 * difficulty) + (random_int(0, 20) * (area * 2)); // meseta amount
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -477,27 +478,27 @@ ItemData CommonItemCreator::create_shop_item(uint8_t difficulty,
|
|||||||
ItemData item;
|
ItemData item;
|
||||||
memset(&item, 0, sizeof(item));
|
memset(&item, 0, sizeof(item));
|
||||||
|
|
||||||
item.item_data1[0] = item_type;
|
item.data1[0] = item_type;
|
||||||
while (item.item_data1[0] == 2) {
|
while (item.data1[0] == 2) {
|
||||||
item.item_data1[0] = rand() % 3;
|
item.data1[0] = rand() % 3;
|
||||||
}
|
}
|
||||||
switch (item.item_data1[0]) {
|
switch (item.data1[0]) {
|
||||||
case 0: { // weapon
|
case 0: { // weapon
|
||||||
item.item_data1[1] = (rand() % 12) + 1;
|
item.data1[1] = (rand() % 12) + 1;
|
||||||
if (item.item_data1[1] > 9) {
|
if (item.data1[1] > 9) {
|
||||||
item.item_data1[2] = difficulty;
|
item.data1[2] = difficulty;
|
||||||
} else {
|
} else {
|
||||||
item.item_data1[2] = (rand() & 1) + difficulty;
|
item.data1[2] = (rand() & 1) + difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.item_data1[3] = rand() % 11;
|
item.data1[3] = rand() % 11;
|
||||||
item.item_data1[4] = rand() % 11;
|
item.data1[4] = rand() % 11;
|
||||||
|
|
||||||
size_t num_percentages = 0;
|
size_t num_percentages = 0;
|
||||||
for (size_t x = 0; (x < 5) && (num_percentages < 3); x++) {
|
for (size_t x = 0; (x < 5) && (num_percentages < 3); x++) {
|
||||||
if ((rand() % 4) == 1) {
|
if ((rand() % 4) == 1) {
|
||||||
item.item_data1[(num_percentages * 2) + 6] = x;
|
item.data1[(num_percentages * 2) + 6] = x;
|
||||||
item.item_data1[(num_percentages * 2) + 7] = rand() % (max_percentages[difficulty] + 1);
|
item.data1[(num_percentages * 2) + 7] = rand() % (max_percentages[difficulty] + 1);
|
||||||
num_percentages++;
|
num_percentages++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,69 +506,69 @@ ItemData CommonItemCreator::create_shop_item(uint8_t difficulty,
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 1: // armor
|
case 1: // armor
|
||||||
item.item_data1[1] = 0;
|
item.data1[1] = 0;
|
||||||
while (item.item_data1[1] == 0) {
|
while (item.data1[1] == 0) {
|
||||||
item.item_data1[1] = rand() & 3;
|
item.data1[1] = rand() & 3;
|
||||||
}
|
}
|
||||||
switch (item.item_data1[1]) {
|
switch (item.data1[1]) {
|
||||||
case 1:
|
case 1:
|
||||||
item.item_data1[2] = (rand() % 6) + (difficulty * 6);
|
item.data1[2] = (rand() % 6) + (difficulty * 6);
|
||||||
item.item_data1[5] = rand() % 5;
|
item.data1[5] = rand() % 5;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
item.item_data2[2] = (rand() % 6) + (difficulty * 5);
|
item.data2[2] = (rand() % 6) + (difficulty * 5);
|
||||||
*reinterpret_cast<short*>(&item.item_data1[6]) = (rand() % 9) - 4;
|
*reinterpret_cast<short*>(&item.data1[6]) = (rand() % 9) - 4;
|
||||||
*reinterpret_cast<short*>(&item.item_data1[9]) = (rand() % 9) - 4;
|
*reinterpret_cast<short*>(&item.data1[9]) = (rand() % 9) - 4;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
item.item_data2[2] = rand() % 0x3B;
|
item.data2[2] = rand() % 0x3B;
|
||||||
*reinterpret_cast<short*>(&item.item_data1[7]) = (rand() % 5) - 4;
|
*reinterpret_cast<short*>(&item.data1[7]) = (rand() % 5) - 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3: // tool
|
case 3: // tool
|
||||||
item.item_data1[1] = rand() % 12;
|
item.data1[1] = rand() % 12;
|
||||||
switch (item.item_data1[1]) {
|
switch (item.data1[1]) {
|
||||||
case 0:
|
case 0:
|
||||||
case 1:
|
case 1:
|
||||||
if (difficulty == 0) {
|
if (difficulty == 0) {
|
||||||
item.item_data1[2] = 0;
|
item.data1[2] = 0;
|
||||||
} else if (difficulty == 1) {
|
} else if (difficulty == 1) {
|
||||||
item.item_data1[2] = rand() % 2;
|
item.data1[2] = rand() % 2;
|
||||||
} else if (difficulty == 2) {
|
} else if (difficulty == 2) {
|
||||||
item.item_data1[2] = (rand() % 2) + 1;
|
item.data1[2] = (rand() % 2) + 1;
|
||||||
} else if (difficulty == 3) {
|
} else if (difficulty == 3) {
|
||||||
item.item_data1[2] = 2;
|
item.data1[2] = 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6:
|
case 6:
|
||||||
item.item_data1[2] = rand() % 2;
|
item.data1[2] = rand() % 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 10:
|
case 10:
|
||||||
item.item_data1[2] = rand() % 3;
|
item.data1[2] = rand() % 3;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 11:
|
case 11:
|
||||||
item.item_data1[2] = rand() % 7;
|
item.data1[2] = rand() % 7;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (item.item_data1[1]) {
|
switch (item.data1[1]) {
|
||||||
case 2:
|
case 2:
|
||||||
item.item_data1[4] = rand() % 19;
|
item.data1[4] = rand() % 19;
|
||||||
switch (item.item_data1[4]) {
|
switch (item.data1[4]) {
|
||||||
case 14:
|
case 14:
|
||||||
case 17:
|
case 17:
|
||||||
item.item_data1[2] = 0; // reverser & ryuker always level 1
|
item.data1[2] = 0; // reverser & ryuker always level 1
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
item.item_data1[2] = rand() % max_anti_level[difficulty];
|
item.data1[2] = rand() % max_anti_level[difficulty];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
item.item_data1[2] = rand() % max_tech_level[difficulty];
|
item.data1[2] = rand() % max_tech_level[difficulty];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
@@ -579,7 +580,7 @@ ItemData CommonItemCreator::create_shop_item(uint8_t difficulty,
|
|||||||
case 7:
|
case 7:
|
||||||
case 8:
|
case 8:
|
||||||
case 16:
|
case 16:
|
||||||
item.item_data1[5] = rand() % (max_quantity[difficulty] + 1);
|
item.data1[5] = rand() % (max_quantity[difficulty] + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-3
@@ -3,8 +3,21 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <phosg/Encoding.hh>
|
||||||
|
|
||||||
#include "Player.hh"
|
|
||||||
|
|
||||||
|
struct PlayerStats {
|
||||||
|
le_uint16_t atp;
|
||||||
|
le_uint16_t mst;
|
||||||
|
le_uint16_t evp;
|
||||||
|
le_uint16_t hp;
|
||||||
|
le_uint16_t dfp;
|
||||||
|
le_uint16_t ata;
|
||||||
|
le_uint16_t lck;
|
||||||
|
|
||||||
|
PlayerStats() noexcept;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
// information on a single level for a single class
|
// information on a single level for a single class
|
||||||
struct LevelStats {
|
struct LevelStats {
|
||||||
@@ -15,7 +28,7 @@ struct LevelStats {
|
|||||||
uint8_t dfp; // dfp to add on level up
|
uint8_t dfp; // dfp to add on level up
|
||||||
uint8_t ata; // ata to add on level up
|
uint8_t ata; // ata to add on level up
|
||||||
uint8_t unknown[2];
|
uint8_t unknown[2];
|
||||||
uint32_t experience; // EXP value of this level
|
le_uint32_t experience; // EXP value of this level
|
||||||
|
|
||||||
void apply(PlayerStats& ps) const;
|
void apply(PlayerStats& ps) const;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
@@ -23,7 +36,7 @@ struct LevelStats {
|
|||||||
// level table format (PlyLevelTbl.prs)
|
// level table format (PlyLevelTbl.prs)
|
||||||
struct LevelTable {
|
struct LevelTable {
|
||||||
PlayerStats base_stats[12];
|
PlayerStats base_stats[12];
|
||||||
uint32_t unknown[12];
|
le_uint32_t unknown[12];
|
||||||
LevelStats levels[12][200];
|
LevelStats levels[12][200];
|
||||||
|
|
||||||
LevelTable(const std::string& filename, bool compressed);
|
LevelTable(const std::string& filename, bool compressed);
|
||||||
|
|||||||
+5
-5
@@ -99,12 +99,12 @@ void Lobby::add_client(shared_ptr<Client> c, bool reverse_indexes) {
|
|||||||
|
|
||||||
// If the lobby is a game, assign the inventory's item IDs
|
// If the lobby is a game, assign the inventory's item IDs
|
||||||
if (this->is_game()) {
|
if (this->is_game()) {
|
||||||
auto& inv = c->player.inventory;
|
auto& inv = c->game_data.player()->inventory;
|
||||||
size_t count = max<uint8_t>(inv.num_items, 30);
|
size_t count = max<uint8_t>(inv.num_items, 30);
|
||||||
for (size_t x = 0; x < count; x++) {
|
for (size_t x = 0; x < count; x++) {
|
||||||
inv.items[x].data.item_id = 0x00010000 + 0x00200000 * c->lobby_client_id + x;
|
inv.items[x].data.id = 0x00010000 + 0x00200000 * c->lobby_client_id + x;
|
||||||
}
|
}
|
||||||
c->player.print_inventory(stderr);
|
c->game_data.player()->print_inventory(stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ shared_ptr<Client> Lobby::find_client(const u16string* identifier,
|
|||||||
(this->clients[x]->license->serial_number == serial_number)) {
|
(this->clients[x]->license->serial_number == serial_number)) {
|
||||||
return this->clients[x];
|
return this->clients[x];
|
||||||
}
|
}
|
||||||
if (identifier && (this->clients[x]->player.disp.name == *identifier)) {
|
if (identifier && (this->clients[x]->game_data.player()->disp.name == *identifier)) {
|
||||||
return this->clients[x];
|
return this->clients[x];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ uint8_t Lobby::game_event_for_lobby_event(uint8_t lobby_event) {
|
|||||||
|
|
||||||
|
|
||||||
void Lobby::add_item(const PlayerInventoryItem& item, uint8_t area, float x, float z) {
|
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];
|
auto& fi = this->item_id_to_floor_item[item.data.id];
|
||||||
fi.inv_item = item;
|
fi.inv_item = item;
|
||||||
fi.area = area;
|
fi.area = area;
|
||||||
fi.x = x;
|
fi.x = x;
|
||||||
|
|||||||
@@ -574,7 +574,6 @@ void PSOBBMultiKeyServerEncryption::ensure_stream_ready() {
|
|||||||
if (!this->client_crypt->active_key.get()) {
|
if (!this->client_crypt->active_key.get()) {
|
||||||
throw logic_error("server crypt cannot be initialized because client crypt is not ready");
|
throw logic_error("server crypt cannot be initialized because client crypt is not ready");
|
||||||
}
|
}
|
||||||
log(INFO, "[PSOBB/MK] Generating server stream");
|
|
||||||
this->stream = PSOBBEncryption::generate_stream(
|
this->stream = PSOBBEncryption::generate_stream(
|
||||||
*this->client_crypt->active_key, this->seed.data(), this->seed.size());
|
*this->client_crypt->active_key, this->seed.data(), this->seed.size());
|
||||||
}
|
}
|
||||||
|
|||||||
+288
-224
@@ -15,10 +15,13 @@ using namespace std;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// originally there was going to be a language-based header, but then I decided against it.
|
// Originally there was going to be a language-based header, but then I decided
|
||||||
// these strings were already in use for that parser, so I didn't bother changing them.
|
// against it. These strings were already in use for that parser, so I didn't
|
||||||
#define PLAYER_FILE_SIGNATURE "newserv player file format; 10 sections present; sequential;"
|
// bother changing them.
|
||||||
#define ACCOUNT_FILE_SIGNATURE "newserv account file format; 7 sections present; sequential;"
|
static const string PLAYER_FILE_SIGNATURE =
|
||||||
|
"newserv player file format; 10 sections present; sequential;";
|
||||||
|
static const string ACCOUNT_FILE_SIGNATURE =
|
||||||
|
"newserv account file format; 7 sections present; sequential;";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -52,14 +55,15 @@ PlayerDispDataPCGC::PlayerDispDataPCGC() noexcept
|
|||||||
void PlayerDispDataPCGC::enforce_pc_limits() {
|
void PlayerDispDataPCGC::enforce_pc_limits() {
|
||||||
// PC has fewer classes, so we'll substitute some here
|
// PC has fewer classes, so we'll substitute some here
|
||||||
if (this->char_class == 11) {
|
if (this->char_class == 11) {
|
||||||
this->char_class = 0; // fomar -> humar
|
this->char_class = 0; // FOmar -> HUmar
|
||||||
} else if (this->char_class == 10) {
|
} else if (this->char_class == 10) {
|
||||||
this->char_class = 1; // ramarl -> hunewearl
|
this->char_class = 1; // RAmarl -> HUnewearl
|
||||||
} else if (this->char_class == 9) {
|
} else if (this->char_class == 9) {
|
||||||
this->char_class = 5; // hucaseal -> racaseal
|
this->char_class = 5; // HUcaseal -> RAcaseal
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the player is still not a valid class, make them appear as the "ninja" NPC
|
// If the player is somehow still not a valid class, make them appear as the
|
||||||
|
// "ninja" NPC
|
||||||
if (this->char_class > 8) {
|
if (this->char_class > 8) {
|
||||||
this->extra_model = 0;
|
this->extra_model = 0;
|
||||||
this->v2_flags |= 2;
|
this->v2_flags |= 2;
|
||||||
@@ -67,7 +71,6 @@ void PlayerDispDataPCGC::enforce_pc_limits() {
|
|||||||
this->version = 2;
|
this->version = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// converts PC/GC player data to BB format
|
|
||||||
PlayerDispDataBB PlayerDispDataPCGC::to_bb() const {
|
PlayerDispDataBB PlayerDispDataPCGC::to_bb() const {
|
||||||
PlayerDispDataBB bb;
|
PlayerDispDataBB bb;
|
||||||
bb.stats.atp = this->stats.atp;
|
bb.stats.atp = this->stats.atp;
|
||||||
@@ -134,7 +137,6 @@ PlayerDispDataBB::PlayerDispDataBB() noexcept
|
|||||||
proportion_x(0),
|
proportion_x(0),
|
||||||
proportion_y(0) { }
|
proportion_y(0) { }
|
||||||
|
|
||||||
// converts BB player data to PC/GC format
|
|
||||||
PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const {
|
PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const {
|
||||||
PlayerDispDataPCGC pcgc;
|
PlayerDispDataPCGC pcgc;
|
||||||
pcgc.stats.atp = this->stats.atp;
|
pcgc.stats.atp = this->stats.atp;
|
||||||
@@ -174,7 +176,6 @@ PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const {
|
|||||||
return pcgc;
|
return pcgc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a player preview, which can then be sent to a BB client for character select
|
|
||||||
PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
|
PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
|
||||||
PlayerDispDataBBPreview pre;
|
PlayerDispDataBBPreview pre;
|
||||||
pre.level = this->level;
|
pre.level = this->level;
|
||||||
@@ -271,7 +272,7 @@ GuildCardBB::GuildCardBB() noexcept
|
|||||||
void PlayerBank::load(const string& filename) {
|
void PlayerBank::load(const string& filename) {
|
||||||
*this = load_object_file<PlayerBank>(filename);
|
*this = load_object_file<PlayerBank>(filename);
|
||||||
for (uint32_t x = 0; x < this->num_items; x++) {
|
for (uint32_t x = 0; x < this->num_items; x++) {
|
||||||
this->items[x].data.item_id = 0x0F010000 + x;
|
this->items[x].data.id = 0x0F010000 + x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,72 +282,8 @@ void PlayerBank::save(const string& filename) const {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Player::import(const PSOPlayerDataPC& pc) {
|
|
||||||
this->inventory = pc.inventory;
|
|
||||||
this->disp = pc.disp.to_bb();
|
|
||||||
// TODO: Add these fields to the existing structure so we can parse them
|
|
||||||
// this->info_board = pc.info_board;
|
|
||||||
// this->blocked_senders = pc.blocked_senders;
|
|
||||||
// this->auto_reply = pc.auto_reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::import(const PSOPlayerDataGC& gc) {
|
|
||||||
this->inventory = gc.inventory;
|
|
||||||
this->disp = gc.disp.to_bb();
|
|
||||||
this->info_board = gc.info_board;
|
|
||||||
this->blocked_senders = gc.blocked_senders;
|
|
||||||
if (gc.auto_reply_enabled) {
|
|
||||||
this->auto_reply = gc.auto_reply;
|
|
||||||
} else {
|
|
||||||
this->auto_reply.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::import(const PSOPlayerDataBB& bb) {
|
|
||||||
// Note: we don't copy the inventory and disp here because we already have
|
|
||||||
// it (we sent the player data to the client in the first place)
|
|
||||||
this->info_board = bb.info_board;
|
|
||||||
this->blocked_senders = bb.blocked_senders;
|
|
||||||
if (bb.auto_reply_enabled) {
|
|
||||||
this->auto_reply = bb.auto_reply;
|
|
||||||
} else {
|
|
||||||
this->auto_reply.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerBB Player::export_bb_player_data() const {
|
|
||||||
PlayerBB bb;
|
|
||||||
bb.inventory = this->inventory;
|
|
||||||
bb.disp = this->disp;
|
|
||||||
bb.unknown.clear();
|
|
||||||
bb.option_flags = this->option_flags;
|
|
||||||
bb.quest_data1 = this->quest_data1;
|
|
||||||
bb.bank = this->bank;
|
|
||||||
bb.serial_number = this->serial_number;
|
|
||||||
bb.name = this->disp.name;
|
|
||||||
bb.team_name = this->team_name;
|
|
||||||
bb.guild_card_desc = this->guild_card_desc;
|
|
||||||
bb.reserved1 = 0;
|
|
||||||
bb.reserved2 = 0;
|
|
||||||
bb.section_id = this->disp.section_id;
|
|
||||||
bb.char_class = this->disp.char_class;
|
|
||||||
bb.unknown3 = 0;
|
|
||||||
bb.symbol_chats = this->symbol_chats;
|
|
||||||
bb.shortcuts = this->shortcuts;
|
|
||||||
bb.auto_reply = this->auto_reply;
|
|
||||||
bb.info_board = this->info_board;
|
|
||||||
bb.unknown5.clear();
|
|
||||||
bb.challenge_data = this->challenge_data;
|
|
||||||
bb.tech_menu_config = this->tech_menu_config;
|
|
||||||
bb.unknown6.clear();
|
|
||||||
bb.quest_data2 = this->quest_data2;
|
|
||||||
bb.key_config = this->key_config;
|
|
||||||
return bb;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// checksums the guild card file for BB player account data
|
|
||||||
uint32_t compute_guild_card_checksum(const void* vdata, size_t size) {
|
uint32_t compute_guild_card_checksum(const void* vdata, size_t size) {
|
||||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(vdata);
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(vdata);
|
||||||
|
|
||||||
@@ -364,65 +301,213 @@ uint32_t compute_guild_card_checksum(const void* vdata, size_t size) {
|
|||||||
return (cs ^ 0xFFFFFFFF);
|
return (cs ^ 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::load_account_data(const string& filename) {
|
ClientGameData::~ClientGameData() {
|
||||||
SavedAccountBB account = load_object_file<SavedAccountBB>(filename);
|
if (!this->bb_username.empty()) {
|
||||||
if (account.signature != ACCOUNT_FILE_SIGNATURE) {
|
if (this->account_data.get()) {
|
||||||
|
this->save_account_data();
|
||||||
|
}
|
||||||
|
if (this->player_data.get()) {
|
||||||
|
this->save_player_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<SavedAccountDataBB> ClientGameData::account(bool should_load) {
|
||||||
|
if (!this->account_data.get() && should_load) {
|
||||||
|
if (this->bb_username.empty()) {
|
||||||
|
this->account_data.reset(new SavedAccountDataBB());
|
||||||
|
} else {
|
||||||
|
this->load_account_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this->account_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<SavedPlayerDataBB> ClientGameData::player(bool should_load) {
|
||||||
|
if (!this->player_data.get() && should_load) {
|
||||||
|
if (this->bb_username.empty()) {
|
||||||
|
this->player_data.reset(new SavedPlayerDataBB());
|
||||||
|
} else {
|
||||||
|
this->load_player_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this->player_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<const SavedAccountDataBB> ClientGameData::account() const {
|
||||||
|
if (!this->account_data.get()) {
|
||||||
|
throw runtime_error("account data is not loaded");
|
||||||
|
}
|
||||||
|
return this->account_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<const SavedPlayerDataBB> ClientGameData::player() const {
|
||||||
|
if (!this->player_data.get()) {
|
||||||
|
throw runtime_error("player data is not loaded");
|
||||||
|
}
|
||||||
|
return this->player_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ClientGameData::account_data_filename() const {
|
||||||
|
if (this->bb_username.empty()) {
|
||||||
|
throw logic_error("non-BB players do not have account data");
|
||||||
|
}
|
||||||
|
return string_printf("system/players/account_%s.nsa",
|
||||||
|
this->bb_username.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string ClientGameData::player_data_filename() const {
|
||||||
|
if (this->bb_username.empty()) {
|
||||||
|
throw logic_error("non-BB players do not have account data");
|
||||||
|
}
|
||||||
|
if (this->bb_player_index == 0) {
|
||||||
|
throw logic_error("0 is not a valid player index");
|
||||||
|
}
|
||||||
|
return string_printf("system/players/player_%s_%zu.nsc",
|
||||||
|
this->bb_username.c_str(), this->bb_player_index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
string ClientGameData::player_template_filename(uint8_t char_class) {
|
||||||
|
return string_printf("system/blueburst/player_class_%hhu.nsc", char_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientGameData::create_player(
|
||||||
|
const PlayerDispDataBBPreview& preview,
|
||||||
|
shared_ptr<const LevelTable> level_table) {
|
||||||
|
shared_ptr<SavedPlayerDataBB> data(new SavedPlayerDataBB(
|
||||||
|
load_object_file<SavedPlayerDataBB>(player_template_filename(preview.char_class))));
|
||||||
|
if (data->signature != PLAYER_FILE_SIGNATURE) {
|
||||||
|
throw runtime_error("player data header is incorrect");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
data->disp.apply_preview(preview);
|
||||||
|
data->disp.stats = level_table->base_stats_for_class(data->disp.char_class);
|
||||||
|
} catch (const exception& e) {
|
||||||
|
throw runtime_error(string_printf("template application failed: %s", e.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->player_data = data;
|
||||||
|
|
||||||
|
this->save_player_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientGameData::load_account_data() {
|
||||||
|
string filename = this->account_data_filename();
|
||||||
|
|
||||||
|
shared_ptr<SavedAccountDataBB> data;
|
||||||
|
try {
|
||||||
|
data.reset(new SavedAccountDataBB(
|
||||||
|
load_object_file<SavedAccountDataBB>(filename)));
|
||||||
|
if (data->signature != ACCOUNT_FILE_SIGNATURE) {
|
||||||
throw runtime_error("account data header is incorrect");
|
throw runtime_error("account data header is incorrect");
|
||||||
}
|
}
|
||||||
this->blocked_senders = account.blocked_senders;
|
} catch (const exception& e) {
|
||||||
this->guild_cards = account.guild_cards;
|
log(INFO, "[BB/Account] No account data for %s; using default",
|
||||||
this->key_config = account.key_config;
|
this->bb_username.c_str());
|
||||||
this->option_flags = account.option_flags;
|
data.reset(new SavedAccountDataBB(
|
||||||
this->shortcuts = account.shortcuts;
|
load_object_file<SavedAccountDataBB>("system/blueburst/default.nsa")));
|
||||||
this->symbol_chats = account.symbol_chats;
|
if (data->signature != ACCOUNT_FILE_SIGNATURE) {
|
||||||
this->team_name = account.team_name;
|
throw runtime_error("default account data header is incorrect");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::save_account_data(const string& filename) const {
|
this->account_data = data;
|
||||||
SavedAccountBB account;
|
log(INFO, "Loaded account data file %s", filename.c_str());
|
||||||
account.signature = ACCOUNT_FILE_SIGNATURE;
|
|
||||||
account.blocked_senders = this->blocked_senders;
|
|
||||||
account.guild_cards = this->guild_cards;
|
|
||||||
account.key_config = this->key_config;
|
|
||||||
account.option_flags = this->option_flags;
|
|
||||||
account.shortcuts = this->shortcuts;
|
|
||||||
account.symbol_chats = this->symbol_chats;
|
|
||||||
account.team_name = this->team_name;
|
|
||||||
save_file(filename, &account, sizeof(account));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::load_player_data(const string& filename) {
|
void ClientGameData::save_account_data() const {
|
||||||
SavedPlayerBB player = load_object_file<SavedPlayerBB>(filename);
|
string filename = this->account_data_filename();
|
||||||
if (player.signature != PLAYER_FILE_SIGNATURE) {
|
save_file(filename, this->account_data.get(), sizeof(SavedAccountDataBB));
|
||||||
throw runtime_error("account data header is incorrect");
|
log(INFO, "Saved account data file %s", filename.c_str());
|
||||||
}
|
|
||||||
this->auto_reply = player.auto_reply;
|
|
||||||
this->bank = player.bank;
|
|
||||||
this->challenge_data = player.challenge_data;
|
|
||||||
this->disp = player.disp;
|
|
||||||
this->guild_card_desc = player.guild_card_desc;
|
|
||||||
this->info_board = player.info_board;
|
|
||||||
this->inventory = player.inventory;
|
|
||||||
this->quest_data1 = player.quest_data1;
|
|
||||||
this->quest_data2 = player.quest_data2;
|
|
||||||
this->tech_menu_config = player.tech_menu_config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::save_player_data(const string& filename) const {
|
void ClientGameData::load_player_data() {
|
||||||
SavedPlayerBB player;
|
string filename = this->player_data_filename();
|
||||||
player.signature = PLAYER_FILE_SIGNATURE;
|
shared_ptr<SavedPlayerDataBB> data(new SavedPlayerDataBB(
|
||||||
player.preview = this->disp.to_preview();
|
load_object_file<SavedPlayerDataBB>(filename)));
|
||||||
player.auto_reply = this->auto_reply;
|
if (data->signature != PLAYER_FILE_SIGNATURE) {
|
||||||
player.bank = this->bank;
|
throw runtime_error("player data header is incorrect");
|
||||||
player.challenge_data = this->challenge_data;
|
}
|
||||||
player.disp = this->disp;
|
this->player_data = data;
|
||||||
player.guild_card_desc = this->guild_card_desc;
|
log(INFO, "Loaded player data file %s", filename.c_str());
|
||||||
player.info_board = this->info_board;
|
}
|
||||||
player.inventory = this->inventory;
|
|
||||||
player.quest_data1 = this->quest_data1;
|
void ClientGameData::save_player_data() const {
|
||||||
player.quest_data2 = this->quest_data2;
|
string filename = this->player_data_filename();
|
||||||
player.tech_menu_config = this->tech_menu_config;
|
save_file(filename, this->player_data.get(), sizeof(SavedPlayerDataBB));
|
||||||
save_file(filename, &player, sizeof(player));
|
log(INFO, "Saved player data file %s", filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientGameData::import_player(const PSOPlayerDataPC& pc) {
|
||||||
|
auto player = this->player();
|
||||||
|
player->inventory = pc.inventory;
|
||||||
|
player->disp = pc.disp.to_bb();
|
||||||
|
// TODO: Add these fields to the command structure so we can parse them
|
||||||
|
// info_board = pc.info_board;
|
||||||
|
// blocked_senders = pc.blocked_senders;
|
||||||
|
// auto_reply = pc.auto_reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientGameData::import_player(const PSOPlayerDataGC& gc) {
|
||||||
|
auto account = this->account();
|
||||||
|
auto player = this->player();
|
||||||
|
player->inventory = gc.inventory;
|
||||||
|
player->disp = gc.disp.to_bb();
|
||||||
|
player->info_board = gc.info_board;
|
||||||
|
account->blocked_senders = gc.blocked_senders;
|
||||||
|
if (gc.auto_reply_enabled) {
|
||||||
|
player->auto_reply = gc.auto_reply;
|
||||||
|
} else {
|
||||||
|
player->auto_reply.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientGameData::import_player(const PSOPlayerDataBB& bb) {
|
||||||
|
auto account = this->account();
|
||||||
|
auto player = this->player();
|
||||||
|
// Note: we don't copy the inventory and disp here because we already have
|
||||||
|
// them (we sent the player data to the client in the first place)
|
||||||
|
player->info_board = bb.info_board;
|
||||||
|
account->blocked_senders = bb.blocked_senders;
|
||||||
|
if (bb.auto_reply_enabled) {
|
||||||
|
player->auto_reply = bb.auto_reply;
|
||||||
|
} else {
|
||||||
|
player->auto_reply.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerBB ClientGameData::export_player_bb() const {
|
||||||
|
auto account = this->account();
|
||||||
|
auto player = this->player();
|
||||||
|
|
||||||
|
PlayerBB ret;
|
||||||
|
ret.inventory = player->inventory;
|
||||||
|
ret.disp = player->disp;
|
||||||
|
ret.unknown.clear();
|
||||||
|
ret.option_flags = account->option_flags;
|
||||||
|
ret.quest_data1 = player->quest_data1;
|
||||||
|
ret.bank = player->bank;
|
||||||
|
ret.serial_number = this->serial_number;
|
||||||
|
ret.name = player->disp.name;
|
||||||
|
ret.team_name = account->team_name;
|
||||||
|
ret.guild_card_desc = player->guild_card_desc;
|
||||||
|
ret.reserved1 = 0;
|
||||||
|
ret.reserved2 = 0;
|
||||||
|
ret.section_id = player->disp.section_id;
|
||||||
|
ret.char_class = player->disp.char_class;
|
||||||
|
ret.unknown3 = 0;
|
||||||
|
ret.symbol_chats = account->symbol_chats;
|
||||||
|
ret.shortcuts = account->shortcuts;
|
||||||
|
ret.auto_reply = player->auto_reply;
|
||||||
|
ret.info_board = player->info_board;
|
||||||
|
ret.unknown5.clear();
|
||||||
|
ret.challenge_data = player->challenge_data;
|
||||||
|
ret.tech_menu_config = player->tech_menu_config;
|
||||||
|
ret.unknown6.clear();
|
||||||
|
ret.quest_data2 = player->quest_data2;
|
||||||
|
ret.key_config = account->key_config;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -440,62 +525,63 @@ PlayerLobbyDataBB::PlayerLobbyDataBB() noexcept
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const uint32_t meseta_identifier = 0x00040000;
|
constexpr uint32_t MESETA_IDENTIFIER = 0x00040000;
|
||||||
|
|
||||||
|
ItemData::ItemData() {
|
||||||
|
this->data1d[0] = 0;
|
||||||
|
this->data1d[1] = 0;
|
||||||
|
this->data1d[2] = 0;
|
||||||
|
this->id = 0xFFFFFFFF;
|
||||||
|
this->data2d = 0;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t ItemData::primary_identifier() const {
|
uint32_t ItemData::primary_identifier() const {
|
||||||
if (this->item_data1[0] == 0x03 && this->item_data1[1] == 0x02) {
|
if (this->data1[0] == 0x03 && this->data1[1] == 0x02) {
|
||||||
return 0x00030200; // Tech disk (data1[2] is level, so omit it)
|
return 0x00030200; // Tech disk (data1[2] is level, so omit it)
|
||||||
} else if (this->item_data1[0] == 0x02) {
|
} else if (this->data1[0] == 0x02) {
|
||||||
return 0x00020000 | (this->item_data1[1] << 8); // Mag
|
return 0x00020000 | (this->data1[1] << 8); // Mag
|
||||||
} else {
|
} else {
|
||||||
return (this->item_data1[0] << 16) | (this->item_data1[1] << 8) | this->item_data1[2];
|
return (this->data1[0] << 16) | (this->data1[1] << 8) | this->data1[2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerBankItem PlayerInventoryItem::to_bank_item() const {
|
PlayerInventoryItem::PlayerInventoryItem()
|
||||||
PlayerBankItem bank_item;
|
: equip_flags(0x0000), tech_flag(0x0000), data() { }
|
||||||
bank_item.data = this->data;
|
|
||||||
|
|
||||||
if (combine_item_to_max.count(this->data.primary_identifier())) {
|
PlayerInventoryItem::PlayerInventoryItem(const PlayerBankItem& src)
|
||||||
bank_item.amount = this->data.item_data1[5];
|
: equip_flags((this->data.data1[0] > 2) ? 0x0044 : 0x0050),
|
||||||
} else {
|
tech_flag(0x0001),
|
||||||
bank_item.amount = 1;
|
data(src.data) { }
|
||||||
}
|
|
||||||
bank_item.show_flags = 1;
|
|
||||||
|
|
||||||
return bank_item;
|
PlayerBankItem::PlayerBankItem()
|
||||||
}
|
: data(), amount(0), show_flags(0) { }
|
||||||
|
|
||||||
PlayerInventoryItem PlayerBankItem::to_inventory_item() const {
|
PlayerBankItem::PlayerBankItem(const PlayerInventoryItem& src)
|
||||||
PlayerInventoryItem item;
|
: data(src.data),
|
||||||
item.data = this->data;
|
amount(combine_item_to_max.count(this->data.primary_identifier()) ? this->data.data1[5] : 1),
|
||||||
if (item.data.item_data1[0] > 2) {
|
show_flags(1) { }
|
||||||
item.equip_flags = 0x0044;
|
|
||||||
} else {
|
|
||||||
item.equip_flags = 0x0050;
|
|
||||||
}
|
|
||||||
item.equip_flags = 0x0001; // TODO: is this a bug?
|
|
||||||
item.tech_flag = 0x0001;
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::add_item(const PlayerInventoryItem& item) {
|
// TODO: Eliminate duplication between this function and the parallel function
|
||||||
|
// in PlayerBank
|
||||||
|
void SavedPlayerDataBB::add_item(const PlayerInventoryItem& item) {
|
||||||
uint32_t pid = item.data.primary_identifier();
|
uint32_t pid = item.data.primary_identifier();
|
||||||
|
|
||||||
// is it meseta? then just add to the meseta total
|
// Annoyingly, meseta is in the disp data, not in the inventory struct. If the
|
||||||
if (pid == meseta_identifier) {
|
// item is meseta, we have to modify disp instead.
|
||||||
this->disp.meseta += item.data.item_data2d;
|
if (pid == MESETA_IDENTIFIER) {
|
||||||
|
this->disp.meseta += item.data.data2d;
|
||||||
if (this->disp.meseta > 999999) {
|
if (this->disp.meseta > 999999) {
|
||||||
this->disp.meseta = 999999;
|
this->disp.meseta = 999999;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// is it a combine item?
|
// Handle combinable items
|
||||||
try {
|
try {
|
||||||
uint32_t combine_max = combine_item_to_max.at(pid);
|
uint32_t combine_max = combine_item_to_max.at(pid);
|
||||||
|
|
||||||
// is there already a stack of it in the player's inventory?
|
// Get the item index if there's already a stack of the same item in the
|
||||||
|
// player's inventory
|
||||||
size_t y;
|
size_t y;
|
||||||
for (y = 0; y < this->inventory.num_items; y++) {
|
for (y = 0; y < this->inventory.num_items; y++) {
|
||||||
if (this->inventory.items[y].data.primary_identifier() == item.data.primary_identifier()) {
|
if (this->inventory.items[y].data.primary_identifier() == item.data.primary_identifier()) {
|
||||||
@@ -503,17 +589,18 @@ void Player::add_item(const PlayerInventoryItem& item) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's already a stack, add to the stack and return
|
// If we found an existing stack, add it to the total and return
|
||||||
if (y < this->inventory.num_items) {
|
if (y < this->inventory.num_items) {
|
||||||
this->inventory.items[y].data.item_data1[5] += item.data.item_data1[5];
|
this->inventory.items[y].data.data1[5] += item.data.data1[5];
|
||||||
if (this->inventory.items[y].data.item_data1[5] > combine_max) {
|
if (this->inventory.items[y].data.data1[5] > combine_max) {
|
||||||
this->inventory.items[y].data.item_data1[5] = combine_max;
|
this->inventory.items[y].data.data1[5] = combine_max;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (const out_of_range&) { }
|
} catch (const out_of_range&) { }
|
||||||
|
|
||||||
// else, just add the item if there's room
|
// If we get here, then it's not meseta and not a combine item, so it needs to
|
||||||
|
// go into an empty inventory slot
|
||||||
if (this->inventory.num_items >= 30) {
|
if (this->inventory.num_items >= 30) {
|
||||||
throw runtime_error("inventory is full");
|
throw runtime_error("inventory is full");
|
||||||
}
|
}
|
||||||
@@ -521,24 +608,20 @@ void Player::add_item(const PlayerInventoryItem& item) {
|
|||||||
this->inventory.num_items++;
|
this->inventory.num_items++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds an item to a bank
|
|
||||||
void PlayerBank::add_item(const PlayerBankItem& item) {
|
void PlayerBank::add_item(const PlayerBankItem& item) {
|
||||||
uint32_t pid = item.data.primary_identifier();
|
uint32_t pid = item.data.primary_identifier();
|
||||||
|
|
||||||
// is it meseta? then just add to the meseta total
|
if (pid == MESETA_IDENTIFIER) {
|
||||||
if (pid == meseta_identifier) {
|
this->meseta += item.data.data2d;
|
||||||
this->meseta += item.data.item_data2d;
|
|
||||||
if (this->meseta > 999999) {
|
if (this->meseta > 999999) {
|
||||||
this->meseta = 999999;
|
this->meseta = 999999;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// is it a combine item?
|
|
||||||
try {
|
try {
|
||||||
uint32_t combine_max = combine_item_to_max.at(pid);
|
uint32_t combine_max = combine_item_to_max.at(pid);
|
||||||
|
|
||||||
// is there already a stack of it in the player's inventory?
|
|
||||||
size_t y;
|
size_t y;
|
||||||
for (y = 0; y < this->num_items; y++) {
|
for (y = 0; y < this->num_items; y++) {
|
||||||
if (this->items[y].data.primary_identifier() == item.data.primary_identifier()) {
|
if (this->items[y].data.primary_identifier() == item.data.primary_identifier()) {
|
||||||
@@ -546,18 +629,16 @@ void PlayerBank::add_item(const PlayerBankItem& item) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's already a stack, add to the stack and return
|
|
||||||
if (y < this->num_items) {
|
if (y < this->num_items) {
|
||||||
this->items[y].data.item_data1[5] += item.data.item_data1[5];
|
this->items[y].data.data1[5] += item.data.data1[5];
|
||||||
if (this->items[y].data.item_data1[5] > combine_max) {
|
if (this->items[y].data.data1[5] > combine_max) {
|
||||||
this->items[y].data.item_data1[5] = combine_max;
|
this->items[y].data.data1[5] = combine_max;
|
||||||
}
|
}
|
||||||
this->items[y].amount = this->items[y].data.item_data1[5];
|
this->items[y].amount = this->items[y].data.data1[5];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (const out_of_range&) { }
|
} catch (const out_of_range&) { }
|
||||||
|
|
||||||
// else, just add the item if there's room
|
|
||||||
if (this->num_items >= 200) {
|
if (this->num_items >= 200) {
|
||||||
throw runtime_error("bank is full");
|
throw runtime_error("bank is full");
|
||||||
}
|
}
|
||||||
@@ -565,38 +646,45 @@ void PlayerBank::add_item(const PlayerBankItem& item) {
|
|||||||
this->num_items++;
|
this->num_items++;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerInventoryItem Player::remove_item(uint32_t item_id, uint32_t amount) {
|
// TODO: Eliminate code duplication between this function and the parallel
|
||||||
|
// function in PlayerBank
|
||||||
|
PlayerInventoryItem SavedPlayerDataBB::remove_item(
|
||||||
|
uint32_t item_id, uint32_t amount) {
|
||||||
PlayerInventoryItem ret;
|
PlayerInventoryItem ret;
|
||||||
|
|
||||||
// are we removing meseta? then create a meseta item
|
// If we're removing meseta (signaled by an invalid item ID), then create a
|
||||||
|
// meseta item.
|
||||||
if (item_id == 0xFFFFFFFF) {
|
if (item_id == 0xFFFFFFFF) {
|
||||||
if (amount > this->disp.meseta) {
|
if (amount > this->disp.meseta) {
|
||||||
throw out_of_range("player does not have enough meseta");
|
throw out_of_range("player does not have enough meseta");
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&ret, 0, sizeof(ret));
|
memset(&ret, 0, sizeof(ret));
|
||||||
ret.data.item_data1[0] = 0x04;
|
ret.data.data1[0] = 0x04;
|
||||||
ret.data.item_data2d = amount;
|
ret.data.data2d = amount;
|
||||||
this->disp.meseta -= amount;
|
this->disp.meseta -= amount;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find this item
|
|
||||||
size_t index = this->inventory.find_item(item_id);
|
size_t index = this->inventory.find_item(item_id);
|
||||||
auto& inventory_item = this->inventory.items[index];
|
auto& inventory_item = this->inventory.items[index];
|
||||||
|
|
||||||
// is it a combine item, and are we removing less than we have of it?
|
// If the item is a combine item and are we removing less than we have of it,
|
||||||
// (amount == 0 means remove all of it)
|
// then create a new item and reduce the amount of the existing stack. Note
|
||||||
if (amount && (amount < inventory_item.data.item_data1[5]) &&
|
// that passing amount == 0 means to remove the entire stack, so this only
|
||||||
|
// applies if amount is nonzero.
|
||||||
|
if (amount && (amount < inventory_item.data.data1[5]) &&
|
||||||
combine_item_to_max.count(inventory_item.data.primary_identifier())) {
|
combine_item_to_max.count(inventory_item.data.primary_identifier())) {
|
||||||
ret = inventory_item;
|
ret = inventory_item;
|
||||||
ret.data.item_data1[5] = amount;
|
ret.data.data1[5] = amount;
|
||||||
ret.data.item_id = 0xFFFFFFFF;
|
ret.data.id = 0xFFFFFFFF;
|
||||||
inventory_item.data.item_data1[5] -= amount;
|
inventory_item.data.data1[5] -= amount;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not a combine item, or we're removing the whole stack? then just remove the item
|
// If we get here, then it's not meseta, and either it's not a combine item or
|
||||||
|
// we're removing the entire stack. Delete the item from the inventory slot
|
||||||
|
// and return the deleted item.
|
||||||
ret = inventory_item;
|
ret = inventory_item;
|
||||||
this->inventory.num_items--;
|
this->inventory.num_items--;
|
||||||
memcpy(&this->inventory.items[index], &this->inventory.items[index + 1],
|
memcpy(&this->inventory.items[index], &this->inventory.items[index + 1],
|
||||||
@@ -604,39 +692,33 @@ PlayerInventoryItem Player::remove_item(uint32_t item_id, uint32_t amount) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes an item from a bank. works just like RemoveItem for inventories; I won't comment it
|
|
||||||
PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) {
|
PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) {
|
||||||
PlayerBankItem ret;
|
PlayerBankItem ret;
|
||||||
|
|
||||||
// are we removing meseta? then create a meseta item
|
|
||||||
if (item_id == 0xFFFFFFFF) {
|
if (item_id == 0xFFFFFFFF) {
|
||||||
if (amount > this->meseta) {
|
if (amount > this->meseta) {
|
||||||
throw out_of_range("player does not have enough meseta");
|
throw out_of_range("player does not have enough meseta");
|
||||||
}
|
}
|
||||||
memset(&ret, 0, sizeof(ret));
|
memset(&ret, 0, sizeof(ret));
|
||||||
ret.data.item_data1[0] = 0x04;
|
ret.data.data1[0] = 0x04;
|
||||||
ret.data.item_data2d = amount;
|
ret.data.data2d = amount;
|
||||||
this->meseta -= amount;
|
this->meseta -= amount;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find this item
|
|
||||||
size_t index = this->find_item(item_id);
|
size_t index = this->find_item(item_id);
|
||||||
auto& bank_item = this->items[index];
|
auto& bank_item = this->items[index];
|
||||||
|
|
||||||
// is it a combine item, and are we removing less than we have of it?
|
if (amount && (amount < bank_item.data.data1[5]) &&
|
||||||
// (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())) {
|
combine_item_to_max.count(bank_item.data.primary_identifier())) {
|
||||||
ret = bank_item;
|
ret = bank_item;
|
||||||
ret.data.item_data1[5] = amount;
|
ret.data.data1[5] = amount;
|
||||||
ret.amount = amount;
|
ret.amount = amount;
|
||||||
bank_item.data.item_data1[5] -= amount;
|
bank_item.data.data1[5] -= amount;
|
||||||
bank_item.amount -= amount;
|
bank_item.amount -= amount;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not a combine item, or we're removing the whole stack? then just remove the item
|
|
||||||
ret = bank_item;
|
ret = bank_item;
|
||||||
this->num_items--;
|
this->num_items--;
|
||||||
memcpy(&this->items[index], &this->items[index + 1],
|
memcpy(&this->items[index], &this->items[index + 1],
|
||||||
@@ -646,7 +728,7 @@ PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) {
|
|||||||
|
|
||||||
size_t PlayerInventory::find_item(uint32_t item_id) {
|
size_t PlayerInventory::find_item(uint32_t item_id) {
|
||||||
for (size_t x = 0; x < this->num_items; x++) {
|
for (size_t x = 0; x < this->num_items; x++) {
|
||||||
if (this->items[x].data.item_id == item_id) {
|
if (this->items[x].data.id == item_id) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,38 +737,20 @@ size_t PlayerInventory::find_item(uint32_t item_id) {
|
|||||||
|
|
||||||
size_t PlayerBank::find_item(uint32_t item_id) {
|
size_t PlayerBank::find_item(uint32_t item_id) {
|
||||||
for (size_t x = 0; x < this->num_items; x++) {
|
for (size_t x = 0; x < this->num_items; x++) {
|
||||||
if (this->items[x].data.item_id == item_id) {
|
if (this->items[x].data.id == item_id) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw out_of_range("item not present");
|
throw out_of_range("item not present");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::print_inventory(FILE* stream) const {
|
void SavedPlayerDataBB::print_inventory(FILE* stream) const {
|
||||||
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.meseta.load());
|
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.meseta.load());
|
||||||
fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items);
|
fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items);
|
||||||
for (size_t x = 0; x < this->inventory.num_items; x++) {
|
for (size_t x = 0; x < this->inventory.num_items; x++) {
|
||||||
const auto& item = this->inventory.items[x];
|
const auto& item = this->inventory.items[x];
|
||||||
auto name = name_for_item(item.data, false);
|
auto name = name_for_item(item.data, false);
|
||||||
fprintf(stream, "[PlayerInventory] %zu (%08" PRIX32 "): %08" PRIX32 " (%s)\n",
|
fprintf(stream, "[PlayerInventory] %zu (%08" PRIX32 "): %06" PRIX32 " (%s)\n",
|
||||||
x, item.data.item_id.load(), item.data.primary_identifier(), name.c_str());
|
x, item.data.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));
|
|
||||||
}
|
|
||||||
|
|
||||||
string filename_for_bank_bb(const string& username, const std::string& bank_name) {
|
|
||||||
return string_printf("system/players/bank_%s_%s.nsb", username.c_str(),
|
|
||||||
bank_name.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
string filename_for_class_template_bb(uint8_t char_class) {
|
|
||||||
return string_printf("system/blueburst/player_class_%hhu.nsc", char_class);
|
|
||||||
}
|
|
||||||
|
|
||||||
string filename_for_account_bb(const string& username) {
|
|
||||||
return string_printf("system/players/account_%s.nsa", username.c_str());
|
|
||||||
}
|
|
||||||
|
|||||||
+65
-78
@@ -7,51 +7,51 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <phosg/Encoding.hh>
|
#include <phosg/Encoding.hh>
|
||||||
|
|
||||||
|
#include "LevelTable.hh"
|
||||||
#include "Version.hh"
|
#include "Version.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// raw item data
|
|
||||||
// TODO: use parray for the fields here
|
|
||||||
struct ItemData {
|
struct ItemData {
|
||||||
union {
|
union {
|
||||||
uint8_t item_data1[12];
|
uint8_t data1[12];
|
||||||
le_uint16_t item_data1w[6];
|
le_uint16_t data1w[6];
|
||||||
le_uint32_t item_data1d[3];
|
le_uint32_t data1d[3];
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
le_uint32_t item_id;
|
le_uint32_t id;
|
||||||
union {
|
union {
|
||||||
uint8_t item_data2[4];
|
uint8_t data2[4];
|
||||||
le_uint16_t item_data2w[2];
|
le_uint16_t data2w[2];
|
||||||
le_uint32_t item_data2d;
|
le_uint32_t data2d;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
ItemData();
|
||||||
|
|
||||||
uint32_t primary_identifier() const;
|
uint32_t primary_identifier() const;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
struct PlayerBankItem;
|
struct PlayerBankItem;
|
||||||
|
|
||||||
// an item in a player's inventory
|
|
||||||
struct PlayerInventoryItem {
|
struct PlayerInventoryItem {
|
||||||
le_uint16_t equip_flags;
|
le_uint16_t equip_flags;
|
||||||
le_uint16_t tech_flag;
|
le_uint16_t tech_flag;
|
||||||
le_uint32_t game_flags;
|
le_uint32_t game_flags;
|
||||||
ItemData data;
|
ItemData data;
|
||||||
|
|
||||||
PlayerBankItem to_bank_item() const;
|
PlayerInventoryItem();
|
||||||
|
PlayerInventoryItem(const PlayerBankItem&);
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
// an item in a player's bank
|
|
||||||
struct PlayerBankItem {
|
struct PlayerBankItem {
|
||||||
ItemData data;
|
ItemData data;
|
||||||
le_uint16_t amount;
|
le_uint16_t amount;
|
||||||
le_uint16_t show_flags;
|
le_uint16_t show_flags;
|
||||||
|
|
||||||
PlayerInventoryItem to_inventory_item() const;
|
PlayerBankItem();
|
||||||
|
PlayerBankItem(const PlayerInventoryItem&);
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
// a player's inventory (remarkably, the format is the same in all versions of PSO)
|
|
||||||
struct PlayerInventory {
|
struct PlayerInventory {
|
||||||
uint8_t num_items;
|
uint8_t num_items;
|
||||||
uint8_t hp_materials_used;
|
uint8_t hp_materials_used;
|
||||||
@@ -62,7 +62,6 @@ struct PlayerInventory {
|
|||||||
size_t find_item(uint32_t item_id);
|
size_t find_item(uint32_t item_id);
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
// a player's bank
|
|
||||||
struct PlayerBank {
|
struct PlayerBank {
|
||||||
le_uint32_t num_items;
|
le_uint32_t num_items;
|
||||||
le_uint32_t meseta;
|
le_uint32_t meseta;
|
||||||
@@ -81,19 +80,6 @@ struct PlayerBank {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// simple player stats
|
|
||||||
struct PlayerStats {
|
|
||||||
le_uint16_t atp;
|
|
||||||
le_uint16_t mst;
|
|
||||||
le_uint16_t evp;
|
|
||||||
le_uint16_t hp;
|
|
||||||
le_uint16_t dfp;
|
|
||||||
le_uint16_t ata;
|
|
||||||
le_uint16_t lck;
|
|
||||||
|
|
||||||
PlayerStats() noexcept;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
struct PlayerDispDataBB;
|
struct PlayerDispDataBB;
|
||||||
|
|
||||||
// PC/GC player appearance and stats data
|
// PC/GC player appearance and stats data
|
||||||
@@ -310,12 +296,12 @@ struct PlayerLobbyDataBB {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct PSOPlayerDataPC { // for command 0x61
|
struct PSOPlayerDataPC { // For command 61
|
||||||
PlayerInventory inventory;
|
PlayerInventory inventory;
|
||||||
PlayerDispDataPCGC disp;
|
PlayerDispDataPCGC disp;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
struct PSOPlayerDataGC { // for command 0x61
|
struct PSOPlayerDataGC { // For command 61
|
||||||
PlayerInventory inventory;
|
PlayerInventory inventory;
|
||||||
PlayerDispDataPCGC disp;
|
PlayerDispDataPCGC disp;
|
||||||
parray<uint8_t, 0x134> unknown;
|
parray<uint8_t, 0x134> unknown;
|
||||||
@@ -325,7 +311,7 @@ struct PSOPlayerDataGC { // for command 0x61
|
|||||||
char auto_reply[0];
|
char auto_reply[0];
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
struct PSOPlayerDataBB { // for command 0x61
|
struct PSOPlayerDataBB { // For command 61
|
||||||
PlayerInventory inventory;
|
PlayerInventory inventory;
|
||||||
PlayerDispDataBB disp;
|
PlayerDispDataBB disp;
|
||||||
ptext<char, 0x174> unused;
|
ptext<char, 0x174> unused;
|
||||||
@@ -335,8 +321,7 @@ struct PSOPlayerDataBB { // for command 0x61
|
|||||||
char16_t auto_reply[0];
|
char16_t auto_reply[0];
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
// complete BB player data format (used in E7 command)
|
struct PlayerBB { // Used in 00E7 command
|
||||||
struct PlayerBB {
|
|
||||||
PlayerInventory inventory; // 0000 // player
|
PlayerInventory inventory; // 0000 // player
|
||||||
PlayerDispDataBB disp; // 034C // player
|
PlayerDispDataBB disp; // 034C // player
|
||||||
parray<uint8_t, 0x0010> unknown; // 04DC //
|
parray<uint8_t, 0x0010> unknown; // 04DC //
|
||||||
@@ -366,22 +351,27 @@ struct PlayerBB {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct SavedPlayerBB { // .nsc file format
|
struct SavedPlayerDataBB { // .nsc file format
|
||||||
ptext<char, 0x40> signature;
|
ptext<char, 0x40> signature;
|
||||||
PlayerDispDataBBPreview preview;
|
PlayerDispDataBBPreview preview;
|
||||||
ptext<char16_t, 0x00AC> auto_reply;
|
ptext<char16_t, 0x00AC> auto_reply;
|
||||||
PlayerBank bank;
|
PlayerBank bank;
|
||||||
parray<uint8_t, 0x0140> challenge_data;
|
parray<uint8_t, 0x0140> challenge_data;
|
||||||
PlayerDispDataBB disp;
|
PlayerDispDataBB disp;
|
||||||
ptext<char16_t, 0x58> guild_card_desc;
|
ptext<char16_t, 0x0058> guild_card_desc;
|
||||||
ptext<char16_t, 0x00AC> info_board;
|
ptext<char16_t, 0x00AC> info_board;
|
||||||
PlayerInventory inventory;
|
PlayerInventory inventory;
|
||||||
parray<uint8_t, 0x0208> quest_data1;
|
parray<uint8_t, 0x0208> quest_data1;
|
||||||
parray<uint8_t, 0x0058> quest_data2;
|
parray<uint8_t, 0x0058> quest_data2;
|
||||||
parray<uint8_t, 0x0028> tech_menu_config;
|
parray<uint8_t, 0x0028> tech_menu_config;
|
||||||
|
|
||||||
|
void add_item(const PlayerInventoryItem& item);
|
||||||
|
PlayerInventoryItem remove_item(uint32_t item_id, uint32_t amount);
|
||||||
|
|
||||||
|
void print_inventory(FILE* stream) const;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
struct SavedAccountBB { // .nsa file format
|
struct SavedAccountDataBB { // .nsa file format
|
||||||
ptext<char, 0x40> signature;
|
ptext<char, 0x40> signature;
|
||||||
parray<le_uint32_t, 0x001E> blocked_senders;
|
parray<le_uint32_t, 0x001E> blocked_senders;
|
||||||
GuildCardFileBB guild_cards;
|
GuildCardFileBB guild_cards;
|
||||||
@@ -392,59 +382,56 @@ struct SavedAccountBB { // .nsa file format
|
|||||||
ptext<char16_t, 0x0010> team_name;
|
ptext<char16_t, 0x0010> team_name;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
// complete player info stored by the server
|
class ClientGameData {
|
||||||
struct Player {
|
private:
|
||||||
le_uint32_t loaded_from_shipgate_time;
|
std::shared_ptr<SavedAccountDataBB> account_data;
|
||||||
ptext<char16_t, 0x00AC> auto_reply; // player
|
std::shared_ptr<SavedPlayerDataBB> player_data;
|
||||||
PlayerBank bank; // player
|
|
||||||
ptext<char, 0x0020> bank_name; // not saved
|
|
||||||
parray<le_uint32_t, 0x001E> blocked_senders; // account
|
|
||||||
parray<uint8_t, 0x0140> challenge_data; // player
|
|
||||||
PlayerDispDataBB disp; // player
|
|
||||||
parray<uint8_t, 0x2408> ep3_config; // not saved
|
|
||||||
ptext<char16_t, 0x0058> guild_card_desc; // player
|
|
||||||
GuildCardFileBB guild_cards; // account
|
|
||||||
PlayerInventoryItem identify_result; // not saved
|
|
||||||
ptext<char16_t, 0x00AC> info_board; // player
|
|
||||||
PlayerInventory inventory; // player
|
|
||||||
KeyAndTeamConfigBB key_config; // account
|
|
||||||
le_uint32_t option_flags; // account
|
|
||||||
parray<uint8_t, 0x0208> quest_data1; // player
|
|
||||||
parray<uint8_t, 0x0058> quest_data2; // player
|
|
||||||
le_uint32_t serial_number; // account identifier
|
|
||||||
std::vector<ItemData> current_shop_contents; // not saved
|
|
||||||
parray<uint8_t, 0x0A40> shortcuts; // account
|
|
||||||
parray<uint8_t, 0x04E0> symbol_chats; // account
|
|
||||||
ptext<char16_t, 0x0010> team_name; // account
|
|
||||||
parray<uint8_t, 0x0028> tech_menu_config; // player
|
|
||||||
|
|
||||||
void load_player_data(const std::string& filename);
|
public:
|
||||||
void save_player_data(const std::string& filename) const;
|
uint32_t serial_number;
|
||||||
|
|
||||||
void load_account_data(const std::string& filename);
|
// The following fields are not saved, and are only used in certain situations
|
||||||
void save_account_data(const std::string& filename) const;
|
|
||||||
|
|
||||||
void import(const PSOPlayerDataPC& pd);
|
// Null unless the client is Episode 3 and has sent its config already
|
||||||
void import(const PSOPlayerDataGC& pd);
|
std::shared_ptr<parray<uint8_t, 0x2408>> ep3_config;
|
||||||
void import(const PSOPlayerDataBB& pd);
|
|
||||||
PlayerBB export_bb_player_data() const;
|
|
||||||
|
|
||||||
void add_item(const PlayerInventoryItem& item);
|
// These are only used if the client is BB
|
||||||
PlayerInventoryItem remove_item(uint32_t item_id, uint32_t amount);
|
std::string bb_username;
|
||||||
size_t find_item(uint32_t item_id);
|
size_t bb_player_index;
|
||||||
|
PlayerInventoryItem identify_result;
|
||||||
|
std::vector<ItemData> shop_contents;
|
||||||
|
|
||||||
void print_inventory(FILE* stream) const;
|
ClientGameData() : serial_number(0), bb_player_index(0) { }
|
||||||
|
~ClientGameData();
|
||||||
|
|
||||||
|
std::shared_ptr<SavedAccountDataBB> account(bool should_load = true);
|
||||||
|
std::shared_ptr<SavedPlayerDataBB> player(bool should_load = true);
|
||||||
|
std::shared_ptr<const SavedAccountDataBB> account() const;
|
||||||
|
std::shared_ptr<const SavedPlayerDataBB> player() const;
|
||||||
|
|
||||||
|
std::string account_data_filename() const;
|
||||||
|
std::string player_data_filename() const;
|
||||||
|
static std::string player_template_filename(uint8_t char_class);
|
||||||
|
|
||||||
|
void create_player(
|
||||||
|
const PlayerDispDataBBPreview& preview,
|
||||||
|
std::shared_ptr<const LevelTable> level_table);
|
||||||
|
|
||||||
|
void load_account_data();
|
||||||
|
void save_account_data() const;
|
||||||
|
void load_player_data();
|
||||||
|
void save_player_data() const;
|
||||||
|
|
||||||
|
void import_player(const PSOPlayerDataPC& pd);
|
||||||
|
void import_player(const PSOPlayerDataGC& pd);
|
||||||
|
void import_player(const PSOPlayerDataBB& pd);
|
||||||
|
PlayerBB export_player_bb() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uint32_t compute_guild_card_checksum(const void* data, size_t size);
|
uint32_t compute_guild_card_checksum(const void* data, size_t size);
|
||||||
|
|
||||||
std::string filename_for_player_bb(const std::string& username, uint8_t player_index);
|
|
||||||
std::string filename_for_bank_bb(const std::string& username, const std::string& bank_name);
|
|
||||||
std::string filename_for_class_template_bb(uint8_t char_class);
|
|
||||||
std::string filename_for_account_bb(const std::string& username);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
template <typename DestT, typename SrcT = DestT>
|
template <typename DestT, typename SrcT = DestT>
|
||||||
|
|||||||
+115
-158
@@ -135,37 +135,8 @@ void process_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
|||||||
|
|
||||||
} else if (c->server_behavior == ServerBehavior::LOBBY_SERVER) {
|
} else if (c->server_behavior == ServerBehavior::LOBBY_SERVER) {
|
||||||
|
|
||||||
// if the client is BB, load thair player and account data
|
|
||||||
if (c->version == GameVersion::BB) {
|
if (c->version == GameVersion::BB) {
|
||||||
string account_filename = filename_for_account_bb(c->license->username);
|
// This implicitly loads the client's account and player data
|
||||||
try {
|
|
||||||
c->player.load_account_data(account_filename);
|
|
||||||
} catch (const exception& e) {
|
|
||||||
c->player.load_account_data("system/blueburst/default.nsa");
|
|
||||||
}
|
|
||||||
|
|
||||||
c->player.bank_name = string_printf("player%d", c->bb_player_index + 1);
|
|
||||||
|
|
||||||
string player_filename = filename_for_player_bb(c->license->username,
|
|
||||||
c->bb_player_index);
|
|
||||||
try {
|
|
||||||
c->player.load_player_data(player_filename);
|
|
||||||
} catch (const exception&) {
|
|
||||||
send_message_box(c, u"$C6Your player data cannot be found.");
|
|
||||||
c->should_disconnect = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string bank_filename = filename_for_bank_bb(c->license->username,
|
|
||||||
c->player.bank_name);
|
|
||||||
try {
|
|
||||||
c->player.bank.load(bank_filename);
|
|
||||||
} catch (const exception&) {
|
|
||||||
send_message_box(c, u"$C6Your bank data cannot be found.");
|
|
||||||
c->should_disconnect = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
send_complete_player_bb(c);
|
send_complete_player_bb(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,20 +153,13 @@ void process_disconnect(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
|||||||
s->remove_client_from_lobby(c);
|
s->remove_client_from_lobby(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->version == GameVersion::BB) {
|
|
||||||
// TODO: Make a timer event for each connected player that saves their data
|
// TODO: Make a timer event for each connected player that saves their data
|
||||||
// periodically, not only when they disconnect
|
// periodically, not only when they disconnect
|
||||||
// TODO: Track play time somewhere
|
// TODO: Track play time somewhere for BB players
|
||||||
// c->player.disp.play_time += ((now() - c->play_time_begin) / 1000000);
|
// c->game_data.player()->disp.play_time += ((now() - c->play_time_begin) / 1000000);
|
||||||
string account_filename = filename_for_account_bb(c->license->username);
|
|
||||||
string player_filename = filename_for_player_bb(c->license->username,
|
// Note: The client's GameData destructor should save their player data
|
||||||
c->bb_player_index);
|
// shortly after this point
|
||||||
string bank_filename = filename_for_bank_bb(c->license->username,
|
|
||||||
c->player.bank_name);
|
|
||||||
c->player.save_account_data(account_filename);
|
|
||||||
c->player.save_player_data(player_filename);
|
|
||||||
c->player.bank.save(bank_filename);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -208,8 +172,9 @@ void process_verify_license_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
|
|
||||||
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
||||||
try {
|
try {
|
||||||
c->license = s->license_manager->verify_gc(serial_number, cmd.access_key,
|
auto l = s->license_manager->verify_gc(serial_number, cmd.access_key,
|
||||||
cmd.password);
|
cmd.password);
|
||||||
|
c->set_license(l);
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
if (!s->allow_unregistered_users) {
|
if (!s->allow_unregistered_users) {
|
||||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||||
@@ -220,7 +185,7 @@ void process_verify_license_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
auto l = LicenseManager::create_license_gc(serial_number, cmd.access_key,
|
auto l = LicenseManager::create_license_gc(serial_number, cmd.access_key,
|
||||||
cmd.password, true);
|
cmd.password, true);
|
||||||
s->license_manager->add(l);
|
s->license_manager->add(l);
|
||||||
c->license = l;
|
c->set_license(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,11 +201,14 @@ void process_login_a_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
|
|
||||||
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
||||||
try {
|
try {
|
||||||
|
shared_ptr<const License> l;
|
||||||
if (c->version == GameVersion::GC) {
|
if (c->version == GameVersion::GC) {
|
||||||
c->license = s->license_manager->verify_gc(serial_number, cmd.access_key);
|
l = s->license_manager->verify_gc(serial_number, cmd.access_key);
|
||||||
} else {
|
} else {
|
||||||
c->license = s->license_manager->verify_pc(serial_number, cmd.access_key);
|
l = s->license_manager->verify_pc(serial_number, cmd.access_key);
|
||||||
}
|
}
|
||||||
|
c->set_license(l);
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
// On GC, the client should have sent a different command containing the
|
// On GC, the client should have sent a different command containing the
|
||||||
// password already, which should have created and added a temporary
|
// password already, which should have created and added a temporary
|
||||||
@@ -263,12 +231,15 @@ void process_login_c_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
|
|
||||||
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
||||||
try {
|
try {
|
||||||
|
shared_ptr<const License> l;
|
||||||
if (c->version == GameVersion::GC) {
|
if (c->version == GameVersion::GC) {
|
||||||
c->license = s->license_manager->verify_gc(serial_number, cmd.access_key,
|
l = s->license_manager->verify_gc(serial_number, cmd.access_key,
|
||||||
cmd.password);
|
cmd.password);
|
||||||
} else {
|
} else {
|
||||||
c->license = s->license_manager->verify_pc(serial_number, cmd.access_key);
|
l = s->license_manager->verify_pc(serial_number, cmd.access_key);
|
||||||
}
|
}
|
||||||
|
c->set_license(l);
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
if (!s->allow_unregistered_users) {
|
if (!s->allow_unregistered_users) {
|
||||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||||
@@ -285,7 +256,7 @@ void process_login_c_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
s->license_manager->add(l);
|
s->license_manager->add(l);
|
||||||
c->license = l;
|
c->set_license(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,13 +297,16 @@ void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
|
|
||||||
uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16);
|
uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16);
|
||||||
try {
|
try {
|
||||||
|
shared_ptr<const License> l;
|
||||||
if (c->version == GameVersion::GC) {
|
if (c->version == GameVersion::GC) {
|
||||||
c->license = s->license_manager->verify_gc(serial_number,
|
l = s->license_manager->verify_gc(serial_number,
|
||||||
base_cmd->access_key);
|
base_cmd->access_key);
|
||||||
} else {
|
} else {
|
||||||
c->license = s->license_manager->verify_pc(serial_number,
|
l = s->license_manager->verify_pc(serial_number,
|
||||||
base_cmd->access_key);
|
base_cmd->access_key);
|
||||||
}
|
}
|
||||||
|
c->set_license(l);
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
// See comment in 9A handler about why we do this even if unregistered users
|
// See comment in 9A handler about why we do this even if unregistered users
|
||||||
// are allowed on the server
|
// are allowed on the server
|
||||||
@@ -358,7 +332,8 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
c->flags |= flags_for_version(c->version, 0);
|
c->flags |= flags_for_version(c->version, 0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
c->license = s->license_manager->verify_bb(cmd.username, cmd.password);
|
auto l = s->license_manager->verify_bb(cmd.username, cmd.password);
|
||||||
|
c->set_license(l);
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||||
send_message_box(c, message.c_str());
|
send_message_box(c, message.c_str());
|
||||||
@@ -378,29 +353,19 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
|
|
||||||
switch (c->bb_game_state) {
|
switch (c->bb_game_state) {
|
||||||
case ClientStateBB::INITIAL_LOGIN:
|
case ClientStateBB::INITIAL_LOGIN:
|
||||||
// first login? send them to the other port
|
// On first login, send the client to the data server port
|
||||||
send_reconnect(c, s->connect_address_for_client(c),
|
send_reconnect(c, s->connect_address_for_client(c),
|
||||||
s->name_to_port_config.at("bb-data1")->port);
|
s->name_to_port_config.at("bb-data1")->port);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ClientStateBB::DOWNLOAD_DATA: {
|
case ClientStateBB::DOWNLOAD_DATA:
|
||||||
// download data? send them their account data and player previews
|
|
||||||
string account_filename = filename_for_account_bb(c->license->username);
|
|
||||||
try {
|
|
||||||
c->player.load_account_data(account_filename);
|
|
||||||
} catch (const exception& e) {
|
|
||||||
c->player.load_account_data("system/blueburst/default.nsa");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ClientStateBB::CHOOSE_PLAYER:
|
case ClientStateBB::CHOOSE_PLAYER:
|
||||||
case ClientStateBB::SAVE_PLAYER:
|
case ClientStateBB::SAVE_PLAYER:
|
||||||
// just wait; the command handlers will handle it
|
// Just wait in these cases; the client will request something from us and
|
||||||
|
// the command handlers will take care of it
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ClientStateBB::SHIP_SELECT:
|
case ClientStateBB::SHIP_SELECT:
|
||||||
// this happens on the login server and later
|
|
||||||
process_login_complete(s, c);
|
process_login_complete(s, c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -805,11 +770,11 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
send_message_box(c, u"$C6Incorrect password.");
|
send_message_box(c, u"$C6Incorrect password.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (c->player.disp.level < game->min_level) {
|
if (c->game_data.player()->disp.level < game->min_level) {
|
||||||
send_message_box(c, u"$C6Your level is too\nlow to join this\ngame.");
|
send_message_box(c, u"$C6Your level is too\nlow to join this\ngame.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (c->player.disp.level > game->max_level) {
|
if (c->game_data.player()->disp.level > game->max_level) {
|
||||||
send_message_box(c, u"$C6Your level is too\nhigh to join this\ngame.");
|
send_message_box(c, u"$C6Your level is too\nhigh to join this\ngame.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1087,51 +1052,51 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
// Note: we add extra buffer on the end when checking sizes because the
|
// Note: we add extra buffer on the end when checking sizes because the
|
||||||
// autoreply text is a variable length
|
// autoreply text is a variable length
|
||||||
switch (c->version) {
|
switch (c->version) {
|
||||||
case GameVersion::PC:
|
case GameVersion::PC: {
|
||||||
check_size_v(data.size(), sizeof(PSOPlayerDataPC), 0xFFFF);
|
const auto& disp = check_size_t<PSOPlayerDataPC>(data,
|
||||||
c->player.import(*reinterpret_cast<const PSOPlayerDataPC*>(data.data()));
|
sizeof(PSOPlayerDataPC), 0xFFFF);
|
||||||
|
c->game_data.import_player(disp);
|
||||||
break;
|
break;
|
||||||
case GameVersion::GC:
|
}
|
||||||
|
case GameVersion::GC: {
|
||||||
|
const PSOPlayerDataGC* disp;
|
||||||
if (flag == 4) { // Episode 3
|
if (flag == 4) { // Episode 3
|
||||||
check_size_v(data.size(), sizeof(PSOPlayerDataGC) + 0x23FC);
|
disp = &check_size_t<PSOPlayerDataGC>(data,
|
||||||
|
sizeof(PSOPlayerDataGC) + 0x23FC);
|
||||||
// TODO: import Episode 3 data somewhere
|
// TODO: import Episode 3 data somewhere
|
||||||
} else {
|
} else {
|
||||||
check_size_v(data.size(), sizeof(PSOPlayerDataGC),
|
disp = &check_size_t<PSOPlayerDataGC>(data, sizeof(PSOPlayerDataGC),
|
||||||
sizeof(PSOPlayerDataGC) + sizeof(char) * c->player.auto_reply.size());
|
sizeof(PSOPlayerDataGC) + c->game_data.player()->auto_reply.bytes());
|
||||||
}
|
}
|
||||||
c->player.import(*reinterpret_cast<const PSOPlayerDataGC*>(data.data()));
|
c->game_data.import_player(*disp);
|
||||||
break;
|
break;
|
||||||
case GameVersion::BB:
|
}
|
||||||
check_size_v(data.size(), sizeof(PSOPlayerDataBB),
|
case GameVersion::BB: {
|
||||||
sizeof(PSOPlayerDataBB) + sizeof(char16_t) * c->player.auto_reply.size());
|
const auto& disp = check_size_t<PSOPlayerDataBB>(data, sizeof(PSOPlayerDataBB),
|
||||||
c->player.import(*reinterpret_cast<const PSOPlayerDataBB*>(data.data()));
|
sizeof(PSOPlayerDataBB) + c->game_data.player()->auto_reply.bytes());
|
||||||
|
c->game_data.import_player(disp);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw logic_error("player data command not implemented for version");
|
throw logic_error("player data command not implemented for version");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command == 0x61 && !c->pending_bb_save_username.empty()) {
|
if (command == 0x61 && !c->pending_bb_save_username.empty()) {
|
||||||
|
string prev_bb_username = c->game_data.bb_username;
|
||||||
|
size_t prev_bb_player_index = c->game_data.bb_player_index;
|
||||||
|
|
||||||
|
c->game_data.bb_username = c->pending_bb_save_username;
|
||||||
|
c->game_data.bb_player_index = c->pending_bb_save_player_index;
|
||||||
|
|
||||||
bool failure = false;
|
bool failure = false;
|
||||||
try {
|
try {
|
||||||
string filename = filename_for_player_bb(c->pending_bb_save_username,
|
c->game_data.save_player_data();
|
||||||
c->pending_bb_save_player_index + 1);
|
|
||||||
c->player.save_player_data(filename);
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
u16string buffer = u"$C6PSOBB player data could\nnot be saved:\n" + decode_sjis(e.what());
|
u16string buffer = u"$C6PSOBB player data could\nnot be saved:\n" + decode_sjis(e.what());
|
||||||
send_text_message(c, buffer.c_str());
|
send_text_message(c, buffer.c_str());
|
||||||
failure = true;
|
failure = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
string filename = string_printf("system/players/player_%s_player%d.nsb",
|
|
||||||
c->pending_bb_save_username.c_str(), c->pending_bb_save_player_index + 1);
|
|
||||||
c->player.bank.save(filename);
|
|
||||||
} catch (const exception& e) {
|
|
||||||
u16string buffer = u"$C6PSOBB bank data could\nnot be saved:\n" + decode_sjis(e.what());
|
|
||||||
send_text_message(c, buffer.c_str());
|
|
||||||
failure = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!failure) {
|
if (!failure) {
|
||||||
send_text_message_printf(c,
|
send_text_message_printf(c,
|
||||||
"$C6PSOBB player data saved\nas player %hhu for user\n%s",
|
"$C6PSOBB player data saved\nas player %hhu for user\n%s",
|
||||||
@@ -1139,6 +1104,9 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
c->pending_bb_save_username.c_str());
|
c->pending_bb_save_username.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c->game_data.bb_username = prev_bb_username;
|
||||||
|
c->game_data.bb_player_index = prev_bb_player_index;
|
||||||
|
|
||||||
c->pending_bb_save_username.clear();
|
c->pending_bb_save_username.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1198,7 +1166,7 @@ void process_chat_generic(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
send_chat_message(l->clients[x], c->license->serial_number,
|
send_chat_message(l->clients[x], c->license->serial_number,
|
||||||
c->player.disp.name.data(), processed_text.c_str());
|
c->game_data.player()->disp.name.data(), processed_text.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1241,18 +1209,18 @@ void process_player_preview_request_bb(shared_ptr<ServerState>, shared_ptr<Clien
|
|||||||
c->should_disconnect = true;
|
c->should_disconnect = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
string filename = filename_for_player_bb(c->license->username,
|
|
||||||
c->bb_player_index);
|
ClientGameData temp_gd;
|
||||||
|
temp_gd.serial_number = c->license->serial_number;
|
||||||
|
temp_gd.bb_username = c->license->username;
|
||||||
|
temp_gd.bb_player_index = c->bb_player_index;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// generate a preview
|
auto preview = temp_gd.player()->disp.to_preview();
|
||||||
Player p;
|
|
||||||
p.load_player_data(filename);
|
|
||||||
auto preview = p.disp.to_preview();
|
|
||||||
send_player_preview_bb(c, cmd.player_index, &preview);
|
send_player_preview_bb(c, cmd.player_index, &preview);
|
||||||
|
|
||||||
} catch (const exception&) {
|
} catch (const exception&) {
|
||||||
// player doesn't exist
|
// Player doesn't exist
|
||||||
send_player_preview_bb(c, cmd.player_index, nullptr);
|
send_player_preview_bb(c, cmd.player_index, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1298,45 +1266,18 @@ void process_create_character_bb(shared_ptr<ServerState> s, shared_ptr<Client> c
|
|||||||
send_message_box(c, u"$C6You are not logged in.");
|
send_message_box(c, u"$C6You are not logged in.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!c->player.disp.name.empty()) {
|
if (!c->game_data.player()->disp.name.empty()) {
|
||||||
send_message_box(c, u"$C6You have already loaded a character.");
|
send_message_box(c, u"$C6You have already loaded a character.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->bb_player_index = cmd.player_index;
|
c->bb_player_index = cmd.player_index;
|
||||||
c->player.bank_name = string_printf("player%" PRIu32, cmd.player_index + 1);
|
|
||||||
string player_filename = filename_for_player_bb(c->license->username, cmd.player_index);
|
|
||||||
string bank_filename = filename_for_bank_bb(c->license->username, c->player.bank_name);
|
|
||||||
string template_filename = filename_for_class_template_bb(cmd.preview.char_class);
|
|
||||||
|
|
||||||
Player p;
|
|
||||||
try {
|
|
||||||
p.load_player_data(template_filename);
|
|
||||||
} catch (const exception& e) {
|
|
||||||
send_message_box(c, u"$C6New character could not be created.\n\nA server file is missing.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
p.disp.apply_preview(cmd.preview);
|
c->game_data.create_player(cmd.preview, s->level_table);
|
||||||
c->player.disp.stats = s->level_table->base_stats_for_class(c->player.disp.char_class);
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
send_message_box(c, u"$C6New character could not be created.\n\nTemplate application failed.");
|
string message = string_printf("$C6New character could not be created:\n%s", e.what());
|
||||||
return;
|
send_message_box(c, decode_sjis(message));
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
p.save_player_data(player_filename);
|
|
||||||
} catch (const exception& e) {
|
|
||||||
send_message_box(c, u"$C6New character could not be created.\n\nThe disk is full or write-protected.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
p.bank.save(bank_filename);
|
|
||||||
} catch (const exception& e) {
|
|
||||||
unlink(player_filename);
|
|
||||||
send_message_box(c, u"$C6New bank could not be created.\n\nThe disk is full or write-protected.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1351,31 +1292,31 @@ void process_change_account_data_bb(shared_ptr<ServerState>, shared_ptr<Client>
|
|||||||
switch (command) {
|
switch (command) {
|
||||||
case 0x01ED:
|
case 0x01ED:
|
||||||
check_size_v(data.size(), sizeof(cmd->option));
|
check_size_v(data.size(), sizeof(cmd->option));
|
||||||
c->player.option_flags = cmd->option;
|
c->game_data.account()->option_flags = cmd->option;
|
||||||
break;
|
break;
|
||||||
case 0x02ED:
|
case 0x02ED:
|
||||||
check_size_v(data.size(), sizeof(cmd->symbol_chats));
|
check_size_v(data.size(), sizeof(cmd->symbol_chats));
|
||||||
c->player.symbol_chats = cmd->symbol_chats;
|
c->game_data.account()->symbol_chats = cmd->symbol_chats;
|
||||||
break;
|
break;
|
||||||
case 0x03ED:
|
case 0x03ED:
|
||||||
check_size_v(data.size(), sizeof(cmd->chat_shortcuts));
|
check_size_v(data.size(), sizeof(cmd->chat_shortcuts));
|
||||||
c->player.shortcuts = cmd->chat_shortcuts;
|
c->game_data.account()->shortcuts = cmd->chat_shortcuts;
|
||||||
break;
|
break;
|
||||||
case 0x04ED:
|
case 0x04ED:
|
||||||
check_size_v(data.size(), sizeof(cmd->key_config));
|
check_size_v(data.size(), sizeof(cmd->key_config));
|
||||||
c->player.key_config.key_config = cmd->key_config;
|
c->game_data.account()->key_config.key_config = cmd->key_config;
|
||||||
break;
|
break;
|
||||||
case 0x05ED:
|
case 0x05ED:
|
||||||
check_size_v(data.size(), sizeof(cmd->pad_config));
|
check_size_v(data.size(), sizeof(cmd->pad_config));
|
||||||
c->player.key_config.joystick_config = cmd->pad_config;
|
c->game_data.account()->key_config.joystick_config = cmd->pad_config;
|
||||||
break;
|
break;
|
||||||
case 0x06ED:
|
case 0x06ED:
|
||||||
check_size_v(data.size(), sizeof(cmd->tech_menu));
|
check_size_v(data.size(), sizeof(cmd->tech_menu));
|
||||||
c->player.tech_menu_config = cmd->tech_menu;
|
c->game_data.player()->tech_menu_config = cmd->tech_menu;
|
||||||
break;
|
break;
|
||||||
case 0x07ED:
|
case 0x07ED:
|
||||||
check_size_v(data.size(), sizeof(cmd->customize));
|
check_size_v(data.size(), sizeof(cmd->customize));
|
||||||
c->player.disp.config = cmd->customize;
|
c->game_data.player()->disp.config = cmd->customize;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw invalid_argument("unknown account command");
|
throw invalid_argument("unknown account command");
|
||||||
@@ -1387,9 +1328,18 @@ void process_return_player_data_bb(shared_ptr<ServerState>, shared_ptr<Client> c
|
|||||||
const auto& cmd = check_size_t<PlayerBB>(data);
|
const auto& cmd = check_size_t<PlayerBB>(data);
|
||||||
|
|
||||||
// We only trust the player's quest data and challenge data.
|
// We only trust the player's quest data and challenge data.
|
||||||
c->player.challenge_data = cmd.challenge_data;
|
c->game_data.player()->challenge_data = cmd.challenge_data;
|
||||||
c->player.quest_data1 = cmd.quest_data1;
|
c->game_data.player()->quest_data1 = cmd.quest_data1;
|
||||||
c->player.quest_data2 = cmd.quest_data2;
|
c->game_data.player()->quest_data2 = cmd.quest_data2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_update_key_config_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||||
|
uint16_t, uint32_t, const string& data) {
|
||||||
|
// Some clients have only a uint32_t at the end for team rewards
|
||||||
|
check_size_t<KeyAndTeamConfigBB>(data,
|
||||||
|
sizeof(KeyAndTeamConfigBB) - 4, sizeof(KeyAndTeamConfigBB));
|
||||||
|
memcpy(&c->game_data.account()->key_config, data.data(), data.size());
|
||||||
|
// TODO: We should send a response here, but I don't know which one!
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -1436,20 +1386,21 @@ void process_simple_mail(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
|
|
||||||
// If the sender is blocked, don't forward the mail
|
// If the sender is blocked, don't forward the mail
|
||||||
for (size_t y = 0; y < 30; y++) {
|
for (size_t y = 0; y < 30; y++) {
|
||||||
if (target->player.blocked_senders.data()[y] == c->license->serial_number) {
|
if (target->game_data.account()->blocked_senders.data()[y] == c->license->serial_number) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the target has auto-reply enabled, send the autoreply
|
// If the target has auto-reply enabled, send the autoreply
|
||||||
if (!target->player.auto_reply.empty()) {
|
if (!target->game_data.player()->auto_reply.empty()) {
|
||||||
send_simple_mail(c, target->license->serial_number,
|
send_simple_mail(c, target->license->serial_number,
|
||||||
target->player.disp.name, target->player.auto_reply);
|
target->game_data.player()->disp.name,
|
||||||
|
target->game_data.player()->auto_reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward the message
|
// Forward the message
|
||||||
u16string msg = decode_sjis(cmd.text);
|
u16string msg = decode_sjis(cmd.text);
|
||||||
send_simple_mail(target, c->license->serial_number, c->player.disp.name, msg);
|
send_simple_mail(target, c->license->serial_number, c->game_data.player()->disp.name, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1466,8 +1417,8 @@ void process_info_board_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
|||||||
template <typename CharT>
|
template <typename CharT>
|
||||||
void process_write_info_board_t(shared_ptr<ServerState>, shared_ptr<Client> c,
|
void process_write_info_board_t(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||||
uint16_t, uint32_t, const string& data) { // D9
|
uint16_t, uint32_t, const string& data) { // D9
|
||||||
check_size_v(data.size(), 0, c->player.info_board.size() * sizeof(CharT));
|
check_size_v(data.size(), 0, c->game_data.player()->info_board.size() * sizeof(CharT));
|
||||||
c->player.info_board.assign(
|
c->game_data.player()->info_board.assign(
|
||||||
reinterpret_cast<const CharT*>(data.data()),
|
reinterpret_cast<const CharT*>(data.data()),
|
||||||
data.size() / sizeof(CharT));
|
data.size() / sizeof(CharT));
|
||||||
}
|
}
|
||||||
@@ -1475,8 +1426,8 @@ void process_write_info_board_t(shared_ptr<ServerState>, shared_ptr<Client> c,
|
|||||||
template <typename CharT>
|
template <typename CharT>
|
||||||
void process_set_auto_reply_t(shared_ptr<ServerState>, shared_ptr<Client> c,
|
void process_set_auto_reply_t(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||||
uint16_t, uint32_t, const string& data) { // C7
|
uint16_t, uint32_t, const string& data) { // C7
|
||||||
check_size_v(data.size(), 0, c->player.auto_reply.size() * sizeof(CharT));
|
check_size_v(data.size(), 0, c->game_data.player()->auto_reply.size() * sizeof(CharT));
|
||||||
c->player.auto_reply.assign(
|
c->game_data.player()->auto_reply.assign(
|
||||||
reinterpret_cast<const CharT*>(data.data()),
|
reinterpret_cast<const CharT*>(data.data()),
|
||||||
data.size() / sizeof(CharT));
|
data.size() / sizeof(CharT));
|
||||||
}
|
}
|
||||||
@@ -1484,13 +1435,13 @@ void process_set_auto_reply_t(shared_ptr<ServerState>, shared_ptr<Client> c,
|
|||||||
void process_disable_auto_reply(shared_ptr<ServerState>, shared_ptr<Client> c,
|
void process_disable_auto_reply(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||||
uint16_t, uint32_t, const string& data) { // C8
|
uint16_t, uint32_t, const string& data) { // C8
|
||||||
check_size_v(data.size(), 0);
|
check_size_v(data.size(), 0);
|
||||||
c->player.auto_reply.clear();
|
c->game_data.player()->auto_reply.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_set_blocked_senders_list(shared_ptr<ServerState>, shared_ptr<Client> c,
|
void process_set_blocked_senders_list(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||||
uint16_t, uint32_t, const string& data) { // C6
|
uint16_t, uint32_t, const string& data) { // C6
|
||||||
const auto& cmd = check_size_t<C_SetBlockedSenders_C6>(data);
|
const auto& cmd = check_size_t<C_SetBlockedSenders_C6>(data);
|
||||||
c->player.blocked_senders = cmd.blocked_senders;
|
c->game_data.account()->blocked_senders = cmd.blocked_senders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1543,7 +1494,7 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
|||||||
|
|
||||||
uint8_t min_level = ((episode == 0xFF) ? 0 : default_minimum_levels[episode - 1][difficulty]);
|
uint8_t min_level = ((episode == 0xFF) ? 0 : default_minimum_levels[episode - 1][difficulty]);
|
||||||
if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES) &&
|
if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES) &&
|
||||||
(min_level > c->player.disp.level)) {
|
(min_level > c->game_data.player()->disp.level)) {
|
||||||
throw invalid_argument("level too low for difficulty");
|
throw invalid_argument("level too low for difficulty");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1552,7 +1503,7 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
|||||||
game->password = password;
|
game->password = password;
|
||||||
game->version = c->version;
|
game->version = c->version;
|
||||||
game->section_id = c->override_section_id >= 0
|
game->section_id = c->override_section_id >= 0
|
||||||
? c->override_section_id : c->player.disp.section_id;
|
? c->override_section_id : c->game_data.player()->disp.section_id;
|
||||||
game->episode = episode;
|
game->episode = episode;
|
||||||
game->difficulty = difficulty;
|
game->difficulty = difficulty;
|
||||||
if (battle) {
|
if (battle) {
|
||||||
@@ -1779,12 +1730,14 @@ independently.\r\n\
|
|||||||
}
|
}
|
||||||
message += u"License check ";
|
message += u"License check ";
|
||||||
try {
|
try {
|
||||||
|
shared_ptr<const License> l;
|
||||||
if (c->flags & Client::Flag::BB_PATCH) {
|
if (c->flags & Client::Flag::BB_PATCH) {
|
||||||
c->license = s->license_manager->verify_bb(cmd.username, cmd.password);
|
l = s->license_manager->verify_bb(cmd.username, cmd.password);
|
||||||
} else {
|
} else {
|
||||||
c->license = s->license_manager->verify_pc(
|
l = s->license_manager->verify_pc(
|
||||||
stoul(cmd.username, nullptr, 16), cmd.password);
|
stoul(cmd.username, nullptr, 16), cmd.password);
|
||||||
}
|
}
|
||||||
|
c->set_license(l);
|
||||||
message += u"OK";
|
message += u"OK";
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
message += u"failed: ";
|
message += u"failed: ";
|
||||||
@@ -2156,7 +2109,7 @@ static process_command_t bb_handlers[0x100] = {
|
|||||||
process_guild_card_data_request_bb, nullptr, nullptr, nullptr,
|
process_guild_card_data_request_bb, nullptr, nullptr, nullptr,
|
||||||
|
|
||||||
// E0
|
// E0
|
||||||
process_key_config_request_bb, nullptr, nullptr, process_player_preview_request_bb,
|
process_key_config_request_bb, nullptr, process_update_key_config_bb, process_player_preview_request_bb,
|
||||||
nullptr, process_create_character_bb, nullptr, process_return_player_data_bb,
|
nullptr, process_create_character_bb, nullptr, process_return_player_data_bb,
|
||||||
process_client_checksum_bb, nullptr, process_team_command_bb, process_stream_file_request_bb,
|
process_client_checksum_bb, nullptr, process_team_command_bb, process_stream_file_request_bb,
|
||||||
process_ignored_command, process_change_account_data_bb, nullptr, nullptr,
|
process_ignored_command, process_change_account_data_bb, nullptr, nullptr,
|
||||||
@@ -2224,7 +2177,11 @@ static process_command_t* handlers[6] = {
|
|||||||
|
|
||||||
void process_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
void process_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||||
uint16_t command, uint32_t flag, const string& data) {
|
uint16_t command, uint32_t flag, const string& data) {
|
||||||
string encoded_name = remove_language_marker(encode_sjis(c->player.disp.name));
|
string encoded_name;
|
||||||
|
auto player = c->game_data.player(false);
|
||||||
|
if (player) {
|
||||||
|
encoded_name = remove_language_marker(encode_sjis(player->disp.name));
|
||||||
|
}
|
||||||
print_received_command(command, flag, data.data(), data.size(), c->version,
|
print_received_command(command, flag, data.data(), data.size(), c->version,
|
||||||
encoded_name.c_str());
|
encoded_name.c_str());
|
||||||
|
|
||||||
|
|||||||
+86
-84
@@ -115,13 +115,13 @@ static void process_subcommand_send_guild_card(shared_ptr<ServerState>,
|
|||||||
|
|
||||||
if (c->version == GameVersion::PC) {
|
if (c->version == GameVersion::PC) {
|
||||||
const auto* cmd = check_size_sc<G_SendGuildCard_PC_6x06>(data);
|
const auto* cmd = check_size_sc<G_SendGuildCard_PC_6x06>(data);
|
||||||
c->player.guild_card_desc = cmd->desc;
|
c->game_data.player()->guild_card_desc = cmd->desc;
|
||||||
} else if (c->version == GameVersion::GC) {
|
} else if (c->version == GameVersion::GC) {
|
||||||
const auto* cmd = check_size_sc<G_SendGuildCard_GC_6x06>(data);
|
const auto* cmd = check_size_sc<G_SendGuildCard_GC_6x06>(data);
|
||||||
c->player.guild_card_desc = cmd->desc;
|
c->game_data.player()->guild_card_desc = cmd->desc;
|
||||||
} else if (c->version == GameVersion::BB) {
|
} else if (c->version == GameVersion::BB) {
|
||||||
const auto* cmd = check_size_sc<G_SendGuildCard_BB_6x06>(data);
|
const auto* cmd = check_size_sc<G_SendGuildCard_BB_6x06>(data);
|
||||||
c->player.guild_card_desc = cmd->desc;
|
c->game_data.player()->guild_card_desc = cmd->desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
send_guild_card(l->clients[flag], c);
|
send_guild_card(l->clients[flag], c);
|
||||||
@@ -256,11 +256,12 @@ static void process_subcommand_player_drop_item(shared_ptr<ServerState>,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
l->add_item(c->player.remove_item(cmd->item_id, 0), cmd->area, cmd->x, cmd->z);
|
l->add_item(c->game_data.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)",
|
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());
|
l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
|
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
@@ -284,11 +285,11 @@ static void process_subcommand_create_inventory_item(shared_ptr<ServerState>,
|
|||||||
item.tech_flag = 0;
|
item.tech_flag = 0;
|
||||||
item.game_flags = 0;
|
item.game_flags = 0;
|
||||||
item.data = cmd->item;
|
item.data = cmd->item;
|
||||||
c->player.add_item(item);
|
c->game_data.player()->add_item(item);
|
||||||
|
|
||||||
log(INFO, "[Items/%08" PRIX32 "] Player %hhu created inventory item %08" PRIX32,
|
log(INFO, "[Items/%08" PRIX32 "] Player %hhu created inventory item %08" PRIX32,
|
||||||
l->lobby_id, cmd->client_id, cmd->item.item_id.load());
|
l->lobby_id, cmd->client_id, cmd->item.id.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
|
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
@@ -316,8 +317,8 @@ static void process_subcommand_drop_partial_stack(shared_ptr<ServerState>,
|
|||||||
l->add_item(item, cmd->area, cmd->x, cmd->z);
|
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)",
|
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());
|
l->lobby_id, cmd->client_id, item.data.id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
|
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
@@ -332,12 +333,12 @@ static void process_subcommand_drop_partial_stack_bb(shared_ptr<ServerState>,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto item = c->player.remove_item(cmd->item_id, cmd->amount);
|
auto item = c->game_data.player()->remove_item(cmd->item_id, cmd->amount);
|
||||||
|
|
||||||
// if a stack was split, the original item still exists, so the dropped item
|
// 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
|
// needs a new ID. remove_item signals this by returning an item with id=-1
|
||||||
if (item.data.item_id == 0xFFFFFFFF) {
|
if (item.data.id == 0xFFFFFFFF) {
|
||||||
item.data.item_id = l->generate_item_id(c->lobby_client_id);
|
item.data.id = l->generate_item_id(c->lobby_client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
l->add_item(item, cmd->area, cmd->x, cmd->z);
|
l->add_item(item, cmd->area, cmd->x, cmd->z);
|
||||||
@@ -345,7 +346,7 @@ static void process_subcommand_drop_partial_stack_bb(shared_ptr<ServerState>,
|
|||||||
log(INFO, "[Items/%08" PRIX32 "] Player %hhu split stack %08" PRIX32 " (%" PRIu32 " of them) at %hu:(%g, %g)",
|
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(),
|
l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->amount.load(),
|
||||||
cmd->area.load(), cmd->x.load(), cmd->z.load());
|
cmd->area.load(), cmd->x.load(), cmd->z.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
|
|
||||||
send_drop_stacked_item(l, item.data, cmd->area, cmd->x, cmd->z);
|
send_drop_stacked_item(l, item.data, cmd->area, cmd->x, cmd->z);
|
||||||
|
|
||||||
@@ -371,11 +372,11 @@ static void process_subcommand_buy_shop_item(shared_ptr<ServerState>,
|
|||||||
item.tech_flag = 0;
|
item.tech_flag = 0;
|
||||||
item.game_flags = 0;
|
item.game_flags = 0;
|
||||||
item.data = cmd->item;
|
item.data = cmd->item;
|
||||||
c->player.add_item(item);
|
c->game_data.player()->add_item(item);
|
||||||
|
|
||||||
log(INFO, "[Items/%08" PRIX32 "] Player %hhu bought item %08" PRIX32 " from shop",
|
log(INFO, "[Items/%08" PRIX32 "] Player %hhu bought item %08" PRIX32 " from shop",
|
||||||
l->lobby_id, cmd->client_id, item.data.item_id.load());
|
l->lobby_id, cmd->client_id, item.data.id.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
|
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
@@ -400,8 +401,8 @@ static void process_subcommand_box_or_enemy_item_drop(shared_ptr<ServerState>,
|
|||||||
l->add_item(item, cmd->area, cmd->x, cmd->z);
|
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)",
|
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());
|
l->lobby_id, item.data.id.load(), cmd->area, cmd->x.load(), cmd->z.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
|
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
@@ -419,11 +420,11 @@ static void process_subcommand_pick_up_item(shared_ptr<ServerState>,
|
|||||||
// BB clients should never send this; only the server should send this
|
// BB clients should never send this; only the server should send this
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c->player.add_item(l->remove_item(cmd->item_id));
|
c->game_data.player()->add_item(l->remove_item(cmd->item_id));
|
||||||
|
|
||||||
log(INFO, "[Items/%08" PRIX32 "] Player %hu picked up %08" PRIX32,
|
log(INFO, "[Items/%08" PRIX32 "] Player %hu picked up %08" PRIX32,
|
||||||
l->lobby_id, cmd->client_id.load(), cmd->item_id.load());
|
l->lobby_id, cmd->client_id.load(), cmd->item_id.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
|
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
@@ -439,7 +440,7 @@ static void process_subcommand_pick_up_item_request(shared_ptr<ServerState>,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->player.add_item(l->remove_item(cmd->item_id));
|
c->game_data.player()->add_item(l->remove_item(cmd->item_id));
|
||||||
|
|
||||||
send_pick_up_item(l, c, cmd->item_id, cmd->area);
|
send_pick_up_item(l, c, cmd->item_id, cmd->area);
|
||||||
|
|
||||||
@@ -459,11 +460,11 @@ static void process_subcommand_equip_unequip_item(shared_ptr<ServerState>,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t index = c->player.inventory.find_item(cmd->item_id);
|
size_t index = c->game_data.player()->inventory.find_item(cmd->item_id);
|
||||||
if (cmd->command == 0x25) {
|
if (cmd->command == 0x25) {
|
||||||
c->player.inventory.items[index].game_flags |= 0x00000008; // equip
|
c->game_data.player()->inventory.items[index].game_flags |= 0x00000008; // equip
|
||||||
} else {
|
} else {
|
||||||
c->player.inventory.items[index].game_flags &= 0xFFFFFFF7; // unequip
|
c->game_data.player()->inventory.items[index].game_flags &= 0xFFFFFFF7; // unequip
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -480,12 +481,12 @@ static void process_subcommand_use_item(shared_ptr<ServerState>,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t index = c->player.inventory.find_item(cmd->item_id);
|
size_t index = c->game_data.player()->inventory.find_item(cmd->item_id);
|
||||||
player_use_item(c, index);
|
player_use_item(c, index);
|
||||||
|
|
||||||
log(INFO, "[Items/%08" PRIX32 "] Player used item %hhu:%08" PRIX32,
|
log(INFO, "[Items/%08" PRIX32 "] Player used item %hhu:%08" PRIX32,
|
||||||
l->lobby_id, cmd->client_id, cmd->item_id.load());
|
l->lobby_id, cmd->client_id, cmd->item_id.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
|
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
@@ -502,9 +503,9 @@ static void process_subcommand_open_shop_bb_or_unknown_ep3(shared_ptr<ServerStat
|
|||||||
uint32_t shop_type = p[1].dword;
|
uint32_t shop_type = p[1].dword;
|
||||||
|
|
||||||
if ((l->version == GameVersion::BB) && l->is_game()) {
|
if ((l->version == GameVersion::BB) && l->is_game()) {
|
||||||
size_t num_items = (rand() % 4) + 9;
|
size_t num_items = 9 + (rand() % 4);
|
||||||
c->player.current_shop_contents.clear();
|
c->game_data.shop_contents.clear();
|
||||||
while (c->player.current_shop_contents.size() < num_items) {
|
while (c->game_data.shop_contents.size() < num_items) {
|
||||||
ItemData item_data;
|
ItemData item_data;
|
||||||
if (shop_type == 0) { // tool shop
|
if (shop_type == 0) { // tool shop
|
||||||
item_data = s->common_item_creator->create_shop_item(l->difficulty, 3);
|
item_data = s->common_item_creator->create_shop_item(l->difficulty, 3);
|
||||||
@@ -516,8 +517,8 @@ static void process_subcommand_open_shop_bb_or_unknown_ep3(shared_ptr<ServerStat
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
item_data.item_id = l->generate_item_id(c->lobby_client_id);
|
item_data.id = l->generate_item_id(c->lobby_client_id);
|
||||||
c->player.current_shop_contents.emplace_back(item_data);
|
c->game_data.shop_contents.emplace_back(item_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
send_shop(c, shop_type);
|
send_shop(c, shop_type);
|
||||||
@@ -543,34 +544,34 @@ static void process_subcommand_bank_action_bb(shared_ptr<ServerState>,
|
|||||||
|
|
||||||
if (cmd->action == 0) { // deposit
|
if (cmd->action == 0) { // deposit
|
||||||
if (cmd->item_id == 0xFFFFFFFF) { // meseta
|
if (cmd->item_id == 0xFFFFFFFF) { // meseta
|
||||||
if (cmd->meseta_amount > c->player.disp.meseta) {
|
if (cmd->meseta_amount > c->game_data.player()->disp.meseta) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((c->player.bank.meseta + cmd->meseta_amount) > 999999) {
|
if ((c->game_data.player()->bank.meseta + cmd->meseta_amount) > 999999) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c->player.bank.meseta += cmd->meseta_amount;
|
c->game_data.player()->bank.meseta += cmd->meseta_amount;
|
||||||
c->player.disp.meseta -= cmd->meseta_amount;
|
c->game_data.player()->disp.meseta -= cmd->meseta_amount;
|
||||||
} else { // item
|
} else { // item
|
||||||
auto item = c->player.remove_item(cmd->item_id, cmd->item_amount);
|
auto item = c->game_data.player()->remove_item(cmd->item_id, cmd->item_amount);
|
||||||
c->player.bank.add_item(item.to_bank_item());
|
c->game_data.player()->bank.add_item(item);
|
||||||
send_destroy_item(l, c, cmd->item_id, cmd->item_amount);
|
send_destroy_item(l, c, cmd->item_id, cmd->item_amount);
|
||||||
}
|
}
|
||||||
} else if (cmd->action == 1) { // take
|
} else if (cmd->action == 1) { // take
|
||||||
if (cmd->item_id == 0xFFFFFFFF) { // meseta
|
if (cmd->item_id == 0xFFFFFFFF) { // meseta
|
||||||
if (cmd->meseta_amount > c->player.bank.meseta) {
|
if (cmd->meseta_amount > c->game_data.player()->bank.meseta) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((c->player.disp.meseta + cmd->meseta_amount) > 999999) {
|
if ((c->game_data.player()->disp.meseta + cmd->meseta_amount) > 999999) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c->player.bank.meseta -= cmd->meseta_amount;
|
c->game_data.player()->bank.meseta -= cmd->meseta_amount;
|
||||||
c->player.disp.meseta += cmd->meseta_amount;
|
c->game_data.player()->disp.meseta += cmd->meseta_amount;
|
||||||
} else { // item
|
} else { // item
|
||||||
auto bank_item = c->player.bank.remove_item(cmd->item_id, cmd->item_amount);
|
auto bank_item = c->game_data.player()->bank.remove_item(cmd->item_id, cmd->item_amount);
|
||||||
PlayerInventoryItem item = bank_item.to_inventory_item();
|
PlayerInventoryItem item = bank_item;
|
||||||
item.data.item_id = l->generate_item_id(0xFF);
|
item.data.id = l->generate_item_id(0xFF);
|
||||||
c->player.add_item(item);
|
c->game_data.player()->add_item(item);
|
||||||
send_create_inventory_item(l, c, item.data);
|
send_create_inventory_item(l, c, item.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -589,18 +590,18 @@ static void process_subcommand_sort_inventory_bb(shared_ptr<ServerState>,
|
|||||||
|
|
||||||
for (size_t x = 0; x < 30; x++) {
|
for (size_t x = 0; x < 30; x++) {
|
||||||
if (cmd->item_ids[x] == 0xFFFFFFFF) {
|
if (cmd->item_ids[x] == 0xFFFFFFFF) {
|
||||||
sorted.items[x].data.item_id = 0xFFFFFFFF;
|
sorted.items[x].data.id = 0xFFFFFFFF;
|
||||||
} else {
|
} else {
|
||||||
size_t index = c->player.inventory.find_item(cmd->item_ids[x]);
|
size_t index = c->game_data.player()->inventory.find_item(cmd->item_ids[x]);
|
||||||
sorted.items[x] = c->player.inventory.items[index];
|
sorted.items[x] = c->game_data.player()->inventory.items[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sorted.num_items = c->player.inventory.num_items;
|
sorted.num_items = c->game_data.player()->inventory.num_items;
|
||||||
sorted.hp_materials_used = c->player.inventory.hp_materials_used;
|
sorted.hp_materials_used = c->game_data.player()->inventory.hp_materials_used;
|
||||||
sorted.tp_materials_used = c->player.inventory.tp_materials_used;
|
sorted.tp_materials_used = c->game_data.player()->inventory.tp_materials_used;
|
||||||
sorted.language = c->player.inventory.language;
|
sorted.language = c->game_data.player()->inventory.language;
|
||||||
c->player.inventory = sorted;
|
c->game_data.player()->inventory = sorted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,10 +621,11 @@ static void process_subcommand_enemy_drop_item_request(shared_ptr<ServerState> s
|
|||||||
PlayerInventoryItem item;
|
PlayerInventoryItem item;
|
||||||
memset(&item, 0, sizeof(PlayerInventoryItem));
|
memset(&item, 0, sizeof(PlayerInventoryItem));
|
||||||
|
|
||||||
|
// TODO: Deduplicate this code with the box drop item request handler
|
||||||
bool is_rare = false;
|
bool is_rare = false;
|
||||||
if (l->next_drop_item.data.item_data1d[0]) {
|
if (l->next_drop_item.data.data1d[0]) {
|
||||||
item = l->next_drop_item;
|
item = l->next_drop_item;
|
||||||
l->next_drop_item.data.item_data1d[0] = 0;
|
l->next_drop_item.data.data1d[0] = 0;
|
||||||
} else {
|
} else {
|
||||||
if (l->rare_item_set) {
|
if (l->rare_item_set) {
|
||||||
if (cmd->enemy_id <= 0x65) {
|
if (cmd->enemy_id <= 0x65) {
|
||||||
@@ -632,10 +634,10 @@ static void process_subcommand_enemy_drop_item_request(shared_ptr<ServerState> s
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_rare) {
|
if (is_rare) {
|
||||||
memcpy(&item.data.item_data1d, l->rare_item_set->rares[cmd->enemy_id].item_code, 3);
|
memcpy(&item.data.data1d, l->rare_item_set->rares[cmd->enemy_id].item_code, 3);
|
||||||
//RandPercentages();
|
//RandPercentages();
|
||||||
if (item.data.item_data1d[0] == 0) {
|
if (item.data.data1d[0] == 0) {
|
||||||
item.data.item_data1[4] |= 0x80; // make it untekked if it's a weapon
|
item.data.data1[4] |= 0x80; // make it unidentified if it's a weapon
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@@ -647,7 +649,7 @@ static void process_subcommand_enemy_drop_item_request(shared_ptr<ServerState> s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item.data.item_id = l->generate_item_id(0xFF);
|
item.data.id = l->generate_item_id(0xFF);
|
||||||
|
|
||||||
l->add_item(item, cmd->area, cmd->x, cmd->z);
|
l->add_item(item, cmd->area, cmd->x, cmd->z);
|
||||||
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z,
|
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z,
|
||||||
@@ -672,9 +674,9 @@ static void process_subcommand_box_drop_item_request(shared_ptr<ServerState> s,
|
|||||||
memset(&item, 0, sizeof(PlayerInventoryItem));
|
memset(&item, 0, sizeof(PlayerInventoryItem));
|
||||||
|
|
||||||
bool is_rare = false;
|
bool is_rare = false;
|
||||||
if (l->next_drop_item.data.item_data1d[0]) {
|
if (l->next_drop_item.data.data1d[0]) {
|
||||||
item = l->next_drop_item;
|
item = l->next_drop_item;
|
||||||
l->next_drop_item.data.item_data1d[0] = 0;
|
l->next_drop_item.data.data1d[0] = 0;
|
||||||
} else {
|
} else {
|
||||||
size_t index;
|
size_t index;
|
||||||
if (l->rare_item_set) {
|
if (l->rare_item_set) {
|
||||||
@@ -690,10 +692,10 @@ static void process_subcommand_box_drop_item_request(shared_ptr<ServerState> s,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_rare) {
|
if (is_rare) {
|
||||||
memcpy(item.data.item_data1, l->rare_item_set->box_rares[index].item_code, 3);
|
memcpy(item.data.data1, l->rare_item_set->box_rares[index].item_code, 3);
|
||||||
//RandPercentages();
|
//RandPercentages();
|
||||||
if (item.data.item_data1d[0] == 0) {
|
if (item.data.data1d[0] == 0) {
|
||||||
item.data.item_data1[4] |= 0x80; // make it untekked if it's a weapon
|
item.data.data1[4] |= 0x80; // make it unidentified if it's a weapon
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@@ -705,7 +707,7 @@ static void process_subcommand_box_drop_item_request(shared_ptr<ServerState> s,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item.data.item_id = l->generate_item_id(0xFF);
|
item.data.id = l->generate_item_id(0xFF);
|
||||||
|
|
||||||
l->add_item(item, cmd->area, cmd->x, cmd->z);
|
l->add_item(item, cmd->area, cmd->x, cmd->z);
|
||||||
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z,
|
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z,
|
||||||
@@ -812,7 +814,7 @@ static void process_subcommand_enemy_killed(shared_ptr<ServerState> s,
|
|||||||
if (!other_c) {
|
if (!other_c) {
|
||||||
continue; // no player
|
continue; // no player
|
||||||
}
|
}
|
||||||
if (other_c->player.disp.level >= 199) {
|
if (other_c->game_data.player()->disp.level >= 199) {
|
||||||
continue; // player is level 200 or higher
|
continue; // player is level 200 or higher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,21 +826,21 @@ static void process_subcommand_enemy_killed(shared_ptr<ServerState> s,
|
|||||||
exp = ((enemy.experience * 77) / 100);
|
exp = ((enemy.experience * 77) / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
other_c->player.disp.experience += exp;
|
other_c->game_data.player()->disp.experience += exp;
|
||||||
send_give_experience(l, other_c, exp);
|
send_give_experience(l, other_c, exp);
|
||||||
|
|
||||||
bool leveled_up = false;
|
bool leveled_up = false;
|
||||||
do {
|
do {
|
||||||
const auto& level = s->level_table->stats_for_level(
|
const auto& level = s->level_table->stats_for_level(
|
||||||
other_c->player.disp.char_class, other_c->player.disp.level + 1);
|
other_c->game_data.player()->disp.char_class, other_c->game_data.player()->disp.level + 1);
|
||||||
if (other_c->player.disp.experience >= level.experience) {
|
if (other_c->game_data.player()->disp.experience >= level.experience) {
|
||||||
leveled_up = true;
|
leveled_up = true;
|
||||||
level.apply(other_c->player.disp.stats);
|
level.apply(other_c->game_data.player()->disp.stats);
|
||||||
other_c->player.disp.level++;
|
other_c->game_data.player()->disp.level++;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while (other_c->player.disp.level < 199);
|
} while (other_c->game_data.player()->disp.level < 199);
|
||||||
if (leveled_up) {
|
if (leveled_up) {
|
||||||
send_level_up(l, other_c);
|
send_level_up(l, other_c);
|
||||||
}
|
}
|
||||||
@@ -856,10 +858,10 @@ static void process_subcommand_destroy_inventory_item(shared_ptr<ServerState>,
|
|||||||
if (cmd->client_id != c->lobby_client_id) {
|
if (cmd->client_id != c->lobby_client_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c->player.remove_item(cmd->item_id, cmd->amount);
|
c->game_data.player()->remove_item(cmd->item_id, cmd->amount);
|
||||||
log(INFO, "[Items/%08" PRIX32 "] Inventory item %hhu:%08" PRIX32 " destroyed (%" PRIX32 " of them)",
|
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());
|
l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->amount.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,7 +875,7 @@ static void process_subcommand_destroy_ground_item(shared_ptr<ServerState>,
|
|||||||
l->remove_item(cmd->item_id);
|
l->remove_item(cmd->item_id);
|
||||||
log(INFO, "[Items/%08" PRIX32 "] Ground item %08" PRIX32 " destroyed (%" PRIX32 " of them)",
|
log(INFO, "[Items/%08" PRIX32 "] Ground item %08" PRIX32 " destroyed (%" PRIX32 " of them)",
|
||||||
l->lobby_id, cmd->item_id.load(), cmd->amount.load());
|
l->lobby_id, cmd->item_id.load(), cmd->amount.load());
|
||||||
// c->player.print_inventory(stderr);
|
// c->game_data.player()->print_inventory(stderr);
|
||||||
forward_subcommand(l, c, command, flag, data);
|
forward_subcommand(l, c, command, flag, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,14 +888,14 @@ static void process_subcommand_identify_item_bb(shared_ptr<ServerState>,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t x = c->player.inventory.find_item(cmd->item_id);
|
size_t x = c->game_data.player()->inventory.find_item(cmd->item_id);
|
||||||
if (c->player.inventory.items[x].data.item_data1[0] != 0) {
|
if (c->game_data.player()->inventory.items[x].data.data1[0] != 0) {
|
||||||
return; // only weapons can be identified
|
return; // only weapons can be identified
|
||||||
}
|
}
|
||||||
|
|
||||||
c->player.disp.meseta -= 100;
|
c->game_data.player()->disp.meseta -= 100;
|
||||||
c->player.identify_result = c->player.inventory.items[x];
|
c->game_data.identify_result = c->game_data.player()->inventory.items[x];
|
||||||
c->player.identify_result.data.item_data1[4] &= 0x7F;
|
c->game_data.identify_result.data.data1[4] &= 0x7F;
|
||||||
|
|
||||||
// TODO: move this into a SendCommands.cc function
|
// TODO: move this into a SendCommands.cc function
|
||||||
G_IdentifyResult_BB_6xB9 res;
|
G_IdentifyResult_BB_6xB9 res;
|
||||||
@@ -901,7 +903,7 @@ static void process_subcommand_identify_item_bb(shared_ptr<ServerState>,
|
|||||||
res.size = sizeof(cmd) / 4;
|
res.size = sizeof(cmd) / 4;
|
||||||
res.client_id = c->lobby_client_id;
|
res.client_id = c->lobby_client_id;
|
||||||
res.unused = 0;
|
res.unused = 0;
|
||||||
res.item = c->player.identify_result.data;
|
res.item = c->game_data.identify_result.data;
|
||||||
send_command(l, 0x60, 0x00, res);
|
send_command(l, 0x60, 0x00, res);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -923,8 +925,8 @@ static void process_subcommand_identify_item_bb(shared_ptr<ServerState>,
|
|||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// size_t x = c->player.inventory.find_item(cmd->item_id);
|
// size_t x = c->game_data.player()->inventory.find_item(cmd->item_id);
|
||||||
// c->player.inventory.items[x] = c->player.identify_result;
|
// c->game_data.player()->inventory.items[x] = c->game_data.player()->identify_result;
|
||||||
// // TODO: what do we send to the other clients? anything?
|
// // TODO: what do we send to the other clients? anything?
|
||||||
//
|
//
|
||||||
// } else {
|
// } else {
|
||||||
|
|||||||
+44
-39
@@ -113,7 +113,11 @@ void send_command(shared_ptr<Client> c, uint16_t command, uint32_t flag,
|
|||||||
if (!c->bev) {
|
if (!c->bev) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
string encoded_name = remove_language_marker(encode_sjis(c->player.disp.name));
|
string encoded_name;
|
||||||
|
auto player = c->game_data.player(false);
|
||||||
|
if (player) {
|
||||||
|
encoded_name = remove_language_marker(encode_sjis(player->disp.name));
|
||||||
|
}
|
||||||
send_command(c->bev, c->version, c->crypt_out.get(), command, flag, data,
|
send_command(c->bev, c->version, c->crypt_out.get(), command, flag, data,
|
||||||
size, encoded_name.c_str());
|
size, encoded_name.c_str());
|
||||||
}
|
}
|
||||||
@@ -294,7 +298,7 @@ void send_client_init_bb(shared_ptr<Client> c, uint32_t error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void send_team_and_key_config_bb(shared_ptr<Client> c) {
|
void send_team_and_key_config_bb(shared_ptr<Client> c) {
|
||||||
send_command(c, 0x00E2, 0x00000000, c->player.key_config);
|
send_command(c, 0x00E2, 0x00000000, c->game_data.account()->key_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_player_preview_bb(shared_ptr<Client> c, uint8_t player_index,
|
void send_player_preview_bb(shared_ptr<Client> c, uint8_t player_index,
|
||||||
@@ -317,8 +321,8 @@ void send_accept_client_checksum_bb(shared_ptr<Client> c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void send_guild_card_header_bb(shared_ptr<Client> c) {
|
void send_guild_card_header_bb(shared_ptr<Client> c) {
|
||||||
uint32_t checksum = compute_guild_card_checksum(&c->player.guild_cards,
|
uint32_t checksum = compute_guild_card_checksum(
|
||||||
sizeof(GuildCardFileBB));
|
&c->game_data.account()->guild_cards, sizeof(GuildCardFileBB));
|
||||||
S_GuildCardHeader_BB_01DC cmd = {1, 0x00000490, checksum};
|
S_GuildCardHeader_BB_01DC cmd = {1, 0x00000490, checksum};
|
||||||
send_command(c, 0x01DC, 0x00000000, cmd);
|
send_command(c, 0x01DC, 0x00000000, cmd);
|
||||||
}
|
}
|
||||||
@@ -336,7 +340,8 @@ void send_guild_card_chunk_bb(shared_ptr<Client> c, size_t chunk_index) {
|
|||||||
StringWriter w;
|
StringWriter w;
|
||||||
w.put_u32l(0);
|
w.put_u32l(0);
|
||||||
w.put_u32l(chunk_index);
|
w.put_u32l(chunk_index);
|
||||||
w.write(&c->player.guild_cards + chunk_offset, data_size);
|
w.write(reinterpret_cast<const uint8_t*>(&c->game_data.account()->guild_cards) + chunk_offset,
|
||||||
|
data_size);
|
||||||
|
|
||||||
send_command(c, 0x02DC, 0x00000000, w.str());
|
send_command(c, 0x02DC, 0x00000000, w.str());
|
||||||
}
|
}
|
||||||
@@ -397,7 +402,7 @@ void send_approve_player_choice_bb(shared_ptr<Client> c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void send_complete_player_bb(shared_ptr<Client> c) {
|
void send_complete_player_bb(shared_ptr<Client> c) {
|
||||||
send_command(c, 0x00E7, 0x00000000, c->player.export_bb_player_data());
|
send_command(c, 0x00E7, 0x00000000, c->game_data.export_player_bb());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -528,8 +533,8 @@ void send_info_board_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto& e = entries.emplace_back();
|
auto& e = entries.emplace_back();
|
||||||
e.name = c->player.disp.name;
|
e.name = c->game_data.player()->disp.name;
|
||||||
e.message = c->player.info_board;
|
e.message = c->game_data.player()->info_board;
|
||||||
add_color_inplace(e.message);
|
add_color_inplace(e.message);
|
||||||
}
|
}
|
||||||
send_command(c, 0xD8, entries.size(), entries);
|
send_command(c, 0xD8, entries.size(), entries);
|
||||||
@@ -582,7 +587,7 @@ void send_card_search_result_t(
|
|||||||
cmd.location_string = location_string;
|
cmd.location_string = location_string;
|
||||||
cmd.menu_id = LOBBY_MENU_ID;
|
cmd.menu_id = LOBBY_MENU_ID;
|
||||||
cmd.lobby_id = result->lobby_id;
|
cmd.lobby_id = result->lobby_id;
|
||||||
cmd.name = result->player.disp.name;
|
cmd.name = result->game_data.player()->disp.name;
|
||||||
|
|
||||||
send_command(c, 0x41, 0x00, cmd);
|
send_command(c, 0x41, 0x00, cmd);
|
||||||
}
|
}
|
||||||
@@ -616,13 +621,13 @@ void send_guild_card_pc_gc(shared_ptr<Client> c, shared_ptr<Client> source) {
|
|||||||
cmd.unused = 0x0000;
|
cmd.unused = 0x0000;
|
||||||
cmd.player_tag = 0x00010000;
|
cmd.player_tag = 0x00010000;
|
||||||
cmd.guild_card_number = source->license->serial_number;
|
cmd.guild_card_number = source->license->serial_number;
|
||||||
cmd.name = source->player.disp.name;
|
cmd.name = source->game_data.player()->disp.name;
|
||||||
remove_language_marker_inplace(cmd.name);
|
remove_language_marker_inplace(cmd.name);
|
||||||
cmd.desc = source->player.guild_card_desc;
|
cmd.desc = source->game_data.player()->guild_card_desc;
|
||||||
cmd.reserved1 = 1;
|
cmd.reserved1 = 1;
|
||||||
cmd.reserved2 = 1;
|
cmd.reserved2 = 1;
|
||||||
cmd.section_id = source->player.disp.section_id;
|
cmd.section_id = source->game_data.player()->disp.section_id;
|
||||||
cmd.char_class = source->player.disp.char_class;
|
cmd.char_class = source->game_data.player()->disp.char_class;
|
||||||
send_command(c, 0x62, c->lobby_client_id, cmd);
|
send_command(c, 0x62, c->lobby_client_id, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,13 +637,13 @@ void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) {
|
|||||||
cmd.size = sizeof(cmd) / 4;
|
cmd.size = sizeof(cmd) / 4;
|
||||||
cmd.unused = 0x0000;
|
cmd.unused = 0x0000;
|
||||||
cmd.guild_card_number = source->license->serial_number;
|
cmd.guild_card_number = source->license->serial_number;
|
||||||
cmd.name = remove_language_marker(source->player.disp.name);
|
cmd.name = remove_language_marker(source->game_data.player()->disp.name);
|
||||||
cmd.team_name = remove_language_marker(source->player.team_name);
|
cmd.team_name = remove_language_marker(source->game_data.account()->team_name);
|
||||||
cmd.desc = source->player.guild_card_desc;
|
cmd.desc = source->game_data.player()->guild_card_desc;
|
||||||
cmd.reserved1 = 1;
|
cmd.reserved1 = 1;
|
||||||
cmd.reserved2 = 1;
|
cmd.reserved2 = 1;
|
||||||
cmd.section_id = source->player.disp.section_id;
|
cmd.section_id = source->game_data.player()->disp.section_id;
|
||||||
cmd.char_class = source->player.disp.char_class;
|
cmd.char_class = source->game_data.player()->disp.char_class;
|
||||||
send_command(c, 0x62, c->lobby_client_id, cmd);
|
send_command(c, 0x62, c->lobby_client_id, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,11 +865,11 @@ void send_join_game_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
|||||||
// See comment in send_join_lobby_t about Episode III behavior here
|
// See comment in send_join_lobby_t about Episode III behavior here
|
||||||
cmd.lobby_data[x].ip_address = 0x7F000001;
|
cmd.lobby_data[x].ip_address = 0x7F000001;
|
||||||
cmd.lobby_data[x].client_id = c->lobby_client_id;
|
cmd.lobby_data[x].client_id = c->lobby_client_id;
|
||||||
cmd.lobby_data[x].name = l->clients[x]->player.disp.name;
|
cmd.lobby_data[x].name = l->clients[x]->game_data.player()->disp.name;
|
||||||
if (l->flags & Lobby::Flag::EPISODE_3_ONLY) {
|
if (l->flags & Lobby::Flag::EPISODE_3_ONLY) {
|
||||||
cmd.players_ep3[x].inventory = l->clients[x]->player.inventory;
|
cmd.players_ep3[x].inventory = l->clients[x]->game_data.player()->inventory;
|
||||||
cmd.players_ep3[x].disp = convert_player_disp_data<DispDataT>(
|
cmd.players_ep3[x].disp = convert_player_disp_data<DispDataT>(
|
||||||
l->clients[x]->player.disp);
|
l->clients[x]->game_data.player()->disp);
|
||||||
}
|
}
|
||||||
player_count++;
|
player_count++;
|
||||||
} else {
|
} else {
|
||||||
@@ -957,9 +962,9 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
|||||||
// to avoid this behavior.
|
// to avoid this behavior.
|
||||||
e.lobby_data.ip_address = 0x7F000001;
|
e.lobby_data.ip_address = 0x7F000001;
|
||||||
e.lobby_data.client_id = lc->lobby_client_id;
|
e.lobby_data.client_id = lc->lobby_client_id;
|
||||||
e.lobby_data.name = lc->player.disp.name;
|
e.lobby_data.name = lc->game_data.player()->disp.name;
|
||||||
e.inventory = lc->player.inventory;
|
e.inventory = lc->game_data.player()->inventory;
|
||||||
e.disp = convert_player_disp_data<DispDataT>(lc->player.disp);
|
e.disp = convert_player_disp_data<DispDataT>(lc->game_data.player()->disp);
|
||||||
if (c->version == GameVersion::PC) {
|
if (c->version == GameVersion::PC) {
|
||||||
e.disp.enforce_pc_limits();
|
e.disp.enforce_pc_limits();
|
||||||
}
|
}
|
||||||
@@ -1173,12 +1178,12 @@ void send_destroy_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
|||||||
|
|
||||||
// sends the player their bank data
|
// sends the player their bank data
|
||||||
void send_bank(shared_ptr<Client> c) {
|
void send_bank(shared_ptr<Client> c) {
|
||||||
vector<PlayerBankItem> items(c->player.bank.items,
|
vector<PlayerBankItem> items(c->game_data.player()->bank.items,
|
||||||
&c->player.bank.items[c->player.bank.num_items]);
|
&c->game_data.player()->bank.items[c->game_data.player()->bank.num_items]);
|
||||||
|
|
||||||
uint32_t checksum = random_object<uint32_t>();
|
uint32_t checksum = random_object<uint32_t>();
|
||||||
G_BankContentsHeader_BB_6xBC cmd = {
|
G_BankContentsHeader_BB_6xBC cmd = {
|
||||||
0xBC, 0, 0, 0, checksum, c->player.bank.num_items, c->player.bank.meseta};
|
0xBC, 0, 0, 0, checksum, c->game_data.player()->bank.num_items, c->game_data.player()->bank.meseta};
|
||||||
|
|
||||||
size_t size = 8 + sizeof(cmd) + items.size() * sizeof(PlayerBankItem);
|
size_t size = 8 + sizeof(cmd) + items.size() * sizeof(PlayerBankItem);
|
||||||
cmd.size = size;
|
cmd.size = size;
|
||||||
@@ -1193,18 +1198,18 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
|
|||||||
0x2C,
|
0x2C,
|
||||||
0x037F,
|
0x037F,
|
||||||
shop_type,
|
shop_type,
|
||||||
static_cast<uint8_t>(c->player.current_shop_contents.size()),
|
static_cast<uint8_t>(c->game_data.shop_contents.size()),
|
||||||
0,
|
0,
|
||||||
{},
|
{},
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t count = c->player.current_shop_contents.size();
|
size_t count = c->game_data.shop_contents.size();
|
||||||
if (count > sizeof(cmd.entries) / sizeof(cmd.entries[0])) {
|
if (count > sizeof(cmd.entries) / sizeof(cmd.entries[0])) {
|
||||||
throw logic_error("too many items in shop");
|
throw logic_error("too many items in shop");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t x = 0; x < count; x++) {
|
for (size_t x = 0; x < count; x++) {
|
||||||
cmd.entries[x] = c->player.current_shop_contents[x];
|
cmd.entries[x] = c->game_data.shop_contents[x];
|
||||||
}
|
}
|
||||||
|
|
||||||
send_command(c, 0x6C, 0x00, &cmd, sizeof(cmd) - sizeof(cmd.entries[0]) * (20 - count));
|
send_command(c, 0x6C, 0x00, &cmd, sizeof(cmd) - sizeof(cmd.entries[0]) * (20 - count));
|
||||||
@@ -1212,15 +1217,15 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
|
|||||||
|
|
||||||
// notifies players about a level up
|
// notifies players about a level up
|
||||||
void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||||
PlayerStats stats = c->player.disp.stats;
|
PlayerStats stats = c->game_data.player()->disp.stats;
|
||||||
|
|
||||||
for (size_t x = 0; x < c->player.inventory.num_items; x++) {
|
for (size_t x = 0; x < c->game_data.player()->inventory.num_items; x++) {
|
||||||
if ((c->player.inventory.items[x].equip_flags & 0x08) &&
|
if ((c->game_data.player()->inventory.items[x].equip_flags & 0x08) &&
|
||||||
(c->player.inventory.items[x].data.item_data1[0] == 0x02)) {
|
(c->game_data.player()->inventory.items[x].data.data1[0] == 0x02)) {
|
||||||
stats.dfp += (c->player.inventory.items[x].data.item_data1w[2] / 100);
|
stats.dfp += (c->game_data.player()->inventory.items[x].data.data1w[2] / 100);
|
||||||
stats.atp += (c->player.inventory.items[x].data.item_data1w[3] / 50);
|
stats.atp += (c->game_data.player()->inventory.items[x].data.data1w[3] / 50);
|
||||||
stats.ata += (c->player.inventory.items[x].data.item_data1w[4] / 200);
|
stats.ata += (c->game_data.player()->inventory.items[x].data.data1w[4] / 200);
|
||||||
stats.mst += (c->player.inventory.items[x].data.item_data1w[5] / 50);
|
stats.mst += (c->game_data.player()->inventory.items[x].data.data1w[5] / 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1235,7 +1240,7 @@ void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
|||||||
stats.hp,
|
stats.hp,
|
||||||
stats.dfp,
|
stats.dfp,
|
||||||
stats.ata,
|
stats.ata,
|
||||||
c->player.disp.level};
|
c->game_data.player()->disp.level};
|
||||||
send_command(l, 0x60, 0x00, cmd);
|
send_command(l, 0x60, 0x00, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-46
@@ -1370,16 +1370,16 @@ uint8_t technique_for_name(const u16string& name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
string name_for_item(const ItemData& item, bool include_color_codes) {
|
string name_for_item(const ItemData& item, bool include_color_codes) {
|
||||||
if (item.item_data1[0] == 0x04) {
|
if (item.data1[0] == 0x04) {
|
||||||
return string_printf("%" PRIu32 " Meseta", item.item_data2d.load());
|
return string_printf("%" PRIu32 " Meseta", item.data2d.load());
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> ret_tokens;
|
vector<string> ret_tokens;
|
||||||
|
|
||||||
// For weapons, specials appear before the weapon name
|
// For weapons, specials appear before the weapon name
|
||||||
if ((item.item_data1[0] == 0x00) && (item.item_data1[4] != 0x00)) {
|
if ((item.data1[0] == 0x00) && (item.data1[4] != 0x00)) {
|
||||||
bool is_present = item.item_data1[4] & 0x40;
|
bool is_present = item.data1[4] & 0x40;
|
||||||
uint8_t special_id = item.item_data1[4] & 0x3F;
|
uint8_t special_id = item.data1[4] & 0x3F;
|
||||||
if (is_present) {
|
if (is_present) {
|
||||||
ret_tokens.emplace_back("Wrapped");
|
ret_tokens.emplace_back("Wrapped");
|
||||||
}
|
}
|
||||||
@@ -1392,7 +1392,7 @@ string name_for_item(const ItemData& item, bool include_color_codes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Mags can be wrapped as well
|
// Mags can be wrapped as well
|
||||||
if ((item.item_data1[0] == 0x02) && (item.item_data2[1] & 0x40)) {
|
if ((item.data1[0] == 0x02) && (item.data2[1] & 0x40)) {
|
||||||
ret_tokens.emplace_back("Wrapped");
|
ret_tokens.emplace_back("Wrapped");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1402,10 +1402,10 @@ string name_for_item(const ItemData& item, bool include_color_codes) {
|
|||||||
ItemNameInfo name_info(nullptr, false, false);
|
ItemNameInfo name_info(nullptr, false, false);
|
||||||
uint32_t primary_identifier = item.primary_identifier();
|
uint32_t primary_identifier = item.primary_identifier();
|
||||||
if ((primary_identifier & 0xFFFFFF00) == 0x00030200) {
|
if ((primary_identifier & 0xFFFFFF00) == 0x00030200) {
|
||||||
string technique_name = name_for_technique(item.item_data1[4]);
|
string technique_name = name_for_technique(item.data1[4]);
|
||||||
technique_name[0] = toupper(technique_name[0]);
|
technique_name[0] = toupper(technique_name[0]);
|
||||||
ret_tokens.emplace_back(string_printf(
|
ret_tokens.emplace_back(string_printf(
|
||||||
"Disk:%s Lv.%d", technique_name.c_str(), item.item_data1[2] + 1));
|
"Disk:%s Lv.%d", technique_name.c_str(), item.data1[2] + 1));
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
name_info = name_info_for_primary_identifier.at(primary_identifier);
|
name_info = name_info_for_primary_identifier.at(primary_identifier);
|
||||||
@@ -1416,22 +1416,22 @@ string name_for_item(const ItemData& item, bool include_color_codes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For weapons, add the grind and percentages, or S-rank name if applicable
|
// For weapons, add the grind and percentages, or S-rank name if applicable
|
||||||
if (item.item_data1[0] == 0x00) {
|
if (item.data1[0] == 0x00) {
|
||||||
if (item.item_data1[3] > 0) {
|
if (item.data1[3] > 0) {
|
||||||
ret_tokens.emplace_back(string_printf("+%hhu", item.item_data1[3]));
|
ret_tokens.emplace_back(string_printf("+%hhu", item.data1[3]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name_info.is_s_rank && (item.item_data1[6] & 0x18)) {
|
if (name_info.is_s_rank && (item.data1[6] & 0x18)) {
|
||||||
// S-rank (has name instead of percent bonuses)
|
// S-rank (has name instead of percent bonuses)
|
||||||
uint8_t char_indexes[8] = {
|
uint8_t char_indexes[8] = {
|
||||||
static_cast<uint8_t>((item.item_data1w[3] >> 5) & 0x1F),
|
static_cast<uint8_t>((item.data1w[3] >> 5) & 0x1F),
|
||||||
static_cast<uint8_t>(item.item_data1w[3] & 0x1F),
|
static_cast<uint8_t>(item.data1w[3] & 0x1F),
|
||||||
static_cast<uint8_t>((item.item_data1w[4] >> 10) & 0x1F),
|
static_cast<uint8_t>((item.data1w[4] >> 10) & 0x1F),
|
||||||
static_cast<uint8_t>((item.item_data1w[4] >> 5) & 0x1F),
|
static_cast<uint8_t>((item.data1w[4] >> 5) & 0x1F),
|
||||||
static_cast<uint8_t>(item.item_data1w[4] & 0x1F),
|
static_cast<uint8_t>(item.data1w[4] & 0x1F),
|
||||||
static_cast<uint8_t>((item.item_data1w[5] >> 10) & 0x1F),
|
static_cast<uint8_t>((item.data1w[5] >> 10) & 0x1F),
|
||||||
static_cast<uint8_t>((item.item_data1w[5] >> 5) & 0x1F),
|
static_cast<uint8_t>((item.data1w[5] >> 5) & 0x1F),
|
||||||
static_cast<uint8_t>(item.item_data1w[5] & 0x1F),
|
static_cast<uint8_t>(item.data1w[5] & 0x1F),
|
||||||
};
|
};
|
||||||
const char* translation_table = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
|
const char* translation_table = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
|
||||||
|
|
||||||
@@ -1450,8 +1450,8 @@ string name_for_item(const ItemData& item, bool include_color_codes) {
|
|||||||
} else { // Not S-rank (extended name bits not set)
|
} else { // Not S-rank (extended name bits not set)
|
||||||
uint8_t percentages[5] = {0, 0, 0, 0, 0};
|
uint8_t percentages[5] = {0, 0, 0, 0, 0};
|
||||||
for (size_t x = 0; x < 3; x++) {
|
for (size_t x = 0; x < 3; x++) {
|
||||||
uint8_t which = item.item_data1[6 + 2 * x];
|
uint8_t which = item.data1[6 + 2 * x];
|
||||||
uint8_t value = item.item_data1[7 + 2 * x];
|
uint8_t value = item.data1[7 + 2 * x];
|
||||||
if (which == 0) {
|
if (which == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1466,9 +1466,9 @@ string name_for_item(const ItemData& item, bool include_color_codes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For armors, add the slots, unit modifiers, and/or DEF/EVP bonuses
|
// For armors, add the slots, unit modifiers, and/or DEF/EVP bonuses
|
||||||
} else if (item.item_data1[0] == 0x01) {
|
} else if (item.data1[0] == 0x01) {
|
||||||
if (item.item_data1[1] == 0x03) { // Units
|
if (item.data1[1] == 0x03) { // Units
|
||||||
uint16_t modifier = (item.item_data1[8] << 8) | item.item_data1[7];
|
uint16_t modifier = (item.data1[8] << 8) | item.data1[7];
|
||||||
if (modifier == 0x0001 || modifier == 0x0002) {
|
if (modifier == 0x0001 || modifier == 0x0002) {
|
||||||
ret_tokens.back().append("+");
|
ret_tokens.back().append("+");
|
||||||
} else if (modifier == 0x0003 || modifier == 0x0004) {
|
} else if (modifier == 0x0003 || modifier == 0x0004) {
|
||||||
@@ -1482,42 +1482,42 @@ string name_for_item(const ItemData& item, bool include_color_codes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else { // Armor/shields
|
} else { // Armor/shields
|
||||||
if (item.item_data1[5] > 0) {
|
if (item.data1[5] > 0) {
|
||||||
if (item.item_data1[5] == 1) {
|
if (item.data1[5] == 1) {
|
||||||
ret_tokens.emplace_back("(1 slot)");
|
ret_tokens.emplace_back("(1 slot)");
|
||||||
} else {
|
} else {
|
||||||
ret_tokens.emplace_back(string_printf("(%hhu slots)", item.item_data1[5]));
|
ret_tokens.emplace_back(string_printf("(%hhu slots)", item.data1[5]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.item_data1w[3] != 0) {
|
if (item.data1w[3] != 0) {
|
||||||
ret_tokens.emplace_back(string_printf("+%hdDEF",
|
ret_tokens.emplace_back(string_printf("+%hdDEF",
|
||||||
static_cast<int16_t>(item.item_data1w[3].load())));
|
static_cast<int16_t>(item.data1w[3].load())));
|
||||||
}
|
}
|
||||||
if (item.item_data1w[4] != 0) {
|
if (item.data1w[4] != 0) {
|
||||||
ret_tokens.emplace_back(string_printf("+%hdEVP",
|
ret_tokens.emplace_back(string_printf("+%hdEVP",
|
||||||
static_cast<int16_t>(item.item_data1w[4].load())));
|
static_cast<int16_t>(item.data1w[4].load())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For mags, add tons of info
|
// For mags, add tons of info
|
||||||
} else if (item.item_data1[0] == 0x02) {
|
} else if (item.data1[0] == 0x02) {
|
||||||
ret_tokens.emplace_back(string_printf("LV%hhu", item.item_data1[2]));
|
ret_tokens.emplace_back(string_printf("LV%hhu", item.data1[2]));
|
||||||
|
|
||||||
ret_tokens.emplace_back(string_printf("%d/%d/%d/%d",
|
ret_tokens.emplace_back(string_printf("%d/%d/%d/%d",
|
||||||
item.item_data1w[2] / 100, item.item_data1w[3] / 100,
|
item.data1w[2] / 100, item.data1w[3] / 100,
|
||||||
item.item_data1w[4] / 100, item.item_data1w[5] / 100));
|
item.data1w[4] / 100, item.data1w[5] / 100));
|
||||||
ret_tokens.emplace_back(string_printf("%hu%%", item.item_data2[3]));
|
ret_tokens.emplace_back(string_printf("%hu%%", item.data2[3]));
|
||||||
ret_tokens.emplace_back(string_printf("%huIQ", item.item_data2[2]));
|
ret_tokens.emplace_back(string_printf("%huIQ", item.data2[2]));
|
||||||
|
|
||||||
uint8_t flags = item.item_data2[1];
|
uint8_t flags = item.data2[1];
|
||||||
if (flags & 7) {
|
if (flags & 7) {
|
||||||
static const vector<const char*> pb_shortnames = {
|
static const vector<const char*> pb_shortnames = {
|
||||||
"F", "E", "G", "P", "L", "M&Y", "MG", "GR"};
|
"F", "E", "G", "P", "L", "M&Y", "MG", "GR"};
|
||||||
|
|
||||||
const char* pb_names[3] = {nullptr, nullptr, nullptr};
|
const char* pb_names[3] = {nullptr, nullptr, nullptr};
|
||||||
uint8_t center_pb = (flags & 2) ? (item.item_data1[3] & 7) : 0xFF;
|
uint8_t center_pb = (flags & 2) ? (item.data1[3] & 7) : 0xFF;
|
||||||
uint8_t right_pb = (flags & 1) ? ((item.item_data1[3] >> 3) & 7) : 0xFF;
|
uint8_t right_pb = (flags & 1) ? ((item.data1[3] >> 3) & 7) : 0xFF;
|
||||||
uint8_t left_pb = (flags & 4) ? ((item.item_data1[3] >> 6) & 3) : 0xFF;
|
uint8_t left_pb = (flags & 4) ? ((item.data1[3] >> 6) & 3) : 0xFF;
|
||||||
if (center_pb != 0xFF) {
|
if (center_pb != 0xFF) {
|
||||||
pb_names[1] = pb_shortnames[center_pb];
|
pb_names[1] = pb_shortnames[center_pb];
|
||||||
}
|
}
|
||||||
@@ -1584,16 +1584,16 @@ string name_for_item(const ItemData& item, bool include_color_codes) {
|
|||||||
"costume color",
|
"costume color",
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(item.item_data2[0])));
|
ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(item.data2[0])));
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", item.item_data2[0]));
|
ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", item.data2[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For tools, add the amount (if applicable)
|
// For tools, add the amount (if applicable)
|
||||||
} else if (item.item_data1[0] == 0x03) {
|
} else if (item.data1[0] == 0x03) {
|
||||||
if (combine_item_to_max.count(primary_identifier)) {
|
if (combine_item_to_max.count(primary_identifier)) {
|
||||||
ret_tokens.emplace_back(string_printf("x%hhu", item.item_data1[5]));
|
ret_tokens.emplace_back(string_printf("x%hhu", item.data1[5]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user