From dc388d061cc28380ae8f1a0f44e59cf2b4cf8bc9 Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 25 Mar 2026 17:28:33 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=86=99=20add=20check=20for=20pets=20w?= =?UTF-8?q?hen=20sellable=20is=20enabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../avatar/IAvatarEditorCategoryPartItem.ts | 1 + .../AvatarEditorFigureSetItemView.tsx | 13 ++++++++---- src/css/index.css | 21 +++++++++++++++++++ src/hooks/avatar-editor/useAvatarEditor.ts | 8 +++++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/api/avatar/IAvatarEditorCategoryPartItem.ts b/src/api/avatar/IAvatarEditorCategoryPartItem.ts index d1cbc0d..46f6991 100644 --- a/src/api/avatar/IAvatarEditorCategoryPartItem.ts +++ b/src/api/avatar/IAvatarEditorCategoryPartItem.ts @@ -7,4 +7,5 @@ export interface IAvatarEditorCategoryPartItem usesColor?: boolean; maxPaletteCount?: number; isClear?: boolean; + isSellableNotOwned?: boolean; } diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx index fb33358..b021c5e 100644 --- a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx @@ -20,6 +20,7 @@ export const AvatarEditorFigureSetItemView: FC<{ const clubLevel = partItem.partSet?.clubLevel ?? 0; const isHC = !GetConfigurationValue('hc.disabled', false) && (clubLevel > 0); const isLocked = isHC && (GetClubMemberLevel() < clubLevel); + const isSellableNotOwned = partItem.isSellableNotOwned ?? false; useEffect(() => { @@ -39,22 +40,26 @@ export const AvatarEditorFigureSetItemView: FC<{ } else { - url = await AvatarEditorThumbnailsHelper.build(setType, partItem, partItem.usesColor, selectedColorParts[setType] ?? null, partIsLocked); + url = await AvatarEditorThumbnailsHelper.build(setType, partItem, partItem.usesColor, selectedColorParts[setType] ?? null, partIsLocked || isSellableNotOwned); } if(url && url.length) setAssetUrl(url); }; loadImage(); - }, [ setType, partItem, selectedColorParts, getFigureStringWithFace ]); + }, [ setType, partItem, selectedColorParts, getFigureStringWithFace, isSellableNotOwned ]); if(!partItem) return null; return ( - + { !partItem.isClear && isHC && } { partItem.isClear && } - { !partItem.isClear && partItem.partSet.isSellable && } + { !partItem.isClear && partItem.partSet.isSellable && !isSellableNotOwned && } + { isSellableNotOwned && +
+ +
}
); }; diff --git a/src/css/index.css b/src/css/index.css index 0e0dc01..4365664 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -1232,6 +1232,27 @@ body { overflow: hidden !important; } +.pet-sellable-locked { + opacity: 0.5; + cursor: not-allowed !important; + position: relative; +} + +.pet-sellable-badge { + position: absolute; + bottom: 2px; + right: 2px; + display: flex; + align-items: center; + gap: 1px; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 3px; + padding: 1px 3px; + font-size: 9px; + color: #ffd700; + font-weight: bold; +} + .pet-remove-btn { background: none; border: none; diff --git a/src/hooks/avatar-editor/useAvatarEditor.ts b/src/hooks/avatar-editor/useAvatarEditor.ts index ae401de..420b9f9 100644 --- a/src/hooks/avatar-editor/useAvatarEditor.ts +++ b/src/hooks/avatar-editor/useAvatarEditor.ts @@ -65,6 +65,8 @@ const useAvatarEditorState = () => if(GetClubMemberLevel() < partItem.partSet.clubLevel) return; + if(partItem.isSellableNotOwned) return; + setMaxPaletteCount(partItem.maxPaletteCount || 1); selectPart(setType, partId); @@ -271,13 +273,15 @@ const useAvatarEditorState = () => if(!partSet || !partSet.isSelectable || ((partSet.gender !== gender) && (partSet.gender !== AvatarFigurePartType.UNISEX))) continue; - if(partSet.isSellable && figureSetIds.indexOf(partSet.id) === -1) continue; + const isSellableNotOwned = partSet.isSellable && figureSetIds.indexOf(partSet.id) === -1; + + if(isSellableNotOwned && setType !== AvatarFigurePartType.PET) continue; let maxPaletteCount = 0; for(const part of partSet.parts) maxPaletteCount = Math.max(maxPaletteCount, part.colorLayerIndex); - partItems.push({ id: partSet.id, partSet, usesColor, maxPaletteCount }); + partItems.push({ id: partSet.id, partSet, usesColor, maxPaletteCount, isSellableNotOwned }); } partItems.sort(AvatarEditorPartSorter(false)); From 0ee4455d8d9ef3fc6331a3fecc74e1d80239b5eb Mon Sep 17 00:00:00 2001 From: duckietm Date: Thu, 26 Mar 2026 13:31:12 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=86=99=20SSO=20failure=20detection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 30 +++++++++++++++++++--- src/components/loading/LoadingView.tsx | 16 ++++++++---- src/components/reconnect/ReconnectView.tsx | 24 +++++++---------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c97d6fa..9608aa8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,30 @@ -import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer'; -import { FC, useEffect, useState } from 'react'; +import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroEventType, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useState } from 'react'; import { GetUIVersion } from './api'; import { Base } from './common'; import { LoadingView } from './components/loading/LoadingView'; import { MainView } from './components/MainView'; import { ReconnectView } from './components/reconnect/ReconnectView'; -import { useMessageEvent } from './hooks'; +import { useMessageEvent, useNitroEvent } from './hooks'; NitroVersion.UI_VERSION = GetUIVersion(); export const App: FC<{}> = props => { const [ isReady, setIsReady ] = useState(false); + const [ errorMessage, setErrorMessage ] = useState(''); + const [ homeUrl, setHomeUrl ] = useState(''); + + const showSessionExpired = useCallback(() => + { + const baseUrl = window.location.origin + '/'; + setHomeUrl(baseUrl); + setErrorMessage('Your session has expired.\nPlease log in again to enter the hotel.'); + setIsReady(false); + }, []); + + // Listen for socket closed events (code 1000 "Bye" - server rejected SSO) + useNitroEvent(NitroEventType.SOCKET_CLOSED, showSessionExpired); useMessageEvent(LoadGameUrlEvent, event => { @@ -30,6 +43,14 @@ export const App: FC<{}> = props => { if(!window.NitroConfig) throw new Error('NitroConfig is not defined!'); + const ssoTicket = window.NitroConfig['sso.ticket']; + + if(!ssoTicket || ssoTicket === '') + { + showSessionExpired(); + return; + } + const renderer = await PrepareRenderer({ width: Math.floor(width), height: Math.floor(height), @@ -83,6 +104,7 @@ export const App: FC<{}> = props => catch(err) { NitroLogger.error(err); + showSessionExpired(); } }; @@ -92,7 +114,7 @@ export const App: FC<{}> = props => return ( { !isReady && - } + 0 } message={ errorMessage } homeUrl={ homeUrl } /> } { isReady && } diff --git a/src/components/loading/LoadingView.tsx b/src/components/loading/LoadingView.tsx index a9dc9bc..c8bb131 100644 --- a/src/components/loading/LoadingView.tsx +++ b/src/components/loading/LoadingView.tsx @@ -4,10 +4,11 @@ import { Base, Column, Text } from '../../common'; interface LoadingViewProps { isError?: boolean; message?: string; + homeUrl?: string; } export const LoadingView: FC = props => { - const { isError = false, message = '' } = props; + const { isError = false, message = '', homeUrl = '' } = props; return ( @@ -19,11 +20,16 @@ export const LoadingView: FC = props => { { isError && (message && message.length) ? - Something went wrong while loading - - { message } - + + { homeUrl && + + Back to Hotel + + } : diff --git a/src/components/reconnect/ReconnectView.tsx b/src/components/reconnect/ReconnectView.tsx index 4c13094..af8e896 100644 --- a/src/components/reconnect/ReconnectView.tsx +++ b/src/components/reconnect/ReconnectView.tsx @@ -48,14 +48,14 @@ export const ReconnectView: FC<{}> = props => const handleReload = useCallback(() => { - window.location.reload(); + window.location.href = window.location.origin + '/'; }, []); const handleGoHome = useCallback(() => { sessionStorage.removeItem('nitro.session.lastRoomId'); sessionStorage.removeItem('nitro.session.lastRoomPassword'); - window.location.reload(); + window.location.href = window.location.origin + '/'; }, []); if(!isReconnecting && !hasFailed) return null; @@ -92,24 +92,18 @@ export const ReconnectView: FC<{}> = props => <> - Connection failed + Session expired - Unable to reconnect to the server after multiple attempts. + Your session has expired. Please log in again to enter the hotel. - - Reload Page - - - Go to Home - + Back to Hotel + ) }