mirror of
https://github.com/redstrate/Kawari.git
synced 2025-07-23 21:17:45 +00:00
Implement basic support for item levels and start working on migrating to using Item instead of BuyBackItem (#122)
This commit is contained in:
parent
747844e07e
commit
49f7b584b9
11 changed files with 258 additions and 47 deletions
|
@ -166,7 +166,12 @@ async fn client_loop(
|
|||
|
||||
// initialize player data if it doesn't exist'
|
||||
if connection.player_data.actor_id == 0 {
|
||||
connection.player_data = database.find_player_data(actor_id);
|
||||
let player_data;
|
||||
{
|
||||
let mut game_data = connection.gamedata.lock().unwrap();
|
||||
player_data = database.find_player_data(actor_id, &mut game_data);
|
||||
}
|
||||
connection.player_data = player_data;
|
||||
}
|
||||
|
||||
if connection_type == ConnectionType::Zone {
|
||||
|
@ -327,6 +332,12 @@ async fn client_loop(
|
|||
.await;
|
||||
}
|
||||
|
||||
connection.actor_control_self(ActorControlSelf {
|
||||
category: ActorControlCategory::SetItemLevel {
|
||||
level: connection.player_data.inventory.equipped.calculate_item_level() as u32,
|
||||
}
|
||||
}).await;
|
||||
|
||||
connection.send_quest_information().await;
|
||||
|
||||
let zone_id = connection.player_data.zone_id;
|
||||
|
@ -806,6 +817,11 @@ async fn client_loop(
|
|||
// if updated equipped items, we have to process that
|
||||
if action.src_storage_id == ContainerType::Equipped || action.dst_storage_id == ContainerType::Equipped {
|
||||
connection.inform_equip().await;
|
||||
connection.actor_control_self(ActorControlSelf {
|
||||
category: ActorControlCategory::SetItemLevel {
|
||||
level: connection.player_data.inventory.equipped.calculate_item_level() as u32,
|
||||
}
|
||||
}).await;
|
||||
}
|
||||
|
||||
if action.operation_type == ItemOperationKind::Discard {
|
||||
|
@ -859,7 +875,7 @@ async fn client_loop(
|
|||
|
||||
if let Some(item_info) = result {
|
||||
if connection.player_data.inventory.currency.gil.quantity >= *item_quantity * item_info.price_mid {
|
||||
if let Some(add_result) = connection.player_data.inventory.add_in_next_free_slot(Item::new(*item_quantity, item_info.id), item_info.stack_size) {
|
||||
if let Some(add_result) = connection.player_data.inventory.add_in_next_free_slot(Item::new(item_info.clone(), *item_quantity)) {
|
||||
connection.player_data.inventory.currency.gil.quantity -= *item_quantity * item_info.price_mid;
|
||||
connection.send_gilshop_item_update(ContainerType::Currency as u16, 0, connection.player_data.inventory.currency.gil.quantity, CurrencyKind::Gil as u32).await;
|
||||
|
||||
|
@ -901,6 +917,7 @@ async fn client_loop(
|
|||
id: item_info.id,
|
||||
quantity,
|
||||
price_low: item_info.price_low,
|
||||
item_level: item_info.item_level,
|
||||
stack_size: item_info.stack_size,
|
||||
};
|
||||
connection.player_data.buyback_list.push_item(*event_id, bb_item);
|
||||
|
|
|
@ -37,7 +37,7 @@ impl Default for GameData {
|
|||
}
|
||||
|
||||
/// Struct detailing various information about an item, pulled from the Items sheet.
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ItemInfo {
|
||||
/// The item's textual name.
|
||||
pub name: String,
|
||||
|
@ -53,6 +53,8 @@ pub struct ItemInfo {
|
|||
pub primary_model_id: u64,
|
||||
/// The item's max stack size.
|
||||
pub stack_size: u32,
|
||||
/// The item's item level.
|
||||
pub item_level: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -190,6 +192,10 @@ impl GameData {
|
|||
panic!("Unexpected type!");
|
||||
};
|
||||
|
||||
let physis::exd::ColumnData::UInt16(item_level) = &matched_row.columns[11] else {
|
||||
panic!("Unexpected type!");
|
||||
};
|
||||
|
||||
let physis::exd::ColumnData::UInt8(equip_category) = &matched_row.columns[17] else {
|
||||
panic!("Unexpected type!");
|
||||
};
|
||||
|
@ -218,6 +224,7 @@ impl GameData {
|
|||
equip_category: *equip_category,
|
||||
primary_model_id: *primary_model_id,
|
||||
stack_size: *stack_size,
|
||||
item_level: *item_level,
|
||||
};
|
||||
|
||||
return Some(item_info);
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::collections::{HashMap, VecDeque};
|
|||
const BUYBACK_LIST_SIZE: usize = 10;
|
||||
const BUYBACK_PARAM_COUNT: usize = 22;
|
||||
|
||||
// TODO: Deprecate this type, Item can now be expanded to support everything we'll need
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct BuyBackItem {
|
||||
pub id: u32,
|
||||
|
@ -11,6 +12,7 @@ pub struct BuyBackItem {
|
|||
// TODO: there are 22 total things the server keeps track of and sends back to the client, we should implement these!
|
||||
// Not every value is not fully understood but they appeared to be related to item quality, materia melds, the crafter's name (if applicable), spiritbond/durability, and maybe more.
|
||||
/// Fields beyond this comment are not part of the 22 datapoints the server sends to the client, but we need them for later item restoration.
|
||||
pub item_level: u16,
|
||||
pub stack_size: u32,
|
||||
}
|
||||
|
||||
|
@ -76,3 +78,16 @@ impl BuyBackList {
|
|||
params
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Once BBItem is deprecated, remove this. This is a transitional impl as we migrate to using Item.
|
||||
impl BuyBackItem {
|
||||
pub fn as_item_info(&self) -> crate::common::ItemInfo {
|
||||
crate::common::ItemInfo {
|
||||
id: self.id,
|
||||
item_level: self.item_level,
|
||||
stack_size: self.stack_size,
|
||||
price_low: self.price_low,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use crate::common::ItemInfo;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Item, Storage};
|
||||
|
@ -39,6 +41,17 @@ pub enum CurrencyKind {
|
|||
TrophyCrystal = 36656,
|
||||
}
|
||||
|
||||
// TODO: Should we just pull this from the Item sheet?
|
||||
// Otherwise, should we not use Default and instead use new with a GameData parameter?
|
||||
pub enum CurrencyStack {
|
||||
_CompanySeal = 90000,
|
||||
_ElementalCrystal = 9999,
|
||||
Gil = 999_999_999,
|
||||
_MGP = 9_999_999,
|
||||
_Tomestone = 2000,
|
||||
_Pvp = 20000,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Deserialize, Serialize, Debug)]
|
||||
pub struct CurrencyStorage {
|
||||
pub gil: Item,
|
||||
|
@ -47,7 +60,14 @@ pub struct CurrencyStorage {
|
|||
impl Default for CurrencyStorage {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gil: Item::new(0, CurrencyKind::Gil as u32),
|
||||
gil: Item::new(
|
||||
ItemInfo {
|
||||
id: CurrencyKind::Gil as u32,
|
||||
stack_size: CurrencyStack::Gil as u32,
|
||||
..Default::default()
|
||||
},
|
||||
0,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,39 @@ pub struct EquippedStorage {
|
|||
pub soul_crystal: Item,
|
||||
}
|
||||
|
||||
impl EquippedStorage {
|
||||
/// Calculates the player's item level.
|
||||
/// TODO: This is not accurate, for several reasons.
|
||||
/// First, it does not take into account if the main hand is a one or two hander.
|
||||
/// Second, it does not take into account if body armour occupies multiple slots or not (e.g. Herklaedi: cannot equip anything to hands, legs, or feet).
|
||||
/// There is currently no known way of properly figuring those out. Presumably, the information is somewhere in the Items sheet.
|
||||
pub fn calculate_item_level(&self) -> u16 {
|
||||
const DIVISOR: u16 = 13;
|
||||
const INDEX_BELT: u32 = 5;
|
||||
const INDEX_SOUL_CRYSTAL: u32 = 13;
|
||||
|
||||
let mut level = self.main_hand.item_level;
|
||||
|
||||
if !self.off_hand.is_empty_slot() {
|
||||
level += self.off_hand.item_level;
|
||||
} else {
|
||||
// Main hand counts twice if off hand is empty. See comments above why this isn't always correct.
|
||||
level += self.main_hand.item_level;
|
||||
}
|
||||
|
||||
for index in 2..self.max_slots() {
|
||||
if index == INDEX_BELT || index == INDEX_SOUL_CRYSTAL {
|
||||
continue;
|
||||
}
|
||||
|
||||
let item = self.get_slot(index as u16);
|
||||
level += item.item_level;
|
||||
}
|
||||
|
||||
std::cmp::min(level / DIVISOR, 9999)
|
||||
}
|
||||
}
|
||||
|
||||
impl Storage for EquippedStorage {
|
||||
fn max_slots(&self) -> u32 {
|
||||
14
|
||||
|
@ -48,6 +81,7 @@ impl Storage for EquippedStorage {
|
|||
2 => &mut self.head,
|
||||
3 => &mut self.body,
|
||||
4 => &mut self.hands,
|
||||
5 => &mut self.belt,
|
||||
6 => &mut self.legs,
|
||||
7 => &mut self.feet,
|
||||
8 => &mut self.ears,
|
||||
|
|
|
@ -1,21 +1,32 @@
|
|||
use crate::common::ItemInfo;
|
||||
|
||||
use crate::ITEM_CONDITION_MAX;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents an item, or if the quanity is zero an empty slot.
|
||||
/// Represents an item, or if the quantity is zero, an empty slot.
|
||||
#[derive(Default, Copy, Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct Item {
|
||||
pub quantity: u32,
|
||||
pub id: u32,
|
||||
pub condition: u16,
|
||||
pub glamour_catalog_id: u32,
|
||||
#[serde(skip)]
|
||||
pub item_level: u16,
|
||||
#[serde(skip)]
|
||||
pub stack_size: u32,
|
||||
#[serde(skip)]
|
||||
pub price_low: u32,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn new(quantity: u32, id: u32) -> Self {
|
||||
pub fn new(item_info: ItemInfo, quantity: u32) -> Self {
|
||||
Self {
|
||||
quantity,
|
||||
id,
|
||||
id: item_info.id,
|
||||
condition: ITEM_CONDITION_MAX,
|
||||
item_level: item_info.item_level,
|
||||
stack_size: item_info.stack_size,
|
||||
price_low: item_info.price_low,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -27,4 +38,8 @@ impl Item {
|
|||
}
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn is_empty_slot(&self) -> bool {
|
||||
self.quantity == 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::GameData;
|
||||
use crate::common::{GameData, ItemInfoQuery};
|
||||
use binrw::binrw;
|
||||
use icarus::{ClassJob::ClassJobSheet, Race::RaceSheet};
|
||||
use physis::common::Language;
|
||||
|
@ -171,7 +171,9 @@ pub fn get_container_type(container_index: u32) -> Option<ContainerType> {
|
|||
|
||||
// currency
|
||||
17 => Some(ContainerType::Currency),
|
||||
_ => panic!("Inventory iterator invalid or the client sent a very weird packet!"),
|
||||
_ => panic!(
|
||||
"Inventory iterator invalid or the client sent a very weird packet! {container_index}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,15 +203,45 @@ impl Inventory {
|
|||
let sheet = ClassJobSheet::read_from(&mut game_data.resource, Language::English).unwrap();
|
||||
let row = sheet.get_row(classjob_id as u32).unwrap();
|
||||
|
||||
self.equipped.main_hand =
|
||||
Item::new(1, *row.ItemStartingWeapon().into_i32().unwrap() as u32);
|
||||
let main_hand_id = *row.ItemStartingWeapon().into_i32().unwrap() as u32;
|
||||
self.equipped.main_hand = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(main_hand_id))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
|
||||
// TODO: don't hardcode
|
||||
self.equipped.ears = Item::new(1, 0x00003b1b);
|
||||
self.equipped.neck = Item::new(1, 0x00003b1a);
|
||||
self.equipped.wrists = Item::new(1, 0x00003b1c);
|
||||
self.equipped.right_ring = Item::new(1, 0x0000114a);
|
||||
self.equipped.left_ring = Item::new(1, 0x00003b1d);
|
||||
self.equipped.ears = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(0x3b1b))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
self.equipped.neck = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(0x3b1a))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
self.equipped.wrists = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(0x3b1c))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
self.equipped.right_ring = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(0x114a))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
self.equipped.left_ring = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(0x3b1d))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
/// Equip the starting items for a given race
|
||||
|
@ -217,17 +249,46 @@ impl Inventory {
|
|||
let sheet = RaceSheet::read_from(&mut game_data.resource, Language::English).unwrap();
|
||||
let row = sheet.get_row(race_id as u32).unwrap();
|
||||
|
||||
if gender == 0 {
|
||||
self.equipped.body = Item::new(1, *row.RSEMBody().into_i32().unwrap() as u32);
|
||||
self.equipped.hands = Item::new(1, *row.RSEMHands().into_i32().unwrap() as u32);
|
||||
self.equipped.legs = Item::new(1, *row.RSEMLegs().into_i32().unwrap() as u32);
|
||||
self.equipped.feet = Item::new(1, *row.RSEMFeet().into_i32().unwrap() as u32);
|
||||
let ids: Vec<u32> = if gender == 0 {
|
||||
vec![
|
||||
*row.RSEMBody().into_i32().unwrap() as u32,
|
||||
*row.RSEMHands().into_i32().unwrap() as u32,
|
||||
*row.RSEMLegs().into_i32().unwrap() as u32,
|
||||
*row.RSEMFeet().into_i32().unwrap() as u32,
|
||||
]
|
||||
} else {
|
||||
self.equipped.body = Item::new(1, *row.RSEFBody().into_i32().unwrap() as u32);
|
||||
self.equipped.hands = Item::new(1, *row.RSEFHands().into_i32().unwrap() as u32);
|
||||
self.equipped.legs = Item::new(1, *row.RSEFLegs().into_i32().unwrap() as u32);
|
||||
self.equipped.feet = Item::new(1, *row.RSEFFeet().into_i32().unwrap() as u32);
|
||||
}
|
||||
vec![
|
||||
*row.RSEFBody().into_i32().unwrap() as u32,
|
||||
*row.RSEFHands().into_i32().unwrap() as u32,
|
||||
*row.RSEFLegs().into_i32().unwrap() as u32,
|
||||
*row.RSEFFeet().into_i32().unwrap() as u32,
|
||||
]
|
||||
};
|
||||
|
||||
self.equipped.body = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(ids[0]))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
self.equipped.hands = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(ids[1]))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
self.equipped.legs = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(ids[2]))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
self.equipped.feet = Item::new(
|
||||
game_data
|
||||
.get_item_info(ItemInfoQuery::ById(ids[3]))
|
||||
.unwrap(),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper functions to reduce boilerplate
|
||||
|
@ -313,15 +374,11 @@ impl Inventory {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn add_in_next_free_slot(
|
||||
&mut self,
|
||||
item: Item,
|
||||
stack_size: u32,
|
||||
) -> Option<ItemDestinationInfo> {
|
||||
if stack_size > 1 {
|
||||
pub fn add_in_next_free_slot(&mut self, item: Item) -> Option<ItemDestinationInfo> {
|
||||
if item.stack_size > 1 {
|
||||
for page in &mut self.pages {
|
||||
for (slot_index, slot) in page.slots.iter_mut().enumerate() {
|
||||
if slot.id == item.id && slot.quantity + item.quantity <= stack_size {
|
||||
if slot.id == item.id && slot.quantity + item.quantity <= item.stack_size {
|
||||
slot.quantity += item.quantity;
|
||||
return Some(ItemDestinationInfo {
|
||||
container: page.kind,
|
||||
|
|
|
@ -87,10 +87,10 @@ impl ChatHandler {
|
|||
if let Some(item_info) =
|
||||
gamedata.get_item_info(ItemInfoQuery::ByName(name.to_string()))
|
||||
{
|
||||
result = connection.player_data.inventory.add_in_next_free_slot(
|
||||
Item::new(1, item_info.id),
|
||||
item_info.stack_size,
|
||||
);
|
||||
result = connection
|
||||
.player_data
|
||||
.inventory
|
||||
.add_in_next_free_slot(Item::new(item_info, 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -478,6 +478,13 @@ impl ZoneConnection {
|
|||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
self.actor_control_self(ActorControlSelf {
|
||||
category: ActorControlCategory::SetItemLevel {
|
||||
level: self.player_data.inventory.equipped.calculate_item_level() as u32,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn warp(&mut self, warp_id: u32) {
|
||||
|
@ -929,10 +936,7 @@ impl ZoneConnection {
|
|||
if self
|
||||
.player_data
|
||||
.inventory
|
||||
.add_in_next_free_slot(
|
||||
Item::new(*quantity, *id),
|
||||
item_info.unwrap().stack_size,
|
||||
)
|
||||
.add_in_next_free_slot(Item::new(item_info.unwrap(), *quantity))
|
||||
.is_some()
|
||||
{
|
||||
if *send_client_update {
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
AETHERYTE_UNLOCK_BITMASK_SIZE, CLASSJOB_ARRAY_SIZE, COMPLETED_QUEST_BITMASK_SIZE,
|
||||
UNLOCK_BITMASK_SIZE,
|
||||
common::{
|
||||
CustomizeData, GameData, Position,
|
||||
CustomizeData, GameData, ItemInfoQuery, Position,
|
||||
workdefinitions::{CharaMake, ClientSelectData, RemakeMode},
|
||||
},
|
||||
inventory::{Inventory, Item, Storage},
|
||||
|
@ -182,7 +182,7 @@ impl WorldDatabase {
|
|||
game_data,
|
||||
);
|
||||
|
||||
let mut player_data = self.find_player_data(actor_id);
|
||||
let mut player_data = self.find_player_data(actor_id, game_data);
|
||||
|
||||
// import jobs
|
||||
for classjob in &character.classjob_levels {
|
||||
|
@ -208,6 +208,7 @@ impl WorldDatabase {
|
|||
id: item.id,
|
||||
condition: item.condition,
|
||||
glamour_catalog_id: item.glamour_id,
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -278,7 +279,7 @@ impl WorldDatabase {
|
|||
tracing::info!("{} added to the world!", character.name);
|
||||
}
|
||||
|
||||
pub fn find_player_data(&self, actor_id: u32) -> PlayerData {
|
||||
pub fn find_player_data(&self, actor_id: u32, game_data: &mut GameData) -> PlayerData {
|
||||
let connection = self.connection.lock().unwrap();
|
||||
|
||||
let mut stmt = connection
|
||||
|
@ -291,7 +292,7 @@ impl WorldDatabase {
|
|||
stmt = connection
|
||||
.prepare("SELECT pos_x, pos_y, pos_z, rotation, zone_id, inventory, gm_rank, classjob_id, classjob_levels, classjob_exp, unlocks, aetherytes, completed_quests FROM character_data WHERE content_id = ?1")
|
||||
.unwrap();
|
||||
let player_data: PlayerData = stmt
|
||||
let mut player_data: PlayerData = stmt
|
||||
.query_row((content_id,), |row| {
|
||||
Ok(PlayerData {
|
||||
actor_id,
|
||||
|
@ -317,9 +318,50 @@ impl WorldDatabase {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
// Before we're finished, we need to populate the items in the inventory with additional static information that we don't bother caching in the db.
|
||||
self.prepare_player_inventory(&mut player_data.inventory, game_data);
|
||||
|
||||
player_data
|
||||
}
|
||||
|
||||
// TODO: Should this and prepare_player_inventory be instead placed somewhere in the inventory modules?
|
||||
fn prepare_items_in_container(&self, container: &mut impl Storage, data: &mut GameData) {
|
||||
for index in 0..container.max_slots() {
|
||||
let item = container.get_slot_mut(index as u16);
|
||||
|
||||
if item.is_empty_slot() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(info) = data.get_item_info(ItemInfoQuery::ById(item.id)) {
|
||||
item.item_level = info.item_level;
|
||||
item.stack_size = info.stack_size;
|
||||
item.price_low = info.price_low;
|
||||
// TODO: There will be much more in the future.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_player_inventory(&self, inventory: &mut Inventory, data: &mut GameData) {
|
||||
// TODO: implement iter_mut for Inventory so all of this can be reduced down
|
||||
for index in 0..inventory.pages.len() {
|
||||
self.prepare_items_in_container(&mut inventory.pages[index], data);
|
||||
}
|
||||
|
||||
self.prepare_items_in_container(&mut inventory.equipped, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_main_hand, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_body, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_hands, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_legs, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_feet, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_off_hand, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_earring, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_necklace, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_bracelet, data);
|
||||
self.prepare_items_in_container(&mut inventory.armoury_rings, data);
|
||||
// Skip soul crystals
|
||||
}
|
||||
|
||||
/// Commit the dynamic player data back to the database
|
||||
pub fn commit_player_data(&self, data: &PlayerData) {
|
||||
let connection = self.connection.lock().unwrap();
|
||||
|
|
|
@ -365,11 +365,11 @@ impl LuaPlayer {
|
|||
|
||||
// This is a no-op since we can't edit PlayerData from the Lua side, but we can queue it up afterward.
|
||||
// We *need* this information, though.
|
||||
let item_to_restore = Item::new(bb_item.quantity, bb_item.id);
|
||||
let item_to_restore = Item::new(bb_item.as_item_info(), bb_item.quantity);
|
||||
let Some(item_dst_info) = self
|
||||
.player_data
|
||||
.inventory
|
||||
.add_in_next_free_slot(item_to_restore, bb_item.stack_size)
|
||||
.add_in_next_free_slot(item_to_restore)
|
||||
else {
|
||||
let error = "Your inventory is full. Unable to restore item.";
|
||||
self.send_message(error, 0);
|
||||
|
|
Loading…
Add table
Reference in a new issue