import { CrackableDataType, CreateLinkEvent, FurnitureFloorUpdateEvent, GetRoomEngine, GetSoundManager, GroupInformationComposer, GroupInformationEvent, NowPlayingEvent, RoomControllerLevel, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomWidgetEnumItemExtradataParameter, RoomWidgetFurniInfoUsagePolicyEnum, SetObjectDataMessageComposer, SongInfoReceivedEvent, StringDataType, UpdateFurniturePositionComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useState } from 'react'; import { FaCrosshairs, FaRulerVertical, FaTimes } from 'react-icons/fa'; import { GrFormNextLink, GrRotateLeft, GrRotateRight } from 'react-icons/gr'; import { AvatarInfoFurni, GetGroupInformation, LocalizeText, SendMessageComposer } from '../../../../../api'; import { Button, Column, Flex, LayoutBadgeImageView, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomObjectImageView, Text, UserProfileIconView } from '../../../../../common'; import { useMessageEvent, useNitroEvent, useRoom, useWiredTools } from '../../../../../hooks'; import { NitroInput } from '../../../../../layout'; interface InfoStandWidgetFurniViewProps { avatarInfo: AvatarInfoFurni; onClose: () => void; } const PICKUP_MODE_NONE: number = 0; const PICKUP_MODE_EJECT: number = 1; const PICKUP_MODE_FULL: number = 2; export const InfoStandWidgetFurniView: FC = props => { const { avatarInfo = null, onClose = null } = props; const { roomSession = null } = useRoom(); const { openInspectionForFurni, showInspectButton } = useWiredTools(); const [ pickupMode, setPickupMode ] = useState(0); const [ canMove, setCanMove ] = useState(false); const [ canRotate, setCanRotate ] = useState(false); const [ canUse, setCanUse ] = useState(false); const [ furniKeys, setFurniKeys ] = useState([]); const [ furniValues, setFurniValues ] = useState([]); const [ customKeys, setCustomKeys ] = useState([]); const [ customValues, setCustomValues ] = useState([]); const [ isCrackable, setIsCrackable ] = useState(false); const [ crackableHits, setCrackableHits ] = useState(0); const [ crackableTarget, setCrackableTarget ] = useState(0); const [ godMode, setGodMode ] = useState(false); const [ canSeeFurniId, setCanSeeFurniId ] = useState(false); const [ groupName, setGroupName ] = useState(null); const [ isJukeBox, setIsJukeBox ] = useState(false); const [ isSongDisk, setIsSongDisk ] = useState(false); const [ songId, setSongId ] = useState(-1); const [ songName, setSongName ] = useState(''); const [ songCreator, setSongCreator ] = useState(''); const [ itemLocation, setItemLocation ] = useState<{ x: number; y: number; z: number }>({ x: -1, y: -1, z: -1 }); const [ dropdownOpen, setDropdownOpen ] = useState(sessionStorage.getItem('dropdownOpen') === 'true'); const [ furniLocationZ, setFurniLocationZ ] = useState(null); const sendUpdate = useCallback((deltaX: number, deltaY: number, newZ: number = 0, deltaDirection: number = 0) => { if(!avatarInfo) return; const roomId = GetRoomEngine().activeRoomId; const roomObject = GetRoomEngine().getRoomObject(roomId, avatarInfo.id, avatarInfo.category); if(!roomObject) return; const newX = roomObject.getLocation().x + deltaX; const newY = roomObject.getLocation().y + deltaY; const currentDirection = roomObject.getDirection().x; const newDirection = (deltaDirection !== 0) ? getValidRoomObjectDirection(roomObject, deltaDirection > 0) / 45 : currentDirection / 45; SendMessageComposer(new UpdateFurniturePositionComposer(avatarInfo.id, newX, newY, Math.round(newZ * 10000), newDirection)); }, [ avatarInfo ]); function getValidRoomObjectDirection(roomObject: any, isPositive: boolean) { if(!roomObject || !roomObject.model) return 0; let allowedDirections: number[] = []; if(roomObject.type === 'monster_plant') { allowedDirections = roomObject.model.getValue('pet_allowed_directions'); } else { allowedDirections = roomObject.model.getValue('furniture_allowed_directions'); } let direction = roomObject.getDirection().x; if(allowedDirections && allowedDirections.length) { let index = allowedDirections.indexOf(direction); if(index < 0) { index = 0; for(let i = 0; i < allowedDirections.length; i++) { if(direction <= allowedDirections[i]) break; index++; } index = index % allowedDirections.length; } if(isPositive) { index = (index + 1) % allowedDirections.length; } else { index = (index - 1 + allowedDirections.length) % allowedDirections.length; } direction = allowedDirections[index]; } return direction; } const handleHeightChange = useCallback((event: React.ChangeEvent) => { let newZ = parseFloat(event.target.value); if(isNaN(newZ) || newZ < 0) newZ = 0; else if(newZ > 40) newZ = 40; setFurniLocationZ(newZ); sendUpdate(0, 0, newZ, 0); }, [ sendUpdate ]); const handleHeightBlur = useCallback((event: React.FocusEvent) => { let newZ = parseFloat(event.target.value); if(isNaN(newZ) || newZ < 0) newZ = 0; else if(newZ > 40) newZ = 40; newZ = parseFloat(newZ.toFixed(4)); setFurniLocationZ(newZ); sendUpdate(0, 0, newZ, 0); }, [ sendUpdate ]); const adjustHeight = useCallback((amount: number) => { let newZ = (furniLocationZ ?? 0) + amount; if(newZ < 0) newZ = 0; else if(newZ > 40) newZ = 40; newZ = parseFloat(newZ.toFixed(4)); setFurniLocationZ(newZ); sendUpdate(0, 0, newZ, 0); }, [ furniLocationZ, sendUpdate ]); useNitroEvent(NowPlayingEvent.NPE_SONG_CHANGED, event => { setSongId(event.id); }, (isJukeBox || isSongDisk)); useNitroEvent(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, event => { if(event.id !== songId) return; const songInfo = GetSoundManager().musicController.getSongInfo(event.id); if(!songInfo) return; setSongName(songInfo.name); setSongCreator(songInfo.creator); }, (isJukeBox || isSongDisk)); useEffect(() => { let pickupMode = PICKUP_MODE_NONE; let canMove = false; let canRotate = false; let canUse = false; let furniKeyss: string[] = []; let furniValuess: string[] = []; let customKeyss: string[] = []; let customValuess: string[] = []; let isCrackable = false; let crackableHits = 0; let crackableTarget = 0; let godMode = false; let canSeeFurniId = false; let furniIsJukebox = false; let furniIsSongDisk = false; let furniSongId = -1; const roomObjForLocation = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, avatarInfo.isWallItem ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR); const location = roomObjForLocation?.getLocation(); if(location) { setItemLocation({ x: location.x, y: location.y, z: location.z }); setFurniLocationZ(location.z); } const isValidController = (avatarInfo.roomControllerLevel >= RoomControllerLevel.GUEST); if(isValidController || avatarInfo.isOwner || avatarInfo.isRoomOwner || avatarInfo.isAnyRoomController) { canMove = true; canRotate = !avatarInfo.isWallItem; if(avatarInfo.roomControllerLevel >= RoomControllerLevel.MODERATOR) godMode = true; } if(avatarInfo.isAnyRoomController) { canSeeFurniId = true; } if((((avatarInfo.usagePolicy === RoomWidgetFurniInfoUsagePolicyEnum.EVERYBODY) || ((avatarInfo.usagePolicy === RoomWidgetFurniInfoUsagePolicyEnum.CONTROLLER) && isValidController)) || ((avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX) && isValidController)) || ((avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.USABLE_PRODUCT) && isValidController)) canUse = true; if(avatarInfo.extraParam) { if(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.CRACKABLE_FURNI) { const stuffData = (avatarInfo.stuffData as CrackableDataType); canUse = true; isCrackable = true; crackableHits = stuffData?.hits ?? 0; crackableTarget = stuffData?.target ?? 0; } else if(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX) { const playlist = GetSoundManager().musicController.getRoomItemPlaylist(); if(playlist) { furniSongId = playlist.currentSongId; } furniIsJukebox = true; } else if(avatarInfo.extraParam.indexOf(RoomWidgetEnumItemExtradataParameter.SONGDISK) === 0) { furniSongId = parseInt(avatarInfo.extraParam.substr(RoomWidgetEnumItemExtradataParameter.SONGDISK.length)); furniIsSongDisk = true; } if(godMode) { const extraParam = avatarInfo.extraParam.substr(RoomWidgetEnumItemExtradataParameter.BRANDING_OPTIONS.length); if(extraParam) { const parts = extraParam.split('\t'); for(const part of parts) { const value = part.split('='); if(value && (value.length === 2)) { furniKeyss.push(value[0]); furniValuess.push(value[1]); } } } } } if(godMode) { const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, (avatarInfo.isWallItem) ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR); if(roomObject) { const customVariables = roomObject.model.getValue(RoomObjectVariable.FURNITURE_CUSTOM_VARIABLES); const furnitureData = roomObject.model.getValue<{ [index: string]: string }>(RoomObjectVariable.FURNITURE_DATA); if(customVariables && customVariables.length) { for(const customVariable of customVariables) { customKeyss.push(customVariable); customValuess.push((furnitureData[customVariable]) || ''); } } } } if(avatarInfo.isOwner || avatarInfo.isAnyRoomController) pickupMode = PICKUP_MODE_FULL; else if(avatarInfo.isRoomOwner || (avatarInfo.roomControllerLevel >= RoomControllerLevel.GUILD_ADMIN)) pickupMode = PICKUP_MODE_EJECT; if(avatarInfo.isStickie) pickupMode = PICKUP_MODE_NONE; setPickupMode(pickupMode); setCanMove(canMove); setCanRotate(canRotate); setCanUse(canUse); setFurniKeys(furniKeyss); setFurniValues(furniValuess); setCustomKeys(customKeyss); setCustomValues(customValuess); setIsCrackable(isCrackable); setCrackableHits(crackableHits); setCrackableTarget(crackableTarget); setGodMode(godMode); setCanSeeFurniId(canSeeFurniId); setGroupName(null); setIsJukeBox(furniIsJukebox); setIsSongDisk(furniIsSongDisk); setSongId(furniSongId); if(avatarInfo.groupId) SendMessageComposer(new GroupInformationComposer(avatarInfo.groupId, false)); }, [ roomSession, avatarInfo ]); useMessageEvent(GroupInformationEvent, event => { const parser = event.getParser(); if(!avatarInfo || avatarInfo.groupId !== parser.id || parser.flag) return; if(groupName) setGroupName(null); setGroupName(parser.title); }); useMessageEvent(FurnitureFloorUpdateEvent, event => { const parser = event.getParser(); const item = parser.item; if(!avatarInfo || item.itemId !== avatarInfo.id) return; setItemLocation({ x: item.x, y: item.y, z: item.z }); setFurniLocationZ(item.z); }); useEffect(() => { const songInfo = GetSoundManager().musicController.getSongInfo(songId); setSongName(songInfo?.name ?? ''); setSongCreator(songInfo?.creator ?? ''); }, [ songId ]); const onFurniSettingChange = useCallback((index: number, value: string) => { const clone = Array.from(furniValues); clone[index] = value; setFurniValues(clone); }, [ furniValues ]); const onCustomVariableChange = useCallback((index: number, value: string) => { const clone = Array.from(customValues); clone[index] = value; setCustomValues(clone); }, [ customValues ]); const getFurniSettingsAsString = useCallback(() => { if(furniKeys.length === 0 || furniValues.length === 0) return ''; let data = ''; let i = 0; while(i < furniKeys.length) { const key = furniKeys[i]; const value = furniValues[i]; data = (data + (key + '=' + value + '\t')); i++; } return data; }, [ furniKeys, furniValues ]); const processButtonAction = useCallback((action: string) => { if(!action || (action === '')) return; let objectData: string = null; switch(action) { case 'buy_one': CreateLinkEvent(`catalog/open/offerId/${ avatarInfo.purchaseOfferId }`); return; case 'move': GetRoomEngine().processRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_MOVE); break; case 'rotate': GetRoomEngine().processRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE); break; case 'pickup': if(pickupMode === PICKUP_MODE_FULL) { GetRoomEngine().processRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_PICKUP); } else { GetRoomEngine().processRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_EJECT); } break; case 'use': GetRoomEngine().useRoomObject(avatarInfo.id, avatarInfo.category); break; case 'save_branding_configuration': { const mapData = new Map(); const dataParts = getFurniSettingsAsString().split('\t'); if(dataParts) { for(const part of dataParts) { const [ key, value ] = part.split('=', 2); mapData.set(key, value); } } GetRoomEngine().modifyRoomObjectDataWithMap(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_SAVE_STUFF_DATA, mapData); break; } case 'save_custom_variables': { const map = new Map(); for(let i = 0; i < customKeys.length; i++) { const key = customKeys[i]; const value = customValues[i]; if((key && key.length) && (value && value.length)) map.set(key, value); } SendMessageComposer(new SetObjectDataMessageComposer(avatarInfo.id, map)); break; } } }, [ avatarInfo, pickupMode, customKeys, customValues, getFurniSettingsAsString ]); const getGroupBadgeCode = useCallback(() => { const stringDataType = (avatarInfo.stuffData as StringDataType); if(!stringDataType || !(stringDataType instanceof StringDataType)) return null; return stringDataType.getValue(2); }, [ avatarInfo ]); if(!avatarInfo) return null; return (
{ avatarInfo.name }
{ avatarInfo.stuffData.isUnique &&
} { (avatarInfo.stuffData.rarityLevel > -1) &&
}

{ avatarInfo.description }
{ LocalizeText('furni.owner', [ 'name' ], [ avatarInfo.ownerName ]) }
{ (avatarInfo.purchaseOfferId > 0) && processButtonAction('buy_one') }> { LocalizeText('infostand.button.buy') } }
{ (isJukeBox || isSongDisk) &&

{ (songId === -1) && { LocalizeText('infostand.jukebox.text.not.playing') } } { !!songName.length &&
{ songName }
} { !!songCreator.length &&
{ songCreator }
}
}
{ isCrackable && <>
{ LocalizeText('infostand.crackable_furni.hits_remaining', [ 'hits', 'target' ], [ (crackableHits ?? 0).toString(), (crackableTarget ?? 0).toString() ]) } } { avatarInfo.groupId > 0 && <>
GetGroupInformation(avatarInfo.groupId) }> { groupName } } { (itemLocation.x > -1) && <>
X: { itemLocation.x } · Y: { itemLocation.y }
{ LocalizeText('stack.magic.tile.height.label') }: { itemLocation.z < 0.01 ? 0 : itemLocation.z }
} { godMode && <>
{ canSeeFurniId &&
ID: { avatarInfo.id }
Sprite: { (() => { const ro = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, avatarInfo.isWallItem ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR); return ro?.model?.getValue(RoomObjectVariable.FURNITURE_TYPE_ID) ?? '?'; })() }
} { (!avatarInfo.isWallItem && canMove) && <> { dropdownOpen &&
{ /* Left panel: position + rotation */ }
{ LocalizeText('group.edit.badge.position') }
sendUpdate(-1, 0, furniLocationZ ?? 0, 0) }>
sendUpdate(0, -1, furniLocationZ ?? 0, 0) }>
sendUpdate(0, 1, furniLocationZ ?? 0, 0) }>
sendUpdate(1, 0, furniLocationZ ?? 0, 0) }>
{ LocalizeText('infostand.button.rotate') }
sendUpdate(0, 0, furniLocationZ ?? 0, -1) }>
sendUpdate(0, 0, furniLocationZ ?? 0, 1) }>
{ /* Right panel: height */ }
{ LocalizeText('stack.magic.tile.height.label') }
adjustHeight(1) }>↑
adjustHeight(-1) }>↓
adjustHeight(0.1) }>↑
adjustHeight(-0.1) }>↓
adjustHeight(0.01) }>↑
_
adjustHeight(-0.01) }>↓
} } { (furniKeys.length > 0) && <>
{ furniKeys.map((key, index) => { return ( { key } onFurniSettingChange(index, event.target.value) } /> ); }) }
} } { (customKeys.length > 0) && <>
{ customKeys.map((key, index) => { return ( { key } onCustomVariableChange(index, event.target.value) } /> ); }) }
}
{ showInspectButton && } { canMove && } { canRotate && } { (pickupMode !== PICKUP_MODE_NONE) && } { canUse && } { ((furniKeys.length > 0 && furniValues.length > 0) && (furniKeys.length === furniValues.length)) && } { ((customKeys.length > 0 && customValues.length > 0) && (customKeys.length === customValues.length)) && } ); };