5180 fichiers 38.5 Mo 0 téléchargements 607 vues mis à jour il y a 12 heures ui
nitro-v36-with-renderer-v36 / src / components / room / widgets / avatar-info / infostand / InfoStandWidgetPetView.tsx
InfoStandWidgetPetView.tsx 15 Ko Télécharger le fichier
import { CreateLinkEvent, PetRespectComposer, PetType } from '@octane/renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { ConvertSeconds, GetConfigurationRecord, LocalizeText, SendMessageComposer } from '../../../../../api';
import { Button, Column, Flex, LayoutCounterTimeView, LayoutPetImageView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common';
import { NativeTextTree } from '../../../../../common/native-text/NativeTextTree';
import { useRoom, useSessionInfo } from '../../../../../hooks';
import { InfoStandHeaderView } from './InfoStandHeaderView';
import { InfoStandUnitIdView } from './InfoStandUnitIdView';

// 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 }) => (
    <NativeTextTree>
        <div className="flex flex-col gap-1">
            <InfoStandHeaderView name={name} onClose={onClose} />
            <Text small wrap variant="white">
                {LocalizeText(`pet.breed.${petType}.${petBreed}`)}
            </Text>
            <div className="octane-infostand__rule" />
        </div>
    </NativeTextTree>
);

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} />
            <div className="octane-infostand__rule" />
        </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 inset-e-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 inset-e-2" level={avatarInfo.rarityLevel} />
            </Column>
            <div className="octane-infostand__rule" />
        </div>
        <div className="flex flex-col gap-1">
            <div className="octane-infostand__score">{LocalizeText('pet.age', ['age'], [avatarInfo.age.toString()])}</div>
            <div className="octane-infostand__rule" />
        </div>
    </>
);

// Sub-component: Regular Pet Stats
const RegularPetStats: FC<{ avatarInfo: AvatarInfoPet }> = ({ avatarInfo }) => (
    <NativeTextTree>
        <>
            <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>
                <div className="octane-infostand__rule" />
            </div>
            <div className="flex flex-col gap-1">
                <div className="octane-infostand__score">{LocalizeText('infostand.text.petrespect', ['count'], [avatarInfo.respect.toString()])}</div>
                <div className="octane-infostand__score">{LocalizeText('pet.age', ['age'], [avatarInfo.age.toString()])}</div>
                <div className="octane-infostand__rule" />
            </div>
        </>
    </NativeTextTree>
);

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/' + GetConfigurationRecord('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 (
        <NativeTextTree>
            <Column alignItems="end" gap={1}>
                <Column className="octane-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>
                            <InfoStandUnitIdView id={avatarInfo.id} ownerId={avatarInfo.ownerId} />
                        </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>
        </NativeTextTree>
    );
};
15Sujets
1Dépôts
20Membres
4En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser