diff --git a/src/components/chat-history/ChatHistoryView.tsx b/src/components/chat-history/ChatHistoryView.tsx index 4755a9f..c59fd92 100644 --- a/src/components/chat-history/ChatHistoryView.tsx +++ b/src/components/chat-history/ChatHistoryView.tsx @@ -1,4 +1,4 @@ -import { ILinkEventTracker } from '@nitrots/nitro-renderer'; +import { ILinkEventTracker, RoomObjectType } from '@nitrots/nitro-renderer'; import { FC, useEffect, useMemo, useRef, useState } from 'react'; import { AddEventLinkTracker, ChatEntryType, LocalizeText, RemoveLinkEventTracker } from '../../api'; import { Column, Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, Button } from '../../common'; @@ -87,6 +87,17 @@ export const ChatHistoryView: FC<{}> = props => scrollToBottom={true} rowRender={row => { + // Define styles where imageUrl should not be loaded + const stylesWithoutImage = [1, 2, 8, 28, 30, 32, 33, 34, 35, 36, 38]; + const shouldDisplayImage = !stylesWithoutImage.includes(row.style); + + // Determine image dimensions and positioning based on entityType + const isPet = row.entityType === RoomObjectType.PET; + const imageWidth = isPet ? '41px' : '90px'; // Double for users to account for transform: scale(0.5) + const imageHeight = isPet ? '54px' : '130px'; // Double for users to account for transform: scale(0.5) + const imageTop = isPet ? '-15px' : '-50px'; // Adjusted to shift user image up + const imageLeft = isPet ? '-9.25px' : '-31px'; // Adjusted to shift user image left + return ( {row.timestamp} @@ -96,8 +107,19 @@ export const ChatHistoryView: FC<{}> = props =>
}
- { row.imageUrl && (row.imageUrl.length > 0) && -
} + { shouldDisplayImage && row.imageUrl && (row.imageUrl.length > 0) && +
}
diff --git a/src/components/room/widgets/chat/ChatWidgetMessageView.tsx b/src/components/room/widgets/chat/ChatWidgetMessageView.tsx index 9854b8a..ec49d2b 100644 --- a/src/components/room/widgets/chat/ChatWidgetMessageView.tsx +++ b/src/components/room/widgets/chat/ChatWidgetMessageView.tsx @@ -1,4 +1,4 @@ -import { RoomChatSettings, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { RoomChatSettings, RoomObjectCategory, RoomObjectType } from '@nitrots/nitro-renderer'; import { FC, useEffect, useMemo, useRef, useState } from 'react'; import { ChatBubbleMessage, GetRoomEngine } from '../../../../api'; import { useOnClickChat } from '../../../../hooks'; @@ -17,7 +17,6 @@ export const ChatWidgetMessageView: FC = props => { const { onClickChat = null } = useOnClickChat(); const elementRef = useRef(); - // Memoize chat properties to prevent unnecessary re-renders const chatMemo = useMemo(() => ({ id: chat?.id, locationX: chat?.location?.x, @@ -31,8 +30,9 @@ export const ChatWidgetMessageView: FC = props => { imageUrl: chat?.imageUrl, type: chat?.type, roomId: chat?.roomId, - senderId: chat?.senderId - }), [chat?.id, chat?.location?.x, chat?.top, chat?.left, chat?.styleId, chat?.color, chat?.chatColours, chat?.username, chat?.formattedText, chat?.imageUrl, chat?.type, chat?.roomId, chat?.senderId]); + senderId: chat?.senderId, + userType: (chat as any)?.userType || 0 + }), [chat?.id, chat?.location?.x, chat?.top, chat?.left, chat?.styleId, chat?.color, chat?.chatColours, chat?.username, chat?.formattedText, chat?.imageUrl, chat?.type, chat?.roomId, chat?.senderId, (chat as any)?.userType]); const getBubbleWidth = useMemo(() => { switch (bubbleWidth) { @@ -60,10 +60,8 @@ export const ChatWidgetMessageView: FC = props => { let left = chat.left; let top = chat.top; - // Calculate content offset based on styleId to account for margin-left in .chat-content const contentOffset = chatMemo.styleId === 33 || chatMemo.styleId === 34 ? 35 : 27; - // Position the bubble above the user (chat.location.x), adjusting for content offset if (!left && !top) { left = chatMemo.locationX ? (chatMemo.locationX - (width / 2) + contentOffset) : contentOffset; top = element.parentElement ? (element.parentElement.offsetHeight - height) : 0; @@ -71,7 +69,6 @@ export const ChatWidgetMessageView: FC = props => { chat.top = top; } - // Apply position immediately element.style.position = 'absolute'; element.style.left = `${left}px`; element.style.top = `${top}px`; @@ -90,6 +87,16 @@ export const ChatWidgetMessageView: FC = props => { setIsVisible(true); }, [chat, isReady, isVisible, makeRoom]); + const isPet = chatMemo.userType === RoomObjectType.PET; + const imageWidth = isPet ? '41px' : '90px'; + const imageHeight = isPet ? '54px' : '130px'; + const imageTop = isPet ? '-15px' : '-50px'; + const imageLeft = isPet ? '-9.25px' : '-31px'; + + // Define styles where imageUrl should not be loaded + const stylesWithoutImage = [1, 2, 8, 28, 30, 32, 33, 34, 35, 36, 38]; + const shouldDisplayImage = !stylesWithoutImage.includes(chatMemo.styleId); + return (
= props => {
)}
-
- {chatMemo.imageUrl && chatMemo.imageUrl.length > 0 && ( -
- )} -
+ {shouldDisplayImage && chatMemo.imageUrl && typeof chatMemo.imageUrl === 'string' && chatMemo.imageUrl.length > 0 && ( +
+
console.log(`Failed to load image for Chat ID ${chatMemo.id}: ${chatMemo.imageUrl}`)} + /> +
+ )}
= props => { const { chatMessages = [], setChatMessages = null, chatSettings = null, getScrollSpeed = 6000 } = useChatWidget(); const elementRef = useRef(); const lastTickTimeRef = useRef(Date.now()); - const scrollAmountPerTick = 15; // Pixels moved per tick - const workerRef = useRef(null); // Preserve worker across re-renders + const scrollAmountPerTick = 15; + const workerRef = useRef(null); const removeHiddenChats = useCallback(() => { setChatMessages(prevValue => { @@ -119,16 +119,14 @@ export const ChatWidgetView: FC<{}> = props => { workerRef.current = null; } }; - }, [moveAllChatsUp]); // Remove getScrollSpeed from deps to prevent re-creation + }, [moveAllChatsUp]); - // Update worker interval if getScrollSpeed changes useEffect(() => { if (workerRef.current) { workerRef.current.postMessage({ action: 'UPDATE', content: getScrollSpeed }); } }, [getScrollSpeed]); - // Handle new chats and ensure immediate scrolling useEffect(() => { if (!chatMessages.length) return; diff --git a/src/hooks/rooms/widgets/useChatWidget.ts b/src/hooks/rooms/widgets/useChatWidget.ts index 5d67025..61a5348 100644 --- a/src/hooks/rooms/widgets/useChatWidget.ts +++ b/src/hooks/rooms/widgets/useChatWidget.ts @@ -1,4 +1,4 @@ -import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetGuestRoomResultEvent, NitroPoint, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, RoomUserData, SystemChatStyleEnum, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; +import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetGuestRoomResultEvent, NitroPoint, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, SystemChatStyleEnum, TextureUtils, Vector3d } from '@nitrots/nitro-renderer'; import { useEffect, useMemo, useRef, useState } from 'react'; import { ChatBubbleMessage, ChatEntryType, ChatHistoryCurrentDate, GetAvatarRenderManager, GetConfiguration, GetRoomEngine, GetRoomObjectScreenLocation, IRoomChatSettings, LocalizeText, PlaySound, RoomChatFormatter } from '../../../api'; import { useMessageEvent, useRoomEngineEvent, useRoomSessionManagerEvent } from '../../events'; @@ -50,13 +50,14 @@ const useChatWidgetState = () => }); if (!avatarImage) { + resolve(null); return; } avatarImage.getCroppedImage(AvatarSetType.HEAD).then(image => { - if (!image || !image.src) { avatarImage.dispose(); + resolve(null); return; } @@ -80,43 +81,57 @@ const useChatWidgetState = () => resolve(image.src); }).catch(error => { avatarImage.dispose(); + resolve(null); }); }); }; - const getUserImage = (figure: string, username: string): string | null => { + const getUserImage = async (figure: string, username: string): Promise => { let existing = avatarImageCache.get(figure); if (!existing) { - setFigureImage(figure, username).then(src => { + const src = await setFigureImage(figure, username); + if (src) { avatarImageCache.set(figure, src); - }); - return; + return src; + } + return null; } return existing; }; - const getPetImage = (figure: string, direction: number, _arg_3: boolean, scale: number = 64, posture: string = null) => - { - let existing = petImageCache.get((figure + posture)); + const getPetImage = async (figure: string, direction: number, _arg_3: boolean, scale: number = 64, posture: string = null): Promise => { + const cacheKey = (figure + posture); + let existing = petImageCache.get(cacheKey); - if(existing) return existing; + if (existing) return existing; const figureData = new PetFigureData(figure); const typeId = figureData.typeId; - const image = GetRoomEngine().getRoomObjectPetImage(typeId, figureData.paletteId, figureData.color, new Vector3d((direction * 45)), scale, null, false, 0, figureData.customParts, posture); - if(image) - { - existing = TextureUtils.generateImageUrl(image.data); - petImageCache.set((figure + posture), existing); + let image = GetRoomEngine().getRoomObjectPetImage(typeId, figureData.paletteId, figureData.color, new Vector3d((direction * 45)), scale, null, false, 0, figureData.customParts, posture); + + if (!image) { + image = GetRoomEngine().getRoomObjectPetImage(typeId, figureData.paletteId, figureData.color, new Vector3d(90), 64, null, false, 0, figureData.customParts, null); } - return existing; + if (image) { + const imageUrl = await TextureUtils.generateImageUrl(image.data); + if (imageUrl) { + petImageCache.set(cacheKey, imageUrl); + return imageUrl; + } else { + petImageCache.delete(cacheKey); + } + } else { + petImageCache.delete(cacheKey); + } + + return null; }; - useRoomSessionManagerEvent(RoomSessionChatEvent.CHAT_EVENT, event => + useRoomSessionManagerEvent(RoomSessionChatEvent.CHAT_EVENT, async event => { const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, event.objectId, RoomObjectCategory.UNIT); const bubbleLocation = roomObject ? GetRoomObjectScreenLocation(roomSession.roomId, roomObject?.id, RoomObjectCategory.UNIT) : new NitroPoint(); @@ -135,18 +150,17 @@ const useChatWidgetState = () => if(userData) { userType = userData.type; - const figure = userData.figure; switch(userType) { case RoomObjectType.PET: - imageUrl = getPetImage(figure, 2, true, 64, roomObject.model.getValue(RoomObjectVariable.FIGURE_POSTURE)); + imageUrl = await getPetImage(figure, 2, true, 64, roomObject.model.getValue(RoomObjectVariable.FIGURE_POSTURE)); petType = new PetFigureData(figure).typeId; chatColours = "black"; break; case RoomObjectType.USER: - imageUrl = getUserImage(figure, userData.name); + imageUrl = await getUserImage(figure, userData.name); break; case RoomObjectType.RENTABLE_BOT: case RoomObjectType.BOT: @@ -163,36 +177,27 @@ const useChatWidgetState = () => { case RoomSessionChatEvent.CHAT_TYPE_RESPECT: text = LocalizeText('widgets.chatbubble.respect', [ 'username' ], [ username ]); - if(GetConfiguration('respect.options')['enabled']) PlaySound(GetConfiguration('respect.options')['sound']); - break; case RoomSessionChatEvent.CHAT_TYPE_PETREVIVE: case RoomSessionChatEvent.CHAT_TYPE_PET_REBREED_FERTILIZE: case RoomSessionChatEvent.CHAT_TYPE_PET_SPEED_FERTILIZE: { let textKey = 'widget.chatbubble.petrevived'; - if(chatType === RoomSessionChatEvent.CHAT_TYPE_PET_REBREED_FERTILIZE) { textKey = 'widget.chatbubble.petrefertilized;'; } - else if(chatType === RoomSessionChatEvent.CHAT_TYPE_PET_SPEED_FERTILIZE) { textKey = 'widget.chatbubble.petspeedfertilized'; } - let targetUserName: string = null; - const newRoomObject = GetRoomEngine().getRoomObject(roomSession.roomId, event.extraParam, RoomObjectCategory.UNIT); - if(newRoomObject) { const newUserData = roomSession.userDataManager.getUserDataByIndex(roomObject.id); - if(newUserData) targetUserName = newUserData.name; } - text = LocalizeText(textKey, [ 'petName', 'userName' ], [ username, targetUserName ]); break; } @@ -209,7 +214,6 @@ const useChatWidgetState = () => const hours = ((event.extraParam > 0) ? Math.floor((event.extraParam / 3600)) : 0).toString(); const minutes = ((event.extraParam > 0) ? Math.floor((event.extraParam % 3600) / 60) : 0).toString(); const seconds = (event.extraParam % 60).toString(); - text = LocalizeText('widget.chatbubble.mutetime', [ 'hours', 'minutes', 'seconds' ], [ hours, minutes, seconds ]); break; } @@ -233,6 +237,9 @@ const useChatWidgetState = () => chatColours ); + // Add userType to ChatBubbleMessage as a dynamic property + (chatMessage as any).userType = userType; + setChatMessages(prevValue => [ ...prevValue, chatMessage ]); addChatEntry({ id: -1, webId: userData.webID, entityId: userData.roomIndex, name: username, imageUrl, style: styleId, chatType: chatType, entityType: userData.type, message: formattedText, timestamp: ChatHistoryCurrentDate(), type: ChatEntryType.TYPE_CHAT, roomId: roomSession.roomId, color, chatColours }); });