mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Use assets_manager to load assets
This commit is contained in:
parent
6ecff460bf
commit
0cf164f33a
107
Cargo.lock
generated
107
Cargo.lock
generated
@ -52,6 +52,17 @@ version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75b7e6a93ecd6dbd2c225154d0fa7f86205574ecaa6c87429fb5f66ee677c44"
|
||||
dependencies = [
|
||||
"getrandom 0.2.0",
|
||||
"lazy_static",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.13"
|
||||
@ -230,6 +241,23 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109"
|
||||
|
||||
[[package]]
|
||||
name = "assets_manager"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8048fbc7e6314fa56e2c7faeb0e1c7be01d23c87bad301a7877afaa493ec3dbb"
|
||||
dependencies = [
|
||||
"ahash 0.6.2",
|
||||
"bincode",
|
||||
"crossbeam-channel 0.5.0",
|
||||
"log",
|
||||
"notify 4.0.15",
|
||||
"parking_lot 0.11.0",
|
||||
"ron",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-std"
|
||||
version = "1.5.0"
|
||||
@ -791,7 +819,7 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.15",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
@ -1745,6 +1773,16 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fsevent-sys 2.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "2.0.2"
|
||||
@ -1752,7 +1790,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97f347202c95c98805c216f9e1df210e8ebaec9fdb2365700a43c10797a35e63"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fsevent-sys",
|
||||
"fsevent-sys 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1972,6 +2019,17 @@ dependencies = [
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gfx"
|
||||
version = "0.18.2"
|
||||
@ -2561,6 +2619,17 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.8.3"
|
||||
@ -3249,6 +3318,24 @@ dependencies = [
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "4.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"filetime",
|
||||
"fsevent 0.4.0",
|
||||
"fsevent-sys 2.0.1",
|
||||
"inotify 0.7.1",
|
||||
"libc",
|
||||
"mio 0.6.22",
|
||||
"mio-extras",
|
||||
"walkdir 2.3.1",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.0.0-pre.3"
|
||||
@ -3259,9 +3346,9 @@ dependencies = [
|
||||
"bitflags",
|
||||
"crossbeam-channel 0.4.4",
|
||||
"filetime",
|
||||
"fsevent",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"fsevent 2.0.2",
|
||||
"fsevent-sys 3.0.2",
|
||||
"inotify 0.8.3",
|
||||
"libc",
|
||||
"mio 0.6.22",
|
||||
"mio-extras",
|
||||
@ -4033,7 +4120,7 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.15",
|
||||
"libc",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
@ -4082,7 +4169,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4231,7 +4318,7 @@ version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.1.15",
|
||||
"redox_syscall",
|
||||
"rust-argon2",
|
||||
]
|
||||
@ -5699,6 +5786,7 @@ name = "veloren-common"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"arraygen",
|
||||
"assets_manager",
|
||||
"criterion",
|
||||
"crossbeam-channel 0.5.0",
|
||||
"crossbeam-utils 0.8.1",
|
||||
@ -5710,7 +5798,6 @@ dependencies = [
|
||||
"image",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"notify",
|
||||
"num-derive",
|
||||
"num-traits 0.2.14",
|
||||
"ordered-float 2.0.1",
|
||||
@ -5933,7 +6020,7 @@ dependencies = [
|
||||
"inline_tweak",
|
||||
"lazy_static",
|
||||
"libloading 0.6.3",
|
||||
"notify",
|
||||
"notify 5.0.0-pre.3",
|
||||
"tracing",
|
||||
"vek 0.12.0",
|
||||
"veloren-common",
|
||||
|
@ -30,10 +30,10 @@ vek = { version = "0.12.0", features = ["serde"] }
|
||||
uuid = { version = "0.8.1", default-features = false, features = ["serde", "v4"] }
|
||||
|
||||
# Assets
|
||||
assets_manager = {version = "0.4.1", features = ["bincode", "ron", "json", "hot-reloading"]}
|
||||
directories-next = "2.0"
|
||||
dot_vox = "4.0"
|
||||
image = { version = "0.23.12", default-features = false, features = ["png"] }
|
||||
notify = "5.0.0-pre.3"
|
||||
|
||||
# Data structures
|
||||
hashbrown = { version = "0.9", features = ["rayon", "serde", "nightly"] }
|
||||
|
@ -1,221 +1,58 @@
|
||||
//! Load assets (images or voxel data) from files
|
||||
pub mod watch;
|
||||
|
||||
use core::{any::Any, fmt, marker::PhantomData};
|
||||
use dot_vox::DotVoxData;
|
||||
use hashbrown::HashMap;
|
||||
use image::DynamicImage;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
fs::{self, File, ReadDir},
|
||||
io::{BufReader, Read},
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
use std::{borrow::Cow, fs, io, path::{Path, PathBuf}, sync::Arc};
|
||||
|
||||
pub use assets_manager::{
|
||||
Asset, AssetCache, BoxedError, Compound, Error, source,
|
||||
loader::{self, BytesLoader, BincodeLoader, Loader, JsonLoader, LoadFrom, RonLoader, StringLoader},
|
||||
};
|
||||
use tracing::{error, trace};
|
||||
|
||||
/// The error returned by asset loading functions
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Error {
|
||||
/// Parsing error occurred.
|
||||
ParseError(Arc<dyn std::fmt::Debug>),
|
||||
/// An asset of a different type has already been loaded with this
|
||||
/// specifier.
|
||||
InvalidType,
|
||||
/// Asset does not exist.
|
||||
NotFound(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn parse_error<E: std::fmt::Debug + 'static>(err: E) -> Self {
|
||||
Self::ParseError(Arc::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::ParseError(err) => write!(f, "{:?}", err),
|
||||
Error::InvalidType => write!(
|
||||
f,
|
||||
"an asset of a different type has already been loaded with this specifier."
|
||||
),
|
||||
Error::NotFound(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<dyn Any + 'static + Sync + Send>> for Error {
|
||||
fn from(_: Arc<dyn Any + 'static + Sync + Send>) -> Self { Error::InvalidType }
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Self { Error::NotFound(format!("{}", err)) }
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// The HashMap where all loaded assets are stored in.
|
||||
static ref ASSETS: RwLock<HashMap<String, Arc<dyn Any + 'static + Sync + Send>>> =
|
||||
RwLock::new(HashMap::new());
|
||||
static ref ASSETS: AssetCache = AssetCache::new(&*ASSETS_PATH).unwrap();
|
||||
}
|
||||
|
||||
fn reload<A: Asset>(specifier: &str) -> Result<(), Error>
|
||||
where
|
||||
A::Output: Send + Sync + 'static,
|
||||
{
|
||||
let asset = Arc::new(A::parse(load_file(specifier, A::ENDINGS)?, specifier)?);
|
||||
let mut assets_write = ASSETS.write().unwrap();
|
||||
match assets_write.get_mut(specifier) {
|
||||
Some(a) => *a = asset,
|
||||
None => {
|
||||
assets_write.insert(specifier.to_owned(), asset);
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn start_hot_reloading() {
|
||||
ASSETS.enhance_hot_reloading();
|
||||
}
|
||||
|
||||
pub type AssetHandle<T> = assets_manager::Handle<'static, T>;
|
||||
pub type AssetDir<T> = assets_manager::DirReader<'static, T, source::FileSystem>;
|
||||
|
||||
/// The Asset trait, which is implemented by all structures that have their data
|
||||
/// stored in the filesystem.
|
||||
pub trait Asset: Sized {
|
||||
type Output = Self;
|
||||
|
||||
const ENDINGS: &'static [&'static str];
|
||||
/// Parse the input file and return the correct Asset.
|
||||
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self::Output, Error>;
|
||||
|
||||
// TODO: Remove this function. It's only used in world/ in a really ugly way.To
|
||||
// do this properly assets should have all their necessary data in one file. A
|
||||
// ron file could be used to combine voxel data with positioning data for
|
||||
// example.
|
||||
/// Function used to load assets from the filesystem or the cache. Permits
|
||||
/// manipulating the loaded asset with a mapping function. Example usage:
|
||||
/// ```no_run
|
||||
/// use vek::*;
|
||||
/// use veloren_common::{assets::Asset, terrain::Structure};
|
||||
///
|
||||
/// let my_tree_structure = Structure::load_map("world.tree.oak_green.1", |s: Structure| {
|
||||
/// s.with_center(Vec3::new(15, 18, 14))
|
||||
/// })
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
fn load_map<F: FnOnce(Self::Output) -> Self::Output>(
|
||||
specifier: &str,
|
||||
f: F,
|
||||
) -> Result<Arc<Self::Output>, Error>
|
||||
where
|
||||
Self::Output: Send + Sync + 'static,
|
||||
{
|
||||
let assets_read = ASSETS.read().unwrap();
|
||||
match assets_read.get(specifier) {
|
||||
Some(asset) => Ok(Arc::clone(asset).downcast()?),
|
||||
None => {
|
||||
drop(assets_read); // Drop the asset hashmap to permit recursive loading
|
||||
let asset = Arc::new(f(Self::parse(
|
||||
load_file(specifier, Self::ENDINGS)?,
|
||||
specifier,
|
||||
)?));
|
||||
let clone = Arc::clone(&asset);
|
||||
ASSETS.write().unwrap().insert(specifier.to_owned(), clone);
|
||||
Ok(asset)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn load_glob(specifier: &str) -> Result<Arc<Vec<Arc<Self::Output>>>, Error>
|
||||
where
|
||||
Self::Output: Send + Sync + 'static,
|
||||
{
|
||||
if let Some(assets) = ASSETS.read().unwrap().get(specifier) {
|
||||
return Ok(Arc::clone(assets).downcast()?);
|
||||
}
|
||||
|
||||
match get_glob_matches(specifier) {
|
||||
Ok(glob_matches) => {
|
||||
let assets = Arc::new(
|
||||
glob_matches
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
Self::load(&name)
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
?e,
|
||||
"Failed to load \"{}\" as part of glob \"{}\"",
|
||||
name,
|
||||
specifier
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
let clone = Arc::clone(&assets);
|
||||
|
||||
let mut assets_write = ASSETS.write().unwrap();
|
||||
assets_write.insert(specifier.to_owned(), clone);
|
||||
Ok(assets)
|
||||
},
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_glob_cloned(specifier: &str) -> Result<Vec<(Self::Output, String)>, Error>
|
||||
where
|
||||
Self::Output: Clone + Send + Sync + 'static,
|
||||
{
|
||||
match get_glob_matches(specifier) {
|
||||
Ok(glob_matches) => Ok(glob_matches
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let full_specifier = &specifier.replace("*", &name);
|
||||
(
|
||||
Self::load_expect_cloned(full_specifier),
|
||||
full_specifier.to_string(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AssetExt: Sized + Send + Sync + 'static {
|
||||
/// Function used to load assets from the filesystem or the cache.
|
||||
/// Example usage:
|
||||
/// ```no_run
|
||||
/// use image::DynamicImage;
|
||||
/// use veloren_common::assets::Asset;
|
||||
/// use veloren_common::assets::{self, AssetExt};
|
||||
///
|
||||
/// let my_image = DynamicImage::load("core.ui.backgrounds.city").unwrap();
|
||||
/// let my_image = assets::Image::load("core.ui.backgrounds.city").unwrap();
|
||||
/// ```
|
||||
fn load(specifier: &str) -> Result<Arc<Self::Output>, Error>
|
||||
where
|
||||
Self::Output: Send + Sync + 'static,
|
||||
{
|
||||
Self::load_map(specifier, |x| x)
|
||||
}
|
||||
fn load(specifier: &str) -> Result<AssetHandle<Self>, Error>;
|
||||
|
||||
/// Function used to load assets from the filesystem or the cache and return
|
||||
/// a clone.
|
||||
fn load_cloned(specifier: &str) -> Result<Self::Output, Error>
|
||||
fn load_cloned(specifier: &str) -> Result<Self, Error>
|
||||
where
|
||||
Self::Output: Clone + Send + Sync + 'static,
|
||||
Self: Clone,
|
||||
{
|
||||
Self::load(specifier).map(|asset| (*asset).clone())
|
||||
Self::load(specifier).map(AssetHandle::cloned)
|
||||
}
|
||||
|
||||
/// Function used to load essential assets from the filesystem or the cache.
|
||||
/// It will panic if the asset is not found. Example usage:
|
||||
/// ```no_run
|
||||
/// use image::DynamicImage;
|
||||
/// use veloren_common::assets::Asset;
|
||||
/// use veloren_common::assets::{self, AssetExt};
|
||||
///
|
||||
/// let my_image = DynamicImage::load_expect("core.ui.backgrounds.city");
|
||||
/// let my_image = assets::Image::load_expect("core.ui.backgrounds.city");
|
||||
/// ```
|
||||
fn load_expect(specifier: &str) -> Arc<Self::Output>
|
||||
where
|
||||
Self::Output: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn load_expect(specifier: &str) -> AssetHandle<Self> {
|
||||
Self::load(specifier).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed loading essential asset: {} (error={:?})",
|
||||
@ -226,121 +63,79 @@ pub trait Asset: Sized {
|
||||
|
||||
/// Function used to load essential assets from the filesystem or the cache
|
||||
/// and return a clone. It will panic if the asset is not found.
|
||||
fn load_expect_cloned(specifier: &str) -> Self::Output
|
||||
#[track_caller]
|
||||
fn load_expect_cloned(specifier: &str) -> Self
|
||||
where
|
||||
Self::Output: Clone + Send + Sync + 'static,
|
||||
Self: Clone,
|
||||
{
|
||||
Self::load_expect(specifier).as_ref().clone()
|
||||
Self::load_expect(specifier).cloned()
|
||||
}
|
||||
|
||||
/// Load an asset while registering it to be watched and reloaded when it
|
||||
/// changes
|
||||
fn load_watched(
|
||||
specifier: &str,
|
||||
indicator: &mut watch::ReloadIndicator,
|
||||
) -> Result<Arc<Self::Output>, Error>
|
||||
where
|
||||
Self::Output: Send + Sync + 'static,
|
||||
{
|
||||
let asset = Self::load(specifier)?;
|
||||
fn load_owned(specifier: &str) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
// Determine path to watch
|
||||
let path = unpack_specifier(specifier);
|
||||
let mut path_with_extension = None;
|
||||
for ending in Self::ENDINGS {
|
||||
let mut path = path.clone();
|
||||
path.set_extension(ending);
|
||||
pub fn load_dir<T: Asset>(specifier: &str) -> Result<AssetDir<T>, Error> {
|
||||
Ok(ASSETS.load_dir(specifier)?)
|
||||
}
|
||||
|
||||
if path.exists() {
|
||||
path_with_extension = Some(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
impl<T: Compound> AssetExt for T {
|
||||
fn load(specifier: &str) -> Result<AssetHandle<Self>, Error> {
|
||||
ASSETS.load(specifier)
|
||||
}
|
||||
|
||||
let owned_specifier = specifier.to_string();
|
||||
indicator.add(
|
||||
path_with_extension
|
||||
.ok_or_else(|| Error::NotFound(path.to_string_lossy().into_owned()))?,
|
||||
move || {
|
||||
if let Err(e) = reload::<Self>(&owned_specifier) {
|
||||
error!(?e, ?owned_specifier, "Error reloading owned_specifier");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Ok(asset)
|
||||
fn load_owned(specifier: &str) -> Result<Self, Error> {
|
||||
ASSETS.load_owned(specifier)
|
||||
}
|
||||
}
|
||||
|
||||
impl Asset for DynamicImage {
|
||||
const ENDINGS: &'static [&'static str] = &["png", "jpg"];
|
||||
pub struct Image(pub Arc<DynamicImage>);
|
||||
|
||||
fn parse(mut buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, Error> {
|
||||
let mut buf = Vec::new();
|
||||
buf_reader.read_to_end(&mut buf)?;
|
||||
image::load_from_memory(&buf).map_err(Error::parse_error)
|
||||
impl Image {
|
||||
pub fn to_image(&self) -> Arc<DynamicImage> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Asset for DotVoxData {
|
||||
const ENDINGS: &'static [&'static str] = &["vox"];
|
||||
|
||||
fn parse(mut buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, Error> {
|
||||
let mut buf = Vec::new();
|
||||
buf_reader.read_to_end(&mut buf)?;
|
||||
dot_vox::load_bytes(&buf).map_err(Error::parse_error)
|
||||
pub struct ImageLoader;
|
||||
impl Loader<Image> for ImageLoader {
|
||||
fn load(content: Cow<[u8]>, _: &str) -> Result<Image, BoxedError> {
|
||||
let image = image::load_from_memory(&content)?;
|
||||
Ok(Image(Arc::new(image)))
|
||||
}
|
||||
}
|
||||
|
||||
// Read a JSON file
|
||||
impl Asset for Value {
|
||||
const ENDINGS: &'static [&'static str] = &["json"];
|
||||
impl Asset for Image {
|
||||
const EXTENSIONS: &'static [&'static str] = &["png", "jpg"];
|
||||
type Loader = ImageLoader;
|
||||
}
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, Error> {
|
||||
serde_json::from_reader(buf_reader).map_err(Error::parse_error)
|
||||
pub struct DotVoxAsset(pub DotVoxData);
|
||||
|
||||
pub struct DotVoxLoader;
|
||||
impl Loader<DotVoxAsset> for DotVoxLoader {
|
||||
fn load(content: std::borrow::Cow<[u8]>, _: &str) -> Result<DotVoxAsset, BoxedError> {
|
||||
let data = dot_vox::load_bytes(&content).map_err(|err| err.to_owned())?;
|
||||
Ok(DotVoxAsset(data))
|
||||
}
|
||||
}
|
||||
|
||||
/// Load from an arbitrary RON file.
|
||||
pub struct Ron<T>(pub PhantomData<T>);
|
||||
#[derive(Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Ron<T>(pub T);
|
||||
|
||||
impl<T: Send + Sync + for<'de> Deserialize<'de>> Asset for Ron<T> {
|
||||
type Output = T;
|
||||
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<T, Error> {
|
||||
ron::de::from_reader(buf_reader).map_err(Error::parse_error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load from a specific asset path.
|
||||
pub struct AssetWith<T: Asset, const ASSET_PATH: &'static str> {
|
||||
pub asset: Arc<T::Output>,
|
||||
}
|
||||
|
||||
impl<T: Asset, const ASSET_PATH: &'static str> Clone for AssetWith<T, ASSET_PATH> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
asset: Arc::clone(&self.asset),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Asset, const ASSET_PATH: &'static str> AssetWith<T, ASSET_PATH>
|
||||
impl<T> Asset for Ron<T>
|
||||
where
|
||||
T::Output: Send + Sync + 'static,
|
||||
T: Send + Sync + for<'de> Deserialize<'de> + 'static,
|
||||
{
|
||||
#[inline]
|
||||
pub fn load_watched(indicator: &mut watch::ReloadIndicator) -> Result<Self, Error> {
|
||||
T::load_watched(ASSET_PATH, indicator).map(|asset| Self { asset })
|
||||
}
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = RonLoader;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reload(&mut self) -> Result<(), Error> {
|
||||
self.asset = T::load(ASSET_PATH)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Asset for DotVoxAsset {
|
||||
const EXTENSION: &'static str = "vox";
|
||||
type Loader = DotVoxLoader;
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
@ -423,86 +218,42 @@ lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts a specifier like "core.backgrounds.city" to
|
||||
/// ".../veloren/assets/core/backgrounds/city".
|
||||
fn unpack_specifier(specifier: &str) -> PathBuf {
|
||||
let mut path = ASSETS_PATH.clone();
|
||||
path.push(specifier.replace(".", "/"));
|
||||
path
|
||||
}
|
||||
fn get_dir_files(files: &mut Vec<String>, path: &Path, specifier: &str) -> io::Result<()> {
|
||||
for entry in fs::read_dir(path)? {
|
||||
if let Ok(entry) = entry {
|
||||
let path = entry.path();
|
||||
let maybe_stem = path.file_stem().and_then(|stem| stem.to_str());
|
||||
|
||||
/// Loads a file based on the specifier and possible extensions
|
||||
pub fn load_file(specifier: &str, endings: &[&str]) -> Result<BufReader<File>, Error> {
|
||||
let path = unpack_specifier(specifier);
|
||||
for ending in endings {
|
||||
let mut path = path.clone();
|
||||
path.set_extension(ending);
|
||||
if let Some(stem) = maybe_stem {
|
||||
let specifier = format!("{}.{}", specifier, stem);
|
||||
|
||||
trace!(?path, "Trying to access");
|
||||
if let Ok(file) = File::open(path) {
|
||||
return Ok(BufReader::new(file));
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::NotFound(path.to_string_lossy().into_owned()))
|
||||
}
|
||||
|
||||
/// Loads a file based on the specifier and possible extensions
|
||||
pub fn load_file_glob(specifier: &str, endings: &[&str]) -> Result<BufReader<File>, Error> {
|
||||
let path = unpack_specifier(specifier);
|
||||
for ending in endings {
|
||||
let mut path = path.clone();
|
||||
path.set_extension(ending);
|
||||
|
||||
trace!(?path, "Trying to access");
|
||||
if let Ok(file) = File::open(path) {
|
||||
return Ok(BufReader::new(file));
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::NotFound(path.to_string_lossy().into_owned()))
|
||||
}
|
||||
|
||||
/// Read directory from `veloren/assets/*`
|
||||
pub fn read_dir(specifier: &str) -> Result<ReadDir, Error> {
|
||||
let dir_name = unpack_specifier(specifier);
|
||||
if dir_name.exists() {
|
||||
Ok(fs::read_dir(dir_name).expect("`read_dir` failed."))
|
||||
} else {
|
||||
Err(Error::NotFound(dir_name.to_string_lossy().into_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
// Finds all files matching the provided glob specifier - includes files from
|
||||
// subdirectories
|
||||
fn get_glob_matches(specifier: &str) -> Result<Vec<String>, Error> {
|
||||
let specifier = specifier.trim_end_matches(".*");
|
||||
read_dir(specifier).map(|dir| {
|
||||
dir.filter_map(|direntry| {
|
||||
direntry.ok().and_then(|dir_entry| {
|
||||
if dir_entry.path().is_dir() {
|
||||
let sub_dir_glob = format!(
|
||||
"{}.{}.*",
|
||||
specifier.to_string(),
|
||||
dir_entry.file_name().to_string_lossy()
|
||||
);
|
||||
Some(get_glob_matches(&sub_dir_glob).ok()?)
|
||||
if path.is_dir() {
|
||||
get_dir_files(files, &path, &specifier)?;
|
||||
} else {
|
||||
Some(vec![format!(
|
||||
"{}.{}",
|
||||
specifier,
|
||||
dir_entry
|
||||
.file_name()
|
||||
.to_string_lossy()
|
||||
.rsplitn(2, '.')
|
||||
.last()
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap()
|
||||
)])
|
||||
files.push(specifier);
|
||||
}
|
||||
})
|
||||
})
|
||||
.flat_map(|x| x)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Directory(Vec<String>);
|
||||
|
||||
impl Directory {
|
||||
pub fn iter(&self) -> impl Iterator<Item=&String> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Compound for Directory {
|
||||
fn load<S: source::Source>(_: &AssetCache<S>, specifier: &str) -> Result<Self, Error> {
|
||||
let root = ASSETS.source().path_of(specifier, "");
|
||||
let mut files = Vec::new();
|
||||
|
||||
get_dir_files(&mut files, &root, specifier)?;
|
||||
|
||||
Ok(Directory(files))
|
||||
}
|
||||
}
|
@ -151,8 +151,9 @@ lazy_static! {
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
/// TODO: Make this use hot-reloading
|
||||
static ref ENTITIES: Vec<String> = {
|
||||
let npc_names = &*npc::NPC_NAMES;
|
||||
let npc_names = &*npc::NPC_NAMES.read();
|
||||
npc::ALL_NPCS
|
||||
.iter()
|
||||
.map(|&npc| npc_names[npc].keyword.clone())
|
||||
|
@ -16,7 +16,8 @@ use arraygen::Arraygen;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{fs::File, io::BufReader, time::Duration};
|
||||
use tracing::error;
|
||||
use std::time::Duration;
|
||||
use vek::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
@ -238,10 +239,18 @@ impl Default for CharacterAbility {
|
||||
}
|
||||
|
||||
impl Asset for CharacterAbility {
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
|
||||
ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error)
|
||||
fn default_value(specifier: &str, err: assets::Error) -> Result<Self, assets::Error> {
|
||||
error!(
|
||||
?err,
|
||||
"Error loading CharacterAbility: {} for the ability \
|
||||
map: replacing with default",
|
||||
specifier
|
||||
);
|
||||
|
||||
Ok(CharacterAbility::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@ use crate::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{fs::File, io::BufReader};
|
||||
use vek::*;
|
||||
|
||||
make_case_elim!(
|
||||
@ -123,15 +122,12 @@ impl<'a, BodyMeta, SpeciesMeta> core::ops::Index<&'a Body> for AllBodies<BodyMet
|
||||
}
|
||||
|
||||
impl<
|
||||
BodyMeta: Send + Sync + for<'de> serde::Deserialize<'de>,
|
||||
SpeciesMeta: Send + Sync + for<'de> serde::Deserialize<'de>,
|
||||
BodyMeta: Send + Sync + for<'de> serde::Deserialize<'de> + 'static,
|
||||
SpeciesMeta: Send + Sync + for<'de> serde::Deserialize<'de> + 'static,
|
||||
> Asset for AllBodies<BodyMeta, SpeciesMeta>
|
||||
{
|
||||
const ENDINGS: &'static [&'static str] = &["json"];
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
|
||||
serde_json::de::from_reader(buf_reader).map_err(assets::Error::parse_error)
|
||||
}
|
||||
const EXTENSION: &'static str = "json";
|
||||
type Loader = assets::JsonLoader;
|
||||
}
|
||||
|
||||
impl Body {
|
||||
|
@ -5,7 +5,7 @@ pub mod tool;
|
||||
pub use tool::{AbilitySet, Hands, Tool, ToolKind, UniqueKind};
|
||||
|
||||
use crate::{
|
||||
assets::{self, Asset, Error},
|
||||
assets::{self, AssetExt, Error},
|
||||
effect::Effect,
|
||||
lottery::Lottery,
|
||||
terrain::{Block, SpriteKind},
|
||||
@ -16,8 +16,6 @@ use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::BufReader,
|
||||
num::{NonZeroU32, NonZeroU64},
|
||||
sync::Arc,
|
||||
};
|
||||
@ -160,28 +158,37 @@ impl PartialEq for Item {
|
||||
}
|
||||
}
|
||||
|
||||
impl Asset for ItemDef {
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
impl assets::Compound for ItemDef {
|
||||
fn load<S: assets_manager::source::Source>(cache: &assets_manager::AssetCache<S>, specifier: &str) -> Result<Self, Error> {
|
||||
let raw = cache.load_owned::<RawItemDef>(specifier)?;
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self, assets::Error> {
|
||||
let item: Result<Self, Error> =
|
||||
ron::de::from_reader(buf_reader).map_err(Error::parse_error);
|
||||
let RawItemDef { name, description, kind, quality} = raw;
|
||||
let item_definition_id = specifier.replace('\\', ".");
|
||||
|
||||
// Some commands like /give_item provide the asset specifier separated with \
|
||||
// instead of .
|
||||
let specifier = specifier.replace('\\', ".");
|
||||
|
||||
item.map(|item| ItemDef {
|
||||
item_definition_id: specifier,
|
||||
..item
|
||||
})
|
||||
Ok(ItemDef { item_definition_id, name, description, kind, quality })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename = "ItemDef")]
|
||||
struct RawItemDef {
|
||||
name: String,
|
||||
description: String,
|
||||
kind: ItemKind,
|
||||
quality: Quality,
|
||||
}
|
||||
|
||||
impl assets::Asset for RawItemDef {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
}
|
||||
|
||||
impl Item {
|
||||
// TODO: consider alternatives such as default abilities that can be added to a
|
||||
// loadout when no weapon is present
|
||||
pub fn empty() -> Self { Item::new(ItemDef::load_expect("common.items.weapons.empty.empty")) }
|
||||
pub fn empty() -> Self {
|
||||
Item::new_from_asset_expect("common.items.weapons.empty.empty")
|
||||
}
|
||||
|
||||
pub fn new(inner_item: Arc<ItemDef>) -> Self {
|
||||
Item {
|
||||
@ -194,27 +201,36 @@ impl Item {
|
||||
/// Creates a new instance of an `Item` from the provided asset identifier
|
||||
/// Panics if the asset does not exist.
|
||||
pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
|
||||
let inner_item = ItemDef::load_expect(asset_specifier);
|
||||
let inner_item = Arc::<ItemDef>::load_expect_cloned(asset_specifier);
|
||||
Item::new(inner_item)
|
||||
}
|
||||
|
||||
/// Creates a Vec containing one of each item that matches the provided
|
||||
/// asset glob pattern
|
||||
pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
|
||||
let items = ItemDef::load_glob(asset_glob)?;
|
||||
//let items = ItemDef::load_glob(asset_glob)?;
|
||||
|
||||
let specifiers = assets::Directory::load(asset_glob)?;
|
||||
|
||||
specifiers.read()
|
||||
.iter()
|
||||
.map(|spec| Self::new_from_asset(&spec))
|
||||
.collect()
|
||||
|
||||
/*
|
||||
let result = items
|
||||
.iter()
|
||||
.map(|item_def| Item::new(Arc::clone(item_def)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(result)
|
||||
*/
|
||||
}
|
||||
|
||||
/// Creates a new instance of an `Item from the provided asset identifier if
|
||||
/// it exists
|
||||
pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
|
||||
let inner_item = ItemDef::load(asset)?;
|
||||
let inner_item = Arc::<ItemDef>::load_cloned(asset)?;
|
||||
Ok(Item::new(inner_item))
|
||||
}
|
||||
|
||||
@ -254,30 +270,30 @@ impl Item {
|
||||
/// up by another player.
|
||||
pub fn put_in_world(&mut self) { self.reset_item_id() }
|
||||
|
||||
pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), assets::Error> {
|
||||
pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), ()> {
|
||||
let amount = u32::from(self.amount);
|
||||
self.amount = amount
|
||||
.checked_add(increase_by)
|
||||
.and_then(NonZeroU32::new)
|
||||
.ok_or(assets::Error::InvalidType)?;
|
||||
.ok_or(())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), assets::Error> {
|
||||
pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), ()> {
|
||||
let amount = u32::from(self.amount);
|
||||
self.amount = amount
|
||||
.checked_sub(decrease_by)
|
||||
.and_then(NonZeroU32::new)
|
||||
.ok_or(assets::Error::InvalidType)?;
|
||||
.ok_or(())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), assets::Error> {
|
||||
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), ()> {
|
||||
if give_amount == 1 || self.item_def.is_stackable() {
|
||||
self.amount = NonZeroU32::new(give_amount).ok_or(assets::Error::InvalidType)?;
|
||||
self.amount = NonZeroU32::new(give_amount).ok_or(())?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(assets::Error::InvalidType)
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,7 +343,7 @@ impl Item {
|
||||
3 => "common.loot_tables.loot_table_armor_cloth",
|
||||
4 => "common.loot_tables.loot_table_armor_heavy",
|
||||
_ => "common.loot_tables.loot_table_armor_misc",
|
||||
});
|
||||
}).read();
|
||||
chosen.choose()
|
||||
},
|
||||
SpriteKind::ChestBurried => {
|
||||
@ -336,7 +352,7 @@ impl Item {
|
||||
2 => "common.loot_tables.loot_table_armor_light",
|
||||
3 => "common.loot_tables.loot_table_armor_cloth",
|
||||
_ => "common.loot_tables.loot_table_armor_misc",
|
||||
});
|
||||
}).read();
|
||||
chosen.choose()
|
||||
},
|
||||
SpriteKind::Mud => {
|
||||
@ -345,14 +361,14 @@ impl Item {
|
||||
1 => "common.loot_tables.loot_table_weapon_common",
|
||||
2 => "common.loot_tables.loot_table_armor_misc",
|
||||
_ => "common.loot_tables.loot_table_rocks",
|
||||
});
|
||||
}).read();
|
||||
chosen.choose()
|
||||
},
|
||||
SpriteKind::Crate => {
|
||||
chosen = Lottery::<String>::load_expect(match rng.gen_range(0, 4) {
|
||||
0 => "common.loot_tables.loot_table_crafting",
|
||||
_ => "common.loot_tables.loot_table_food",
|
||||
});
|
||||
}).read();
|
||||
chosen.choose()
|
||||
},
|
||||
SpriteKind::Beehive => "common.items.crafting_ing.honey",
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs::File, io::BufReader, time::Duration};
|
||||
use std::{time::Duration};
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
@ -107,7 +107,7 @@ pub struct AbilitySet<T> {
|
||||
}
|
||||
|
||||
impl AbilitySet<CharacterAbility> {
|
||||
pub fn modified_by_tool(self, tool: &Tool) -> Self {
|
||||
fn modified_by_tool(self, tool: &Tool) -> Self {
|
||||
self.map(|a| a.adjusted_by_stats(tool.base_power(), tool.base_speed()))
|
||||
}
|
||||
}
|
||||
@ -120,6 +120,14 @@ impl<T> AbilitySet<T> {
|
||||
skills: self.skills.into_iter().map(|x| f(x)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilitySet<U> {
|
||||
AbilitySet {
|
||||
primary: f(&self.primary),
|
||||
secondary: f(&self.secondary),
|
||||
skills: self.skills.iter().map(|x| f(x)).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AbilitySet<CharacterAbility> {
|
||||
@ -135,37 +143,28 @@ impl Default for AbilitySet<CharacterAbility> {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AbilityMap<T = CharacterAbility>(HashMap<ToolKind, AbilitySet<T>>);
|
||||
|
||||
impl Asset for AbilityMap {
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
impl Asset for AbilityMap<String> {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
}
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self, assets::Error> {
|
||||
ron::de::from_reader::<BufReader<File>, AbilityMap<String>>(buf_reader)
|
||||
.map(|map| {
|
||||
AbilityMap(
|
||||
map.0
|
||||
.into_iter()
|
||||
.map(|(kind, set)| {
|
||||
(
|
||||
kind,
|
||||
set.map(|s| match CharacterAbility::load(&s) {
|
||||
Ok(ability) => ability.as_ref().clone(),
|
||||
Err(err) => {
|
||||
error!(
|
||||
?err,
|
||||
"Error loading CharacterAbility: {} for the ability \
|
||||
map: {} replacing with default",
|
||||
s,
|
||||
specifier
|
||||
);
|
||||
CharacterAbility::default()
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.map_err(assets::Error::parse_error)
|
||||
impl assets::Compound for AbilityMap {
|
||||
fn load<S: assets_manager::source::Source>(cache: &assets_manager::AssetCache<S>, specifier: &str) -> Result<Self, assets::Error> {
|
||||
let manifest = cache.load::<AbilityMap<String>>(specifier)?.read();
|
||||
|
||||
Ok(AbilityMap(
|
||||
manifest.0
|
||||
.iter()
|
||||
.map(|(kind, set)| {
|
||||
(
|
||||
kind.clone(),
|
||||
// expect cannot fail because CharacterAbility always
|
||||
// provides a default value in case of failure
|
||||
set.map_ref(|s| cache.load_expect(&s).cloned())
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ fn loadout_insert(
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::{
|
||||
/// assets::Asset,
|
||||
/// assets::AssetExt,
|
||||
/// comp::{
|
||||
/// item::tool::AbilityMap,
|
||||
/// slot::{loadout_remove, EquipSlot},
|
||||
@ -263,7 +263,7 @@ pub fn swap(
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::{
|
||||
/// assets::Asset,
|
||||
/// assets::AssetExt,
|
||||
/// comp::{
|
||||
/// item::tool::AbilityMap,
|
||||
/// slot::{equip, EquipSlot},
|
||||
@ -324,7 +324,7 @@ pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout, map:
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::{
|
||||
/// assets::Asset,
|
||||
/// assets::AssetExt,
|
||||
/// comp::{
|
||||
/// item::tool::AbilityMap,
|
||||
/// slot::{unequip, EquipSlot},
|
||||
@ -365,7 +365,7 @@ pub fn unequip(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{comp::Item, LoadoutBuilder};
|
||||
use crate::{assets::AssetExt, comp::Item, LoadoutBuilder};
|
||||
|
||||
#[test]
|
||||
fn test_unequip_items_both_hands() {
|
||||
@ -374,7 +374,6 @@ mod tests {
|
||||
amount: 0,
|
||||
};
|
||||
|
||||
use crate::assets::Asset;
|
||||
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
|
||||
let sword = LoadoutBuilder::default_item_config_from_str(
|
||||
@ -419,7 +418,6 @@ mod tests {
|
||||
|
||||
let mut loadout = LoadoutBuilder::new().defaults().build();
|
||||
|
||||
use crate::assets::Asset;
|
||||
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
|
||||
// We should start with the starting sandles
|
||||
@ -444,7 +442,6 @@ mod tests {
|
||||
"common.items.armor.starter.sandals_0",
|
||||
));
|
||||
|
||||
use crate::assets::Asset;
|
||||
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
|
||||
let mut loadout = LoadoutBuilder::new().defaults().build();
|
||||
@ -469,7 +466,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_loadout_remove() {
|
||||
use crate::assets::Asset;
|
||||
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
|
||||
let sword = LoadoutBuilder::default_item_config_from_str(
|
||||
|
@ -115,22 +115,23 @@ impl EntityInfo {
|
||||
}
|
||||
|
||||
pub fn with_automatic_name(mut self) -> Self {
|
||||
let npc_names = NPC_NAMES.read();
|
||||
self.name = match &self.body {
|
||||
Body::Humanoid(body) => Some(get_npc_name(&NPC_NAMES.humanoid, body.species)),
|
||||
Body::Humanoid(body) => Some(get_npc_name(&npc_names.humanoid, body.species)),
|
||||
Body::QuadrupedMedium(body) => {
|
||||
Some(get_npc_name(&NPC_NAMES.quadruped_medium, body.species))
|
||||
Some(get_npc_name(&npc_names.quadruped_medium, body.species))
|
||||
},
|
||||
Body::BirdMedium(body) => Some(get_npc_name(&NPC_NAMES.bird_medium, body.species)),
|
||||
Body::FishSmall(body) => Some(get_npc_name(&NPC_NAMES.fish_small, body.species)),
|
||||
Body::FishMedium(body) => Some(get_npc_name(&NPC_NAMES.fish_medium, body.species)),
|
||||
Body::Theropod(body) => Some(get_npc_name(&NPC_NAMES.theropod, body.species)),
|
||||
Body::BirdMedium(body) => Some(get_npc_name(&npc_names.bird_medium, body.species)),
|
||||
Body::FishSmall(body) => Some(get_npc_name(&npc_names.fish_small, body.species)),
|
||||
Body::FishMedium(body) => Some(get_npc_name(&npc_names.fish_medium, body.species)),
|
||||
Body::Theropod(body) => Some(get_npc_name(&npc_names.theropod, body.species)),
|
||||
Body::QuadrupedSmall(body) => {
|
||||
Some(get_npc_name(&NPC_NAMES.quadruped_small, body.species))
|
||||
Some(get_npc_name(&npc_names.quadruped_small, body.species))
|
||||
},
|
||||
Body::Dragon(body) => Some(get_npc_name(&NPC_NAMES.dragon, body.species)),
|
||||
Body::QuadrupedLow(body) => Some(get_npc_name(&NPC_NAMES.quadruped_low, body.species)),
|
||||
Body::Golem(body) => Some(get_npc_name(&NPC_NAMES.golem, body.species)),
|
||||
Body::BipedLarge(body) => Some(get_npc_name(&NPC_NAMES.biped_large, body.species)),
|
||||
Body::Dragon(body) => Some(get_npc_name(&npc_names.dragon, body.species)),
|
||||
Body::QuadrupedLow(body) => Some(get_npc_name(&npc_names.quadruped_low, body.species)),
|
||||
Body::Golem(body) => Some(get_npc_name(&npc_names.golem, body.species)),
|
||||
Body::BipedLarge(body) => Some(get_npc_name(&npc_names.biped_large, body.species)),
|
||||
_ => None,
|
||||
}
|
||||
.map(|s| {
|
||||
|
@ -11,7 +11,7 @@ use rand::Rng;
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::{
|
||||
/// assets::Asset,
|
||||
/// assets::AssetExt,
|
||||
/// comp::item::tool::AbilityMap,
|
||||
/// LoadoutBuilder,
|
||||
/// };
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::assets::{self, Asset};
|
||||
use crate::assets;
|
||||
use rand::prelude::*;
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use std::{fs::File, io::BufReader};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
pub struct Lottery<T> {
|
||||
@ -9,28 +8,25 @@ pub struct Lottery<T> {
|
||||
total: f32,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned + Send + Sync> Asset for Lottery<T> {
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
impl<T: DeserializeOwned + Send + Sync + 'static> assets::Asset for Lottery<T> {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::LoadFrom<Vec<(f32, T)>, assets::RonLoader>;
|
||||
}
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
|
||||
ron::de::from_reader::<BufReader<File>, Vec<(f32, T)>>(buf_reader)
|
||||
.map(|items| Lottery::from_rates(items.into_iter()))
|
||||
.map_err(assets::Error::parse_error)
|
||||
impl<T> From<Vec<(f32, T)>> for Lottery<T> {
|
||||
fn from(mut items: Vec<(f32, T)>) -> Lottery<T> {
|
||||
let mut total = 0.0;
|
||||
|
||||
for (rate, _) in &mut items {
|
||||
total += *rate;
|
||||
*rate = total - *rate;
|
||||
}
|
||||
|
||||
Self { items, total }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Lottery<T> {
|
||||
pub fn from_rates(items: impl Iterator<Item = (f32, T)>) -> Self {
|
||||
let mut total = 0.0;
|
||||
let items = items
|
||||
.map(|(rate, item)| {
|
||||
total += rate;
|
||||
(total - rate, item)
|
||||
})
|
||||
.collect();
|
||||
Self { items, total }
|
||||
}
|
||||
|
||||
pub fn choose_seeded(&self, seed: u32) -> &T {
|
||||
let x = ((seed % 65536) as f32 / 65536.0) * self.total;
|
||||
&self.items[self
|
||||
@ -48,13 +44,13 @@ impl<T> Lottery<T> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assets::Asset, comp::Item};
|
||||
use crate::{assets::AssetExt, comp::Item};
|
||||
|
||||
#[test]
|
||||
fn test_loot_table() {
|
||||
let test = Lottery::<String>::load_expect("common.loot_tables.loot_table");
|
||||
|
||||
for (_, item_asset_specifier) in test.iter() {
|
||||
for (_, item_asset_specifier) in test.read().iter() {
|
||||
assert!(
|
||||
Item::new_from_asset(item_asset_specifier).is_ok(),
|
||||
"Invalid loot table item '{}'",
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
assets::Asset,
|
||||
assets::{AssetExt, AssetHandle},
|
||||
comp::{self, AllBodies, Body},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use rand::seq::SliceRandom;
|
||||
use serde::Deserialize;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum NpcKind {
|
||||
@ -67,14 +67,14 @@ pub struct SpeciesNames {
|
||||
pub type NpcNames = AllBodies<BodyNames, SpeciesNames>;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref NPC_NAMES: Arc<NpcNames> = NpcNames::load_expect("common.npc_names");
|
||||
pub static ref NPC_NAMES: AssetHandle<NpcNames> = NpcNames::load_expect("common.npc_names");
|
||||
}
|
||||
|
||||
impl FromStr for NpcKind {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let npc_names = &*NPC_NAMES;
|
||||
fn from_str(s: &str) -> Result<Self, ()> {
|
||||
let npc_names = &*NPC_NAMES.read();
|
||||
ALL_NPCS
|
||||
.iter()
|
||||
.copied()
|
||||
@ -83,11 +83,12 @@ impl FromStr for NpcKind {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_npc_name(npc_type: NpcKind) -> &'static str {
|
||||
let BodyNames { keyword, names } = &NPC_NAMES[npc_type];
|
||||
pub fn get_npc_name(npc_type: NpcKind) -> String {
|
||||
let npc_names = NPC_NAMES.read();
|
||||
let BodyNames { keyword, names } = &npc_names[npc_type];
|
||||
|
||||
// If no pretty name is found, fall back to the keyword.
|
||||
names.choose(&mut rand::thread_rng()).unwrap_or(keyword)
|
||||
names.choose(&mut rand::thread_rng()).unwrap_or(keyword).clone()
|
||||
}
|
||||
|
||||
/// Randomly generates a body associated with this NPC kind.
|
||||
@ -164,7 +165,7 @@ impl NpcBody {
|
||||
)
|
||||
})
|
||||
}
|
||||
let npc_names = &NPC_NAMES;
|
||||
let npc_names = &*NPC_NAMES.read();
|
||||
// First, parse npc kind names.
|
||||
NpcKind::from_str(s)
|
||||
.map(|kind| NpcBody(kind, Box::new(move || kind_to_body(kind))))
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::{
|
||||
assets::{self, Asset},
|
||||
assets::{self, AssetExt, AssetHandle},
|
||||
comp::{item::ItemDef, Inventory, Item},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs::File, io::BufReader, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Recipe {
|
||||
@ -65,36 +65,40 @@ impl RecipeBook {
|
||||
}
|
||||
}
|
||||
|
||||
impl Asset for RecipeBook {
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
#[derive(Deserialize)]
|
||||
#[serde(transparent)]
|
||||
struct RawRecipeBook(HashMap<String, ((String, u32), Vec<(String, u32)>)>);
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
|
||||
ron::de::from_reader::<
|
||||
BufReader<File>,
|
||||
HashMap<String, ((String, u32), Vec<(String, u32)>)>,
|
||||
>(buf_reader)
|
||||
.map_err(assets::Error::parse_error)
|
||||
.and_then(|recipes| {
|
||||
Ok(RecipeBook {
|
||||
recipes: recipes
|
||||
.into_iter()
|
||||
.map::<Result<(String, Recipe), assets::Error>, _>(
|
||||
|(name, ((output, amount), inputs))| {
|
||||
Ok((name, Recipe {
|
||||
output: (ItemDef::load(&output)?, amount),
|
||||
inputs: inputs
|
||||
.into_iter()
|
||||
.map::<Result<(Arc<ItemDef>, u32), assets::Error>, _>(
|
||||
|(name, amount)| Ok((ItemDef::load(&name)?, amount)),
|
||||
)
|
||||
.collect::<Result<_, _>>()?,
|
||||
}))
|
||||
},
|
||||
)
|
||||
.collect::<Result<_, _>>()?,
|
||||
impl assets::Asset for RawRecipeBook {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
}
|
||||
|
||||
impl assets::Compound for RecipeBook {
|
||||
|
||||
fn load<S: assets_manager::source::Source>(cache: &assets_manager::AssetCache<S>, specifier: &str) -> Result<Self, assets_manager::Error> {
|
||||
#[inline]
|
||||
fn load_item_def(spec: &(String, u32)) -> Result<(Arc<ItemDef>, u32), assets::Error> {
|
||||
let def = Arc::<ItemDef>::load_cloned(&spec.0)?;
|
||||
Ok((def, spec.1))
|
||||
}
|
||||
|
||||
let raw = cache.load::<RawRecipeBook>(specifier)?.read();
|
||||
|
||||
let recipes = raw.0.iter()
|
||||
.map(|(name, (output, inputs))| {
|
||||
let inputs = inputs.iter()
|
||||
.map(load_item_def)
|
||||
.collect::<Result<_, _>>()?;
|
||||
let output = load_item_def(output)?;
|
||||
Ok((name.clone(), Recipe { inputs, output }))
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, assets::Error>>()?;
|
||||
|
||||
Ok(RecipeBook { recipes })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_recipe_book() -> Arc<RecipeBook> { RecipeBook::load_expect("common.recipe_book") }
|
||||
pub fn default_recipe_book() -> AssetHandle<RecipeBook> {
|
||||
RecipeBook::load_expect("common.recipe_book")
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ pub use self::{
|
||||
map::MapSizeLg,
|
||||
site::SitesKind,
|
||||
sprite::SpriteKind,
|
||||
structure::Structure,
|
||||
structure::{Structure, StructuresGroup},
|
||||
};
|
||||
use roots::find_roots_cubic;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -1,13 +1,12 @@
|
||||
use super::BlockKind;
|
||||
use crate::{
|
||||
assets::{self, Asset, Ron},
|
||||
assets::{self, AssetExt, AssetHandle, DotVoxAsset, Error},
|
||||
make_case_elim,
|
||||
vol::{BaseVol, ReadVol, SizedVol, WriteVol},
|
||||
volumes::dyna::{Dyna, DynaError},
|
||||
};
|
||||
use dot_vox::DotVoxData;
|
||||
use serde::Deserialize;
|
||||
use std::{fs::File, io::BufReader, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
make_case_elim!(
|
||||
@ -40,20 +39,46 @@ pub enum StructureError {}
|
||||
#[derive(Clone)]
|
||||
pub struct Structure {
|
||||
center: Vec3<i32>,
|
||||
base: Arc<BaseStructure>,
|
||||
}
|
||||
|
||||
struct BaseStructure {
|
||||
vol: Dyna<StructureBlock, ()>,
|
||||
empty: StructureBlock,
|
||||
default_kind: BlockKind,
|
||||
}
|
||||
|
||||
pub struct StructuresGroup(Vec<Structure>);
|
||||
|
||||
impl std::ops::Deref for StructuresGroup {
|
||||
type Target = [Structure];
|
||||
|
||||
fn deref(&self) -> &[Structure] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl assets::Compound for StructuresGroup {
|
||||
fn load<S: assets_manager::source::Source>(cache: &assets_manager::AssetCache<S>, specifier: &str) -> Result<Self, Error> {
|
||||
let specs = cache.load::<StructuresGroupSpec>(specifier)?.read();
|
||||
|
||||
Ok(StructuresGroup(
|
||||
specs.0.iter()
|
||||
.map(|sp| {
|
||||
let base = cache.load::<Arc<BaseStructure>>(&sp.specifier)?.cloned();
|
||||
Ok(Structure {
|
||||
center: Vec3::from(sp.center),
|
||||
base,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, Error>>()?
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Structure {
|
||||
pub fn load_group(specifier: &str) -> Vec<Arc<Structure>> {
|
||||
let spec = StructuresSpec::load_expect(&["world.manifests.", specifier].concat());
|
||||
spec.iter()
|
||||
.map(|sp| {
|
||||
Structure::load_map(&sp.specifier[..], |s| s.with_center(Vec3::from(sp.center)))
|
||||
.unwrap()
|
||||
})
|
||||
.collect()
|
||||
pub fn load_group(specifier: &str) -> AssetHandle<StructuresGroup> {
|
||||
StructuresGroup::load_expect(&["world.manifests.", specifier].concat())
|
||||
}
|
||||
|
||||
pub fn with_center(mut self, center: Vec3<i32>) -> Self {
|
||||
@ -61,19 +86,14 @@ impl Structure {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_default_kind(mut self, kind: BlockKind) -> Self {
|
||||
self.default_kind = kind;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_bounds(&self) -> Aabb<i32> {
|
||||
Aabb {
|
||||
min: -self.center,
|
||||
max: self.vol.size().map(|e| e as i32) - self.center,
|
||||
max: self.base.vol.size().map(|e| e as i32) - self.center,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_kind(&self) -> BlockKind { self.default_kind }
|
||||
pub fn default_kind(&self) -> BlockKind { self.base.default_kind }
|
||||
}
|
||||
|
||||
impl BaseVol for Structure {
|
||||
@ -84,18 +104,17 @@ impl BaseVol for Structure {
|
||||
impl ReadVol for Structure {
|
||||
#[inline(always)]
|
||||
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, StructureError> {
|
||||
match self.vol.get(pos + self.center) {
|
||||
match self.base.vol.get(pos + self.center) {
|
||||
Ok(block) => Ok(block),
|
||||
Err(DynaError::OutOfBounds) => Ok(&self.empty),
|
||||
Err(DynaError::OutOfBounds) => Ok(&self.base.empty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Asset for Structure {
|
||||
const ENDINGS: &'static [&'static str] = &["vox"];
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self, assets::Error> {
|
||||
let dot_vox_data = DotVoxData::parse(buf_reader, specifier)?;
|
||||
impl assets::Compound for BaseStructure {
|
||||
fn load<S: assets_manager::source::Source>(cache: &assets_manager::AssetCache<S>, specifier: &str) -> Result<Self, Error> {
|
||||
let dot_vox_data = cache.load::<DotVoxAsset>(specifier)?.read();
|
||||
let dot_vox_data = &dot_vox_data.0;
|
||||
|
||||
if let Some(model) = dot_vox_data.models.get(0) {
|
||||
let palette = dot_vox_data
|
||||
@ -138,15 +157,13 @@ impl Asset for Structure {
|
||||
let _ = vol.set(Vec3::new(voxel.x, voxel.y, voxel.z).map(i32::from), block);
|
||||
}
|
||||
|
||||
Ok(Structure {
|
||||
center: Vec3::zero(),
|
||||
Ok(BaseStructure {
|
||||
vol,
|
||||
empty: StructureBlock::None,
|
||||
default_kind: BlockKind::Misc,
|
||||
})
|
||||
} else {
|
||||
Ok(Self {
|
||||
center: Vec3::zero(),
|
||||
Ok(BaseStructure {
|
||||
vol: Dyna::filled(Vec3::zero(), StructureBlock::None, ()),
|
||||
empty: StructureBlock::None,
|
||||
default_kind: BlockKind::Misc,
|
||||
@ -161,4 +178,10 @@ struct StructureSpec {
|
||||
center: [i32; 3],
|
||||
}
|
||||
|
||||
type StructuresSpec = Ron<Vec<StructureSpec>>;
|
||||
#[derive(Deserialize)]
|
||||
struct StructuresGroupSpec(Vec<StructureSpec>);
|
||||
|
||||
impl assets::Asset for StructuresGroupSpec {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
}
|
@ -777,7 +777,7 @@ fn handle_spawn(
|
||||
.state
|
||||
.create_npc(
|
||||
pos,
|
||||
comp::Stats::new(get_npc_name(id).into(), body),
|
||||
comp::Stats::new(get_npc_name(id), body),
|
||||
comp::Health::new(body, 1),
|
||||
loadout,
|
||||
body,
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
Server, SpawnPoint, StateExt,
|
||||
};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
assets::AssetExt,
|
||||
comp::{
|
||||
self, aura, buff,
|
||||
chat::{KillSource, KillType},
|
||||
@ -436,7 +436,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
let item = {
|
||||
let mut item_drops = state.ecs().write_storage::<comp::ItemDrop>();
|
||||
item_drops.remove(entity).map_or_else(
|
||||
|| Item::new_from_asset_expect(lottery().choose()),
|
||||
|| Item::new_from_asset_expect(lottery().read().choose()),
|
||||
|item_drop| item_drop.0,
|
||||
)
|
||||
};
|
||||
|
@ -446,7 +446,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
.write_storage::<comp::Inventory>()
|
||||
.get_mut(entity)
|
||||
{
|
||||
let recipe_book = default_recipe_book();
|
||||
let recipe_book = default_recipe_book().read();
|
||||
let craft_result = recipe_book.get(&recipe).and_then(|r| r.perform(inv).ok());
|
||||
|
||||
// FIXME: We should really require the drop and write to be atomic!
|
||||
|
@ -47,7 +47,7 @@ use crate::{
|
||||
sys::sentinel::{DeletedEntities, TrackedComps},
|
||||
};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
assets::AssetExt,
|
||||
cmd::ChatCommand,
|
||||
comp,
|
||||
event::{EventBus, ServerEvent},
|
||||
@ -939,7 +939,7 @@ impl Server {
|
||||
max_group_size: self.settings().max_player_group_size,
|
||||
client_timeout: self.settings().client_timeout,
|
||||
world_map: self.map.clone(),
|
||||
recipe_book: (&*default_recipe_book()).clone(),
|
||||
recipe_book: default_recipe_book().cloned(),
|
||||
ability_map: (&*self
|
||||
.state
|
||||
.ecs()
|
||||
|
@ -145,11 +145,13 @@ impl<'a> System<'a> for Sys {
|
||||
if entity.is_giant {
|
||||
if rand::random::<f32>() < 0.65 && entity.alignment != Alignment::Enemy {
|
||||
let body_new = comp::humanoid::Body::random();
|
||||
let npc_names = NPC_NAMES.read();
|
||||
|
||||
body = comp::Body::Humanoid(body_new);
|
||||
stats = comp::Stats::new(
|
||||
format!(
|
||||
"Gentle Giant {}",
|
||||
get_npc_name(&NPC_NAMES.humanoid, body_new.species)
|
||||
get_npc_name(&npc_names.humanoid, body_new.species)
|
||||
),
|
||||
body,
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
scene::Camera,
|
||||
};
|
||||
use client::Client;
|
||||
use common::{assets, vol::ReadVol};
|
||||
use common::{assets::{self, AssetExt, AssetHandle}, vol::ReadVol};
|
||||
use common_sys::state::State;
|
||||
use serde::Deserialize;
|
||||
use std::time::Instant;
|
||||
@ -27,7 +27,7 @@ pub struct AmbientItem {
|
||||
}
|
||||
|
||||
pub struct AmbientMgr {
|
||||
soundtrack: AmbientCollection,
|
||||
soundtrack: AssetHandle<AmbientCollection>,
|
||||
began_playing: Instant,
|
||||
next_track_change: f32,
|
||||
volume: f32,
|
||||
@ -56,7 +56,7 @@ impl AmbientMgr {
|
||||
client: &Client,
|
||||
camera: &Camera,
|
||||
) {
|
||||
if audio.sfx_enabled() && !self.soundtrack.tracks.is_empty() {
|
||||
if audio.sfx_enabled() && !self.soundtrack.read().tracks.is_empty() {
|
||||
let focus_off = camera.get_focus_pos().map(f32::trunc);
|
||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||
|
||||
@ -107,8 +107,8 @@ impl AmbientMgr {
|
||||
// Right now there is only wind non-positional sfx so it is always
|
||||
// selected. Modify this variable assignment when adding other non-
|
||||
// positional sfx
|
||||
let track = &self
|
||||
.soundtrack
|
||||
let soundtrack = self.soundtrack.read();
|
||||
let track = &soundtrack
|
||||
.tracks
|
||||
.iter()
|
||||
.find(|track| track.tag == AmbientChannelTag::Wind);
|
||||
@ -123,27 +123,21 @@ impl AmbientMgr {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_soundtrack_items() -> AmbientCollection {
|
||||
match assets::load_file("voxygen.audio.ambient", &["ron"]) {
|
||||
Ok(file) => match ron::de::from_reader(file) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error parsing music config file, music will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
AmbientCollection::default()
|
||||
},
|
||||
},
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error reading music config file, music will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
AmbientCollection::default()
|
||||
},
|
||||
}
|
||||
fn load_soundtrack_items() -> AssetHandle<AmbientCollection> {
|
||||
// Cannot fail: A default value is always provided
|
||||
AmbientCollection::load_expect("voxygen.audio.ambient")
|
||||
}
|
||||
}
|
||||
|
||||
impl assets::Asset for AmbientCollection {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
|
||||
fn default_value(_: &str, error: assets::Error) -> Result<Self, assets::Error> {
|
||||
warn!(
|
||||
"Error reading music config file, music will not be available: {:#?}", error
|
||||
);
|
||||
|
||||
Ok(AmbientCollection::default())
|
||||
}
|
||||
}
|
@ -10,12 +10,12 @@ pub mod soundcache;
|
||||
use channel::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel};
|
||||
use fader::Fader;
|
||||
use sfx::{SfxEvent, SfxTriggerItem};
|
||||
use soundcache::SoundCache;
|
||||
use soundcache::{OggSound, WavSound};
|
||||
use std::time::Duration;
|
||||
use tracing::debug;
|
||||
|
||||
use common::assets;
|
||||
use rodio::{source::Source, Decoder, OutputStream, OutputStreamHandle, StreamError};
|
||||
use common::assets::AssetExt;
|
||||
use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@ -38,7 +38,6 @@ pub struct AudioFrontend {
|
||||
//pub audio_device: Option<Device>,
|
||||
pub stream: Option<rodio::OutputStream>,
|
||||
audio_stream: Option<rodio::OutputStreamHandle>,
|
||||
sound_cache: SoundCache,
|
||||
|
||||
music_channels: Vec<MusicChannel>,
|
||||
ambient_channels: Vec<AmbientChannel>,
|
||||
@ -76,7 +75,6 @@ impl AudioFrontend {
|
||||
//audio_device,
|
||||
stream,
|
||||
audio_stream,
|
||||
sound_cache: SoundCache::default(),
|
||||
music_channels: Vec::new(),
|
||||
sfx_channels,
|
||||
ambient_channels: Vec::new(),
|
||||
@ -95,7 +93,6 @@ impl AudioFrontend {
|
||||
//audio_device: None,
|
||||
stream: None,
|
||||
audio_stream: None,
|
||||
sound_cache: SoundCache::default(),
|
||||
music_channels: Vec::new(),
|
||||
sfx_channels: Vec::new(),
|
||||
ambient_channels: Vec::new(),
|
||||
@ -231,9 +228,9 @@ impl AudioFrontend {
|
||||
/// Play (once) an sfx file by file path at the give position and volume
|
||||
pub fn play_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
|
||||
if self.audio_stream.is_some() {
|
||||
let sound = self
|
||||
.sound_cache
|
||||
.load_sound(sound)
|
||||
let sound = WavSound::load_expect(sound)
|
||||
.cloned()
|
||||
.decoder()
|
||||
.amplify(vol.unwrap_or(1.0));
|
||||
|
||||
let listener = self.listener.clone();
|
||||
@ -250,9 +247,9 @@ impl AudioFrontend {
|
||||
/// being underwater
|
||||
pub fn play_underwater_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
|
||||
if self.audio_stream.is_some() {
|
||||
let sound = self
|
||||
.sound_cache
|
||||
.load_sound(sound)
|
||||
let sound = WavSound::load_expect(sound)
|
||||
.cloned()
|
||||
.decoder()
|
||||
.amplify(vol.unwrap_or(1.0));
|
||||
|
||||
let listener = self.listener.clone();
|
||||
@ -272,9 +269,7 @@ impl AudioFrontend {
|
||||
) {
|
||||
if self.audio_stream.is_some() {
|
||||
if let Some(channel) = self.get_ambient_channel(channel_tag, volume_multiplier) {
|
||||
let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound");
|
||||
let sound = Decoder::new(file).expect("Failed to decode sound");
|
||||
|
||||
let sound = OggSound::load_expect(sound).cloned().decoder();
|
||||
channel.play(sound);
|
||||
}
|
||||
}
|
||||
@ -326,9 +321,7 @@ impl AudioFrontend {
|
||||
fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) {
|
||||
if self.music_enabled() {
|
||||
if let Some(channel) = self.get_music_channel(channel_tag) {
|
||||
let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound");
|
||||
let sound = Decoder::new(file).expect("Failed to decode sound");
|
||||
|
||||
let sound = OggSound::load_expect(sound).cloned().decoder();
|
||||
channel.play(sound, channel_tag);
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
||||
use crate::audio::{AudioFrontend, MusicChannelTag};
|
||||
use client::Client;
|
||||
use common::{
|
||||
assets,
|
||||
assets::{self, AssetExt, AssetHandle},
|
||||
terrain::{BiomeKind, SitesKind},
|
||||
};
|
||||
use common_sys::state::State;
|
||||
@ -103,7 +103,7 @@ enum PlayState {
|
||||
/// Provides methods to control music playback
|
||||
pub struct MusicMgr {
|
||||
/// Collection of all the tracks
|
||||
soundtrack: SoundtrackCollection,
|
||||
soundtrack: AssetHandle<SoundtrackCollection>,
|
||||
/// Instant at which the current track began playing
|
||||
began_playing: Instant,
|
||||
/// Time until the next track should be played
|
||||
@ -139,7 +139,7 @@ impl MusicMgr {
|
||||
//}
|
||||
|
||||
if audio.music_enabled()
|
||||
&& !self.soundtrack.tracks.is_empty()
|
||||
&& !self.soundtrack.read().tracks.is_empty()
|
||||
&& self.began_playing.elapsed().as_secs_f32() > self.next_track_change
|
||||
{
|
||||
self.play_random_track(audio, state, client);
|
||||
@ -158,8 +158,8 @@ impl MusicMgr {
|
||||
let current_site = client.current_site();
|
||||
|
||||
// Filters out tracks not matching the timing, site, and biome
|
||||
let maybe_tracks = self
|
||||
.soundtrack
|
||||
let soundtrack = self.soundtrack.read();
|
||||
let maybe_tracks = soundtrack
|
||||
.tracks
|
||||
.iter()
|
||||
.filter(|track| {
|
||||
@ -225,27 +225,21 @@ impl MusicMgr {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_soundtrack_items() -> SoundtrackCollection {
|
||||
match assets::load_file("voxygen.audio.soundtrack", &["ron"]) {
|
||||
Ok(file) => match ron::de::from_reader(file) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error parsing music config file, music will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
SoundtrackCollection::default()
|
||||
},
|
||||
},
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error reading music config file, music will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
SoundtrackCollection::default()
|
||||
},
|
||||
}
|
||||
fn load_soundtrack_items() -> AssetHandle<SoundtrackCollection> {
|
||||
// Cannot fail: A default value is always provided
|
||||
SoundtrackCollection::load_expect("voxygen.audio.soundtrack")
|
||||
}
|
||||
}
|
||||
|
||||
impl assets::Asset for SoundtrackCollection {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
|
||||
fn default_value(_: &str, error: assets::Error) -> Result<Self, assets::Error> {
|
||||
warn!(
|
||||
"Error reading music config file, music will not be available: {:#?}", error
|
||||
);
|
||||
|
||||
Ok(SoundtrackCollection::default())
|
||||
}
|
||||
}
|
@ -89,7 +89,7 @@ use crate::{
|
||||
|
||||
use client::Client;
|
||||
use common::{
|
||||
assets,
|
||||
assets::{self, AssetExt, AssetHandle},
|
||||
comp::{
|
||||
item::{ItemKind, ToolKind},
|
||||
object, Body, CharacterAbilityType, InventoryUpdateEvent,
|
||||
@ -237,7 +237,9 @@ impl SfxTriggers {
|
||||
}
|
||||
|
||||
pub struct SfxMgr {
|
||||
pub triggers: SfxTriggers,
|
||||
/// This is an `AssetHandle` so it is reloaded automatically
|
||||
/// when the manifest is edited.
|
||||
pub triggers: AssetHandle<SfxTriggers>,
|
||||
event_mapper: SfxEventMapper,
|
||||
}
|
||||
|
||||
@ -273,12 +275,14 @@ impl SfxMgr {
|
||||
// same direction as the camera
|
||||
audio.set_listener_pos(cam_pos, camera.dependents().cam_dir);
|
||||
|
||||
let triggers = self.triggers.read();
|
||||
|
||||
self.event_mapper.maintain(
|
||||
audio,
|
||||
state,
|
||||
player_entity,
|
||||
camera,
|
||||
&self.triggers,
|
||||
&triggers,
|
||||
terrain,
|
||||
client,
|
||||
);
|
||||
@ -357,27 +361,21 @@ impl SfxMgr {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_sfx_items() -> SfxTriggers {
|
||||
match assets::load_file("voxygen.audio.sfx", &["ron"]) {
|
||||
Ok(file) => match ron::de::from_reader(file) {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error parsing sfx config file, sfx will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
SfxTriggers::default()
|
||||
},
|
||||
},
|
||||
Err(error) => {
|
||||
warn!(
|
||||
"Error reading sfx config file, sfx will not be available: {}",
|
||||
format!("{:#?}", error)
|
||||
);
|
||||
|
||||
SfxTriggers::default()
|
||||
},
|
||||
}
|
||||
fn load_sfx_items() -> AssetHandle<SfxTriggers> {
|
||||
// Cannot fail: A default value is always provided
|
||||
SfxTriggers::load_expect("voxygen.audio.sfx")
|
||||
}
|
||||
}
|
||||
|
||||
impl assets::Asset for SfxTriggers {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
|
||||
fn default_value(_: &str, error: assets::Error) -> Result<Self, assets::Error> {
|
||||
warn!(
|
||||
"Error reading sfx config file, sfx will not be available: {:#?}", error
|
||||
);
|
||||
|
||||
Ok(SfxTriggers::default())
|
||||
}
|
||||
}
|
@ -1,63 +1,82 @@
|
||||
//! Handles caching and retrieval of decoded `.wav` sfx sound data, eliminating
|
||||
//! the need to decode files on each playback
|
||||
use common::assets;
|
||||
use hashbrown::HashMap;
|
||||
use std::{convert::AsRef, io, io::Read, sync::Arc};
|
||||
use std::{borrow::Cow, io, sync::Arc};
|
||||
use tracing::warn;
|
||||
|
||||
// Implementation of sound taken from this github issue:
|
||||
// https://github.com/RustAudio/rodio/issues/141
|
||||
pub struct Sound(Arc<Vec<u8>>);
|
||||
#[derive(Clone)]
|
||||
pub struct WavSound(Arc<Vec<u8>>);
|
||||
|
||||
impl AsRef<[u8]> for Sound {
|
||||
impl AsRef<[u8]> for WavSound {
|
||||
fn as_ref(&self) -> &[u8] { &self.0 }
|
||||
}
|
||||
|
||||
pub struct SoundLoader;
|
||||
|
||||
impl assets::Loader<WavSound> for SoundLoader {
|
||||
fn load(content: Cow<[u8]>, _: &str) -> Result<WavSound, assets::BoxedError> {
|
||||
let arc = Arc::new(content.into_owned());
|
||||
Ok(WavSound(arc))
|
||||
}
|
||||
}
|
||||
|
||||
impl assets::Asset for WavSound {
|
||||
const EXTENSION: &'static str = "wav";
|
||||
type Loader = SoundLoader;
|
||||
|
||||
fn default_value(specifier: &str, error: assets::Error) -> Result<Self, assets::Error> {
|
||||
warn!(?specifier, ?error, "Failed to load sound");
|
||||
|
||||
Ok(WavSound::empty())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for decoded audio data
|
||||
impl Sound {
|
||||
pub fn load(filename: &str) -> Result<Sound, assets::Error> {
|
||||
let mut file = assets::load_file(filename, &["wav"])?;
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf)?;
|
||||
Ok(Sound(Arc::new(buf)))
|
||||
impl WavSound {
|
||||
pub fn decoder(self) -> rodio::Decoder<io::Cursor<WavSound>> {
|
||||
let cursor = io::Cursor::new(self);
|
||||
rodio::Decoder::new(cursor).unwrap()
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> io::Cursor<Sound> { io::Cursor::new(Sound(Arc::clone(&self.0))) }
|
||||
|
||||
pub fn decoder(&self) -> rodio::Decoder<io::Cursor<Sound>> {
|
||||
rodio::Decoder::new(self.cursor()).unwrap()
|
||||
}
|
||||
|
||||
/// Returns a `Sound` containing empty .wav data. This intentionally doesn't
|
||||
/// Returns a `WavSound` containing empty .wav data. This intentionally doesn't
|
||||
/// load from the filesystem so we have a reliable fallback when there
|
||||
/// is a failure to read a file.
|
||||
///
|
||||
/// The data below is the result of passing a very short, silent .wav file
|
||||
/// to `Sound::load()`.
|
||||
pub fn empty() -> Sound {
|
||||
Sound(Arc::new(vec![
|
||||
pub fn empty() -> WavSound {
|
||||
WavSound(Arc::new(vec![
|
||||
82, 73, 70, 70, 40, 0, 0, 0, 87, 65, 86, 69, 102, 109, 116, 32, 16, 0, 0, 0, 1, 0, 1,
|
||||
0, 68, 172, 0, 0, 136, 88, 1, 0, 2, 0, 16, 0, 100, 97, 116, 97, 4, 0, 0, 0, 0, 0, 0, 0,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SoundCache {
|
||||
sounds: HashMap<String, Sound>,
|
||||
#[derive(Clone)]
|
||||
pub struct OggSound(Arc<Vec<u8>>);
|
||||
|
||||
impl AsRef<[u8]> for OggSound {
|
||||
fn as_ref(&self) -> &[u8] { &self.0 }
|
||||
}
|
||||
|
||||
impl SoundCache {
|
||||
pub fn load_sound(&mut self, name: &str) -> rodio::Decoder<io::Cursor<Sound>> {
|
||||
self.sounds
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(|| {
|
||||
Sound::load(name).unwrap_or_else(|_| {
|
||||
warn!(?name, "SoundCache: Failed to load sound");
|
||||
|
||||
Sound::empty()
|
||||
})
|
||||
})
|
||||
.decoder()
|
||||
impl assets::Loader<OggSound> for SoundLoader {
|
||||
fn load(content: Cow<[u8]>, _: &str) -> Result<OggSound, assets::BoxedError> {
|
||||
let arc = Arc::new(content.into_owned());
|
||||
Ok(OggSound(arc))
|
||||
}
|
||||
}
|
||||
|
||||
impl assets::Asset for OggSound {
|
||||
const EXTENSION: &'static str = "ogg";
|
||||
type Loader = SoundLoader;
|
||||
}
|
||||
|
||||
/// Wrapper for decoded audio data
|
||||
impl OggSound {
|
||||
pub fn decoder(self) -> rodio::Decoder<io::Cursor<OggSound>> {
|
||||
let cursor = io::Cursor::new(self);
|
||||
rodio::Decoder::new(cursor).unwrap()
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use crate::ui::{Graphic, SampleStrat, Transform, Ui};
|
||||
use common::{
|
||||
assets::{self, watch::ReloadIndicator, Asset},
|
||||
assets::{self, AssetExt, AssetHandle, DotVoxAsset},
|
||||
comp::item::{
|
||||
armor::{Armor, ArmorKind},
|
||||
Glider, ItemDesc, ItemKind, Lantern, Throwable, Utility,
|
||||
@ -8,11 +8,10 @@ use common::{
|
||||
figure::Segment,
|
||||
};
|
||||
use conrod_core::image::Id;
|
||||
use dot_vox::DotVoxData;
|
||||
use hashbrown::HashMap;
|
||||
use image::DynamicImage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs::File, io::BufReader, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, warn};
|
||||
use vek::*;
|
||||
|
||||
@ -84,37 +83,34 @@ impl ImageSpec {
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ItemImagesSpec(HashMap<ItemKey, ImageSpec>);
|
||||
impl Asset for ItemImagesSpec {
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
|
||||
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
|
||||
ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error)
|
||||
}
|
||||
impl assets::Asset for ItemImagesSpec {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
}
|
||||
|
||||
// TODO: when there are more images don't load them all into memory
|
||||
pub struct ItemImgs {
|
||||
map: HashMap<ItemKey, Id>,
|
||||
indicator: ReloadIndicator,
|
||||
manifest: AssetHandle<ItemImagesSpec>,
|
||||
not_found: Id,
|
||||
}
|
||||
|
||||
impl ItemImgs {
|
||||
pub fn new(ui: &mut Ui, not_found: Id) -> Self {
|
||||
let mut indicator = ReloadIndicator::new();
|
||||
Self {
|
||||
map: ItemImagesSpec::load_watched(
|
||||
"voxygen.item_image_manifest",
|
||||
&mut indicator,
|
||||
)
|
||||
.expect("Unable to load item image manifest")
|
||||
let manifest = ItemImagesSpec::load_expect("voxygen.item_image_manifest");
|
||||
let map = manifest
|
||||
.read()
|
||||
.0
|
||||
.iter()
|
||||
// TODO: what if multiple kinds map to the same image, it would be nice to use the same
|
||||
// image id for both, although this does interfere with the current hot-reloading
|
||||
// strategy
|
||||
.map(|(kind, spec)| (kind.clone(), ui.add_graphic(spec.create_graphic())))
|
||||
.collect(),
|
||||
indicator,
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
map,
|
||||
manifest,
|
||||
not_found,
|
||||
}
|
||||
}
|
||||
@ -122,12 +118,8 @@ impl ItemImgs {
|
||||
/// Checks if the manifest has been changed and reloads the images if so
|
||||
/// Reuses img ids
|
||||
pub fn reload_if_changed(&mut self, ui: &mut Ui) {
|
||||
if self.indicator.reloaded() {
|
||||
for (kind, spec) in ItemImagesSpec::load("voxygen.item_image_manifest")
|
||||
.expect("Unable to load item image manifest")
|
||||
.0
|
||||
.iter()
|
||||
{
|
||||
if self.manifest.reloaded() {
|
||||
for (kind, spec) in self.manifest.read().0.iter() {
|
||||
// Load new graphic
|
||||
let graphic = spec.create_graphic();
|
||||
// See if we already have an id we can use
|
||||
@ -163,30 +155,31 @@ impl ItemImgs {
|
||||
|
||||
// Copied from figure/load.rs
|
||||
// TODO: remove code dup?
|
||||
fn graceful_load_vox(specifier: &str) -> Arc<DotVoxData> {
|
||||
fn graceful_load_vox(specifier: &str) -> AssetHandle<DotVoxAsset> {
|
||||
let full_specifier: String = ["voxygen.", specifier].concat();
|
||||
match DotVoxData::load(full_specifier.as_str()) {
|
||||
match DotVoxAsset::load(full_specifier.as_str()) {
|
||||
Ok(dot_vox) => dot_vox,
|
||||
Err(_) => {
|
||||
error!(?full_specifier, "Could not load vox file for item images",);
|
||||
DotVoxData::load_expect("voxygen.voxel.not_found")
|
||||
DotVoxAsset::load_expect("voxygen.voxel.not_found")
|
||||
},
|
||||
}
|
||||
}
|
||||
fn graceful_load_img(specifier: &str) -> Arc<DynamicImage> {
|
||||
let full_specifier: String = ["voxygen.", specifier].concat();
|
||||
match DynamicImage::load(full_specifier.as_str()) {
|
||||
let handle = match assets::Image::load(&full_specifier) {
|
||||
Ok(img) => img,
|
||||
Err(_) => {
|
||||
error!(?full_specifier, "Could not load image file for item images");
|
||||
DynamicImage::load_expect("voxygen.element.not_found")
|
||||
assets::Image::load_expect("voxygen.element.not_found")
|
||||
},
|
||||
}
|
||||
};
|
||||
handle.read().to_image()
|
||||
}
|
||||
|
||||
fn graceful_load_segment_no_skin(specifier: &str) -> Arc<Segment> {
|
||||
use common::figure::{mat_cell::MatCell, MatSegment};
|
||||
let mat_seg = MatSegment::from(&*graceful_load_vox(specifier));
|
||||
let mat_seg = MatSegment::from(&graceful_load_vox(specifier).read().0);
|
||||
let seg = mat_seg
|
||||
.map(|mat_cell| match mat_cell {
|
||||
MatCell::None => None,
|
||||
|
@ -46,7 +46,7 @@ use spell::Spell;
|
||||
use crate::{
|
||||
ecs::{comp as vcomp, comp::HpFloaterList},
|
||||
hud::img_ids::ImgsRot,
|
||||
i18n::{i18n_asset_key, LanguageMetadata, Localization},
|
||||
i18n::{LanguageMetadata, Localization},
|
||||
render::{Consts, Globals, RenderMode, Renderer},
|
||||
scene::camera::{self, Camera},
|
||||
ui::{fonts::Fonts, img_ids::Rotations, slot, Graphic, Ingameable, ScaleMode, Ui},
|
||||
@ -55,7 +55,6 @@ use crate::{
|
||||
};
|
||||
use client::Client;
|
||||
use common::{
|
||||
assets::Asset,
|
||||
comp,
|
||||
comp::{
|
||||
item::{ItemDesc, Quality},
|
||||
@ -627,7 +626,6 @@ pub struct Hud {
|
||||
tab_complete: Option<String>,
|
||||
pulse: f32,
|
||||
velocity: f32,
|
||||
i18n: std::sync::Arc<Localization>,
|
||||
slot_manager: slots::SlotManager,
|
||||
hotbar: hotbar::State,
|
||||
events: Vec<Event>,
|
||||
@ -660,12 +658,8 @@ impl Hud {
|
||||
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!");
|
||||
// Load item images.
|
||||
let item_imgs = ItemImgs::new(&mut ui, imgs.not_found);
|
||||
// Load language.
|
||||
let i18n = Localization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
));
|
||||
// Load fonts.
|
||||
let fonts = Fonts::load(&i18n.fonts, &mut ui).expect("Impossible to load fonts!");
|
||||
let fonts = Fonts::load(&global_state.i18n.read().fonts, &mut ui).expect("Impossible to load fonts!");
|
||||
// Get the server name.
|
||||
let server = &client.server_info().name;
|
||||
// Get the id, unwrap is safe because this CANNOT be None at this
|
||||
@ -726,7 +720,6 @@ impl Hud {
|
||||
tab_complete: None,
|
||||
pulse: 0.0,
|
||||
velocity: 0.0,
|
||||
i18n,
|
||||
slot_manager,
|
||||
hotbar: hotbar_state,
|
||||
events: Vec::new(),
|
||||
@ -734,10 +727,8 @@ impl Hud {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_language(&mut self, i18n: std::sync::Arc<Localization>) {
|
||||
self.i18n = i18n;
|
||||
self.fonts =
|
||||
Fonts::load(&self.i18n.fonts, &mut self.ui).expect("Impossible to load fonts!");
|
||||
pub fn update_fonts(&mut self, i18n: &Localization) {
|
||||
self.fonts = Fonts::load(&i18n.fonts, &mut self.ui).expect("Impossible to load fonts!");
|
||||
}
|
||||
|
||||
#[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587
|
||||
@ -759,6 +750,7 @@ impl Hud {
|
||||
// FPS
|
||||
let fps = global_state.clock.stats().average_tps;
|
||||
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
||||
let i18n = &*global_state.i18n.read();
|
||||
|
||||
if self.show.ingame {
|
||||
let ecs = client.state().ecs();
|
||||
@ -1267,7 +1259,7 @@ impl Hud {
|
||||
in_group,
|
||||
&global_state.settings.gameplay,
|
||||
self.pulse,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
)
|
||||
@ -1470,8 +1462,8 @@ impl Hud {
|
||||
Intro::Show => {
|
||||
if self.pulse > 20.0 {
|
||||
self.show.want_grab = false;
|
||||
let quest_headline = &self.i18n.get("hud.temp_quest_headline");
|
||||
let quest_text = &self.i18n.get("hud.temp_quest_text");
|
||||
let quest_headline = &i18n.get("hud.temp_quest_headline");
|
||||
let quest_text = &i18n.get("hud.temp_quest_text");
|
||||
Image::new(self.imgs.quest_bg)
|
||||
.w_h(404.0, 858.0)
|
||||
.middle_of(ui_widgets.window)
|
||||
@ -1508,7 +1500,7 @@ impl Hud {
|
||||
.hover_image(self.imgs.button_hover)
|
||||
.press_image(self.imgs.button_press)
|
||||
.mid_bottom_with_margin_on(self.ids.q_text_bg, -120.0)
|
||||
.label(&self.i18n.get("common.accept"))
|
||||
.label(&i18n.get("common.accept"))
|
||||
.label_font_id(self.fonts.cyri.conrod_id)
|
||||
.label_font_size(self.fonts.cyri.scale(22))
|
||||
.label_color(TEXT_COLOR)
|
||||
@ -1685,8 +1677,7 @@ impl Hud {
|
||||
// Help Window
|
||||
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
|
||||
Text::new(
|
||||
&self
|
||||
.i18n
|
||||
&i18n
|
||||
.get("hud.press_key_to_toggle_keybindings_fmt")
|
||||
.replace("{key}", help_key.to_string().as_str()),
|
||||
)
|
||||
@ -1703,8 +1694,7 @@ impl Hud {
|
||||
.get_binding(GameInput::ToggleDebug)
|
||||
{
|
||||
Text::new(
|
||||
&self
|
||||
.i18n
|
||||
&i18n
|
||||
.get("hud.press_key_to_toggle_debug_info_fmt")
|
||||
.replace("{key}", toggle_debug_key.to_string().as_str()),
|
||||
)
|
||||
@ -1718,8 +1708,7 @@ impl Hud {
|
||||
// Help Window
|
||||
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
|
||||
Text::new(
|
||||
&self
|
||||
.i18n
|
||||
&i18n
|
||||
.get("hud.press_key_to_show_keybindings_fmt")
|
||||
.replace("{key}", help_key.to_string().as_str()),
|
||||
)
|
||||
@ -1736,8 +1725,7 @@ impl Hud {
|
||||
.get_binding(GameInput::ToggleDebug)
|
||||
{
|
||||
Text::new(
|
||||
&self
|
||||
.i18n
|
||||
&i18n
|
||||
.get("hud.press_key_to_show_debug_info_fmt")
|
||||
.replace("{key}", toggle_debug_key.to_string().as_str()),
|
||||
)
|
||||
@ -1754,8 +1742,7 @@ impl Hud {
|
||||
.get_binding(GameInput::ToggleLantern)
|
||||
{
|
||||
Text::new(
|
||||
&self
|
||||
.i18n
|
||||
&i18n
|
||||
.get("hud.press_key_to_toggle_lantern_fmt")
|
||||
.replace("{key}", toggle_lantern_key.to_string().as_str()),
|
||||
)
|
||||
@ -1800,7 +1787,7 @@ impl Hud {
|
||||
global_state,
|
||||
&self.rot_imgs,
|
||||
tooltip_manager,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
&player_stats,
|
||||
)
|
||||
.set(self.ids.buttons, ui_widgets)
|
||||
@ -1822,7 +1809,7 @@ impl Hud {
|
||||
&self.fonts,
|
||||
&self.rot_imgs,
|
||||
tooltip_manager,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
&player_buffs,
|
||||
self.pulse,
|
||||
&global_state,
|
||||
@ -1842,7 +1829,7 @@ impl Hud {
|
||||
&self.imgs,
|
||||
&self.rot_imgs,
|
||||
&self.fonts,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
self.pulse,
|
||||
&global_state,
|
||||
tooltip_manager,
|
||||
@ -1859,7 +1846,7 @@ impl Hud {
|
||||
}
|
||||
// Popup (waypoint saved and similar notifications)
|
||||
Popup::new(
|
||||
&self.i18n,
|
||||
i18n,
|
||||
client,
|
||||
&self.new_notifications,
|
||||
&self.fonts,
|
||||
@ -1895,7 +1882,7 @@ impl Hud {
|
||||
tooltip_manager,
|
||||
&mut self.slot_manager,
|
||||
self.pulse,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
&player_stats,
|
||||
&self.show,
|
||||
)
|
||||
@ -1963,7 +1950,7 @@ impl Hud {
|
||||
&self.hotbar,
|
||||
tooltip_manager,
|
||||
&mut self.slot_manager,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
&self.show,
|
||||
&ability_map,
|
||||
)
|
||||
@ -1978,7 +1965,7 @@ impl Hud {
|
||||
client,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
&self.rot_imgs,
|
||||
tooltip_manager,
|
||||
&self.item_imgs,
|
||||
@ -2017,7 +2004,7 @@ impl Hud {
|
||||
global_state,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
)
|
||||
.and_then(self.force_chat_input.take(), |c, input| c.input(input))
|
||||
.and_then(self.tab_complete.take(), |c, input| {
|
||||
@ -2054,7 +2041,7 @@ impl Hud {
|
||||
&self.show,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
fps as f32,
|
||||
)
|
||||
.set(self.ids.settings_window, ui_widgets)
|
||||
@ -2210,7 +2197,7 @@ impl Hud {
|
||||
client,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
info.selected_entity,
|
||||
&self.rot_imgs,
|
||||
tooltip_manager,
|
||||
@ -2238,7 +2225,7 @@ impl Hud {
|
||||
|
||||
// Spellbook
|
||||
if self.show.spell {
|
||||
match Spell::new(&self.show, client, &self.imgs, &self.fonts, &self.i18n)
|
||||
match Spell::new(&self.show, client, &self.imgs, &self.fonts, i18n)
|
||||
.set(self.ids.spell, ui_widgets)
|
||||
{
|
||||
Some(spell::Event::Close) => {
|
||||
@ -2258,7 +2245,7 @@ impl Hud {
|
||||
&self.world_map,
|
||||
&self.fonts,
|
||||
self.pulse,
|
||||
&self.i18n,
|
||||
i18n,
|
||||
&global_state,
|
||||
tooltip_manager,
|
||||
)
|
||||
@ -2302,7 +2289,7 @@ impl Hud {
|
||||
}
|
||||
|
||||
if self.show.esc_menu {
|
||||
match EscMenu::new(&self.imgs, &self.fonts, &self.i18n)
|
||||
match EscMenu::new(&self.imgs, &self.fonts, i18n)
|
||||
.set(self.ids.esc_menu, ui_widgets)
|
||||
{
|
||||
Some(esc_menu::Event::OpenSettings(tab)) => {
|
||||
@ -2344,8 +2331,7 @@ impl Hud {
|
||||
{
|
||||
if self.show.free_look {
|
||||
Text::new(
|
||||
&self
|
||||
.i18n
|
||||
&i18n
|
||||
.get("hud.free_look_indicator")
|
||||
.replace("{key}", freelook_key.to_string().as_str()),
|
||||
)
|
||||
@ -2355,8 +2341,7 @@ impl Hud {
|
||||
.font_size(self.fonts.cyri.scale(20))
|
||||
.set(self.ids.free_look_bg, ui_widgets);
|
||||
Text::new(
|
||||
&self
|
||||
.i18n
|
||||
&i18n
|
||||
.get("hud.free_look_indicator")
|
||||
.replace("{key}", freelook_key.to_string().as_str()),
|
||||
)
|
||||
@ -2370,13 +2355,13 @@ impl Hud {
|
||||
|
||||
// Auto walk indicator
|
||||
if self.show.auto_walk {
|
||||
Text::new(&self.i18n.get("hud.auto_walk_indicator"))
|
||||
Text::new(i18n.get("hud.auto_walk_indicator"))
|
||||
.color(TEXT_BG)
|
||||
.mid_top_with_margin_on(ui_widgets.window, 70.0)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(20))
|
||||
.set(self.ids.auto_walk_bg, ui_widgets);
|
||||
Text::new(&self.i18n.get("hud.auto_walk_indicator"))
|
||||
Text::new(i18n.get("hud.auto_walk_indicator"))
|
||||
.color(KILL_COLOR)
|
||||
.top_left_with_margins_on(self.ids.auto_walk_bg, -1.0, -1.0)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
|
@ -1,9 +1,10 @@
|
||||
use common::assets::{self, Asset};
|
||||
use common::assets::{self, AssetExt};
|
||||
use deunicode::deunicode;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use ron::de::from_reader;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs::File, io::BufReader};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use tracing::warn;
|
||||
|
||||
/// The reference language, aka the more up-to-date localization data.
|
||||
@ -44,7 +45,7 @@ impl Font {
|
||||
pub type Fonts = HashMap<String, Font>;
|
||||
|
||||
/// Store internationalization data
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Localization {
|
||||
/// A map storing the localized texts
|
||||
///
|
||||
@ -92,9 +93,9 @@ impl Localization {
|
||||
}
|
||||
|
||||
/// Return the missing keys compared to the reference language
|
||||
pub fn list_missing_entries(&self) -> (HashSet<String>, HashSet<String>) {
|
||||
fn list_missing_entries(&self) -> (HashSet<String>, HashSet<String>) {
|
||||
let reference_localization =
|
||||
Localization::load_expect(i18n_asset_key(REFERENCE_LANG).as_ref());
|
||||
Localization::load_expect(&i18n_asset_key(REFERENCE_LANG)).read();
|
||||
|
||||
let reference_string_keys: HashSet<_> =
|
||||
reference_localization.string_map.keys().cloned().collect();
|
||||
@ -133,15 +134,15 @@ impl Localization {
|
||||
}
|
||||
}
|
||||
|
||||
impl Asset for Localization {
|
||||
const ENDINGS: &'static [&'static str] = &["ron"];
|
||||
impl assets::Asset for Localization {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = LocalizationLoader;
|
||||
}
|
||||
|
||||
/// Load the translations located in the input buffer and convert them
|
||||
/// into a `Localization` object.
|
||||
#[allow(clippy::into_iter_on_ref)] // TODO: Pending review in #587
|
||||
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
|
||||
let mut asked_localization: Localization =
|
||||
from_reader(buf_reader).map_err(assets::Error::parse_error)?;
|
||||
pub struct LocalizationLoader;
|
||||
impl assets::Loader<Localization> for LocalizationLoader {
|
||||
fn load(content: Cow<[u8]>, ext: &str) -> Result<Localization, assets::BoxedError> {
|
||||
let mut asked_localization: Localization = assets::RonLoader::load(content, ext)?;
|
||||
|
||||
// Update the text if UTF-8 to ASCII conversion is enabled
|
||||
if asked_localization.convert_utf8_to_ascii {
|
||||
@ -162,9 +163,13 @@ impl Asset for Localization {
|
||||
|
||||
/// Load all the available languages located in the voxygen asset directory
|
||||
pub fn list_localizations() -> Vec<LanguageMetadata> {
|
||||
let voxygen_locales_assets = "voxygen.i18n.*";
|
||||
let lang_list = Localization::load_glob(voxygen_locales_assets).unwrap();
|
||||
lang_list.iter().map(|e| (*e).metadata.clone()).collect()
|
||||
assets::load_dir::<Localization>("voxygen.i18n")
|
||||
.unwrap()
|
||||
.iter_all()
|
||||
.filter_map(|(_, lang)| {
|
||||
lang.ok().map(|e| e.read().metadata.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return the asset associated with the language_id
|
||||
|
@ -34,12 +34,13 @@ pub use crate::error::Error;
|
||||
use crate::singleplayer::Singleplayer;
|
||||
use crate::{
|
||||
audio::AudioFrontend,
|
||||
i18n::Localization,
|
||||
profile::Profile,
|
||||
render::Renderer,
|
||||
settings::Settings,
|
||||
window::{Event, Window},
|
||||
};
|
||||
use common::{assets::watch, clock::Clock, span};
|
||||
use common::{assets::AssetHandle, clock::Clock, span};
|
||||
|
||||
/// A type used to store state that is shared between all play states.
|
||||
pub struct GlobalState {
|
||||
@ -52,7 +53,7 @@ pub struct GlobalState {
|
||||
#[cfg(feature = "singleplayer")]
|
||||
pub singleplayer: Option<Singleplayer>,
|
||||
// TODO: redo this so that the watcher doesn't have to exist for reloading to occur
|
||||
pub localization_watcher: watch::ReloadIndicator,
|
||||
pub i18n: AssetHandle<Localization>,
|
||||
pub clipboard: Option<iced_winit::Clipboard>,
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ use veloren_voxygen::{
|
||||
};
|
||||
|
||||
use common::{
|
||||
assets::{watch, Asset},
|
||||
assets::{self, AssetExt},
|
||||
clock::Clock,
|
||||
};
|
||||
use std::panic;
|
||||
@ -138,6 +138,8 @@ fn main() {
|
||||
default_hook(panic_info);
|
||||
}));
|
||||
|
||||
assets::start_hot_reloading();
|
||||
|
||||
// Initialise watcher for animation hotreloading
|
||||
#[cfg(feature = "hot-anim")]
|
||||
anim::init();
|
||||
@ -155,10 +157,8 @@ fn main() {
|
||||
// Load the profile.
|
||||
let profile = Profile::load();
|
||||
|
||||
let mut localization_watcher = watch::ReloadIndicator::new();
|
||||
let localized_strings = Localization::load_watched(
|
||||
let i18n = Localization::load(
|
||||
&i18n_asset_key(&settings.language.selected_language),
|
||||
&mut localization_watcher,
|
||||
)
|
||||
.unwrap_or_else(|error| {
|
||||
let selected_language = &settings.language.selected_language;
|
||||
@ -168,13 +168,9 @@ fn main() {
|
||||
"Impossible to load language: change to the default language (English) instead.",
|
||||
);
|
||||
settings.language.selected_language = i18n::REFERENCE_LANG.to_owned();
|
||||
Localization::load_watched(
|
||||
&i18n_asset_key(&settings.language.selected_language),
|
||||
&mut localization_watcher,
|
||||
)
|
||||
.unwrap()
|
||||
Localization::load_expect(&i18n_asset_key(&settings.language.selected_language))
|
||||
});
|
||||
localized_strings.log_missing_entries();
|
||||
i18n.read().log_missing_entries();
|
||||
|
||||
// Create window
|
||||
let (window, event_loop) = Window::new(&settings).expect("Failed to create window!");
|
||||
@ -192,7 +188,7 @@ fn main() {
|
||||
info_message: None,
|
||||
#[cfg(feature = "singleplayer")]
|
||||
singleplayer: None,
|
||||
localization_watcher,
|
||||
i18n,
|
||||
clipboard,
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
mod ui;
|
||||
|
||||
use crate::{
|
||||
i18n::{i18n_asset_key, Localization},
|
||||
render::Renderer,
|
||||
scene::simple::{self as scene, Scene},
|
||||
session::SessionState,
|
||||
@ -10,7 +9,7 @@ use crate::{
|
||||
Direction, GlobalState, PlayState, PlayStateResult,
|
||||
};
|
||||
use client::{self, Client};
|
||||
use common::{assets::Asset, comp, resources::DeltaTime, span};
|
||||
use common::{comp, resources::DeltaTime, span};
|
||||
use specs::WorldExt;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use tracing::error;
|
||||
@ -64,11 +63,7 @@ impl PlayState for CharSelectionState {
|
||||
self.client.borrow_mut().load_character_list();
|
||||
|
||||
// Updated localization in case the selected language was changed
|
||||
let localized_strings = crate::i18n::Localization::load_expect(
|
||||
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
|
||||
);
|
||||
self.char_selection_ui
|
||||
.update_language(std::sync::Arc::clone(&localized_strings));
|
||||
self.char_selection_ui.update_language(global_state.i18n);
|
||||
// Set scale mode in case it was change
|
||||
self.char_selection_ui
|
||||
.set_scale_mode(global_state.settings.gameplay.ui_scale);
|
||||
@ -167,9 +162,7 @@ impl PlayState for CharSelectionState {
|
||||
}
|
||||
|
||||
// Tick the client (currently only to keep the connection alive).
|
||||
let localized_strings = Localization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
));
|
||||
let localized_strings = &*global_state.i18n.read();
|
||||
|
||||
match self.client.borrow_mut().tick(
|
||||
comp::ControllerInputs::default(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
i18n::{i18n_asset_key, Localization},
|
||||
i18n::Localization,
|
||||
render::Renderer,
|
||||
ui::{
|
||||
self,
|
||||
@ -22,7 +22,7 @@ use crate::{
|
||||
};
|
||||
use client::Client;
|
||||
use common::{
|
||||
assets::Asset,
|
||||
assets::AssetHandle,
|
||||
character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
|
||||
comp::{self, humanoid, item::tool::AbilityMap},
|
||||
LoadoutBuilder,
|
||||
@ -220,7 +220,6 @@ enum InfoContent {
|
||||
struct Controls {
|
||||
fonts: Fonts,
|
||||
imgs: Imgs,
|
||||
i18n: std::sync::Arc<Localization>,
|
||||
// Voxygen version
|
||||
version: String,
|
||||
// Alpha disclaimer
|
||||
@ -267,7 +266,6 @@ impl Controls {
|
||||
fn new(
|
||||
fonts: Fonts,
|
||||
imgs: Imgs,
|
||||
i18n: std::sync::Arc<Localization>,
|
||||
selected: Option<CharacterId>,
|
||||
) -> Self {
|
||||
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
||||
@ -276,7 +274,6 @@ impl Controls {
|
||||
Self {
|
||||
fonts,
|
||||
imgs,
|
||||
i18n,
|
||||
version,
|
||||
alpha,
|
||||
|
||||
@ -287,12 +284,13 @@ impl Controls {
|
||||
}
|
||||
}
|
||||
|
||||
fn view(
|
||||
&mut self,
|
||||
fn view<'a>(
|
||||
&'a mut self,
|
||||
_settings: &Settings,
|
||||
client: &Client,
|
||||
error: &Option<String>,
|
||||
) -> Element<Message> {
|
||||
i18n: &'a Localization,
|
||||
) -> Element<'a, Message> {
|
||||
// TODO: use font scale thing for text size (use on button size for buttons with
|
||||
// text)
|
||||
|
||||
@ -301,7 +299,6 @@ impl Controls {
|
||||
|
||||
let imgs = &self.imgs;
|
||||
let fonts = &self.fonts;
|
||||
let i18n = &self.i18n;
|
||||
let tooltip_manager = &self.tooltip_manager;
|
||||
|
||||
let button_style = style::button::Style::new(imgs.button)
|
||||
@ -1298,7 +1295,7 @@ impl Controls {
|
||||
body.skin = rng.gen_range(0, species.num_skin_colors());
|
||||
body.eye_color = rng.gen_range(0, species.num_eye_colors());
|
||||
body.eyes = rng.gen_range(0, species.num_eyes(body_type));
|
||||
*name = npc::get_npc_name(npc::NpcKind::Humanoid).to_string();
|
||||
*name = npc::get_npc_name(npc::NpcKind::Humanoid);
|
||||
}
|
||||
},
|
||||
Message::ConfirmDeletion => {
|
||||
@ -1402,20 +1399,10 @@ impl CharSelectionUi {
|
||||
let selected_character = global_state.profile.get_selected_character(server_name);
|
||||
|
||||
// Load language
|
||||
let i18n = Localization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
));
|
||||
let i18n = global_state.i18n.read();
|
||||
|
||||
// TODO: don't add default font twice
|
||||
let font = {
|
||||
use std::io::Read;
|
||||
let mut buf = Vec::new();
|
||||
common::assets::load_file(&i18n.fonts.get("cyri").unwrap().asset_key, &["ttf"])
|
||||
.unwrap()
|
||||
.read_to_end(&mut buf)
|
||||
.unwrap();
|
||||
ui::ice::Font::try_from_vec(buf).unwrap()
|
||||
};
|
||||
let font = ui::ice::load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
|
||||
|
||||
let mut ui = Ui::new(
|
||||
&mut global_state.window,
|
||||
@ -1429,7 +1416,6 @@ impl CharSelectionUi {
|
||||
let controls = Controls::new(
|
||||
fonts,
|
||||
Imgs::load(&mut ui).expect("Failed to load images"),
|
||||
i18n,
|
||||
selected_character,
|
||||
);
|
||||
|
||||
@ -1476,19 +1462,12 @@ impl CharSelectionUi {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_language(&mut self, i18n: std::sync::Arc<Localization>) {
|
||||
let font = {
|
||||
use std::io::Read;
|
||||
let mut buf = Vec::new();
|
||||
common::assets::load_file(&i18n.fonts.get("cyri").unwrap().asset_key, &["ttf"])
|
||||
.unwrap()
|
||||
.read_to_end(&mut buf)
|
||||
.unwrap();
|
||||
ui::ice::Font::try_from_vec(buf).unwrap()
|
||||
};
|
||||
self.controls.i18n = i18n;
|
||||
pub fn update_language(&mut self, i18n: AssetHandle<Localization>) {
|
||||
let i18n = i18n.read();
|
||||
let font = ui::ice::load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
|
||||
|
||||
self.ui.clear_fonts(font);
|
||||
self.controls.fonts = Fonts::load(&self.controls.i18n.fonts, &mut self.ui)
|
||||
self.controls.fonts = Fonts::load(&i18n.fonts, &mut self.ui)
|
||||
.expect("Impossible to load fonts!");
|
||||
}
|
||||
|
||||
@ -1503,10 +1482,11 @@ impl CharSelectionUi {
|
||||
// TODO: do we need whole client here or just character list?
|
||||
pub fn maintain(&mut self, global_state: &mut GlobalState, client: &Client) -> Vec<Event> {
|
||||
let mut events = Vec::new();
|
||||
let i18n = global_state.i18n.read();
|
||||
|
||||
let (mut messages, _) = self.ui.maintain(
|
||||
self.controls
|
||||
.view(&global_state.settings, &client, &self.error),
|
||||
.view(&global_state.settings, &client, &self.error, &i18n),
|
||||
global_state.window.renderer_mut(),
|
||||
global_state.clipboard.as_ref(),
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||
Direction, GlobalState, PlayState, PlayStateResult,
|
||||
};
|
||||
use client_init::{ClientInit, Error as InitError, Msg as InitMsg};
|
||||
use common::{assets::Asset, comp, span};
|
||||
use common::{assets::AssetExt, comp, span};
|
||||
use tracing::error;
|
||||
use ui::{Event as MainMenuEvent, MainMenuUi};
|
||||
|
||||
@ -48,13 +48,7 @@ impl PlayState for MainMenuState {
|
||||
}
|
||||
|
||||
// Updated localization in case the selected language was changed
|
||||
let localized_strings = crate::i18n::Localization::load_expect(
|
||||
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
|
||||
);
|
||||
self.main_menu_ui.update_language(
|
||||
std::sync::Arc::clone(&localized_strings),
|
||||
&global_state.settings,
|
||||
);
|
||||
self.main_menu_ui.update_language(global_state.i18n, &global_state.settings);
|
||||
// Set scale mode in case it was change
|
||||
self.main_menu_ui
|
||||
.set_scale_mode(global_state.settings.gameplay.ui_scale);
|
||||
@ -63,9 +57,6 @@ impl PlayState for MainMenuState {
|
||||
#[allow(clippy::single_match)] // TODO: remove when event match has multiple arms
|
||||
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
||||
span!(_guard, "tick", "<MainMenuState as PlayState>::tick");
|
||||
let mut localized_strings = crate::i18n::Localization::load_expect(
|
||||
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
|
||||
);
|
||||
|
||||
// Poll server creation
|
||||
#[cfg(feature = "singleplayer")]
|
||||
@ -121,6 +112,7 @@ impl PlayState for MainMenuState {
|
||||
)));
|
||||
},
|
||||
Some(InitMsg::Done(Err(err))) => {
|
||||
let localized_strings = global_state.i18n.read();
|
||||
self.client_init = None;
|
||||
global_state.info_message = Some({
|
||||
let err = match err {
|
||||
@ -268,12 +260,12 @@ impl PlayState for MainMenuState {
|
||||
MainMenuEvent::ChangeLanguage(new_language) => {
|
||||
global_state.settings.language.selected_language =
|
||||
new_language.language_identifier;
|
||||
localized_strings = Localization::load_expect(&i18n_asset_key(
|
||||
global_state.i18n = Localization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
));
|
||||
localized_strings.log_missing_entries();
|
||||
global_state.i18n.read().log_missing_entries();
|
||||
self.main_menu_ui.update_language(
|
||||
std::sync::Arc::clone(&localized_strings),
|
||||
global_state.i18n,
|
||||
&global_state.settings,
|
||||
);
|
||||
},
|
||||
|
@ -5,12 +5,12 @@ mod login;
|
||||
mod servers;
|
||||
|
||||
use crate::{
|
||||
i18n::{i18n_asset_key, LanguageMetadata, Localization},
|
||||
i18n::{LanguageMetadata, Localization},
|
||||
render::Renderer,
|
||||
ui::{
|
||||
self,
|
||||
fonts::IcedFonts as Fonts,
|
||||
ice::{style, widget, Element, Font, IcedUi as Ui},
|
||||
ice::{style, widget, Element, IcedUi as Ui, load_font},
|
||||
img_ids::{ImageGraphic, VoxelGraphic},
|
||||
Graphic,
|
||||
},
|
||||
@ -19,8 +19,7 @@ use crate::{
|
||||
use iced::{text_input, Column, Container, HorizontalAlignment, Length, Row, Space};
|
||||
//ImageFrame, Tooltip,
|
||||
use crate::settings::Settings;
|
||||
use common::assets::Asset;
|
||||
use image::DynamicImage;
|
||||
use common::assets::{self, AssetExt, AssetHandle};
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use std::time::Duration;
|
||||
|
||||
@ -132,7 +131,7 @@ struct Controls {
|
||||
fonts: Fonts,
|
||||
imgs: Imgs,
|
||||
bg_img: widget::image::Handle,
|
||||
i18n: std::sync::Arc<Localization>,
|
||||
i18n: AssetHandle<Localization>,
|
||||
// Voxygen version
|
||||
version: String,
|
||||
// Alpha disclaimer
|
||||
@ -177,7 +176,7 @@ impl Controls {
|
||||
fonts: Fonts,
|
||||
imgs: Imgs,
|
||||
bg_img: widget::image::Handle,
|
||||
i18n: std::sync::Arc<Localization>,
|
||||
i18n: AssetHandle<Localization>,
|
||||
settings: &Settings,
|
||||
) -> Self {
|
||||
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
||||
@ -281,7 +280,7 @@ impl Controls {
|
||||
&self.imgs,
|
||||
&self.login_info,
|
||||
error.as_deref(),
|
||||
&self.i18n,
|
||||
&self.i18n.read(),
|
||||
self.is_selecting_language,
|
||||
self.selected_language_index,
|
||||
&language_metadatas,
|
||||
@ -293,7 +292,7 @@ impl Controls {
|
||||
&self.imgs,
|
||||
&settings.networking.servers,
|
||||
self.selected_server_index,
|
||||
&self.i18n,
|
||||
&self.i18n.read(),
|
||||
button_style,
|
||||
),
|
||||
Screen::Connecting {
|
||||
@ -304,7 +303,7 @@ impl Controls {
|
||||
&self.imgs,
|
||||
&connection_state,
|
||||
self.time,
|
||||
&self.i18n,
|
||||
&self.i18n.read(),
|
||||
button_style,
|
||||
settings.gameplay.loading_tips,
|
||||
),
|
||||
@ -482,20 +481,9 @@ pub struct MainMenuUi {
|
||||
impl<'a> MainMenuUi {
|
||||
pub fn new(global_state: &mut GlobalState) -> Self {
|
||||
// Load language
|
||||
let i18n = Localization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
));
|
||||
|
||||
let i18n = &*global_state.i18n.read();
|
||||
// TODO: don't add default font twice
|
||||
let font = {
|
||||
use std::io::Read;
|
||||
let mut buf = Vec::new();
|
||||
common::assets::load_file(&i18n.fonts.get("cyri").unwrap().asset_key, &["ttf"])
|
||||
.unwrap()
|
||||
.read_to_end(&mut buf)
|
||||
.unwrap();
|
||||
Font::try_from_vec(buf).unwrap()
|
||||
};
|
||||
let font = load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
|
||||
|
||||
let mut ui = Ui::new(
|
||||
&mut global_state.window,
|
||||
@ -508,30 +496,24 @@ impl<'a> MainMenuUi {
|
||||
|
||||
let bg_img_spec = BG_IMGS.choose(&mut thread_rng()).unwrap();
|
||||
|
||||
let bg_img = assets::Image::load_expect(bg_img_spec).read().to_image();
|
||||
let controls = Controls::new(
|
||||
fonts,
|
||||
Imgs::load(&mut ui).expect("Failed to load images"),
|
||||
ui.add_graphic(Graphic::Image(DynamicImage::load_expect(bg_img_spec), None)),
|
||||
i18n,
|
||||
ui.add_graphic(Graphic::Image(bg_img, None)),
|
||||
global_state.i18n,
|
||||
&global_state.settings,
|
||||
);
|
||||
|
||||
Self { ui, controls }
|
||||
}
|
||||
|
||||
pub fn update_language(&mut self, i18n: std::sync::Arc<Localization>, settings: &Settings) {
|
||||
let font = {
|
||||
use std::io::Read;
|
||||
let mut buf = Vec::new();
|
||||
common::assets::load_file(&i18n.fonts.get("cyri").unwrap().asset_key, &["ttf"])
|
||||
.unwrap()
|
||||
.read_to_end(&mut buf)
|
||||
.unwrap();
|
||||
Font::try_from_vec(buf).unwrap()
|
||||
};
|
||||
pub fn update_language(&mut self, i18n: AssetHandle<Localization>, settings: &Settings) {
|
||||
self.controls.i18n = i18n;
|
||||
let i18n = &*i18n.read();
|
||||
let font = load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
|
||||
self.ui.clear_fonts(font);
|
||||
self.controls.fonts = Fonts::load(&self.controls.i18n.fonts, &mut self.ui)
|
||||
self.controls.fonts = Fonts::load(&i18n.fonts, &mut self.ui)
|
||||
.expect("Impossible to load fonts!");
|
||||
let language_metadatas = crate::i18n::list_localizations();
|
||||
self.controls.selected_language_index = language_metadatas
|
||||
|
@ -13,7 +13,7 @@ use super::{
|
||||
ShadowMapMode, ShadowMode, WrapMode,
|
||||
};
|
||||
use common::{
|
||||
assets::{self, watch::ReloadIndicator, Asset},
|
||||
assets::{self, AssetExt, AssetHandle},
|
||||
span,
|
||||
};
|
||||
use core::convert::TryFrom;
|
||||
@ -24,11 +24,6 @@ use gfx::{
|
||||
traits::{Device, Factory, FactoryExt},
|
||||
};
|
||||
use glsl_include::Context as IncludeContext;
|
||||
use image::DynamicImage;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, Read},
|
||||
};
|
||||
use tracing::{error, warn};
|
||||
use vek::*;
|
||||
|
||||
@ -104,17 +99,119 @@ pub type ColLightInfo = (
|
||||
);
|
||||
|
||||
/// Load from a GLSL file.
|
||||
pub struct Glsl;
|
||||
pub struct Glsl(String);
|
||||
|
||||
impl Asset for Glsl {
|
||||
type Output = String;
|
||||
impl From<String> for Glsl {
|
||||
fn from(s: String) -> Glsl {
|
||||
Glsl(s)
|
||||
}
|
||||
}
|
||||
|
||||
const ENDINGS: &'static [&'static str] = &["glsl"];
|
||||
impl assets::Asset for Glsl {
|
||||
const EXTENSION: &'static str = "glsl";
|
||||
type Loader = assets::LoadFrom<String, assets::StringLoader>;
|
||||
}
|
||||
|
||||
fn parse(mut buf_reader: BufReader<File>, _specifier: &str) -> Result<String, assets::Error> {
|
||||
let mut string = String::new();
|
||||
buf_reader.read_to_string(&mut string)?;
|
||||
Ok(string)
|
||||
struct Shaders {
|
||||
constants: AssetHandle<Glsl>,
|
||||
globals: AssetHandle<Glsl>,
|
||||
sky: AssetHandle<Glsl>,
|
||||
light: AssetHandle<Glsl>,
|
||||
srgb: AssetHandle<Glsl>,
|
||||
random: AssetHandle<Glsl>,
|
||||
lod: AssetHandle<Glsl>,
|
||||
shadows: AssetHandle<Glsl>,
|
||||
|
||||
anti_alias_none: AssetHandle<Glsl>,
|
||||
anti_alias_fxaa: AssetHandle<Glsl>,
|
||||
anti_alias_msaa_x4: AssetHandle<Glsl>,
|
||||
anti_alias_msaa_x8: AssetHandle<Glsl>,
|
||||
anti_alias_msaa_x16: AssetHandle<Glsl>,
|
||||
cloud_none: AssetHandle<Glsl>,
|
||||
cloud_regular: AssetHandle<Glsl>,
|
||||
figure_vert: AssetHandle<Glsl>,
|
||||
|
||||
terrain_point_shadow_vert: AssetHandle<Glsl>,
|
||||
terrain_directed_shadow_vert: AssetHandle<Glsl>,
|
||||
figure_directed_shadow_vert: AssetHandle<Glsl>,
|
||||
directed_shadow_frag: AssetHandle<Glsl>,
|
||||
|
||||
skybox_vert: AssetHandle<Glsl>,
|
||||
skybox_frag: AssetHandle<Glsl>,
|
||||
figure_frag: AssetHandle<Glsl>,
|
||||
terrain_vert: AssetHandle<Glsl>,
|
||||
terrain_frag: AssetHandle<Glsl>,
|
||||
fluid_vert: AssetHandle<Glsl>,
|
||||
fluid_frag_cheap: AssetHandle<Glsl>,
|
||||
fluid_frag_shiny: AssetHandle<Glsl>,
|
||||
sprite_vert: AssetHandle<Glsl>,
|
||||
sprite_frag: AssetHandle<Glsl>,
|
||||
particle_vert: AssetHandle<Glsl>,
|
||||
particle_frag: AssetHandle<Glsl>,
|
||||
ui_vert: AssetHandle<Glsl>,
|
||||
ui_frag: AssetHandle<Glsl>,
|
||||
lod_terrain_vert: AssetHandle<Glsl>,
|
||||
lod_terrain_frag: AssetHandle<Glsl>,
|
||||
clouds_vert: AssetHandle<Glsl>,
|
||||
clouds_frag: AssetHandle<Glsl>,
|
||||
postprocess_vert: AssetHandle<Glsl>,
|
||||
postprocess_frag: AssetHandle<Glsl>,
|
||||
player_shadow_frag: AssetHandle<Glsl>,
|
||||
light_shadows_geom: AssetHandle<Glsl>,
|
||||
light_shadows_frag: AssetHandle<Glsl>,
|
||||
}
|
||||
|
||||
impl assets::Compound for Shaders {
|
||||
fn load<S: assets::source::Source>(_: &assets::AssetCache<S>, _: &str) -> Result<Shaders, assets::Error> {
|
||||
Ok(Shaders {
|
||||
constants: AssetExt::load("voxygen.shaders.include.constants")?,
|
||||
globals: AssetExt::load("voxygen.shaders.include.globals")?,
|
||||
sky: AssetExt::load("voxygen.shaders.include.sky")?,
|
||||
light: AssetExt::load("voxygen.shaders.include.light")?,
|
||||
srgb: AssetExt::load("voxygen.shaders.include.srgb")?,
|
||||
random: AssetExt::load("voxygen.shaders.include.random")?,
|
||||
lod: AssetExt::load("voxygen.shaders.include.lod")?,
|
||||
shadows: AssetExt::load("voxygen.shaders.include.shadows")?,
|
||||
|
||||
anti_alias_none: AssetExt::load("voxygen.shaders.antialias.none")?,
|
||||
anti_alias_fxaa: AssetExt::load("voxygen.shaders.antialias.fxaa")?,
|
||||
anti_alias_msaa_x4: AssetExt::load("voxygen.shaders.antialias.msaa-x4")?,
|
||||
anti_alias_msaa_x8: AssetExt::load("voxygen.shaders.antialias.msaa-x8")?,
|
||||
anti_alias_msaa_x16: AssetExt::load("voxygen.shaders.antialias.msaa-x16")?,
|
||||
cloud_none: AssetExt::load("voxygen.shaders.include.cloud.none")?,
|
||||
cloud_regular: AssetExt::load("voxygen.shaders.include.cloud.regular")?,
|
||||
figure_vert: AssetExt::load("voxygen.shaders.figure-vert")?,
|
||||
|
||||
terrain_point_shadow_vert: AssetExt::load("voxygen.shaders.light-shadows-vert")?,
|
||||
terrain_directed_shadow_vert: AssetExt::load("voxygen.shaders.light-shadows-directed-vert")?,
|
||||
figure_directed_shadow_vert: AssetExt::load("voxygen.shaders.light-shadows-figure-vert")?,
|
||||
directed_shadow_frag: AssetExt::load("voxygen.shaders.light-shadows-directed-frag")?,
|
||||
|
||||
skybox_vert: AssetExt::load("voxygen.shaders.skybox-vert")?,
|
||||
skybox_frag: AssetExt::load("voxygen.shaders.skybox-frag")?,
|
||||
figure_frag: AssetExt::load("voxygen.shaders.figure-frag")?,
|
||||
terrain_vert: AssetExt::load("voxygen.shaders.terrain-vert")?,
|
||||
terrain_frag: AssetExt::load("voxygen.shaders.terrain-frag")?,
|
||||
fluid_vert: AssetExt::load("voxygen.shaders.fluid-vert")?,
|
||||
fluid_frag_cheap: AssetExt::load("voxygen.shaders.fluid-frag.cheap")?,
|
||||
fluid_frag_shiny: AssetExt::load("voxygen.shaders.fluid-frag.shiny")?,
|
||||
sprite_vert: AssetExt::load("voxygen.shaders.sprite-vert")?,
|
||||
sprite_frag: AssetExt::load("voxygen.shaders.sprite-frag")?,
|
||||
particle_vert: AssetExt::load("voxygen.shaders.particle-vert")?,
|
||||
particle_frag: AssetExt::load("voxygen.shaders.particle-frag")?,
|
||||
ui_vert: AssetExt::load("voxygen.shaders.ui-vert")?,
|
||||
ui_frag: AssetExt::load("voxygen.shaders.ui-frag")?,
|
||||
lod_terrain_vert: AssetExt::load("voxygen.shaders.lod-terrain-vert")?,
|
||||
lod_terrain_frag: AssetExt::load("voxygen.shaders.lod-terrain-frag")?,
|
||||
clouds_vert: AssetExt::load("voxygen.shaders.clouds-vert")?,
|
||||
clouds_frag: AssetExt::load("voxygen.shaders.clouds-frag")?,
|
||||
postprocess_vert: AssetExt::load("voxygen.shaders.postprocess-vert")?,
|
||||
postprocess_frag: AssetExt::load("voxygen.shaders.postprocess-frag")?,
|
||||
player_shadow_frag: AssetExt::load("voxygen.shaders.player-shadow-frag")?,
|
||||
light_shadows_geom: AssetExt::load("voxygen.shaders.light-shadows-geom")?,
|
||||
light_shadows_frag: AssetExt::load("voxygen.shaders.light-shadows-frag")?,
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,7 +269,7 @@ pub struct Renderer {
|
||||
postprocess_pipeline: GfxPipeline<postprocess::pipe::Init<'static>>,
|
||||
player_shadow_pipeline: GfxPipeline<figure::pipe::Init<'static>>,
|
||||
|
||||
shader_reload_indicator: ReloadIndicator,
|
||||
shaders: AssetHandle<Shaders>,
|
||||
|
||||
noise_tex: Texture<(gfx::format::R8, gfx::format::Unorm)>,
|
||||
|
||||
@ -198,7 +295,6 @@ impl Renderer {
|
||||
|
||||
let dims = win_color_view.get_dimensions();
|
||||
|
||||
let mut shader_reload_indicator = ReloadIndicator::new();
|
||||
let shadow_views = Self::create_shadow_views(
|
||||
&mut factory,
|
||||
(dims.0, dims.1),
|
||||
@ -209,6 +305,8 @@ impl Renderer {
|
||||
})
|
||||
.ok();
|
||||
|
||||
let shaders = Shaders::load_expect("");
|
||||
|
||||
let (
|
||||
skybox_pipeline,
|
||||
figure_pipeline,
|
||||
@ -226,9 +324,9 @@ impl Renderer {
|
||||
figure_directed_shadow_pipeline,
|
||||
) = create_pipelines(
|
||||
&mut factory,
|
||||
&shaders.read(),
|
||||
&mode,
|
||||
shadow_views.is_some(),
|
||||
&mut shader_reload_indicator,
|
||||
)?;
|
||||
|
||||
let (
|
||||
@ -285,7 +383,7 @@ impl Renderer {
|
||||
|
||||
let noise_tex = Texture::new(
|
||||
&mut factory,
|
||||
&DynamicImage::load_expect("voxygen.texture.noise"),
|
||||
&assets::Image::load_expect("voxygen.texture.noise").read().0,
|
||||
Some(gfx::texture::FilterMethod::Trilinear),
|
||||
Some(gfx::texture::WrapMode::Tile),
|
||||
None,
|
||||
@ -323,7 +421,7 @@ impl Renderer {
|
||||
postprocess_pipeline,
|
||||
player_shadow_pipeline,
|
||||
|
||||
shader_reload_indicator,
|
||||
shaders,
|
||||
|
||||
noise_tex,
|
||||
|
||||
@ -789,7 +887,7 @@ impl Renderer {
|
||||
self.device.cleanup();
|
||||
|
||||
// If the shaders files were changed attempt to recreate the shaders
|
||||
if self.shader_reload_indicator.reloaded() {
|
||||
if self.shaders.reloaded() {
|
||||
self.recreate_pipelines();
|
||||
}
|
||||
}
|
||||
@ -798,9 +896,9 @@ impl Renderer {
|
||||
fn recreate_pipelines(&mut self) {
|
||||
match create_pipelines(
|
||||
&mut self.factory,
|
||||
&self.shaders.read(),
|
||||
&self.mode,
|
||||
self.shadow_map.is_some(),
|
||||
&mut self.shader_reload_indicator,
|
||||
) {
|
||||
Ok((
|
||||
skybox_pipeline,
|
||||
@ -1771,9 +1869,9 @@ struct GfxPipeline<P: gfx::pso::PipelineInit> {
|
||||
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
||||
fn create_pipelines(
|
||||
factory: &mut gfx_backend::Factory,
|
||||
shaders: &Shaders,
|
||||
mode: &RenderMode,
|
||||
has_shadow_views: bool,
|
||||
shader_reload_indicator: &mut ReloadIndicator,
|
||||
) -> Result<
|
||||
(
|
||||
GfxPipeline<skybox::pipe::Init<'static>>,
|
||||
@ -1793,20 +1891,6 @@ fn create_pipelines(
|
||||
),
|
||||
RenderError,
|
||||
> {
|
||||
let constants =
|
||||
Glsl::load_watched("voxygen.shaders.include.constants", shader_reload_indicator).unwrap();
|
||||
let globals =
|
||||
Glsl::load_watched("voxygen.shaders.include.globals", shader_reload_indicator).unwrap();
|
||||
let sky = Glsl::load_watched("voxygen.shaders.include.sky", shader_reload_indicator).unwrap();
|
||||
let light =
|
||||
Glsl::load_watched("voxygen.shaders.include.light", shader_reload_indicator).unwrap();
|
||||
let srgb = Glsl::load_watched("voxygen.shaders.include.srgb", shader_reload_indicator).unwrap();
|
||||
let random =
|
||||
Glsl::load_watched("voxygen.shaders.include.random", shader_reload_indicator).unwrap();
|
||||
let lod = Glsl::load_watched("voxygen.shaders.include.lod", shader_reload_indicator).unwrap();
|
||||
let shadows =
|
||||
Glsl::load_watched("voxygen.shaders.include.shadows", shader_reload_indicator).unwrap();
|
||||
|
||||
// We dynamically add extra configuration settings to the constants file.
|
||||
let constants = format!(
|
||||
r#"
|
||||
@ -1819,7 +1903,7 @@ fn create_pipelines(
|
||||
#define SHADOW_MODE {}
|
||||
|
||||
"#,
|
||||
constants,
|
||||
shaders.constants.read().0,
|
||||
// TODO: Configurable vertex/fragment shader preference.
|
||||
"VOXYGEN_COMPUTATION_PREFERENCE_FRAGMENT",
|
||||
match mode.fluid {
|
||||
@ -1846,74 +1930,37 @@ fn create_pipelines(
|
||||
},
|
||||
);
|
||||
|
||||
let anti_alias = Glsl::load_watched(
|
||||
&["voxygen.shaders.antialias.", match mode.aa {
|
||||
AaMode::None => "none",
|
||||
AaMode::Fxaa => "fxaa",
|
||||
AaMode::MsaaX4 => "msaa-x4",
|
||||
AaMode::MsaaX8 => "msaa-x8",
|
||||
AaMode::MsaaX16 => "msaa-x16",
|
||||
}]
|
||||
.concat(),
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap();
|
||||
let anti_alias = &match mode.aa {
|
||||
AaMode::None => shaders.anti_alias_none,
|
||||
AaMode::Fxaa => shaders.anti_alias_fxaa,
|
||||
AaMode::MsaaX4 => shaders.anti_alias_msaa_x4,
|
||||
AaMode::MsaaX8 => shaders.anti_alias_msaa_x8,
|
||||
AaMode::MsaaX16 => shaders.anti_alias_msaa_x16,
|
||||
};
|
||||
|
||||
let cloud = Glsl::load_watched(
|
||||
&["voxygen.shaders.include.cloud.", match mode.cloud {
|
||||
CloudMode::None => "none",
|
||||
_ => "regular",
|
||||
}]
|
||||
.concat(),
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap();
|
||||
let cloud = &match mode.cloud {
|
||||
CloudMode::None => shaders.cloud_none,
|
||||
_ => shaders.cloud_regular,
|
||||
};
|
||||
|
||||
let mut include_ctx = IncludeContext::new();
|
||||
include_ctx.include("constants.glsl", &constants);
|
||||
include_ctx.include("globals.glsl", &globals);
|
||||
include_ctx.include("shadows.glsl", &shadows);
|
||||
include_ctx.include("sky.glsl", &sky);
|
||||
include_ctx.include("light.glsl", &light);
|
||||
include_ctx.include("srgb.glsl", &srgb);
|
||||
include_ctx.include("random.glsl", &random);
|
||||
include_ctx.include("lod.glsl", &lod);
|
||||
include_ctx.include("anti-aliasing.glsl", &anti_alias);
|
||||
include_ctx.include("cloud.glsl", &cloud);
|
||||
|
||||
let figure_vert =
|
||||
Glsl::load_watched("voxygen.shaders.figure-vert", shader_reload_indicator).unwrap();
|
||||
|
||||
let terrain_point_shadow_vert = Glsl::load_watched(
|
||||
"voxygen.shaders.light-shadows-vert",
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let terrain_directed_shadow_vert = Glsl::load_watched(
|
||||
"voxygen.shaders.light-shadows-directed-vert",
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let figure_directed_shadow_vert = Glsl::load_watched(
|
||||
"voxygen.shaders.light-shadows-figure-vert",
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let directed_shadow_frag = Glsl::load_watched(
|
||||
"voxygen.shaders.light-shadows-directed-frag",
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap();
|
||||
include_ctx.include("globals.glsl", &shaders.globals.read().0);
|
||||
include_ctx.include("shadows.glsl", &shaders.shadows.read().0);
|
||||
include_ctx.include("sky.glsl", &shaders.sky.read().0);
|
||||
include_ctx.include("light.glsl", &shaders.light.read().0);
|
||||
include_ctx.include("srgb.glsl", &shaders.srgb.read().0);
|
||||
include_ctx.include("random.glsl", &shaders.random.read().0);
|
||||
include_ctx.include("lod.glsl", &shaders.lod.read().0);
|
||||
include_ctx.include("anti-aliasing.glsl", &anti_alias.read().0);
|
||||
include_ctx.include("cloud.glsl", &cloud.read().0);
|
||||
|
||||
// Construct a pipeline for rendering skyboxes
|
||||
let skybox_pipeline = create_pipeline(
|
||||
factory,
|
||||
skybox::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.skybox-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched("voxygen.shaders.skybox-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.skybox_vert.read().0,
|
||||
&shaders.skybox_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -1922,8 +1969,8 @@ fn create_pipelines(
|
||||
let figure_pipeline = create_pipeline(
|
||||
factory,
|
||||
figure::pipe::new(),
|
||||
&figure_vert,
|
||||
&Glsl::load_watched("voxygen.shaders.figure-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.figure_vert.read().0,
|
||||
&shaders.figure_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -1932,8 +1979,8 @@ fn create_pipelines(
|
||||
let terrain_pipeline = create_pipeline(
|
||||
factory,
|
||||
terrain::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.terrain-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched("voxygen.shaders.terrain-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.terrain_vert.read().0,
|
||||
&shaders.terrain_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -1942,16 +1989,11 @@ fn create_pipelines(
|
||||
let fluid_pipeline = create_pipeline(
|
||||
factory,
|
||||
fluid::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.fluid-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched(
|
||||
&["voxygen.shaders.fluid-frag.", match mode.fluid {
|
||||
FluidMode::Cheap => "cheap",
|
||||
FluidMode::Shiny => "shiny",
|
||||
}]
|
||||
.concat(),
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap(),
|
||||
&shaders.fluid_vert.read().0,
|
||||
&match mode.fluid {
|
||||
FluidMode::Cheap => shaders.fluid_frag_cheap,
|
||||
FluidMode::Shiny => shaders.fluid_frag_shiny,
|
||||
}.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Nothing,
|
||||
)?;
|
||||
@ -1960,8 +2002,8 @@ fn create_pipelines(
|
||||
let sprite_pipeline = create_pipeline(
|
||||
factory,
|
||||
sprite::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.sprite-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched("voxygen.shaders.sprite-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.sprite_vert.read().0,
|
||||
&shaders.sprite_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -1970,8 +2012,8 @@ fn create_pipelines(
|
||||
let particle_pipeline = create_pipeline(
|
||||
factory,
|
||||
particle::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.particle-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched("voxygen.shaders.particle-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.particle_vert.read().0,
|
||||
&shaders.particle_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -1980,8 +2022,8 @@ fn create_pipelines(
|
||||
let ui_pipeline = create_pipeline(
|
||||
factory,
|
||||
ui::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.ui-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched("voxygen.shaders.ui-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.ui_vert.read().0,
|
||||
&shaders.ui_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -1990,8 +2032,8 @@ fn create_pipelines(
|
||||
let lod_terrain_pipeline = create_pipeline(
|
||||
factory,
|
||||
lod_terrain::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.lod-terrain-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched("voxygen.shaders.lod-terrain-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.lod_terrain_vert.read().0,
|
||||
&shaders.lod_terrain_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -2000,8 +2042,8 @@ fn create_pipelines(
|
||||
let clouds_pipeline = create_pipeline(
|
||||
factory,
|
||||
clouds::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.clouds-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched("voxygen.shaders.clouds-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.clouds_vert.read().0,
|
||||
&shaders.clouds_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -2010,8 +2052,8 @@ fn create_pipelines(
|
||||
let postprocess_pipeline = create_pipeline(
|
||||
factory,
|
||||
postprocess::pipe::new(),
|
||||
&Glsl::load_watched("voxygen.shaders.postprocess-vert", shader_reload_indicator).unwrap(),
|
||||
&Glsl::load_watched("voxygen.shaders.postprocess-frag", shader_reload_indicator).unwrap(),
|
||||
&shaders.postprocess_vert.read().0,
|
||||
&shaders.postprocess_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -2028,12 +2070,8 @@ fn create_pipelines(
|
||||
),*/),
|
||||
..figure::pipe::new()
|
||||
},
|
||||
&figure_vert,
|
||||
&Glsl::load_watched(
|
||||
"voxygen.shaders.player-shadow-frag",
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap(),
|
||||
&shaders.figure_vert.read().0,
|
||||
&shaders.player_shadow_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
)?;
|
||||
@ -2042,19 +2080,9 @@ fn create_pipelines(
|
||||
let point_shadow_pipeline = match create_shadow_pipeline(
|
||||
factory,
|
||||
shadow::pipe::new(),
|
||||
&terrain_point_shadow_vert,
|
||||
Some(
|
||||
&Glsl::load_watched(
|
||||
"voxygen.shaders.light-shadows-geom",
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
&Glsl::load_watched(
|
||||
"voxygen.shaders.light-shadows-frag",
|
||||
shader_reload_indicator,
|
||||
)
|
||||
.unwrap(),
|
||||
&shaders.terrain_point_shadow_vert.read().0,
|
||||
Some(&shaders.light_shadows_geom.read().0),
|
||||
&shaders.light_shadows_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
None, // Some(gfx::state::Offset(2, 0))
|
||||
@ -2070,9 +2098,9 @@ fn create_pipelines(
|
||||
let terrain_directed_shadow_pipeline = match create_shadow_pipeline(
|
||||
factory,
|
||||
shadow::pipe::new(),
|
||||
&terrain_directed_shadow_vert,
|
||||
&shaders.terrain_directed_shadow_vert.read().0,
|
||||
None,
|
||||
&directed_shadow_frag,
|
||||
&shaders.directed_shadow_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
None, // Some(gfx::state::Offset(2, 1))
|
||||
@ -2091,9 +2119,9 @@ fn create_pipelines(
|
||||
let figure_directed_shadow_pipeline = match create_shadow_pipeline(
|
||||
factory,
|
||||
shadow::figure_pipe::new(),
|
||||
&figure_directed_shadow_vert,
|
||||
&shaders.figure_directed_shadow_vert.read().0,
|
||||
None,
|
||||
&directed_shadow_frag,
|
||||
&shaders.directed_shadow_frag.read().0,
|
||||
&include_ctx,
|
||||
gfx::state::CullFace::Back,
|
||||
None, // Some(gfx::state::Offset(2, 1))
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
use anim::Skeleton;
|
||||
use common::{
|
||||
assets::watch::ReloadIndicator,
|
||||
assets::AssetHandle,
|
||||
comp::{
|
||||
item::{
|
||||
armor::{Armor, ArmorKind},
|
||||
@ -234,8 +234,7 @@ where
|
||||
Skel::Body: BodySpec,
|
||||
{
|
||||
models: HashMap<FigureKey<Skel::Body>, ((FigureModelEntryFuture<LOD_COUNT>, Skel::Attr), u64)>,
|
||||
manifests: Arc<<Skel::Body as BodySpec>::Spec>,
|
||||
manifest_indicator: ReloadIndicator,
|
||||
manifests: AssetHandle<<Skel::Body as BodySpec>::Spec>,
|
||||
}
|
||||
|
||||
impl<Skel: Skeleton> FigureModelCache<Skel>
|
||||
@ -244,15 +243,10 @@ where
|
||||
{
|
||||
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
||||
pub fn new() -> Self {
|
||||
let mut manifest_indicator = ReloadIndicator::new();
|
||||
Self {
|
||||
models: HashMap::new(),
|
||||
// NOTE: It might be better to bubble this error up rather than panicking.
|
||||
manifests: Arc::new(
|
||||
<Skel::Body as BodySpec>::load_watched(&mut manifest_indicator)
|
||||
.expect("Could not load manifests for body type"),
|
||||
),
|
||||
manifest_indicator,
|
||||
manifests: <Skel::Body as BodySpec>::load_spec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,13 +358,13 @@ where
|
||||
Entry::Vacant(v) => {
|
||||
let key = v.key().clone();
|
||||
let slot = Arc::new(atomic::AtomicCell::new(None));
|
||||
let manifests = Arc::clone(&self.manifests);
|
||||
let manifests = self.manifests;
|
||||
let slot_ = Arc::clone(&slot);
|
||||
|
||||
thread_pool.execute(move || {
|
||||
// First, load all the base vertex data.
|
||||
let manifests = &*manifests;
|
||||
let meshes = <Skel::Body as BodySpec>::bone_meshes(&key, &*manifests);
|
||||
let manifests = &*manifests.read();
|
||||
let meshes = <Skel::Body as BodySpec>::bone_meshes(&key, manifests);
|
||||
|
||||
// Then, set up meshing context.
|
||||
let mut greedy = FigureModel::make_greedy();
|
||||
@ -522,12 +516,9 @@ where
|
||||
{
|
||||
// Check for reloaded manifests
|
||||
// TODO: maybe do this in a different function, maintain?
|
||||
if self.manifest_indicator.reloaded() {
|
||||
if self.manifests.reloaded() {
|
||||
col_lights.atlas.clear();
|
||||
self.models.clear();
|
||||
if let Err(err) = <Skel::Body as BodySpec>::reload(Arc::make_mut(&mut self.manifests)) {
|
||||
tracing::warn!(?err, "Hot reload failed.");
|
||||
}
|
||||
}
|
||||
// TODO: Don't hard-code this.
|
||||
if tick % 60 == 0 {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::cache::FigureKey;
|
||||
use common::{
|
||||
assets::{self, watch::ReloadIndicator, Asset, AssetWith, Ron},
|
||||
assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron},
|
||||
comp::{
|
||||
biped_large::{self, BodyType as BLBodyType, Species as BLSpecies},
|
||||
bird_medium::{self, BodyType as BMBodyType, Species as BMSpecies},
|
||||
@ -18,10 +18,8 @@ use common::{
|
||||
},
|
||||
figure::{DynaUnionizer, MatSegment, Material, Segment},
|
||||
};
|
||||
use dot_vox::DotVoxData;
|
||||
use hashbrown::HashMap;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, warn};
|
||||
use vek::*;
|
||||
|
||||
@ -29,29 +27,29 @@ pub type BoneMeshes = (Segment, Vec3<f32>);
|
||||
|
||||
fn load_segment(mesh_name: &str) -> Segment {
|
||||
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
|
||||
Segment::from(DotVoxData::load_expect(full_specifier.as_str()).as_ref())
|
||||
Segment::from(&DotVoxAsset::load_expect(&full_specifier).read().0)
|
||||
}
|
||||
fn graceful_load_vox(mesh_name: &str) -> Arc<DotVoxData> {
|
||||
fn graceful_load_vox(mesh_name: &str) -> AssetHandle<DotVoxAsset> {
|
||||
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
|
||||
match DotVoxData::load(full_specifier.as_str()) {
|
||||
match DotVoxAsset::load(&full_specifier) {
|
||||
Ok(dot_vox) => dot_vox,
|
||||
Err(_) => {
|
||||
error!(?full_specifier, "Could not load vox file for figure");
|
||||
DotVoxData::load_expect("voxygen.voxel.not_found")
|
||||
DotVoxAsset::load_expect("voxygen.voxel.not_found")
|
||||
},
|
||||
}
|
||||
}
|
||||
fn graceful_load_segment(mesh_name: &str) -> Segment {
|
||||
Segment::from(graceful_load_vox(mesh_name).as_ref())
|
||||
Segment::from(&graceful_load_vox(mesh_name).read().0)
|
||||
}
|
||||
fn graceful_load_segment_flipped(mesh_name: &str) -> Segment {
|
||||
Segment::from_vox(graceful_load_vox(mesh_name).as_ref(), true)
|
||||
Segment::from_vox(&graceful_load_vox(mesh_name).read().0, true)
|
||||
}
|
||||
fn graceful_load_mat_segment(mesh_name: &str) -> MatSegment {
|
||||
MatSegment::from(graceful_load_vox(mesh_name).as_ref())
|
||||
MatSegment::from(&graceful_load_vox(mesh_name).read().0)
|
||||
}
|
||||
fn graceful_load_mat_segment_flipped(mesh_name: &str) -> MatSegment {
|
||||
MatSegment::from_vox(graceful_load_vox(mesh_name).as_ref(), true)
|
||||
MatSegment::from_vox(&graceful_load_vox(mesh_name).read().0, true)
|
||||
}
|
||||
|
||||
pub fn load_mesh(mesh_name: &str, position: Vec3<f32>) -> BoneMeshes {
|
||||
@ -76,12 +74,8 @@ fn recolor_grey(rgb: Rgb<u8>, color: Rgb<u8>) -> Rgb<u8> {
|
||||
pub trait BodySpec: Sized {
|
||||
type Spec;
|
||||
|
||||
/// Initialize all the specifications for this Body and watch for changes.
|
||||
fn load_watched(indicator: &mut ReloadIndicator) -> Result<Self::Spec, assets::Error>;
|
||||
|
||||
/// Reload all specifications for this Body (to be called if the reload
|
||||
/// indicator is set).
|
||||
fn reload(spec: &mut Self::Spec) -> Result<(), assets::Error>;
|
||||
/// Initialize all the specifications for this Body.
|
||||
fn load_spec() -> Result<AssetHandle<Self::Spec>, assets::Error>;
|
||||
|
||||
/// Mesh bones using the given spec, character state, and mesh generation
|
||||
/// function.
|
||||
@ -104,23 +98,23 @@ macro_rules! make_vox_spec {
|
||||
) => {
|
||||
#[derive(Clone)]
|
||||
pub struct $Spec {
|
||||
$( $field: AssetWith<Ron<$ty>, $asset_path>, )*
|
||||
$( $field: AssetHandle<Ron<$ty>>, )*
|
||||
}
|
||||
|
||||
impl assets::Compound for $Spec {
|
||||
fn load<S: assets::source::Source>(_: &assets::AssetCache<S>, _: &str) -> Result<Self, assets::Error> {
|
||||
Ok($Spec {
|
||||
$( $field: AssetExt::load($asset_path)?, )*
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BodySpec for $body {
|
||||
type Spec = $Spec;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn load_watched(indicator: &mut ReloadIndicator) -> Result<Self::Spec, assets::Error> {
|
||||
Ok(Self::Spec {
|
||||
$( $field: AssetWith::load_watched(indicator)?, )*
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn reload(spec: &mut Self::Spec) -> Result<(), assets::Error> {
|
||||
$( spec.$field.reload()?; )*
|
||||
Ok(())
|
||||
fn load_spec() -> Result<AssetHandle<Self::Spec>, assets::Error> {
|
||||
Self::Spec::load("")
|
||||
}
|
||||
|
||||
fn bone_meshes(
|
||||
@ -375,95 +369,97 @@ make_vox_spec!(
|
||||
let hand = loadout.hand.as_deref();
|
||||
let foot = loadout.foot.as_deref();
|
||||
|
||||
let color = &spec.color.read().0;
|
||||
|
||||
[
|
||||
third_person.map(|_| {
|
||||
spec.head.asset.mesh_head(
|
||||
spec.head.read().0.mesh_head(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
)
|
||||
}),
|
||||
third_person.map(|loadout| {
|
||||
spec.armor_chest.asset.mesh_chest(
|
||||
spec.armor_chest.read().0.mesh_chest(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
loadout.chest.as_deref(),
|
||||
)
|
||||
}),
|
||||
third_person.map(|loadout| {
|
||||
spec.armor_belt.asset.mesh_belt(
|
||||
spec.armor_belt.read().0.mesh_belt(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
loadout.belt.as_deref(),
|
||||
)
|
||||
}),
|
||||
third_person.map(|loadout| {
|
||||
spec.armor_back.asset.mesh_back(
|
||||
spec.armor_back.read().0.mesh_back(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
loadout.back.as_deref(),
|
||||
)
|
||||
}),
|
||||
third_person.map(|loadout| {
|
||||
spec.armor_pants.asset.mesh_pants(
|
||||
spec.armor_pants.read().0.mesh_pants(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
loadout.pants.as_deref(),
|
||||
)
|
||||
}),
|
||||
Some(spec.armor_hand.asset.mesh_left_hand(
|
||||
Some(spec.armor_hand.read().0.mesh_left_hand(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
hand,
|
||||
)),
|
||||
Some(spec.armor_hand.asset.mesh_right_hand(
|
||||
Some(spec.armor_hand.read().0.mesh_right_hand(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
hand,
|
||||
)),
|
||||
Some(spec.armor_foot.asset.mesh_left_foot(
|
||||
Some(spec.armor_foot.read().0.mesh_left_foot(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
foot,
|
||||
)),
|
||||
Some(spec.armor_foot.asset.mesh_right_foot(
|
||||
Some(spec.armor_foot.read().0.mesh_right_foot(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
foot,
|
||||
)),
|
||||
third_person.map(|loadout| {
|
||||
spec.armor_shoulder.asset.mesh_left_shoulder(
|
||||
spec.armor_shoulder.read().0.mesh_left_shoulder(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
loadout.shoulder.as_deref(),
|
||||
)
|
||||
}),
|
||||
third_person.map(|loadout| {
|
||||
spec.armor_shoulder.asset.mesh_right_shoulder(
|
||||
spec.armor_shoulder.read().0.mesh_right_shoulder(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
loadout.shoulder.as_deref(),
|
||||
)
|
||||
}),
|
||||
Some(spec.armor_glider.asset.mesh_glider(
|
||||
Some(spec.armor_glider.read().0.mesh_glider(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
glider,
|
||||
)),
|
||||
tool.and_then(|tool| tool.active.as_ref()).map(|tool| {
|
||||
spec.main_weapon.asset.mesh_main_weapon(
|
||||
spec.main_weapon.read().0.mesh_main_weapon(
|
||||
tool,
|
||||
false,
|
||||
)
|
||||
}),
|
||||
tool.and_then(|tool| tool.second.as_ref()).map(|tool| {
|
||||
spec.main_weapon.asset.mesh_main_weapon(
|
||||
spec.main_weapon.read().0.mesh_main_weapon(
|
||||
tool,
|
||||
true,
|
||||
)
|
||||
}),
|
||||
Some(spec.armor_lantern.asset.mesh_lantern(
|
||||
Some(spec.armor_lantern.read().0.mesh_lantern(
|
||||
body,
|
||||
&spec.color.asset,
|
||||
color,
|
||||
lantern,
|
||||
)),
|
||||
Some(mesh_hold()),
|
||||
@ -1065,31 +1061,31 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head(
|
||||
Some(spec.central.read().0.mesh_head(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_chest(
|
||||
Some(spec.central.read().0.mesh_chest(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_fl(
|
||||
Some(spec.lateral.read().0.mesh_foot_fl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_fr(
|
||||
Some(spec.lateral.read().0.mesh_foot_fr(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_bl(
|
||||
Some(spec.lateral.read().0.mesh_foot_bl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_br(
|
||||
Some(spec.lateral.read().0.mesh_foot_br(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail(
|
||||
Some(spec.central.read().0.mesh_tail(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -1269,63 +1265,63 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head(
|
||||
Some(spec.central.read().0.mesh_head(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_neck(
|
||||
Some(spec.central.read().0.mesh_neck(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_jaw(
|
||||
Some(spec.central.read().0.mesh_jaw(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail(
|
||||
Some(spec.central.read().0.mesh_tail(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_torso_front(
|
||||
Some(spec.central.read().0.mesh_torso_front(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_torso_back(
|
||||
Some(spec.central.read().0.mesh_torso_back(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_ears(
|
||||
Some(spec.central.read().0.mesh_ears(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_fl(
|
||||
Some(spec.lateral.read().0.mesh_leg_fl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_fr(
|
||||
Some(spec.lateral.read().0.mesh_leg_fr(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_bl(
|
||||
Some(spec.lateral.read().0.mesh_leg_bl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_br(
|
||||
Some(spec.lateral.read().0.mesh_leg_br(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_fl(
|
||||
Some(spec.lateral.read().0.mesh_foot_fl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_fr(
|
||||
Some(spec.lateral.read().0.mesh_foot_fr(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_bl(
|
||||
Some(spec.lateral.read().0.mesh_foot_bl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_br(
|
||||
Some(spec.lateral.read().0.mesh_foot_br(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -1618,31 +1614,31 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head(
|
||||
Some(spec.central.read().0.mesh_head(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_torso(
|
||||
Some(spec.central.read().0.mesh_torso(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail(
|
||||
Some(spec.central.read().0.mesh_tail(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_wing_l(
|
||||
Some(spec.lateral.read().0.mesh_wing_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_wing_r(
|
||||
Some(spec.lateral.read().0.mesh_wing_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_l(
|
||||
Some(spec.lateral.read().0.mesh_foot_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_r(
|
||||
Some(spec.lateral.read().0.mesh_foot_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -1817,55 +1813,55 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head(
|
||||
Some(spec.central.read().0.mesh_head(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_jaw(
|
||||
Some(spec.central.read().0.mesh_jaw(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_neck(
|
||||
Some(spec.central.read().0.mesh_neck(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_chest_front(
|
||||
Some(spec.central.read().0.mesh_chest_front(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_chest_back(
|
||||
Some(spec.central.read().0.mesh_chest_back(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail_front(
|
||||
Some(spec.central.read().0.mesh_tail_front(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail_back(
|
||||
Some(spec.central.read().0.mesh_tail_back(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_hand_l(
|
||||
Some(spec.lateral.read().0.mesh_hand_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_hand_r(
|
||||
Some(spec.lateral.read().0.mesh_hand_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_l(
|
||||
Some(spec.lateral.read().0.mesh_leg_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_r(
|
||||
Some(spec.lateral.read().0.mesh_leg_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_l(
|
||||
Some(spec.lateral.read().0.mesh_foot_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_r(
|
||||
Some(spec.lateral.read().0.mesh_foot_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -2124,31 +2120,31 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head(
|
||||
Some(spec.central.read().0.mesh_head(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_jaw(
|
||||
Some(spec.central.read().0.mesh_jaw(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_chest_front(
|
||||
Some(spec.central.read().0.mesh_chest_front(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_chest_back(
|
||||
Some(spec.central.read().0.mesh_chest_back(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail(
|
||||
Some(spec.central.read().0.mesh_tail(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_fin_l(
|
||||
Some(spec.lateral.read().0.mesh_fin_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_fin_r(
|
||||
Some(spec.lateral.read().0.mesh_fin_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -2316,19 +2312,19 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_chest(
|
||||
Some(spec.central.read().0.mesh_chest(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail(
|
||||
Some(spec.central.read().0.mesh_tail(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_fin_l(
|
||||
Some(spec.lateral.read().0.mesh_fin_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_fin_r(
|
||||
Some(spec.lateral.read().0.mesh_fin_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -2465,63 +2461,63 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head_upper(
|
||||
Some(spec.central.read().0.mesh_head_upper(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_head_lower(
|
||||
Some(spec.central.read().0.mesh_head_lower(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_jaw(
|
||||
Some(spec.central.read().0.mesh_jaw(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_chest_front(
|
||||
Some(spec.central.read().0.mesh_chest_front(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_chest_rear(
|
||||
Some(spec.central.read().0.mesh_chest_rear(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail_front(
|
||||
Some(spec.central.read().0.mesh_tail_front(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail_rear(
|
||||
Some(spec.central.read().0.mesh_tail_rear(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_wing_in_l(
|
||||
Some(spec.lateral.read().0.mesh_wing_in_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_wing_in_r(
|
||||
Some(spec.lateral.read().0.mesh_wing_in_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_wing_out_l(
|
||||
Some(spec.lateral.read().0.mesh_wing_out_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_wing_out_r(
|
||||
Some(spec.lateral.read().0.mesh_wing_out_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_fl(
|
||||
Some(spec.lateral.read().0.mesh_foot_fl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_fr(
|
||||
Some(spec.lateral.read().0.mesh_foot_fr(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_bl(
|
||||
Some(spec.lateral.read().0.mesh_foot_bl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_br(
|
||||
Some(spec.lateral.read().0.mesh_foot_br(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -2883,63 +2879,63 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head(
|
||||
Some(spec.central.read().0.mesh_head(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_jaw(
|
||||
Some(spec.central.read().0.mesh_jaw(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_torso_upper(
|
||||
Some(spec.central.read().0.mesh_torso_upper(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_torso_lower(
|
||||
Some(spec.central.read().0.mesh_torso_lower(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail(
|
||||
Some(spec.central.read().0.mesh_tail(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_main(
|
||||
Some(spec.central.read().0.mesh_main(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_second(
|
||||
Some(spec.central.read().0.mesh_second(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_shoulder_l(
|
||||
Some(spec.lateral.read().0.mesh_shoulder_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_shoulder_r(
|
||||
Some(spec.lateral.read().0.mesh_shoulder_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_hand_l(
|
||||
Some(spec.lateral.read().0.mesh_hand_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_hand_r(
|
||||
Some(spec.lateral.read().0.mesh_hand_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_l(
|
||||
Some(spec.lateral.read().0.mesh_leg_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_r(
|
||||
Some(spec.lateral.read().0.mesh_leg_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_l(
|
||||
Some(spec.lateral.read().0.mesh_foot_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_r(
|
||||
Some(spec.lateral.read().0.mesh_foot_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -3235,51 +3231,51 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head(
|
||||
Some(spec.central.read().0.mesh_head(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_jaw(
|
||||
Some(spec.central.read().0.mesh_jaw(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_torso_upper(
|
||||
Some(spec.central.read().0.mesh_torso_upper(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_torso_lower(
|
||||
Some(spec.central.read().0.mesh_torso_lower(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_shoulder_l(
|
||||
Some(spec.lateral.read().0.mesh_shoulder_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_shoulder_r(
|
||||
Some(spec.lateral.read().0.mesh_shoulder_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_hand_l(
|
||||
Some(spec.lateral.read().0.mesh_hand_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_hand_r(
|
||||
Some(spec.lateral.read().0.mesh_hand_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_l(
|
||||
Some(spec.lateral.read().0.mesh_leg_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_leg_r(
|
||||
Some(spec.lateral.read().0.mesh_leg_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_l(
|
||||
Some(spec.lateral.read().0.mesh_foot_l(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_r(
|
||||
Some(spec.lateral.read().0.mesh_foot_r(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -3529,43 +3525,43 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_head_upper(
|
||||
Some(spec.central.read().0.mesh_head_upper(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_head_lower(
|
||||
Some(spec.central.read().0.mesh_head_lower(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_jaw(
|
||||
Some(spec.central.read().0.mesh_jaw(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_chest(
|
||||
Some(spec.central.read().0.mesh_chest(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail_front(
|
||||
Some(spec.central.read().0.mesh_tail_front(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.central.asset.mesh_tail_rear(
|
||||
Some(spec.central.read().0.mesh_tail_rear(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_fl(
|
||||
Some(spec.lateral.read().0.mesh_foot_fl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_fr(
|
||||
Some(spec.lateral.read().0.mesh_foot_fr(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_bl(
|
||||
Some(spec.lateral.read().0.mesh_foot_bl(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
Some(spec.lateral.asset.mesh_foot_br(
|
||||
Some(spec.lateral.read().0.mesh_foot_br(
|
||||
body.species,
|
||||
body.body_type,
|
||||
)),
|
||||
@ -3765,7 +3761,7 @@ make_vox_spec!(
|
||||
},
|
||||
|FigureKey { body, .. }, spec| {
|
||||
[
|
||||
Some(spec.central.asset.mesh_bone0(
|
||||
Some(spec.central.read().0.mesh_bone0(
|
||||
body,
|
||||
)),
|
||||
None,
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
assets::{AssetExt, DotVoxAsset},
|
||||
comp::{item::Reagent, object, Body, CharacterState, Ori, Pos, Shockwave},
|
||||
figure::Segment,
|
||||
outcome::Outcome,
|
||||
@ -18,7 +18,6 @@ use common::{
|
||||
terrain::TerrainChunk,
|
||||
vol::{RectRasterableVol, SizedVol},
|
||||
};
|
||||
use dot_vox::DotVoxData;
|
||||
use hashbrown::HashMap;
|
||||
use rand::prelude::*;
|
||||
use specs::{Join, WorldExt};
|
||||
@ -686,7 +685,7 @@ fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model<Particl
|
||||
let mut model_cache = HashMap::new();
|
||||
|
||||
model_cache.entry(DEFAULT_MODEL_KEY).or_insert_with(|| {
|
||||
let vox = DotVoxData::load_expect(DEFAULT_MODEL_KEY);
|
||||
let vox = DotVoxAsset::load_expect(DEFAULT_MODEL_KEY);
|
||||
|
||||
// NOTE: If we add texturing we may eventually try to share it among all
|
||||
// particles in a single atlas.
|
||||
@ -695,7 +694,7 @@ fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model<Particl
|
||||
guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size));
|
||||
let mut greedy = GreedyMesh::new(max_size);
|
||||
|
||||
let segment = Segment::from(vox.as_ref());
|
||||
let segment = Segment::from(&vox.read().0);
|
||||
let segment_size = segment.size();
|
||||
let mut mesh =
|
||||
Meshable::<ParticlePipeline, &mut GreedyMesh>::generate_mesh(segment, &mut greedy).0;
|
||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||
|
||||
use super::{math, LodData, SceneData};
|
||||
use common::{
|
||||
assets::{Asset, Ron},
|
||||
assets::{self, AssetExt, DotVoxAsset},
|
||||
figure::Segment,
|
||||
span,
|
||||
spiral::Spiral2d,
|
||||
@ -23,11 +23,9 @@ use common::{
|
||||
};
|
||||
use core::{f32, fmt::Debug, i32, marker::PhantomData, time::Duration};
|
||||
use crossbeam::channel;
|
||||
use dot_vox::DotVoxData;
|
||||
use enum_iterator::IntoEnumIterator;
|
||||
use guillotiere::AtlasAllocator;
|
||||
use hashbrown::HashMap;
|
||||
use image::DynamicImage;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use tracing::warn;
|
||||
@ -125,7 +123,14 @@ struct SpriteConfig<Model> {
|
||||
/// Configuration data for all sprite models.
|
||||
///
|
||||
/// NOTE: Model is an asset path to the appropriate sprite .vox model.
|
||||
type SpriteSpec = sprite::sprite_kind::PureCases<Option<SpriteConfig<String>>>;
|
||||
#[derive(Deserialize)]
|
||||
#[serde(transparent)]
|
||||
struct SpriteSpec(sprite::sprite_kind::PureCases<Option<SpriteConfig<String>>>);
|
||||
|
||||
impl assets::Asset for SpriteSpec {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
}
|
||||
|
||||
/// Function executed by worker threads dedicated to chunk meshing.
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
@ -177,7 +182,7 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug + '
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(cfg) = sprite.elim_case_pure(&sprite_config) {
|
||||
if let Some(cfg) = sprite.elim_case_pure(&sprite_config.0) {
|
||||
let seed = wpos.x as u64 * 3
|
||||
+ wpos.y as u64 * 7
|
||||
+ wpos.x as u64 * wpos.y as u64; // Awful PRNG
|
||||
@ -227,6 +232,9 @@ struct SpriteData {
|
||||
|
||||
pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
|
||||
atlas: AtlasAllocator,
|
||||
/// FIXME: This could possibly become an `AssetHandle<SpriteSpec>`, to get
|
||||
/// hot-reloading for free, but I am not sure if sudden changes of this
|
||||
/// value would break something
|
||||
sprite_config: Arc<SpriteSpec>,
|
||||
chunks: HashMap<Vec2<i32>, TerrainChunkData>,
|
||||
/// Temporary storage for dead chunks that might still be shadowing chunks
|
||||
@ -268,8 +276,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||
pub fn new(renderer: &mut Renderer) -> Self {
|
||||
// Load all the sprite config data.
|
||||
let sprite_config = Ron::<SpriteSpec>::load("voxygen.voxel.sprite_manifest")
|
||||
.expect("Failed to find sprite model data!");
|
||||
let sprite_config = Arc::<SpriteSpec>::load_expect("voxygen.voxel.sprite_manifest").cloned();
|
||||
|
||||
// Create a new mpsc (Multiple Produced, Single Consumer) pair for communicating
|
||||
// with worker threads that are meshing chunks.
|
||||
@ -287,7 +294,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
// NOTE: Tracks the start vertex of the next model to be meshed.
|
||||
|
||||
let sprite_data: HashMap<(SpriteKind, usize), _> = SpriteKind::into_enum_iter()
|
||||
.filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_).as_ref()?)))
|
||||
.filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_.0).as_ref()?)))
|
||||
.flat_map(|(kind, sprite_config)| {
|
||||
let wind_sway = sprite_config.wind_sway;
|
||||
sprite_config.variations.iter().enumerate().map(
|
||||
@ -302,9 +309,11 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
let scaled = [1.0, 0.8, 0.6, 0.4, 0.2];
|
||||
let offset = Vec3::from(*offset);
|
||||
let lod_axes = Vec3::from(*lod_axes);
|
||||
let model = DotVoxData::load_expect(model);
|
||||
let model = DotVoxAsset::load_expect(model);
|
||||
let zero = Vec3::zero();
|
||||
let model_size = model
|
||||
.read()
|
||||
.0
|
||||
.models
|
||||
.first()
|
||||
.map(
|
||||
@ -344,7 +353,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
// interesting return value, but updates the mesh.
|
||||
let mut opaque_mesh = Mesh::new();
|
||||
Meshable::<SpritePipeline, &mut GreedyMesh>::generate_mesh(
|
||||
Segment::from(model.as_ref()).scaled_by(lod_scale),
|
||||
Segment::from(&model.read().0).scaled_by(lod_scale),
|
||||
(
|
||||
greedy,
|
||||
&mut opaque_mesh,
|
||||
@ -403,7 +412,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
sprite_col_lights,
|
||||
waves: renderer
|
||||
.create_texture(
|
||||
&DynamicImage::load_expect("voxygen.texture.waves"),
|
||||
&assets::Image::load_expect("voxygen.texture.waves").read().0,
|
||||
Some(gfx::texture::FilterMethod::Trilinear),
|
||||
Some(gfx::texture::WrapMode::Tile),
|
||||
None,
|
||||
@ -1154,7 +1163,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
for (kind, instances) in (&chunk.sprite_instances).into_iter() {
|
||||
let SpriteData { model, locals, .. } = if kind
|
||||
.0
|
||||
.elim_case_pure(&self.sprite_config)
|
||||
.elim_case_pure(&self.sprite_config.0)
|
||||
.as_ref()
|
||||
.map(|config| config.wind_sway >= 0.4)
|
||||
.unwrap_or(false)
|
||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||
};
|
||||
use client::{self, Client};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
assets::AssetExt,
|
||||
comp,
|
||||
comp::{ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel},
|
||||
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
|
||||
@ -29,7 +29,7 @@ use common::{
|
||||
use common_net::msg::PresenceKind;
|
||||
use ordered_float::OrderedFloat;
|
||||
use specs::{Join, WorldExt};
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration};
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
use tracing::{error, info};
|
||||
use vek::*;
|
||||
|
||||
@ -48,7 +48,6 @@ pub struct SessionState {
|
||||
key_state: KeyState,
|
||||
inputs: comp::ControllerInputs,
|
||||
selected_block: Block,
|
||||
i18n: std::sync::Arc<Localization>,
|
||||
walk_forward_dir: Vec2<f32>,
|
||||
walk_right_dir: Vec2<f32>,
|
||||
freefly_vel: Vec3<f32>,
|
||||
@ -75,10 +74,6 @@ impl SessionState {
|
||||
.camera_mut()
|
||||
.set_fov_deg(global_state.settings.graphics.fov);
|
||||
let hud = Hud::new(global_state, &client.borrow());
|
||||
let i18n = Localization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
));
|
||||
|
||||
let walk_forward_dir = scene.camera().forward_xy();
|
||||
let walk_right_dir = scene.camera().right_xy();
|
||||
|
||||
@ -89,7 +84,6 @@ impl SessionState {
|
||||
inputs: comp::ControllerInputs::default(),
|
||||
hud,
|
||||
selected_block: Block::new(BlockKind::Misc, Rgb::broadcast(255)),
|
||||
i18n,
|
||||
walk_forward_dir,
|
||||
walk_right_dir,
|
||||
freefly_vel: Vec3::zero(),
|
||||
@ -125,24 +119,27 @@ impl SessionState {
|
||||
self.hud.new_message(m);
|
||||
},
|
||||
client::Event::InventoryUpdated(inv_event) => {
|
||||
let sfx_trigger_item = self
|
||||
let sfx_triggers = self
|
||||
.scene
|
||||
.sfx_mgr
|
||||
.triggers
|
||||
.get_key_value(&SfxEvent::from(&inv_event));
|
||||
.read();
|
||||
|
||||
let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
|
||||
global_state.audio.emit_sfx_item(sfx_trigger_item);
|
||||
|
||||
let i18n = global_state.i18n.read();
|
||||
|
||||
match inv_event {
|
||||
InventoryUpdateEvent::CollectFailed => {
|
||||
self.hud.new_message(ChatMsg {
|
||||
message: self.i18n.get("hud.chat.loot_fail").to_string(),
|
||||
message: i18n.get("hud.chat.loot_fail").to_string(),
|
||||
chat_type: ChatType::CommandError,
|
||||
});
|
||||
},
|
||||
InventoryUpdateEvent::Collected(item) => {
|
||||
self.hud.new_message(ChatMsg {
|
||||
message: self
|
||||
.i18n
|
||||
message: i18n
|
||||
.get("hud.chat.loot_msg")
|
||||
.replace("{item}", item.name()),
|
||||
chat_type: ChatType::Loot,
|
||||
@ -153,10 +150,11 @@ impl SessionState {
|
||||
},
|
||||
client::Event::Disconnect => return Ok(TickAction::Disconnect),
|
||||
client::Event::DisconnectionNotification(time) => {
|
||||
let i18n = global_state.i18n.read();
|
||||
|
||||
let message = match time {
|
||||
0 => String::from(self.i18n.get("hud.chat.goodbye")),
|
||||
_ => self
|
||||
.i18n
|
||||
0 => String::from(i18n.get("hud.chat.goodbye")),
|
||||
_ => i18n
|
||||
.get("hud.chat.connection_lost")
|
||||
.replace("{time}", time.to_string().as_str()),
|
||||
};
|
||||
@ -169,7 +167,7 @@ impl SessionState {
|
||||
client::Event::Kicked(reason) => {
|
||||
global_state.info_message = Some(format!(
|
||||
"{}: {}",
|
||||
self.i18n.get("main.login.kicked").to_string(),
|
||||
global_state.i18n.read().get("main.login.kicked").to_string(),
|
||||
reason
|
||||
));
|
||||
return Ok(TickAction::Disconnect);
|
||||
@ -212,10 +210,6 @@ impl PlayState for SessionState {
|
||||
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
||||
span!(_guard, "tick", "<Session as PlayState>::tick");
|
||||
// TODO: let mut client = self.client.borrow_mut();
|
||||
// NOTE: Not strictly necessary, but useful for hotloading translation changes.
|
||||
self.i18n = Localization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
));
|
||||
|
||||
// TODO: can this be a method on the session or are there borrowcheck issues?
|
||||
let (client_presence, client_registered) = {
|
||||
@ -681,7 +675,7 @@ impl PlayState for SessionState {
|
||||
Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu
|
||||
Err(err) => {
|
||||
global_state.info_message =
|
||||
Some(self.i18n.get("common.connection_lost").to_owned());
|
||||
Some(global_state.i18n.read().get("common.connection_lost").to_owned());
|
||||
error!("[session] Failed to tick the scene: {:?}", err);
|
||||
|
||||
return PlayStateResult::Pop;
|
||||
@ -758,9 +752,9 @@ impl PlayState for SessionState {
|
||||
);
|
||||
|
||||
// Look for changes in the localization files
|
||||
if global_state.localization_watcher.reloaded() {
|
||||
if global_state.i18n.reloaded() {
|
||||
hud_events.push(HudEvent::ChangeLanguage(Box::new(
|
||||
self.i18n.metadata.clone(),
|
||||
global_state.i18n.read().metadata.clone(),
|
||||
)));
|
||||
}
|
||||
|
||||
@ -1016,13 +1010,11 @@ impl PlayState for SessionState {
|
||||
HudEvent::ChangeLanguage(new_language) => {
|
||||
global_state.settings.language.selected_language =
|
||||
new_language.language_identifier;
|
||||
self.i18n = Localization::load_watched(
|
||||
global_state.i18n = Localization::load_expect(
|
||||
&i18n_asset_key(&global_state.settings.language.selected_language),
|
||||
&mut global_state.localization_watcher,
|
||||
)
|
||||
.unwrap();
|
||||
self.i18n.log_missing_entries();
|
||||
self.hud.update_language(Arc::clone(&self.i18n));
|
||||
);
|
||||
global_state.i18n.read().log_missing_entries();
|
||||
self.hud.update_fonts(&global_state.i18n.read());
|
||||
},
|
||||
HudEvent::ChangeFullscreenMode(new_fullscreen_settings) => {
|
||||
global_state
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::i18n;
|
||||
use common::assets::Asset;
|
||||
use crate::{i18n, ui::ice::RawFont};
|
||||
use common::assets::{self, AssetExt};
|
||||
|
||||
pub struct Font {
|
||||
metadata: i18n::Font,
|
||||
@ -8,11 +8,13 @@ pub struct Font {
|
||||
|
||||
impl Font {
|
||||
#[allow(clippy::needless_return)] // TODO: Pending review in #587
|
||||
pub fn new(font: &i18n::Font, ui: &mut crate::ui::Ui) -> Self {
|
||||
Self {
|
||||
fn new(font: &i18n::Font, ui: &mut crate::ui::Ui) -> Result<Self, assets::Error> {
|
||||
let raw_font = RawFont::load(&font.asset_key)?.cloned();
|
||||
|
||||
Ok(Self {
|
||||
metadata: font.clone(),
|
||||
conrod_id: ui.new_font(crate::ui::ice::RawFont::load_expect(&font.asset_key)),
|
||||
}
|
||||
conrod_id: ui.new_font(raw_font),
|
||||
})
|
||||
}
|
||||
|
||||
/// Scale input size to final UI size
|
||||
@ -27,9 +29,9 @@ macro_rules! conrod_fonts {
|
||||
}
|
||||
|
||||
impl Fonts {
|
||||
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::Ui) -> Result<Self, common::assets::Error> {
|
||||
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::Ui) -> Result<Self, assets::Error> {
|
||||
Ok(Self {
|
||||
$( $name: Font::new(fonts.get(stringify!($name)).unwrap(), ui),)*
|
||||
$( $name: Font::new(fonts.get(stringify!($name)).unwrap(), ui)?, )*
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -47,11 +49,13 @@ pub struct IcedFont {
|
||||
}
|
||||
|
||||
impl IcedFont {
|
||||
pub fn new(font: &i18n::Font, ui: &mut crate::ui::ice::IcedUi) -> Self {
|
||||
Self {
|
||||
fn new(font: &i18n::Font, ui: &mut crate::ui::ice::IcedUi) -> Result<Self, assets::Error> {
|
||||
let raw_font = RawFont::load(&font.asset_key)?.cloned();
|
||||
|
||||
Ok(Self {
|
||||
metadata: font.clone(),
|
||||
id: ui.add_font((*crate::ui::ice::RawFont::load_expect(&font.asset_key)).clone()),
|
||||
}
|
||||
id: ui.add_font(raw_font),
|
||||
})
|
||||
}
|
||||
|
||||
/// Scale input size to final UI size
|
||||
@ -67,9 +71,9 @@ macro_rules! iced_fonts {
|
||||
}
|
||||
|
||||
impl IcedFonts {
|
||||
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::ice::IcedUi) -> Result<Self, common::assets::Error> {
|
||||
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::ice::IcedUi) -> Result<Self, assets::Error> {
|
||||
Ok(Self {
|
||||
$( $name: IcedFont::new(fonts.get(stringify!($name)).unwrap(), ui),)*
|
||||
$( $name: IcedFont::new(fonts.get(stringify!($name)).unwrap(), ui)?, )*
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ use crate::{
|
||||
render::{Renderer, Texture},
|
||||
Error,
|
||||
};
|
||||
use common::assets::{self, AssetExt};
|
||||
use glyph_brush::GlyphBrushBuilder;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::{borrow::Cow, cell::{RefCell, RefMut}};
|
||||
use vek::*;
|
||||
|
||||
// Multiplied by current window size
|
||||
@ -20,6 +21,24 @@ type GlyphBrush = glyph_brush::GlyphBrush<(Aabr<f32>, Aabr<f32>), ()>;
|
||||
// TODO: might not need pub
|
||||
pub type Font = glyph_brush::ab_glyph::FontArc;
|
||||
|
||||
struct FontAsset(Font);
|
||||
struct FontLoader;
|
||||
impl assets::Loader<FontAsset> for FontLoader {
|
||||
fn load(data: Cow<[u8]>, _: &str) -> Result<FontAsset, assets::BoxedError> {
|
||||
let font = Font::try_from_vec(data.into_owned())?;
|
||||
Ok(FontAsset(font))
|
||||
}
|
||||
}
|
||||
|
||||
impl assets::Asset for FontAsset {
|
||||
const EXTENSION: &'static str = "ttf";
|
||||
type Loader = FontLoader;
|
||||
}
|
||||
|
||||
pub fn load_font(specifier: &str) -> Font {
|
||||
FontAsset::load_expect(specifier).read().0.clone()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct FontId(pub(super) glyph_brush::FontId);
|
||||
|
||||
@ -121,16 +140,14 @@ impl Cache {
|
||||
// TODO: use font type instead of raw vec once we convert to full iced
|
||||
#[derive(Clone)]
|
||||
pub struct RawFont(pub Vec<u8>);
|
||||
impl common::assets::Asset for RawFont {
|
||||
const ENDINGS: &'static [&'static str] = &["ttf"];
|
||||
|
||||
fn parse(
|
||||
mut buf_reader: std::io::BufReader<std::fs::File>,
|
||||
_specifier: &str,
|
||||
) -> Result<Self, common::assets::Error> {
|
||||
use std::io::Read;
|
||||
let mut buf = Vec::new();
|
||||
buf_reader.read_to_end(&mut buf)?;
|
||||
Ok(Self(buf))
|
||||
impl From<Vec<u8>> for RawFont {
|
||||
fn from(raw: Vec<u8>) -> RawFont {
|
||||
RawFont(raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl assets::Asset for RawFont {
|
||||
const EXTENSION: &'static str = "ttf";
|
||||
type Loader = assets::LoadFrom<Vec<u8>, assets::BytesLoader>;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ pub mod component;
|
||||
mod renderer;
|
||||
pub mod widget;
|
||||
|
||||
pub use cache::{Font, FontId, RawFont};
|
||||
pub use cache::{Font, FontId, RawFont, load_font};
|
||||
pub use graphic::{Id, Rotation};
|
||||
pub use iced::Event;
|
||||
pub use iced_winit::conversion::window_event;
|
||||
|
@ -1,10 +1,8 @@
|
||||
use super::{Graphic, SampleStrat, Transform};
|
||||
use common::{
|
||||
assets::{Asset, Error},
|
||||
assets::{self, AssetExt, DotVoxAsset, Error},
|
||||
figure::Segment,
|
||||
};
|
||||
use dot_vox::DotVoxData;
|
||||
use image::DynamicImage;
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
@ -24,7 +22,8 @@ impl<'a> GraphicCreator<'a> for ImageGraphic {
|
||||
type Specifier = &'a str;
|
||||
|
||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||
Ok(Graphic::Image(DynamicImage::load(specifier)?, None))
|
||||
let image = assets::Image::load(specifier)?.read().to_image();
|
||||
Ok(Graphic::Image(image, None))
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,8 +36,8 @@ pub enum VoxelSs9Graphic {}
|
||||
pub enum VoxelPixArtGraphic {}
|
||||
|
||||
fn load_segment(specifier: &str) -> Result<Arc<Segment>, Error> {
|
||||
let dot_vox = DotVoxData::load(specifier)?;
|
||||
let seg = dot_vox.as_ref().into();
|
||||
let dot_vox = DotVoxAsset::load(specifier)?;
|
||||
let seg = Segment::from(&dot_vox.read().0);
|
||||
Ok(Arc::new(seg))
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ use conrod_core::{
|
||||
use core::{convert::TryInto, f32, f64, ops::Range};
|
||||
use graphic::TexId;
|
||||
use hashbrown::hash_map::Entry;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::time::Duration;
|
||||
use tracing::{error, warn};
|
||||
use vek::*;
|
||||
|
||||
@ -215,8 +215,8 @@ impl Ui {
|
||||
self.image_map.replace(id, (graphic_id, Rotation::None));
|
||||
}
|
||||
|
||||
pub fn new_font(&mut self, font: Arc<crate::ui::ice::RawFont>) -> font::Id {
|
||||
let font = text::Font::from_bytes(font.0.clone()).unwrap();
|
||||
pub fn new_font(&mut self, font: crate::ui::ice::RawFont) -> font::Id {
|
||||
let font = text::Font::from_bytes(font.0).unwrap();
|
||||
self.ui.fonts.insert(font)
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ const H: usize = 480;
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
fn main() {
|
||||
let seed = 1337;
|
||||
let (ref index, ref colors) = Index::new(seed);
|
||||
let (ref index, colors) = Index::new(seed);
|
||||
|
||||
let mut win =
|
||||
minifb::Window::new("Settlement Viewer", W, H, minifb::WindowOptions::default()).unwrap();
|
||||
@ -17,6 +17,7 @@ fn main() {
|
||||
|
||||
let mut focus = Vec2::<f32>::zero();
|
||||
let mut zoom = 1.0;
|
||||
let colors = &**colors.read();
|
||||
let index = IndexRef { colors, index };
|
||||
|
||||
while win.is_open() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{site::Site, Colors};
|
||||
use common::{
|
||||
assets::{watch::ReloadIndicator, Asset, Ron},
|
||||
assets::{AssetExt, AssetHandle},
|
||||
store::Store,
|
||||
};
|
||||
use core::ops::Deref;
|
||||
@ -14,7 +14,6 @@ pub struct Index {
|
||||
pub time: f32,
|
||||
pub noise: Noise,
|
||||
pub sites: Store<Site>,
|
||||
indicator: ReloadIndicator,
|
||||
}
|
||||
|
||||
/// An owned reference to indexed data.
|
||||
@ -26,6 +25,10 @@ pub struct Index {
|
||||
pub struct IndexOwned {
|
||||
colors: Arc<Colors>,
|
||||
index: Arc<Index>,
|
||||
|
||||
/// Stored separatly so `colors` is only updated when `reload_colors_if_changed`
|
||||
/// is called
|
||||
colors_handle: AssetHandle<Arc<Colors>>,
|
||||
}
|
||||
|
||||
impl Deref for IndexOwned {
|
||||
@ -51,10 +54,8 @@ impl<'a> Deref for IndexRef<'a> {
|
||||
|
||||
impl Index {
|
||||
/// NOTE: Panics if the color manifest cannot be loaded.
|
||||
pub fn new(seed: u32) -> (Self, Arc<Colors>) {
|
||||
let mut indicator = ReloadIndicator::new();
|
||||
let colors = Ron::<Colors>::load_watched(WORLD_COLORS_MANIFEST, &mut indicator)
|
||||
.expect("Could not load world colors!");
|
||||
pub fn new(seed: u32) -> (Self, AssetHandle<Arc<Colors>>) {
|
||||
let colors = Arc::<Colors>::load_expect(WORLD_COLORS_MANIFEST);
|
||||
|
||||
(
|
||||
Self {
|
||||
@ -62,7 +63,6 @@ impl Index {
|
||||
time: 0.0,
|
||||
noise: Noise::new(seed),
|
||||
sites: Store::default(),
|
||||
indicator,
|
||||
},
|
||||
colors,
|
||||
)
|
||||
@ -70,10 +70,11 @@ impl Index {
|
||||
}
|
||||
|
||||
impl IndexOwned {
|
||||
pub fn new(index: Index, colors: Arc<Colors>) -> Self {
|
||||
pub fn new(index: Index, colors: AssetHandle<Arc<Colors>>) -> Self {
|
||||
Self {
|
||||
index: Arc::new(index),
|
||||
colors,
|
||||
colors: colors.cloned(),
|
||||
colors_handle: colors,
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,9 +89,9 @@ impl IndexOwned {
|
||||
&mut self,
|
||||
reload: impl FnOnce(&mut Self) -> R,
|
||||
) -> Option<R> {
|
||||
self.indicator.reloaded().then(move || {
|
||||
// We know the asset was loaded before, so load_expect should be fine.
|
||||
self.colors = Ron::<Colors>::load_expect(WORLD_COLORS_MANIFEST);
|
||||
self.colors_handle.reloaded().then(move || {
|
||||
// Reload the color from the asse handle, which is updated automatically
|
||||
self.colors = self.colors_handle.cloned();
|
||||
reload(self)
|
||||
})
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
Canvas, IndexRef,
|
||||
};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
assets::AssetExt,
|
||||
comp,
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
lottery::Lottery,
|
||||
@ -182,8 +182,8 @@ pub fn apply_caves_to(canvas: &mut Canvas) {
|
||||
.chance(wpos2d.into(), 0.001 * difficulty.powf(1.5))
|
||||
&& cave_base < surface_z as i32 - 25
|
||||
{
|
||||
let kind = *Lottery::<SpriteKind>::load_expect("common.cave_scatter")
|
||||
.choose_seeded(RandomField::new(info.index().seed + 1).get(wpos2d.into()));
|
||||
let lottery = Lottery::<SpriteKind>::load_expect("common.cave_scatter").read();
|
||||
let kind = *lottery.choose_seeded(RandomField::new(info.index().seed + 1).get(wpos2d.into()));
|
||||
canvas.map(Vec3::new(wpos2d.x, wpos2d.y, cave_base), |block| {
|
||||
block.with_sprite(kind)
|
||||
});
|
||||
|
@ -6,27 +6,28 @@ use crate::{
|
||||
Canvas, CONFIG,
|
||||
};
|
||||
use common::{
|
||||
terrain::{structure::Structure, Block, BlockKind},
|
||||
assets::AssetHandle,
|
||||
terrain::{Structure, StructuresGroup, Block, BlockKind},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use lazy_static::lazy_static;
|
||||
use std::{f32, sync::Arc};
|
||||
use std::f32;
|
||||
use vek::*;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref OAKS: Vec<Arc<Structure>> = Structure::load_group("oaks");
|
||||
pub static ref OAK_STUMPS: Vec<Arc<Structure>> = Structure::load_group("oak_stumps");
|
||||
pub static ref PINES: Vec<Arc<Structure>> = Structure::load_group("pines");
|
||||
pub static ref PALMS: Vec<Arc<Structure>> = Structure::load_group("palms");
|
||||
pub static ref ACACIAS: Vec<Arc<Structure>> = Structure::load_group("acacias");
|
||||
pub static ref BAOBABS: Vec<Arc<Structure>> = Structure::load_group("baobabs");
|
||||
pub static ref FRUIT_TREES: Vec<Arc<Structure>> = Structure::load_group("fruit_trees");
|
||||
pub static ref BIRCHES: Vec<Arc<Structure>> = Structure::load_group("birch");
|
||||
pub static ref MANGROVE_TREES: Vec<Arc<Structure>> = Structure::load_group("mangrove_trees");
|
||||
pub static ref QUIRKY: Vec<Arc<Structure>> = Structure::load_group("quirky");
|
||||
pub static ref QUIRKY_DRY: Vec<Arc<Structure>> = Structure::load_group("quirky_dry");
|
||||
pub static ref SWAMP_TREES: Vec<Arc<Structure>> = Structure::load_group("swamp_trees");
|
||||
static ref OAKS: AssetHandle<StructuresGroup> = Structure::load_group("oaks");
|
||||
static ref OAK_STUMPS: AssetHandle<StructuresGroup> = Structure::load_group("oak_stumps");
|
||||
static ref PINES: AssetHandle<StructuresGroup> = Structure::load_group("pines");
|
||||
static ref PALMS: AssetHandle<StructuresGroup> = Structure::load_group("palms");
|
||||
static ref ACACIAS: AssetHandle<StructuresGroup> = Structure::load_group("acacias");
|
||||
static ref BAOBABS: AssetHandle<StructuresGroup> = Structure::load_group("baobabs");
|
||||
static ref FRUIT_TREES: AssetHandle<StructuresGroup> = Structure::load_group("fruit_trees");
|
||||
static ref BIRCHES: AssetHandle<StructuresGroup> = Structure::load_group("birch");
|
||||
static ref MANGROVE_TREES: AssetHandle<StructuresGroup> = Structure::load_group("mangrove_trees");
|
||||
static ref QUIRKY: AssetHandle<StructuresGroup> = Structure::load_group("quirky");
|
||||
static ref QUIRKY_DRY: AssetHandle<StructuresGroup> = Structure::load_group("quirky_dry");
|
||||
static ref SWAMP_TREES: AssetHandle<StructuresGroup> = Structure::load_group("swamp_trees");
|
||||
}
|
||||
|
||||
static MODEL_RAND: RandomPerm = RandomPerm::new(0xDB21C052);
|
||||
@ -37,7 +38,7 @@ static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F);
|
||||
pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
struct Tree {
|
||||
pos: Vec3<i32>,
|
||||
model: Arc<Structure>,
|
||||
model: Structure,
|
||||
seed: u32,
|
||||
units: (Vec2<i32>, Vec2<i32>),
|
||||
}
|
||||
@ -72,34 +73,34 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
Some(Tree {
|
||||
pos: Vec3::new(tree_wpos.x, tree_wpos.y, col.alt as i32),
|
||||
model: {
|
||||
let models: &'static [_] = if is_quirky {
|
||||
let models: AssetHandle<_> = if is_quirky {
|
||||
if col.temp > CONFIG.desert_temp {
|
||||
&QUIRKY_DRY
|
||||
*QUIRKY_DRY
|
||||
} else {
|
||||
&QUIRKY
|
||||
*QUIRKY
|
||||
}
|
||||
} else {
|
||||
match col.forest_kind {
|
||||
ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => {
|
||||
&OAK_STUMPS
|
||||
*OAK_STUMPS
|
||||
},
|
||||
ForestKind::Oak if QUIRKY_RAND.chance(seed + 2, 1.0 / 20.0) => {
|
||||
&FRUIT_TREES
|
||||
*FRUIT_TREES
|
||||
},
|
||||
ForestKind::Palm => &PALMS,
|
||||
ForestKind::Acacia => &ACACIAS,
|
||||
ForestKind::Baobab => &BAOBABS,
|
||||
ForestKind::Oak => &OAKS,
|
||||
ForestKind::Pine => &PINES,
|
||||
ForestKind::Birch => &BIRCHES,
|
||||
ForestKind::Mangrove => &MANGROVE_TREES,
|
||||
ForestKind::Swamp => &SWAMP_TREES,
|
||||
ForestKind::Palm => *PALMS,
|
||||
ForestKind::Acacia => *ACACIAS,
|
||||
ForestKind::Baobab => *BAOBABS,
|
||||
ForestKind::Oak => *OAKS,
|
||||
ForestKind::Pine => *PINES,
|
||||
ForestKind::Birch => *BIRCHES,
|
||||
ForestKind::Mangrove => *MANGROVE_TREES,
|
||||
ForestKind::Swamp => *SWAMP_TREES,
|
||||
}
|
||||
};
|
||||
Arc::clone(
|
||||
&models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize
|
||||
% models.len()],
|
||||
)
|
||||
|
||||
let models = models.read();
|
||||
models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize % models.len()]
|
||||
.clone()
|
||||
},
|
||||
seed,
|
||||
units: UNIT_CHOOSER.get(seed),
|
||||
|
@ -41,6 +41,7 @@ use crate::{
|
||||
util::{Grid, Sampler},
|
||||
};
|
||||
use common::{
|
||||
assets,
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||
vol::{ReadVol, RectVolSize, WriteVol},
|
||||
@ -70,6 +71,11 @@ pub struct Colors {
|
||||
pub site: site::Colors,
|
||||
}
|
||||
|
||||
impl assets::Asset for Colors {
|
||||
const EXTENSION: &'static str = "ron";
|
||||
type Loader = assets::RonLoader;
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn generate(seed: u32, opts: sim::WorldOpts) -> (Self, IndexOwned) {
|
||||
// NOTE: Generating index first in order to quickly fail if the color manifest
|
||||
|
@ -35,7 +35,7 @@ use crate::{
|
||||
IndexRef, CONFIG,
|
||||
};
|
||||
use common::{
|
||||
assets,
|
||||
assets::{self, AssetExt},
|
||||
grid::Grid,
|
||||
store::Id,
|
||||
terrain::{
|
||||
@ -220,7 +220,7 @@ pub enum WorldFileError {
|
||||
/// invalidation or make sure that the map is synchronized with updates to
|
||||
/// noise-rs, changes to other parameters, etc.
|
||||
///
|
||||
/// The map is verisoned to enable format detection between versions of Veloren,
|
||||
/// The map is versioned to enable format detection between versions of Veloren,
|
||||
/// so that when we update the map format we don't break existing maps (or at
|
||||
/// least, we will try hard not to break maps between versions; if we can't
|
||||
/// avoid it, we can at least give a reasonable error message).
|
||||
@ -245,6 +245,11 @@ pub enum WorldFile {
|
||||
Veloren0_7_0(WorldMap_0_7_0) = 1,
|
||||
}
|
||||
|
||||
impl assets::Asset for WorldFile {
|
||||
const EXTENSION: &'static str = "bin";
|
||||
type Loader = assets::BincodeLoader;
|
||||
}
|
||||
|
||||
/// Data for the most recent map type. Update this when you add a new map
|
||||
/// version.
|
||||
pub type ModernMap = WorldMap_0_7_0;
|
||||
@ -409,26 +414,21 @@ impl WorldSim {
|
||||
map.into_modern()
|
||||
},
|
||||
FileOpts::LoadAsset(ref specifier) => {
|
||||
let reader = match assets::load_file(specifier, &["bin"]) {
|
||||
Ok(reader) => reader,
|
||||
Err(e) => {
|
||||
warn!(?e, ?specifier, "Couldn't read asset specifier for maps",);
|
||||
match WorldFile::load_owned(&specifier) {
|
||||
Ok(map) => map.into_modern(),
|
||||
Err(err) => {
|
||||
match err {
|
||||
assets::Error::Io(e) => {
|
||||
warn!(?e, ?specifier, "Couldn't read asset specifier for maps");
|
||||
}
|
||||
assets::Error::Conversion(e) => {
|
||||
warn!(?e, "Couldn't parse modern map. Maybe you meant to try a legacy load?");
|
||||
}
|
||||
assets::Error::NoDefaultValue => unreachable!(),
|
||||
}
|
||||
return None;
|
||||
},
|
||||
};
|
||||
|
||||
let map: WorldFile = match bincode::deserialize_from(reader) {
|
||||
Ok(map) => map,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
?e,
|
||||
"Couldn't parse modern map. Maybe you meant to try a legacy load?"
|
||||
);
|
||||
return None;
|
||||
},
|
||||
};
|
||||
|
||||
map.into_modern()
|
||||
}
|
||||
}
|
||||
},
|
||||
FileOpts::Generate | FileOpts::Save => return None,
|
||||
};
|
||||
|
@ -8,13 +8,13 @@ use crate::{
|
||||
IndexRef,
|
||||
};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
assets::{AssetExt, AssetHandle},
|
||||
astar::Astar,
|
||||
comp::{self},
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
lottery::Lottery,
|
||||
store::{Id, Store},
|
||||
terrain::{Block, BlockKind, SpriteKind, Structure, TerrainChunkSize},
|
||||
terrain::{Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol},
|
||||
};
|
||||
use core::{f32, hash::BuildHasherDefault};
|
||||
@ -22,7 +22,6 @@ use fxhash::FxHasher64;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
pub struct Dungeon {
|
||||
@ -111,11 +110,12 @@ impl Dungeon {
|
||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
) {
|
||||
lazy_static! {
|
||||
pub static ref ENTRANCES: Vec<Arc<Structure>> =
|
||||
pub static ref ENTRANCES: AssetHandle<StructuresGroup> =
|
||||
Structure::load_group("dungeon_entrances");
|
||||
}
|
||||
|
||||
let entrance = &ENTRANCES[self.seed as usize % ENTRANCES.len()];
|
||||
let entrances = ENTRANCES.read();
|
||||
let entrance = &entrances[self.seed as usize % entrances.len()];
|
||||
|
||||
for y in 0..vol.size_xy().y as i32 {
|
||||
for x in 0..vol.size_xy().x as i32 {
|
||||
@ -581,6 +581,7 @@ impl Floor {
|
||||
"common.loot_tables.loot_table_armor_misc",
|
||||
),
|
||||
};
|
||||
let chosen = chosen.read();
|
||||
let chosen = chosen.choose();
|
||||
//let is_giant =
|
||||
// RandomField::new(room.seed.wrapping_add(1)).chance(Vec3::from(tile_pos),
|
||||
@ -741,6 +742,7 @@ impl Floor {
|
||||
"common.loot_tables.loot_table_armor_misc",
|
||||
),
|
||||
};
|
||||
let chosen = chosen.read();
|
||||
let chosen = chosen.choose();
|
||||
let entity = match room.difficulty {
|
||||
0 => vec![
|
||||
@ -923,6 +925,7 @@ impl Floor {
|
||||
"common.loot_tables.loot_table_armor_misc",
|
||||
),
|
||||
};
|
||||
let chosen = chosen.read();
|
||||
let chosen = chosen.choose();
|
||||
let entity = match room.difficulty {
|
||||
0 => vec![
|
||||
|
Loading…
Reference in New Issue
Block a user