Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5186b36
Implement sector allocator
caelunshun Oct 1, 2019
cfb006b
Implement conversion of chunk data compound to `nbt::Blob`
caelunshun Oct 3, 2019
c3f97c4
Initial implementation of RegionHandle::save_chunk (untested)
caelunshun Oct 3, 2019
ab7123b
Initial chunk worker support for chunk saving
caelunshun Oct 3, 2019
bc2dcf5
Save chunks upon unload
caelunshun Oct 3, 2019
692531b
Implement periodic saving of chunks, with interval defined in config
caelunshun Oct 3, 2019
6a63107
Implement saving of chunks on shutdown
caelunshun Oct 3, 2019
c3995f1
Merge develop into branch
caelunshun Oct 3, 2019
c330d89
Implement creation of new region files
caelunshun Oct 3, 2019
1ff37e9
Save level.dat on shutdown
caelunshun Oct 3, 2019
f60f339
Begin work on entity saving
caelunshun Oct 4, 2019
8dc868f
Fix saving of item entities
caelunshun Oct 4, 2019
24c704c
Add more data to chunk save so that the vanilla server accepts it
caelunshun Oct 4, 2019
ef16a3a
Fix panic when chunk containing players was saved
caelunshun Oct 4, 2019
ad31994
Attempt to fix Windows overflow error
caelunshun Oct 4, 2019
b9953e1
Implement saving of player data on disconnect and shutdown
caelunshun Oct 4, 2019
4a87546
Fix ordering of pitch and yaw
caelunshun Oct 4, 2019
f45c674
Merge branch 'develop' into feature/world-persistence
caelunshun Oct 4, 2019
3d48cf9
Fix panic on double chunk unload
caelunshun Oct 4, 2019
ee05c2a
Fix panic when entities without position are saved
caelunshun Oct 4, 2019
0e3af87
Use more idiomatic early return syntax
caelunshun Oct 4, 2019
22667ea
Implement conversion from global to section palette for chunk saving
caelunshun Oct 5, 2019
c5271d2
Fix merge conflicts
caelunshun Oct 5, 2019
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
305 changes: 182 additions & 123 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ hash32-derive = "0.1.0"
strum = "0.15.0"
strum_macros = "0.15.0"
tokio = "=0.2.0-alpha.6"
failure = "0.1.5"
failure = "0.1.5"
bitvec = "0.15.2"
multimap = "0.6.0"
5 changes: 5 additions & 0 deletions core/src/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ impl Inventory {
pub fn slot_count(&self) -> u16 {
self.items.len() as u16
}

/// Returns a reference to this inventory's items.
pub fn items(&self) -> &[Option<ItemStack>] {
&self.items
}
}

/// Represents an item stack.
Expand Down
76 changes: 76 additions & 0 deletions core/src/save/entity.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::Position;
use nbt::Value;
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "id")]
Expand All @@ -13,6 +15,32 @@ pub enum EntityData {
Unknown,
}

impl EntityData {
pub fn into_nbt_value(self) -> Value {
let mut map = HashMap::new();

map.insert(
String::from("id"),
Value::String(
match self {
EntityData::Item(_) => "minecraft:item",
EntityData::Arrow(_) => "minecraft:arrow",
EntityData::Unknown => panic!("Cannot write unknown entities"),
}
.to_string(),
),
);

match self {
EntityData::Item(data) => data.write_to_map(&mut map),
EntityData::Arrow(data) => data.write_to_map(&mut map),
EntityData::Unknown => panic!("Cannot write unknown entities"),
}

Value::Compound(map)
}
}

/// Common entity tags.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaseEntityData {
Expand All @@ -24,6 +52,23 @@ pub struct BaseEntityData {
pub velocity: Vec<f64>,
}

impl BaseEntityData {
fn write_to_map(self, map: &mut HashMap<String, Value>) {
map.insert(
String::from("Pos"),
Value::List(self.position.into_iter().map(Value::Double).collect()),
);
map.insert(
String::from("Rotation"),
Value::List(self.rotation.into_iter().map(Value::Float).collect()),
);
map.insert(
String::from("Motion"),
Value::List(self.velocity.into_iter().map(Value::Double).collect()),
);
}
}

impl BaseEntityData {
/// Reads the position and rotation fields. If the fields are invalid, None is returned.
pub fn read_position(self: &BaseEntityData) -> Option<Position> {
Expand Down Expand Up @@ -74,6 +119,13 @@ pub struct ItemData {
pub item: String,
}

impl ItemData {
fn write_to_map(self, map: &mut HashMap<String, Value>) {
map.insert(String::from("Count"), Value::Byte(self.count as i8));
map.insert(String::from("id"), Value::String(self.item));
}
}

/// Data for an Item entity (`minecraft:item`).
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
pub struct ItemEntityData {
Expand All @@ -90,6 +142,22 @@ pub struct ItemEntityData {
pub item: ItemData,
}

impl ItemEntityData {
fn write_to_map(self, map: &mut HashMap<String, Value>) {
self.entity.write_to_map(map);

let mut item = HashMap::new();
self.item.write_to_map(&mut item);
map.insert(String::from("Item"), Value::Compound(item));

map.insert(String::from("Age"), Value::Short(self.age));
map.insert(
String::from("PickupDelay"),
Value::Byte(self.pickup_delay as i8),
);
}
}

/// Data for an Arrow entity (`minecraft:arrow`).
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ArrowEntityData {
Expand All @@ -105,6 +173,14 @@ pub struct ArrowEntityData {
pub critical: u8,
}

impl ArrowEntityData {
fn write_to_map(self, map: &mut HashMap<String, Value>) {
self.entity.write_to_map(map);

map.insert(String::from("crit"), Value::Byte(self.critical as i8));
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
62 changes: 56 additions & 6 deletions core/src/save/player_data.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::fs::File;

use crate::entity::BaseEntityData;
use crate::inventory::SlotIndex;
use crate::inventory::{
SlotIndex, HOTBAR_SIZE, INVENTORY_SIZE, SLOT_ARMOR_MAX, SLOT_ARMOR_MIN, SLOT_HOTBAR_OFFSET,
SLOT_INVENTORY_OFFSET, SLOT_OFFHAND,
};
use crate::ItemStack;
use feather_items::Item;
use std::io::Read;
use std::path::Path;
use std::fs;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use uuid::Uuid;

/// Represents the contents of a player data file.
Expand All @@ -22,7 +26,7 @@ pub struct PlayerData {
}

/// Represents a single inventory slot (including position index).
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InventorySlot {
#[serde(rename = "Count")]
pub count: i8,
Expand All @@ -41,6 +45,31 @@ impl InventorySlot {
}
}

/// Converts a network protocol index, item, and count
/// to an `InventorySlot`.
pub fn from_network_index(network: SlotIndex, stack: ItemStack) -> Self {
let slot = if SLOT_HOTBAR_OFFSET <= network && network < SLOT_HOTBAR_OFFSET + HOTBAR_SIZE {
// Hotbar
(network - SLOT_HOTBAR_OFFSET) as i8
} else if network == SLOT_OFFHAND {
-106
} else if SLOT_ARMOR_MIN <= network && network <= SLOT_ARMOR_MAX {
((SLOT_ARMOR_MAX - network) + 100) as i8
} else if SLOT_INVENTORY_OFFSET <= network
&& network < SLOT_INVENTORY_OFFSET + INVENTORY_SIZE
{
network as i8
} else {
panic!("Invalid slot index {} on server", network);
};

Self {
count: stack.amount as i8,
slot,
item: stack.ty.identifier().to_string(),
}
}

/// Converts an NBT inventory index to a network protocol index.
/// Returns None if the index is invalid.
pub fn convert_index(&self) -> Option<SlotIndex> {
Expand Down Expand Up @@ -68,12 +97,27 @@ fn load_from_file<R: Read>(reader: R) -> Result<PlayerData, nbt::Error> {
}

pub fn load_player_data(world_dir: &Path, uuid: Uuid) -> Result<PlayerData, nbt::Error> {
let file_path = world_dir.join("playerdata").join(format!("{}.dat", uuid));
let file_path = file_path(world_dir, uuid);
let file = File::open(file_path)?;
let data = load_from_file(file)?;
Ok(data)
}

fn save_to_file<W: Write>(mut writer: W, data: PlayerData) -> Result<(), nbt::Error> {
nbt::to_gzip_writer(&mut writer, &data, None)
}

pub fn save_player_data(world_dir: &Path, uuid: Uuid, data: PlayerData) -> Result<(), nbt::Error> {
fs::create_dir_all(world_dir.join("playerdata"))?;
let file_path = file_path(world_dir, uuid);
let file = File::create(file_path)?;
save_to_file(file, data)
}

fn file_path(world_dir: &Path, uuid: Uuid) -> PathBuf {
world_dir.join("playerdata").join(format!("{}.dat", uuid))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -135,14 +179,20 @@ mod tests {
map.insert(x, x as usize);
}

dbg!(map.clone());

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

// Check that invalid slots error out
Expand Down
Loading