mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
1020 lines
38 KiB
TypeScript
1020 lines
38 KiB
TypeScript
import { BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPublishedMessageEvent, ClubGiftInfoEvent, CreateLinkEvent, FrontPageItem, FurniturePlaceComposer, FurniturePlacePaintComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetClubGiftInfo, GetGiftWrappingConfigurationComposer, GetRoomEngine, GetSessionDataManager, GetTickerTime, GiftWrappingConfigurationEvent, GuildMembershipsMessageEvent, HabboClubOffersMessageEvent, LegacyDataType, LimitedEditionSoldOutEvent, MarketplaceMakeOfferResult, NodeData, ProductOfferEvent, PurchaseErrorMessageEvent, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, RoomControllerLevel, RoomEngineObjectPlacedEvent, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType, RoomObjectVariable, RoomPreviewer, SellablePetPalettesMessageEvent, Vector3d } from '@nitrots/nitro-renderer';
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { useBetween } from 'use-between';
|
|
import { BuilderFurniPlaceableStatus, CatalogNode, CatalogPage, CatalogPetPalette, CatalogType, DispatchUiEvent, FurniCategory, GetFurnitureData, GetProductDataForLocalization, GetRoomSession, GiftWrappingConfiguration, ICatalogNode, ICatalogOptions, ICatalogPage, IPageLocalization, IProduct, IPurchasableOffer, IPurchaseOptions, LocalizeText, NotificationAlertType, Offer, PageLocalization, PlacedObjectPurchaseData, PlaySound, Product, ProductTypeEnum, RequestedPage, SearchResult, SendMessageComposer, SoundNames } from '../../api';
|
|
import { CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogPurchaseSoldOutEvent, CatalogPurchasedEvent, InventoryFurniAddedEvent } from '../../events';
|
|
import { useMessageEvent, useNitroEvent, useUiEvent } from '../events';
|
|
import { useNotification } from '../notification';
|
|
import { useCatalogPlaceMultipleItems } from './useCatalogPlaceMultipleItems';
|
|
import { useCatalogSkipPurchaseConfirmation } from './useCatalogSkipPurchaseConfirmation';
|
|
|
|
const DUMMY_PAGE_ID_FOR_OFFER_SEARCH = -12345678;
|
|
const DRAG_AND_DROP_ENABLED = true;
|
|
|
|
const useCatalogState = () =>
|
|
{
|
|
const [ isVisible, setIsVisible ] = useState(false);
|
|
const [ isBusy, setIsBusy ] = useState(false);
|
|
const [ pageId, setPageId ] = useState(-1);
|
|
const [ previousPageId, setPreviousPageId ] = useState(-1);
|
|
const [ currentType, setCurrentType ] = useState(CatalogType.NORMAL);
|
|
const [ rootNode, setRootNode ] = useState<ICatalogNode>(null);
|
|
const [ offersToNodes, setOffersToNodes ] = useState<Map<number, ICatalogNode[]>>(null);
|
|
const [ currentPage, setCurrentPage ] = useState<ICatalogPage>(null);
|
|
const [ currentOffer, setCurrentOffer ] = useState<IPurchasableOffer>(null);
|
|
const [ activeNodes, setActiveNodes ] = useState<ICatalogNode[]>([]);
|
|
const [ searchResult, setSearchResult ] = useState<SearchResult>(null);
|
|
const [ frontPageItems, setFrontPageItems ] = useState<FrontPageItem[]>([]);
|
|
const [ roomPreviewer, setRoomPreviewer ] = useState<RoomPreviewer>(null);
|
|
const [ navigationHidden, setNavigationHidden ] = useState(false);
|
|
const [ purchaseOptions, setPurchaseOptions ] = useState<IPurchaseOptions>({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null });
|
|
const [ catalogOptions, setCatalogOptions ] = useState<ICatalogOptions>({});
|
|
const [ objectMoverRequested, setObjectMoverRequested ] = useState(false);
|
|
const [ catalogPlaceMultipleObjects, setCatalogPlaceMultipleObjects ] = useCatalogPlaceMultipleItems();
|
|
const [ catalogSkipPurchaseConfirmation, setCatalogSkipPurchaseConfirmation ] = useCatalogSkipPurchaseConfirmation();
|
|
const [ purchasableOffer, setPurchaseableOffer ] = useState<IPurchasableOffer>(null);
|
|
const [ placedObjectPurchaseData, setPlacedObjectPurchaseData ] = useState<PlacedObjectPurchaseData>(null);
|
|
const [ furniCount, setFurniCount ] = useState(0);
|
|
const [ furniLimit, setFurniLimit ] = useState(0);
|
|
const [ maxFurniLimit, setMaxFurniLimit ] = useState(0);
|
|
const [ secondsLeft, setSecondsLeft ] = useState(0);
|
|
const [ updateTime, setUpdateTime ] = useState(0);
|
|
const [ secondsLeftWithGrace, setSecondsLeftWithGrace ] = useState(0);
|
|
const [ builderPlacementBlockedByVisitors, setBuilderPlacementBlockedByVisitors ] = useState(false);
|
|
const [ builderPlacementAllowedInCurrentRoom, setBuilderPlacementAllowedInCurrentRoom ] = useState(false);
|
|
const [ builderTrialRoomHideConfirmed, setBuilderTrialRoomHideConfirmed ] = useState(false);
|
|
const { simpleAlert = null, showConfirm = null } = useNotification();
|
|
const requestedPage = useRef(new RequestedPage());
|
|
|
|
const resetState = useCallback(() =>
|
|
{
|
|
setPageId(-1);
|
|
setPreviousPageId(-1);
|
|
setRootNode(null);
|
|
setOffersToNodes(null);
|
|
setCurrentPage(null);
|
|
setCurrentOffer(null);
|
|
setActiveNodes([]);
|
|
setSearchResult(null);
|
|
setFrontPageItems([]);
|
|
setIsVisible(false);
|
|
}, []);
|
|
|
|
const normalizeCatalogType = useCallback((type?: string) =>
|
|
{
|
|
if(type === CatalogType.BUILDER) return CatalogType.BUILDER;
|
|
|
|
return CatalogType.NORMAL;
|
|
}, []);
|
|
|
|
const resetVisibleCatalogState = useCallback((type?: string) =>
|
|
{
|
|
requestedPage.current.resetRequest();
|
|
|
|
setPageId(-1);
|
|
setPreviousPageId(-1);
|
|
setRootNode(null);
|
|
setOffersToNodes(null);
|
|
setCurrentPage(null);
|
|
setCurrentOffer(null);
|
|
setActiveNodes([]);
|
|
setSearchResult(null);
|
|
setFrontPageItems([]);
|
|
setNavigationHidden(false);
|
|
setCurrentType(normalizeCatalogType(type));
|
|
}, [ normalizeCatalogType ]);
|
|
|
|
const openCatalogByType = useCallback((type?: string) =>
|
|
{
|
|
const catalogType = normalizeCatalogType(type);
|
|
|
|
if(currentType !== catalogType)
|
|
{
|
|
resetVisibleCatalogState(catalogType);
|
|
}
|
|
|
|
setIsVisible(true);
|
|
}, [ currentType, normalizeCatalogType, resetVisibleCatalogState ]);
|
|
|
|
const toggleCatalogByType = useCallback((type?: string) =>
|
|
{
|
|
const catalogType = normalizeCatalogType(type);
|
|
|
|
if(isVisible && (currentType === catalogType))
|
|
{
|
|
setIsVisible(false);
|
|
|
|
return;
|
|
}
|
|
|
|
if(currentType !== catalogType)
|
|
{
|
|
resetVisibleCatalogState(catalogType);
|
|
}
|
|
|
|
setIsVisible(true);
|
|
}, [ isVisible, currentType, normalizeCatalogType, resetVisibleCatalogState ]);
|
|
|
|
const getBuilderFurniPlaceableStatus = useCallback((offer: IPurchasableOffer) =>
|
|
{
|
|
if(!offer) return BuilderFurniPlaceableStatus.MISSING_OFFER;
|
|
|
|
const roomSession = GetRoomSession();
|
|
const canUseGuildAdminFallback = (!!roomSession
|
|
&& roomSession.isGuildRoom
|
|
&& (roomSession.controllerLevel >= RoomControllerLevel.GUILD_ADMIN)
|
|
&& (secondsLeft > 0));
|
|
const usesSharedPlacementPool = (!!roomSession && !roomSession.isRoomOwner && (builderPlacementAllowedInCurrentRoom || canUseGuildAdminFallback));
|
|
|
|
if(!roomSession) return BuilderFurniPlaceableStatus.NOT_IN_ROOM;
|
|
|
|
if(!roomSession.isRoomOwner && !builderPlacementAllowedInCurrentRoom && !canUseGuildAdminFallback) return BuilderFurniPlaceableStatus.NOT_GROUP_ADMIN;
|
|
|
|
if(!usesSharedPlacementPool && ((furniCount < 0) || (furniCount >= furniLimit))) return BuilderFurniPlaceableStatus.FURNI_LIMIT_REACHED;
|
|
|
|
if((secondsLeft <= 0) && builderPlacementBlockedByVisitors) return BuilderFurniPlaceableStatus.VISITORS_IN_ROOM;
|
|
|
|
if(secondsLeft <= 0)
|
|
{
|
|
const roomEngine = GetRoomEngine();
|
|
const userDataManager = roomSession.userDataManager;
|
|
const sessionDataManager = GetSessionDataManager();
|
|
|
|
if(!roomEngine || !userDataManager || !sessionDataManager) return BuilderFurniPlaceableStatus.OKAY;
|
|
|
|
const roomObjects = roomEngine.getRoomObjects(roomSession.roomId, RoomObjectCategory.UNIT);
|
|
|
|
if(!roomObjects || !roomObjects.length) return BuilderFurniPlaceableStatus.OKAY;
|
|
|
|
for(const roomObject of roomObjects)
|
|
{
|
|
if(!roomObject) continue;
|
|
|
|
const userData = userDataManager.getUserDataByIndex(roomObject.id);
|
|
|
|
if(!userData || (userData.type !== RoomObjectType.USER)) continue;
|
|
if(userData.webID === sessionDataManager.userId) continue;
|
|
if(userData.isModerator) continue;
|
|
|
|
return BuilderFurniPlaceableStatus.VISITORS_IN_ROOM;
|
|
}
|
|
}
|
|
|
|
return BuilderFurniPlaceableStatus.OKAY;
|
|
}, [ builderPlacementAllowedInCurrentRoom, builderPlacementBlockedByVisitors, furniCount, furniLimit, secondsLeft ]);
|
|
|
|
const isDraggable = useCallback((offer: IPurchasableOffer) =>
|
|
{
|
|
const roomSession = GetRoomSession();
|
|
|
|
if(((DRAG_AND_DROP_ENABLED && roomSession && offer.page && (offer.page.layoutCode !== 'sold_ltd_items') && (currentType === CatalogType.NORMAL) && (roomSession.isRoomOwner || (roomSession.isGuildRoom && (roomSession.controllerLevel >= RoomControllerLevel.GUILD_MEMBER)))) || ((currentType === CatalogType.BUILDER) && (getBuilderFurniPlaceableStatus(offer) === BuilderFurniPlaceableStatus.OKAY))) && (offer.pricingModel !== Offer.PRICING_MODEL_BUNDLE) && (offer.product.productType !== ProductTypeEnum.EFFECT) && (offer.product.productType !== ProductTypeEnum.HABBO_CLUB)) return true;
|
|
|
|
return false;
|
|
}, [ currentType, getBuilderFurniPlaceableStatus ]);
|
|
|
|
const requestOfferToMover = useCallback((offer: IPurchasableOffer) =>
|
|
{
|
|
if(!isDraggable(offer)) return;
|
|
|
|
const product = offer.product;
|
|
|
|
if(!product) return;
|
|
|
|
let category = 0;
|
|
|
|
switch(product.productType)
|
|
{
|
|
case ProductTypeEnum.FLOOR:
|
|
category = RoomObjectCategory.FLOOR;
|
|
break;
|
|
case ProductTypeEnum.WALL:
|
|
category = RoomObjectCategory.WALL;
|
|
break;
|
|
}
|
|
|
|
if(GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.CATALOG, -(offer.offerId), category, product.productClassId, product.extraParam))
|
|
{
|
|
setPurchaseableOffer(offer);
|
|
setObjectMoverRequested(true);
|
|
|
|
setIsVisible(false);
|
|
}
|
|
}, [ isDraggable ]);
|
|
|
|
const resetRoomPaint = useCallback((planeType: string, type: string) =>
|
|
{
|
|
const roomEngine = GetRoomEngine();
|
|
|
|
let wallType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE);
|
|
let floorType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE);
|
|
let landscapeType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_LANDSCAPE_TYPE);
|
|
|
|
wallType = (wallType && wallType.length) ? wallType : '101';
|
|
floorType = (floorType && floorType.length) ? floorType : '101';
|
|
landscapeType = (landscapeType && landscapeType.length) ? landscapeType : '1.1';
|
|
|
|
switch(planeType)
|
|
{
|
|
case 'floor':
|
|
roomEngine.updateRoomInstancePlaneType(roomEngine.activeRoomId, type, wallType, landscapeType, true);
|
|
return;
|
|
case 'wallpaper':
|
|
roomEngine.updateRoomInstancePlaneType(roomEngine.activeRoomId, floorType, type, landscapeType, true);
|
|
return;
|
|
case 'landscape':
|
|
roomEngine.updateRoomInstancePlaneType(roomEngine.activeRoomId, floorType, wallType, type, true);
|
|
return;
|
|
default:
|
|
roomEngine.updateRoomInstancePlaneType(roomEngine.activeRoomId, floorType, wallType, landscapeType, true);
|
|
return;
|
|
}
|
|
}, []);
|
|
|
|
const cancelObjectMover = useCallback(() =>
|
|
{
|
|
if(!purchasableOffer) return;
|
|
|
|
GetRoomEngine().cancelRoomObjectInsert();
|
|
|
|
setObjectMoverRequested(false);
|
|
setPurchaseableOffer(null);
|
|
}, [ purchasableOffer ]);
|
|
|
|
const resetObjectMover = useCallback((flag: boolean = true) =>
|
|
{
|
|
setObjectMoverRequested(prevValue =>
|
|
{
|
|
if(prevValue && flag)
|
|
{
|
|
CreateLinkEvent('catalog/open');
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}, []);
|
|
|
|
const resetPlacedOfferData = useCallback((flag: boolean = false) =>
|
|
{
|
|
if(!flag) resetObjectMover();
|
|
|
|
setPlacedObjectPurchaseData(prevValue =>
|
|
{
|
|
if(prevValue)
|
|
{
|
|
switch(prevValue.category)
|
|
{
|
|
case RoomObjectCategory.FLOOR:
|
|
GetRoomEngine().removeRoomObjectFloor(prevValue.roomId, prevValue.objectId);
|
|
break;
|
|
case RoomObjectCategory.WALL: {
|
|
|
|
switch(prevValue.furniData.className)
|
|
{
|
|
case 'floor':
|
|
case 'wallpaper':
|
|
case 'landscape':
|
|
resetRoomPaint('reset', '');
|
|
break;
|
|
default:
|
|
GetRoomEngine().removeRoomObjectWall(prevValue.roomId, prevValue.objectId);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
GetRoomEngine().deleteRoomObject(prevValue.objectId, prevValue.category);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
});
|
|
}, [ resetObjectMover, resetRoomPaint ]);
|
|
|
|
const getNodeById = useCallback((id: number, node: ICatalogNode) =>
|
|
{
|
|
if((node.pageId === id) && (node !== rootNode)) return node;
|
|
|
|
for(const child of node.children)
|
|
{
|
|
const found = (getNodeById(id, child));
|
|
|
|
if(found) return found;
|
|
}
|
|
|
|
return null;
|
|
}, [ rootNode ]);
|
|
|
|
const getNodeByName = useCallback((name: string, node: ICatalogNode) =>
|
|
{
|
|
if((node.pageName === name) && (node !== rootNode)) return node;
|
|
|
|
for(const child of node.children)
|
|
{
|
|
const found = (getNodeByName(name, child));
|
|
|
|
if(found) return found;
|
|
}
|
|
|
|
return null;
|
|
}, [ rootNode ]);
|
|
|
|
const getNodesByOfferId = useCallback((offerId: number, flag: boolean = false) =>
|
|
{
|
|
if(!offersToNodes || !offersToNodes.size) return null;
|
|
|
|
if(flag)
|
|
{
|
|
const nodes: ICatalogNode[] = [];
|
|
const offers = offersToNodes.get(offerId);
|
|
|
|
if(offers && offers.length) for(const offer of offers) (offer.isVisible && nodes.push(offer));
|
|
|
|
if(nodes.length) return nodes;
|
|
}
|
|
|
|
return offersToNodes.get(offerId);
|
|
}, [ offersToNodes ]);
|
|
|
|
const loadCatalogPage = useCallback((pageId: number, offerId: number) =>
|
|
{
|
|
if(pageId < 0) return;
|
|
|
|
setIsBusy(true);
|
|
setPageId(pageId);
|
|
|
|
if(pageId > -1) SendMessageComposer(new GetCatalogPageComposer(pageId, offerId, currentType));
|
|
}, [ currentType ]);
|
|
|
|
const showCatalogPage = useCallback((pageId: number, layoutCode: string, localization: IPageLocalization, offers: IPurchasableOffer[], offerId: number, acceptSeasonCurrencyAsCredits: boolean) =>
|
|
{
|
|
const catalogPage = (new CatalogPage(pageId, layoutCode, localization, offers, acceptSeasonCurrencyAsCredits) as ICatalogPage);
|
|
|
|
setCurrentPage(catalogPage);
|
|
setPreviousPageId(prevValue => ((pageId !== -1) ? pageId : prevValue));
|
|
setNavigationHidden(false);
|
|
|
|
if((offerId > -1) && catalogPage.offers.length)
|
|
{
|
|
for(const offer of catalogPage.offers)
|
|
{
|
|
if(offer.offerId !== offerId) continue;
|
|
|
|
setCurrentOffer(offer);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
const activateNode = useCallback((targetNode: ICatalogNode, offerId: number = -1) =>
|
|
{
|
|
cancelObjectMover();
|
|
|
|
if(targetNode.parent.pageName === 'root')
|
|
{
|
|
if(targetNode.children.length)
|
|
{
|
|
for(const child of targetNode.children)
|
|
{
|
|
if(!child.isVisible) continue;
|
|
|
|
targetNode = child;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const nodes: ICatalogNode[] = [];
|
|
|
|
let node = targetNode;
|
|
|
|
while(node && (node.pageName !== 'root'))
|
|
{
|
|
nodes.push(node);
|
|
|
|
node = node.parent;
|
|
}
|
|
|
|
nodes.reverse();
|
|
|
|
setActiveNodes(prevValue =>
|
|
{
|
|
const isActive = (prevValue.indexOf(targetNode) >= 0);
|
|
const isOpen = targetNode.isOpen;
|
|
|
|
for(const existing of prevValue)
|
|
{
|
|
existing.deactivate();
|
|
|
|
if(nodes.indexOf(existing) === -1) existing.close();
|
|
}
|
|
|
|
for(const n of nodes)
|
|
{
|
|
n.activate();
|
|
|
|
if(n.parent) n.open();
|
|
|
|
if((n === targetNode.parent) && n.children.length) n.open();
|
|
}
|
|
|
|
if(isActive && isOpen) targetNode.close();
|
|
else targetNode.open();
|
|
|
|
return nodes;
|
|
});
|
|
|
|
if(targetNode.pageId > -1) loadCatalogPage(targetNode.pageId, offerId);
|
|
}, [ setActiveNodes, loadCatalogPage, cancelObjectMover ]);
|
|
|
|
const openPageById = useCallback((id: number) =>
|
|
{
|
|
if(id !== -1) setSearchResult(null);
|
|
|
|
if(!isVisible)
|
|
{
|
|
requestedPage.current.requestById = id;
|
|
|
|
setIsVisible(true);
|
|
}
|
|
else
|
|
{
|
|
const node = getNodeById(id, rootNode);
|
|
|
|
if(node) activateNode(node);
|
|
}
|
|
}, [ isVisible, rootNode, getNodeById, activateNode ]);
|
|
|
|
const openPageByName = useCallback((name: string) =>
|
|
{
|
|
setSearchResult(null);
|
|
|
|
if(!isVisible)
|
|
{
|
|
requestedPage.current.requestByName = name;
|
|
|
|
setIsVisible(true);
|
|
}
|
|
else
|
|
{
|
|
const node = getNodeByName(name, rootNode);
|
|
|
|
if(node) activateNode(node);
|
|
}
|
|
}, [ isVisible, rootNode, getNodeByName, activateNode ]);
|
|
|
|
const openPageByOfferId = useCallback((offerId: number) =>
|
|
{
|
|
setSearchResult(null);
|
|
|
|
if(!isVisible)
|
|
{
|
|
requestedPage.current.requestedByOfferId = offerId;
|
|
|
|
setIsVisible(true);
|
|
}
|
|
else
|
|
{
|
|
const nodes = getNodesByOfferId(offerId);
|
|
|
|
if(!nodes || !nodes.length) return;
|
|
|
|
activateNode(nodes[0], offerId);
|
|
}
|
|
}, [ isVisible, getNodesByOfferId, activateNode ]);
|
|
|
|
const refreshBuilderStatus = useCallback(() =>
|
|
{
|
|
|
|
}, []);
|
|
|
|
useMessageEvent<CatalogPagesListEvent>(CatalogPagesListEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
const parserCatalogType = normalizeCatalogType(parser.catalogType);
|
|
|
|
if(parserCatalogType !== currentType) return;
|
|
|
|
const offers: Map<number, ICatalogNode[]> = new Map();
|
|
|
|
const getCatalogNode = (node: NodeData, depth: number, parent: ICatalogNode) =>
|
|
{
|
|
const catalogNode = (new CatalogNode(node, depth, parent) as ICatalogNode);
|
|
|
|
for(const offerId of catalogNode.offerIds)
|
|
{
|
|
if(offers.has(offerId)) offers.get(offerId).push(catalogNode);
|
|
else offers.set(offerId, [ catalogNode ]);
|
|
}
|
|
|
|
depth++;
|
|
|
|
for(const child of node.children) catalogNode.addChild(getCatalogNode(child, depth, catalogNode));
|
|
|
|
return catalogNode;
|
|
};
|
|
|
|
setRootNode(getCatalogNode(parser.root, 0, null));
|
|
setOffersToNodes(offers);
|
|
});
|
|
|
|
useMessageEvent<CatalogPageMessageEvent>(CatalogPageMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
if(parser.catalogType !== currentType) return;
|
|
|
|
const purchasableOffers: IPurchasableOffer[] = [];
|
|
|
|
for(const offer of parser.offers)
|
|
{
|
|
const products: IProduct[] = [];
|
|
const productData = GetProductDataForLocalization(offer.localizationId);
|
|
|
|
for(const product of offer.products)
|
|
{
|
|
const furnitureData = GetFurnitureData(product.furniClassId, product.productType);
|
|
|
|
products.push(new Product(product.productType, product.furniClassId, product.extraParam, product.productCount, productData, furnitureData, product.uniqueLimitedItem, product.uniqueLimitedSeriesSize, product.uniqueLimitedItemsLeft));
|
|
}
|
|
|
|
if(!products.length) continue;
|
|
|
|
const purchasableOffer = new Offer(offer.offerId, offer.localizationId, offer.rent, offer.priceCredits, offer.priceActivityPoints, offer.priceActivityPointsType, offer.giftable, offer.clubLevel, products, offer.bundlePurchaseAllowed, offer.itemIds, offer.haveOffer);
|
|
|
|
if((currentType === CatalogType.NORMAL) || ((purchasableOffer.pricingModel !== Offer.PRICING_MODEL_BUNDLE) && (purchasableOffer.pricingModel !== Offer.PRICING_MODEL_MULTI))) purchasableOffers.push(purchasableOffer);
|
|
}
|
|
|
|
if(parser.frontPageItems && parser.frontPageItems.length) setFrontPageItems(parser.frontPageItems);
|
|
|
|
setIsBusy(false);
|
|
|
|
if(pageId === parser.pageId)
|
|
{
|
|
showCatalogPage(parser.pageId, parser.layoutCode, new PageLocalization(parser.localization.images.concat(), parser.localization.texts.concat()), purchasableOffers, parser.offerId, parser.acceptSeasonCurrencyAsCredits);
|
|
}
|
|
});
|
|
|
|
useMessageEvent<PurchaseOKMessageEvent>(PurchaseOKMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
DispatchUiEvent(new CatalogPurchasedEvent(parser.offer));
|
|
});
|
|
|
|
useMessageEvent<PurchaseErrorMessageEvent>(PurchaseErrorMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
DispatchUiEvent(new CatalogPurchaseFailureEvent(parser.code));
|
|
});
|
|
|
|
useMessageEvent<PurchaseNotAllowedMessageEvent>(PurchaseNotAllowedMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
DispatchUiEvent(new CatalogPurchaseNotAllowedEvent(parser.code));
|
|
});
|
|
|
|
useMessageEvent<LimitedEditionSoldOutEvent>(LimitedEditionSoldOutEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
DispatchUiEvent(new CatalogPurchaseSoldOutEvent());
|
|
});
|
|
|
|
useMessageEvent<ProductOfferEvent>(ProductOfferEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
const offerData = parser.offer;
|
|
|
|
if(!offerData || !offerData.products.length) return;
|
|
|
|
const offerProductData = offerData.products[0];
|
|
|
|
if(offerProductData.uniqueLimitedItem)
|
|
{
|
|
// update unique
|
|
}
|
|
|
|
const products: IProduct[] = [];
|
|
const productData = GetProductDataForLocalization(offerData.localizationId);
|
|
|
|
for(const product of offerData.products)
|
|
{
|
|
const furnitureData = GetFurnitureData(product.furniClassId, product.productType);
|
|
|
|
products.push(new Product(product.productType, product.furniClassId, product.extraParam, product.productCount, productData, furnitureData, product.uniqueLimitedItem, product.uniqueLimitedSeriesSize, product.uniqueLimitedItemsLeft));
|
|
}
|
|
|
|
const offer = new Offer(offerData.offerId, offerData.localizationId, offerData.rent, offerData.priceCredits, offerData.priceActivityPoints, offerData.priceActivityPointsType, offerData.giftable, offerData.clubLevel, products, offerData.bundlePurchaseAllowed, offerData.itemIds, offerData.haveOffer);
|
|
|
|
if(!((currentType === CatalogType.NORMAL) || ((offer.pricingModel !== Offer.PRICING_MODEL_BUNDLE) && (offer.pricingModel !== Offer.PRICING_MODEL_MULTI)))) return;
|
|
|
|
offer.page = currentPage;
|
|
|
|
setCurrentOffer(offer);
|
|
|
|
if(offer.product && (offer.product.productType === ProductTypeEnum.WALL))
|
|
{
|
|
setPurchaseOptions(prevValue =>
|
|
{
|
|
const newValue = { ...prevValue };
|
|
|
|
newValue.extraData =( offer.product.extraParam || null);
|
|
|
|
return newValue;
|
|
});
|
|
}
|
|
|
|
// (this._isObjectMoverRequested) && (this._purchasableOffer)
|
|
});
|
|
|
|
useMessageEvent<SellablePetPalettesMessageEvent>(SellablePetPalettesMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
const petPalette = new CatalogPetPalette(parser.productCode, parser.palettes.slice());
|
|
|
|
setCatalogOptions(prevValue =>
|
|
{
|
|
const petPalettes = [];
|
|
|
|
if(prevValue.petPalettes) petPalettes.push(...prevValue.petPalettes);
|
|
|
|
for(let i = 0; i < petPalettes.length; i++)
|
|
{
|
|
const palette = petPalettes[i];
|
|
|
|
if(palette.breed === petPalette.breed)
|
|
{
|
|
petPalettes.splice(i, 1);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
petPalettes.push(petPalette);
|
|
|
|
return { ...prevValue, petPalettes };
|
|
});
|
|
});
|
|
|
|
useMessageEvent<HabboClubOffersMessageEvent>(HabboClubOffersMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
setCatalogOptions(prevValue =>
|
|
{
|
|
const windowId = parser.windowId;
|
|
const clubOffersByWindowId = { ...(prevValue.clubOffersByWindowId || {}) };
|
|
|
|
clubOffersByWindowId[windowId] = parser.offers;
|
|
|
|
const clubOffers = clubOffersByWindowId[1] || prevValue.clubOffers;
|
|
|
|
return { ...prevValue, clubOffers, clubOffersByWindowId };
|
|
});
|
|
});
|
|
|
|
useMessageEvent<GuildMembershipsMessageEvent>(GuildMembershipsMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
setCatalogOptions(prevValue =>
|
|
{
|
|
const groups = parser.groups;
|
|
|
|
return { ...prevValue, groups };
|
|
});
|
|
});
|
|
|
|
useMessageEvent<GiftWrappingConfigurationEvent>(GiftWrappingConfigurationEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
setCatalogOptions(prevValue =>
|
|
{
|
|
const giftConfiguration = new GiftWrappingConfiguration(parser);
|
|
|
|
return { ...prevValue, giftConfiguration };
|
|
});
|
|
});
|
|
|
|
useMessageEvent<MarketplaceMakeOfferResult>(MarketplaceMakeOfferResult, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
if(!parser) return;
|
|
|
|
let title = '';
|
|
if(parser.result === 1)
|
|
{
|
|
title = LocalizeText('inventory.marketplace.result.title.success');
|
|
}
|
|
else
|
|
{
|
|
title = LocalizeText('inventory.marketplace.result.title.failure');
|
|
}
|
|
|
|
const message = LocalizeText(`inventory.marketplace.result.${ parser.result }`);
|
|
|
|
simpleAlert(message, NotificationAlertType.DEFAULT, null, null, title);
|
|
});
|
|
|
|
useMessageEvent<ClubGiftInfoEvent>(ClubGiftInfoEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
setCatalogOptions(prevValue =>
|
|
{
|
|
const clubGifts = parser;
|
|
|
|
return { ...prevValue, clubGifts };
|
|
});
|
|
});
|
|
|
|
useMessageEvent<CatalogPublishedMessageEvent>(CatalogPublishedMessageEvent, event =>
|
|
{
|
|
const wasVisible = isVisible;
|
|
|
|
resetState();
|
|
|
|
if(wasVisible) simpleAlert(LocalizeText('catalog.alert.published.description'), NotificationAlertType.ALERT, null, null, LocalizeText('catalog.alert.published.title'));
|
|
});
|
|
|
|
useMessageEvent<BuildersClubFurniCountMessageEvent>(BuildersClubFurniCountMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
setFurniCount(parser.furniCount);
|
|
|
|
refreshBuilderStatus();
|
|
});
|
|
|
|
useMessageEvent<BuildersClubSubscriptionStatusMessageEvent>(BuildersClubSubscriptionStatusMessageEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
setFurniLimit(parser.furniLimit);
|
|
setMaxFurniLimit(parser.maxFurniLimit);
|
|
setSecondsLeft(parser.secondsLeft);
|
|
setUpdateTime(GetTickerTime());
|
|
setSecondsLeftWithGrace(parser.secondsLeftWithGrace);
|
|
setBuilderPlacementBlockedByVisitors(parser.placementBlockedByVisitors);
|
|
setBuilderPlacementAllowedInCurrentRoom(parser.placementAllowedInCurrentRoom);
|
|
|
|
refreshBuilderStatus();
|
|
});
|
|
|
|
useUiEvent<CatalogPurchasedEvent>(CatalogPurchasedEvent.PURCHASE_SUCCESS, event => PlaySound(SoundNames.CREDITS));
|
|
|
|
useNitroEvent<RoomEngineObjectPlacedEvent>(RoomEngineObjectPlacedEvent.PLACED, event =>
|
|
{
|
|
if(!objectMoverRequested || (event.type !== RoomEngineObjectPlacedEvent.PLACED)) return;
|
|
|
|
resetPlacedOfferData(true);
|
|
|
|
if(!purchasableOffer)
|
|
{
|
|
resetObjectMover();
|
|
|
|
return;
|
|
}
|
|
|
|
let placed = false;
|
|
|
|
const product = purchasableOffer.product;
|
|
|
|
if(event.category === RoomObjectCategory.WALL)
|
|
{
|
|
switch(product.furnitureData.className)
|
|
{
|
|
case 'floor':
|
|
case 'wallpaper':
|
|
case 'landscape':
|
|
placed = (event.placedOnFloor || event.placedOnWall);
|
|
break;
|
|
default:
|
|
placed = event.placedInRoom;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
placed = event.placedInRoom;
|
|
}
|
|
|
|
if(!placed)
|
|
{
|
|
resetObjectMover();
|
|
|
|
return;
|
|
}
|
|
|
|
setPlacedObjectPurchaseData(new PlacedObjectPurchaseData(event.roomId, event.objectId, event.category, event.wallLocation, event.x, event.y, event.direction, purchasableOffer));
|
|
|
|
switch(currentType)
|
|
{
|
|
case CatalogType.NORMAL: {
|
|
switch(event.category)
|
|
{
|
|
case RoomObjectCategory.FLOOR:
|
|
GetRoomEngine().addFurnitureFloor(event.roomId, event.objectId, product.productClassId, new Vector3d(event.x, event.y, event.z), new Vector3d(event.direction), 0, new LegacyDataType());
|
|
break;
|
|
case RoomObjectCategory.WALL: {
|
|
switch(product.furnitureData.className)
|
|
{
|
|
case 'floor':
|
|
case 'wallpaper':
|
|
case 'landscape':
|
|
resetRoomPaint(product.furnitureData.className, product.extraParam);
|
|
break;
|
|
default:
|
|
GetRoomEngine().addFurnitureWall(event.roomId, event.objectId, product.productClassId, new Vector3d(event.x, event.y, event.z), new Vector3d(event.direction * 45), 0, event.instanceData, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category);
|
|
|
|
if(roomObject) roomObject.model.setValue(RoomObjectVariable.FURNITURE_ALPHA_MULTIPLIER, 0.5);
|
|
|
|
if(catalogSkipPurchaseConfirmation)
|
|
{
|
|
SendMessageComposer(new PurchaseFromCatalogComposer(pageId, purchasableOffer.offerId, product.extraParam, 1));
|
|
|
|
if(catalogPlaceMultipleObjects) requestOfferToMover(purchasableOffer);
|
|
}
|
|
else
|
|
{
|
|
// confirm
|
|
|
|
if(catalogPlaceMultipleObjects) requestOfferToMover(purchasableOffer);
|
|
}
|
|
break;
|
|
}
|
|
case CatalogType.BUILDER: {
|
|
const placeBuilderItem = () =>
|
|
{
|
|
let pageId = purchasableOffer.page.pageId;
|
|
|
|
if(pageId === DUMMY_PAGE_ID_FOR_OFFER_SEARCH)
|
|
{
|
|
pageId = -1;
|
|
}
|
|
|
|
switch(event.category)
|
|
{
|
|
case RoomObjectCategory.FLOOR:
|
|
SendMessageComposer(new BuildersClubPlaceRoomItemMessageComposer(pageId, purchasableOffer.offerId, product.extraParam, event.x, event.y, event.direction));
|
|
break;
|
|
case RoomObjectCategory.WALL:
|
|
SendMessageComposer(new BuildersClubPlaceWallItemMessageComposer(pageId, purchasableOffer.offerId, product.extraParam, event.wallLocation));
|
|
break;
|
|
}
|
|
|
|
if(catalogPlaceMultipleObjects && ((furniCount + 1) < furniLimit)) requestOfferToMover(purchasableOffer);
|
|
};
|
|
|
|
if((secondsLeft <= 0) && (furniCount <= 0) && !builderTrialRoomHideConfirmed && showConfirm)
|
|
{
|
|
showConfirm(LocalizeText('room.confirm.hide_room'), () =>
|
|
{
|
|
setBuilderTrialRoomHideConfirmed(true);
|
|
placeBuilderItem();
|
|
}, () => resetPlacedOfferData());
|
|
}
|
|
else
|
|
{
|
|
placeBuilderItem();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
useUiEvent<InventoryFurniAddedEvent>(InventoryFurniAddedEvent.FURNI_ADDED, event =>
|
|
{
|
|
const roomEngine = GetRoomEngine();
|
|
|
|
if(!placedObjectPurchaseData || (placedObjectPurchaseData.productClassId !== event.spriteId) || (placedObjectPurchaseData.roomId !== roomEngine.activeRoomId)) return;
|
|
|
|
switch(event.category)
|
|
{
|
|
case FurniCategory.FLOOR: {
|
|
const floorType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE);
|
|
|
|
if(placedObjectPurchaseData.extraParam !== floorType) SendMessageComposer(new FurniturePlacePaintComposer(event.id));
|
|
break;
|
|
}
|
|
case FurniCategory.WALL_PAPER: {
|
|
const wallType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE);
|
|
|
|
if(placedObjectPurchaseData.extraParam !== wallType) SendMessageComposer(new FurniturePlacePaintComposer(event.id));
|
|
break;
|
|
}
|
|
case FurniCategory.LANDSCAPE: {
|
|
const landscapeType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_LANDSCAPE_TYPE);
|
|
|
|
if(placedObjectPurchaseData.extraParam !== landscapeType) SendMessageComposer(new FurniturePlacePaintComposer(event.id));
|
|
break;
|
|
}
|
|
default:
|
|
SendMessageComposer(new FurniturePlaceComposer(event.id, placedObjectPurchaseData.category, placedObjectPurchaseData.wallLocation, placedObjectPurchaseData.x, placedObjectPurchaseData.y, placedObjectPurchaseData.direction));
|
|
}
|
|
|
|
if(!catalogPlaceMultipleObjects) resetPlacedOfferData();
|
|
});
|
|
|
|
useEffect(() =>
|
|
{
|
|
return () => setCurrentOffer(null);
|
|
}, [ currentPage ]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
if(!isVisible || !rootNode || !offersToNodes || !requestedPage.current) return;
|
|
|
|
switch(requestedPage.current.requestType)
|
|
{
|
|
case RequestedPage.REQUEST_TYPE_NONE:
|
|
if(currentPage) return;
|
|
|
|
if(rootNode.isBranch)
|
|
{
|
|
for(const child of rootNode.children)
|
|
{
|
|
if(child && child.isVisible)
|
|
{
|
|
activateNode(child);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
case RequestedPage.REQUEST_TYPE_ID:
|
|
openPageById(requestedPage.current.requestById);
|
|
requestedPage.current.resetRequest();
|
|
return;
|
|
case RequestedPage.REQUEST_TYPE_OFFER:
|
|
openPageByOfferId(requestedPage.current.requestedByOfferId);
|
|
requestedPage.current.resetRequest();
|
|
return;
|
|
case RequestedPage.REQUEST_TYPE_NAME:
|
|
openPageByName(requestedPage.current.requestByName);
|
|
requestedPage.current.resetRequest();
|
|
return;
|
|
}
|
|
}, [ isVisible, rootNode, offersToNodes, currentPage, activateNode, openPageById, openPageByOfferId, openPageByName ]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
if(!searchResult && currentPage && (currentPage.pageId === -1)) openPageById(previousPageId);
|
|
}, [ searchResult, currentPage, previousPageId, openPageById ]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
if(!currentOffer) return;
|
|
|
|
setPurchaseOptions({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null });
|
|
}, [ currentOffer ]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
if(secondsLeft > 0) setBuilderTrialRoomHideConfirmed(false);
|
|
}, [ secondsLeft ]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
if(!isVisible || rootNode) return;
|
|
|
|
SendMessageComposer(new GetGiftWrappingConfigurationComposer());
|
|
SendMessageComposer(new GetClubGiftInfo());
|
|
SendMessageComposer(new GetCatalogIndexComposer(currentType));
|
|
SendMessageComposer(new BuildersClubQueryFurniCountMessageComposer());
|
|
}, [ isVisible, rootNode, currentType ]);
|
|
|
|
useEffect(() =>
|
|
{
|
|
setRoomPreviewer(new RoomPreviewer(GetRoomEngine(), ++RoomPreviewer.PREVIEW_COUNTER));
|
|
|
|
return () =>
|
|
{
|
|
setRoomPreviewer(prevValue =>
|
|
{
|
|
prevValue.dispose();
|
|
|
|
return null;
|
|
});
|
|
};
|
|
}, []);
|
|
|
|
return { isVisible, setIsVisible, isBusy, pageId, previousPageId, currentType, rootNode, offersToNodes, currentPage, setCurrentPage, currentOffer, setCurrentOffer, activeNodes, searchResult, setSearchResult, frontPageItems, roomPreviewer, navigationHidden, setNavigationHidden, purchaseOptions, setPurchaseOptions, catalogOptions, setCatalogOptions, getNodeById, getNodeByName, activateNode, openPageById, openPageByName, openPageByOfferId, requestOfferToMover, openCatalogByType, toggleCatalogByType, furniCount, furniLimit, maxFurniLimit, secondsLeft, secondsLeftWithGrace, updateTime, catalogPlaceMultipleObjects, setCatalogPlaceMultipleObjects, getBuilderFurniPlaceableStatus };
|
|
};
|
|
|
|
export const useCatalog = () => useBetween(useCatalogState);
|