mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-20 14:47: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,
|
GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
|
||||||
};
|
};
|
||||||
use kawari::world::{
|
use kawari::world::{
|
||||||
Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, LuaPlayer, PlayerData,
|
Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, Item, LuaPlayer,
|
||||||
ServerHandle, StatusEffects, ToServer, WorldDatabase,
|
PlayerData, ServerHandle, StatusEffects, ToServer, WorldDatabase,
|
||||||
};
|
};
|
||||||
use kawari::world::{
|
use kawari::world::{
|
||||||
ChatHandler, Zone, ZoneConnection,
|
ChatHandler, Zone, ZoneConnection,
|
||||||
|
@ -615,8 +615,7 @@ async fn client_loop(
|
||||||
})
|
})
|
||||||
.await,
|
.await,
|
||||||
GameMasterCommandType::GiveItem => {
|
GameMasterCommandType::GiveItem => {
|
||||||
connection.player_data.inventory.extra_slot.id = *arg;
|
connection.player_data.inventory.add_in_next_free_slot(Item { id: *arg, quantity: 1 });
|
||||||
connection.player_data.inventory.extra_slot.quantity = 1;
|
|
||||||
connection.send_inventory(false).await;
|
connection.send_inventory(false).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -816,7 +815,7 @@ async fn client_loop(
|
||||||
game_data.get_citystate(chara_make.classjob_id as u16);
|
game_data.get_citystate(chara_make.classjob_id as u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut inventory = Inventory::new();
|
let mut inventory = Inventory::default();
|
||||||
|
|
||||||
// fill inventory
|
// fill inventory
|
||||||
inventory.equip_racial_items(
|
inventory.equip_racial_items(
|
||||||
|
@ -1002,7 +1001,7 @@ async fn client_loop(
|
||||||
connection.process_effects_list().await;
|
connection.process_effects_list().await;
|
||||||
|
|
||||||
// update lua player
|
// 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();
|
lua_player.status_effects = connection.status_effects.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,7 +322,7 @@ impl ChatHandler {
|
||||||
"!spawnclone" => {
|
"!spawnclone" => {
|
||||||
// spawn another one of us
|
// spawn another one of us
|
||||||
|
|
||||||
let player = connection.player_data;
|
let player = &connection.player_data;
|
||||||
|
|
||||||
let mut common = connection
|
let mut common = connection
|
||||||
.get_player_common_spawn(Some(player.position), Some(player.rotation));
|
.get_player_common_spawn(Some(player.position), Some(player.rotation));
|
||||||
|
|
|
@ -19,6 +19,7 @@ use crate::{
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone,
|
Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone,
|
||||||
|
inventory::Container,
|
||||||
ipc::{
|
ipc::{
|
||||||
ActorControlSelf, ActorMove, ActorSetPos, ClientZoneIpcSegment, CommonSpawn, ContainerInfo,
|
ActorControlSelf, ActorMove, ActorSetPos, ClientZoneIpcSegment, CommonSpawn, ContainerInfo,
|
||||||
ContainerType, DisplayFlag, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind, PlayerSubKind,
|
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 {
|
pub struct PlayerData {
|
||||||
// Static data
|
// Static data
|
||||||
pub actor_id: u32,
|
pub actor_id: u32,
|
||||||
|
@ -428,16 +429,25 @@ impl ZoneConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_inventory(&mut self, send_appearance_update: bool) {
|
pub async fn send_inventory(&mut self, send_appearance_update: bool) {
|
||||||
// page 1
|
let mut sequence = 0;
|
||||||
{
|
|
||||||
let extra_slot = self.player_data.inventory.extra_slot;
|
// 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 mut send_slot = async |slot_index: u16, item: &Item| {
|
||||||
let ipc = ServerZoneIpcSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: ServerZoneIpcType::ItemInfo,
|
op_code: ServerZoneIpcType::ItemInfo,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: ServerZoneIpcData::ItemInfo(ItemInfo {
|
data: ServerZoneIpcData::ItemInfo(ItemInfo {
|
||||||
container: ContainerType::Inventory0,
|
sequence,
|
||||||
|
container: kind,
|
||||||
slot: slot_index,
|
slot: slot_index,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
catalog_id: item.id,
|
catalog_id: item.id,
|
||||||
|
@ -455,7 +465,33 @@ impl ZoneConnection {
|
||||||
.await;
|
.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
|
// equipped
|
||||||
|
@ -467,6 +503,7 @@ impl ZoneConnection {
|
||||||
op_code: ServerZoneIpcType::ItemInfo,
|
op_code: ServerZoneIpcType::ItemInfo,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: ServerZoneIpcData::ItemInfo(ItemInfo {
|
data: ServerZoneIpcData::ItemInfo(ItemInfo {
|
||||||
|
sequence,
|
||||||
container: ContainerType::Equipped,
|
container: ContainerType::Equipped,
|
||||||
slot: slot_index,
|
slot: slot_index,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
|
@ -485,6 +522,7 @@ impl ZoneConnection {
|
||||||
.await;
|
.await;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: make containers enumerable to vastly simplify this code
|
||||||
send_slot(0, &equipped.main_hand).await;
|
send_slot(0, &equipped.main_hand).await;
|
||||||
send_slot(1, &equipped.off_hand).await;
|
send_slot(1, &equipped.off_hand).await;
|
||||||
send_slot(2, &equipped.head).await;
|
send_slot(2, &equipped.head).await;
|
||||||
|
@ -500,27 +538,6 @@ impl ZoneConnection {
|
||||||
send_slot(13, &equipped.soul_crystal).await;
|
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
|
// inform the client they have items equipped
|
||||||
{
|
{
|
||||||
let ipc = ServerZoneIpcSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
|
@ -529,7 +546,7 @@ impl ZoneConnection {
|
||||||
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
data: ServerZoneIpcData::ContainerInfo(ContainerInfo {
|
||||||
container: ContainerType::Equipped,
|
container: ContainerType::Equipped,
|
||||||
num_items: self.player_data.inventory.equipped.num_items(),
|
num_items: self.player_data.inventory.equipped.num_items(),
|
||||||
sequence: 1,
|
sequence,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -117,7 +117,7 @@ impl WorldDatabase {
|
||||||
&chara_make.to_json(),
|
&chara_make.to_json(),
|
||||||
2,
|
2,
|
||||||
132,
|
132,
|
||||||
Inventory::new(),
|
Inventory::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
tracing::info!("{} added to the world!", character.name);
|
tracing::info!("{} added to the world!", character.name);
|
||||||
|
|
|
@ -8,6 +8,12 @@ use crate::config::get_config;
|
||||||
|
|
||||||
use super::ipc::{ContainerType, InventoryModify};
|
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)]
|
#[derive(Default, Copy, Clone, Serialize, Deserialize, Debug)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub quantity: u32,
|
pub quantity: u32,
|
||||||
|
@ -37,8 +43,8 @@ pub struct EquippedContainer {
|
||||||
pub soul_crystal: Item,
|
pub soul_crystal: Item,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EquippedContainer {
|
impl Container for EquippedContainer {
|
||||||
pub fn num_items(&self) -> u32 {
|
fn num_items(&self) -> u32 {
|
||||||
self.main_hand.quantity
|
self.main_hand.quantity
|
||||||
+ self.off_hand.quantity
|
+ self.off_hand.quantity
|
||||||
+ self.head.quantity
|
+ self.head.quantity
|
||||||
|
@ -54,7 +60,7 @@ impl EquippedContainer {
|
||||||
+ self.soul_crystal.quantity
|
+ 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 {
|
match index {
|
||||||
0 => &mut self.main_hand,
|
0 => &mut self.main_hand,
|
||||||
1 => &mut self.off_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 struct Inventory {
|
||||||
pub equipped: EquippedContainer,
|
pub equipped: EquippedContainer,
|
||||||
pub extra_slot: Item, // WIP for inventory pages
|
pub pages: [InventoryPage; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Inventory {
|
impl Default for Inventory {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self {
|
||||||
|
equipped: EquippedContainer::default(),
|
||||||
|
pages: std::array::from_fn(|_| InventoryPage::default()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inventory {
|
impl Inventory {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
equipped: EquippedContainer::default(),
|
|
||||||
extra_slot: Item::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Equip the starting items for a given race
|
/// Equip the starting items for a given race
|
||||||
pub fn equip_racial_items(&mut self, race_id: u8, gender: u8) {
|
pub fn equip_racial_items(&mut self, race_id: u8, gender: u8) {
|
||||||
let config = get_config();
|
let config = get_config();
|
||||||
|
@ -138,16 +163,60 @@ impl Inventory {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_action(&mut self, action: &InventoryModify) {
|
pub fn process_action(&mut self, action: &InventoryModify) {
|
||||||
// equipped
|
if action.operation_type == 571 {
|
||||||
if action.src_storage_id == ContainerType::Equipped {
|
// discard
|
||||||
let src_slot = self.equipped.get_slot(action.src_container_index);
|
let src_container = self.get_container(&action.src_storage_id);
|
||||||
|
let src_slot = src_container.get_slot(action.src_container_index);
|
||||||
// it only unequips for now, doesn't move the item
|
|
||||||
*src_slot = Item::default();
|
*src_slot = Item::default();
|
||||||
} else if action.src_storage_id == ContainerType::Inventory0 {
|
} else {
|
||||||
let dst_slot = self.equipped.get_slot(action.dst_container_index);
|
// 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]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
#[brw(repr = u16)]
|
#[brw(repr = u16)]
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||||
pub enum ContainerType {
|
pub enum ContainerType {
|
||||||
#[default]
|
#[default]
|
||||||
Inventory0 = 0,
|
Inventory0 = 0,
|
||||||
|
|
Loading…
Add table
Reference in a new issue