mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +00:00
🆙 Init V3
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { IRoomUserData, PetTrainingMessageParser, PetTrainingPanelMessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { LocalizeText } from '../../../../api';
|
||||
import { Button, Column, Flex, Grid, LayoutPetImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useMessageEvent, useRoom, useSessionInfo } from '../../../../hooks';
|
||||
|
||||
export const AvatarInfoPetTrainingPanelView: FC<{}> = props =>
|
||||
{
|
||||
const [ petData, setPetData ] = useState<IRoomUserData>(null);
|
||||
const [ petTrainInformation, setPetTrainInformation ] = useState<PetTrainingMessageParser>(null);
|
||||
const { chatStyleId = 0 } = useSessionInfo();
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
useMessageEvent<PetTrainingPanelMessageEvent>(PetTrainingPanelMessageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser) return;
|
||||
|
||||
const roomPetData = roomSession.userDataManager.getPetData(parser.petId);
|
||||
|
||||
if(!roomPetData) return;
|
||||
|
||||
setPetData(roomPetData);
|
||||
setPetTrainInformation(parser);
|
||||
});
|
||||
|
||||
const processPetAction = (petName: string, commandName: string) =>
|
||||
{
|
||||
if(!petName || !commandName) return;
|
||||
|
||||
roomSession?.sendChatMessage(`${ petName } ${ commandName }`, chatStyleId);
|
||||
};
|
||||
|
||||
if(!petData || !petTrainInformation) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="user-settings-window no-resize" theme="primary-slim" uniqueKey="user-settings">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('widgets.pet.commands.title') } onCloseClick={ () => setPetTrainInformation(null) } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<Flex alignItems="center" gap={ 2 } justifyContent="center">
|
||||
<Grid columnCount={ 2 }>
|
||||
<Column fullWidth className="body-image pet p-1" overflow="hidden">
|
||||
<LayoutPetImageView direction={ 2 } figure={ petData.figure } posture={ 'std' } />
|
||||
</Column>
|
||||
<Text small wrap variant="black">{ petData.name }</Text>
|
||||
</Grid>
|
||||
</Flex>
|
||||
<Grid columnCount={ 2 }>
|
||||
{
|
||||
(petTrainInformation.commands && petTrainInformation.commands.length > 0) && petTrainInformation.commands.map((command, index) =>
|
||||
<Button key={ index } disabled={ !petTrainInformation.enabledCommands.includes(command) } onClick={ () => processPetAction(petData.name, LocalizeText(`pet.command.${ command }`)) }>{ LocalizeText(`pet.command.${ command }`) }</Button>
|
||||
)
|
||||
}
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import { BotSkillSaveComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { BotSkillsEnum, GetRoomObjectBounds, GetRoomSession, LocalizeText, RoomWidgetUpdateRentableBotChatEvent, SendMessageComposer } from '../../../../api';
|
||||
import { Button, Column, DraggableWindow, DraggableWindowPosition, Flex, Text } from '../../../../common';
|
||||
import { NitroInput } from '../../../../layout';
|
||||
import { ContextMenuHeaderView } from '../context-menu/ContextMenuHeaderView';
|
||||
|
||||
interface AvatarInfoRentableBotChatViewProps
|
||||
{
|
||||
chatEvent: RoomWidgetUpdateRentableBotChatEvent;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export const AvatarInfoRentableBotChatView: FC<AvatarInfoRentableBotChatViewProps> = props =>
|
||||
{
|
||||
const { chatEvent = null, onClose = null } = props;
|
||||
|
||||
const [ newText, setNewText ] = useState<string>(chatEvent.chat === '${bot.skill.chatter.configuration.text.placeholder}' ? '' : chatEvent.chat);
|
||||
const [ automaticChat, setAutomaticChat ] = useState<boolean>(chatEvent.automaticChat);
|
||||
const [ mixSentences, setMixSentences ] = useState<boolean>(chatEvent.mixSentences);
|
||||
const [ chatDelay, setChatDelay ] = useState<number>(chatEvent.chatDelay);
|
||||
|
||||
const getObjectLocation = useMemo(() => GetRoomObjectBounds(GetRoomSession().roomId, chatEvent.objectId, chatEvent.category, 1), [ chatEvent ]);
|
||||
|
||||
const formatChatString = (value: string) => value.replace(/;#;/g, ' ').replace(/\r\n|\r|\n/g, '\r');
|
||||
|
||||
const save = () =>
|
||||
{
|
||||
const chatConfiguration = formatChatString(newText) + ';#;' + automaticChat + ';#;' + chatDelay + ';#;' + mixSentences;
|
||||
|
||||
SendMessageComposer(new BotSkillSaveComposer(chatEvent.botId, BotSkillsEnum.SETUP_CHAT, chatConfiguration));
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<DraggableWindow dragStyle={ { top: getObjectLocation.y, left: getObjectLocation.x } } handleSelector=".drag-handler" windowPosition={ DraggableWindowPosition.NOTHING }>
|
||||
<div className="nitro-context-menu bot-chat">
|
||||
<ContextMenuHeaderView className="drag-handler">
|
||||
{ LocalizeText('bot.skill.chatter.configuration.title') }
|
||||
</ContextMenuHeaderView>
|
||||
<Column className="p-1">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text variant="white">{ LocalizeText('bot.skill.chatter.configuration.chat.text') }</Text>
|
||||
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" placeholder={ LocalizeText('bot.skill.chatter.configuration.text.placeholder') } rows={ 7 } value={ newText } onChange={ e => setNewText(e.target.value) } />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Flex alignItems="center" gap={ 1 } justifyContent="between">
|
||||
<Text fullWidth variant="white">{ LocalizeText('bot.skill.chatter.configuration.automatic.chat') }</Text>
|
||||
<input checked={ automaticChat } className="form-check-input" type="checkbox" onChange={ event => setAutomaticChat(event.target.checked) } />
|
||||
</Flex>
|
||||
<Flex alignItems="center" gap={ 1 } justifyContent="between">
|
||||
<Text fullWidth variant="white">{ LocalizeText('bot.skill.chatter.configuration.markov') }</Text>
|
||||
<input checked={ mixSentences } className="form-check-input" type="checkbox" onChange={ event => setMixSentences(event.target.checked) } />
|
||||
</Flex>
|
||||
<Flex alignItems="center" gap={ 1 } justifyContent="between">
|
||||
<Text fullWidth variant="white">{ LocalizeText('bot.skill.chatter.configuration.chat.delay') }</Text>
|
||||
<NitroInput type="number" value={ chatDelay } onChange={ event => setChatDelay(event.target.valueAsNumber) } />
|
||||
</Flex>
|
||||
</div>
|
||||
<Flex alignItems="center" gap={ 1 } justifyContent="between">
|
||||
<Button fullWidth variant="primary" onClick={ onClose }>{ LocalizeText('cancel') }</Button>
|
||||
<Button fullWidth variant="success" onClick={ save }>{ LocalizeText('save') }</Button>
|
||||
</Flex>
|
||||
</Column>
|
||||
</div>
|
||||
</DraggableWindow>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,281 @@
|
||||
import { GetRoomEngine, IFurnitureData, IPetCustomPart, IRoomUserData, PetCustomPart, PetFigureData, RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FurniCategory, GetFurnitureDataForRoomObject, LocalizeText, UseProductItem } from '../../../../api';
|
||||
import { Button, Column, Flex, LayoutPetImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useRoom } from '../../../../hooks';
|
||||
|
||||
interface AvatarInfoUseProductConfirmViewProps
|
||||
{
|
||||
item: UseProductItem;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const PRODUCT_PAGE_UKNOWN: number = -1;
|
||||
const PRODUCT_PAGE_SHAMPOO: number = 0;
|
||||
const PRODUCT_PAGE_CUSTOM_PART: number = 1;
|
||||
const PRODUCT_PAGE_CUSTOM_PART_SHAMPOO: number = 2;
|
||||
const PRODUCT_PAGE_SADDLE: number = 3;
|
||||
const PRODUCT_PAGE_REVIVE: number = 4;
|
||||
const PRODUCT_PAGE_REBREED: number = 5;
|
||||
const PRODUCT_PAGE_FERTILIZE: number = 6;
|
||||
|
||||
export const AvatarInfoUseProductConfirmView: FC<AvatarInfoUseProductConfirmViewProps> = props =>
|
||||
{
|
||||
const { item = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(PRODUCT_PAGE_UKNOWN);
|
||||
const [ petData, setPetData ] = useState<IRoomUserData>(null);
|
||||
const [ furniData, setFurniData ] = useState<IFurnitureData>(null);
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
const selectRoomObject = () =>
|
||||
{
|
||||
if(!petData) return;
|
||||
|
||||
GetRoomEngine().selectRoomObject(roomSession.roomId, petData.roomIndex, RoomObjectCategory.UNIT);
|
||||
};
|
||||
|
||||
const useProduct = () =>
|
||||
{
|
||||
roomSession.usePetProduct(item.requestRoomObjectId, petData.webID);
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
const getPetImage = useMemo(() =>
|
||||
{
|
||||
if(!petData || !furniData) return null;
|
||||
|
||||
const petFigureData = new PetFigureData(petData.figure);
|
||||
const customParts = furniData.customParams.split(' ');
|
||||
const petIndex = parseInt(customParts[0]);
|
||||
|
||||
switch(furniData.specialType)
|
||||
{
|
||||
case FurniCategory.PET_SHAMPOO: {
|
||||
if(customParts.length < 2) return null;
|
||||
|
||||
const currentPalette = GetRoomEngine().getPetColorResult(petIndex, petFigureData.paletteId);
|
||||
const possiblePalettes = GetRoomEngine().getPetColorResultsForTag(petIndex, customParts[1]);
|
||||
|
||||
let paletteId = -1;
|
||||
|
||||
for(const result of possiblePalettes)
|
||||
{
|
||||
if(result.breed === currentPalette.breed)
|
||||
{
|
||||
paletteId = parseInt(result.id);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return <LayoutPetImageView customParts={ petFigureData.customParts } direction={ 2 } paletteId={ paletteId } petColor={ petFigureData.color } typeId={ petFigureData.typeId } />;
|
||||
}
|
||||
case FurniCategory.PET_CUSTOM_PART: {
|
||||
if(customParts.length < 4) return null;
|
||||
|
||||
const newCustomParts: IPetCustomPart[] = [];
|
||||
|
||||
const _local_6 = customParts[1].split(',').map(piece => parseInt(piece));
|
||||
const _local_7 = customParts[2].split(',').map(piece => parseInt(piece));
|
||||
const _local_8 = customParts[3].split(',').map(piece => parseInt(piece));
|
||||
|
||||
let _local_10 = 0;
|
||||
|
||||
while(_local_10 < _local_6.length)
|
||||
{
|
||||
const _local_13 = _local_6[_local_10];
|
||||
const _local_15 = petFigureData.getCustomPart(_local_13);
|
||||
|
||||
let _local_12 = _local_8[_local_10];
|
||||
|
||||
if(_local_15 != null) _local_12 = _local_15.paletteId;
|
||||
|
||||
newCustomParts.push(new PetCustomPart(_local_13, _local_7[_local_10], _local_12));
|
||||
|
||||
_local_10++;
|
||||
}
|
||||
|
||||
return <LayoutPetImageView customParts={ newCustomParts } direction={ 2 } paletteId={ petFigureData.paletteId } petColor={ petFigureData.color } typeId={ petFigureData.typeId } />;
|
||||
}
|
||||
case FurniCategory.PET_CUSTOM_PART_SHAMPOO: {
|
||||
if(customParts.length < 3) return null;
|
||||
|
||||
const newCustomParts: IPetCustomPart[] = [];
|
||||
|
||||
const _local_6 = customParts[1].split(',').map(piece => parseInt(piece));
|
||||
const _local_8 = customParts[2].split(',').map(piece => parseInt(piece));
|
||||
|
||||
let _local_10 = 0;
|
||||
|
||||
while(_local_10 < _local_6.length)
|
||||
{
|
||||
const _local_13 = _local_6[_local_10];
|
||||
const _local_15 = petFigureData.getCustomPart(_local_13);
|
||||
|
||||
let _local_14 = -1;
|
||||
|
||||
if(_local_15 != null) _local_14 = _local_15.partId;
|
||||
|
||||
newCustomParts.push(new PetCustomPart(_local_6[_local_10], _local_14, _local_8[_local_10]));
|
||||
|
||||
_local_10++;
|
||||
}
|
||||
|
||||
return <LayoutPetImageView customParts={ newCustomParts } direction={ 2 } paletteId={ petFigureData.paletteId } petColor={ petFigureData.color } typeId={ petFigureData.typeId } />;
|
||||
}
|
||||
case FurniCategory.PET_SADDLE: {
|
||||
if(customParts.length < 4) return null;
|
||||
|
||||
const newCustomParts: IPetCustomPart[] = [];
|
||||
|
||||
const _local_6 = customParts[1].split(',').map(piece => parseInt(piece));
|
||||
const _local_7 = customParts[2].split(',').map(piece => parseInt(piece));
|
||||
const _local_8 = customParts[3].split(',').map(piece => parseInt(piece));
|
||||
|
||||
let _local_10 = 0;
|
||||
|
||||
while(_local_10 < _local_6.length)
|
||||
{
|
||||
newCustomParts.push(new PetCustomPart(_local_6[_local_10], _local_7[_local_10], _local_8[_local_10]));
|
||||
|
||||
_local_10++;
|
||||
}
|
||||
|
||||
for(const _local_21 of petFigureData.customParts)
|
||||
{
|
||||
if(_local_6.indexOf(_local_21.layerId) === -1)
|
||||
{
|
||||
newCustomParts.push(_local_21);
|
||||
}
|
||||
}
|
||||
|
||||
return <LayoutPetImageView customParts={ newCustomParts } direction={ 2 } paletteId={ petFigureData.paletteId } petColor={ petFigureData.color } typeId={ petFigureData.typeId } />;
|
||||
}
|
||||
case FurniCategory.MONSTERPLANT_REBREED:
|
||||
case FurniCategory.MONSTERPLANT_REVIVAL:
|
||||
case FurniCategory.MONSTERPLANT_FERTILIZE: {
|
||||
let posture = 'rip';
|
||||
|
||||
const roomObject = GetRoomEngine().getRoomObject(roomSession.roomId, petData.roomIndex, RoomObjectCategory.UNIT);
|
||||
|
||||
if(roomObject)
|
||||
{
|
||||
posture = roomObject.model.getValue<string>(RoomObjectVariable.FIGURE_POSTURE);
|
||||
|
||||
if(posture === 'rip')
|
||||
{
|
||||
const level = petData.petLevel;
|
||||
|
||||
if(level < 7) posture = `grw${ level }`;
|
||||
else posture = 'std';
|
||||
}
|
||||
}
|
||||
|
||||
return <LayoutPetImageView customParts={ petFigureData.customParts } direction={ 2 } paletteId={ petFigureData.paletteId } petColor={ petFigureData.color } posture={ posture } typeId={ petFigureData.typeId } />;
|
||||
}
|
||||
}
|
||||
}, [ petData, furniData, roomSession ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const userData = roomSession.userDataManager.getUserDataByIndex(item.id);
|
||||
|
||||
setPetData(userData);
|
||||
|
||||
const furniData = GetFurnitureDataForRoomObject(roomSession.roomId, item.requestRoomObjectId, RoomObjectCategory.FLOOR);
|
||||
|
||||
if(!furniData) return;
|
||||
|
||||
setFurniData(furniData);
|
||||
|
||||
let mode = PRODUCT_PAGE_UKNOWN;
|
||||
|
||||
switch(furniData.specialType)
|
||||
{
|
||||
case FurniCategory.PET_SHAMPOO:
|
||||
mode = PRODUCT_PAGE_SHAMPOO;
|
||||
break;
|
||||
case FurniCategory.PET_CUSTOM_PART:
|
||||
mode = PRODUCT_PAGE_CUSTOM_PART;
|
||||
break;
|
||||
case FurniCategory.PET_CUSTOM_PART_SHAMPOO:
|
||||
mode = PRODUCT_PAGE_CUSTOM_PART_SHAMPOO;
|
||||
break;
|
||||
case FurniCategory.PET_SADDLE:
|
||||
mode = PRODUCT_PAGE_SADDLE;
|
||||
break;
|
||||
case FurniCategory.MONSTERPLANT_REVIVAL:
|
||||
mode = PRODUCT_PAGE_REVIVE;
|
||||
break;
|
||||
case FurniCategory.MONSTERPLANT_REBREED:
|
||||
mode = PRODUCT_PAGE_REBREED;
|
||||
break;
|
||||
case FurniCategory.MONSTERPLANT_FERTILIZE:
|
||||
mode = PRODUCT_PAGE_FERTILIZE;
|
||||
break;
|
||||
}
|
||||
|
||||
setMode(mode);
|
||||
}, [ roomSession, item ]);
|
||||
|
||||
if(!petData) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-use-product-confirmation">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('useproduct.widget.title', [ 'name' ], [ petData.name ]) } onCloseClick={ onClose } />
|
||||
<NitroCardContentView center>
|
||||
<Flex gap={ 2 } overflow="hidden">
|
||||
<div className="flex flex-col">
|
||||
<div className="product-preview cursor-pointer" onClick={ selectRoomObject }>
|
||||
{ getPetImage }
|
||||
</div>
|
||||
</div>
|
||||
<Column justifyContent="between" overflow="auto">
|
||||
<Column gap={ 2 }>
|
||||
{ (mode === PRODUCT_PAGE_SHAMPOO) &&
|
||||
<>
|
||||
<Text>{ LocalizeText('useproduct.widget.text.shampoo', [ 'productName' ], [ furniData.name ]) }</Text>
|
||||
<Text>{ LocalizeText('useproduct.widget.info.shampoo') }</Text>
|
||||
</> }
|
||||
{ (mode === PRODUCT_PAGE_CUSTOM_PART) &&
|
||||
<>
|
||||
<Text>{ LocalizeText('useproduct.widget.text.custompart', [ 'productName' ], [ furniData.name ]) }</Text>
|
||||
<Text>{ LocalizeText('useproduct.widget.info.custompart') }</Text>
|
||||
</> }
|
||||
{ (mode === PRODUCT_PAGE_CUSTOM_PART_SHAMPOO) &&
|
||||
<>
|
||||
<Text>{ LocalizeText('useproduct.widget.text.custompartshampoo', [ 'productName' ], [ furniData.name ]) }</Text>
|
||||
<Text>{ LocalizeText('useproduct.widget.info.custompartshampoo') }</Text>
|
||||
</> }
|
||||
{ (mode === PRODUCT_PAGE_SADDLE) &&
|
||||
<>
|
||||
<Text>{ LocalizeText('useproduct.widget.text.saddle', [ 'productName' ], [ furniData.name ]) }</Text>
|
||||
<Text>{ LocalizeText('useproduct.widget.info.saddle') }</Text>
|
||||
</> }
|
||||
{ (mode === PRODUCT_PAGE_REVIVE) &&
|
||||
<>
|
||||
<Text>{ LocalizeText('useproduct.widget.text.revive_monsterplant', [ 'productName' ], [ furniData.name ]) }</Text>
|
||||
<Text>{ LocalizeText('useproduct.widget.info.revive_monsterplant') }</Text>
|
||||
</> }
|
||||
{ (mode === PRODUCT_PAGE_REBREED) &&
|
||||
<>
|
||||
<Text>{ LocalizeText('useproduct.widget.text.rebreed_monsterplant', [ 'productName' ], [ furniData.name ]) }</Text>
|
||||
<Text>{ LocalizeText('useproduct.widget.info.rebreed_monsterplant') }</Text>
|
||||
</> }
|
||||
{ (mode === PRODUCT_PAGE_FERTILIZE) &&
|
||||
<>
|
||||
<Text>{ LocalizeText('useproduct.widget.text.fertilize_monsterplant', [ 'productName' ], [ furniData.name ]) }</Text>
|
||||
<Text>{ LocalizeText('useproduct.widget.info.fertilize_monsterplant') }</Text>
|
||||
</> }
|
||||
</Column>
|
||||
<div className="flex items-center justify-between">
|
||||
<Button variant="danger" onClick={ onClose }>{ LocalizeText('useproduct.widget.cancel') }</Button>
|
||||
<Button variant="success" onClick={ useProduct }>{ LocalizeText('useproduct.widget.use') }</Button>
|
||||
</div>
|
||||
</Column>
|
||||
</Flex>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
import { RoomObjectCategory, RoomObjectType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FurniCategory, GetFurnitureDataForRoomObject, LocalizeText, UseProductItem } from '../../../../api';
|
||||
import { useRoom } from '../../../../hooks';
|
||||
import { ContextMenuHeaderView } from '../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoUseProductViewProps
|
||||
{
|
||||
item: UseProductItem;
|
||||
updateConfirmingProduct: (product: UseProductItem) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const PRODUCT_PAGE_UKNOWN: number = 0;
|
||||
const PRODUCT_PAGE_SHAMPOO: number = 1;
|
||||
const PRODUCT_PAGE_CUSTOM_PART: number = 2;
|
||||
const PRODUCT_PAGE_CUSTOM_PART_SHAMPOO: number = 3;
|
||||
const PRODUCT_PAGE_SADDLE: number = 4;
|
||||
const PRODUCT_PAGE_REVIVE: number = 5;
|
||||
const PRODUCT_PAGE_REBREED: number = 6;
|
||||
const PRODUCT_PAGE_FERTILIZE: number = 7;
|
||||
|
||||
export const AvatarInfoUseProductView: FC<AvatarInfoUseProductViewProps> = props =>
|
||||
{
|
||||
const { item = null, updateConfirmingProduct = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(0);
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
const processAction = (name: string) =>
|
||||
{
|
||||
if(!name) return;
|
||||
|
||||
switch(name)
|
||||
{
|
||||
case 'use_product':
|
||||
case 'use_product_shampoo':
|
||||
case 'use_product_custom_part':
|
||||
case 'use_product_custom_part_shampoo':
|
||||
case 'use_product_saddle':
|
||||
case 'replace_product_saddle':
|
||||
case 'revive_monsterplant':
|
||||
case 'rebreed_monsterplant':
|
||||
case 'fertilize_monsterplant':
|
||||
updateConfirmingProduct(item);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!item) return;
|
||||
|
||||
const furniData = GetFurnitureDataForRoomObject(roomSession.roomId, item.requestRoomObjectId, RoomObjectCategory.FLOOR);
|
||||
|
||||
if(!furniData) return;
|
||||
|
||||
let mode = PRODUCT_PAGE_UKNOWN;
|
||||
|
||||
switch(furniData.specialType)
|
||||
{
|
||||
case FurniCategory.PET_SHAMPOO:
|
||||
mode = PRODUCT_PAGE_SHAMPOO;
|
||||
break;
|
||||
case FurniCategory.PET_CUSTOM_PART:
|
||||
mode = PRODUCT_PAGE_CUSTOM_PART;
|
||||
break;
|
||||
case FurniCategory.PET_CUSTOM_PART_SHAMPOO:
|
||||
mode = PRODUCT_PAGE_CUSTOM_PART_SHAMPOO;
|
||||
break;
|
||||
case FurniCategory.PET_SADDLE:
|
||||
mode = PRODUCT_PAGE_SADDLE;
|
||||
break;
|
||||
case FurniCategory.MONSTERPLANT_REVIVAL:
|
||||
mode = PRODUCT_PAGE_REVIVE;
|
||||
break;
|
||||
case FurniCategory.MONSTERPLANT_REBREED:
|
||||
mode = PRODUCT_PAGE_REBREED;
|
||||
break;
|
||||
case FurniCategory.MONSTERPLANT_FERTILIZE:
|
||||
mode = PRODUCT_PAGE_FERTILIZE;
|
||||
break;
|
||||
}
|
||||
|
||||
setMode(mode);
|
||||
}, [ roomSession, item ]);
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ RoomObjectCategory.UNIT } collapsable={ true } objectId={ item.id } userType={ RoomObjectType.PET } onClose={ onClose }>
|
||||
<ContextMenuHeaderView>
|
||||
{ item.name }
|
||||
</ContextMenuHeaderView>
|
||||
{ (mode === PRODUCT_PAGE_UKNOWN) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('use_product') }>
|
||||
{ LocalizeText('infostand.button.useproduct') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (mode === PRODUCT_PAGE_SHAMPOO) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('use_product_shampoo') }>
|
||||
{ LocalizeText('infostand.button.useproduct_shampoo') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (mode === PRODUCT_PAGE_CUSTOM_PART) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('use_product_custom_part') }>
|
||||
{ LocalizeText('infostand.button.useproduct_custom_part') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (mode === PRODUCT_PAGE_CUSTOM_PART_SHAMPOO) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('use_product_custom_part_shampoo') }>
|
||||
{ LocalizeText('infostand.button.useproduct_custom_part_shampoo') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (mode === PRODUCT_PAGE_SADDLE) &&
|
||||
<>
|
||||
{ item.replace &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('replace_product_saddle') }>
|
||||
{ LocalizeText('infostand.button.replaceproduct_saddle') }
|
||||
</ContextMenuListItemView> }
|
||||
{ !item.replace &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('use_product_saddle') }>
|
||||
{ LocalizeText('infostand.button.useproduct_saddle') }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === PRODUCT_PAGE_REVIVE) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('revive_monsterplant') }>
|
||||
{ LocalizeText('infostand.button.revive_monsterplant') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (mode === PRODUCT_PAGE_REBREED) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('rebreed_monsterplant') }>
|
||||
{ LocalizeText('infostand.button.rebreed_monsterplant') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (mode === PRODUCT_PAGE_FERTILIZE) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('fertilize_monsterplant') }>
|
||||
{ LocalizeText('infostand.button.fertilize_monsterplant') }
|
||||
</ContextMenuListItemView> }
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
import { GetSessionDataManager, RoomEngineEvent, RoomEnterEffect, RoomSessionDanceEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { AvatarInfoFurni, AvatarInfoPet, AvatarInfoRentableBot, AvatarInfoUser, GetConfigurationValue, RoomWidgetUpdateRentableBotChatEvent } from '../../../../api';
|
||||
import { Column } from '../../../../common';
|
||||
import { useAvatarInfoWidget, useNitroEvent, useRoom, useUiEvent } from '../../../../hooks';
|
||||
import { AvatarInfoPetTrainingPanelView } from './AvatarInfoPetTrainingPanelView';
|
||||
import { AvatarInfoRentableBotChatView } from './AvatarInfoRentableBotChatView';
|
||||
import { AvatarInfoUseProductConfirmView } from './AvatarInfoUseProductConfirmView';
|
||||
import { AvatarInfoUseProductView } from './AvatarInfoUseProductView';
|
||||
import { InfoStandWidgetBotView } from './infostand/InfoStandWidgetBotView';
|
||||
import { InfoStandWidgetFurniView } from './infostand/InfoStandWidgetFurniView';
|
||||
import { InfoStandWidgetPetView } from './infostand/InfoStandWidgetPetView';
|
||||
import { InfoStandWidgetRentableBotView } from './infostand/InfoStandWidgetRentableBotView';
|
||||
import { InfoStandWidgetUserView } from './infostand/InfoStandWidgetUserView';
|
||||
import { AvatarInfoWidgetAvatarView } from './menu/AvatarInfoWidgetAvatarView';
|
||||
import { AvatarInfoWidgetDecorateView } from './menu/AvatarInfoWidgetDecorateView';
|
||||
import { AvatarInfoWidgetFurniView } from './menu/AvatarInfoWidgetFurniView';
|
||||
import { AvatarInfoWidgetNameView } from './menu/AvatarInfoWidgetNameView';
|
||||
import { AvatarInfoWidgetOwnAvatarView } from './menu/AvatarInfoWidgetOwnAvatarView';
|
||||
import { AvatarInfoWidgetOwnPetView } from './menu/AvatarInfoWidgetOwnPetView';
|
||||
import { AvatarInfoWidgetPetView } from './menu/AvatarInfoWidgetPetView';
|
||||
import { AvatarInfoWidgetRentableBotView } from './menu/AvatarInfoWidgetRentableBotView';
|
||||
|
||||
export const AvatarInfoWidgetView: FC<{}> = props =>
|
||||
{
|
||||
const [ isGameMode, setGameMode ] = useState(false);
|
||||
const [ isDancing, setIsDancing ] = useState(false);
|
||||
const [ rentableBotChatEvent, setRentableBotChatEvent ] = useState<RoomWidgetUpdateRentableBotChatEvent>(null);
|
||||
const { avatarInfo = null, setAvatarInfo = null, activeNameBubble = null, setActiveNameBubble = null, nameBubbles = [], removeNameBubble = null, productBubbles = [], confirmingProduct = null, updateConfirmingProduct = null, removeProductBubble = null, isDecorating = false, setIsDecorating = null } = useAvatarInfoWidget();
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
useNitroEvent<RoomEngineEvent>(RoomEngineEvent.NORMAL_MODE, event =>
|
||||
{
|
||||
if(isGameMode) setGameMode(false);
|
||||
});
|
||||
|
||||
useNitroEvent<RoomEngineEvent>(RoomEngineEvent.GAME_MODE, event =>
|
||||
{
|
||||
if(!isGameMode) setGameMode(true);
|
||||
});
|
||||
|
||||
useNitroEvent<RoomSessionDanceEvent>(RoomSessionDanceEvent.RSDE_DANCE, event =>
|
||||
{
|
||||
if(event.roomIndex !== roomSession.ownRoomIndex) return;
|
||||
|
||||
setIsDancing((event.danceId !== 0));
|
||||
});
|
||||
|
||||
useUiEvent<RoomWidgetUpdateRentableBotChatEvent>(RoomWidgetUpdateRentableBotChatEvent.UPDATE_CHAT, event => setRentableBotChatEvent(event));
|
||||
|
||||
const getMenuView = () =>
|
||||
{
|
||||
if(!roomSession || isGameMode) return null;
|
||||
|
||||
if(activeNameBubble) return <AvatarInfoWidgetNameView nameInfo={ activeNameBubble } onClose={ () => setActiveNameBubble(null) } />;
|
||||
|
||||
if(avatarInfo)
|
||||
{
|
||||
switch(avatarInfo.type)
|
||||
{
|
||||
case AvatarInfoFurni.FURNI: {
|
||||
const info = (avatarInfo as AvatarInfoFurni);
|
||||
|
||||
if(!isDecorating) return null;
|
||||
|
||||
return <AvatarInfoWidgetFurniView avatarInfo={ info } onClose={ () => setAvatarInfo(null) } />;
|
||||
}
|
||||
case AvatarInfoUser.OWN_USER:
|
||||
case AvatarInfoUser.PEER: {
|
||||
const info = (avatarInfo as AvatarInfoUser);
|
||||
if(GetConfigurationValue('user.tags.enabled')) GetSessionDataManager().getUserTags(info.roomIndex);
|
||||
|
||||
if(info.isSpectatorMode) return null;
|
||||
|
||||
if(info.isOwnUser)
|
||||
{
|
||||
if(RoomEnterEffect.isRunning()) return null;
|
||||
|
||||
return <AvatarInfoWidgetOwnAvatarView avatarInfo={ info } isDancing={ isDancing } setIsDecorating={ setIsDecorating } onClose={ () => setAvatarInfo(null) } />;
|
||||
}
|
||||
|
||||
return <AvatarInfoWidgetAvatarView avatarInfo={ info } onClose={ () => setAvatarInfo(null) } />;
|
||||
}
|
||||
case AvatarInfoPet.PET_INFO: {
|
||||
const info = (avatarInfo as AvatarInfoPet);
|
||||
|
||||
if(info.isOwner) return <AvatarInfoWidgetOwnPetView avatarInfo={ info } onClose={ () => setAvatarInfo(null) } />;
|
||||
|
||||
return <AvatarInfoWidgetPetView avatarInfo={ info } onClose={ () => setAvatarInfo(null) } />;
|
||||
}
|
||||
case AvatarInfoRentableBot.RENTABLE_BOT: {
|
||||
return <AvatarInfoWidgetRentableBotView avatarInfo={ (avatarInfo as AvatarInfoRentableBot) } onClose={ () => setAvatarInfo(null) } />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getInfostandView = () =>
|
||||
{
|
||||
if(!avatarInfo) return null;
|
||||
|
||||
switch(avatarInfo.type)
|
||||
{
|
||||
case AvatarInfoFurni.FURNI:
|
||||
return <InfoStandWidgetFurniView avatarInfo={ (avatarInfo as AvatarInfoFurni) } onClose={ () => setAvatarInfo(null) } />;
|
||||
case AvatarInfoUser.OWN_USER:
|
||||
case AvatarInfoUser.PEER:
|
||||
return <InfoStandWidgetUserView avatarInfo={ (avatarInfo as AvatarInfoUser) } setAvatarInfo={ setAvatarInfo } onClose={ () => setAvatarInfo(null) } />;
|
||||
case AvatarInfoUser.BOT:
|
||||
return <InfoStandWidgetBotView avatarInfo={ (avatarInfo as AvatarInfoUser) } onClose={ () => setAvatarInfo(null) } />;
|
||||
case AvatarInfoRentableBot.RENTABLE_BOT:
|
||||
return <InfoStandWidgetRentableBotView avatarInfo={ (avatarInfo as AvatarInfoRentableBot) } onClose={ () => setAvatarInfo(null) } />;
|
||||
case AvatarInfoPet.PET_INFO:
|
||||
return <InfoStandWidgetPetView avatarInfo={ (avatarInfo as AvatarInfoPet) } onClose={ () => setAvatarInfo(null) } />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{ isDecorating &&
|
||||
<AvatarInfoWidgetDecorateView roomIndex={ roomSession.ownRoomIndex } setIsDecorating={ setIsDecorating } userId={ GetSessionDataManager().userId } userName={ GetSessionDataManager().userName } /> }
|
||||
{ getMenuView() }
|
||||
{ avatarInfo &&
|
||||
<Column alignItems="end" className="absolute right-[10px] bottom-[65px] pointer-events-none z-30 text-white">
|
||||
{ getInfostandView() }
|
||||
</Column> }
|
||||
{ (nameBubbles.length > 0) && nameBubbles.map((name, index) => <AvatarInfoWidgetNameView key={ index } nameInfo={ name } onClose={ () => removeNameBubble(index) } />) }
|
||||
{ (productBubbles.length > 0) && productBubbles.map((item, index) =>
|
||||
{
|
||||
return <AvatarInfoUseProductView key={ item.id } item={ item } updateConfirmingProduct={ updateConfirmingProduct } onClose={ () => removeProductBubble(index) } />;
|
||||
}) }
|
||||
{ rentableBotChatEvent && <AvatarInfoRentableBotChatView chatEvent={ rentableBotChatEvent } onClose={ () => setRentableBotChatEvent(null) } /> }
|
||||
{ confirmingProduct && <AvatarInfoUseProductConfirmView item={ confirmingProduct } onClose={ () => updateConfirmingProduct(null) } /> }
|
||||
<AvatarInfoPetTrainingPanelView />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import { FC } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { AvatarInfoUser, LocalizeText } from '../../../../../api';
|
||||
import { Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, Text } from '../../../../../common';
|
||||
|
||||
interface InfoStandWidgetBotViewProps
|
||||
{
|
||||
avatarInfo: AvatarInfoUser;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const InfoStandWidgetBotView: FC<InfoStandWidgetBotViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
|
||||
if(!avatarInfo) return null;
|
||||
|
||||
return (
|
||||
<Column className="nitro-infostand rounded">
|
||||
<Column className="container-fluid content-area" gap={ 1 } overflow="visible">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Flex alignItems="center" gap={ 1 } justifyContent="between">
|
||||
<Text small wrap variant="white">{ avatarInfo.name }</Text>
|
||||
<FaTimes className="cursor-pointer fa-icon" onClick={ onClose } />
|
||||
</Flex>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1">
|
||||
<Column fullWidth className="body-image bot">
|
||||
<LayoutAvatarImageView direction={ 4 } figure={ avatarInfo.figure } />
|
||||
</Column>
|
||||
<Column center grow gap={ 0 }>
|
||||
{ (avatarInfo.badges.length > 0) && avatarInfo.badges.map(result =>
|
||||
{
|
||||
return <LayoutBadgeImageView key={ result } badgeCode={ result } showInfo={ true } />;
|
||||
}) }
|
||||
</Column>
|
||||
</div>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
<Flex alignItems="center" className="bg-light-dark rounded py-1 px-2">
|
||||
<Text fullWidth small textBreak wrap className="min-h-[18px]" variant="white">{ avatarInfo.motto }</Text>
|
||||
</Flex>
|
||||
{ (avatarInfo.carryItem > 0) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<hr className="m-0" />
|
||||
<Text small wrap variant="white">
|
||||
{ LocalizeText('infostand.text.handitem', [ 'item' ], [ LocalizeText('handitem' + avatarInfo.carryItem) ]) }
|
||||
</Text>
|
||||
</div> }
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,475 @@
|
||||
import { CrackableDataType, CreateLinkEvent, GetRoomEngine, GetSoundManager, GroupInformationComposer, GroupInformationEvent, NowPlayingEvent, RoomControllerLevel, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomWidgetEnumItemExtradataParameter, RoomWidgetFurniInfoUsagePolicyEnum, SetObjectDataMessageComposer, SongInfoReceivedEvent, StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { AvatarInfoFurni, GetGroupInformation, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Button, Column, Flex, LayoutBadgeImageView, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomObjectImageView, Text, UserProfileIconView } from '../../../../../common';
|
||||
import { useMessageEvent, useNitroEvent, useRoom } 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<InfoStandWidgetFurniViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
const [ pickupMode, setPickupMode ] = useState(0);
|
||||
const [ canMove, setCanMove ] = useState(false);
|
||||
const [ canRotate, setCanRotate ] = useState(false);
|
||||
const [ canUse, setCanUse ] = useState(false);
|
||||
const [ furniKeys, setFurniKeys ] = useState<string[]>([]);
|
||||
const [ furniValues, setFurniValues ] = useState<string[]>([]);
|
||||
const [ customKeys, setCustomKeys ] = useState<string[]>([]);
|
||||
const [ customValues, setCustomValues ] = useState<string[]>([]);
|
||||
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<string>(null);
|
||||
const [ isJukeBox, setIsJukeBox ] = useState<boolean>(false);
|
||||
const [ isSongDisk, setIsSongDisk ] = useState<boolean>(false);
|
||||
const [ songId, setSongId ] = useState<number>(-1);
|
||||
const [ songName, setSongName ] = useState<string>('');
|
||||
const [ songCreator, setSongCreator ] = useState<string>('');
|
||||
|
||||
useNitroEvent<NowPlayingEvent>(NowPlayingEvent.NPE_SONG_CHANGED, event =>
|
||||
{
|
||||
setSongId(event.id);
|
||||
}, (isJukeBox || isSongDisk));
|
||||
|
||||
useNitroEvent<NowPlayingEvent>(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 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;
|
||||
crackableTarget = stuffData.target;
|
||||
}
|
||||
|
||||
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<string[]>(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>(GroupInformationEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!avatarInfo || avatarInfo.groupId !== parser.id || parser.flag) return;
|
||||
|
||||
if(groupName) setGroupName(null);
|
||||
|
||||
setGroupName(parser.title);
|
||||
});
|
||||
|
||||
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<string, string>();
|
||||
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 (
|
||||
<Column alignItems="end" gap={ 1 }>
|
||||
<Column className="relative min-w-[190px] max-w-[190px] z-30 pointer-events-auto bg-[rgba(28,_28,_32,_.95)] [box-shadow:inset_0_5px_#22222799,_inset_0_-4px_#12121599] rounded">
|
||||
<Column className="h-full p-[8px] overflow-auto" gap={ 1 } overflow="visible">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Flex alignItems="center" gap={ 1 } justifyContent="between">
|
||||
<Text small wrap variant="white">{ avatarInfo.name }</Text>
|
||||
<FaTimes className="cursor-pointer fa-icon" onClick={ onClose } />
|
||||
</Flex>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Flex gap={ 1 } position="relative">
|
||||
{ avatarInfo.stuffData.isUnique &&
|
||||
<div className="absolute end-0">
|
||||
<LayoutLimitedEditionCompactPlateView uniqueNumber={ avatarInfo.stuffData.uniqueNumber } uniqueSeries={ avatarInfo.stuffData.uniqueSeries } />
|
||||
</div> }
|
||||
{ (avatarInfo.stuffData.rarityLevel > -1) &&
|
||||
<div className="absolute end-0">
|
||||
<LayoutRarityLevelView level={ avatarInfo.stuffData.rarityLevel } />
|
||||
</div> }
|
||||
<Flex center fullWidth>
|
||||
<LayoutRoomObjectImageView category={ avatarInfo.category } objectId={ avatarInfo.id } roomId={ roomSession.roomId } />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text fullWidth small textBreak wrap variant="white">{ avatarInfo.description }</Text>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<UserProfileIconView userId={ avatarInfo.ownerId } />
|
||||
<Text small wrap variant="white">
|
||||
{ LocalizeText('furni.owner', [ 'name' ], [ avatarInfo.ownerName ]) }
|
||||
</Text>
|
||||
</div>
|
||||
{ (avatarInfo.purchaseOfferId > 0) &&
|
||||
<Flex>
|
||||
<Text pointer small underline variant="white" onClick={ event => processButtonAction('buy_one') }>
|
||||
{ LocalizeText('infostand.button.buy') }
|
||||
</Text>
|
||||
</Flex> }
|
||||
</div>
|
||||
{ (isJukeBox || isSongDisk) &&
|
||||
<div className="flex flex-col gap-1">
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
{ (songId === -1) &&
|
||||
<Text small wrap variant="white">
|
||||
{ LocalizeText('infostand.jukebox.text.not.playing') }
|
||||
</Text> }
|
||||
{ !!songName.length &&
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="icon disk-icon" />
|
||||
<Text small wrap variant="white">
|
||||
{ songName }
|
||||
</Text>
|
||||
</div> }
|
||||
{ !!songCreator.length &&
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="icon disk-creator" />
|
||||
<Text small wrap variant="white">
|
||||
{ songCreator }
|
||||
</Text>
|
||||
</div> }
|
||||
</div> }
|
||||
<div className="flex flex-col gap-1">
|
||||
{ isCrackable &&
|
||||
<>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
<Text small wrap variant="white">{ LocalizeText('infostand.crackable_furni.hits_remaining', [ 'hits', 'target' ], [ crackableHits.toString(), crackableTarget.toString() ]) }</Text>
|
||||
</> }
|
||||
{ avatarInfo.groupId > 0 &&
|
||||
<>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
<Flex pointer alignItems="center" gap={ 2 } onClick={ () => GetGroupInformation(avatarInfo.groupId) }>
|
||||
<LayoutBadgeImageView badgeCode={ getGroupBadgeCode() } isGroup={ true } />
|
||||
<Text underline variant="white">{ groupName }</Text>
|
||||
</Flex>
|
||||
</> }
|
||||
{ godMode &&
|
||||
<>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
{ canSeeFurniId && <Text small wrap variant="white">ID: { avatarInfo.id }</Text> }
|
||||
{ (furniKeys.length > 0) &&
|
||||
<>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
<div className="flex flex-col gap-1">
|
||||
{ furniKeys.map((key, index) =>
|
||||
{
|
||||
return (
|
||||
<Flex key={ index } alignItems="center" gap={ 1 }>
|
||||
<Text small wrap align="end" className="col-span-4" variant="white">{ key }</Text>
|
||||
<NitroInput type="text" value={ furniValues[index] } onChange={ event => onFurniSettingChange(index, event.target.value) } />
|
||||
</Flex>);
|
||||
}) }
|
||||
</div>
|
||||
</> }
|
||||
</> }
|
||||
{ (customKeys.length > 0) &&
|
||||
<>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[.5] h-px" />
|
||||
<div className="flex flex-col gap-1">
|
||||
{ customKeys.map((key, index) =>
|
||||
{
|
||||
return (
|
||||
<Flex key={ index } alignItems="center" gap={ 1 }>
|
||||
<Text small wrap align="end" className="col-span-4" variant="white">{ key }</Text>
|
||||
<NitroInput type="text" value={ customValues[index] } onChange={ event => onCustomVariableChange(index, event.target.value) } />
|
||||
</Flex>);
|
||||
}) }
|
||||
</div>
|
||||
</> }
|
||||
</div>
|
||||
</Column>
|
||||
</Column>
|
||||
<Flex gap={ 1 } justifyContent="end">
|
||||
{ canMove &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('move') }>
|
||||
{ LocalizeText('infostand.button.move') }
|
||||
</Button> }
|
||||
{ canRotate &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('rotate') }>
|
||||
{ LocalizeText('infostand.button.rotate') }
|
||||
</Button> }
|
||||
{ (pickupMode !== PICKUP_MODE_NONE) &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('pickup') }>
|
||||
{ LocalizeText((pickupMode === PICKUP_MODE_EJECT) ? 'infostand.button.eject' : 'infostand.button.pickup') }
|
||||
</Button> }
|
||||
{ canUse &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('use') }>
|
||||
{ LocalizeText('infostand.button.use') }
|
||||
</Button> }
|
||||
{ ((furniKeys.length > 0 && furniValues.length > 0) && (furniKeys.length === furniValues.length)) &&
|
||||
<Button variant="dark" onClick={ () => processButtonAction('save_branding_configuration') }>
|
||||
{ LocalizeText('save') }
|
||||
</Button> }
|
||||
{ ((customKeys.length > 0 && customValues.length > 0) && (customKeys.length === customValues.length)) &&
|
||||
<Button variant="dark" onClick={ () => processButtonAction('save_custom_variables') }>
|
||||
{ LocalizeText('save') }
|
||||
</Button> }
|
||||
</Flex>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,343 @@
|
||||
import { CreateLinkEvent, PetRespectComposer, PetType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState, useCallback } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { AvatarInfoPet, ConvertSeconds, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Button, Column, Flex, LayoutCounterTimeView, LayoutPetImageView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common';
|
||||
import { useRoom, useSessionInfo } from '../../../../../hooks';
|
||||
|
||||
// TypeScript interface for AvatarInfoPet
|
||||
interface AvatarInfoPet {
|
||||
id: number;
|
||||
name: string;
|
||||
petType: number;
|
||||
petBreed: number;
|
||||
petFigure: string;
|
||||
posture: string;
|
||||
level: number;
|
||||
maximumLevel: number;
|
||||
age: number;
|
||||
ownerId: number;
|
||||
ownerName: string;
|
||||
respect: number;
|
||||
dead?: boolean;
|
||||
energy?: number;
|
||||
maximumEnergy?: number;
|
||||
happyness?: number;
|
||||
maximumHappyness?: number;
|
||||
experience?: number;
|
||||
levelExperienceGoal?: number;
|
||||
remainingGrowTime?: number;
|
||||
remainingTimeToLive?: number;
|
||||
maximumTimeToLive?: number;
|
||||
rarityLevel?: number;
|
||||
isOwner?: boolean;
|
||||
}
|
||||
|
||||
interface InfoStandWidgetPetViewProps {
|
||||
avatarInfo: AvatarInfoPet;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const PetHeader: FC<{ name: string; petType: number; petBreed: number; onClose: () => void }> = ({ name, petType, petBreed, onClose }) => (
|
||||
<div className="flex flex-col gap-1">
|
||||
<Flex alignItems="center" gap={1} justifyContent="between">
|
||||
<Text small wrap variant="white">
|
||||
{name}
|
||||
</Text>
|
||||
<FaTimes
|
||||
className="cursor-pointer fa-icon"
|
||||
onClick={onClose}
|
||||
aria-label={LocalizeText('generic.close')}
|
||||
title={LocalizeText('generic.close')}
|
||||
/>
|
||||
</Flex>
|
||||
<Text small wrap variant="white">
|
||||
{LocalizeText(`pet.breed.${petType}.${petBreed}`)}
|
||||
</Text>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const MonsterplantStats: FC<{
|
||||
avatarInfo: AvatarInfoPet;
|
||||
remainingGrowTime: number;
|
||||
remainingTimeToLive: number;
|
||||
}> = ({ avatarInfo, remainingGrowTime, remainingTimeToLive }) => (
|
||||
<>
|
||||
<Column center gap={1}>
|
||||
<LayoutPetImageView direction={4} figure={avatarInfo.petFigure} posture={avatarInfo.posture} />
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
<div className="flex flex-col gap-2">
|
||||
{!avatarInfo.dead && (
|
||||
<Column alignItems="center" gap={1}>
|
||||
<Text center small wrap variant="white">
|
||||
{LocalizeText('pet.level', ['level', 'maxlevel'], [avatarInfo.level.toString(), avatarInfo.maximumLevel.toString()])}
|
||||
</Text>
|
||||
</Column>
|
||||
)}
|
||||
<Column alignItems="center" gap={1}>
|
||||
<Text small truncate variant="white">
|
||||
{LocalizeText('infostand.pet.text.wellbeing')}
|
||||
</Text>
|
||||
<div className="bg-light-dark rounded relative overflow-hidden w-full">
|
||||
<div className="flex justify-center items-center size-full absolute">
|
||||
<Text small variant="white">
|
||||
{avatarInfo.dead || remainingTimeToLive <= 0
|
||||
? '00:00:00'
|
||||
: `${ConvertSeconds(remainingTimeToLive).split(':')[1]}:${ConvertSeconds(remainingTimeToLive).split(':')[2]}:${ConvertSeconds(remainingTimeToLive).split(':')[3]}`}
|
||||
</Text>
|
||||
</div>
|
||||
<div
|
||||
className="bg-success rounded pet-stats"
|
||||
style={{
|
||||
width: avatarInfo.dead || remainingTimeToLive <= 0 ? '0' : `${(remainingTimeToLive / avatarInfo.maximumTimeToLive) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Column>
|
||||
{remainingGrowTime > 0 && (
|
||||
<Column alignItems="center" gap={1}>
|
||||
<Text small truncate variant="white">
|
||||
{LocalizeText('infostand.pet.text.growth')}
|
||||
</Text>
|
||||
<LayoutCounterTimeView
|
||||
className="top-2 end-2"
|
||||
day={ConvertSeconds(remainingGrowTime).split(':')[0]}
|
||||
hour={ConvertSeconds(remainingGrowTime).split(':')[1]}
|
||||
minutes={ConvertSeconds(remainingGrowTime).split(':')[2]}
|
||||
seconds={ConvertSeconds(remainingGrowTime).split(':')[3]}
|
||||
/>
|
||||
</Column>
|
||||
)}
|
||||
<Column alignItems="center" gap={1}>
|
||||
<Text small truncate variant="white">
|
||||
{LocalizeText('infostand.pet.text.raritylevel', ['level'], [LocalizeText(`infostand.pet.raritylevel.${avatarInfo.rarityLevel}`)])}
|
||||
</Text>
|
||||
<LayoutRarityLevelView className="top-2 end-2" level={avatarInfo.rarityLevel} />
|
||||
</Column>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text small wrap variant="white">
|
||||
{LocalizeText('pet.age', ['age'], [avatarInfo.age.toString()])}
|
||||
</Text>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// Sub-component: Regular Pet Stats
|
||||
const RegularPetStats: FC<{ avatarInfo: AvatarInfoPet }> = ({ avatarInfo }) => (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1">
|
||||
<Column fullWidth className="body-image pet p-1" overflow="hidden">
|
||||
<LayoutPetImageView direction={4} figure={avatarInfo.petFigure} posture={avatarInfo.posture} />
|
||||
</Column>
|
||||
<Column grow gap={1}>
|
||||
<Text center small wrap variant="white">
|
||||
{LocalizeText('pet.level', ['level', 'maxlevel'], [avatarInfo.level.toString(), avatarInfo.maximumLevel.toString()])}
|
||||
</Text>
|
||||
<Column alignItems="center" gap={1}>
|
||||
<Text small truncate variant="white">
|
||||
{LocalizeText('infostand.pet.text.happiness')}
|
||||
</Text>
|
||||
<div className="bg-light-dark rounded relative overflow-hidden w-full">
|
||||
<div className="flex justify-center items-center size-full absolute">
|
||||
<Text small variant="white">
|
||||
{avatarInfo.happyness + '/' + avatarInfo.maximumHappyness}
|
||||
</Text>
|
||||
</div>
|
||||
<div
|
||||
className="bg-info rounded pet-stats"
|
||||
style={{ width: (avatarInfo.happyness / avatarInfo.maximumHappyness) * 100 + '%' }}
|
||||
/>
|
||||
</div>
|
||||
</Column>
|
||||
<Column alignItems="center" gap={1}>
|
||||
<Text small truncate variant="white">
|
||||
{LocalizeText('infostand.pet.text.experience')}
|
||||
</Text>
|
||||
<div className="bg-light-dark rounded relative overflow-hidden w-full">
|
||||
<div className="flex justify-center items-center size-full absolute">
|
||||
<Text small variant="white">
|
||||
{avatarInfo.experience + '/' + avatarInfo.levelExperienceGoal}
|
||||
</Text>
|
||||
</div>
|
||||
<div
|
||||
className="bg-purple rounded pet-stats"
|
||||
style={{ width: (avatarInfo.experience / avatarInfo.levelExperienceGoal) * 100 + '%' }}
|
||||
/>
|
||||
</div>
|
||||
</Column>
|
||||
<Column alignItems="center" gap={1}>
|
||||
<Text small truncate variant="white">
|
||||
{LocalizeText('infostand.pet.text.energy')}
|
||||
</Text>
|
||||
<div className="bg-light-dark rounded relative overflow-hidden w-full">
|
||||
<div className="flex justify-center items-center size-full absolute">
|
||||
<Text small variant="white">
|
||||
{avatarInfo.energy + '/' + avatarInfo.maximumEnergy}
|
||||
</Text>
|
||||
</div>
|
||||
<div
|
||||
className="bg-success rounded pet-stats"
|
||||
style={{ width: (avatarInfo.energy / avatarInfo.maximumEnergy) * 100 + '%' }}
|
||||
/>
|
||||
</div>
|
||||
</Column>
|
||||
</Column>
|
||||
</div>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text small wrap variant="white">
|
||||
{LocalizeText('infostand.text.petrespect', ['count'], [avatarInfo.respect.toString()])}
|
||||
</Text>
|
||||
<Text small wrap variant="white">
|
||||
{LocalizeText('pet.age', ['age'], [avatarInfo.age.toString()])}
|
||||
</Text>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = ({ avatarInfo, onClose }) => {
|
||||
const [remainingGrowTime, setRemainingGrowTime] = useState(0);
|
||||
const [remainingTimeToLive, setRemainingTimeToLive] = useState(0);
|
||||
const { roomSession = null } = useRoom();
|
||||
const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
|
||||
|
||||
useEffect(() => {
|
||||
setRemainingGrowTime(avatarInfo.remainingGrowTime || 0);
|
||||
setRemainingTimeToLive(avatarInfo.remainingTimeToLive || 0);
|
||||
}, [avatarInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (avatarInfo.petType !== PetType.MONSTERPLANT || avatarInfo.dead) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setRemainingGrowTime((prev) => (prev <= 0 ? 0 : prev - 1));
|
||||
setRemainingTimeToLive((prev) => (prev <= 0 ? 0 : prev - 1));
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [avatarInfo]);
|
||||
|
||||
const processButtonAction = useCallback(
|
||||
async (action: string) => {
|
||||
try {
|
||||
let hideMenu = true;
|
||||
if (!action) return;
|
||||
|
||||
switch (action) {
|
||||
case 'respect':
|
||||
await respectPet(avatarInfo.id);
|
||||
if (petRespectRemaining - 1 >= 1) hideMenu = false;
|
||||
break;
|
||||
case 'buyfood':
|
||||
CreateLinkEvent('catalog/open/' + GetConfigurationValue('catalog.links')['pets.buy_food']);
|
||||
break;
|
||||
case 'train':
|
||||
roomSession?.requestPetCommands(avatarInfo.id);
|
||||
break;
|
||||
case 'treat':
|
||||
SendMessageComposer(new PetRespectComposer(avatarInfo.id));
|
||||
break;
|
||||
case 'compost':
|
||||
roomSession?.compostPlant(avatarInfo.id);
|
||||
break;
|
||||
case 'pick_up':
|
||||
roomSession?.pickupPet(avatarInfo.id);
|
||||
break;
|
||||
}
|
||||
|
||||
if (hideMenu) onClose();
|
||||
} catch (error) {
|
||||
console.error(`Failed to process action ${action}:`, error);
|
||||
}
|
||||
},
|
||||
[avatarInfo, petRespectRemaining, respectPet, roomSession, onClose]
|
||||
);
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
action: 'buyfood',
|
||||
label: LocalizeText('infostand.button.buyfood'),
|
||||
condition: avatarInfo.petType !== PetType.MONSTERPLANT,
|
||||
},
|
||||
{
|
||||
action: 'train',
|
||||
label: LocalizeText('infostand.button.train'),
|
||||
condition: avatarInfo.isOwner && avatarInfo.petType !== PetType.MONSTERPLANT,
|
||||
},
|
||||
{
|
||||
action: 'treat',
|
||||
label: LocalizeText('infostand.button.pettreat'),
|
||||
condition:
|
||||
!avatarInfo.dead &&
|
||||
avatarInfo.petType === PetType.MONSTERPLANT &&
|
||||
avatarInfo.energy / avatarInfo.maximumEnergy < 0.98,
|
||||
},
|
||||
{
|
||||
action: 'compost',
|
||||
label: LocalizeText('infostand.button.compost'),
|
||||
condition: roomSession?.isRoomOwner && avatarInfo.petType === PetType.MONSTERPLANT,
|
||||
},
|
||||
{
|
||||
action: 'pick_up',
|
||||
label: LocalizeText('inventory.pets.pickup'),
|
||||
condition: avatarInfo.isOwner,
|
||||
},
|
||||
{
|
||||
action: 'respect',
|
||||
label: LocalizeText('infostand.button.petrespect', ['count'], [petRespectRemaining.toString()]),
|
||||
condition: petRespectRemaining > 0 && avatarInfo.petType !== PetType.MONSTERPLANT,
|
||||
},
|
||||
];
|
||||
|
||||
if (!avatarInfo) return <Text variant="white">{LocalizeText('generic.loading')}</Text>;
|
||||
|
||||
return (
|
||||
<Column alignItems="end" gap={1}>
|
||||
<Column className="nitro-infostand rounded">
|
||||
<Column className="container-fluid content-area" gap={1} overflow="visible">
|
||||
<PetHeader
|
||||
name={avatarInfo.name}
|
||||
petType={avatarInfo.petType}
|
||||
petBreed={avatarInfo.petBreed}
|
||||
onClose={onClose}
|
||||
/>
|
||||
{avatarInfo.petType === PetType.MONSTERPLANT ? (
|
||||
<MonsterplantStats
|
||||
avatarInfo={avatarInfo}
|
||||
remainingGrowTime={remainingGrowTime}
|
||||
remainingTimeToLive={remainingTimeToLive}
|
||||
/>
|
||||
) : (
|
||||
<RegularPetStats avatarInfo={avatarInfo} />
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<UserProfileIconView userId={avatarInfo.ownerId} />
|
||||
<Text small wrap variant="white">
|
||||
{LocalizeText('infostand.text.petowner', ['name'], [avatarInfo.ownerName])}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Column>
|
||||
</Column>
|
||||
<Flex gap={1} justifyContent="end">
|
||||
{buttons.map(
|
||||
(button) =>
|
||||
button.condition && (
|
||||
<Button key={button.action} variant="dark" onClick={() => processButtonAction(button.action)}>
|
||||
{button.label}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
</Flex>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import { BotRemoveComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { AvatarInfoRentableBot, BotSkillsEnum, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Button, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, Text, UserProfileIconView } from '../../../../../common';
|
||||
|
||||
interface InfoStandWidgetRentableBotViewProps
|
||||
{
|
||||
avatarInfo: AvatarInfoRentableBot;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const InfoStandWidgetRentableBotView: FC<InfoStandWidgetRentableBotViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
|
||||
const canPickup = useMemo(() =>
|
||||
{
|
||||
if(avatarInfo.botSkills.indexOf(BotSkillsEnum.NO_PICK_UP) >= 0) return false;
|
||||
|
||||
if(!avatarInfo.amIOwner && !avatarInfo.amIAnyRoomController) return false;
|
||||
|
||||
return true;
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
const pickupBot = () => SendMessageComposer(new BotRemoveComposer(avatarInfo.webID));
|
||||
|
||||
if(!avatarInfo) return;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-col nitro-infostand rounded">
|
||||
<div className="flex flex-col gap-1 overflow-visible container-fluid content-area">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Flex alignItems="center" gap={ 1 } justifyContent="between">
|
||||
<Text small wrap variant="white">{ avatarInfo.name }</Text>
|
||||
<FaTimes className="cursor-pointer fa-icon" onClick={ onClose } />
|
||||
</Flex>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1">
|
||||
<Column fullWidth className="body-image bot">
|
||||
<LayoutAvatarImageView direction={ 4 } figure={ avatarInfo.figure } />
|
||||
</Column>
|
||||
<Column center grow gap={ 0 }>
|
||||
{ (avatarInfo.badges.length > 0) && avatarInfo.badges.map(result =>
|
||||
{
|
||||
return <LayoutBadgeImageView key={ result } badgeCode={ result } showInfo={ true } />;
|
||||
}) }
|
||||
</Column>
|
||||
</div>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Flex alignItems="center" className="bg-light-dark rounded py-1 px-2">
|
||||
<Text fullWidth small textBreak wrap className="min-h-[18px]" variant="white">{ avatarInfo.motto }</Text>
|
||||
</Flex>
|
||||
<hr className="m-0" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<UserProfileIconView userId={ avatarInfo.ownerId } />
|
||||
<Text small wrap variant="white">
|
||||
{ LocalizeText('infostand.text.botowner', [ 'name' ], [ avatarInfo.ownerName ]) }
|
||||
</Text>
|
||||
</div>
|
||||
{ (avatarInfo.carryItem > 0) &&
|
||||
<>
|
||||
<hr className="m-0" />
|
||||
<Text small wrap variant="white">
|
||||
{ LocalizeText('infostand.text.handitem', [ 'item' ], [ LocalizeText('handitem' + avatarInfo.carryItem) ]) }
|
||||
</Text>
|
||||
</> }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ canPickup &&
|
||||
<div className="flex justify-end">
|
||||
<Button variant="dark" onClick={ pickupBot }>{ LocalizeText('infostand.button.pickup') }</Button>
|
||||
</div> }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { RelationshipStatusEnum, RelationshipStatusInfo } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { GetUserProfile, LocalizeText } from '../../../../../api';
|
||||
import { Flex, Text } from '../../../../../common';
|
||||
|
||||
interface InfoStandWidgetUserRelationshipsRelationshipItemViewProps
|
||||
{
|
||||
type: number;
|
||||
relationship: RelationshipStatusInfo;
|
||||
}
|
||||
|
||||
export const InfoStandWidgetUserRelationshipsRelationshipItemView: FC<InfoStandWidgetUserRelationshipsRelationshipItemViewProps> = props =>
|
||||
{
|
||||
const { type = -1, relationship = null } = props;
|
||||
|
||||
if(!relationship) return null;
|
||||
|
||||
const relationshipName = RelationshipStatusEnum.RELATIONSHIP_NAMES[type].toLocaleLowerCase();
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<i className={ `nitro-friends-spritesheet icon-${ relationshipName }` } />
|
||||
<Flex alignItems="center" gap={ 0 }>
|
||||
<Text small variant="white" onClick={ event => GetUserProfile(relationship.randomFriendId) }>
|
||||
<u>{ relationship.randomFriendName }</u>
|
||||
{ (relationship.friendCount > 1) && (' ' + LocalizeText(`extendedprofile.relstatus.others.${ relationshipName }`, [ 'count' ], [ (relationship.friendCount - 1).toString() ])) }
|
||||
</Text>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { RelationshipStatusEnum, RelationshipStatusInfoMessageParser } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { InfoStandWidgetUserRelationshipsRelationshipItemView } from './InfoStandWidgetUserRelationshipItemView';
|
||||
|
||||
interface InfoStandWidgetUserRelationshipsViewProps
|
||||
{
|
||||
relationships: RelationshipStatusInfoMessageParser;
|
||||
}
|
||||
|
||||
export const InfoStandWidgetUserRelationshipsView: FC<InfoStandWidgetUserRelationshipsViewProps> = props =>
|
||||
{
|
||||
const { relationships = null } = props;
|
||||
|
||||
if(!relationships || !relationships.relationshipStatusMap.length) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<InfoStandWidgetUserRelationshipsRelationshipItemView relationship={ relationships.relationshipStatusMap.getValue(RelationshipStatusEnum.HEART) } type={ RelationshipStatusEnum.HEART } />
|
||||
<InfoStandWidgetUserRelationshipsRelationshipItemView relationship={ relationships.relationshipStatusMap.getValue(RelationshipStatusEnum.SMILE) } type={ RelationshipStatusEnum.SMILE } />
|
||||
<InfoStandWidgetUserRelationshipsRelationshipItemView relationship={ relationships.relationshipStatusMap.getValue(RelationshipStatusEnum.BOBBA) } type={ RelationshipStatusEnum.BOBBA } />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { CreateLinkEvent, NavigatorSearchComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { SendMessageComposer } from '../../../../../api';
|
||||
import { Flex, Text } from '../../../../../common';
|
||||
|
||||
interface InfoStandWidgetUserTagsViewProps
|
||||
{
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
const processAction = (tag: string) =>
|
||||
{
|
||||
CreateLinkEvent(`navigator/search/${ tag }`);
|
||||
SendMessageComposer(new NavigatorSearchComposer('hotel_view', `tag:${ tag }`));
|
||||
};
|
||||
|
||||
export const InfoStandWidgetUserTagsView: FC<InfoStandWidgetUserTagsViewProps> = props =>
|
||||
{
|
||||
const { tags = null } = props;
|
||||
|
||||
if(!tags || !tags.length) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr className="m-0" />
|
||||
<Flex className="flex-tags">
|
||||
{ tags && (tags.length > 0) && tags.map((tag, index) => <Text key={ index } className="text-tags" variant="white" onClick={ event => processAction(tag) }>{ tag }</Text>) }
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,262 @@
|
||||
import { GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionFavoriteGroupUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, FC, FocusEvent, KeyboardEvent, SetStateAction, useCallback, useEffect, useState } from 'react';
|
||||
import { FaPencilAlt, FaTimes } from 'react-icons/fa';
|
||||
import { AvatarInfoUser, CloneObject, GetConfigurationValue, GetGroupInformation, GetUserProfile, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Base, Column, Flex, LayoutAvatarImageView, LayoutBadgeImageView, Text, UserProfileIconView } from '../../../../../common';
|
||||
import { useMessageEvent, useNitroEvent, useRoom } from '../../../../../hooks';
|
||||
import { InfoStandWidgetUserRelationshipsView } from './InfoStandWidgetUserRelationshipsView';
|
||||
import { InfoStandWidgetUserTagsView } from './InfoStandWidgetUserTagsView';
|
||||
import { BackgroundsView } from '../../../../backgrounds/BackgroundsView';
|
||||
|
||||
interface InfoStandWidgetUserViewProps {
|
||||
avatarInfo: AvatarInfoUser;
|
||||
setAvatarInfo: Dispatch<SetStateAction<AvatarInfoUser>>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const InfoStandWidgetUserView: FC<InfoStandWidgetUserViewProps> = props => {
|
||||
const { avatarInfo = null, setAvatarInfo = null, onClose = null } = props;
|
||||
const [motto, setMotto] = useState<string>(null);
|
||||
const [isEditingMotto, setIsEditingMotto] = useState(false);
|
||||
const [relationships, setRelationships] = useState<RelationshipStatusInfoMessageParser>(null);
|
||||
const [backgroundId, setBackgroundId] = useState<number>(null);
|
||||
const [standId, setStandId] = useState<number>(null);
|
||||
const [overlayId, setOverlayId] = useState<number>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
const infostandBackgroundClass = `background-${backgroundId ?? 'default'}`;
|
||||
const infostandStandClass = `stand-${standId ?? 'default'}`;
|
||||
const infostandOverlayClass = `overlay-${overlayId ?? 'default'}`;
|
||||
|
||||
const handleProfileClick = useCallback(() => { GetUserProfile(avatarInfo.webID); }, [avatarInfo.webID]);
|
||||
|
||||
const handleEditClick = useCallback((event: React.MouseEvent) => { event.stopPropagation(); setIsVisible(prev => !prev); }, []);
|
||||
|
||||
const saveMotto = (motto: string) => {
|
||||
if (!isEditingMotto || motto.length > GetConfigurationValue<number>('motto.max.length', 38) || !roomSession) return;
|
||||
|
||||
roomSession.sendMottoMessage(motto);
|
||||
setIsEditingMotto(false);
|
||||
};
|
||||
|
||||
const onMottoBlur = (event: FocusEvent<HTMLInputElement>) => saveMotto(event.target.value);
|
||||
|
||||
const onMottoKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
event.stopPropagation();
|
||||
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
saveMotto((event.target as HTMLInputElement).value);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
useNitroEvent<RoomSessionUserBadgesEvent>(RoomSessionUserBadgesEvent.RSUBE_BADGES, event => {
|
||||
if (!avatarInfo || avatarInfo.webID !== event.userId) return;
|
||||
|
||||
const oldBadges = avatarInfo.badges.join('');
|
||||
|
||||
if (oldBadges === event.badges.join('')) return;
|
||||
|
||||
setAvatarInfo(prevValue => {
|
||||
const newValue = CloneObject(prevValue);
|
||||
newValue.badges = event.badges;
|
||||
return newValue;
|
||||
});
|
||||
});
|
||||
|
||||
useNitroEvent<RoomSessionUserFigureUpdateEvent>(RoomSessionUserFigureUpdateEvent.USER_FIGURE, event => {
|
||||
if (!avatarInfo || avatarInfo.roomIndex !== event.roomIndex) return;
|
||||
|
||||
setAvatarInfo(prevValue => {
|
||||
const newValue = CloneObject(prevValue);
|
||||
newValue.figure = event.figure;
|
||||
newValue.motto = event.customInfo;
|
||||
newValue.achievementScore = event.activityPoints;
|
||||
newValue.backgroundId = event.backgroundId;
|
||||
newValue.standId = event.standId;
|
||||
newValue.overlayId = event.overlayId;
|
||||
return newValue;
|
||||
});
|
||||
});
|
||||
|
||||
useNitroEvent<RoomSessionFavoriteGroupUpdateEvent>(RoomSessionFavoriteGroupUpdateEvent.FAVOURITE_GROUP_UPDATE, event => {
|
||||
if (!avatarInfo || avatarInfo.roomIndex !== event.roomIndex) return;
|
||||
|
||||
setAvatarInfo(prevValue => {
|
||||
const newValue = CloneObject(prevValue);
|
||||
const clearGroup = (event.status === -1) || (event.habboGroupId <= 0);
|
||||
|
||||
newValue.groupId = clearGroup ? -1 : event.habboGroupId;
|
||||
newValue.groupName = clearGroup ? null : event.habboGroupName;
|
||||
newValue.groupBadgeId = clearGroup ? null : GetSessionDataManager().getGroupBadge(event.habboGroupId);
|
||||
return newValue;
|
||||
});
|
||||
});
|
||||
|
||||
useMessageEvent<RelationshipStatusInfoEvent>(RelationshipStatusInfoEvent, event => {
|
||||
const parser = event.getParser();
|
||||
|
||||
if (!avatarInfo || avatarInfo.webID !== parser.userId) return;
|
||||
|
||||
setRelationships(parser);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setIsEditingMotto(false);
|
||||
setMotto(avatarInfo.motto);
|
||||
setBackgroundId(avatarInfo.backgroundId);
|
||||
setStandId(avatarInfo.standId);
|
||||
setOverlayId(avatarInfo.overlayId);
|
||||
|
||||
SendMessageComposer(new UserRelationshipsComposer(avatarInfo.webID));
|
||||
|
||||
return () => {
|
||||
setIsEditingMotto(false);
|
||||
setMotto(null);
|
||||
setRelationships(null);
|
||||
setBackgroundId(null);
|
||||
setStandId(null);
|
||||
setOverlayId(null);
|
||||
};
|
||||
}, [avatarInfo]);
|
||||
|
||||
if (!avatarInfo) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column className="relative min-w-[190px] max-w-[190px] z-30 pointer-events-auto bg-[rgba(28,28,32,0.95)] [box-shadow:inset_0_5px_#22222799,_inset_0_-4px_#12121599] rounded">
|
||||
<Column className="h-full p-[8px] overflow-auto" gap={1} overflow="visible">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<UserProfileIconView userId={avatarInfo.webID} />
|
||||
<Text small wrap variant="white">{avatarInfo.name}</Text>
|
||||
</div>
|
||||
<FaTimes className="cursor-pointer fa-icon" onClick={onClose} />
|
||||
</div>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[0.5] h-px" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1">
|
||||
<Column
|
||||
fullWidth
|
||||
className={`flex items-center w-full max-w-[68px] rounded-[0.25rem] profile-background ${infostandBackgroundClass}`}
|
||||
onClick={handleProfileClick}
|
||||
>
|
||||
<Base position="absolute" className={`profile-stand ${infostandStandClass}`} />
|
||||
<LayoutAvatarImageView direction={2} figure={avatarInfo.figure} />
|
||||
<Base position="absolute" className={`profile-overlay ${infostandOverlayClass}`} />
|
||||
{avatarInfo.type === AvatarInfoUser.OWN_USER && (
|
||||
<Base
|
||||
className="background-edit-icon background-edit-position"
|
||||
style={{ pointerEvents: 'auto', cursor: 'pointer' }}
|
||||
onClick={handleEditClick}
|
||||
aria-label="Edit profile background"
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
<Column grow alignItems="center" gap={0}>
|
||||
<div className="flex gap-1">
|
||||
<div className="flex items-center justify-center relative w-[40px] h-[40px] bg-no-repeat bg-center">
|
||||
{avatarInfo.badges[0] && <LayoutBadgeImageView badgeCode={avatarInfo.badges[0]} showInfo={true} />}
|
||||
</div>
|
||||
<Flex center className="relative w-[40px] h-[40px] bg-no-repeat bg-center" pointer={avatarInfo.groupId > 0} onClick={event => GetGroupInformation(avatarInfo.groupId)}>
|
||||
{avatarInfo.groupId > 0 &&
|
||||
<LayoutBadgeImageView badgeCode={avatarInfo.groupBadgeId} customTitle={avatarInfo.groupName} isGroup={true} showInfo={true} />}
|
||||
</Flex>
|
||||
</div>
|
||||
<Flex center gap={1}>
|
||||
<div className="flex items-center justify-center relative w-[40px] h-[40px] bg-no-repeat bg-center">
|
||||
{avatarInfo.badges[1] && <LayoutBadgeImageView badgeCode={avatarInfo.badges[1]} showInfo={true} />}
|
||||
</div>
|
||||
<div className="flex items-center justify-center relative w-[40px] h-[40px] bg-no-repeat bg-center">
|
||||
{avatarInfo.badges[2] && <LayoutBadgeImageView badgeCode={avatarInfo.badges[2]} showInfo={true} />}
|
||||
</div>
|
||||
</Flex>
|
||||
<Flex center gap={1}>
|
||||
<div className="flex items-center justify-center relative w-[40px] h-[40px] bg-no-repeat bg-center">
|
||||
{avatarInfo.badges[3] && <LayoutBadgeImageView badgeCode={avatarInfo.badges[3]} showInfo={true} />}
|
||||
</div>
|
||||
<div className="flex items-center justify-center relative w-[40px] h-[40px] bg-no-repeat bg-center">
|
||||
{avatarInfo.badges[4] && <LayoutBadgeImageView badgeCode={avatarInfo.badges[4]} showInfo={true} />}
|
||||
</div>
|
||||
</Flex>
|
||||
</Column>
|
||||
</div>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[0.5] h-px" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Flex alignItems="center" className="bg-light-dark rounded py-1 px-2">
|
||||
{avatarInfo.type !== AvatarInfoUser.OWN_USER && (
|
||||
<Flex grow alignItems="center" className="min-h-[18px]">
|
||||
<Text fullWidth pointer small textBreak wrap variant="white">{motto}</Text>
|
||||
</Flex>
|
||||
)}
|
||||
{avatarInfo.type === AvatarInfoUser.OWN_USER && (
|
||||
<Flex grow alignItems="center" gap={2}>
|
||||
<FaPencilAlt className="small fa-icon" />
|
||||
<Flex grow alignItems="center" className="min-h-[18px]">
|
||||
{!isEditingMotto && (
|
||||
<Text fullWidth pointer small textBreak wrap variant="white" onClick={event => setIsEditingMotto(true)}>
|
||||
{motto}
|
||||
</Text>
|
||||
)}
|
||||
{isEditingMotto && (
|
||||
<input
|
||||
autoFocus={true}
|
||||
className="w-full h-full text-[12px] p-0 outline-[0] border-[0] text-[#fff] relative bg-transparent resize-none focus:italic border-transparent focus:border-transparent focus:ring-0"
|
||||
maxLength={GetConfigurationValue<number>('motto.max.length', 38)}
|
||||
type="text"
|
||||
value={motto}
|
||||
onBlur={onMottoBlur}
|
||||
onChange={event => setMotto(event.target.value)}
|
||||
onKeyDown={onMottoKeyDown}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[0.5] h-px" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text small wrap variant="white">
|
||||
{LocalizeText('infostand.text.achievement_score') + ' ' + avatarInfo.achievementScore}
|
||||
</Text>
|
||||
{avatarInfo.carryItem > 0 && (
|
||||
<>
|
||||
<hr className="m-0 bg-[#0003] border-[0] opacity-[0.5] h-px" />
|
||||
<Text small wrap variant="white">
|
||||
{LocalizeText('infostand.text.handitem', ['item'], [LocalizeText('handitem' + avatarInfo.carryItem)])}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<InfoStandWidgetUserRelationshipsView relationships={relationships} />
|
||||
</div>
|
||||
{GetConfigurationValue('user.tags.enabled') && (
|
||||
<Column className="mt-1" gap={1}>
|
||||
<InfoStandWidgetUserTagsView tags={GetSessionDataManager().tags} />
|
||||
</Column>
|
||||
)}
|
||||
</Column>
|
||||
</Column>
|
||||
{isVisible && avatarInfo.type === AvatarInfoUser.OWN_USER && (
|
||||
<div className="backgrounds-view-container">
|
||||
<BackgroundsView
|
||||
setIsVisible={setIsVisible}
|
||||
selectedBackground={backgroundId}
|
||||
setSelectedBackground={setBackgroundId}
|
||||
selectedStand={standId}
|
||||
setSelectedStand={setStandId}
|
||||
selectedOverlay={overlayId}
|
||||
setSelectedOverlay={setOverlayId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,372 @@
|
||||
import { CreateLinkEvent, GetSessionDataManager, RoomControllerLevel, RoomObjectCategory, RoomObjectVariable, RoomUnitGiveHandItemComposer, SetRelationshipStatusComposer, TradingOpenComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
|
||||
import { AvatarInfoUser, DispatchUiEvent, GetOwnRoomObject, GetUserProfile, LocalizeText, MessengerFriend, ReportType, RoomWidgetUpdateChatInputContentEvent, SendMessageComposer } from '../../../../../api';
|
||||
import { Flex } from '../../../../../common';
|
||||
import { useFriends, useHelp, useRoom, useSessionInfo } from '../../../../../hooks';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoWidgetAvatarViewProps
|
||||
{
|
||||
avatarInfo: AvatarInfoUser;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const MODE_NORMAL = 0;
|
||||
const MODE_MODERATE = 1;
|
||||
const MODE_MODERATE_BAN = 2;
|
||||
const MODE_MODERATE_MUTE = 3;
|
||||
const MODE_AMBASSADOR = 4;
|
||||
const MODE_AMBASSADOR_MUTE = 5;
|
||||
const MODE_RELATIONSHIP = 6;
|
||||
|
||||
export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(MODE_NORMAL);
|
||||
const { canRequestFriend = null } = useFriends();
|
||||
const { report = null } = useHelp();
|
||||
const { roomSession = null } = useRoom();
|
||||
const { userRespectRemaining = 0, respectUser = null } = useSessionInfo();
|
||||
|
||||
const isShowGiveRights = useMemo(() =>
|
||||
{
|
||||
return (avatarInfo.amIOwner && (avatarInfo.targetRoomControllerLevel < RoomControllerLevel.GUEST) && !avatarInfo.isGuildRoom);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
const isShowRemoveRights = useMemo(() =>
|
||||
{
|
||||
return (avatarInfo.amIOwner && (avatarInfo.targetRoomControllerLevel === RoomControllerLevel.GUEST) && !avatarInfo.isGuildRoom);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
const moderateMenuHasContent = useMemo(() =>
|
||||
{
|
||||
return (avatarInfo.canBeKicked || avatarInfo.canBeBanned || avatarInfo.canBeMuted || isShowGiveRights || isShowRemoveRights);
|
||||
}, [ isShowGiveRights, isShowRemoveRights, avatarInfo ]);
|
||||
|
||||
const canGiveHandItem = useMemo(() =>
|
||||
{
|
||||
let flag = false;
|
||||
|
||||
const roomObject = GetOwnRoomObject();
|
||||
|
||||
if(roomObject)
|
||||
{
|
||||
const carryId = roomObject.model.getValue<number>(RoomObjectVariable.FIGURE_CARRY_OBJECT);
|
||||
|
||||
if((carryId > 0) && (carryId < 999999)) flag = true;
|
||||
}
|
||||
|
||||
return flag;
|
||||
}, []);
|
||||
|
||||
const processAction = (name: string) =>
|
||||
{
|
||||
let hideMenu = true;
|
||||
|
||||
if(name)
|
||||
{
|
||||
switch(name)
|
||||
{
|
||||
case 'moderate':
|
||||
hideMenu = false;
|
||||
setMode(MODE_MODERATE);
|
||||
break;
|
||||
case 'ban':
|
||||
hideMenu = false;
|
||||
setMode(MODE_MODERATE_BAN);
|
||||
break;
|
||||
case 'mute':
|
||||
hideMenu = false;
|
||||
setMode(MODE_MODERATE_MUTE);
|
||||
break;
|
||||
case 'ambassador':
|
||||
hideMenu = false;
|
||||
setMode(MODE_AMBASSADOR);
|
||||
break;
|
||||
case 'ambassador_mute':
|
||||
hideMenu = false;
|
||||
setMode(MODE_AMBASSADOR_MUTE);
|
||||
break;
|
||||
case 'back_moderate':
|
||||
hideMenu = false;
|
||||
setMode(MODE_MODERATE);
|
||||
break;
|
||||
case 'back_ambassador':
|
||||
hideMenu = false;
|
||||
setMode(MODE_AMBASSADOR);
|
||||
break;
|
||||
case 'back':
|
||||
hideMenu = false;
|
||||
setMode(MODE_NORMAL);
|
||||
break;
|
||||
case 'whisper':
|
||||
DispatchUiEvent(new RoomWidgetUpdateChatInputContentEvent(RoomWidgetUpdateChatInputContentEvent.WHISPER, avatarInfo.name));
|
||||
break;
|
||||
case 'friend':
|
||||
CreateLinkEvent(`friends/request/${ avatarInfo.webID }/${ avatarInfo.name }`);
|
||||
break;
|
||||
case 'relationship':
|
||||
hideMenu = false;
|
||||
setMode(MODE_RELATIONSHIP);
|
||||
break;
|
||||
case 'respect': {
|
||||
respectUser(avatarInfo.webID);
|
||||
|
||||
if((userRespectRemaining - 1) >= 1) hideMenu = false;
|
||||
break;
|
||||
}
|
||||
case 'ignore':
|
||||
GetSessionDataManager().ignoreUser(avatarInfo.name);
|
||||
break;
|
||||
case 'unignore':
|
||||
GetSessionDataManager().unignoreUser(avatarInfo.name);
|
||||
break;
|
||||
case 'kick':
|
||||
roomSession.sendKickMessage(avatarInfo.webID);
|
||||
break;
|
||||
case 'ban_hour':
|
||||
roomSession.sendBanMessage(avatarInfo.webID, 'RWUAM_BAN_USER_HOUR');
|
||||
break;
|
||||
case 'ban_day':
|
||||
roomSession.sendBanMessage(avatarInfo.webID, 'RWUAM_BAN_USER_DAY');
|
||||
break;
|
||||
case 'perm_ban':
|
||||
roomSession.sendBanMessage(avatarInfo.webID, 'RWUAM_BAN_USER_PERM');
|
||||
break;
|
||||
case 'mute_2min':
|
||||
roomSession.sendMuteMessage(avatarInfo.webID, 2);
|
||||
break;
|
||||
case 'mute_5min':
|
||||
roomSession.sendMuteMessage(avatarInfo.webID, 5);
|
||||
break;
|
||||
case 'mute_10min':
|
||||
roomSession.sendMuteMessage(avatarInfo.webID, 10);
|
||||
break;
|
||||
case 'give_rights':
|
||||
roomSession.sendGiveRightsMessage(avatarInfo.webID);
|
||||
break;
|
||||
case 'remove_rights':
|
||||
roomSession.sendTakeRightsMessage(avatarInfo.webID);
|
||||
break;
|
||||
case 'trade':
|
||||
SendMessageComposer(new TradingOpenComposer(avatarInfo.roomIndex));
|
||||
break;
|
||||
case 'report':
|
||||
report(ReportType.BULLY, { reportedUserId: avatarInfo.webID });
|
||||
break;
|
||||
case 'pass_hand_item':
|
||||
SendMessageComposer(new RoomUnitGiveHandItemComposer(avatarInfo.webID));
|
||||
break;
|
||||
case 'ambassador_alert':
|
||||
roomSession.sendAmbassadorAlertMessage(avatarInfo.webID);
|
||||
break;
|
||||
case 'ambassador_kick':
|
||||
roomSession.sendKickMessage(avatarInfo.webID);
|
||||
break;
|
||||
case 'ambassador_mute_2min':
|
||||
roomSession.sendMuteMessage(avatarInfo.webID, 2);
|
||||
break;
|
||||
case 'ambassador_mute_10min':
|
||||
roomSession.sendMuteMessage(avatarInfo.webID, 10);
|
||||
break;
|
||||
case 'ambassador_mute_60min':
|
||||
roomSession.sendMuteMessage(avatarInfo.webID, 60);
|
||||
break;
|
||||
case 'ambassador_mute_18hour':
|
||||
roomSession.sendMuteMessage(avatarInfo.webID, 1080);
|
||||
break;
|
||||
case 'rship_heart':
|
||||
SendMessageComposer(new SetRelationshipStatusComposer(avatarInfo.webID, MessengerFriend.RELATIONSHIP_HEART));
|
||||
break;
|
||||
case 'rship_smile':
|
||||
SendMessageComposer(new SetRelationshipStatusComposer(avatarInfo.webID, MessengerFriend.RELATIONSHIP_SMILE));
|
||||
break;
|
||||
case 'rship_bobba':
|
||||
SendMessageComposer(new SetRelationshipStatusComposer(avatarInfo.webID, MessengerFriend.RELATIONSHIP_BOBBA));
|
||||
break;
|
||||
case 'rship_none':
|
||||
SendMessageComposer(new SetRelationshipStatusComposer(avatarInfo.webID, MessengerFriend.RELATIONSHIP_NONE));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(hideMenu) onClose();
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setMode(MODE_NORMAL);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ RoomObjectCategory.UNIT } collapsable={ true } objectId={ avatarInfo.roomIndex } userType={ avatarInfo.userType } onClose={ onClose }>
|
||||
<ContextMenuHeaderView className="cursor-pointer" onClick={ event => GetUserProfile(avatarInfo.webID) }>
|
||||
{ avatarInfo.name }
|
||||
</ContextMenuHeaderView>
|
||||
{ (mode === MODE_NORMAL) &&
|
||||
<>
|
||||
{ canRequestFriend(avatarInfo.webID) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('friend') }>
|
||||
{ LocalizeText('infostand.button.friend') }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('trade') }>
|
||||
{ LocalizeText('infostand.button.trade') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('whisper') }>
|
||||
{ LocalizeText('infostand.button.whisper') }
|
||||
</ContextMenuListItemView>
|
||||
{ (userRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.respect', [ 'count' ], [ userRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
{ !canRequestFriend(avatarInfo.webID) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('relationship') }>
|
||||
{ LocalizeText('infostand.link.relationship') }
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
</ContextMenuListItemView> }
|
||||
{ !avatarInfo.isIgnored &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('ignore') }>
|
||||
{ LocalizeText('infostand.button.ignore') }
|
||||
</ContextMenuListItemView> }
|
||||
{ avatarInfo.isIgnored &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('unignore') }>
|
||||
{ LocalizeText('infostand.button.unignore') }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('report') }>
|
||||
{ LocalizeText('infostand.button.report') }
|
||||
</ContextMenuListItemView>
|
||||
{ moderateMenuHasContent &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('moderate') }>
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
{ LocalizeText('infostand.link.moderate') }
|
||||
</ContextMenuListItemView> }
|
||||
{ avatarInfo.isAmbassador &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('ambassador') }>
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
{ LocalizeText('infostand.link.ambassador') }
|
||||
</ContextMenuListItemView> }
|
||||
{ canGiveHandItem && <ContextMenuListItemView onClick={ event => processAction('pass_hand_item') }>
|
||||
{ LocalizeText('avatar.widget.pass_hand_item') }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_MODERATE) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('kick') }>
|
||||
{ LocalizeText('infostand.button.kick') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('mute') }>
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
{ LocalizeText('infostand.button.mute') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ban') }>
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
{ LocalizeText('infostand.button.ban') }
|
||||
</ContextMenuListItemView>
|
||||
{ isShowGiveRights &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('give_rights') }>
|
||||
{ LocalizeText('infostand.button.giverights') }
|
||||
</ContextMenuListItemView> }
|
||||
{ isShowRemoveRights &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('remove_rights') }>
|
||||
{ LocalizeText('infostand.button.removerights') }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('back') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
{ (mode === MODE_MODERATE_BAN) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ban_hour') }>
|
||||
{ LocalizeText('infostand.button.ban_hour') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ban_day') }>
|
||||
{ LocalizeText('infostand.button.ban_day') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('perm_ban') }>
|
||||
{ LocalizeText('infostand.button.perm_ban') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('back_moderate') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
{ (mode === MODE_MODERATE_MUTE) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('mute_2min') }>
|
||||
{ LocalizeText('infostand.button.mute_2min') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('mute_5min') }>
|
||||
{ LocalizeText('infostand.button.mute_5min') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('mute_10min') }>
|
||||
{ LocalizeText('infostand.button.mute_10min') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('back_moderate') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
{ (mode === MODE_AMBASSADOR) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ambassador_alert') }>
|
||||
{ LocalizeText('infostand.button.alert') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ambassador_kick') }>
|
||||
{ LocalizeText('infostand.button.kick') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ambassador_mute') }>
|
||||
{ LocalizeText('infostand.button.mute') }
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('back') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
{ (mode === MODE_AMBASSADOR_MUTE) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ambassador_mute_2min') }>
|
||||
{ LocalizeText('infostand.button.mute_2min') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ambassador_mute_10min') }>
|
||||
{ LocalizeText('infostand.button.mute_10min') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ambassador_mute_60min') }>
|
||||
{ LocalizeText('infostand.button.mute_60min') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('ambassador_mute_18hr') }>
|
||||
{ LocalizeText('infostand.button.mute_18hour') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('back_ambassador') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
{ (mode === MODE_RELATIONSHIP) &&
|
||||
<>
|
||||
<Flex className="menu-list-split-3">
|
||||
<ContextMenuListItemView onClick={ event => processAction('rship_heart') }>
|
||||
<div className="nitro-friends-spritesheet icon-heart cursor-pointer" />
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('rship_smile') }>
|
||||
<div className="nitro-friends-spritesheet icon-smile cursor-pointer" />
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('rship_bobba') }>
|
||||
<div className="nitro-friends-spritesheet icon-bobba cursor-pointer" />
|
||||
</ContextMenuListItemView>
|
||||
</Flex>
|
||||
<ContextMenuListItemView onClick={ event => processAction('rship_none') }>
|
||||
{ LocalizeText('avatar.widget.clear_relationship') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('back') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { RoomObjectCategory } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, FC, SetStateAction } from 'react';
|
||||
import { LocalizeText } from '../../../../../api';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuListView } from '../../context-menu/ContextMenuListView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoWidgetDecorateViewProps
|
||||
{
|
||||
userId: number;
|
||||
userName: string;
|
||||
roomIndex: number;
|
||||
setIsDecorating: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const AvatarInfoWidgetDecorateView: FC<AvatarInfoWidgetDecorateViewProps> = props =>
|
||||
{
|
||||
const { userId = -1, userName = '', roomIndex = -1, setIsDecorating = null } = props;
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ RoomObjectCategory.UNIT } objectId={ roomIndex } onClose={ null }>
|
||||
<ContextMenuListView>
|
||||
<ContextMenuListItemView onClick={ event => setIsDecorating(false) }>
|
||||
{ LocalizeText('widget.avatar.stop_decorating') }
|
||||
</ContextMenuListItemView>
|
||||
</ContextMenuListView>
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { RoomControllerLevel, RoomObjectOperationType } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { FaArrowsAlt, FaSyncAlt, FaTrashRestore } from 'react-icons/fa';
|
||||
import { AvatarInfoFurni, ProcessRoomObjectOperation } from '../../../../../api';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoWidgetFurniViewProps
|
||||
{
|
||||
avatarInfo: AvatarInfoFurni;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const AvatarInfoWidgetFurniView: FC<AvatarInfoWidgetFurniViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
|
||||
const processAction = (name: string) =>
|
||||
{
|
||||
let hideMenu = true;
|
||||
|
||||
if(name)
|
||||
{
|
||||
switch(name)
|
||||
{
|
||||
case 'move':
|
||||
ProcessRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_MOVE);
|
||||
break;
|
||||
case 'rotate':
|
||||
ProcessRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_ROTATE_POSITIVE);
|
||||
break;
|
||||
case 'pickup':
|
||||
ProcessRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_PICKUP);
|
||||
break;
|
||||
case 'eject':
|
||||
ProcessRoomObjectOperation(avatarInfo.id, avatarInfo.category, RoomObjectOperationType.OBJECT_EJECT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ avatarInfo.category } collapsable={ true } objectId={ avatarInfo.id } onClose={ onClose }>
|
||||
<ContextMenuHeaderView>
|
||||
{ avatarInfo.name }
|
||||
</ContextMenuHeaderView>
|
||||
<div className="flex menu-list-split-3">
|
||||
<ContextMenuListItemView onClick={ event => processAction('move') }>
|
||||
<FaArrowsAlt className="center fa-icon" />
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView disabled={ avatarInfo.isWallItem } onClick={ event => processAction('rotate') }>
|
||||
<FaSyncAlt className="center fa-icon" />
|
||||
</ContextMenuListItemView>
|
||||
{ (avatarInfo.isOwner || avatarInfo.isAnyRoomController) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('pickup') }>
|
||||
<FaTrashRestore className="center fa-icon" />
|
||||
</ContextMenuListItemView> }
|
||||
{ (!avatarInfo.isOwner && !avatarInfo.isAnyRoomController) && (avatarInfo.isRoomOwner || (avatarInfo.roomControllerLevel >= RoomControllerLevel.GUILD_ADMIN)) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('eject') }>
|
||||
<FaTrashRestore className="center fa-icon" />
|
||||
</ContextMenuListItemView> }
|
||||
</div>
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { GetSessionDataManager } from '@nitrots/nitro-renderer';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { AvatarInfoName } from '../../../../../api';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoWidgetNameViewProps
|
||||
{
|
||||
nameInfo: AvatarInfoName;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const AvatarInfoWidgetNameView: FC<AvatarInfoWidgetNameViewProps> = props =>
|
||||
{
|
||||
const { nameInfo = null, onClose = null } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'name-only' ];
|
||||
|
||||
if(nameInfo.isFriend) newClassNames.push('is-friend');
|
||||
|
||||
return newClassNames;
|
||||
}, [ nameInfo ]);
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ nameInfo.category } classNames={ getClassNames } fades={ (nameInfo.id !== GetSessionDataManager().userId) } objectId={ nameInfo.roomIndex } userType={ nameInfo.userType } onClose={ onClose }>
|
||||
<div className="text-shadow">
|
||||
{ nameInfo.name }
|
||||
</div>
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,292 @@
|
||||
import { AvatarAction, AvatarExpressionEnum, CreateLinkEvent, RoomControllerLevel, RoomObjectCategory, RoomUnitDropHandItemComposer } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, FC, SetStateAction, useState } from 'react';
|
||||
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
|
||||
import { AvatarInfoUser, DispatchUiEvent, GetCanStandUp, GetCanUseExpression, GetOwnPosture, GetUserProfile, HasHabboClub, HasHabboVip, IsRidingHorse, LocalizeText, PostureTypeEnum, SendMessageComposer } from '../../../../../api';
|
||||
import { LayoutCurrencyIcon } from '../../../../../common';
|
||||
import { HelpNameChangeEvent } from '../../../../../events';
|
||||
import { useRoom } from '../../../../../hooks';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoWidgetOwnAvatarViewProps
|
||||
{
|
||||
avatarInfo: AvatarInfoUser;
|
||||
isDancing: boolean;
|
||||
setIsDecorating: Dispatch<SetStateAction<boolean>>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const MODE_NORMAL = 0;
|
||||
const MODE_CLUB_DANCES = 1;
|
||||
const MODE_NAME_CHANGE = 2;
|
||||
const MODE_EXPRESSIONS = 3;
|
||||
const MODE_SIGNS = 4;
|
||||
|
||||
export const AvatarInfoWidgetOwnAvatarView: FC<AvatarInfoWidgetOwnAvatarViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, isDancing = false, setIsDecorating = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState((isDancing && HasHabboClub()) ? MODE_CLUB_DANCES : MODE_NORMAL);
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
const processAction = (name: string) =>
|
||||
{
|
||||
let hideMenu = true;
|
||||
|
||||
if(name)
|
||||
{
|
||||
if(name.startsWith('sign_'))
|
||||
{
|
||||
const sign = parseInt(name.split('_')[1]);
|
||||
|
||||
roomSession.sendSignMessage(sign);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(name)
|
||||
{
|
||||
case 'decorate':
|
||||
setIsDecorating(true);
|
||||
break;
|
||||
case 'change_name':
|
||||
DispatchUiEvent(new HelpNameChangeEvent(HelpNameChangeEvent.INIT));
|
||||
break;
|
||||
case 'change_looks':
|
||||
CreateLinkEvent('avatar-editor/show');
|
||||
break;
|
||||
case 'expressions':
|
||||
hideMenu = false;
|
||||
setMode(MODE_EXPRESSIONS);
|
||||
break;
|
||||
case 'sit':
|
||||
roomSession.sendPostureMessage(PostureTypeEnum.POSTURE_SIT);
|
||||
break;
|
||||
case 'stand':
|
||||
roomSession.sendPostureMessage(PostureTypeEnum.POSTURE_STAND);
|
||||
break;
|
||||
case 'wave':
|
||||
roomSession.sendExpressionMessage(AvatarExpressionEnum.WAVE.ordinal);
|
||||
break;
|
||||
case 'blow':
|
||||
roomSession.sendExpressionMessage(AvatarExpressionEnum.BLOW.ordinal);
|
||||
break;
|
||||
case 'laugh':
|
||||
roomSession.sendExpressionMessage(AvatarExpressionEnum.LAUGH.ordinal);
|
||||
break;
|
||||
case 'idle':
|
||||
roomSession.sendExpressionMessage(AvatarExpressionEnum.IDLE.ordinal);
|
||||
break;
|
||||
case 'dance_menu':
|
||||
hideMenu = false;
|
||||
setMode(MODE_CLUB_DANCES);
|
||||
break;
|
||||
case 'dance':
|
||||
roomSession.sendDanceMessage(1);
|
||||
break;
|
||||
case 'dance_stop':
|
||||
roomSession.sendDanceMessage(0);
|
||||
break;
|
||||
case 'dance_1':
|
||||
case 'dance_2':
|
||||
case 'dance_3':
|
||||
case 'dance_4':
|
||||
roomSession.sendDanceMessage(parseInt(name.charAt((name.length - 1))));
|
||||
break;
|
||||
case 'signs':
|
||||
hideMenu = false;
|
||||
setMode(MODE_SIGNS);
|
||||
break;
|
||||
case 'back':
|
||||
hideMenu = false;
|
||||
setMode(MODE_NORMAL);
|
||||
break;
|
||||
case 'drop_carry_item':
|
||||
SendMessageComposer(new RoomUnitDropHandItemComposer());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(hideMenu) onClose();
|
||||
};
|
||||
|
||||
const isShowDecorate = () => (avatarInfo.amIOwner || avatarInfo.amIAnyRoomController || (avatarInfo.roomControllerLevel > RoomControllerLevel.GUEST));
|
||||
|
||||
const isRidingHorse = IsRidingHorse();
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ RoomObjectCategory.UNIT } collapsable={ true } objectId={ avatarInfo.roomIndex } userType={ avatarInfo.userType } onClose={ onClose }>
|
||||
|
||||
<ContextMenuHeaderView className="cursor-pointer" onClick={ event => GetUserProfile(avatarInfo.webID) }>
|
||||
{ avatarInfo.name }
|
||||
</ContextMenuHeaderView>
|
||||
{ (mode === MODE_NORMAL) &&
|
||||
<>
|
||||
{ avatarInfo.allowNameChange &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('change_name') }>
|
||||
{ LocalizeText('widget.avatar.change_name') }
|
||||
</ContextMenuListItemView> }
|
||||
{ isShowDecorate() &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('decorate') }>
|
||||
{ LocalizeText('widget.avatar.decorate') }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('change_looks') }>
|
||||
{ LocalizeText('widget.memenu.myclothes') }
|
||||
</ContextMenuListItemView>
|
||||
{ (HasHabboClub() && !isRidingHorse) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance_menu') }>
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
{ LocalizeText('widget.memenu.dance') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (!isDancing && !HasHabboClub() && !isRidingHorse) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance') }>
|
||||
{ LocalizeText('widget.memenu.dance') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (isDancing && !HasHabboClub() && !isRidingHorse) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance_stop') }>
|
||||
{ LocalizeText('widget.memenu.dance.stop') }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('expressions') }>
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
{ LocalizeText('infostand.link.expressions') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('signs') }>
|
||||
<FaChevronRight className="right fa-icon" />
|
||||
{ LocalizeText('infostand.show.signs') }
|
||||
</ContextMenuListItemView>
|
||||
{ (avatarInfo.carryItem > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('drop_carry_item') }>
|
||||
{ LocalizeText('avatar.widget.drop_hand_item') }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_CLUB_DANCES) &&
|
||||
<>
|
||||
{ isDancing &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance_stop') }>
|
||||
{ LocalizeText('widget.memenu.dance.stop') }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance_1') }>
|
||||
{ LocalizeText('widget.memenu.dance1') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance_2') }>
|
||||
{ LocalizeText('widget.memenu.dance2') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance_3') }>
|
||||
{ LocalizeText('widget.memenu.dance3') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance_4') }>
|
||||
{ LocalizeText('widget.memenu.dance4') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('back') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
{ (mode === MODE_EXPRESSIONS) &&
|
||||
<>
|
||||
{ (GetOwnPosture() === AvatarAction.POSTURE_STAND) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('sit') }>
|
||||
{ LocalizeText('widget.memenu.sit') }
|
||||
</ContextMenuListItemView> }
|
||||
{ GetCanStandUp() &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('stand') }>
|
||||
{ LocalizeText('widget.memenu.stand') }
|
||||
</ContextMenuListItemView> }
|
||||
{ GetCanUseExpression() &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('wave') }>
|
||||
{ LocalizeText('widget.memenu.wave') }
|
||||
</ContextMenuListItemView> }
|
||||
{ GetCanUseExpression() &&
|
||||
<ContextMenuListItemView disabled={ !HasHabboVip() } onClick={ event => processAction('laugh') }>
|
||||
{ !HasHabboVip() && <LayoutCurrencyIcon type="hc" /> }
|
||||
{ LocalizeText('widget.memenu.laugh') }
|
||||
</ContextMenuListItemView> }
|
||||
{ GetCanUseExpression() &&
|
||||
<ContextMenuListItemView disabled={ !HasHabboVip() } onClick={ event => processAction('blow') }>
|
||||
{ !HasHabboVip() && <LayoutCurrencyIcon type="hc" /> }
|
||||
{ LocalizeText('widget.memenu.blow') }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('idle') }>
|
||||
{ LocalizeText('widget.memenu.idle') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('back') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
{ (mode === MODE_SIGNS) &&
|
||||
<>
|
||||
<div className="flex menu-list-split-3">
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_1') }>
|
||||
1
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_2') }>
|
||||
2
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_3') }>
|
||||
3
|
||||
</ContextMenuListItemView>
|
||||
</div>
|
||||
<div className="flex menu-list-split-3">
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_4') }>
|
||||
4
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_5') }>
|
||||
5
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_6') }>
|
||||
6
|
||||
</ContextMenuListItemView>
|
||||
</div>
|
||||
<div className="flex menu-list-split-3">
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_7') }>
|
||||
7
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_8') }>
|
||||
8
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_9') }>
|
||||
9
|
||||
</ContextMenuListItemView>
|
||||
</div>
|
||||
<div className="flex menu-list-split-3">
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_10') }>
|
||||
10
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_11') }>
|
||||
<i className="nitro-icon icon-sign-heart" />
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_12') }>
|
||||
<i className="nitro-icon icon-sign-skull" />
|
||||
</ContextMenuListItemView>
|
||||
</div>
|
||||
<div className="flex menu-list-split-3">
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_0') }>
|
||||
0
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_13') }>
|
||||
<i className="nitro-icon icon-sign-exclamation" />
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_15') }>
|
||||
<i className="nitro-icon icon-sign-smile" />
|
||||
</ContextMenuListItemView>
|
||||
</div>
|
||||
<div className="flex menu-list-split-3">
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_14') }>
|
||||
<i className="nitro-icon icon-sign-soccer" />
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_17') }>
|
||||
<i className="nitro-icon icon-sign-yellow" />
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('sign_16') }>
|
||||
<i className="nitro-icon icon-sign-red" />
|
||||
</ContextMenuListItemView>
|
||||
</div>
|
||||
<ContextMenuListItemView onClick={ event => processAction('back') }>
|
||||
<FaChevronLeft className="left fa-icon" />
|
||||
{ LocalizeText('generic.back') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,220 @@
|
||||
import { CreateLinkEvent, PetRespectComposer, PetType, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitGiveHandItemPetComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { AvatarInfoPet, GetConfigurationValue, GetOwnRoomObject, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { useRoom, useSessionInfo } from '../../../../../hooks';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoWidgetOwnPetViewProps
|
||||
{
|
||||
avatarInfo: AvatarInfoPet;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const MODE_NORMAL: number = 0;
|
||||
const MODE_SADDLED_UP: number = 1;
|
||||
const MODE_RIDING: number = 2;
|
||||
const MODE_MONSTER_PLANT: number = 3;
|
||||
|
||||
export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(MODE_NORMAL);
|
||||
const { roomSession = null } = useRoom();
|
||||
const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
|
||||
|
||||
const canGiveHandItem = useMemo(() =>
|
||||
{
|
||||
let flag = false;
|
||||
|
||||
const roomObject = GetOwnRoomObject();
|
||||
|
||||
if(roomObject)
|
||||
{
|
||||
const carryId = roomObject.model.getValue<number>(RoomObjectVariable.FIGURE_CARRY_OBJECT);
|
||||
|
||||
if((carryId > 0) && (carryId < 999999)) flag = true;
|
||||
}
|
||||
|
||||
return flag;
|
||||
}, []);
|
||||
|
||||
const processAction = (name: string) =>
|
||||
{
|
||||
let hideMenu = true;
|
||||
|
||||
if(name)
|
||||
{
|
||||
switch(name)
|
||||
{
|
||||
case 'respect':
|
||||
respectPet(avatarInfo.id);
|
||||
|
||||
if((petRespectRemaining - 1) >= 1) hideMenu = false;
|
||||
break;
|
||||
case 'treat':
|
||||
SendMessageComposer(new PetRespectComposer(avatarInfo.id));
|
||||
break;
|
||||
case 'pass_handitem':
|
||||
SendMessageComposer(new RoomUnitGiveHandItemPetComposer(avatarInfo.id));
|
||||
break;
|
||||
case 'train':
|
||||
roomSession.requestPetCommands(avatarInfo.id);
|
||||
break;
|
||||
case 'pick_up':
|
||||
roomSession.pickupPet(avatarInfo.id);
|
||||
break;
|
||||
case 'mount':
|
||||
roomSession.mountPet(avatarInfo.id);
|
||||
break;
|
||||
case 'toggle_riding_permission':
|
||||
roomSession.togglePetRiding(avatarInfo.id);
|
||||
break;
|
||||
case 'toggle_breeding_permission':
|
||||
roomSession.togglePetBreeding(avatarInfo.id);
|
||||
break;
|
||||
case 'dismount':
|
||||
roomSession.dismountPet(avatarInfo.id);
|
||||
break;
|
||||
case 'saddle_off':
|
||||
roomSession.removePetSaddle(avatarInfo.id);
|
||||
break;
|
||||
case 'breed':
|
||||
if(mode === MODE_NORMAL)
|
||||
{
|
||||
// _local_7 = RoomWidgetPetCommandMessage._Str_16282;
|
||||
// _local_8 = ("pet.command." + _local_7);
|
||||
// _local_9 = _Str_2268.catalog.localization.getLocalization(_local_8);
|
||||
// _local_4 = new RoomWidgetPetCommandMessage(RoomWidgetPetCommandMessage.RWPCM_PET_COMMAND, this._Str_594.id, ((this._Str_594.name + " ") + _local_9));
|
||||
}
|
||||
|
||||
else if(mode === MODE_MONSTER_PLANT)
|
||||
{
|
||||
// messageType = RoomWidgetUserActionMessage.REQUEST_BREED_PET;
|
||||
}
|
||||
break;
|
||||
case 'harvest':
|
||||
roomSession.harvestPet(avatarInfo.id);
|
||||
break;
|
||||
case 'revive':
|
||||
//
|
||||
break;
|
||||
case 'compost':
|
||||
roomSession.compostPlant(avatarInfo.id);
|
||||
break;
|
||||
case 'buy_saddle':
|
||||
CreateLinkEvent('catalog/open/' + GetConfigurationValue('catalog.links')['pets.buy_saddle']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(hideMenu) onClose();
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setMode(prevValue =>
|
||||
{
|
||||
if(avatarInfo.petType === PetType.MONSTERPLANT) return MODE_MONSTER_PLANT;
|
||||
else if(avatarInfo.saddle && !avatarInfo.rider) return MODE_SADDLED_UP;
|
||||
else if(avatarInfo.rider) return MODE_RIDING;
|
||||
|
||||
return MODE_NORMAL;
|
||||
});
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ RoomObjectCategory.UNIT } collapsable={ true } objectId={ avatarInfo.roomIndex } userType={ RoomObjectType.PET } onClose={ onClose }>
|
||||
<ContextMenuHeaderView>
|
||||
{ avatarInfo.name }
|
||||
</ContextMenuHeaderView>
|
||||
{ (mode === MODE_NORMAL) &&
|
||||
<>
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('train') }>
|
||||
{ LocalizeText('infostand.button.train') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('pick_up') }>
|
||||
{ LocalizeText('infostand.button.pickup') }
|
||||
</ContextMenuListItemView>
|
||||
{ (avatarInfo.petType === PetType.HORSE) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('buy_saddle') }>
|
||||
{ LocalizeText('infostand.button.buy_saddle') }
|
||||
</ContextMenuListItemView> }
|
||||
{ ([ PetType.BEAR, PetType.TERRIER, PetType.CAT, PetType.DOG, PetType.PIG ].indexOf(avatarInfo.petType) > -1) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('breed') }>
|
||||
{ LocalizeText('infostand.button.breed') }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_SADDLED_UP) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('mount') }>
|
||||
{ LocalizeText('infostand.button.mount') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView gap={ 1 } onClick={ event => processAction('toggle_riding_permission') }>
|
||||
<input checked={ !!avatarInfo.publiclyRideable } readOnly={ true } type="checkbox" />
|
||||
{ LocalizeText('infostand.button.toggle_riding_permission') }
|
||||
</ContextMenuListItemView>
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('train') }>
|
||||
{ LocalizeText('infostand.button.train') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('pick_up') }>
|
||||
{ LocalizeText('infostand.button.pickup') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('saddle_off') }>
|
||||
{ LocalizeText('infostand.button.saddleoff') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
{ (mode === MODE_RIDING) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('dismount') }>
|
||||
{ LocalizeText('infostand.button.dismount') }
|
||||
</ContextMenuListItemView>
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_MONSTER_PLANT) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('pick_up') }>
|
||||
{ LocalizeText('infostand.button.pickup') }
|
||||
</ContextMenuListItemView>
|
||||
{ avatarInfo.dead &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('revive') }>
|
||||
{ LocalizeText('infostand.button.revive') }
|
||||
</ContextMenuListItemView> }
|
||||
{ roomSession.isRoomOwner &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('compost') }>
|
||||
{ LocalizeText('infostand.button.compost') }
|
||||
</ContextMenuListItemView> }
|
||||
{ !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('treat') }>
|
||||
{ LocalizeText('infostand.button.pettreat') }
|
||||
</ContextMenuListItemView> }
|
||||
{ !avatarInfo.dead && (avatarInfo.level === avatarInfo.maximumLevel) && avatarInfo.breedable &&
|
||||
<>
|
||||
<ContextMenuListItemView gap={ 1 } onClick={ event => processAction('toggle_breeding_permission') }>
|
||||
<input checked={ avatarInfo.publiclyBreedable } readOnly={ true } type="checkbox" />
|
||||
{ LocalizeText('infostand.button.toggle_breeding_permission') }
|
||||
</ContextMenuListItemView>
|
||||
<ContextMenuListItemView onClick={ event => processAction('breed') }>
|
||||
{ LocalizeText('infostand.button.breed') }
|
||||
</ContextMenuListItemView>
|
||||
</> }
|
||||
</> }
|
||||
{ canGiveHandItem &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('pass_hand_item') }>
|
||||
{ LocalizeText('infostand.button.pass_hand_item') }
|
||||
</ContextMenuListItemView> }
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
import { GetSessionDataManager, PetRespectComposer, PetType, RoomControllerLevel, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitGiveHandItemPetComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { AvatarInfoPet, GetOwnRoomObject, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { useRoom, useSessionInfo } from '../../../../../hooks';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoWidgetPetViewProps
|
||||
{
|
||||
avatarInfo: AvatarInfoPet;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const MODE_NORMAL: number = 0;
|
||||
const MODE_SADDLED_UP: number = 1;
|
||||
const MODE_RIDING: number = 2;
|
||||
const MODE_MONSTER_PLANT: number = 3;
|
||||
|
||||
export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(MODE_NORMAL);
|
||||
const { roomSession = null } = useRoom();
|
||||
const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
|
||||
|
||||
const canPickUp = useMemo(() =>
|
||||
{
|
||||
return (roomSession.isRoomOwner || (roomSession.controllerLevel >= RoomControllerLevel.GUEST) || GetSessionDataManager().isModerator);
|
||||
}, [ roomSession ]);
|
||||
|
||||
const canGiveHandItem = useMemo(() =>
|
||||
{
|
||||
let flag = false;
|
||||
|
||||
const roomObject = GetOwnRoomObject();
|
||||
|
||||
if(roomObject)
|
||||
{
|
||||
const carryId = roomObject.model.getValue<number>(RoomObjectVariable.FIGURE_CARRY_OBJECT);
|
||||
|
||||
if((carryId > 0) && (carryId < 999999)) flag = true;
|
||||
}
|
||||
|
||||
return flag;
|
||||
}, []);
|
||||
|
||||
const processAction = (name: string) =>
|
||||
{
|
||||
let hideMenu = true;
|
||||
|
||||
if(name)
|
||||
{
|
||||
switch(name)
|
||||
{
|
||||
case 'respect':
|
||||
respectPet(avatarInfo.id);
|
||||
|
||||
if((petRespectRemaining - 1) >= 1) hideMenu = false;
|
||||
break;
|
||||
case 'treat':
|
||||
SendMessageComposer(new PetRespectComposer(avatarInfo.id));
|
||||
break;
|
||||
case 'pass_handitem':
|
||||
SendMessageComposer(new RoomUnitGiveHandItemPetComposer(avatarInfo.id));
|
||||
break;
|
||||
case 'pick_up':
|
||||
roomSession.pickupPet(avatarInfo.id);
|
||||
break;
|
||||
case 'mount':
|
||||
roomSession.mountPet(avatarInfo.id);
|
||||
break;
|
||||
case 'dismount':
|
||||
roomSession.dismountPet(avatarInfo.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(hideMenu) onClose();
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setMode(prevValue =>
|
||||
{
|
||||
if(avatarInfo.petType === PetType.MONSTERPLANT) return MODE_MONSTER_PLANT;
|
||||
else if(avatarInfo.saddle && !avatarInfo.rider) return MODE_SADDLED_UP;
|
||||
else if(avatarInfo.rider) return MODE_RIDING;
|
||||
|
||||
return MODE_NORMAL;
|
||||
});
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ RoomObjectCategory.UNIT } collapsable={ true } objectId={ avatarInfo.roomIndex } userType={ RoomObjectType.PET } onClose={ onClose }>
|
||||
<ContextMenuHeaderView>
|
||||
{ avatarInfo.name }
|
||||
</ContextMenuHeaderView>
|
||||
{ (mode === MODE_NORMAL) && (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
{ (mode === MODE_SADDLED_UP) &&
|
||||
<>
|
||||
{ !!avatarInfo.publiclyRideable &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('mount') }>
|
||||
{ LocalizeText('infostand.button.mount') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_RIDING) &&
|
||||
<>
|
||||
<ContextMenuListItemView onClick={ event => processAction('dismount') }>
|
||||
{ LocalizeText('infostand.button.dismount') }
|
||||
</ContextMenuListItemView>
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_MONSTER_PLANT) && !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('treat') }>
|
||||
{ LocalizeText('infostand.button.pettreat') }
|
||||
</ContextMenuListItemView> }
|
||||
{ canPickUp &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('pick_up') }>
|
||||
{ LocalizeText('infostand.button.pickup') }
|
||||
</ContextMenuListItemView> }
|
||||
{ canGiveHandItem &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('pass_hand_item') }>
|
||||
{ LocalizeText('infostand.button.pass_hand_item') }
|
||||
</ContextMenuListItemView> }
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
import { BotCommandConfigurationEvent, BotRemoveComposer, BotSkillSaveComposer, CreateLinkEvent, RequestBotCommandConfigurationComposer, RoomObjectCategory, RoomObjectType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarInfoRentableBot, BotSkillsEnum, DispatchUiEvent, GetConfigurationValue, LocalizeText, RoomWidgetUpdateRentableBotChatEvent, SendMessageComposer } from '../../../../../api';
|
||||
import { Button, Column, Text } from '../../../../../common';
|
||||
import { useMessageEvent } from '../../../../../hooks';
|
||||
import { NitroInput } from '../../../../../layout';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
|
||||
interface AvatarInfoWidgetRentableBotViewProps
|
||||
{
|
||||
avatarInfo: AvatarInfoRentableBot;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const MODE_NORMAL = 0;
|
||||
const MODE_CHANGE_NAME = 1;
|
||||
const MODE_CHANGE_MOTTO = 2;
|
||||
|
||||
export const AvatarInfoWidgetRentableBotView: FC<AvatarInfoWidgetRentableBotViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(MODE_NORMAL);
|
||||
const [ newName, setNewName ] = useState('');
|
||||
const [ newMotto, setNewMotto ] = useState('');
|
||||
|
||||
useMessageEvent<BotCommandConfigurationEvent>(BotCommandConfigurationEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(parser.botId !== avatarInfo.webID) return;
|
||||
|
||||
switch(parser.commandId)
|
||||
{
|
||||
case BotSkillsEnum.CHANGE_BOT_NAME:
|
||||
setNewName(parser.data);
|
||||
setMode(MODE_CHANGE_NAME);
|
||||
return;
|
||||
case BotSkillsEnum.CHANGE_BOT_MOTTO:
|
||||
setNewMotto(parser.data);
|
||||
setMode(MODE_CHANGE_MOTTO);
|
||||
return;
|
||||
case BotSkillsEnum.SETUP_CHAT: {
|
||||
const data = parser.data;
|
||||
const pieces = data.split(((data.indexOf(';#;') === -1) ? ';' : ';#;'));
|
||||
|
||||
if((pieces.length === 3) || (pieces.length === 4))
|
||||
{
|
||||
DispatchUiEvent(new RoomWidgetUpdateRentableBotChatEvent(
|
||||
avatarInfo.roomIndex,
|
||||
RoomObjectCategory.UNIT,
|
||||
avatarInfo.webID,
|
||||
pieces[0],
|
||||
((pieces[1].toLowerCase() === 'true') || (pieces[1] === '1')),
|
||||
parseInt(pieces[2]),
|
||||
((pieces[3]) ? ((pieces[3].toLowerCase() === 'true') || (pieces[3] === '1')) : false)));
|
||||
|
||||
onClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const requestBotCommandConfiguration = (skillType: number) => SendMessageComposer(new RequestBotCommandConfigurationComposer(avatarInfo.webID, skillType));
|
||||
|
||||
const processAction = (name: string) =>
|
||||
{
|
||||
let hideMenu = true;
|
||||
|
||||
if(name)
|
||||
{
|
||||
switch(name)
|
||||
{
|
||||
case 'donate_to_all':
|
||||
requestBotCommandConfiguration(BotSkillsEnum.DONATE_TO_ALL);
|
||||
SendMessageComposer(new BotSkillSaveComposer(avatarInfo.webID, BotSkillsEnum.DONATE_TO_ALL, ''));
|
||||
break;
|
||||
case 'donate_to_user':
|
||||
requestBotCommandConfiguration(BotSkillsEnum.DONATE_TO_USER);
|
||||
SendMessageComposer(new BotSkillSaveComposer(avatarInfo.webID, BotSkillsEnum.DONATE_TO_USER, ''));
|
||||
break;
|
||||
case 'change_bot_name':
|
||||
requestBotCommandConfiguration(BotSkillsEnum.CHANGE_BOT_NAME);
|
||||
hideMenu = false;
|
||||
break;
|
||||
case 'save_bot_name':
|
||||
SendMessageComposer(new BotSkillSaveComposer(avatarInfo.webID, BotSkillsEnum.CHANGE_BOT_NAME, newName));
|
||||
break;
|
||||
case 'change_bot_motto':
|
||||
requestBotCommandConfiguration(BotSkillsEnum.CHANGE_BOT_MOTTO);
|
||||
hideMenu = false;
|
||||
break;
|
||||
case 'save_bot_motto':
|
||||
SendMessageComposer(new BotSkillSaveComposer(avatarInfo.webID, BotSkillsEnum.CHANGE_BOT_MOTTO, newMotto));
|
||||
break;
|
||||
case 'dress_up':
|
||||
SendMessageComposer(new BotSkillSaveComposer(avatarInfo.webID, BotSkillsEnum.DRESS_UP, ''));
|
||||
break;
|
||||
case 'random_walk':
|
||||
SendMessageComposer(new BotSkillSaveComposer(avatarInfo.webID, BotSkillsEnum.RANDOM_WALK, ''));
|
||||
break;
|
||||
case 'setup_chat':
|
||||
requestBotCommandConfiguration(BotSkillsEnum.SETUP_CHAT);
|
||||
hideMenu = false;
|
||||
break;
|
||||
case 'dance':
|
||||
SendMessageComposer(new BotSkillSaveComposer(avatarInfo.webID, BotSkillsEnum.DANCE, ''));
|
||||
break;
|
||||
case 'nux_take_tour':
|
||||
CreateLinkEvent('help/tour');
|
||||
SendMessageComposer(new BotSkillSaveComposer(avatarInfo.webID, BotSkillsEnum.NUX_TAKE_TOUR, ''));
|
||||
break;
|
||||
case 'pick':
|
||||
SendMessageComposer(new BotRemoveComposer(avatarInfo.webID));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(hideMenu) onClose();
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setMode(MODE_NORMAL);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
const canControl = (avatarInfo.amIOwner || avatarInfo.amIAnyRoomController);
|
||||
|
||||
return (
|
||||
<ContextMenuView category={ RoomObjectCategory.UNIT } collapsable={ true } objectId={ avatarInfo.roomIndex } userType={ RoomObjectType.RENTABLE_BOT } onClose={ onClose }>
|
||||
<ContextMenuHeaderView>
|
||||
{ avatarInfo.name }
|
||||
</ContextMenuHeaderView>
|
||||
{ (mode === MODE_NORMAL) && canControl &&
|
||||
<>
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.DONATE_TO_ALL) >= 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('donate_to_all') }>
|
||||
{ LocalizeText('avatar.widget.donate_to_all') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.DONATE_TO_USER) >= 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('donate_to_user') }>
|
||||
{ LocalizeText('avatar.widget.donate_to_user') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.CHANGE_BOT_NAME) >= 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('change_bot_name') }>
|
||||
{ LocalizeText('avatar.widget.change_bot_name') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.CHANGE_BOT_MOTTO) >= 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('change_bot_motto') }>
|
||||
{ LocalizeText('avatar.widget.change_bot_motto') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.DRESS_UP) >= 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('dress_up') }>
|
||||
{ LocalizeText('avatar.widget.dress_up') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.RANDOM_WALK) >= 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('random_walk') }>
|
||||
{ LocalizeText('avatar.widget.random_walk') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.SETUP_CHAT) >= 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('setup_chat') }>
|
||||
{ LocalizeText('avatar.widget.setup_chat') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.DANCE) >= 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('dance') }>
|
||||
{ LocalizeText('avatar.widget.dance') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (avatarInfo.botSkills.indexOf(BotSkillsEnum.NO_PICK_UP) === -1) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('pick') }>
|
||||
{ LocalizeText('avatar.widget.pick_up') }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_CHANGE_NAME) &&
|
||||
<Column className="menu-item" gap={ 1 } onClick={ null }>
|
||||
<Text variant="white">{ LocalizeText('bot.skill.name.configuration.new.name') }</Text>
|
||||
<NitroInput maxLength={ GetConfigurationValue<number>('bot.name.max.length', 15) } type="text" value={ newName } onChange={ event => setNewName(event.target.value) } />
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<Button fullWidth variant="secondary" onClick={ event => processAction(null) }>{ LocalizeText('cancel') }</Button>
|
||||
<Button fullWidth variant="success" onClick={ event => processAction('save_bot_name') }>{ LocalizeText('save') }</Button>
|
||||
</div>
|
||||
</Column> }
|
||||
{ (mode === MODE_CHANGE_MOTTO) &&
|
||||
<Column className="menu-item" gap={ 1 } onClick={ null }>
|
||||
<Text variant="white">{ LocalizeText('bot.skill.name.configuration.new.motto') }</Text>
|
||||
<NitroInput maxLength={ GetConfigurationValue<number>('motto.max.length', 38) } type="text" value={ newMotto } onChange={ event => setNewMotto(event.target.value) } />
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<Button fullWidth variant="secondary" onClick={ event => processAction(null) }>{ LocalizeText('cancel') }</Button>
|
||||
<Button fullWidth variant="success" onClick={ event => processAction('save_bot_motto') }>{ LocalizeText('save') }</Button>
|
||||
</div>
|
||||
</Column> }
|
||||
</ContextMenuView>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user