implement proper equip/unequip tracking
This commit is contained in:
+11
-3
@@ -3918,12 +3918,20 @@ struct G_TeleportPlayer_6x24 {
|
||||
} __packed__;
|
||||
|
||||
// 6x25: Equip item
|
||||
// 6x26: Unequip item
|
||||
|
||||
struct G_EquipOrUnequipItem_6x25_6x26 {
|
||||
struct G_EquipItem_6x25 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t item_id = 0;
|
||||
le_uint32_t equip_slot = 0; // Unused for 6x26 (unequip item)
|
||||
// Values here match the EquipSlot enum (in ItemData.hh)
|
||||
le_uint32_t equip_slot = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x26: Unequip item
|
||||
|
||||
struct G_UnequipItem_6x26 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t item_id = 0;
|
||||
le_uint32_t unused = 0;
|
||||
} __packed__;
|
||||
|
||||
// 6x27: Use item
|
||||
|
||||
@@ -567,6 +567,26 @@ bool ItemData::is_s_rank_weapon() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ItemData::can_be_equipped_in_slot(EquipSlot slot) const {
|
||||
switch (slot) {
|
||||
case EquipSlot::MAG:
|
||||
return (this->data1[0] == 0x02);
|
||||
case EquipSlot::ARMOR:
|
||||
return ((this->data1[0] == 0x01) && (this->data1[1] == 0x01));
|
||||
case EquipSlot::SHIELD:
|
||||
return ((this->data1[0] == 0x01) && (this->data1[1] == 0x02));
|
||||
case EquipSlot::UNIT_1:
|
||||
case EquipSlot::UNIT_2:
|
||||
case EquipSlot::UNIT_3:
|
||||
case EquipSlot::UNIT_4:
|
||||
return ((this->data1[0] == 0x01) && (this->data1[1] == 0x03));
|
||||
case EquipSlot::WEAPON:
|
||||
return (this->data1[0] == 0x00);
|
||||
default:
|
||||
throw runtime_error("invalid equip slot");
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemData::compare_for_sort(const ItemData& a, const ItemData& b) {
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (a.data1[z] < b.data1[z]) {
|
||||
|
||||
+14
-1
@@ -6,10 +6,21 @@
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
constexpr uint32_t MESETA_IDENTIFIER = 0x00040000;
|
||||
constexpr uint32_t MESETA_IDENTIFIER = 0x040000;
|
||||
|
||||
class ItemParameterTable;
|
||||
|
||||
enum class EquipSlot {
|
||||
MAG = 0x01,
|
||||
ARMOR = 0x02,
|
||||
SHIELD = 0x03,
|
||||
WEAPON = 0x06,
|
||||
UNIT_1 = 0x09,
|
||||
UNIT_2 = 0x0A,
|
||||
UNIT_3 = 0x0B,
|
||||
UNIT_4 = 0x0C,
|
||||
};
|
||||
|
||||
struct ItemMagStats {
|
||||
uint16_t iq;
|
||||
uint16_t synchro;
|
||||
@@ -152,6 +163,8 @@ struct ItemData { // 0x14 bytes
|
||||
bool has_bonuses() const;
|
||||
bool is_s_rank_weapon() const;
|
||||
|
||||
bool can_be_equipped_in_slot(EquipSlot slot) const;
|
||||
|
||||
bool empty() const;
|
||||
|
||||
static bool compare_for_sort(const ItemData& a, const ItemData& b);
|
||||
|
||||
+8
-8
@@ -36,7 +36,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
throw runtime_error("incorrect grinder value");
|
||||
}
|
||||
|
||||
auto& weapon = player->inventory.items[player->inventory.find_equipped_weapon()];
|
||||
auto& weapon = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::WEAPON)];
|
||||
// Don't enforce the weapon's grind limit on V1 and V2. This is necessary
|
||||
// because the V2 client replaces its inventory items on the fly with items
|
||||
// compatible with V1 when sending the 61 and 98 commands. There appears to
|
||||
@@ -107,7 +107,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
}
|
||||
|
||||
} else if ((item_identifier & 0xFFFF00) == 0x030F00) { // AddSlot
|
||||
auto& armor = player->inventory.items[player->inventory.find_equipped_armor()];
|
||||
auto& armor = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::ARMOR)];
|
||||
if (armor.data.data1[5] >= 4) {
|
||||
throw runtime_error("armor already at maximum slot count");
|
||||
}
|
||||
@@ -140,32 +140,32 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
|
||||
} else if (item_identifier == 0x030C00) {
|
||||
// Cell of MAG 502
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
|
||||
mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x1D : 0x21;
|
||||
|
||||
} else if (item_identifier == 0x030C01) {
|
||||
// Cell of MAG 213
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
|
||||
mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x27 : 0x22;
|
||||
|
||||
} else if (item_identifier == 0x030C02) {
|
||||
// Parts of RoboChao
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
|
||||
mag.data.data1[1] = 0x28;
|
||||
|
||||
} else if (item_identifier == 0x030C03) {
|
||||
// Heart of Opa Opa
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
|
||||
mag.data.data1[1] = 0x29;
|
||||
|
||||
} else if (item_identifier == 0x030C04) {
|
||||
// Heart of Pian
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
|
||||
mag.data.data1[1] = 0x2A;
|
||||
|
||||
} else if (item_identifier == 0x030C05) {
|
||||
// Heart of Chao
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
|
||||
mag.data.data1[1] = 0x2B;
|
||||
|
||||
} else if ((item_identifier & 0xFFFF00) == 0x031500) {
|
||||
|
||||
+57
-40
@@ -525,67 +525,84 @@ size_t PlayerInventory::find_item_by_primary_identifier(uint32_t primary_identif
|
||||
throw out_of_range("item not present");
|
||||
}
|
||||
|
||||
size_t PlayerInventory::find_equipped_weapon() const {
|
||||
size_t PlayerInventory::find_equipped_item(EquipSlot slot) const {
|
||||
ssize_t ret = -1;
|
||||
for (size_t y = 0; y < this->num_items; y++) {
|
||||
if (!(this->items[y].flags & 0x00000008)) {
|
||||
const auto& i = this->items[y];
|
||||
if (!(i.flags & 0x00000008)) {
|
||||
continue;
|
||||
}
|
||||
if (this->items[y].data.data1[0] != 0) {
|
||||
if (!i.data.can_be_equipped_in_slot(slot)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Units can be equipped in multiple slots, so the currently-equipped slot
|
||||
// is stored in the item data itself.
|
||||
if (((slot == EquipSlot::UNIT_1) && (i.data.data1[4] != 0x00)) ||
|
||||
((slot == EquipSlot::UNIT_2) && (i.data.data1[4] != 0x01)) ||
|
||||
((slot == EquipSlot::UNIT_3) && (i.data.data1[4] != 0x02)) ||
|
||||
((slot == EquipSlot::UNIT_4) && (i.data.data1[4] != 0x03))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
ret = y;
|
||||
} else {
|
||||
throw runtime_error("multiple weapons are equipped");
|
||||
throw runtime_error("multiple items are equipped in the same slot");
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
throw out_of_range("no weapon is equipped");
|
||||
throw out_of_range("no item is equipped in this slot");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PlayerInventory::find_equipped_armor() const {
|
||||
ssize_t ret = -1;
|
||||
for (size_t y = 0; y < this->num_items; y++) {
|
||||
if (!(this->items[y].flags & 0x00000008)) {
|
||||
continue;
|
||||
}
|
||||
if (this->items[y].data.data1[0] != 1 || this->items[y].data.data1[1] != 1) {
|
||||
continue;
|
||||
}
|
||||
if (ret < 0) {
|
||||
ret = y;
|
||||
} else {
|
||||
throw runtime_error("multiple armors are equipped");
|
||||
}
|
||||
bool PlayerInventory::has_equipped_item(EquipSlot slot) const {
|
||||
try {
|
||||
this->find_equipped_item(slot);
|
||||
return true;
|
||||
} catch (const out_of_range&) {
|
||||
return false;
|
||||
}
|
||||
if (ret < 0) {
|
||||
throw out_of_range("no armor is equipped");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PlayerInventory::find_equipped_mag() const {
|
||||
ssize_t ret = -1;
|
||||
for (size_t y = 0; y < this->num_items; y++) {
|
||||
if (!(this->items[y].flags & 0x00000008)) {
|
||||
continue;
|
||||
}
|
||||
if (this->items[y].data.data1[0] != 2) {
|
||||
continue;
|
||||
}
|
||||
if (ret < 0) {
|
||||
ret = y;
|
||||
} else {
|
||||
throw runtime_error("multiple mags are equipped");
|
||||
void PlayerInventory::equip_item(uint32_t item_id, EquipSlot slot) {
|
||||
size_t index = this->find_item(item_id);
|
||||
auto& item = this->items[index];
|
||||
|
||||
if (!item.data.can_be_equipped_in_slot(slot)) {
|
||||
throw runtime_error("incorrect item type for equip slot");
|
||||
}
|
||||
if (this->has_equipped_item(slot)) {
|
||||
throw runtime_error("equip slot is already in use");
|
||||
}
|
||||
|
||||
item.flags |= 0x00000008;
|
||||
// Units store which slot they're equipped in within the item data itself
|
||||
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) {
|
||||
item.data.data1[4] = static_cast<uint8_t>(slot) - 9;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerInventory::unequip_item(uint32_t item_id) {
|
||||
size_t index = this->find_item(item_id);
|
||||
auto& item = this->items[index];
|
||||
|
||||
item.flags &= (~0x00000008);
|
||||
// Units store which slot they're equipped in within the item data itself
|
||||
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) {
|
||||
item.data.data1[4] = 0x00;
|
||||
}
|
||||
// If the item is an armor, remove all units too
|
||||
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x01)) {
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
auto& unit = this->items[z];
|
||||
if ((unit.data.data1[0] == 0x01) && (unit.data.data1[1] == 0x03)) {
|
||||
unit.flags &= (~0x00000008);
|
||||
unit.data.data1[4] = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
throw out_of_range("no mag is equipped");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PlayerInventory::remove_all_items_of_type(uint8_t data1_0, int16_t data1_1) {
|
||||
|
||||
@@ -71,9 +71,10 @@ struct PlayerInventory {
|
||||
size_t find_item(uint32_t item_id) const;
|
||||
size_t find_item_by_primary_identifier(uint32_t primary_identifier) const;
|
||||
|
||||
size_t find_equipped_weapon() const;
|
||||
size_t find_equipped_armor() const;
|
||||
size_t find_equipped_mag() const;
|
||||
size_t find_equipped_item(EquipSlot slot) const;
|
||||
bool has_equipped_item(EquipSlot slot) const;
|
||||
void equip_item(uint32_t item_id, EquipSlot slot);
|
||||
void unequip_item(uint32_t item_id);
|
||||
|
||||
size_t remove_all_items_of_type(uint8_t data0, int16_t data1 = -1);
|
||||
|
||||
|
||||
+26
-12
@@ -625,7 +625,7 @@ static void on_player_died(shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||
try {
|
||||
auto& inventory = c->game_data.character()->inventory;
|
||||
size_t mag_index = inventory.find_equipped_mag();
|
||||
size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG);
|
||||
auto& data = inventory.items[mag_index].data;
|
||||
data.data2[0] = max<int8_t>(static_cast<int8_t>(data.data2[0] - 5), 0);
|
||||
} catch (const out_of_range&) {
|
||||
@@ -1146,8 +1146,27 @@ static void on_pick_up_item_request(shared_ptr<Client> c, uint8_t command, uint8
|
||||
}
|
||||
}
|
||||
|
||||
static void on_equip_unequip_item(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_EquipOrUnequipItem_6x25_6x26>(data, size);
|
||||
static void on_equip_item(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_EquipItem_6x25>(data, size);
|
||||
|
||||
if (cmd.header.client_id != c->lobby_client_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto l = c->require_lobby();
|
||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||
EquipSlot slot = static_cast<EquipSlot>(cmd.equip_slot.load());
|
||||
auto p = c->game_data.character();
|
||||
p->inventory.equip_item(cmd.item_id, slot);
|
||||
} else if (l->base_version == GameVersion::BB) {
|
||||
throw logic_error("item tracking not enabled in BB game");
|
||||
}
|
||||
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_unequip_item(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_UnequipItem_6x26>(data, size);
|
||||
|
||||
if (cmd.header.client_id != c->lobby_client_id) {
|
||||
return;
|
||||
@@ -1156,12 +1175,7 @@ static void on_equip_unequip_item(shared_ptr<Client> c, uint8_t command, uint8_t
|
||||
auto l = c->require_lobby();
|
||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||
auto p = c->game_data.character();
|
||||
size_t index = p->inventory.find_item(cmd.item_id);
|
||||
if (cmd.header.subcommand == 0x25) { // Equip
|
||||
p->inventory.items[index].flags |= 0x00000008;
|
||||
} else { // Unequip
|
||||
p->inventory.items[index].flags &= 0xFFFFFFF7;
|
||||
}
|
||||
p->inventory.unequip_item(cmd.item_id);
|
||||
} else if (l->base_version == GameVersion::BB) {
|
||||
throw logic_error("item tracking not enabled in BB game");
|
||||
}
|
||||
@@ -1833,7 +1847,7 @@ static void on_steal_exp_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*
|
||||
auto p = c->game_data.character();
|
||||
const auto& enemy = l->map->enemies.at(cmd.enemy_index);
|
||||
const auto& inventory = p->inventory;
|
||||
const auto& weapon = inventory.items[inventory.find_equipped_weapon()];
|
||||
const auto& weapon = inventory.items[inventory.find_equipped_item(EquipSlot::WEAPON)];
|
||||
|
||||
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
|
||||
|
||||
@@ -2524,8 +2538,8 @@ subcommand_handler_t subcommand_handlers[0x100] = {
|
||||
/* 6x22 */ on_forward_check_size_client,
|
||||
/* 6x23 */ on_set_player_visibility,
|
||||
/* 6x24 */ on_forward_check_size_game,
|
||||
/* 6x25 */ on_equip_unequip_item,
|
||||
/* 6x26 */ on_equip_unequip_item,
|
||||
/* 6x25 */ on_equip_item,
|
||||
/* 6x26 */ on_unequip_item,
|
||||
/* 6x27 */ on_use_item,
|
||||
/* 6x28 */ on_feed_mag,
|
||||
/* 6x29 */ on_destroy_inventory_item,
|
||||
|
||||
Reference in New Issue
Block a user