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) {