diff --git a/crates/common/src/structs.rs b/crates/common/src/structs.rs index 6cc7ff3f..0cb0b2d6 100644 --- a/crates/common/src/structs.rs +++ b/crates/common/src/structs.rs @@ -1202,10 +1202,11 @@ pub struct MicState { pub enabled: bool, } -#[derive(Resource, Default)] +#[derive(Debug, Resource, Default)] pub struct PreviewMode { pub server: Option, pub is_preview: bool, + pub preview_parcel: Option, } // resource into which systems can add debug info diff --git a/crates/ipfs/src/lib.rs b/crates/ipfs/src/lib.rs index 4cf40b0e..f5c58ed4 100644 --- a/crates/ipfs/src/lib.rs +++ b/crates/ipfs/src/lib.rs @@ -43,7 +43,7 @@ use bevy::asset::io::wasm::HttpWasmAssetReader; use bevy_console::{ConsoleCommand, PrintConsoleLine}; use common::{ sets::RealmLifecycle, - structs::{AppConfig, CommsConfig, CurrentRealm, ServerConfiguration}, + structs::{AppConfig, CommsConfig, CurrentRealm, PreviewMode, ServerConfiguration}, util::TaskCompat, }; use ipfs_path::IpfsAsset; @@ -574,6 +574,7 @@ pub fn change_realm( >, mut current_realm: ResMut, mut print: EventWriter, + preview_mode: Res, ) { match *realm_change { None => *realm_change = Some(ipfs.realm_config_receiver.clone()), @@ -606,16 +607,23 @@ pub fn change_realm( } if !change_realm_requests.is_empty() { - let ipfs = ipfs.clone(); - let request = change_realm_requests.read().last().unwrap(); - - let new_realm = map_realm_name(&request.new_realm); - let content_server_override = request.content_server_override.to_owned(); - IoTaskPool::get() - .spawn_compat(async move { - ipfs.set_realm(new_realm, content_server_override).await; - }) - .detach(); + if preview_mode.is_preview { + print.write(PrintConsoleLine { + line: "Changing realm is disabled in preview mode.".to_owned(), + }); + change_realm_requests.clear(); + } else { + let ipfs = ipfs.clone(); + let request = change_realm_requests.read().last().unwrap(); + + let new_realm = map_realm_name(&request.new_realm); + let content_server_override = request.content_server_override.to_owned(); + IoTaskPool::get() + .spawn_compat(async move { + ipfs.set_realm(new_realm, content_server_override).await; + }) + .detach(); + } } } diff --git a/crates/scene_runner/src/initialize_scene.rs b/crates/scene_runner/src/initialize_scene.rs index f9d73430..7fdd9bf2 100644 --- a/crates/scene_runner/src/initialize_scene.rs +++ b/crates/scene_runner/src/initialize_scene.rs @@ -43,6 +43,7 @@ use system_bridge::{LiveSceneInfo, SystemApi, SystemBridge}; use super::{update_world::CrdtExtractors, LoadSceneEvent, PrimaryUser, SceneSets, SceneUpdates}; use crate::{ bounds_calc::scene_regions, + parcel_to_vec3, renderer_context::RendererSceneContext, update_world::{visibility::VisibilityComponent, ComponentTracker}, vec3_to_parcel, ContainerEntity, DeletedSceneEntities, OutOfWorld, SceneEntity, @@ -1366,7 +1367,7 @@ fn load_active_entities( #[derive(Resource, Default)] pub struct CurrentImposterScene(pub Option<(PointerResult, bool)>); -#[allow(clippy::type_complexity, clippy::too_many_arguments)] +#[expect(clippy::type_complexity, clippy::too_many_arguments)] pub fn process_scene_lifecycle( mut commands: Commands, current_realm: Res, @@ -1381,6 +1382,7 @@ pub fn process_scene_lifecycle( mut spawn: EventWriter, pointers: Res, imposter_scene: Res, + preview_mode: Res, ) { let mut required_scene_ids: HashMap<(String, Option), bool> = HashMap::new(); @@ -1388,14 +1390,19 @@ pub fn process_scene_lifecycle( let Ok(focus) = focus.single() else { return; }; + let focus = preview_mode + .preview_parcel + .as_ref() + .map(|p| GlobalTransform::from(Transform::from_translation(parcel_to_vec3(*p)))) + .unwrap_or(*focus); - let current_scene = parcels_in_range(focus, 0.0, pointers.min(), pointers.max()) + let current_scene = parcels_in_range(&focus, 0.0, pointers.min(), pointers.max()) .first() .and_then(|(p, _)| pointers.get(p)) .and_then(PointerResult::hash_and_urn); let pir = parcels_in_range( - focus, + &focus, range.load + range.unload, pointers.min(), pointers.max(), diff --git a/crates/scene_runner/src/lib.rs b/crates/scene_runner/src/lib.rs index 1275ecc6..e522326d 100644 --- a/crates/scene_runner/src/lib.rs +++ b/crates/scene_runner/src/lib.rs @@ -501,6 +501,14 @@ pub fn vec3_to_parcel(position: Vec3) -> IVec2 { .as_ivec2() } +pub fn parcel_to_vec3(parcel: IVec2) -> Vec3 { + Vec3::new( + (parcel.x as f32 + 0.5) * PARCEL_SIZE, + 0., + -(parcel.y as f32 + 0.5) * PARCEL_SIZE, + ) +} + impl ContainingScene<'_, '_> { // just the parcel at the position pub fn get_parcel_position(&self, position: Vec3) -> Option { diff --git a/crates/system_ui/src/sysinfo.rs b/crates/system_ui/src/sysinfo.rs index 8954a776..80d4b730 100644 --- a/crates/system_ui/src/sysinfo.rs +++ b/crates/system_ui/src/sysinfo.rs @@ -25,12 +25,13 @@ use console::DoAddConsoleCommand; use scene_material::{SceneMaterial, SCENE_MATERIAL_OUTLINE}; use scene_runner::{ initialize_scene::{SceneLoading, TestingData, PARCEL_SIZE}, + parcel_to_vec3, renderer_context::RendererSceneContext, update_world::{ gltf_container::{GltfLoadingCount, SceneResourceLookup}, ComponentTracker, TrackComponents, }, - ContainerEntity, ContainingScene, Toaster, + vec3_to_parcel, ContainerEntity, ContainingScene, Toaster, }; use ui_core::{ bound_node::BoundedImageMaterial, @@ -441,23 +442,25 @@ fn setup_minimap( fn update_minimap( q: Query<&DuiEntities, With>, mut maps: Query<&mut MapTexture>, - player: Query<(Entity, &GlobalTransform), With>, + player: Query<&GlobalTransform, With>, containing_scene: ContainingScene, scenes: Query<(&RendererSceneContext, Option<&GltfLoadingCount>)>, mut text: Query<&mut Text>, preview: Res, ) { - let Ok((player, gt)) = player.single() else { + let Ok(gt) = player.single() else { return; }; let player_translation = (gt.translation().xz() * Vec2::new(1.0, -1.0)) / PARCEL_SIZE; let map_center = player_translation - Vec2::Y; + let parcel = preview + .preview_parcel + .unwrap_or_else(|| player_translation.floor().as_ivec2()); let scene = containing_scene - .get_parcel_oow(player) + .get_parcel_position(parcel_to_vec3(parcel)) .and_then(|scene| scenes.get(scene).ok()); - let parcel = player_translation.floor().as_ivec2(); let title = scene .map(|(context, _)| context.title.clone()) .unwrap_or("???".to_owned()); @@ -500,7 +503,7 @@ fn update_tracker( mut q: Query<(Ref, &DuiEntities)>, stats: Query<&SceneResourceLookup>, f: Res, - player: Query>, + player: Query<&GlobalTransform, With>, containing_scene: ContainingScene, dui: Res, mesh_handles: Query<(&Mesh3d, &ContainerEntity, &Visibility)>, @@ -514,6 +517,7 @@ fn update_tracker( materials: Res>, diagnostics: Res, images: Res>, + preview: Res, ) { let Ok((tracker, entities)) = q.single_mut() else { return; @@ -535,7 +539,10 @@ fn update_tracker( return; }; - let scenes = containing_scene.get_parcel(player); + let parcel = preview + .preview_parcel + .unwrap_or_else(|| vec3_to_parcel(player.translation())); + let scenes = containing_scene.get_parcel_position(parcel_to_vec3(parcel)); let Some(scene) = scenes.iter().next() else { return; }; diff --git a/src/lib/actual_web.rs b/src/lib/actual_web.rs index 80565d0c..2b791662 100644 --- a/src/lib/actual_web.rs +++ b/src/lib/actual_web.rs @@ -36,7 +36,10 @@ use image_processing::ImageProcessingPlugin; use imposters::DclImposterPlugin; use restricted_actions::{process_startup_scenes, RestrictedActionsPlugin}; use scene_material::SceneBoundPlugin; -use scene_runner::{initialize_scene::TestingData, vec3_to_parcel, OutOfWorld, SceneRunnerPlugin}; +use scene_runner::{ + initialize_scene::{parcels_in_range, ScenePointers, TestingData}, + vec3_to_parcel, OutOfWorld, SceneRunnerPlugin, +}; use av::AVPlayerPlugin; use avatar::AvatarPlugin; @@ -88,11 +91,13 @@ fn main_inner( }); let base_graphics = base_config.graphics.clone(); + let location = IVec2Arg::from_str(location) + .map(|l| l.0) + .unwrap_or(base_config.location); + let final_config = AppConfig { server: server.to_owned(), - location: IVec2Arg::from_str(location) - .map(|l| l.0) - .unwrap_or(base_config.location), + location, graphics: common::structs::GraphicsSettings { gpu_bytes_per_frame: rabpf, ..base_graphics @@ -252,6 +257,7 @@ fn main_inner( app.insert_resource(PreviewMode { server: is_preview.then_some(map_realm_name(&final_config.server)), is_preview, + preview_parcel: None, }); app.insert_resource(SceneParams::from_query_string(params, true)); @@ -332,6 +338,8 @@ fn main_inner( app.add_console_command::(scene_distance); app.add_console_command::(scene_threads); app.add_console_command::(set_fps); + app.add_console_command::(lock_preview); + app.add_console_command::(unlock_preview); app.add_systems( Update, @@ -458,6 +466,52 @@ fn scene_distance( } } +/// Locks the preview mode to the current parcel +#[derive(clap::Parser, ConsoleCommand)] +#[command(name = "/lock_preview")] +struct LockPreviewCommand; + +fn lock_preview( + mut input: ConsoleCommand, + mut preview_mode: ResMut, + focus: Single<&GlobalTransform, With>, + pointers: Res, +) { + if let Some(Ok(_command)) = input.take() { + let Some((parcel, _)) = parcels_in_range(&focus, 0.0, pointers.min(), pointers.max()).pop() + else { + unreachable!("Player should never be in a invalid parcel."); + }; + let Some(_current_scene) = pointers.get(parcel) else { + input.reply_failed(format!("failed to locked preview to parcel {}", parcel)); + return; + }; + preview_mode.preview_parcel = Some(parcel); + + input.reply_ok(format!("locked preview to parcel {}", parcel)); + } +} + +/// Unlocks the preview mode to the current parcel +#[derive(clap::Parser, ConsoleCommand)] +#[command(name = "/unlock_preview")] +struct UnlockPreviewCommand; + +fn unlock_preview( + mut input: ConsoleCommand, + mut preview_mode: ResMut, +) { + if let Some(Ok(_command)) = input.take() { + let parcel = preview_mode.preview_parcel.take(); + + if let Some(parcel) = parcel { + input.reply_ok(format!("unlocked preview to parcel {}", parcel)); + } else { + input.reply("Preview was not locked to a parcel."); + } + } +} + // set thread count #[derive(clap::Parser, ConsoleCommand)] #[command(name = "/scene_threads")] diff --git a/src/main.rs b/src/main.rs index 489c1420..699808e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,7 @@ use restricted_actions::{process_startup_scenes, RestrictedActionsPlugin}; use scene_material::SceneBoundPlugin; use scene_runner::{ automatic_testing::AutomaticTestingPlugin, - initialize_scene::{TestingData, PARCEL_SIZE}, + initialize_scene::{parcels_in_range, ScenePointers, TestingData, PARCEL_SIZE}, update_world::NoGltf, OutOfWorld, SceneRunnerPlugin, }; @@ -149,16 +149,18 @@ fn main() { Default::default() }); + let location = args + .value_from_str::<_, IVec2Arg>("--location") + .ok() + .map(|va| va.0) + .unwrap_or(base_config.location); + let final_config = AppConfig { server: args .value_from_str("--server") .ok() .unwrap_or(base_config.server), - location: args - .value_from_str::<_, IVec2Arg>("--location") - .ok() - .map(|va| va.0) - .unwrap_or(base_config.location), + location, previous_login: base_config.previous_login, graphics: GraphicsSettings { vsync: args @@ -446,6 +448,7 @@ fn main() { app.insert_resource(PreviewMode { server: is_preview.then_some(map_realm_name(&final_config.server)), is_preview, + preview_parcel: None, }); app.insert_resource(SceneLoadDistance { @@ -539,6 +542,8 @@ fn main() { app.add_console_command::(scene_distance); app.add_console_command::(scene_threads); app.add_console_command::(set_fps); + app.add_console_command::(lock_preview); + app.add_console_command::(unlock_preview); info!("Bevy-Explorer version {}", version); @@ -664,6 +669,52 @@ fn scene_distance( } } +/// Locks the preview mode to the current parcel +#[derive(clap::Parser, ConsoleCommand)] +#[command(name = "/lock_preview")] +struct LockPreviewCommand; + +fn lock_preview( + mut input: ConsoleCommand, + mut preview_mode: ResMut, + focus: Single<&GlobalTransform, With>, + pointers: Res, +) { + if let Some(Ok(_command)) = input.take() { + let Some((parcel, _)) = parcels_in_range(&focus, 0.0, pointers.min(), pointers.max()).pop() + else { + unreachable!("Player should never be in a invalid parcel."); + }; + let Some(_current_scene) = pointers.get(parcel) else { + input.reply_failed(format!("failed to locked preview to parcel {}", parcel)); + return; + }; + preview_mode.preview_parcel = Some(parcel); + + input.reply_ok(format!("locked preview to parcel {}", parcel)); + } +} + +/// Unlocks the preview mode to the current parcel +#[derive(clap::Parser, ConsoleCommand)] +#[command(name = "/unlock_preview")] +struct UnlockPreviewCommand; + +fn unlock_preview( + mut input: ConsoleCommand, + mut preview_mode: ResMut, +) { + if let Some(Ok(_command)) = input.take() { + let parcel = preview_mode.preview_parcel.take(); + + if let Some(parcel) = parcel { + input.reply_ok(format!("unlocked preview to parcel {}", parcel)); + } else { + input.reply("Preview was not locked to a parcel."); + } + } +} + // set thread count #[derive(clap::Parser, ConsoleCommand)] #[command(name = "/scene_threads")]