mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Load missing plugins from the server
individual commits combined here: send active plugins compute plugin sha hash single position for defining Hash type request plugins from the server Server sending the plugin to the client store received plugin in file and use it handle plugin data at the right place pass config_dir to client init load local plugins operational plugin caching simplify the interface clippy suggestions remove artifacts fix compilation of test world ChangeLog entry code quality fixes improve readability adapt to multiple systems
This commit is contained in:
parent
030c2f5219
commit
3e0ca7d6d4
@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Sand and crystal cave biome
|
- Sand and crystal cave biome
|
||||||
- In commands that reference assets you can now use `#name` and press tab to cycle through assets with that name.
|
- In commands that reference assets you can now use `#name` and press tab to cycle through assets with that name.
|
||||||
- Allow moving and resizing the chat with left and right mouse button respectively
|
- Allow moving and resizing the chat with left and right mouse button respectively
|
||||||
|
- Missing plugins are requested from the server and cached locally
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -7114,10 +7114,12 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures",
|
"futures",
|
||||||
"hashbrown 0.13.2",
|
"hashbrown 0.13.2",
|
||||||
|
"hex",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"rayon",
|
"rayon",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sha2",
|
||||||
"specs",
|
"specs",
|
||||||
"tar",
|
"tar",
|
||||||
"timer-queue",
|
"timer-queue",
|
||||||
@ -7364,6 +7366,7 @@ dependencies = [
|
|||||||
"rodio",
|
"rodio",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sha2",
|
||||||
"shaderc",
|
"shaderc",
|
||||||
"slab",
|
"slab",
|
||||||
"specs",
|
"specs",
|
||||||
|
@ -154,6 +154,8 @@ rayon = { version = "1.5" }
|
|||||||
|
|
||||||
clap = { version = "4.2", features = ["derive"]}
|
clap = { version = "4.2", features = ["derive"]}
|
||||||
async-trait = "0.1.42"
|
async-trait = "0.1.42"
|
||||||
|
sha2 = "0.10"
|
||||||
|
hex = "0.4.3"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
shred = { git = "https://github.com/amethyst/shred.git", rev = "5d52c6fc390dd04c12158633e77591f6523d1f85" }
|
shred = { git = "https://github.com/amethyst/shred.git", rev = "5d52c6fc390dd04c12158633e77591f6523d1f85" }
|
||||||
|
@ -67,6 +67,7 @@ fn main() {
|
|||||||
|provider| provider == "https://auth.veloren.net",
|
|provider| provider == "https://auth.veloren.net",
|
||||||
&|_| {},
|
&|_| {},
|
||||||
|_| {},
|
|_| {},
|
||||||
|
Default::default(),
|
||||||
))
|
))
|
||||||
.expect("Failed to create client instance");
|
.expect("Failed to create client instance");
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ pub fn make_client(
|
|||||||
|_| true,
|
|_| true,
|
||||||
&|_| {},
|
&|_| {},
|
||||||
|_| {},
|
|_| {},
|
||||||
|
Default::default(),
|
||||||
))
|
))
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ use common::{
|
|||||||
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
||||||
MapMarkerChange, PresenceKind, UtteranceKind,
|
MapMarkerChange, PresenceKind, UtteranceKind,
|
||||||
},
|
},
|
||||||
event::{EventBus, LocalEvent, UpdateCharacterMetadata},
|
event::{EventBus, LocalEvent, PluginHash, UpdateCharacterMetadata},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
link::Is,
|
link::Is,
|
||||||
lod,
|
lod,
|
||||||
@ -64,6 +64,8 @@ use common_net::{
|
|||||||
},
|
},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
use common_state::plugin::PluginMgr;
|
||||||
use common_state::State;
|
use common_state::State;
|
||||||
use common_systems::add_local_systems;
|
use common_systems::add_local_systems;
|
||||||
use comp::BuffKind;
|
use comp::BuffKind;
|
||||||
@ -82,6 +84,7 @@ use std::{
|
|||||||
collections::{BTreeMap, VecDeque},
|
collections::{BTreeMap, VecDeque},
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
mem,
|
mem,
|
||||||
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
@ -120,6 +123,7 @@ pub enum Event {
|
|||||||
MapMarker(comp::MapMarkerUpdate),
|
MapMarker(comp::MapMarkerUpdate),
|
||||||
StartSpectate(Vec3<f32>),
|
StartSpectate(Vec3<f32>),
|
||||||
SpectatePosition(Vec3<f32>),
|
SpectatePosition(Vec3<f32>),
|
||||||
|
PluginDataReceived(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -326,6 +330,10 @@ pub struct Client {
|
|||||||
dt_adjustment: f64,
|
dt_adjustment: f64,
|
||||||
|
|
||||||
connected_server_constants: ServerConstants,
|
connected_server_constants: ServerConstants,
|
||||||
|
/// Requested but not yet received plugins
|
||||||
|
missing_plugins: u32,
|
||||||
|
/// Locally cached plugins needed by the server
|
||||||
|
local_plugins: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Holds data related to the current players characters, as well as some
|
/// Holds data related to the current players characters, as well as some
|
||||||
@ -392,6 +400,7 @@ impl Client {
|
|||||||
auth_trusted: impl FnMut(&str) -> bool,
|
auth_trusted: impl FnMut(&str) -> bool,
|
||||||
init_stage_update: &(dyn Fn(ClientInitStage) + Send + Sync),
|
init_stage_update: &(dyn Fn(ClientInitStage) + Send + Sync),
|
||||||
add_foreign_systems: impl Fn(&mut DispatcherBuilder) + Send + 'static,
|
add_foreign_systems: impl Fn(&mut DispatcherBuilder) + Send + 'static,
|
||||||
|
config_dir: PathBuf,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let network = Network::new(Pid::new(), &runtime);
|
let network = Network::new(Pid::new(), &runtime);
|
||||||
|
|
||||||
@ -580,6 +589,7 @@ impl Client {
|
|||||||
server_constants,
|
server_constants,
|
||||||
repair_recipe_book,
|
repair_recipe_book,
|
||||||
description,
|
description,
|
||||||
|
active_plugins,
|
||||||
} = loop {
|
} = loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
// Spawn in a blocking thread (leaving the network thread free). This is mostly
|
// Spawn in a blocking thread (leaving the network thread free). This is mostly
|
||||||
@ -614,6 +624,25 @@ impl Client {
|
|||||||
add_foreign_systems(dispatch_builder);
|
add_foreign_systems(dispatch_builder);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
let mut missing_plugins: Vec<PluginHash> = Vec::new();
|
||||||
|
let mut local_plugins: Vec<PathBuf> = Vec::new();
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
{
|
||||||
|
let already_present = state.ecs().read_resource::<PluginMgr>().plugin_list();
|
||||||
|
for hash in active_plugins.iter() {
|
||||||
|
if !already_present.contains(hash) {
|
||||||
|
// look in config_dir first (cache)
|
||||||
|
if let Ok(local_path) = common_state::plugin::find_cached(&config_dir, hash)
|
||||||
|
{
|
||||||
|
local_plugins.push(local_path);
|
||||||
|
} else {
|
||||||
|
//tracing::info!("cache not found {local_path:?}");
|
||||||
|
tracing::info!("Server requires plugin {hash:x?}");
|
||||||
|
missing_plugins.push(*hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Client-only components
|
// Client-only components
|
||||||
state.ecs_mut().register::<comp::Last<CharacterState>>();
|
state.ecs_mut().register::<comp::Last<CharacterState>>();
|
||||||
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
||||||
@ -887,6 +916,8 @@ impl Client {
|
|||||||
repair_recipe_book,
|
repair_recipe_book,
|
||||||
max_group_size,
|
max_group_size,
|
||||||
client_timeout,
|
client_timeout,
|
||||||
|
missing_plugins,
|
||||||
|
local_plugins,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -904,12 +935,18 @@ impl Client {
|
|||||||
repair_recipe_book,
|
repair_recipe_book,
|
||||||
max_group_size,
|
max_group_size,
|
||||||
client_timeout,
|
client_timeout,
|
||||||
|
missing_plugins,
|
||||||
|
local_plugins,
|
||||||
) = loop {
|
) = loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = &mut task => break res.expect("Client thread should not panic")?,
|
res = &mut task => break res.expect("Client thread should not panic")?,
|
||||||
_ = ping_interval.tick() => ping_stream.send(PingMsg::Ping)?,
|
_ = ping_interval.tick() => ping_stream.send(PingMsg::Ping)?,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let missing_plugins_num = missing_plugins.len();
|
||||||
|
if !missing_plugins.is_empty() {
|
||||||
|
stream.send(ClientGeneral::RequestPlugins(missing_plugins))?;
|
||||||
|
}
|
||||||
ping_stream.send(PingMsg::Ping)?;
|
ping_stream.send(PingMsg::Ping)?;
|
||||||
|
|
||||||
debug!("Initial sync done");
|
debug!("Initial sync done");
|
||||||
@ -990,6 +1027,8 @@ impl Client {
|
|||||||
dt_adjustment: 1.0,
|
dt_adjustment: 1.0,
|
||||||
|
|
||||||
connected_server_constants: server_constants,
|
connected_server_constants: server_constants,
|
||||||
|
missing_plugins: missing_plugins_num as u32,
|
||||||
|
local_plugins,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1123,7 +1162,8 @@ impl Client {
|
|||||||
// Always possible
|
// Always possible
|
||||||
ClientGeneral::ChatMsg(_)
|
ClientGeneral::ChatMsg(_)
|
||||||
| ClientGeneral::Command(_, _)
|
| ClientGeneral::Command(_, _)
|
||||||
| ClientGeneral::Terminate => &mut self.general_stream,
|
| ClientGeneral::Terminate
|
||||||
|
| ClientGeneral::RequestPlugins(_) => &mut self.general_stream,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "tracy")]
|
#[cfg(feature = "tracy")]
|
||||||
{
|
{
|
||||||
@ -2539,6 +2579,10 @@ impl Client {
|
|||||||
ServerGeneral::Notification(n) => {
|
ServerGeneral::Notification(n) => {
|
||||||
frontend_events.push(Event::Notification(n));
|
frontend_events.push(Event::Notification(n));
|
||||||
},
|
},
|
||||||
|
ServerGeneral::PluginData(d) => {
|
||||||
|
tracing::info!("plugin data #1 {}", d.len());
|
||||||
|
frontend_events.push(Event::PluginDataReceived(d));
|
||||||
|
},
|
||||||
_ => unreachable!("Not a general msg"),
|
_ => unreachable!("Not a general msg"),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -3182,6 +3226,20 @@ impl Client {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// another plugin data received, is this the last one
|
||||||
|
pub fn decrement_missing_plugins(&mut self) -> u32 {
|
||||||
|
if self.missing_plugins > 0 {
|
||||||
|
self.missing_plugins -= 1;
|
||||||
|
}
|
||||||
|
self.missing_plugins
|
||||||
|
}
|
||||||
|
|
||||||
|
/// number of requested plugins
|
||||||
|
pub fn missing_plugins(&self) -> u32 { self.missing_plugins }
|
||||||
|
|
||||||
|
/// extract list of locally cached plugins to load
|
||||||
|
pub fn take_local_plugins(&mut self) -> Vec<PathBuf> { std::mem::take(&mut self.local_plugins) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Client {
|
impl Drop for Client {
|
||||||
@ -3247,6 +3305,7 @@ mod tests {
|
|||||||
|suggestion: &str| suggestion == auth_server,
|
|suggestion: &str| suggestion == auth_server,
|
||||||
&|_| {},
|
&|_| {},
|
||||||
|_| {},
|
|_| {},
|
||||||
|
PathBuf::default(),
|
||||||
));
|
));
|
||||||
let localisation = LocalizationHandle::load_expect("en");
|
let localisation = LocalizationHandle::load_expect("en");
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ chrono = { workspace = true }
|
|||||||
chrono-tz = { workspace = true }
|
chrono-tz = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
sha2 = "0.10"
|
sha2 = { workspace = true }
|
||||||
|
|
||||||
# Strum
|
# Strum
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use super::{world_msg::SiteId, PingMsg};
|
use super::{world_msg::SiteId, PingMsg};
|
||||||
use common::{character::CharacterId, comp, comp::Skill, terrain::block::Block, ViewDistances};
|
use common::{
|
||||||
|
character::CharacterId, comp, comp::Skill, event::PluginHash, terrain::block::Block,
|
||||||
|
ViewDistances,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -95,6 +98,7 @@ pub enum ClientGeneral {
|
|||||||
RequestLossyTerrainCompression {
|
RequestLossyTerrainCompression {
|
||||||
lossy_terrain_compression: bool,
|
lossy_terrain_compression: bool,
|
||||||
},
|
},
|
||||||
|
RequestPlugins(Vec<PluginHash>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientMsg {
|
impl ClientMsg {
|
||||||
@ -143,6 +147,7 @@ impl ClientMsg {
|
|||||||
| ClientGeneral::Terminate
|
| ClientGeneral::Terminate
|
||||||
// LodZoneRequest is required by the char select screen
|
// LodZoneRequest is required by the char select screen
|
||||||
| ClientGeneral::LodZoneRequest { .. } => true,
|
| ClientGeneral::LodZoneRequest { .. } => true,
|
||||||
|
| ClientGeneral::RequestPlugins(_) => true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientMsg::Ping(_) => true,
|
ClientMsg::Ping(_) => true,
|
||||||
|
@ -7,7 +7,7 @@ use common::{
|
|||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
character::{self, CharacterItem},
|
character::{self, CharacterItem},
|
||||||
comp::{self, body::Gender, invite::InviteKind, item::MaterialStatManifest, Content},
|
comp::{self, body::Gender, invite::InviteKind, item::MaterialStatManifest, Content},
|
||||||
event::UpdateCharacterMetadata,
|
event::{PluginHash, UpdateCharacterMetadata},
|
||||||
lod,
|
lod,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
recipe::{ComponentRecipeBook, RecipeBook, RepairRecipeBook},
|
recipe::{ComponentRecipeBook, RecipeBook, RepairRecipeBook},
|
||||||
@ -75,6 +75,7 @@ pub enum ServerInit {
|
|||||||
ability_map: comp::item::tool::AbilityMap,
|
ability_map: comp::item::tool::AbilityMap,
|
||||||
server_constants: ServerConstants,
|
server_constants: ServerConstants,
|
||||||
description: ServerDescription,
|
description: ServerDescription,
|
||||||
|
active_plugins: Vec<PluginHash>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +220,8 @@ pub enum ServerGeneral {
|
|||||||
/// Suggest the client to spectate a position. Called after client has
|
/// Suggest the client to spectate a position. Called after client has
|
||||||
/// requested teleport etc.
|
/// requested teleport etc.
|
||||||
SpectatePosition(Vec3<f32>),
|
SpectatePosition(Vec3<f32>),
|
||||||
|
/// Plugin data requested from the server
|
||||||
|
PluginData(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerGeneral {
|
impl ServerGeneral {
|
||||||
@ -358,6 +361,7 @@ impl ServerMsg {
|
|||||||
| ServerGeneral::Disconnect(_)
|
| ServerGeneral::Disconnect(_)
|
||||||
| ServerGeneral::Notification(_)
|
| ServerGeneral::Notification(_)
|
||||||
| ServerGeneral::LodZoneUpdate { .. } => true,
|
| ServerGeneral::LodZoneUpdate { .. } => true,
|
||||||
|
ServerGeneral::PluginData(_) => true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ServerMsg::Ping(_) => true,
|
ServerMsg::Ping(_) => true,
|
||||||
|
@ -27,6 +27,8 @@ use uuid::Uuid;
|
|||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
pub type SiteId = u64;
|
pub type SiteId = u64;
|
||||||
|
/// Plugin identifier (sha256)
|
||||||
|
pub type PluginHash = [u8; 32];
|
||||||
|
|
||||||
pub enum LocalEvent {
|
pub enum LocalEvent {
|
||||||
/// Applies upward force to entity's `Vel`
|
/// Applies upward force to entity's `Vel`
|
||||||
@ -424,6 +426,10 @@ pub struct ToggleSpriteLightEvent {
|
|||||||
pub pos: Vec3<i32>,
|
pub pos: Vec3<i32>,
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
}
|
}
|
||||||
|
pub struct RequestPluginsEvent {
|
||||||
|
pub entity: EcsEntity,
|
||||||
|
pub plugins: Vec<PluginHash>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct EventBus<E> {
|
pub struct EventBus<E> {
|
||||||
queue: Mutex<VecDeque<E>>,
|
queue: Mutex<VecDeque<E>>,
|
||||||
@ -548,6 +554,7 @@ pub fn register_event_busses(ecs: &mut World) {
|
|||||||
ecs.insert(EventBus::<StartTeleportingEvent>::default());
|
ecs.insert(EventBus::<StartTeleportingEvent>::default());
|
||||||
ecs.insert(EventBus::<ToggleSpriteLightEvent>::default());
|
ecs.insert(EventBus::<ToggleSpriteLightEvent>::default());
|
||||||
ecs.insert(EventBus::<TransformEvent>::default());
|
ecs.insert(EventBus::<TransformEvent>::default());
|
||||||
|
ecs.insert(EventBus::<RequestPluginsEvent>::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define ecs read data for event busses. And a way to convert them all to
|
/// Define ecs read data for event busses. And a way to convert them all to
|
||||||
|
@ -6,7 +6,7 @@ version = "0.10.0"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
simd = ["vek/platform_intrinsics"]
|
simd = ["vek/platform_intrinsics"]
|
||||||
plugins = ["common-assets/plugins", "toml", "wasmtime", "wasmtime-wasi", "tar", "bincode", "serde"]
|
plugins = ["common-assets/plugins", "toml", "wasmtime", "wasmtime-wasi", "tar", "bincode", "serde", "dep:sha2", "dep:hex"]
|
||||||
|
|
||||||
default = ["simd"]
|
default = ["simd"]
|
||||||
|
|
||||||
@ -40,6 +40,8 @@ wasmtime-wasi = { version = "17.0.0", optional = true }
|
|||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
bytes = "^1"
|
bytes = "^1"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
sha2 = { workspace = true, optional = true }
|
||||||
|
hex = { workspace = true, optional = true }
|
||||||
|
|
||||||
# Tweak running code
|
# Tweak running code
|
||||||
#inline_tweak = { version = "1.0.8", features = ["release_tweak"] }
|
#inline_tweak = { version = "1.0.8", features = ["release_tweak"] }
|
||||||
|
@ -3,12 +3,12 @@ pub mod memory_manager;
|
|||||||
pub mod module;
|
pub mod module;
|
||||||
|
|
||||||
use bincode::ErrorKind;
|
use bincode::ErrorKind;
|
||||||
use common::{assets::ASSETS_PATH, uid::Uid};
|
use common::{assets::ASSETS_PATH, event::PluginHash, uid::Uid};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
fs,
|
fs,
|
||||||
io::Read,
|
io::{Read, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
@ -19,6 +19,8 @@ use self::{
|
|||||||
module::PluginModule,
|
module::PluginModule,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use sha2::Digest;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct PluginData {
|
pub struct PluginData {
|
||||||
name: String,
|
name: String,
|
||||||
@ -26,17 +28,62 @@ pub struct PluginData {
|
|||||||
dependencies: HashSet<String>,
|
dependencies: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compute_hash(data: &[u8]) -> PluginHash {
|
||||||
|
let shasum = sha2::Sha256::digest(data);
|
||||||
|
let mut shasum_iter = shasum.iter();
|
||||||
|
// a newer generic-array supports into_array ...
|
||||||
|
let shasum: PluginHash = std::array::from_fn(|_| *shasum_iter.next().unwrap());
|
||||||
|
shasum
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_file_name(
|
||||||
|
mut base_dir: PathBuf,
|
||||||
|
hash: &PluginHash,
|
||||||
|
create_dir: bool,
|
||||||
|
) -> Result<PathBuf, std::io::Error> {
|
||||||
|
base_dir.push("server-plugins");
|
||||||
|
if create_dir {
|
||||||
|
std::fs::create_dir_all(base_dir.as_path())?;
|
||||||
|
}
|
||||||
|
let name = hex::encode(hash);
|
||||||
|
base_dir.push(name);
|
||||||
|
base_dir.set_extension("plugin.tar");
|
||||||
|
Ok(base_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write received plugin to disk cache
|
||||||
|
pub fn store_server_plugin(base_dir: &Path, data: Vec<u8>) -> Result<PathBuf, std::io::Error> {
|
||||||
|
let shasum = compute_hash(data.as_slice());
|
||||||
|
let result = cache_file_name(base_dir.to_path_buf(), &shasum, true)?;
|
||||||
|
let mut file = std::fs::File::create(result.as_path())?;
|
||||||
|
file.write_all(data.as_slice())?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_cached(base_dir: &Path, hash: &PluginHash) -> Result<PathBuf, std::io::Error> {
|
||||||
|
let local_path = cache_file_name(base_dir.to_path_buf(), hash, false)?;
|
||||||
|
if local_path.as_path().exists() {
|
||||||
|
Ok(local_path)
|
||||||
|
} else {
|
||||||
|
Err(std::io::Error::from(std::io::ErrorKind::NotFound))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Plugin {
|
pub struct Plugin {
|
||||||
data: PluginData,
|
data: PluginData,
|
||||||
modules: Vec<PluginModule>,
|
modules: Vec<PluginModule>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
files: HashMap<PathBuf, Vec<u8>>,
|
hash: PluginHash,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin {
|
impl Plugin {
|
||||||
pub fn from_reader<R: Read>(mut reader: R) -> Result<Self, PluginError> {
|
pub fn from_path(path_buf: PathBuf) -> Result<Self, PluginError> {
|
||||||
|
let mut reader = fs::File::open(path_buf.as_path()).map_err(PluginError::Io)?;
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
reader.read_to_end(&mut buf).map_err(PluginError::Io)?;
|
reader.read_to_end(&mut buf).map_err(PluginError::Io)?;
|
||||||
|
let shasum = compute_hash(buf.as_slice());
|
||||||
|
|
||||||
let mut files = tar::Archive::new(&*buf)
|
let mut files = tar::Archive::new(&*buf)
|
||||||
.entries()
|
.entries()
|
||||||
@ -76,7 +123,8 @@ impl Plugin {
|
|||||||
Ok(Plugin {
|
Ok(Plugin {
|
||||||
data,
|
data,
|
||||||
modules,
|
modules,
|
||||||
files,
|
hash: shasum,
|
||||||
|
path: path_buf,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +159,9 @@ impl Plugin {
|
|||||||
});
|
});
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get the path to the plugin file
|
||||||
|
pub fn path(&self) -> &Path { return self.path.as_path() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -140,14 +191,12 @@ impl PluginMgr {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
info!("Loading plugin at {:?}", entry.path());
|
info!("Loading plugin at {:?}", entry.path());
|
||||||
Plugin::from_reader(fs::File::open(entry.path()).map_err(PluginError::Io)?).map(
|
Plugin::from_path(entry.path()).map(|plugin| {
|
||||||
|plugin| {
|
|
||||||
if let Err(e) = common::assets::register_tar(entry.path()) {
|
if let Err(e) = common::assets::register_tar(entry.path()) {
|
||||||
error!("Plugin {:?} tar error {e:?}", entry.path());
|
error!("Plugin {:?} tar error {e:?}", entry.path());
|
||||||
}
|
}
|
||||||
Some(plugin)
|
Some(plugin)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@ -169,6 +218,36 @@ impl PluginMgr {
|
|||||||
Ok(Self { plugins })
|
Ok(Self { plugins })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a plugin received from the server
|
||||||
|
pub fn load_server_plugin(&mut self, path: PathBuf) {
|
||||||
|
let _ = Plugin::from_path(path.clone()).map(|plugin| {
|
||||||
|
if let Err(e) = common::assets::register_tar(path.clone()) {
|
||||||
|
error!("Plugin {:?} tar error {e:?}", path.as_path());
|
||||||
|
}
|
||||||
|
self.plugins.push(plugin);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_server_plugin(
|
||||||
|
&mut self,
|
||||||
|
base_dir: &Path,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
let path = store_server_plugin(base_dir, data)?;
|
||||||
|
self.load_server_plugin(path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// list all registered plugins
|
||||||
|
pub fn plugin_list(&self) -> Vec<PluginHash> {
|
||||||
|
self.plugins.iter().map(|plugin| plugin.hash).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// retrieve a specific plugin
|
||||||
|
pub fn find(&self, hash: &PluginHash) -> Option<&Plugin> {
|
||||||
|
self.plugins.iter().find(|plugin| &plugin.hash == hash)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_event(
|
pub fn load_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
ecs: &EcsWorld,
|
ecs: &EcsWorld,
|
||||||
|
@ -213,7 +213,8 @@ impl Client {
|
|||||||
| ServerGeneral::CreateEntity(_)
|
| ServerGeneral::CreateEntity(_)
|
||||||
| ServerGeneral::DeleteEntity(_)
|
| ServerGeneral::DeleteEntity(_)
|
||||||
| ServerGeneral::Disconnect(_)
|
| ServerGeneral::Disconnect(_)
|
||||||
| ServerGeneral::Notification(_) => {
|
| ServerGeneral::Notification(_)
|
||||||
|
| ServerGeneral::PluginData(_) => {
|
||||||
PreparedMsg::new(3, &g, &self.general_stream_params)
|
PreparedMsg::new(3, &g, &self.general_stream_params)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
use crate::client::Client;
|
use crate::{client::Client, events::DispatcherBuilder};
|
||||||
use common::event::RequestSiteInfoEvent;
|
use common::event::{RequestPluginsEvent, RequestSiteInfoEvent};
|
||||||
use common_net::msg::{world_msg::EconomyInfo, ServerGeneral};
|
use common_net::msg::{world_msg::EconomyInfo, ServerGeneral};
|
||||||
use specs::{DispatcherBuilder, ReadExpect, ReadStorage};
|
use specs::{DispatcherBuilder, ReadExpect, ReadStorage};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use world::IndexOwned;
|
use world::IndexOwned;
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
use {common_state::plugin::PluginMgr, std::io::Read};
|
||||||
|
|
||||||
use super::{event_dispatch, ServerEvent};
|
use super::{event_dispatch, ServerEvent};
|
||||||
|
|
||||||
pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
|
pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
|
||||||
event_dispatch::<RequestSiteInfoEvent>(builder);
|
event_dispatch::<RequestSiteInfoEvent>(builder);
|
||||||
|
event_dispatch::<RequestPluginsEvent>(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
@ -64,3 +67,53 @@ impl ServerEvent for RequestSiteInfoEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send missing plugins to the client
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
impl ServerEvent for RequestPluginsEvent {
|
||||||
|
type SystemData<'a> = (ReadExpect<'a, PluginMgr>, ReadStorage<'a, Client>);
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
events: impl ExactSizeIterator<Item = Self>,
|
||||||
|
(plugin_mgr, clients): Self::SystemData<'_>,
|
||||||
|
) {
|
||||||
|
for mut ev in events {
|
||||||
|
for hash in ev.plugins.drain(..) {
|
||||||
|
if let Some(plugin) = plugin_mgr.find(&hash) {
|
||||||
|
match std::fs::File::open(plugin.path()) {
|
||||||
|
Ok(mut reader) => {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
match reader.read_to_end(&mut buf) {
|
||||||
|
Ok(_) => {
|
||||||
|
clients.get(ev.entity).map(|c| {
|
||||||
|
c.send(ServerGeneral::PluginData(buf)).unwrap_or_else(|e| {
|
||||||
|
tracing::warn!(
|
||||||
|
"Error {e} sending plugin {hash:?} to client"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Error {e} reading plugin file {:?}",
|
||||||
|
plugin.path()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Error {e} opening plugin file {:?}", plugin.path());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "plugins"))]
|
||||||
|
impl ServerEvent for RequestPluginsEvent {
|
||||||
|
type SystemData<'a> = ();
|
||||||
|
|
||||||
|
fn handle(events: impl ExactSizeIterator<Item = Self>, _: Self::SystemData<'_>) {}
|
||||||
|
}
|
||||||
|
@ -117,12 +117,15 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "worldgen"))]
|
|
||||||
use test_world::{IndexOwned, World};
|
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
pub use world::{civ::WorldCivStage, sim::WorldSimStage, WorldGenerateStage};
|
pub use world::{civ::WorldCivStage, sim::WorldSimStage, WorldGenerateStage};
|
||||||
|
#[cfg(not(feature = "worldgen"))]
|
||||||
|
use {
|
||||||
|
common_net::msg::WorldMapMsg,
|
||||||
|
test_world::{IndexOwned, World},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
persistence::{DatabaseSettings, SqlLogMode},
|
persistence::{DatabaseSettings, SqlLogMode},
|
||||||
@ -308,6 +311,7 @@ impl Server {
|
|||||||
horizons: [(vec![0], vec![0]), (vec![0], vec![0])],
|
horizons: [(vec![0], vec![0]), (vec![0], vec![0])],
|
||||||
alt: Grid::new(Vec2::new(1, 1), 1),
|
alt: Grid::new(Vec2::new(1, 1), 1),
|
||||||
sites: Vec::new(),
|
sites: Vec::new(),
|
||||||
|
possible_starting_sites: Vec::new(),
|
||||||
pois: Vec::new(),
|
pois: Vec::new(),
|
||||||
default_chunk: Arc::new(world.generate_oob_chunk()),
|
default_chunk: Arc::new(world.generate_oob_chunk()),
|
||||||
};
|
};
|
||||||
@ -318,7 +322,10 @@ impl Server {
|
|||||||
|
|
||||||
let mut state = State::server(
|
let mut state = State::server(
|
||||||
Arc::clone(&pools),
|
Arc::clone(&pools),
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
world.sim().map_size_lg(),
|
world.sim().map_size_lg(),
|
||||||
|
#[cfg(not(feature = "worldgen"))]
|
||||||
|
common::terrain::map::MapSizeLg::new(Vec2::one()).unwrap(),
|
||||||
Arc::clone(&map.default_chunk),
|
Arc::clone(&map.default_chunk),
|
||||||
|dispatcher_builder| {
|
|dispatcher_builder| {
|
||||||
add_local_systems(dispatcher_builder);
|
add_local_systems(dispatcher_builder);
|
||||||
|
@ -37,7 +37,9 @@ impl Lod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
pub fn from_world(world: &World, index: IndexRef) -> Self { Self::default() }
|
pub fn from_world(world: &World, index: IndexRef, _threadpool: &rayon::ThreadPool) -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn zone(&self, zone_pos: Vec2<i32>) -> &lod::Zone {
|
pub fn zone(&self, zone_pos: Vec2<i32>) -> &lod::Zone {
|
||||||
self.zones.get(&zone_pos).unwrap_or(&EMPTY_ZONE)
|
self.zones.get(&zone_pos).unwrap_or(&EMPTY_ZONE)
|
||||||
|
@ -182,6 +182,7 @@ impl Sys {
|
|||||||
offhand.clone(),
|
offhand.clone(),
|
||||||
body,
|
body,
|
||||||
character_updater,
|
character_updater,
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
start_site.and_then(|site_idx| {
|
start_site.and_then(|site_idx| {
|
||||||
// TODO: This corresponds to the ID generation logic in
|
// TODO: This corresponds to the ID generation logic in
|
||||||
// `world/src/lib.rs`. Really, we should have
|
// `world/src/lib.rs`. Really, we should have
|
||||||
@ -214,6 +215,8 @@ impl Sys {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
#[cfg(not(feature = "worldgen"))]
|
||||||
|
None,
|
||||||
) {
|
) {
|
||||||
debug!(
|
debug!(
|
||||||
?error,
|
?error,
|
||||||
|
@ -17,7 +17,7 @@ event_emitters! {
|
|||||||
command: event::CommandEvent,
|
command: event::CommandEvent,
|
||||||
client_disconnect: event::ClientDisconnectEvent,
|
client_disconnect: event::ClientDisconnectEvent,
|
||||||
chat: event::ChatEvent,
|
chat: event::ChatEvent,
|
||||||
|
plugins: event::RequestPluginsEvent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +72,10 @@ impl Sys {
|
|||||||
common::comp::DisconnectReason::ClientRequested,
|
common::comp::DisconnectReason::ClientRequested,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
ClientGeneral::RequestPlugins(plugins) => {
|
||||||
|
tracing::info!("Plugin request {plugins:x?}, {}", player.is_some());
|
||||||
|
emitters.emit(event::RequestPluginsEvent { entity, plugins });
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
debug!("Kicking possible misbehaving client due to invalid message request");
|
debug!("Kicking possible misbehaving client due to invalid message request");
|
||||||
emitters.emit(event::ClientDisconnectEvent(
|
emitters.emit(event::ClientDisconnectEvent(
|
||||||
|
@ -259,7 +259,8 @@ impl Sys {
|
|||||||
| ClientGeneral::LodZoneRequest { .. }
|
| ClientGeneral::LodZoneRequest { .. }
|
||||||
| ClientGeneral::ChatMsg(_)
|
| ClientGeneral::ChatMsg(_)
|
||||||
| ClientGeneral::Command(..)
|
| ClientGeneral::Command(..)
|
||||||
| ClientGeneral::Terminate => {
|
| ClientGeneral::Terminate
|
||||||
|
| ClientGeneral::RequestPlugins(_) => {
|
||||||
debug!("Kicking possibly misbehaving client due to invalid client in game request");
|
debug!("Kicking possibly misbehaving client due to invalid client in game request");
|
||||||
emitters.emit(event::ClientDisconnectEvent(
|
emitters.emit(event::ClientDisconnectEvent(
|
||||||
entity,
|
entity,
|
||||||
|
@ -11,7 +11,7 @@ use common::{
|
|||||||
recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book},
|
recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book},
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
shared_server_config::ServerConstants,
|
shared_server_config::ServerConstants,
|
||||||
uid::{IdMaps, Uid},
|
uid::Uid,
|
||||||
};
|
};
|
||||||
use common_base::prof_span;
|
use common_base::prof_span;
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
@ -52,9 +52,8 @@ pub struct ReadData<'a> {
|
|||||||
ability_map: ReadExpect<'a, comp::item::tool::AbilityMap>,
|
ability_map: ReadExpect<'a, comp::item::tool::AbilityMap>,
|
||||||
map: ReadExpect<'a, WorldMapMsg>,
|
map: ReadExpect<'a, WorldMapMsg>,
|
||||||
trackers: TrackedStorages<'a>,
|
trackers: TrackedStorages<'a>,
|
||||||
_healths: ReadStorage<'a, Health>, // used by plugin feature
|
#[allow(dead_code)]
|
||||||
_plugin_mgr: ReadPlugin<'a>, // used by plugin feature
|
plugin_mgr: ReadPlugin<'a>, // only used by plugins feature
|
||||||
_id_maps: Read<'a, IdMaps>, // used by plugin feature
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This system will handle new messages from clients
|
/// This system will handle new messages from clients
|
||||||
@ -330,6 +329,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Tell the client its request was successful.
|
// Tell the client its request was successful.
|
||||||
client.send(Ok(()))?;
|
client.send(Ok(()))?;
|
||||||
|
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
let active_plugins = read_data.plugin_mgr.plugin_list();
|
||||||
|
#[cfg(not(feature = "plugins"))]
|
||||||
|
let active_plugins = Vec::default();
|
||||||
|
|
||||||
let server_descriptions = &read_data.editable_settings.server_description;
|
let server_descriptions = &read_data.editable_settings.server_description;
|
||||||
let description = ServerDescription {
|
let description = ServerDescription {
|
||||||
@ -358,6 +361,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
day_cycle_coefficient: read_data.settings.day_cycle_coefficient()
|
day_cycle_coefficient: read_data.settings.day_cycle_coefficient()
|
||||||
},
|
},
|
||||||
description,
|
description,
|
||||||
|
active_plugins,
|
||||||
})?;
|
})?;
|
||||||
debug!("Done initial sync with client.");
|
debug!("Done initial sync with client.");
|
||||||
|
|
||||||
|
@ -717,11 +717,14 @@ where
|
|||||||
let world_aabr_in_chunks = Aabr {
|
let world_aabr_in_chunks = Aabr {
|
||||||
min: Vec2::zero(),
|
min: Vec2::zero(),
|
||||||
// NOTE: Cast is correct because chunk coordinates must fit in an i32 (actually, i16).
|
// NOTE: Cast is correct because chunk coordinates must fit in an i32 (actually, i16).
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
max: world
|
max: world
|
||||||
.sim()
|
.sim()
|
||||||
.get_size()
|
.get_size()
|
||||||
.map(|x| x.saturating_sub(1))
|
.map(|x| x.saturating_sub(1))
|
||||||
.as_::<i32>(),
|
.as_::<i32>(),
|
||||||
|
#[cfg(not(feature = "worldgen"))]
|
||||||
|
max: Vec2::one(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut presences_positions_entities, mut presences_positions): (Vec<_>, Vec<_>) =
|
let (mut presences_positions_entities, mut presences_positions): (Vec<_>, Vec<_>) =
|
||||||
|
@ -45,6 +45,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
let max_view_distance = server_settings.max_view_distance.unwrap_or(u32::MAX);
|
let max_view_distance = server_settings.max_view_distance.unwrap_or(u32::MAX);
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
let (presences_position_entities, _) = super::terrain::prepare_player_presences(
|
let (presences_position_entities, _) = super::terrain::prepare_player_presences(
|
||||||
&world,
|
&world,
|
||||||
max_view_distance,
|
max_view_distance,
|
||||||
@ -53,6 +54,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
&presences,
|
&presences,
|
||||||
&clients,
|
&clients,
|
||||||
);
|
);
|
||||||
|
#[cfg(not(feature = "worldgen"))]
|
||||||
|
let presences_position_entities: Vec<((vek::Vec2<i16>, i32), specs::Entity)> = Vec::new();
|
||||||
let real_max_view_distance =
|
let real_max_view_distance =
|
||||||
super::terrain::convert_to_loaded_vd(u32::MAX, max_view_distance);
|
super::terrain::convert_to_loaded_vd(u32::MAX, max_view_distance);
|
||||||
|
|
||||||
|
@ -2,11 +2,13 @@ use common::{
|
|||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
generation::{ChunkSupplement, EntityInfo},
|
generation::{ChunkSupplement, EntityInfo},
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
|
rtsim::ChunkResource,
|
||||||
terrain::{
|
terrain::{
|
||||||
Block, BlockKind, MapSizeLg, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize,
|
Block, BlockKind, MapSizeLg, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize,
|
||||||
},
|
},
|
||||||
vol::{ReadVol, RectVolSize, WriteVol},
|
vol::{ReadVol, RectVolSize, WriteVol},
|
||||||
};
|
};
|
||||||
|
use enum_map::EnumMap;
|
||||||
use rand::{prelude::*, rngs::SmallRng};
|
use rand::{prelude::*, rngs::SmallRng};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -48,6 +50,7 @@ impl World {
|
|||||||
&self,
|
&self,
|
||||||
_index: IndexRef,
|
_index: IndexRef,
|
||||||
chunk_pos: Vec2<i32>,
|
chunk_pos: Vec2<i32>,
|
||||||
|
_rtsim_resources: Option<EnumMap<ChunkResource, f32>>,
|
||||||
_should_continue: impl FnMut() -> bool,
|
_should_continue: impl FnMut() -> bool,
|
||||||
_time: Option<(TimeOfDay, Calendar)>,
|
_time: Option<(TimeOfDay, Calendar)>,
|
||||||
) -> Result<(TerrainChunk, ChunkSupplement), ()> {
|
) -> Result<(TerrainChunk, ChunkSupplement), ()> {
|
||||||
@ -71,4 +74,6 @@ impl World {
|
|||||||
supplement,
|
supplement,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_location_name(&self, _index: IndexRef, _wpos2d: Vec2<i32>) -> Option<String> { None }
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,7 @@ tokio = { workspace = true, features = ["rt-multi-thread"] }
|
|||||||
num_cpus = "1.0"
|
num_cpus = "1.0"
|
||||||
inline_tweak = { workspace = true }
|
inline_tweak = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
sha2 = { workspace = true }
|
||||||
|
|
||||||
# Discord RPC
|
# Discord RPC
|
||||||
discord-sdk = { version = "0.3.0", optional = true }
|
discord-sdk = { version = "0.3.0", optional = true }
|
||||||
|
@ -12,6 +12,8 @@ use crate::{
|
|||||||
use client::{self, Client};
|
use client::{self, Client};
|
||||||
use common::{comp, event::UpdateCharacterMetadata, resources::DeltaTime};
|
use common::{comp, event::UpdateCharacterMetadata, resources::DeltaTime};
|
||||||
use common_base::span;
|
use common_base::span;
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
use common_state::plugin::PluginMgr;
|
||||||
use specs::WorldExt;
|
use specs::WorldExt;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
@ -69,7 +71,9 @@ impl CharSelectionState {
|
|||||||
impl PlayState for CharSelectionState {
|
impl PlayState for CharSelectionState {
|
||||||
fn enter(&mut self, global_state: &mut GlobalState, _: Direction) {
|
fn enter(&mut self, global_state: &mut GlobalState, _: Direction) {
|
||||||
// Load the player's character list
|
// Load the player's character list
|
||||||
|
if self.client.borrow().missing_plugins() == 0 {
|
||||||
self.client.borrow_mut().load_character_list();
|
self.client.borrow_mut().load_character_list();
|
||||||
|
}
|
||||||
|
|
||||||
// Updated localization in case the selected language was changed
|
// Updated localization in case the selected language was changed
|
||||||
self.char_selection_ui.update_language(global_state.i18n);
|
self.char_selection_ui.update_language(global_state.i18n);
|
||||||
@ -274,6 +278,20 @@ impl PlayState for CharSelectionState {
|
|||||||
Rc::clone(&self.client),
|
Rc::clone(&self.client),
|
||||||
)));
|
)));
|
||||||
},
|
},
|
||||||
|
client::Event::PluginDataReceived(data) => {
|
||||||
|
tracing::info!("plugin data {}", data.len());
|
||||||
|
let mut client = self.client.borrow_mut();
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
let _ = client
|
||||||
|
.state()
|
||||||
|
.ecs()
|
||||||
|
.write_resource::<PluginMgr>()
|
||||||
|
.cache_server_plugin(&global_state.config_dir, data);
|
||||||
|
if client.decrement_missing_plugins() == 0 {
|
||||||
|
// now load characters (plugins might contain items)
|
||||||
|
client.load_character_list();
|
||||||
|
}
|
||||||
|
},
|
||||||
// TODO: See if we should handle StartSpectate here instead.
|
// TODO: See if we should handle StartSpectate here instead.
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use client::{
|
|||||||
};
|
};
|
||||||
use crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError};
|
use crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError};
|
||||||
use std::{
|
use std::{
|
||||||
|
path::Path,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
@ -50,6 +51,7 @@ impl ClientInit {
|
|||||||
password: String,
|
password: String,
|
||||||
runtime: Arc<runtime::Runtime>,
|
runtime: Arc<runtime::Runtime>,
|
||||||
locale: Option<String>,
|
locale: Option<String>,
|
||||||
|
config_dir: &Path,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (tx, rx) = unbounded();
|
let (tx, rx) = unbounded();
|
||||||
let (trust_tx, trust_rx) = unbounded();
|
let (trust_tx, trust_rx) = unbounded();
|
||||||
@ -58,6 +60,7 @@ impl ClientInit {
|
|||||||
let cancel2 = Arc::clone(&cancel);
|
let cancel2 = Arc::clone(&cancel);
|
||||||
|
|
||||||
let runtime2 = Arc::clone(&runtime);
|
let runtime2 = Arc::clone(&runtime);
|
||||||
|
let config_dir = config_dir.to_path_buf();
|
||||||
|
|
||||||
runtime.spawn(async move {
|
runtime.spawn(async move {
|
||||||
let trust_fn = |auth_server: &str| {
|
let trust_fn = |auth_server: &str| {
|
||||||
@ -89,6 +92,7 @@ impl ClientInit {
|
|||||||
let _ = init_stage_tx.send(stage);
|
let _ = init_stage_tx.send(stage);
|
||||||
},
|
},
|
||||||
crate::ecs::sys::add_local_systems,
|
crate::ecs::sys::add_local_systems,
|
||||||
|
config_dir.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -18,10 +18,13 @@ use client::{
|
|||||||
use client_init::{ClientInit, Error as InitError, Msg as InitMsg};
|
use client_init::{ClientInit, Error as InitError, Msg as InitMsg};
|
||||||
use common::comp;
|
use common::comp;
|
||||||
use common_base::span;
|
use common_base::span;
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
use common_state::plugin::PluginMgr;
|
||||||
use i18n::LocalizationHandle;
|
use i18n::LocalizationHandle;
|
||||||
#[cfg(feature = "singleplayer")]
|
#[cfg(feature = "singleplayer")]
|
||||||
use server::ServerInitStage;
|
use server::ServerInitStage;
|
||||||
use std::sync::Arc;
|
use specs::WorldExt;
|
||||||
|
use std::{path::Path, sync::Arc};
|
||||||
use tokio::runtime;
|
use tokio::runtime;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use ui::{Event as MainMenuEvent, MainMenuUi};
|
use ui::{Event as MainMenuEvent, MainMenuUi};
|
||||||
@ -129,6 +132,7 @@ impl PlayState for MainMenuState {
|
|||||||
global_state.settings.language.selected_language.clone(),
|
global_state.settings.language.selected_language.clone(),
|
||||||
),
|
),
|
||||||
&global_state.i18n,
|
&global_state.i18n,
|
||||||
|
&global_state.config_dir,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
@ -216,6 +220,15 @@ impl PlayState for MainMenuState {
|
|||||||
// Poll client creation.
|
// Poll client creation.
|
||||||
match self.init.client().and_then(|init| init.poll()) {
|
match self.init.client().and_then(|init| init.poll()) {
|
||||||
Some(InitMsg::Done(Ok(mut client))) => {
|
Some(InitMsg::Done(Ok(mut client))) => {
|
||||||
|
// load local plugins needed by the server
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
for path in client.take_local_plugins().drain(..) {
|
||||||
|
client
|
||||||
|
.state_mut()
|
||||||
|
.ecs_mut()
|
||||||
|
.write_resource::<PluginMgr>()
|
||||||
|
.load_server_plugin(path);
|
||||||
|
}
|
||||||
// Register voxygen components / resources
|
// Register voxygen components / resources
|
||||||
crate::ecs::init(client.state_mut().ecs_mut());
|
crate::ecs::init(client.state_mut().ecs_mut());
|
||||||
self.init = InitState::Pipeline(Box::new(client));
|
self.init = InitState::Pipeline(Box::new(client));
|
||||||
@ -267,6 +280,21 @@ impl PlayState for MainMenuState {
|
|||||||
);
|
);
|
||||||
self.init = InitState::None;
|
self.init = InitState::None;
|
||||||
},
|
},
|
||||||
|
client::Event::PluginDataReceived(data) => {
|
||||||
|
tracing::info!("plugin data {}", data.len());
|
||||||
|
if let InitState::Pipeline(client) = &mut self.init {
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
let _ = client
|
||||||
|
.state()
|
||||||
|
.ecs()
|
||||||
|
.write_resource::<PluginMgr>()
|
||||||
|
.cache_server_plugin(&global_state.config_dir, data);
|
||||||
|
if client.decrement_missing_plugins() == 0 {
|
||||||
|
// now load characters (plugins might contain items)
|
||||||
|
client.load_character_list();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,6 +406,7 @@ impl PlayState for MainMenuState {
|
|||||||
.send_to_server
|
.send_to_server
|
||||||
.then_some(global_state.settings.language.selected_language.clone()),
|
.then_some(global_state.settings.language.selected_language.clone()),
|
||||||
&global_state.i18n,
|
&global_state.i18n,
|
||||||
|
&global_state.config_dir,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
MainMenuEvent::CancelLoginAttempt => {
|
MainMenuEvent::CancelLoginAttempt => {
|
||||||
@ -609,6 +638,7 @@ fn attempt_login(
|
|||||||
runtime: &Arc<runtime::Runtime>,
|
runtime: &Arc<runtime::Runtime>,
|
||||||
locale: Option<String>,
|
locale: Option<String>,
|
||||||
localized_strings: &LocalizationHandle,
|
localized_strings: &LocalizationHandle,
|
||||||
|
config_dir: &Path,
|
||||||
) {
|
) {
|
||||||
let localization = localized_strings.read();
|
let localization = localized_strings.read();
|
||||||
if let Err(err) = comp::Player::alias_validate(&username) {
|
if let Err(err) = comp::Player::alias_validate(&username) {
|
||||||
@ -641,6 +671,7 @@ fn attempt_login(
|
|||||||
password,
|
password,
|
||||||
Arc::clone(runtime),
|
Arc::clone(runtime),
|
||||||
locale,
|
locale,
|
||||||
|
config_dir,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,6 +447,9 @@ impl SessionState {
|
|||||||
client::Event::SpectatePosition(pos) => {
|
client::Event::SpectatePosition(pos) => {
|
||||||
self.scene.camera_mut().force_focus_pos(pos);
|
self.scene.camera_mut().force_focus_pos(pos);
|
||||||
},
|
},
|
||||||
|
client::Event::PluginDataReceived(data) => {
|
||||||
|
tracing::warn!("Received plugin data at wrong time {}", data.len());
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user