mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-20 06:37:45 +00:00
Add more inventory management
Instead of one single slot available in your inventory, all four pages should be available now. Moving items around should be less buggy, and it's now possible to discard items. Items cannot stack still, and when given will always take up the next free slot.
This commit is contained in:
parent
6d1e9d4e73
commit
216778ea8b
6 changed files with 143 additions and 58 deletions
|
@ -18,8 +18,8 @@ use kawari::world::ipc::{
|
|||
GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
|
||||
};
|
||||
use kawari::world::{
|
||||
Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, LuaPlayer, PlayerData,
|
||||
ServerHandle, StatusEffects, ToServer, WorldDatabase,
|
||||
Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, Item, LuaPlayer,
|
||||
PlayerData, ServerHandle, StatusEffects, ToServer, WorldDatabase,
|
||||
};
|
||||
use kawari::world::{
|
||||
ChatHandler, Zone, ZoneConnection,
|
||||
|
@ -615,8 +615,7 @@ async fn client_loop(
|
|||
})
|
||||
.await,
|
||||
GameMasterCommandType::GiveItem => {
|
||||
connection.player_data.inventory.extra_slot.id = *arg;
|
||||
connection.player_data.inventory.extra_slot.quantity = 1;
|
||||
connection.player_data.inventory.add_in_next_free_slot(Item { id: *arg, quantity: 1 });
|
||||
connection.send_inventory(false).await;
|
||||
}
|
||||
}
|
||||
|
@ -816,7 +815,7 @@ async fn client_loop(
|
|||
game_data.get_citystate(chara_make.classjob_id as u16);
|
||||
}
|
||||
|
||||
let mut inventory = Inventory::new();
|
||||
let mut inventory = Inventory::default();
|
||||
|
||||
// fill inventory
|
||||
inventory.equip_racial_items(
|
||||
|
@ -1002,7 +1001,7 @@ async fn client_loop(
|
|||
connection.process_effects_list().await;
|
||||
|
||||
// update lua player
|
||||
lua_player.player_data = connection.player_data;
|
||||
lua_player.player_data = connection.player_data.clone();
|
||||
lua_player.status_effects = connection.status_effects.clone();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -322,7 +322,7 @@ impl ChatHandler {
|
|||
"!spawnclone" => {
|
||||
// spawn another one of us
|
||||
|
||||
let player = connection.player_data;
|
||||
let player = &connection.player_data;
|
||||
|
||||
let mut common = connection
|
||||
.get_player_common_spawn(Some(player.position), Some(player.rotation));
|
||||
|
|
|
@ -19,6 +19,7 @@ use crate::{
|
|||
|
||||
use super::{
|
||||
Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone,
|
||||
inventory::Container,
|
||||
ipc::{
|
||||
ActorControlSelf, ActorMove, ActorSetPos, ClientZoneIpcSegment, CommonSpawn, ContainerInfo,
|
||||
ContainerType, DisplayFlag, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind, PlayerSubKind,
|
||||
|
@ -27,7 +28,7 @@ use super::{
|
|||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PlayerData {
|
||||
// Static data
|
||||
pub actor_id: u32,
|
||||
|
@ -428,16 +429,25 @@ impl ZoneConnection {
|
|||
}
|
||||
|
||||
pub async fn send_inventory(&mut self, send_appearance_update: bool) {
|
||||
// page 1
|
||||
{
|
||||
let extra_slot = self.player_data.inventory.extra_slot;
|
||||
let mut sequence = 0;
|
||||
|
||||
// pages
|
||||
for (i, page) in self.player_data.inventory.pages.clone().iter().enumerate() {
|
||||
let kind = match i {
|
||||
0 => ContainerType::Inventory0,
|
||||
1 => ContainerType::Inventory1,
|
||||
2 => ContainerType::Inventory2,
|
||||
3 => ContainerType::Inventory3,
|
||||
_ => panic!("Shouldn't be anything else!"),
|
||||
};
|
||||
|
||||
let mut send_slot = async |slot_index: u16, item: &Item| {
|
||||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::ItemInfo,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::ItemInfo(ItemInfo {
|
||||
container: ContainerType::Inventory0,
|
||||
sequence,
|
||||
container: kind,
|
||||
slot: slot_index,
|
||||
quantity: item.quantity,
|
||||
catalog_id: item.id,
|
||||
|
@ -455,7 +465,33 @@ impl ZoneConnection {
|
|||
.await;
|
||||
};
|
||||
|
||||
send_slot(0, &extra_slot).await;
|
||||
for (i, slot) in page.slots.iter().enumerate() {
|
||||
send_slot(i as u16, slot).await;
|
||||
}
|
||||
|
||||
// inform the client of container state
|
||||
{
|
||||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::ContainerInfo,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
||||
container: kind,
|
||||
num_items: page.num_items(),
|
||||
sequence,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.send_segment(PacketSegment {
|
||||
source_actor: self.player_data.actor_id,
|
||||
target_actor: self.player_data.actor_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
sequence += 1;
|
||||
}
|
||||
|
||||
// equipped
|
||||
|
@ -467,6 +503,7 @@ impl ZoneConnection {
|
|||
op_code: ServerZoneIpcType::ItemInfo,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::ItemInfo(ItemInfo {
|
||||
sequence,
|
||||
container: ContainerType::Equipped,
|
||||
slot: slot_index,
|
||||
quantity: item.quantity,
|
||||
|
@ -485,6 +522,7 @@ impl ZoneConnection {
|
|||
.await;
|
||||
};
|
||||
|
||||
// TODO: make containers enumerable to vastly simplify this code
|
||||
send_slot(0, &equipped.main_hand).await;
|
||||
send_slot(1, &equipped.off_hand).await;
|
||||
send_slot(2, &equipped.head).await;
|
||||
|
@ -500,27 +538,6 @@ impl ZoneConnection {
|
|||
send_slot(13, &equipped.soul_crystal).await;
|
||||
}
|
||||
|
||||
// inform the client of page 1
|
||||
{
|
||||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::ContainerInfo,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
||||
container: ContainerType::Inventory0,
|
||||
num_items: 1,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.send_segment(PacketSegment {
|
||||
source_actor: self.player_data.actor_id,
|
||||
target_actor: self.player_data.actor_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// inform the client they have items equipped
|
||||
{
|
||||
let ipc = ServerZoneIpcSegment {
|
||||
|
@ -529,7 +546,7 @@ impl ZoneConnection {
|
|||
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
||||
container: ContainerType::Equipped,
|
||||
num_items: self.player_data.inventory.equipped.num_items(),
|
||||
sequence: 1,
|
||||
sequence,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
|
|
|
@ -117,7 +117,7 @@ impl WorldDatabase {
|
|||
&chara_make.to_json(),
|
||||
2,
|
||||
132,
|
||||
Inventory::new(),
|
||||
Inventory::default(),
|
||||
);
|
||||
|
||||
tracing::info!("{} added to the world!", character.name);
|
||||
|
|
|
@ -8,6 +8,12 @@ use crate::config::get_config;
|
|||
|
||||
use super::ipc::{ContainerType, InventoryModify};
|
||||
|
||||
// TODO: rename to storage?
|
||||
pub trait Container {
|
||||
fn num_items(&self) -> u32;
|
||||
fn get_slot<'a>(&'a mut self, index: u16) -> &'a mut Item;
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct Item {
|
||||
pub quantity: u32,
|
||||
|
@ -37,8 +43,8 @@ pub struct EquippedContainer {
|
|||
pub soul_crystal: Item,
|
||||
}
|
||||
|
||||
impl EquippedContainer {
|
||||
pub fn num_items(&self) -> u32 {
|
||||
impl Container for EquippedContainer {
|
||||
fn num_items(&self) -> u32 {
|
||||
self.main_hand.quantity
|
||||
+ self.off_hand.quantity
|
||||
+ self.head.quantity
|
||||
|
@ -54,7 +60,7 @@ impl EquippedContainer {
|
|||
+ self.soul_crystal.quantity
|
||||
}
|
||||
|
||||
pub fn get_slot(&mut self, index: u16) -> &mut Item {
|
||||
fn get_slot(&mut self, index: u16) -> &mut Item {
|
||||
match index {
|
||||
0 => &mut self.main_hand,
|
||||
1 => &mut self.off_hand,
|
||||
|
@ -74,26 +80,45 @@ impl EquippedContainer {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct InventoryPage {
|
||||
pub slots: Vec<Item>,
|
||||
}
|
||||
|
||||
impl InventoryPage {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
slots: vec![Item::default(); 35],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Container for InventoryPage {
|
||||
fn num_items(&self) -> u32 {
|
||||
self.slots.iter().filter(|item| item.quantity > 0).count() as u32
|
||||
}
|
||||
|
||||
fn get_slot(&mut self, index: u16) -> &mut Item {
|
||||
self.slots.get_mut(index as usize).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Inventory {
|
||||
pub equipped: EquippedContainer,
|
||||
pub extra_slot: Item, // WIP for inventory pages
|
||||
pub pages: [InventoryPage; 4],
|
||||
}
|
||||
|
||||
impl Default for Inventory {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self {
|
||||
equipped: EquippedContainer::default(),
|
||||
pages: std::array::from_fn(|_| InventoryPage::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inventory {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
equipped: EquippedContainer::default(),
|
||||
extra_slot: Item::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Equip the starting items for a given race
|
||||
pub fn equip_racial_items(&mut self, race_id: u8, gender: u8) {
|
||||
let config = get_config();
|
||||
|
@ -138,16 +163,60 @@ impl Inventory {
|
|||
}
|
||||
|
||||
pub fn process_action(&mut self, action: &InventoryModify) {
|
||||
// equipped
|
||||
if action.src_storage_id == ContainerType::Equipped {
|
||||
let src_slot = self.equipped.get_slot(action.src_container_index);
|
||||
|
||||
// it only unequips for now, doesn't move the item
|
||||
if action.operation_type == 571 {
|
||||
// discard
|
||||
let src_container = self.get_container(&action.src_storage_id);
|
||||
let src_slot = src_container.get_slot(action.src_container_index);
|
||||
*src_slot = Item::default();
|
||||
} else if action.src_storage_id == ContainerType::Inventory0 {
|
||||
let dst_slot = self.equipped.get_slot(action.dst_container_index);
|
||||
} else {
|
||||
// NOTE: only swaps items for now
|
||||
|
||||
*dst_slot = self.extra_slot;
|
||||
let src_item;
|
||||
// get the source item
|
||||
{
|
||||
let src_container = self.get_container(&action.src_storage_id);
|
||||
let src_slot = src_container.get_slot(action.src_container_index);
|
||||
src_item = src_slot.clone();
|
||||
}
|
||||
|
||||
let dst_item;
|
||||
// move into dst item
|
||||
{
|
||||
let dst_container = self.get_container(&action.dst_storage_id);
|
||||
let dst_slot = dst_container.get_slot(action.dst_container_index);
|
||||
|
||||
dst_item = dst_slot.clone();
|
||||
dst_slot.clone_from(&src_item);
|
||||
}
|
||||
|
||||
// move dst item into src slot
|
||||
{
|
||||
let src_container = self.get_container(&action.src_storage_id);
|
||||
let src_slot = src_container.get_slot(action.src_container_index);
|
||||
src_slot.clone_from(&dst_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_in_next_free_slot(&mut self, item: Item) {
|
||||
for page in &mut self.pages {
|
||||
for slot in &mut page.slots {
|
||||
if slot.quantity == 0 {
|
||||
slot.clone_from(&item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_container(&mut self, container_type: &ContainerType) -> &mut dyn Container {
|
||||
match container_type {
|
||||
ContainerType::Inventory0 => &mut self.pages[0],
|
||||
ContainerType::Inventory1 => &mut self.pages[1],
|
||||
ContainerType::Inventory2 => &mut self.pages[2],
|
||||
ContainerType::Inventory3 => &mut self.pages[3],
|
||||
ContainerType::Equipped => &mut self.equipped,
|
||||
ContainerType::ArmouryBody => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use binrw::binrw;
|
|||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[brw(repr = u16)]
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
pub enum ContainerType {
|
||||
#[default]
|
||||
Inventory0 = 0,
|
||||
|
|
Loading…
Add table
Reference in a new issue