feat: Football Gate

By @oobjectt
https://github.com/billsonnn/nitro-react/pull/145
This commit is contained in:
Niklas 2023-10-23 16:22:13 +02:00
parent 6c48eea770
commit 983e77a0bc
8 changed files with 192 additions and 37 deletions

View File

@ -4,6 +4,7 @@ import {
GetWardrobeMessageComposer,
IAvatarFigureContainer,
ILinkEventTracker,
SetClothingChangeDataMessageComposer,
UserFigureComposer,
UserWardrobePageEvent,
} from "@nitro/renderer";
@ -47,6 +48,8 @@ import {AvatarEditorWardrobeView} from "./views/AvatarEditorWardrobeView";
const DEFAULT_MALE_FIGURE: string = "hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007";
const DEFAULT_FEMALE_FIGURE: string = "hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68";
const DEFAULT_MALE_FOOTBALL_GATE: string = "ch-3109-92-1408.lg-3116-82-1408.sh-3115-1408-1408";
const DEFAULT_FEMALE_FOOTBALL_GATE: string = "ch-3112-1408-1408.lg-3116-71-1408.sh-3115-1408-1408";
export const AvatarEditorView: FC<{}> = props => {
const [isVisible, setIsVisible] = useState(false);
@ -62,9 +65,17 @@ export const AvatarEditorView: FC<{}> = props => {
const [lastGender, setLastGender] = useState<string>(null);
const [needsReset, setNeedsReset] = useState(true);
const [isInitalized, setIsInitalized] = useState(false);
const [genderFootballGate, setGenderFootballGate] = useState<string>(null);
const [objectFootballGate, setObjectFootballGate] = useState<number>(null);
const maxWardrobeSlots = useMemo(() => GetConfiguration<number>("avatar.wardrobe.max.slots", 10), []);
const onClose = () => {
setGenderFootballGate(null);
setObjectFootballGate(null);
setIsVisible(false);
};
useMessageEvent<FigureSetIdsMessageEvent>(FigureSetIdsMessageEvent, event => {
const parser = event.getParser();
@ -105,13 +116,18 @@ export const AvatarEditorView: FC<{}> = props => {
const resetCategories = useCallback(() => {
const categories = new Map();
categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel());
categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel());
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel());
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel());
if (!genderFootballGate) {
categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel());
categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel());
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel());
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel());
} else {
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel());
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel());
}
setCategories(categories);
}, []);
}, [genderFootballGate]);
const setupFigures = useCallback(() => {
const figures: Map<string, FigureData> = new Map();
@ -167,12 +183,14 @@ export const AvatarEditorView: FC<{}> = props => {
resetCategories();
return;
case AvatarEditorAction.ACTION_SAVE:
SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString()));
setIsVisible(false);
!genderFootballGate
? SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString()))
: SendMessageComposer(new SetClothingChangeDataMessageComposer(objectFootballGate, genderFootballGate, figureData.getFigureString()));
onClose();
return;
}
},
[figureData, lastFigure, lastGender, figureSetIds, loadAvatarInEditor, resetCategories]
[loadAvatarInEditor, figureData, resetCategories, lastFigure, lastGender, figureSetIds, genderFootballGate, objectFootballGate]
);
const setGender = useCallback(
@ -189,6 +207,9 @@ export const AvatarEditorView: FC<{}> = props => {
linkReceived: (url: string) => {
const parts = url.split("/");
setGenderFootballGate(parts[2] ? parts[2] : null);
setObjectFootballGate(parts[3] ? Number(parts[3]) : null);
if (parts.length < 2) return;
switch (parts[1]) {
@ -231,8 +252,8 @@ export const AvatarEditorView: FC<{}> = props => {
useEffect(() => {
if (!categories) return;
selectCategory(AvatarEditorFigureCategory.GENERIC);
}, [categories, selectCategory]);
selectCategory(!genderFootballGate ? AvatarEditorFigureCategory.GENERIC : AvatarEditorFigureCategory.TORSO);
}, [categories, genderFootballGate, selectCategory]);
useEffect(() => {
if (!figureData) return;
@ -271,9 +292,22 @@ export const AvatarEditorView: FC<{}> = props => {
useEffect(() => {
if (!isVisible || !isInitalized || !needsReset) return;
loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender);
loadAvatarInEditor(
!genderFootballGate ? GetSessionDataManager().figure : genderFootballGate === FigureData.MALE ? DEFAULT_MALE_FOOTBALL_GATE : DEFAULT_FEMALE_FOOTBALL_GATE,
!genderFootballGate ? GetSessionDataManager().gender : genderFootballGate
);
setNeedsReset(false);
}, [isVisible, isInitalized, needsReset, loadAvatarInEditor]);
}, [isVisible, isInitalized, needsReset, loadAvatarInEditor, genderFootballGate]);
useEffect(() =>
// This is so when you have the look editor open and you change the mode to Boy or Girl
{
if (!isVisible) return;
return () => {
setNeedsReset(true);
};
}, [isVisible, genderFootballGate]);
useEffect(() => {
if (isVisible) return;
@ -287,7 +321,10 @@ export const AvatarEditorView: FC<{}> = props => {
return (
<NitroCardView uniqueKey="avatar-editor" className="nitro-avatar-editor">
<NitroCardHeaderView headerText={LocalizeText("avatareditor.title")} onCloseClick={event => setIsVisible(false)} />
<NitroCardHeaderView
headerText={!genderFootballGate ? LocalizeText("avatareditor.title") : LocalizeText("widget.furni.clothingchange.editor.title")}
onCloseClick={onClose}
/>
<NitroCardTabsView>
{categories &&
categories.size > 0 &&
@ -300,14 +337,23 @@ export const AvatarEditorView: FC<{}> = props => {
</NitroCardTabsItemView>
);
})}
<NitroCardTabsItemView isActive={isWardrobeVisible} onClick={event => setIsWardrobeVisible(true)}>
{LocalizeText("avatareditor.category.wardrobe")}
</NitroCardTabsItemView>
{!genderFootballGate && (
<NitroCardTabsItemView isActive={isWardrobeVisible} onClick={event => setIsWardrobeVisible(true)}>
{LocalizeText("avatareditor.category.wardrobe")}
</NitroCardTabsItemView>
)}
</NitroCardTabsView>
<NitroCardContentView>
<Grid>
<Column size={9} overflow="hidden">
{activeCategory && !isWardrobeVisible && <AvatarEditorModelView model={activeCategory} gender={figureData.gender} setGender={setGender} />}
{activeCategory && !isWardrobeVisible && (
<AvatarEditorModelView
model={activeCategory}
gender={figureData.gender}
isFromFootballGate={!genderFootballGate ? false : true}
setGender={setGender}
/>
)}
{isWardrobeVisible && (
<AvatarEditorWardrobeView
figureData={figureData}
@ -320,17 +366,19 @@ export const AvatarEditorView: FC<{}> = props => {
<Column size={3} overflow="hidden">
<AvatarEditorFigurePreviewView figureData={figureData} />
<Column grow gap={1}>
<ButtonGroup>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RESET)}>
<FaUndo className="fa-icon" />
</Button>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_CLEAR)}>
<FaTrash className="fa-icon" />
</Button>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RANDOMIZE)}>
<FaDice className="fa-icon" />
</Button>
</ButtonGroup>
{!genderFootballGate && (
<ButtonGroup>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RESET)}>
<FaUndo className="fa-icon" />
</Button>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_CLEAR)}>
<FaTrash className="fa-icon" />
</Button>
<Button variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RANDOMIZE)}>
<FaDice className="fa-icon" />
</Button>
</ButtonGroup>
)}
<Button className="w-100" variant="success" onClick={event => processAction(AvatarEditorAction.ACTION_SAVE)}>
{LocalizeText("avatareditor.save")}
</Button>
@ -341,3 +389,4 @@ export const AvatarEditorView: FC<{}> = props => {
</NitroCardView>
);
};

View File

@ -1,4 +1,4 @@
import {Dispatch, FC, SetStateAction, useCallback, useEffect, useState} from "react";
import {FC, useCallback, useEffect, useState} from "react";
import {CategoryData, FigureData, IAvatarEditorCategoryModel} from "../../../api";
import {Column, Flex, Grid} from "../../../common";
@ -6,14 +6,17 @@ import {AvatarEditorIcon} from "./AvatarEditorIcon";
import {AvatarEditorFigureSetView} from "./figure-set/AvatarEditorFigureSetView";
import {AvatarEditorPaletteSetView} from "./palette-set/AvatarEditorPaletteSetView";
const CATEGORY_FOOTBALL_GATE = ["ch", "cp", "lg", "sh"];
export interface AvatarEditorModelViewProps {
model: IAvatarEditorCategoryModel;
gender: string;
setGender: Dispatch<SetStateAction<string>>;
isFromFootballGate: boolean;
setGender: (gender: string) => void;
}
export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props => {
const {model = null, gender = null, setGender = null} = props;
const {model = null, gender = null, isFromFootballGate = false, setGender = null} = props;
const [activeCategory, setActiveCategory] = useState<CategoryData>(null);
const [maxPaletteCount, setMaxPaletteCount] = useState(1);
@ -70,14 +73,16 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props => {
const category = model.categories.get(name);
return (
<Flex center pointer key={name} className="category-item" onClick={event => selectCategory(name)}>
<AvatarEditorIcon icon={category.name} selected={activeCategory === category} />
</Flex>
(!isFromFootballGate || (isFromFootballGate && CATEGORY_FOOTBALL_GATE.includes(category.name))) && (
<Flex key={category.name} center pointer className="category-item" onClick={event => selectCategory(name)}>
<AvatarEditorIcon icon={category.name} selected={activeCategory === category} />
</Flex>
)
);
})}
</Column>
<Column size={5} overflow="hidden">
<AvatarEditorFigureSetView model={model} category={activeCategory} setMaxPaletteCount={setMaxPaletteCount} />
<AvatarEditorFigureSetView model={model} category={activeCategory} isFromFootballGate={isFromFootballGate} setMaxPaletteCount={setMaxPaletteCount} />
</Column>
<Column size={5} overflow="hidden">
{maxPaletteCount >= 1 && (
@ -90,3 +95,4 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props => {
</Grid>
);
};

View File

@ -4,14 +4,20 @@ import {AvatarEditorGridPartItem, CategoryData, IAvatarEditorCategoryModel} from
import {AutoGrid} from "../../../../common";
import {AvatarEditorFigureSetItemView} from "./AvatarEditorFigureSetItemView";
const TSHIRT_FOOTBALL_GATE = [3111, 3110, 3109, 3030, 3114, 266, 265, 262, 3113, 3112, 691, 690, 667];
const NUMBER_BEHIND_FOOTBALL_GATE = [3128, 3127, 3126, 3125, 3124, 3123, 3122, 3121, 3120, 3119];
const PANTS_FOOTBALL_GATE = [3116, 281, 275, 715, 700, 696, 3006];
const SHOES_FOOTBALL_GATE = [3115, 3068, 906];
export interface AvatarEditorFigureSetViewProps {
model: IAvatarEditorCategoryModel;
category: CategoryData;
isFromFootballGate: boolean;
setMaxPaletteCount: Dispatch<SetStateAction<number>>;
}
export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props => {
const {model = null, category = null, setMaxPaletteCount = null} = props;
const {model = null, category = null, isFromFootballGate = false, setMaxPaletteCount = null} = props;
const elementRef = useRef<HTMLDivElement>(null);
const selectPart = useCallback(
@ -38,7 +44,15 @@ export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = pro
return (
<AutoGrid innerRef={elementRef} columnCount={3} columnMinHeight={50}>
{category.parts.length > 0 &&
category.parts.map((item, index) => <AvatarEditorFigureSetItemView key={index} partItem={item} onClick={event => selectPart(item)} />)}
category.parts.map(
item =>
(!isFromFootballGate ||
(isFromFootballGate && TSHIRT_FOOTBALL_GATE.includes(item.id)) ||
NUMBER_BEHIND_FOOTBALL_GATE.includes(item.id) ||
PANTS_FOOTBALL_GATE.includes(item.id) ||
SHOES_FOOTBALL_GATE.includes(item.id)) && <AvatarEditorFigureSetItemView key={item.id} partItem={item} onClick={event => selectPart(item)} />
)}
</AutoGrid>
);
};

View File

@ -0,0 +1,35 @@
import {FC} from "react";
import {CreateLinkEvent, FigureData, LocalizeText} from "../../../../api";
import {Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView} from "../../../../common";
import {useFurnitureFootballGateWidget} from "../../../../hooks";
export const FurnitureFootballGateView: FC<{}> = props => {
const {objectId, setObjectId, onClose} = useFurnitureFootballGateWidget();
const onGender = (gender: string) => {
CreateLinkEvent(`avatar-editor/show/${gender}/${objectId}`);
setObjectId(-1);
};
if (objectId === -1) return null;
return (
<NitroCardView className="nitro-football-gate no-resize" theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText("widget.furni.clothingchange.gender.title")} onCloseClick={onClose} />
<NitroCardContentView className="football-gate-content">
<Flex fullWidth center>
<Column>{LocalizeText("widget.furni.clothingchange.gender.info")}</Column>
</Flex>
<Flex className="mt-4 px-2" justifyContent="between">
<Button className="size-buttons" onClick={e => onGender(FigureData.MALE)}>
{LocalizeText("widget.furni.clothingchange.gender.male")}
</Button>
<Button className="size-buttons" onClick={e => onGender(FigureData.FEMALE)}>
{LocalizeText("widget.furni.clothingchange.gender.female")}
</Button>
</Flex>
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -514,3 +514,15 @@
}
}
}
.nitro-football-gate {
width: 300px;
.football-gate-content {
color: black;
.size-buttons {
width: 100px;
}
}
}

View File

@ -7,6 +7,7 @@ import {FurnitureCraftingView} from "./FurnitureCraftingView";
import {FurnitureDimmerView} from "./FurnitureDimmerView";
import {FurnitureExchangeCreditView} from "./FurnitureExchangeCreditView";
import {FurnitureExternalImageView} from "./FurnitureExternalImageView";
import {FurnitureFootballGateView} from "./FurnitureFootballGateView";
import {FurnitureFriendFurniView} from "./FurnitureFriendFurniView";
import {FurnitureGiftOpeningView} from "./FurnitureGiftOpeningView";
import {FurnitureHighScoreView} from "./FurnitureHighScoreView";
@ -43,6 +44,8 @@ export const FurnitureWidgetsView: FC<{}> = props => {
<FurnitureTrophyView />
<FurnitureContextMenuView />
<FurnitureYoutubeDisplayView />
<FurnitureFootballGateView />
</Base>
);
};

View File

@ -5,6 +5,7 @@ export * from "./useFurnitureCraftingWidget";
export * from "./useFurnitureDimmerWidget";
export * from "./useFurnitureExchangeWidget";
export * from "./useFurnitureExternalImageWidget";
export * from './useFurnitureFootballGateWidget';
export * from "./useFurnitureFriendFurniWidget";
export * from "./useFurnitureHighScoreWidget";
export * from "./useFurnitureInternalLinkWidget";

View File

@ -0,0 +1,35 @@
import {RoomEngineTriggerWidgetEvent} from "@nitro/renderer";
import {useState} from "react";
import {GetRoomEngine, IsOwnerOfFurniture} from "../../../../api";
import {useRoomEngineEvent} from "../../../events";
import {useFurniRemovedEvent} from "../../engine";
const useFurnitureFootballGateWidgetState = () => {
const [objectId, setObjectId] = useState<number>(-1);
const [category, setCategory] = useState<number>(-1);
const onClose = () => {
setObjectId(-1);
setCategory(-1);
};
useRoomEngineEvent<RoomEngineTriggerWidgetEvent>(RoomEngineTriggerWidgetEvent.REQUEST_CLOTHING_CHANGE, event => {
const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category);
if (!roomObject || !IsOwnerOfFurniture(roomObject)) return;
setObjectId(event.objectId);
setCategory(event.category);
});
useFurniRemovedEvent(objectId !== -1 && category !== -1, event => {
if (event.id !== objectId || event.category !== category) return;
onClose();
});
return {objectId, setObjectId, onClose};
};
export const useFurnitureFootballGateWidget = useFurnitureFootballGateWidgetState;