mirror of
https://github.com/Gurkengewuerz/nitro.git
synced 2024-06-03 03:35:34 +02:00
529 lines
18 KiB
TypeScript
529 lines
18 KiB
TypeScript
import {
|
|
CrackableDataType,
|
|
GroupInformationComposer,
|
|
GroupInformationEvent,
|
|
NowPlayingEvent,
|
|
RoomControllerLevel,
|
|
RoomObjectCategory,
|
|
RoomObjectOperationType,
|
|
RoomObjectVariable,
|
|
RoomWidgetEnumItemExtradataParameter,
|
|
RoomWidgetFurniInfoUsagePolicyEnum,
|
|
SetObjectDataMessageComposer,
|
|
SongInfoReceivedEvent,
|
|
StringDataType,
|
|
} from "@nitro/renderer";
|
|
import {FC, useCallback, useEffect, useState} from "react";
|
|
import {FaTimes} from "react-icons/fa";
|
|
|
|
import {AvatarInfoFurni, CreateLinkEvent, GetGroupInformation, GetNitroInstance, GetRoomEngine, LocalizeText, SendMessageComposer} from "../../../../../api";
|
|
import {
|
|
Base,
|
|
Button,
|
|
Column,
|
|
Flex,
|
|
LayoutBadgeImageView,
|
|
LayoutLimitedEditionCompactPlateView,
|
|
LayoutRarityLevelView,
|
|
Text,
|
|
UserProfileIconView,
|
|
} from "../../../../../common";
|
|
import {useMessageEvent, useRoom, useSoundEvent} from "../../../../../hooks";
|
|
|
|
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>("");
|
|
|
|
useSoundEvent<NowPlayingEvent>(
|
|
NowPlayingEvent.NPE_SONG_CHANGED,
|
|
event => {
|
|
setSongId(event.id);
|
|
},
|
|
isJukeBox || isSongDisk
|
|
);
|
|
|
|
useSoundEvent<NowPlayingEvent>(
|
|
SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED,
|
|
event => {
|
|
if (event.id !== songId) return;
|
|
|
|
const songInfo = GetNitroInstance().soundManager.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 = GetNitroInstance().soundManager.musicController.getRoomItemPlaylist();
|
|
|
|
if (playlist) {
|
|
furniSongId = playlist.nowPlayingSongId;
|
|
}
|
|
|
|
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 = GetNitroInstance().soundManager.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 gap={1} alignItems="end">
|
|
<Column className="nitro-infostand rounded">
|
|
<Column overflow="visible" className="container-fluid content-area" gap={1}>
|
|
<Column gap={1}>
|
|
<Flex alignItems="center" justifyContent="between" gap={1}>
|
|
<Text variant="white" small wrap>
|
|
{avatarInfo.name}
|
|
</Text>
|
|
<FaTimes className="cursor-pointer fa-icon" onClick={onClose} />
|
|
</Flex>
|
|
<hr className="m-0" />
|
|
</Column>
|
|
<Column gap={1}>
|
|
<Flex position="relative" gap={1}>
|
|
{avatarInfo.stuffData.isUnique && (
|
|
<div className="position-absolute end-0">
|
|
<LayoutLimitedEditionCompactPlateView uniqueNumber={avatarInfo.stuffData.uniqueNumber} uniqueSeries={avatarInfo.stuffData.uniqueSeries} />
|
|
</div>
|
|
)}
|
|
{avatarInfo.stuffData.rarityLevel > -1 && (
|
|
<div className="position-absolute end-0">
|
|
<LayoutRarityLevelView level={avatarInfo.stuffData.rarityLevel} />
|
|
</div>
|
|
)}
|
|
{avatarInfo.image && avatarInfo.image.src.length && <img className="d-block mx-auto" src={avatarInfo.image.src} alt="" />}
|
|
</Flex>
|
|
<hr className="m-0" />
|
|
</Column>
|
|
<Column gap={1}>
|
|
<Text fullWidth wrap textBreak variant="white" small>
|
|
{avatarInfo.description}
|
|
</Text>
|
|
<hr className="m-0" />
|
|
</Column>
|
|
<Column gap={1}>
|
|
<Flex alignItems="center" gap={1}>
|
|
<UserProfileIconView userId={avatarInfo.ownerId} />
|
|
<Text variant="white" small wrap>
|
|
{LocalizeText("furni.owner", ["name"], [avatarInfo.ownerName])}
|
|
</Text>
|
|
</Flex>
|
|
{avatarInfo.purchaseOfferId > 0 && (
|
|
<Flex>
|
|
<Text variant="white" small underline pointer onClick={event => processButtonAction("buy_one")}>
|
|
{LocalizeText("infostand.button.buy")}
|
|
</Text>
|
|
</Flex>
|
|
)}
|
|
</Column>
|
|
{(isJukeBox || isSongDisk) && (
|
|
<Column gap={1}>
|
|
<hr className="m-0" />
|
|
{songId === -1 && (
|
|
<Text variant="white" small wrap>
|
|
{LocalizeText("infostand.jukebox.text.not.playing")}
|
|
</Text>
|
|
)}
|
|
{!!songName.length && (
|
|
<Flex alignItems="center" gap={1}>
|
|
<Base className="icon disk-icon" />
|
|
<Text variant="white" small wrap>
|
|
{songName}
|
|
</Text>
|
|
</Flex>
|
|
)}
|
|
{!!songCreator.length && (
|
|
<Flex alignItems="center" gap={1}>
|
|
<Base className="icon disk-creator" />
|
|
<Text variant="white" small wrap>
|
|
{songCreator}
|
|
</Text>
|
|
</Flex>
|
|
)}
|
|
</Column>
|
|
)}
|
|
<Column gap={1}>
|
|
{isCrackable && (
|
|
<>
|
|
<hr className="m-0" />
|
|
<Text variant="white" small wrap>
|
|
{LocalizeText("infostand.crackable_furni.hits_remaining", ["hits", "target"], [crackableHits?.toString(), crackableTarget?.toString()])}
|
|
</Text>
|
|
</>
|
|
)}
|
|
{avatarInfo.groupId > 0 && (
|
|
<>
|
|
<hr className="m-0" />
|
|
<Flex pointer alignItems="center" gap={2} onClick={() => GetGroupInformation(avatarInfo.groupId)}>
|
|
<LayoutBadgeImageView badgeCode={getGroupBadgeCode()} isGroup={true} />
|
|
<Text variant="white" underline>
|
|
{groupName}
|
|
</Text>
|
|
</Flex>
|
|
</>
|
|
)}
|
|
{godMode && (
|
|
<>
|
|
<hr className="m-0" />
|
|
{canSeeFurniId && (
|
|
<Text small wrap variant="white">
|
|
ID: {avatarInfo.id}
|
|
</Text>
|
|
)}
|
|
{furniKeys.length > 0 && (
|
|
<>
|
|
<hr className="m-0" />
|
|
<Column gap={1}>
|
|
{furniKeys.map((key, index) => {
|
|
return (
|
|
<Flex key={index} alignItems="center" gap={1}>
|
|
<Text small wrap align="end" variant="white" className="col-4">
|
|
{key}
|
|
</Text>
|
|
<input
|
|
type="text"
|
|
className="form-control form-control-sm"
|
|
value={furniValues[index]}
|
|
onChange={event => onFurniSettingChange(index, event.target.value)}
|
|
/>
|
|
</Flex>
|
|
);
|
|
})}
|
|
</Column>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
{customKeys.length > 0 && (
|
|
<>
|
|
<hr className="m-0 my-1" />
|
|
<Column gap={1}>
|
|
{customKeys.map((key, index) => {
|
|
return (
|
|
<Flex key={index} alignItems="center" gap={1}>
|
|
<Text small wrap align="end" variant="white" className="col-4">
|
|
{key}
|
|
</Text>
|
|
<input
|
|
type="text"
|
|
className="form-control form-control-sm"
|
|
value={customValues[index]}
|
|
onChange={event => onCustomVariableChange(index, event.target.value)}
|
|
/>
|
|
</Flex>
|
|
);
|
|
})}
|
|
</Column>
|
|
</>
|
|
)}
|
|
</Column>
|
|
</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>
|
|
);
|
|
};
|