Merge branch 'zesterer/small-fixes' into 'master'

Actually small fixes this time

See merge request 
This commit is contained in:
Joshua Barretto 2020-12-06 13:31:04 +00:00
commit 38f9bc15ae
21 changed files with 182 additions and 76 deletions
assets
common
voxygen
shaders/include/cloud
voxel
chat-cli/src
client/src
common/src
voxygen/src
world/src

View File

@ -1,10 +1,10 @@
[
(10, Velorite),
(15, VeloriteFrag),
(110, Stones),
(110, Stones),
(150, ShortGrass),
(60, Mushroom),
(120, CaveMushroom),
(2, ShinyGem),
(2, Chest),
(15, Crate),
(15, Crate),
]

View File

@ -44,6 +44,8 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission) {
float turb_noise = 0.0;
float sun_access = 0.0;
float moon_access = 0.0;
float cloud_sun_access = 0.0;
float cloud_moon_access = 0.0;
// This is a silly optimisation but it actually nets us a fair few fps by skipping quite a few expensive calcs
if (cloud_tendency > 0 || mist > 0.0) {
// Turbulence (small variations in clouds/mist)
@ -66,9 +68,9 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission) {
cloud = cloud_flat * pow(cloud_factor, 2) * 20;
// What proportion of sunlight is *not* being blocked by nearby cloud? (approximation)
sun_access = clamp((pos.z - cloud_attr.x + turb_noise * 250.0) * 0.002 + 0.35 + max(mist * 20000, 0), 0, 1);
cloud_sun_access = clamp((pos.z - cloud_attr.x + turb_noise * 250.0) * 0.002 + 0.35, 0, 1);
// Since we're assuming the sun/moon is always above (not always correct) it's the same for the moon
moon_access = sun_access;
cloud_moon_access = sun_access;
#if (CLOUD_MODE >= CLOUD_MODE_HIGH)
// Try to calculate a reasonable approximation of the cloud normal
@ -79,11 +81,16 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission) {
(cloud_tendency - cloud_tendency_y) * 4,
(pos.z - cloud_attr.x) / 250 + turb_noise + 0.25
);
sun_access = mix(max(dot(-sun_dir.xyz, cloud_norm) + 0.0, 0.025), sun_access, 0.25);
moon_access = mix(max(dot(-moon_dir.xyz, cloud_norm) + 0.35, 0.025), moon_access, 0.25);
cloud_sun_access = mix(max(dot(-sun_dir.xyz, cloud_norm) + 0.0, 0.025), cloud_sun_access, 0.25);
cloud_moon_access = mix(max(dot(-moon_dir.xyz, cloud_norm) + 0.35, 0.025), cloud_moon_access, 0.25);
#endif
}
float mist_sun_access = 0.5 + turb_noise * 0.5;
float mist_moon_access = mist_sun_access;
sun_access = mix(cloud_sun_access, mist_sun_access, clamp(mist * 20000, 0, 1));
moon_access = mix(cloud_moon_access, mist_moon_access, clamp(mist * 20000, 0, 1));
// Prevent mist (i.e: vapour beneath clouds) being accessible to the sun to avoid visual problems
//float suppress_mist = clamp((pos.z - cloud_attr.x + cloud_attr.y) / 300, 0, 1);
//sun_access *= suppress_mist;
@ -102,6 +109,9 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission) {
#if (CLOUD_MODE >= CLOUD_MODE_LOW)
emission_alt += (noise_3d(vec3(wind_pos.xy * 0.00003 + cloud_tendency * 0.2, time_of_day.x * 0.0001)) - 0.5) * 6000;
#endif
#if (CLOUD_MODE >= CLOUD_MODE_HIGH)
emission_alt += (noise_3d(vec3(wind_pos.xy * 0.0005 + cloud_tendency * 0.2, emission_alt * 0.0001 + time_of_day.x * 0.0005)) - 0.5) * 1000;
#endif
float tail = (texture(t_noise, wind_pos.xy * 0.00005).x - 0.5) * 10 + (z - emission_alt) * 0.001;
vec3 emission_col = vec3(0.6 + tail * 0.6, 1.0, 0.3 + tail * 0.2);
float emission_nz = max(texture(t_noise, wind_pos.xy * 0.00003).x - 0.6, 0) / (10.0 + abs(z - emission_alt) / 40);

View File

@ -519,6 +519,13 @@ Mushroom: Some((
offset: (-6.0, -6.0, 0.0),
lod_axes: (1.0, 1.0, 1.0),
),
],
wind_sway: 0.1,
)),
// Cave Mushrooms
CaveMushroom: Some((
variations: [
(
model: "voxygen.voxel.sprite.mushrooms.mushroom-11",
offset: (-8.0, -8.0, 0.0),
@ -2028,7 +2035,7 @@ GrassBlue: Some((
model: "voxygen.voxel.sprite.underwater_grass.blue-1",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.5),
),
),
],
wind_sway: 1.0,
)),
@ -2044,7 +2051,7 @@ ChestBurried: Some((
model: "voxygen.voxel.sprite.underwater_chests.chest_vines",
offset: (-10.0, -8.5, 0.0),
lod_axes: (0.0, 0.0, 0.5),
),
),
],
wind_sway: 0.0,
)),
@ -2060,7 +2067,7 @@ Mud: Some((
model: "voxygen.voxel.sprite.underwater_mud.mud-1",
offset: (-8.5, -7.5, 0.0),
lod_axes: (0.0, 0.0, 0.5),
),
),
],
wind_sway: 0.0,
)),

View File

@ -48,7 +48,7 @@ fn main() {
)
.expect("Failed to create client instance");
println!("Server info: {:?}", client.server_info);
println!("Server info: {:?}", client.server_info());
println!("Players online: {:?}", client.get_players());

View File

@ -24,6 +24,7 @@ use common::{
InventoryManip, InventoryUpdateEvent,
},
event::{EventBus, LocalEvent},
grid::Grid,
msg::{
validate_chat_msg, world_msg::SiteInfo, ChatMsgValidationError, ClientGeneral, ClientMsg,
ClientRegister, ClientType, DisconnectReason, InviteAnswer, Notification, PingMsg,
@ -70,25 +71,22 @@ pub enum Event {
SetViewDistance(u32),
Outcome(Outcome),
CharacterCreated(CharacterId),
CharacterError(String),
}
pub struct Client {
registered: bool,
presence: Option<PresenceKind>,
thread_pool: ThreadPool,
pub server_info: ServerInfo,
pub struct WorldData {
/// Just the "base" layer for LOD; currently includes colors and nothing
/// else. In the future we'll add more layers, like shadows, rivers, and
/// probably foliage, cities, roads, and other structures.
pub lod_base: Vec<u32>,
pub lod_base: Grid<u32>,
/// The "height" layer for LOD; currently includes only land altitudes, but
/// in the future should also water depth, and probably other
/// information as well.
pub lod_alt: Vec<u32>,
pub lod_alt: Grid<u32>,
/// The "shadow" layer for LOD. Includes east and west horizon angles and
/// an approximate max occluder height, which we use to try to
/// approximate soft and volumetric shadows.
pub lod_horizon: Vec<u32>,
pub lod_horizon: Grid<u32>,
/// A fully rendered map image for use with the map and minimap; note that
/// this can be constructed dynamically by combining the layers of world
/// map data (e.g. with shadow map data or river data), but at present
@ -98,9 +96,27 @@ pub struct Client {
/// in chunks), and the third element holds the minimum height for any land
/// chunk (i.e. the sea level) in its x coordinate, and the maximum land
/// height above this height (i.e. the max height) in its y coordinate.
pub world_map: (Arc<DynamicImage>, Vec2<u16>, Vec2<f32>),
pub player_list: HashMap<Uid, PlayerInfo>,
pub character_list: CharacterList,
map: (Arc<DynamicImage>, Vec2<u16>, Vec2<f32>),
}
impl WorldData {
pub fn chunk_size(&self) -> Vec2<u16> { self.map.1 }
pub fn map_image(&self) -> &Arc<DynamicImage> { &self.map.0 }
pub fn min_chunk_alt(&self) -> f32 { self.map.2.x }
pub fn max_chunk_alt(&self) -> f32 { self.map.2.y }
}
pub struct Client {
registered: bool,
presence: Option<PresenceKind>,
thread_pool: ThreadPool,
server_info: ServerInfo,
world_data: WorldData,
player_list: HashMap<Uid, PlayerInfo>,
character_list: CharacterList,
sites: Vec<SiteInfo>,
recipe_book: RecipeBook,
available_recipes: HashSet<String>,
@ -145,7 +161,6 @@ pub struct Client {
pub struct CharacterList {
pub characters: Vec<CharacterItem>,
pub loading: bool,
pub error: Option<String>,
}
impl Client {
@ -229,11 +244,10 @@ impl Client {
let sea_level = world_map.sea_level;
let rgba = world_map.rgba;
let alt = world_map.alt;
let expected_size = (u32::from(map_size.x) * u32::from(map_size.y)) as usize;
if rgba.len() != expected_size {
if rgba.size() != map_size.map(|e| e as i32) {
return Err(Error::Other("Server sent a bad world map image".into()));
}
if alt.len() != expected_size {
if alt.size() != map_size.map(|e| e as i32) {
return Err(Error::Other("Server sent a bad altitude map.".into()));
}
let [west, east] = world_map.horizons;
@ -257,7 +271,7 @@ impl Client {
let horizons = [unzip_horizons(&west), unzip_horizons(&east)];
// Redraw map (with shadows this time).
let mut world_map_rgba = vec![0u32; rgba.len()];
let mut world_map_rgba = vec![0u32; rgba.size().product() as usize];
let mut map_config = common::terrain::map::MapConfig::orthographic(
map_size_lg,
core::ops::RangeInclusive::new(0.0, max_height),
@ -274,14 +288,14 @@ impl Client {
|pos| {
let (rgba, alt, downhill_wpos) = if bounds_check(pos) {
let posi = pos.y as usize * map_size.x as usize + pos.x as usize;
let [r, g, b, a] = rgba[posi].to_le_bytes();
let alti = alt[posi];
let [r, g, b, a] = rgba[pos].to_le_bytes();
let alti = alt[pos];
// Compute downhill.
let downhill = {
let mut best = -1;
let mut besth = alti;
for nposi in neighbors(map_size_lg, posi) {
let nbh = alt[nposi];
let nbh = alt.raw()[nposi];
if nbh < besth {
besth = nbh;
best = nposi as isize;
@ -317,8 +331,7 @@ impl Client {
|wpos| {
let pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32);
rescale_height(if bounds_check(pos) {
let posi = pos.y as usize * map_size.x as usize + pos.x as usize;
scale_height_big(alt[posi])
scale_height_big(alt[pos])
} else {
0.0
})
@ -361,7 +374,7 @@ impl Client {
entity,
lod_base,
lod_alt,
lod_horizon,
Grid::from_raw(map_size.map(|e| e as i32), lod_horizon),
(world_map_img, map_size, map_bounds),
world_map.sites,
recipe_book,
@ -386,10 +399,12 @@ impl Client {
presence: None,
thread_pool,
server_info,
world_map,
lod_base,
lod_alt,
lod_horizon,
world_data: WorldData {
lod_base,
lod_alt,
lod_horizon,
map: world_map,
},
player_list: HashMap::new(),
character_list: CharacterList::default(),
sites,
@ -612,6 +627,14 @@ impl Client {
}
}
pub fn player_list(&self) -> &HashMap<Uid, PlayerInfo> { &self.player_list }
pub fn character_list(&self) -> &CharacterList { &self.character_list }
pub fn server_info(&self) -> &ServerInfo { &self.server_info }
pub fn world_data(&self) -> &WorldData { &self.world_data }
pub fn recipe_book(&self) -> &RecipeBook { &self.recipe_book }
pub fn available_recipes(&self) -> &HashSet<String> { &self.available_recipes }
@ -1481,13 +1504,13 @@ impl Client {
},
ServerGeneral::CharacterActionError(error) => {
warn!("CharacterActionError: {:?}.", error);
self.character_list.error = Some(error);
events.push(Event::CharacterError(error));
},
ServerGeneral::CharacterDataLoadError(error) => {
trace!("Handling join error by server");
self.presence = None;
self.clean_state();
self.character_list.error = Some(error);
events.push(Event::CharacterError(error));
},
ServerGeneral::CharacterCreated(character_id) => {
events.push(Event::CharacterCreated(character_id));

View File

@ -1,11 +1,20 @@
use serde::{Deserialize, Serialize};
use std::ops::{Index, IndexMut};
use vek::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Grid<T> {
cells: Vec<T>,
size: Vec2<i32>,
size: Vec2<i32>, // TODO: use u32
}
impl<T> Grid<T> {
pub fn from_raw(size: Vec2<i32>, raw: impl Into<Vec<T>>) -> Self {
let cells = raw.into();
assert_eq!(size.product() as usize, cells.len());
Self { cells, size }
}
pub fn populate_from(size: Vec2<i32>, mut f: impl FnMut(Vec2<i32>) -> T) -> Self {
Self {
cells: (0..size.y)
@ -81,4 +90,32 @@ impl<T> Grid<T> {
})
.flatten()
}
pub fn raw(&self) -> &[T] { &self.cells }
}
impl<T> Index<Vec2<i32>> for Grid<T> {
type Output = T;
fn index(&self, index: Vec2<i32>) -> &Self::Output {
self.get(index).unwrap_or_else(|| {
panic!(
"Attempted to index grid of size {:?} with index {:?}",
self.size(),
index
)
})
}
}
impl<T> IndexMut<Vec2<i32>> for Grid<T> {
fn index_mut(&mut self, index: Vec2<i32>) -> &mut Self::Output {
let size = self.size();
self.get_mut(index).unwrap_or_else(|| {
panic!(
"Attempted to index grid of size {:?} with index {:?}",
size, index
)
})
}
}

View File

@ -30,6 +30,7 @@ pub mod event;
pub mod explosion;
pub mod figure;
pub mod generation;
pub mod grid;
pub mod loadout_builder;
pub mod lottery;
pub mod metrics;

View File

@ -1,3 +1,4 @@
use crate::grid::Grid;
use serde::{Deserialize, Serialize};
use vek::*;
@ -30,12 +31,12 @@ pub struct WorldMapMsg {
pub max_height: f32,
/// RGB+A; the alpha channel is currently unused, but will be used in the
/// future. Entries are in the usual chunk order.
pub rgba: Vec<u32>,
pub rgba: Grid<u32>,
/// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for
/// altitude. The remainder are currently unused, but we have plans to
/// use 7 bits for water depth (using an integer f7 encoding), and we
/// will find other uses for the remaining 12 bits.
pub alt: Vec<u32>,
pub alt: Grid<u32>,
/// Horizon mapping. This is a variant of shadow mapping that is
/// specifically designed for height maps; it takes advantage of their
/// regular structure (e.g. no holes) to compress all information needed

View File

@ -173,6 +173,7 @@ impl Block {
SpriteKind::WallLamp => Some(16),
SpriteKind::FireBowlGround => Some(16),
SpriteKind::Velorite | SpriteKind::VeloriteFrag => Some(6),
SpriteKind::CaveMushroom => Some(12),
_ => None,
}
}

View File

@ -108,6 +108,7 @@ make_case_elim!(
ChestBurried = 0x52,
Mud = 0x53,
FireBowlGround = 0x54,
CaveMushroom = 0x55,
}
);

View File

@ -178,7 +178,7 @@ impl<'a> Widget for Group<'a> {
}
// Helper
let uid_to_name_text = |uid, client: &Client| match client.player_list.get(&uid) {
let uid_to_name_text = |uid, client: &Client| match client.player_list().get(&uid) {
Some(player_info) => player_info
.character
.as_ref()

View File

@ -648,10 +648,10 @@ impl Hud {
// Load world map
let world_map = (
ui.add_graphic_with_rotations(Graphic::Image(
Arc::clone(&client.world_map.0),
Arc::clone(client.world_data().map_image()),
Some(water_color),
)),
client.world_map.1.map(u32::from),
client.world_data().chunk_size().map(|e| e as u32),
);
// Load images.
let imgs = Imgs::load(&mut ui).expect("Failed to load images!");
@ -666,7 +666,7 @@ impl Hud {
// Load fonts.
let fonts = Fonts::load(&i18n.fonts, &mut ui).expect("Impossible to load fonts!");
// Get the server name.
let server = &client.server_info.name;
let server = &client.server_info().name;
// Get the id, unwrap is safe because this CANNOT be None at this
// point.

View File

@ -266,7 +266,11 @@ impl<'a> Widget for Social<'a> {
}
// Online Tab
if let SocialTab::Online = self.show.social_tab {
let players = self.client.player_list.iter().filter(|(_, p)| p.is_online);
let players = self
.client
.player_list()
.iter()
.filter(|(_, p)| p.is_online);
let count = players.clone().count();
let height = if count > 1 {
count as f64 - 1.0 + 20.0 * count as f64 - 1.0
@ -517,12 +521,12 @@ impl<'a> Widget for Social<'a> {
.as_ref()
.map(|(s, _)| *s)
.filter(|selected| {
self.client
.player_list
.get(selected)
.map_or(false, |selected_player| {
self.client.player_list().get(selected).map_or(
false,
|selected_player| {
selected_player.is_online && selected_player.character.is_some()
})
},
)
})
.or_else(|| {
self.selected_entity

View File

@ -44,7 +44,7 @@ impl CharSelectionState {
client: &'a Client,
) -> (Option<comp::humanoid::Body>, Option<&'a comp::Loadout>) {
char_selection_ui
.display_body_loadout(&client.character_list.characters)
.display_body_loadout(&client.character_list().characters)
.map(|(body, loadout)| {
(
match body {
@ -124,11 +124,11 @@ impl PlayState for CharSelectionState {
)));
},
ui::Event::ClearCharacterListError => {
self.client.borrow_mut().character_list.error = None;
self.char_selection_ui.error = None;
},
ui::Event::SelectCharacter(selected) => {
let client = self.client.borrow();
let server_name = &client.server_info.name;
let server_name = &client.server_info().name;
// Select newly created character
global_state
.profile
@ -194,6 +194,9 @@ impl PlayState for CharSelectionState {
client::Event::CharacterCreated(character_id) => {
self.char_selection_ui.select_character(character_id);
},
client::Event::CharacterError(error) => {
self.char_selection_ui.display_error(error);
},
_ => {},
}
}

View File

@ -287,7 +287,12 @@ impl Controls {
}
}
fn view(&mut self, _settings: &Settings, client: &Client) -> Element<Message> {
fn view(
&mut self,
_settings: &Settings,
client: &Client,
error: &Option<String>,
) -> Element<Message> {
// TODO: use font scale thing for text size (use on button size for buttons with
// text)
@ -348,7 +353,7 @@ impl Controls {
// Note: we don't need to persist this because it is the default
if self.selected.is_none() {
self.selected = client
.character_list
.character_list()
.characters
.get(0)
.and_then(|i| i.character.id);
@ -356,13 +361,13 @@ impl Controls {
// Get the index of the selected character
let selected = self.selected.and_then(|id| {
client
.character_list
.character_list()
.characters
.iter()
.position(|i| i.character.id == Some(id))
});
if let Some(error) = &client.character_list.error {
if let Some(error) = error {
// TODO: use more user friendly errors with suggestions on potential solutions
// instead of directly showing error message here
*info_content = Some(InfoContent::CharacterError(format!(
@ -377,14 +382,14 @@ impl Controls {
Some(InfoContent::LoadingCharacters)
| Some(InfoContent::CreatingCharacter)
| Some(InfoContent::DeletingCharacter)
) && !client.character_list.loading
) && !client.character_list().loading
{
*info_content = None;
}
let server = Container::new(
Column::with_children(vec![
Text::new(&client.server_info.name)
Text::new(&client.server_info().name)
.size(fonts.cyri.scale(25))
.into(),
// TODO: show additional server info here
@ -399,7 +404,7 @@ impl Controls {
.width(Length::Fill);
let characters = {
let characters = &client.character_list.characters;
let characters = &client.character_list().characters;
let num = characters.len();
// Ensure we have enough button states
character_buttons.resize_with(num * 2, Default::default);
@ -1387,12 +1392,13 @@ pub struct CharSelectionUi {
controls: Controls,
enter_pressed: bool,
select_character: Option<CharacterId>,
pub error: Option<String>,
}
impl CharSelectionUi {
pub fn new(global_state: &mut GlobalState, client: &Client) -> Self {
// Load up the last selected character for this server
let server_name = &client.server_info.name;
let server_name = &client.server_info().name;
let selected_character = global_state.profile.get_selected_character(server_name);
// Load language
@ -1432,6 +1438,7 @@ impl CharSelectionUi {
controls,
enter_pressed: false,
select_character: None,
error: None,
}
}
@ -1491,12 +1498,15 @@ impl CharSelectionUi {
pub fn select_character(&mut self, id: CharacterId) { self.select_character = Some(id); }
pub fn display_error(&mut self, error: String) { self.error = Some(error); }
// 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 (mut messages, _) = self.ui.maintain(
self.controls.view(&global_state.settings, &client),
self.controls
.view(&global_state.settings, &client, &self.error),
global_state.window.renderer_mut(),
global_state.clipboard.as_ref(),
);
@ -1514,7 +1524,7 @@ impl CharSelectionUi {
self.controls.update(
message,
&mut events,
&client.character_list.characters,
&client.character_list().characters,
&*client.state().ability_map(),
)
});

View File

@ -28,10 +28,10 @@ impl Lod {
locals: renderer.create_consts(&[Locals::default()]).unwrap(),
data: LodData::new(
renderer,
client.world_map.1,
&client.lod_base,
&client.lod_alt,
&client.lod_horizon,
client.world_data().chunk_size(),
client.world_data().lod_base.raw(),
client.world_data().lod_alt.raw(),
client.world_data().lod_horizon.raw(),
settings.graphics.lod_detail.max(100).min(2500),
water_color().into_array().into(),
),

View File

@ -304,7 +304,10 @@ impl Scene {
terrain: Terrain::new(renderer),
lod: Lod::new(renderer, client, settings),
loaded_distance: 0.0,
map_bounds: client.world_map.2,
map_bounds: Vec2::new(
client.world_data().min_chunk_alt(),
client.world_data().max_chunk_alt(),
),
select_pos: None,
light_data: Vec::new(),
particle_mgr: ParticleMgr::new(renderer),

View File

@ -106,7 +106,10 @@ impl Scene {
let start_angle = 90.0f32.to_radians();
let resolution = renderer.get_resolution().map(|e| e as f32);
let map_bounds = client.world_map.2;
let map_bounds = Vec2::new(
client.world_data().min_chunk_alt(),
client.world_data().max_chunk_alt(),
);
let map_border = [0.0, 0.0, 0.0, 0.0];
let map_image = [0];
let alt_image = [0];

View File

@ -183,6 +183,7 @@ impl SessionState {
},
client::Event::Outcome(outcome) => outcomes.push(outcome),
client::Event::CharacterCreated(_) => {},
client::Event::CharacterError(_) => {},
}
}
@ -931,7 +932,7 @@ impl PlayState for SessionState {
HudEvent::ChangeHotbarState(state) => {
let client = self.client.borrow();
let server_name = &client.server_info.name;
let server_name = &client.server_info().name;
// If we are changing the hotbar state this CANNOT be None.
let character_id = match client.presence().unwrap() {
PresenceKind::Character(id) => id,

View File

@ -36,6 +36,7 @@ use crate::{
};
use common::{
assets,
grid::Grid,
msg::WorldMapMsg,
store::Id,
terrain::{
@ -1498,8 +1499,8 @@ impl WorldSim {
dimensions_lg: self.map_size_lg().vec(),
sea_level: CONFIG.sea_level,
max_height: self.max_height,
rgba: v,
alt: alts,
rgba: Grid::from_raw(self.get_size().map(|e| e as i32), v),
alt: Grid::from_raw(self.get_size().map(|e| e as i32), alts),
horizons,
sites: Vec::new(), // Will be substituted later
}

View File

@ -1,5 +1,4 @@
pub mod fast_noise;
pub mod grid;
pub mod map_vec;
pub mod random;
pub mod sampler;
@ -11,7 +10,6 @@ pub mod unit_chooser;
// Reexports
pub use self::{
fast_noise::FastNoise,
grid::Grid,
map_vec::MapVec,
random::{RandomField, RandomPerm},
sampler::{Sampler, SamplerMut},
@ -20,6 +18,8 @@ pub use self::{
unit_chooser::UnitChooser,
};
pub use common::grid::Grid;
use fxhash::FxHasher32;
use hashbrown::{HashMap, HashSet};
use std::hash::BuildHasherDefault;