Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 118 additions & 1 deletion core/src/save/player_data.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::fs::File;

use crate::Position;
use crate::inventory::SlotIndex;
use crate::{ItemStack, Position};
use feather_items::Item;
use std::io::Read;
use uuid::Uuid;

Expand All @@ -13,6 +15,50 @@ pub struct PlayerData {
pub position: Vec<f64>,
#[serde(rename = "Rotation")]
pub rotation: Vec<f32>,
#[serde(rename = "Inventory")]
pub inventory: Vec<InventorySlot>,
}

/// Represents a single inventory slot (including position index).
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct InventorySlot {
#[serde(rename = "Count")]
pub count: i8,
#[serde(rename = "Slot")]
pub slot: i8,
#[serde(rename = "id")]
pub item: String,
}

impl InventorySlot {
/// Converts a slot to an ItemStack.
pub fn to_stack(&self) -> ItemStack {
ItemStack {
ty: Item::from_identifier(self.item.as_str()).unwrap_or(Item::Air),
amount: self.count as u8,
}
}

/// Converts an NBT inventory index to a network protocol index.
/// Returns None if the index is invalid.
pub fn convert_index(&self) -> Option<SlotIndex> {
if 0 <= self.slot && self.slot <= 8 {
// Hotbar
Some(crate::inventory::SLOT_HOTBAR_OFFSET + (self.slot as usize))
} else if self.slot == -106 {
// Offhand
Some(crate::inventory::SLOT_OFFHAND as usize)
} else if 100 <= self.slot && self.slot <= 103 {
// Equipment
Some((108 - self.slot) as usize)
} else if 9 <= self.slot && self.slot <= 35 {
// Rest of inventory
Some(self.slot as usize)
} else {
// Unknown index
None
}
}
}

impl PlayerData {
Expand Down Expand Up @@ -47,6 +93,7 @@ pub fn load_player_data(uuid: Uuid) -> Result<PlayerData, nbt::Error> {
mod tests {
use super::*;
use crate::Gamemode;
use hashbrown::HashMap;
use std::io::Cursor;

#[test]
Expand All @@ -63,6 +110,7 @@ mod tests {
gamemode: 0,
position: vec![1.0, 2.0, 3.0],
rotation: vec![4.0, 5.0],
inventory: vec![],
};
let pos = data.read_position().unwrap();

Expand All @@ -80,6 +128,7 @@ mod tests {
gamemode: 0,
position: vec![1.0],
rotation: vec![2.0, 3.0],
inventory: vec![],
};
let pos = data.read_position();
assert!(pos.is_none());
Expand All @@ -91,8 +140,76 @@ mod tests {
gamemode: 0,
position: vec![1.0, 2.0, 3.0],
rotation: vec![4.0],
inventory: vec![],
};
let pos = data.read_position();
assert!(pos.is_none());
}

#[test]
fn test_convert_item() {
let slot = InventorySlot {
count: 1,
slot: 2,
item: String::from(Item::Feather.identifier()),
};

let item_stack = slot.to_stack();
assert_eq!(item_stack.ty, Item::Feather);
assert_eq!(item_stack.amount, 1);
}

#[test]
fn test_convert_item_unknown_type() {
let slot = InventorySlot {
count: 1,
slot: 2,
item: String::from("invalid:identifier"),
};

let item_stack = slot.to_stack();
assert_eq!(item_stack.ty, Item::Air);
}

#[test]
fn test_convert_slot_index() {
let mut map: HashMap<i8, usize> = HashMap::new();

// Equipment
map.insert(103, crate::inventory::SLOT_ARMOR_HEAD);
map.insert(102, crate::inventory::SLOT_ARMOR_CHEST);
map.insert(101, crate::inventory::SLOT_ARMOR_LEGS);
map.insert(100, crate::inventory::SLOT_ARMOR_FEET);
map.insert(-106, crate::inventory::SLOT_OFFHAND);

// Hotbar
for x in 0..9 {
map.insert(x, crate::inventory::SLOT_HOTBAR_OFFSET + (x as usize));
}

// Rest of inventory
for x in 9..36 {
map.insert(x, x as usize);
}

// Check all valid slots
for (src, expected) in map {
let slot = InventorySlot {
slot: src,
count: 1,
item: String::from("invalid:identifier"),
};
assert_eq!(slot.convert_index().unwrap(), expected);
}

// Check that invalid slots error out
for invalid_slot in [-1, -2, 104].iter() {
let slot = InventorySlot {
slot: *invalid_slot as i8,
count: 1,
item: String::from("invalid:identifier"),
};
assert!(slot.convert_index().is_none());
}
}
}
11 changes: 10 additions & 1 deletion server/src/joinhandler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::chunk_logic::{ChunkHolderComponent, ChunkHolders, ChunkWorkerHandle};
use crate::config::Config;
use crate::entity::{EntitySpawnEvent, EntityType, PlayerComponent, PositionComponent};
use crate::network::NetworkComponent;
use crate::player::{ChunkPendingComponent, LoadedChunksComponent};
use crate::player::{ChunkPendingComponent, InventoryUpdateEvent, LoadedChunksComponent};
use crate::PlayerCount;

#[derive(Default)]
Expand Down Expand Up @@ -74,6 +74,7 @@ impl<'a> System<'a> for JoinHandlerSystem {
ReadStorage<'a, ChunkPendingComponent>,
Write<'a, EventChannel<PlayerJoinEvent>>,
Write<'a, EventChannel<EntitySpawnEvent>>,
Write<'a, EventChannel<InventoryUpdateEvent>>,
Read<'a, ChunkWorkerHandle>,
Entities<'a>,
Read<'a, LazyUpdate>,
Expand All @@ -95,6 +96,7 @@ impl<'a> System<'a> for JoinHandlerSystem {
pending_chunks,
mut join_events,
mut spawn_events,
mut inv_events,
worker_handle,
entities,
lazy,
Expand Down Expand Up @@ -202,6 +204,13 @@ impl<'a> System<'a> for JoinHandlerSystem {
};
spawn_events.single_write(event);

// Trigger inventory update event on the entire inventory
let event = InventoryUpdateEvent {
slots: (0..46).collect(),
player,
};
inv_events.single_write(event);

// We're finished here.
to_remove.push(player);
}
Expand Down
32 changes: 22 additions & 10 deletions server/src/player/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl<'a> System<'a> for PlayerInitSystem {

fn run(&mut self, data: Self::SystemData) {
let (
events,
join_events,
mut player_comps,
mut positions,
mut nameds,
Expand All @@ -50,20 +50,26 @@ impl<'a> System<'a> for PlayerInitSystem {
) = data;

// Run through events
for event in events.read(&mut self.join_event_reader.as_mut().unwrap()) {
for event in join_events.read(&mut self.join_event_reader.as_mut().unwrap()) {
// Load player data
let uuid = event.uuid;
// If this is a new player, set gamemode to server's default (config)
let default_gamemode = &config.server.default_gamemode.clone();

debug!("Loading player data for UUID {}", uuid);
let (gamemode, pos) = match feather_core::player_data::load_player_data(uuid) {
Ok(data) => (Gamemode::from_id(data.gamemode as u8), data.read_position()),
Err(_) => (
Gamemode::from_string(default_gamemode.as_str()),
None, // Invalid position will default to world spawn
),
};
let (gamemode, pos, inventory_slots) =
match feather_core::player_data::load_player_data(uuid) {
Ok(data) => (
Gamemode::from_id(data.gamemode as u8),
data.read_position(),
data.inventory,
),
Err(_) => (
Gamemode::from_string(default_gamemode.as_str()),
None, // Invalid position will default to world spawn
vec![], // Empty inventory
),
};

let player_comp = PlayerComponent {
profile_properties: event.profile_properties.clone(),
Expand Down Expand Up @@ -100,7 +106,13 @@ impl<'a> System<'a> for PlayerInitSystem {
.insert(event.player, loaded_chunk_comp)
.unwrap();

let inventory_comp = InventoryComponent::new();
let mut inventory_comp = InventoryComponent::new();
for slot in inventory_slots {
let slot_index = slot.convert_index();
if let Some(slot_index) = slot_index {
inventory_comp.set_item_at(slot_index, slot.to_stack());
}
}
inventory_comps
.insert(event.player, inventory_comp)
.unwrap();
Expand Down