mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'christof/plugin_network' into 'master'
Load missing plugins from the server See merge request veloren/veloren!4256
This commit is contained in:
commit
24c511b5a4
@ -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: HashSet<PluginHash>,
|
||||||
|
/// 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_set = missing_plugins.iter().cloned().collect();
|
||||||
|
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_set,
|
||||||
|
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,11 @@ impl Client {
|
|||||||
ServerGeneral::Notification(n) => {
|
ServerGeneral::Notification(n) => {
|
||||||
frontend_events.push(Event::Notification(n));
|
frontend_events.push(Event::Notification(n));
|
||||||
},
|
},
|
||||||
|
ServerGeneral::PluginData(d) => {
|
||||||
|
let plugin_len = d.len();
|
||||||
|
tracing::info!(?plugin_len, "plugin data");
|
||||||
|
frontend_events.push(Event::PluginDataReceived(d));
|
||||||
|
},
|
||||||
_ => unreachable!("Not a general msg"),
|
_ => unreachable!("Not a general msg"),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -3182,6 +3227,20 @@ impl Client {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// another plugin data received, is this the last one
|
||||||
|
pub fn plugin_received(&mut self, hash: PluginHash) -> usize {
|
||||||
|
if !self.missing_plugins.remove(&hash) {
|
||||||
|
tracing::warn!(?hash, "received unrequested plugin");
|
||||||
|
}
|
||||||
|
self.missing_plugins.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// number of requested plugins
|
||||||
|
pub fn num_missing_plugins(&self) -> usize { self.missing_plugins.len() }
|
||||||
|
|
||||||
|
/// 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 +3306,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,64 @@ 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,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
data_buf: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@ -73,10 +122,14 @@ impl Plugin {
|
|||||||
})
|
})
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
let data_buf = fs::read(&path_buf).map_err(PluginError::Io)?;
|
||||||
|
|
||||||
Ok(Plugin {
|
Ok(Plugin {
|
||||||
data,
|
data,
|
||||||
modules,
|
modules,
|
||||||
files,
|
hash: shasum,
|
||||||
|
path: path_buf,
|
||||||
|
data_buf,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +164,12 @@ impl Plugin {
|
|||||||
});
|
});
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get the path to the plugin file
|
||||||
|
pub fn path(&self) -> &Path { self.path.as_path() }
|
||||||
|
|
||||||
|
/// Get the data of this plugin
|
||||||
|
pub fn data_buf(&self) -> &[u8] { &self.data_buf }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -140,14 +199,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 +226,37 @@ impl PluginMgr {
|
|||||||
Ok(Self { plugins })
|
Ok(Self { plugins })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a plugin received from the server
|
||||||
|
pub fn load_server_plugin(&mut self, path: PathBuf) -> Result<PluginHash, PluginError> {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
let hash = plugin.hash;
|
||||||
|
self.plugins.push(plugin);
|
||||||
|
hash
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_server_plugin(
|
||||||
|
&mut self,
|
||||||
|
base_dir: &Path,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<PluginHash, PluginError> {
|
||||||
|
let path = store_server_plugin(base_dir, data).map_err(PluginError::Io)?;
|
||||||
|
self.load_server_plugin(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,6 +1,8 @@
|
|||||||
use crate::client::Client;
|
use crate::client::Client;
|
||||||
use common::event::RequestSiteInfoEvent;
|
use common::event::RequestSiteInfoEvent;
|
||||||
use common_net::msg::{world_msg::EconomyInfo, ServerGeneral};
|
use common_net::msg::{world_msg::EconomyInfo, ServerGeneral};
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
use common_state::plugin::PluginMgr;
|
||||||
use specs::{DispatcherBuilder, ReadExpect, ReadStorage};
|
use specs::{DispatcherBuilder, ReadExpect, ReadStorage};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use world::IndexOwned;
|
use world::IndexOwned;
|
||||||
@ -9,6 +11,8 @@ 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);
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
event_dispatch::<common::event::RequestPluginsEvent>(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
@ -64,3 +68,33 @@ impl ServerEvent for RequestSiteInfoEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send missing plugins to the client
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
impl ServerEvent for common::event::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 {
|
||||||
|
let Some(client) = clients.get(ev.entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for hash in ev.plugins.drain(..) {
|
||||||
|
if let Some(plugin) = plugin_mgr.find(&hash) {
|
||||||
|
let buf = Vec::from(plugin.data_buf());
|
||||||
|
// TODO: @perf We could possibly make this more performant by caching prepared
|
||||||
|
// messages for each plugin.
|
||||||
|
client
|
||||||
|
.send(ServerGeneral::PluginData(buf))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
tracing::warn!("Error {e} sending plugin {hash:?} to client")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -6,12 +6,12 @@ use crate::{
|
|||||||
EditableSettings, Settings,
|
EditableSettings, Settings,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, Admin, Health, Player, Stats},
|
comp::{self, Admin, Player, Stats},
|
||||||
event::{ClientDisconnectEvent, EventBus, MakeAdminEvent},
|
event::{ClientDisconnectEvent, EventBus, MakeAdminEvent},
|
||||||
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().num_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,27 @@ impl PlayState for CharSelectionState {
|
|||||||
Rc::clone(&self.client),
|
Rc::clone(&self.client),
|
||||||
)));
|
)));
|
||||||
},
|
},
|
||||||
|
client::Event::PluginDataReceived(data) => {
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
{
|
||||||
|
tracing::info!("plugin data {}", data.len());
|
||||||
|
let mut client = self.client.borrow_mut();
|
||||||
|
let hash = client
|
||||||
|
.state()
|
||||||
|
.ecs()
|
||||||
|
.write_resource::<PluginMgr>()
|
||||||
|
.cache_server_plugin(&global_state.config_dir, data);
|
||||||
|
match hash {
|
||||||
|
Ok(hash) => {
|
||||||
|
if client.plugin_received(hash) == 0 {
|
||||||
|
// now load characters (plugins might contain items)
|
||||||
|
client.load_character_list();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => tracing::error!(?e, "cache_server_plugin"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
// 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,18 @@ 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(..) {
|
||||||
|
if let Err(e) = client
|
||||||
|
.state_mut()
|
||||||
|
.ecs_mut()
|
||||||
|
.write_resource::<PluginMgr>()
|
||||||
|
.load_server_plugin(path)
|
||||||
|
{
|
||||||
|
tracing::error!(?e, "load local plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
// 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 +283,29 @@ impl PlayState for MainMenuState {
|
|||||||
);
|
);
|
||||||
self.init = InitState::None;
|
self.init = InitState::None;
|
||||||
},
|
},
|
||||||
|
client::Event::PluginDataReceived(data) => {
|
||||||
|
#[cfg(feature = "plugins")]
|
||||||
|
{
|
||||||
|
tracing::info!("plugin data {}", data.len());
|
||||||
|
if let InitState::Pipeline(client) = &mut self.init {
|
||||||
|
let hash = client
|
||||||
|
.state()
|
||||||
|
.ecs()
|
||||||
|
.write_resource::<PluginMgr>()
|
||||||
|
.cache_server_plugin(&global_state.config_dir, data);
|
||||||
|
match hash {
|
||||||
|
Ok(hash) => {
|
||||||
|
if client.plugin_received(hash) == 0 {
|
||||||
|
// now load characters (plugins might contain
|
||||||
|
// items)
|
||||||
|
client.load_character_list();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => tracing::error!(?e, "cache_server_plugin"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,6 +417,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 +649,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 +682,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