diff --git a/src/api/utils/ColorUtils.ts b/src/api/utils/ColorUtils.ts index e31643b6..c32f8fba 100644 --- a/src/api/utils/ColorUtils.ts +++ b/src/api/utils/ColorUtils.ts @@ -51,4 +51,15 @@ export class ColorUtils { return (((val1) << 24) + ((val2) << 16) + ((val3) << 8) + (val4| 0)); } + + public static int2rgb(color: number): string + { + color >>>= 0; + const b = color & 0xFF; + const g = (color & 0xFF00) >>> 8; + const r = (color & 0xFF0000) >>> 16; + const a = ((color & 0xFF000000) >>> 24) / 255; + + return 'rgba(' + [ r, g, b, 1 ].join(',') + ')'; + } } diff --git a/src/assets/images/mysterybox/chain_mysterybox_box_overlay.png b/src/assets/images/mysterybox/chain_mysterybox_box_overlay.png new file mode 100644 index 00000000..5914aa5a Binary files /dev/null and b/src/assets/images/mysterybox/chain_mysterybox_box_overlay.png differ diff --git a/src/assets/images/mysterybox/key_overlay.png b/src/assets/images/mysterybox/key_overlay.png new file mode 100644 index 00000000..8f8c2a5b Binary files /dev/null and b/src/assets/images/mysterybox/key_overlay.png differ diff --git a/src/assets/images/mysterybox/mystery_box.png b/src/assets/images/mysterybox/mystery_box.png new file mode 100644 index 00000000..e85f966c Binary files /dev/null and b/src/assets/images/mysterybox/mystery_box.png differ diff --git a/src/assets/images/mysterybox/mystery_box_key.png b/src/assets/images/mysterybox/mystery_box_key.png new file mode 100644 index 00000000..79b43dee Binary files /dev/null and b/src/assets/images/mysterybox/mystery_box_key.png differ diff --git a/src/assets/images/prize/prize_background.png b/src/assets/images/prize/prize_background.png new file mode 100644 index 00000000..ec9c0306 Binary files /dev/null and b/src/assets/images/prize/prize_background.png differ diff --git a/src/common/layout/LayoutFurniImageView.tsx b/src/common/layout/LayoutFurniImageView.tsx index d74d7e4b..ef8696c5 100644 --- a/src/common/layout/LayoutFurniImageView.tsx +++ b/src/common/layout/LayoutFurniImageView.tsx @@ -71,7 +71,7 @@ export const LayoutFurniImageView: FC = props => if(imageResult) { const image = imageResult.getImage(); - + image.onload = () => setImageElement(image); } }, [ productType, productClassId, direction, extraData ]); diff --git a/src/common/layout/LayoutPrizeProductImageView.tsx b/src/common/layout/LayoutPrizeProductImageView.tsx new file mode 100644 index 00000000..206c7a80 --- /dev/null +++ b/src/common/layout/LayoutPrizeProductImageView.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; +import { ProductTypeEnum } from '../../api'; +import { LayoutBadgeImageView } from './LayoutBadgeImageView'; +import { LayoutCurrencyIcon } from './LayoutCurrencyIcon'; +import { LayoutFurniImageView } from './LayoutFurniImageView'; + +interface LayoutPrizeProductImageViewProps +{ + productType: string; + classId: number; + extraParam?: string; +} + +export const LayoutPrizeProductImageView: FC = props => +{ + const { productType = ProductTypeEnum.FLOOR, classId = -1, extraParam = undefined } = props; + + switch(productType) + { + case ProductTypeEnum.WALL: + case ProductTypeEnum.FLOOR: + return + case ProductTypeEnum.BADGE: + return + case ProductTypeEnum.HABBO_CLUB: + return + } + + return null; +} diff --git a/src/components/right-side/RightSideView.tsx b/src/components/right-side/RightSideView.tsx index c679711a..92e81290 100644 --- a/src/components/right-side/RightSideView.tsx +++ b/src/components/right-side/RightSideView.tsx @@ -4,6 +4,7 @@ import { OfferView } from '../catalog/views/targeted-offer/OfferView'; import { GroupRoomInformationView } from '../groups/views/GroupRoomInformationView'; import { NotificationCenterView } from '../notification-center/NotificationCenterView'; import { PurseView } from '../purse/PurseView'; +import { MysteryBoxExtensionView } from '../room/widgets/mysterybox/MysteryBoxExtensionView'; import { RoomPromotesWidgetView } from '../room/widgets/room-promotes/RoomPromotesWidgetView'; export const RightSideView: FC<{}> = props => @@ -13,6 +14,7 @@ export const RightSideView: FC<{}> = props => + diff --git a/src/components/room/widgets/RoomWidgets.scss b/src/components/room/widgets/RoomWidgets.scss index 38270c32..d529dcca 100644 --- a/src/components/room/widgets/RoomWidgets.scss +++ b/src/components/room/widgets/RoomWidgets.scss @@ -106,3 +106,4 @@ @import './context-menu/ContextMenu'; @import './friend-request/FriendRequestDialogView'; @import './furniture/FurnitureWidgets'; +@import './mysterybox/MysteryBoxExtensionView'; diff --git a/src/components/room/widgets/furniture/FurnitureMysteryBoxOpenDialogView.tsx b/src/components/room/widgets/furniture/FurnitureMysteryBoxOpenDialogView.tsx new file mode 100644 index 00000000..88821e0f --- /dev/null +++ b/src/components/room/widgets/furniture/FurnitureMysteryBoxOpenDialogView.tsx @@ -0,0 +1,81 @@ +import { CancelMysteryBoxWaitMessageEvent, GotMysteryBoxPrizeMessageEvent, MysteryBoxWaitingCanceledMessageComposer, ShowMysteryBoxWaitMessageEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../api'; +import { Button, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; +import { LayoutPrizeProductImageView } from '../../../../common/layout/LayoutPrizeProductImageView'; +import { useMessageEvent } from '../../../../hooks'; + +interface FurnitureMysteryBoxOpenDialogViewProps +{ + ownerId: number; +} + +type PrizeData = { + contentType:string; + classId:number; +} + +enum ViewMode { + HIDDEN, + WAITING, + PRIZE +} + +export const FurnitureMysteryBoxOpenDialogView: FC = props => +{ + const { ownerId = -1 } = props; + const [ mode, setMode ] = useState(ViewMode.HIDDEN); + const [ prizeData, setPrizeData ] = useState(undefined); + + const close = () => + { + if(mode === ViewMode.WAITING) SendMessageComposer(new MysteryBoxWaitingCanceledMessageComposer(ownerId)); + setMode(ViewMode.HIDDEN); + setPrizeData(undefined); + } + + useMessageEvent(ShowMysteryBoxWaitMessageEvent, event => + { + setMode(ViewMode.WAITING); + }); + + useMessageEvent(CancelMysteryBoxWaitMessageEvent, event => + { + setMode(ViewMode.HIDDEN); + setPrizeData(undefined); + }); + + useMessageEvent(GotMysteryBoxPrizeMessageEvent, event => + { + const parser = event.getParser(); + setPrizeData({ contentType: parser.contentType, classId: parser.classId }); + setMode(ViewMode.PRIZE); + }); + + const isOwner = GetSessionDataManager().userId === ownerId; + + if(mode === ViewMode.HIDDEN) return null; + + return ( + + + + { mode === ViewMode.WAITING && <> + { LocalizeText(`mysterybox.dialog.${ isOwner ? 'owner' : 'other' }.subtitle`) } + { LocalizeText(`mysterybox.dialog.${ isOwner ? 'owner' : 'other' }.description`) } + { LocalizeText(`mysterybox.dialog.${ isOwner ? 'owner' : 'other' }.waiting`) } + + + } + { mode === ViewMode.PRIZE && prizeData && <> + { LocalizeText('mysterybox.reward.text') } + + + + + + } + + + ); +} diff --git a/src/components/room/widgets/furniture/FurnitureWidgets.scss b/src/components/room/widgets/furniture/FurnitureWidgets.scss index 433a139c..f1783b61 100644 --- a/src/components/room/widgets/furniture/FurnitureWidgets.scss +++ b/src/components/room/widgets/furniture/FurnitureWidgets.scss @@ -406,6 +406,7 @@ &:not(.playing-song):not(.selected-song) { -webkit-mask-image: url("../../../../assets/images/room-widgets/playlist-editor/disk_2.png"); + mask-image: url("../../../../assets/images/room-widgets/playlist-editor/disk_2.png"); } } @@ -442,8 +443,22 @@ .disk-image { background: url("../../../../assets/images/room-widgets/playlist-editor/disk_image.png"); -webkit-mask-image: url("../../../../assets/images/room-widgets/playlist-editor/disk_image.png"); + mask-image: url("../../../../assets/images/room-widgets/playlist-editor/disk_image.png"); height: 76px; width: 76px; } } } + +.nitro-mysterybox-dialog { + width: 375px; + height: 210px; + + .prize-container { + height: 80px; + width: 81px; + background-image: url('../../../../assets/images/prize/prize_background.png'); + background-repeat: no-repeat; + background-position: center; + } +} diff --git a/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx b/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx index a0795b58..6eacf27b 100644 --- a/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx +++ b/src/components/room/widgets/furniture/context-menu/FurnitureContextMenuView.tsx @@ -1,22 +1,18 @@ import { ContextMenuEnum, CustomUserNotificationMessageEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; import { FC } from 'react'; -import { GetGroupInformation, LocalizeText } from '../../../../../api'; -import { useFurnitureContextMenuWidget, useMessageEvent, useNotification } from '../../../../../hooks'; +import { GetGroupInformation, GetSessionDataManager, LocalizeText } from '../../../../../api'; +import { EFFECTBOX_OPEN, GROUP_FURNITURE, MONSTERPLANT_SEED_CONFIRMATION, PURCHASABLE_CLOTHING_CONFIRMATION, useFurnitureContextMenuWidget, useMessageEvent, useNotification } from '../../../../../hooks'; import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; import { ContextMenuView } from '../../context-menu/ContextMenuView'; +import { FurnitureMysteryBoxOpenDialogView } from '../FurnitureMysteryBoxOpenDialogView'; import { EffectBoxConfirmView } from './EffectBoxConfirmView'; import { MonsterPlantSeedConfirmView } from './MonsterPlantSeedConfirmView'; import { PurchasableClothingConfirmView } from './PurchasableClothingConfirmView'; -const MONSTERPLANT_SEED_CONFIRMATION: string = 'MONSTERPLANT_SEED_CONFIRMATION'; -const PURCHASABLE_CLOTHING_CONFIRMATION: string = 'PURCHASABLE_CLOTHING_CONFIRMATION'; -const GROUP_FURNITURE: string = 'GROUP_FURNITURE'; -const EFFECTBOX_OPEN: string = 'EFFECTBOX_OPEN'; - export const FurnitureContextMenuView: FC<{}> = props => { - const { closeConfirm = null, processAction = null, onClose = null, objectId = -1, mode = null, confirmMode = null, confirmingObjectId = -1, groupData = null, isGroupMember = false } = useFurnitureContextMenuWidget(); + const { closeConfirm = null, processAction = null, onClose = null, objectId = -1, mode = null, confirmMode = null, confirmingObjectId = -1, groupData = null, isGroupMember = false, objectOwnerId = -1 } = useFurnitureContextMenuWidget(); const { simpleAlert = null } = useNotification(); useMessageEvent(CustomUserNotificationMessageEvent, event => @@ -41,6 +37,8 @@ export const FurnitureContextMenuView: FC<{}> = props => } }); + const isOwner = GetSessionDataManager().userId === objectOwnerId; + return ( <> { (confirmMode === MONSTERPLANT_SEED_CONFIRMATION) && @@ -49,6 +47,7 @@ export const FurnitureContextMenuView: FC<{}> = props => } { (confirmMode === EFFECTBOX_OPEN) && } + { (objectId >= 0) && mode && { (mode === ContextMenuEnum.FRIEND_FURNITURE) && @@ -87,6 +86,15 @@ export const FurnitureContextMenuView: FC<{}> = props => { LocalizeText('widget.generic_usable.button.use') } } + { (mode === ContextMenuEnum.MYSTERY_BOX) && + <> + + { LocalizeText('mysterybox.context.title') } + + processAction('use_mystery_box') }> + { LocalizeText('mysterybox.context.' + ((isOwner) ? 'owner' : 'other') + '.use') } + + } { (mode === GROUP_FURNITURE) && groupData && <> GetGroupInformation(groupData.guildId) }> diff --git a/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss new file mode 100644 index 00000000..6fed0381 --- /dev/null +++ b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.scss @@ -0,0 +1,52 @@ +.mysterybox-extension { + + .mysterybox-container { + max-width: 50px; + max-height: 50px; + width: 50px; + height: 50px; + + background-color: rgba(28, 28, 32, 0.95); + box-shadow: inset 0px 5px rgb(34 34 39 / 60%), inset 0 -4px rgb(18 18 21 / 60%); + border-color: #5b5a57; + } + + .box-image { + width: 31px; + height: 36px; + position: relative; + background-image: url('../../../../assets/images/mysterybox/mystery_box.png'); + -webkit-mask-image: url('../../../../assets/images/mysterybox/mystery_box.png'); + mask-image: url('../../../../assets/images/mysterybox/mystery_box.png'); + + .chain-overlay-image { + width: 31px; + height: 36px; + position: absolute; + background-image: url('../../../../assets/images/mysterybox/chain_mysterybox_box_overlay.png'); + } + } + + .key-image { + width: 39px; + height: 39px; + position: relative; + background-image: url('../../../../assets/images/mysterybox/mystery_box_key.png'); + -webkit-mask-image: url('../../../../assets/images/mysterybox/mystery_box_key.png'); + mask-image: url('../../../../assets/images/mysterybox/mystery_box_key.png'); + + .key-overlay-image { + width: 39px; + height: 39px; + position: absolute; + background-image: url('../../../../assets/images/mysterybox/key_overlay.png'); + } + } + + .box-image, + .key-image { + background-blend-mode: multiply; + background-position: center; + background-repeat: no-repeat; + } +} diff --git a/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx new file mode 100644 index 00000000..3dd3e935 --- /dev/null +++ b/src/components/room/widgets/mysterybox/MysteryBoxExtensionView.tsx @@ -0,0 +1,66 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { MysteryBoxKeysUpdateEvent } from '@nitrots/nitro-renderer'; +import { FC, useState } from 'react'; +import { ColorUtils, LocalizeText } from '../../../../api'; +import { Base, Column, Flex, LayoutGridItem, Text } from '../../../../common'; +import { useSessionDataManagerEvent } from '../../../../hooks'; + +const colorMap = { + 'purple': 9452386, + 'blue': 3891856, + 'green': 6459451, + 'yellow': 10658089, + 'lilac': 6897548, + 'orange': 10841125, + 'turquoise': 2661026, + 'red': 10104881 +} + +export const MysteryBoxExtensionView: FC<{}> = props => +{ + const [ isOpen, setIsOpen ] = useState(true); + const [ keyColor, setKeyColor ] = useState(''); + const [ boxColor, setBoxColor ] = useState(''); + + useSessionDataManagerEvent(MysteryBoxKeysUpdateEvent.MYSTERY_BOX_KEYS_UPDATE, event => + { + setKeyColor(event.keyColor); + setBoxColor(event.boxColor); + }); + + const getRgbColor = (color: string) => + { + const colorInt = colorMap[color]; + + return ColorUtils.int2rgb(colorInt); + } + + if(keyColor === '' && boxColor === '') return null; + + return ( + + + setIsOpen(value => !value) }> + { LocalizeText('mysterybox.tracker.title') } + + + { isOpen && + <> + { LocalizeText('mysterybox.tracker.description') } + + +
+
+
+ + +
+
+
+ + + } + + + ); +} diff --git a/src/hooks/rooms/widgets/furniture/useFurnitureContextMenuWidget.ts b/src/hooks/rooms/widgets/furniture/useFurnitureContextMenuWidget.ts index 22b735c8..53e4e0fd 100644 --- a/src/hooks/rooms/widgets/furniture/useFurnitureContextMenuWidget.ts +++ b/src/hooks/rooms/widgets/furniture/useFurnitureContextMenuWidget.ts @@ -1,13 +1,13 @@ -import { ContextMenuEnum, GroupFurniContextMenuInfoMessageEvent, GroupFurniContextMenuInfoMessageParser, RoomEngineTriggerWidgetEvent, RoomObjectCategory } from '@nitrots/nitro-renderer'; +import { ContextMenuEnum, GroupFurniContextMenuInfoMessageEvent, GroupFurniContextMenuInfoMessageParser, RoomEngineTriggerWidgetEvent, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer'; import { useState } from 'react'; import { GetRoomEngine, IsOwnerOfFurniture, TryJoinGroup, TryVisitRoom } from '../../../../api'; import { useMessageEvent, useRoomEngineEvent } from '../../../events'; import { useRoom } from '../../useRoom'; -const MONSTERPLANT_SEED_CONFIRMATION: string = 'MONSTERPLANT_SEED_CONFIRMATION'; -const PURCHASABLE_CLOTHING_CONFIRMATION: string = 'PURCHASABLE_CLOTHING_CONFIRMATION'; -const GROUP_FURNITURE: string = 'GROUP_FURNITURE'; -const EFFECTBOX_OPEN: string = 'EFFECTBOX_OPEN'; +export const MONSTERPLANT_SEED_CONFIRMATION: string = 'MONSTERPLANT_SEED_CONFIRMATION'; +export const PURCHASABLE_CLOTHING_CONFIRMATION: string = 'PURCHASABLE_CLOTHING_CONFIRMATION'; +export const GROUP_FURNITURE: string = 'GROUP_FURNITURE'; +export const EFFECTBOX_OPEN: string = 'EFFECTBOX_OPEN'; const useFurnitureContextMenuWidgetState = () => { @@ -17,6 +17,7 @@ const useFurnitureContextMenuWidgetState = () => const [ confirmingObjectId, setConfirmingObjectId ] = useState(-1); const [ groupData, setGroupData ] = useState(null); const [ isGroupMember, setIsGroupMember ] = useState(false); + const [ objectOwnerId, setObjectOwnerId ] = useState(-1); const { roomSession = null } = useRoom(); const onClose = () => @@ -53,6 +54,9 @@ const useFurnitureContextMenuWidgetState = () => setConfirmMode(PURCHASABLE_CLOTHING_CONFIRMATION); setConfirmingObjectId(objectId); break; + case 'use_mystery_box': + roomSession.useMultistateItem(objectId); + break; case 'join_group': TryJoinGroup(groupData.guildId); setIsGroupMember(true); @@ -71,13 +75,16 @@ const useFurnitureContextMenuWidgetState = () => RoomEngineTriggerWidgetEvent.CLOSE_FURNI_CONTEXT_MENU, RoomEngineTriggerWidgetEvent.REQUEST_MONSTERPLANT_SEED_PLANT_CONFIRMATION_DIALOG, RoomEngineTriggerWidgetEvent.REQUEST_PURCHASABLE_CLOTHING_CONFIRMATION_DIALOG, - RoomEngineTriggerWidgetEvent.REQUEST_EFFECTBOX_OPEN_DIALOG + RoomEngineTriggerWidgetEvent.REQUEST_EFFECTBOX_OPEN_DIALOG, + RoomEngineTriggerWidgetEvent.REQUEST_MYSTERYBOX_OPEN_DIALOG ], event => { const object = GetRoomEngine().getRoomObject(roomSession.roomId, event.objectId, event.category); if(!object) return; + setObjectOwnerId(object.model.getValue(RoomObjectVariable.FURNITURE_OWNER_ID)); + switch(event.type) { case RoomEngineTriggerWidgetEvent.REQUEST_MONSTERPLANT_SEED_PLANT_CONFIRMATION_DIALOG: @@ -102,6 +109,11 @@ const useFurnitureContextMenuWidgetState = () => setConfirmingObjectId(object.id); setConfirmMode(PURCHASABLE_CLOTHING_CONFIRMATION); + onClose(); + return; + case RoomEngineTriggerWidgetEvent.REQUEST_MYSTERYBOX_OPEN_DIALOG: + roomSession.useMultistateItem(object.id); + onClose(); return; case RoomEngineTriggerWidgetEvent.OPEN_FURNI_CONTEXT_MENU: @@ -117,6 +129,7 @@ const useFurnitureContextMenuWidgetState = () => if(IsOwnerOfFurniture(object)) setMode(ContextMenuEnum.MONSTERPLANT_SEED); return; case ContextMenuEnum.MYSTERY_BOX: + setMode(ContextMenuEnum.MYSTERY_BOX); return; case ContextMenuEnum.RANDOM_TELEPORT: setMode(ContextMenuEnum.RANDOM_TELEPORT); @@ -143,7 +156,7 @@ const useFurnitureContextMenuWidgetState = () => setMode(GROUP_FURNITURE); }); - return { objectId, mode, confirmMode, confirmingObjectId, groupData, isGroupMember, closeConfirm, processAction, onClose }; + return { objectId, mode, confirmMode, confirmingObjectId, groupData, isGroupMember, objectOwnerId, closeConfirm, processAction, onClose }; } export const useFurnitureContextMenuWidget = useFurnitureContextMenuWidgetState;