diff --git a/packages/oneclient_app/assets/alex.png b/packages/oneclient_app/assets/alex.png new file mode 100644 index 00000000..99af1933 Binary files /dev/null and b/packages/oneclient_app/assets/alex.png differ diff --git a/packages/oneclient_app/assets/oneclient.svg b/packages/oneclient_app/assets/oneclient.svg deleted file mode 100644 index 3f305562..00000000 --- a/packages/oneclient_app/assets/oneclient.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/oneclient_app/assets/steve.png b/packages/oneclient_app/assets/steve.png index b821a518..16077860 100644 Binary files a/packages/oneclient_app/assets/steve.png and b/packages/oneclient_app/assets/steve.png differ diff --git a/packages/oneclient_app/src/components/avatar.rs b/packages/oneclient_app/src/components/avatar.rs index 9f5bebf0..e1d5c488 100644 --- a/packages/oneclient_app/src/components/avatar.rs +++ b/packages/oneclient_app/src/components/avatar.rs @@ -2,10 +2,8 @@ use bytes::Bytes; use freya::elements::image::{ImageHandle, image}; use freya::engine::prelude::{Paint, SkData, SkImage, raster_n32_premul}; use freya::prelude::*; -use freya::query::QueryStateData; -use crate::AppAssets; -use crate::hooks::{use_cached_image, use_player_profile}; +use crate::hooks::use_player_skin; const AVATAR_SIZE: f32 = 32.; const FACE: f32 = 8.; @@ -37,32 +35,7 @@ impl ContainerSizeExt for Avatar {} impl Component for Avatar { fn render(&self) -> impl IntoElement { - let profile = use_player_profile(self.uuid.clone(), None::); - - let skin_url = match &*profile.read().state() { - QueryStateData::Settled { - res: Ok(profile), .. - } => profile.skin_url.clone(), - _ => None, - }; - - let skin_query = use_cached_image(skin_url.clone(), 256); - - let steve = use_memo(|| AppAssets::get_bytes("steve.png").unwrap_or_default()); - - let skin_bytes = { - let reader = skin_query.read(); - match (&skin_url, &*reader.state()) { - (Some(_), QueryStateData::Settled { res: Ok(bytes), .. }) - | ( - Some(_), - QueryStateData::Loading { - res: Some(Ok(bytes)), - }, - ) => bytes.clone(), - _ => steve.read().clone(), - } - }; + let (skin_bytes, _is_slim) = use_player_skin(self.uuid.clone()); let mut cache = use_state(|| None::<(usize, ImageHandle)>); let src_ptr = skin_bytes.as_ptr() as usize; diff --git a/packages/oneclient_app/src/components/player_model.rs b/packages/oneclient_app/src/components/player_model.rs index 91133b98..1f5054ab 100644 --- a/packages/oneclient_app/src/components/player_model.rs +++ b/packages/oneclient_app/src/components/player_model.rs @@ -5,12 +5,10 @@ use freya::engine::prelude::{ Data, Paint, SamplingOptions, Shader, SkData, SkImage, SkRect, TileMode, }; use freya::prelude::*; -use freya::query::QueryStateData; use skia_safe::RuntimeEffect; use skia_safe::runtime_effect::ChildPtr; -use crate::AppAssets; -use crate::hooks::{use_cached_image, use_player_profile}; +use crate::hooks::use_player_skin; const CAM_DIST: f32 = 80.0; const CAM_FOCAL: f32 = 1.85; @@ -81,31 +79,7 @@ impl PlayerModel { impl Component for PlayerModel { fn render(&self) -> impl IntoElement { - let profile = use_player_profile(self.uuid.clone(), None::); - - let skin_url = match &*profile.read().state() { - QueryStateData::Settled { - res: Ok(profile), .. - } => profile.skin_url.clone(), - _ => None, - }; - - let skin_query = use_cached_image(skin_url.clone(), 256); - let steve = use_memo(|| AppAssets::get_bytes("steve.png").unwrap_or_default()); - - let skin_bytes = { - let reader = skin_query.read(); - match (&skin_url, &*reader.state()) { - (Some(_), QueryStateData::Settled { res: Ok(bytes), .. }) - | ( - Some(_), - QueryStateData::Loading { - res: Some(Ok(bytes)), - }, - ) => bytes.clone(), - _ => steve.read().clone(), - } - }; + let (skin_bytes, is_slim) = use_player_skin(self.uuid.clone()); let mut cache = use_state(|| None::<(usize, Shader)>); let src_ptr = skin_bytes.as_ptr() as usize; @@ -151,15 +125,19 @@ impl Component for PlayerModel { let (Some(effect), Some(skin)) = (&effect, &skin_shader) else { return; }; + let (w, h) = (ctx.size.width, ctx.size.height); - if w <= 0.0 || h <= 0.0 { + + if w <= 0.0 || h <= 0.0 { return; } let (cur_yaw, cur_pitch) = (yaw(), pitch()); - let mut uniforms = Vec::with_capacity(24); - for value in [w, h, cur_yaw, cur_pitch, CAM_DIST, CAM_FOCAL] { + let slim = if is_slim { 1.0 } else { 0.0 }; + + let mut uniforms = Vec::with_capacity(28); + for value in [w, h, cur_yaw, cur_pitch, CAM_DIST, CAM_FOCAL, slim] { uniforms.extend_from_slice(&value.to_le_bytes()); } @@ -237,6 +215,7 @@ uniform float uYaw; uniform float uPitch; uniform float uDist; uniform float uFocal; +uniform float uSlim; uniform shader uSkin; void hitBox(float3 ro, float3 rd, float3 cmin, float3 cmax, float3 dims, float3 td, @@ -266,8 +245,8 @@ void hitBox(float3 ro, float3 rd, float3 cmin, float3 cmax, float3 dims, float3 nrm = float3(1.0, 0.0, 0.0); } } else if (tN == tmin.y) { - if (rd.y > 0.0) { // -Y face (bottom) - uv = float2(base.x + d + w + n.x * w, base.y + (1.0 - n.z) * d); + if (rd.y > 0.0) { // -Y face (bottom): 180deg from top, flip both axes + uv = float2(base.x + d + w + (1.0 - n.x) * w, base.y + (1.0 - n.z) * d); nrm = float3(0.0, -1.0, 0.0); } else { // +Y face (top) uv = float2(base.x + d + n.x * w, base.y + n.z * d); @@ -285,9 +264,9 @@ void hitBox(float3 ro, float3 rd, float3 cmin, float3 cmax, float3 dims, float3 half4 s = uSkin.eval(uv); - if (s.a < 0.5) { - return; - } + if (s.a < 0.5) { + return; + } float3 L = normalize(float3(0.4, 0.8, 0.6)); float diff = 0.65 + 0.35 * clamp(dot(nrm, L), 0.0, 1.0); @@ -311,14 +290,17 @@ half4 main(float2 fragCoord) { float best = 1e9; half4 col = half4(0.0); + float aw = uSlim > 0.5 ? 3.0 : 4.0; // arm width: slim = 3px, classic = 4px + float sw = aw + 0.5; // arm overlay (sleeve) width + hitBox(ro, rd, float3(-4.0, 8.0, -4.0), float3(4.0, 16.0, 4.0), float3(8.0, 8.0, 8.0), float3(8.0, 8.0, 8.0), float2(0.0, 0.0), best, col); // head hitBox(ro, rd, float3(-4.0, -4.0, -2.0), float3(4.0, 8.0, 2.0), float3(8.0, 12.0, 4.0), float3(8.0, 12.0, 4.0), float2(16.0, 16.0), best, col); // body - hitBox(ro, rd, float3(-8.0, -4.0, -2.0), float3(-4.0, 8.0, 2.0), - float3(4.0, 12.0, 4.0), float3(4.0, 12.0, 4.0), float2(40.0, 16.0), best, col); // right arm - hitBox(ro, rd, float3(4.0, -4.0, -2.0), float3(8.0, 8.0, 2.0), - float3(4.0, 12.0, 4.0), float3(4.0, 12.0, 4.0), float2(32.0, 48.0), best, col); // left arm + hitBox(ro, rd, float3(-4.0 - aw, -4.0, -2.0), float3(-4.0, 8.0, 2.0), + float3(aw, 12.0, 4.0), float3(aw, 12.0, 4.0), float2(40.0, 16.0), best, col); // right arm + hitBox(ro, rd, float3(4.0, -4.0, -2.0), float3(4.0 + aw, 8.0, 2.0), + float3(aw, 12.0, 4.0), float3(aw, 12.0, 4.0), float2(32.0, 48.0), best, col); // left arm hitBox(ro, rd, float3(-4.0, -16.0, -2.0), float3(0.0, -4.0, 2.0), float3(4.0, 12.0, 4.0), float3(4.0, 12.0, 4.0), float2(0.0, 16.0), best, col); // right leg hitBox(ro, rd, float3(0.0, -16.0, -2.0), float3(4.0, -4.0, 2.0), @@ -328,10 +310,10 @@ half4 main(float2 fragCoord) { float3(9.0, 9.0, 9.0), float3(8.0, 8.0, 8.0), float2(32.0, 0.0), best, col); // hat hitBox(ro, rd, float3(-4.25, -4.25, -2.25), float3(4.25, 8.25, 2.25), float3(8.5, 12.5, 4.5), float3(8.0, 12.0, 4.0), float2(16.0, 32.0), best, col); // jacket - hitBox(ro, rd, float3(-8.25, -4.25, -2.25), float3(-3.75, 8.25, 2.25), - float3(4.5, 12.5, 4.5), float3(4.0, 12.0, 4.0), float2(40.0, 32.0), best, col); // right sleeve - hitBox(ro, rd, float3(3.75, -4.25, -2.25), float3(8.25, 8.25, 2.25), - float3(4.5, 12.5, 4.5), float3(4.0, 12.0, 4.0), float2(48.0, 48.0), best, col); // left sleeve + hitBox(ro, rd, float3(-3.75 - sw, -4.25, -2.25), float3(-3.75, 8.25, 2.25), + float3(sw, 12.5, 4.5), float3(aw, 12.0, 4.0), float2(40.0, 32.0), best, col); // right sleeve + hitBox(ro, rd, float3(3.75, -4.25, -2.25), float3(3.75 + sw, 8.25, 2.25), + float3(sw, 12.5, 4.5), float3(aw, 12.0, 4.0), float2(48.0, 48.0), best, col); // left sleeve hitBox(ro, rd, float3(-4.25, -16.25, -2.25), float3(0.25, -3.75, 2.25), float3(4.5, 12.5, 4.5), float3(4.0, 12.0, 4.0), float2(0.0, 32.0), best, col); // right pant hitBox(ro, rd, float3(-0.25, -16.25, -2.25), float3(4.25, -3.75, 2.25), diff --git a/packages/oneclient_app/src/hooks/mod.rs b/packages/oneclient_app/src/hooks/mod.rs index 3d8e13c8..25d18ef3 100644 --- a/packages/oneclient_app/src/hooks/mod.rs +++ b/packages/oneclient_app/src/hooks/mod.rs @@ -6,52 +6,92 @@ mod view_state; pub use view_state::{PersistedView, use_view_state}; pub use active_cluster::{ - use_active_cluster_id, use_provide_active_cluster, ActiveClusterState, BrowserCompatState, - use_browser_compat, use_provide_browser_compat, LinkConfirmState, use_link_confirm, - use_provide_link_confirm, BrowserUiState, BrowserStateStore, use_browser_state_store, - use_provide_browser_state, OnboardingSelectionState, use_onboarding_selection, + ActiveClusterState, BrowserCompatState, BrowserStateStore, BrowserUiState, LinkConfirmState, + OnboardingSelectionState, use_active_cluster_id, use_browser_compat, use_browser_state_store, + use_link_confirm, use_onboarding_selection, use_provide_active_cluster, + use_provide_browser_compat, use_provide_browser_state, use_provide_link_confirm, use_provide_onboarding_selection, }; pub use dispatch::BridgeDispatch; pub use queries::{ - try_cluster_analytics, try_global_analytics, use_cluster_analytics, use_global_analytics, - ClusterAction, invalidate_cluster_queries, invalidate_profile_queries, use_cluster_mutation, - AddOfflineAccountKeys, RefreshAccountKeys, RemoveAccountKeys, SetDefaultAccountKeys, - UseRefreshAccount, UseRemoveAccount, UseSetDefaultAccount, accounts_have_microsoft, - login_code_already_handled, - mutation_error, mutation_is_pending, pick_version_metadata, try_account, try_accounts, - try_default_account, try_game_profile, use_account, use_accounts, use_add_microsoft_account, - use_add_offline_account, use_begin_microsoft_login, use_bundle_updates, - bundle_overrides_map, bundles_with_status_items, use_bundle_overrides, use_bundles_with_status, - ClusterBundles, OnboardingBundlesQuery, onboarding_bundles_items, use_onboarding_bundles, - use_cached_image, CachedImageQuery, use_changelog, changelog_error, changelog_groups, - changelog_is_loading, use_cluster_content, cluster_content_items, - invalidate_java_queries, java_runtimes, provider_versions, use_java_runtimes, - use_provider_versions, - content_type_for_slug, package_meta_batch, project_detail, search_items, search_pending, - search_total, use_package_meta_batch, use_package_project, use_package_search, - use_package_versions, version_list, versions_total, use_package_categories, category_list, - BROWSE_PAGE_SIZE, VERSIONS_PAGE_SIZE, - use_cluster_profile, use_cluster_settings, + AddOfflineAccountKeys, BROWSE_PAGE_SIZE, CachedImageQuery, ClusterAction, ClusterBundles, + ClusterLogsQuery, LogAction, LogContentQuery, OnboardingBundlesQuery, RefreshAccountKeys, + RemoveAccountKeys, ScreenshotAction, SetDefaultAccountKeys, UploadLogKeys, UploadLogMutation, + UseLogAction, UseRefreshAccount, UseRemoveAccount, UseScreenshotAction, UseSetDefaultAccount, + UseUploadLog, VERSIONS_PAGE_SIZE, accounts_have_microsoft, bundle_overrides_map, + bundles_with_status_items, category_list, changelog_error, changelog_groups, + changelog_is_loading, cluster_content_items, content_type_for_slug, invalidate_cluster_queries, + invalidate_java_queries, invalidate_logs_queries, invalidate_profile_queries, + invalidate_screenshots_queries, java_runtimes, loader_versions, login_code_already_handled, + mutation_error, mutation_is_pending, onboarding_bundles_items, package_meta_batch, + pick_version_metadata, project_detail, provider_versions, search_items, search_pending, + search_total, try_account, try_accounts, try_cluster_analytics, try_cluster_logs, + try_cluster_screenshots, try_default_account, try_game_profile, try_global_analytics, + try_log_content, use_account, use_accounts, use_add_microsoft_account, use_add_offline_account, + use_begin_microsoft_login, use_bundle_overrides, use_bundle_updates, use_bundles_with_status, + use_cached_image, use_changelog, use_cluster_analytics, use_cluster_content, use_cluster_logs, + use_cluster_mutation, use_cluster_profile, use_cluster_screenshots, use_cluster_settings, use_clusters, use_current_account, use_default_account, use_finish_microsoft_login, - use_game_profile, use_named_profiles, use_player_profile, use_refresh_account, - use_refresh_all_accounts, use_remove_account, use_set_default_account, use_version_metadata, - use_versions, use_loader_versions, loader_versions, - ClusterLogsQuery, LogAction, LogContentQuery, UploadLogKeys, UploadLogMutation, - UseLogAction, UseUploadLog, invalidate_logs_queries, - try_cluster_logs, try_log_content, use_cluster_logs, use_log_action, use_log_content, - use_upload_log, - ScreenshotAction, UseScreenshotAction, invalidate_screenshots_queries, - try_cluster_screenshots, use_cluster_screenshots, use_local_image, use_screenshot_action, + use_game_profile, use_global_analytics, use_java_runtimes, use_loader_versions, + use_local_image, use_log_action, use_log_content, use_named_profiles, use_onboarding_bundles, + use_package_categories, use_package_meta_batch, use_package_project, use_package_search, + use_package_versions, use_player_profile, use_provider_versions, use_refresh_account, + use_refresh_all_accounts, use_remove_account, use_screenshot_action, use_set_default_account, + use_upload_log, use_version_metadata, use_versions, version_list, versions_total, }; use crate::notifications::NotificationSnapshot; +use bytes::Bytes; use freya::prelude::*; +use freya::query::QueryStateData; + +use crate::AppAssets; + +pub fn use_player_skin(uuid: String) -> (Bytes, bool) { + let profile = use_player_profile(uuid.clone(), None::); + + let (skin_url, is_slim) = match &*profile.read().state() { + QueryStateData::Settled { + res: Ok(profile), .. + } => (profile.skin_url.clone(), profile.is_slim), + _ => (None, false), + }; + + let skin_query = use_cached_image(skin_url.clone(), 256); + + let steve = use_memo(|| AppAssets::get_bytes("steve.png").unwrap_or_default()); + let alex = use_memo(|| AppAssets::get_bytes("alex.png").unwrap_or_default()); + + // No custom skin: pick alex (slim) or steve (classic) from the UUID. + let default_slim = (java_string_hash(&uuid) & 1) == 1; + + let reader = skin_query.read(); + match (&skin_url, &*reader.state()) { + (Some(_), QueryStateData::Settled { res: Ok(bytes), .. }) + | ( + Some(_), + QueryStateData::Loading { + res: Some(Ok(bytes)), + }, + ) => (bytes.clone(), is_slim), + _ if default_slim => (alex.read().clone(), true), + _ => (steve.read().clone(), false), + } +} + +/// Java `String.hashCode()`: h = h*31 + c over UTF-16 units, wrapping i32. +fn java_string_hash(s: &str) -> i32 { + let mut h: i32 = 0; + for c in s.encode_utf16() { + h = h.wrapping_mul(31).wrapping_add(c as i32); + } + h +} use crate::bridge::{ - use_bridge_snapshot, BridgeSnapshot, ClustersSnapshot, GameSnapshot, JavaSnapshot, LauncherInit, - OneClientBridge, ProfilesSnapshot, SettingsSnapshot, + BridgeSnapshot, ClustersSnapshot, GameSnapshot, JavaSnapshot, LauncherInit, OneClientBridge, + ProfilesSnapshot, SettingsSnapshot, use_bridge_snapshot, }; pub fn use_provide_bridge(bridge: &OneClientBridge) {