Add unseen tracker

This commit is contained in:
Bill 2021-07-16 13:17:52 -04:00
parent bc9d0aa4a1
commit 183f4f6d53
48 changed files with 741 additions and 129 deletions

View File

@ -750,6 +750,7 @@
"${images.url}/additions/number_3.png",
"${images.url}/additions/number_4.png",
"${images.url}/additions/number_5.png",
"${images.url}/additions/pet_experience_bubble.png",
"${images.url}/loading_icon.png",
"${images.url}/clear_icon.png",
"${images.url}/big_arrow.png"

View File

@ -5,6 +5,7 @@
@import './fontawesome/brands';
@import './fontawesome/regular';
@import './scrollbars';
@import './slider';
@import './grid';
@import './icons';
@import './utils';

View File

@ -0,0 +1,54 @@
.nitro-slider {
display: flex;
align-items: center;
width: 100%;
height: 25px;
.track {
height: 3px;
border-radius: $border-radius;
overflow: hidden;
&.track-0 {
background-color: $primary;
}
&.track-1 {
background-color: $muted;
}
}
.thumb {
border-radius: 50%;
width: 25px;
height: 25px;
background-color: gray;
font-size: 10px;
text-align: center;
line-height: 25px;
padding: 0 3px;
&:hover,
.active {
cursor: pointer;
}
&.active {
outline: none;
}
&.degree {
&:after {
content: "\00b0"
}
}
&.percent {
&:after {
content: "\0025"
}
}
}
}

View File

@ -0,0 +1,20 @@
import { NitroEvent } from 'nitro-renderer';
export class UnseenItemTrackerUpdateEvent extends NitroEvent
{
public static UPDATE_COUNT: string = 'UITUE_UPDATE_COUNTER';
private _count: number;
constructor(count: number)
{
super(UnseenItemTrackerUpdateEvent.UPDATE_COUNT);
this._count = count;
}
public get count(): number
{
return this._count;
}
}

View File

@ -1,3 +1,4 @@
export * from './InventoryEvent';
export * from './InventoryTradeRequestEvent';
export * from './InventoryTradeStartEvent';
export * from './UnseenItemTrackerUpdateEvent';

View File

@ -6,7 +6,7 @@
width: 100%;
height: 100%;
border-color: $grid-border-color !important;
background-color: $grid-bg-color !important;
background-color: $grid-bg-color;
background-position: center;
background-repeat: no-repeat;
overflow: hidden;
@ -16,6 +16,10 @@
background-color: $grid-active-bg-color !important;
}
&.unseen {
background-color: rgba($success, 0.4);
}
.badge {
top: 2px;
right: 2px;

View File

@ -4,13 +4,13 @@ import { NitroCardGridItemViewProps } from './NitroCardGridItemView.types';
export const NitroCardGridItemView: FC<NitroCardGridItemViewProps> = props =>
{
const { itemActive = false, itemCount = 1, itemUnique = false, itemUniqueNumber = 0, itemImage = null, className = '', style = {}, children = null, ...rest } = props;
const { itemImage = null, itemActive = false, itemCount = 1, itemUnique = false, itemUniqueNumber = 0, itemUnseen = false, className = '', style = {}, children = null, ...rest } = props;
const imageUrl = `url(${ itemImage })`;
return (
<div className="col pb-1 grid-item-container">
<div className={ `position-relative border border-2 rounded grid-item cursor-pointer${ itemActive ? ' active' : '' }${ itemUnique ? ' unique-item' : '' } ${ className || '' }` } style={ itemImage ? { ...style, backgroundImage: imageUrl } : style } { ...rest }>
<div className={ `position-relative border border-2 rounded grid-item cursor-pointer${ itemActive ? ' active' : '' }${ itemUnique ? ' unique-item' : '' }${ itemUnseen ? ' unseen' : ''} ${ className || '' }` } style={ itemImage ? { ...style, backgroundImage: imageUrl } : style } { ...rest }>
{ (itemCount > 1) &&
<span className="position-absolute badge border bg-danger px-1 rounded-circle">{ itemCount }</span> }
{ itemUnique &&

View File

@ -7,4 +7,5 @@ export interface NitroCardGridItemViewProps extends DetailsHTMLAttributes<HTMLDi
itemCount?: number;
itemUnique?: boolean;
itemUniqueNumber?: number;
itemUnseen?: boolean;
}

View File

@ -1,6 +1,12 @@
.nitro-card-header {
min-height: 33px;
max-height: 33px;
white-space: nowrap;
overflow: hidden;
.header-text {
margin: 0 35px;
}
&.simple-header {
min-height: 28px;

View File

@ -27,7 +27,7 @@ export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
<div className="drag-handler container-fluid bg-primary">
<div className="row nitro-card-header">
<div className="d-flex justify-content-center align-items-center w-100 position-relative">
<div className="h4 m-0 text-white text-shadow">{ headerText }</div>
<div className="h4 text-white text-shadow header-text">{ headerText }</div>
<div className="position-absolute header-close" onMouseDown={ event => event.stopPropagation() } onClick={ onCloseClick }>
<i className="fas fa-times" />
</div>

View File

@ -3,4 +3,11 @@
&:last-child() {
margin-right: 0 !important;
}
.count {
color: $white;
font-size: 10px;
line-height: 10px;
padding: 2px 3px;
}
}

View File

@ -4,11 +4,15 @@ import { NitroCardTabsItemViewProps } from './NitroCardTabsItemView.types';
export const NitroCardTabsItemView: FC<NitroCardTabsItemViewProps> = props =>
{
const { children = null, isActive = false, onClick = null } = props;
const { children = null, isActive = false, count = 0, onClick = null } = props;
return (
<li className="nav-item me-1 cursor-pointer" onClick={ onClick }>
<span className={ 'nav-link ' + classNames({ 'active': isActive }) }>{ children }</span>
<span className={ 'nav-link ' + classNames({ 'active': isActive }) }>
{ children }
{ (count > 0) &&
<span className="bg-danger ms-1 rounded count">{ count }</span> }
</span>
</li>
);
}

View File

@ -3,5 +3,6 @@ import { MouseEventHandler } from 'react';
export interface NitroCardTabsItemViewProps
{
isActive?: boolean;
count?: number;
onClick?: MouseEventHandler<HTMLLIElement>;
}

View File

@ -1,4 +1,4 @@
import { AdvancedMap, BadgesEvent, BotAddedToInventoryEvent, BotInventoryMessageEvent, BotRemovedFromInventoryEvent, FurnitureListAddOrUpdateEvent, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListItemParser, FurnitureListRemovedEvent, FurniturePostItPlacedEvent, PetAddedToInventoryEvent, PetData, PetInventoryEvent, PetRemovedFromInventory, TradingAcceptEvent, TradingCloseEvent, TradingCompletedEvent, TradingConfirmationEvent, TradingListItemEvent, TradingNotOpenEvent, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingYouAreNotAllowedEvent } from 'nitro-renderer';
import { AdvancedMap, BadgesEvent, BotAddedToInventoryEvent, BotInventoryMessageEvent, BotRemovedFromInventoryEvent, FurnitureListAddOrUpdateEvent, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListItemParser, FurnitureListRemovedEvent, FurniturePostItPlacedEvent, PetAddedToInventoryEvent, PetData, PetInventoryEvent, PetRemovedFromInventory, TradingAcceptEvent, TradingCloseEvent, TradingCompletedEvent, TradingConfirmationEvent, TradingListItemEvent, TradingNotOpenEvent, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingYouAreNotAllowedEvent, UnseenItemsEvent } from 'nitro-renderer';
import { FC, useCallback } from 'react';
import { GetRoomSession, GetSessionDataManager } from '../../api';
import { CreateMessageHook } from '../../hooks/messages/message-event';
@ -17,7 +17,7 @@ let petMsgFragments: Map<number, PetData>[] = null;
export const InventoryMessageHandler: FC<InventoryMessageHandlerProps> = props =>
{
const { dispatchFurnitureState = null, dispatchBotState = null, dispatchPetState = null, dispatchBadgeState = null } = useInventoryContext();
const { dispatchFurnitureState = null, dispatchBotState = null, dispatchPetState = null, dispatchBadgeState = null, unseenTracker = null } = useInventoryContext();
const onFurnitureListAddOrUpdateEvent = useCallback((event: FurnitureListAddOrUpdateEvent) =>
{
@ -43,9 +43,9 @@ export const InventoryMessageHandler: FC<InventoryMessageHandlerProps> = props =
dispatchFurnitureState({
type: InventoryFurnitureActions.PROCESS_FRAGMENT,
payload: { fragment }
payload: { fragment, unseenTracker }
});
}, [ dispatchFurnitureState ]);
}, [ unseenTracker, dispatchFurnitureState ]);
const onFurnitureListInvalidateEvent = useCallback((event: FurnitureListInvalidateEvent) =>
{
@ -82,9 +82,9 @@ export const InventoryMessageHandler: FC<InventoryMessageHandlerProps> = props =
dispatchBotState({
type: InventoryBotActions.PROCESS_FRAGMENT,
payload: { fragment }
payload: { fragment, unseenTracker }
});
}, [ dispatchBotState ]);
}, [ dispatchBotState, unseenTracker ]);
const onBotAddedToInventoryEvent = useCallback((event: BotAddedToInventoryEvent) =>
{
@ -122,9 +122,9 @@ export const InventoryMessageHandler: FC<InventoryMessageHandlerProps> = props =
dispatchPetState({
type: InventoryPetActions.PROCESS_FRAGMENT,
payload: { fragment }
payload: { fragment, unseenTracker }
});
}, [dispatchPetState ]);
}, [ dispatchPetState, unseenTracker ]);
const onPetAddedToInventoryEvent = useCallback((event: PetAddedToInventoryEvent) =>
{
@ -294,6 +294,20 @@ export const InventoryMessageHandler: FC<InventoryMessageHandlerProps> = props =
console.log(parser);
}, []);
const onUnseenItemsEvent = useCallback((event: UnseenItemsEvent) =>
{
const parser = event.getParser();
console.log(parser);
for(const category of parser.categories)
{
const itemIds = parser.getItemsByCategory(category);
unseenTracker.addItems(category, itemIds);
}
}, [ unseenTracker ]);
CreateMessageHook(FurnitureListAddOrUpdateEvent, onFurnitureListAddOrUpdateEvent);
CreateMessageHook(FurnitureListEvent, onFurnitureListEvent);
CreateMessageHook(FurnitureListInvalidateEvent, onFurnitureListInvalidateEvent);
@ -316,6 +330,7 @@ export const InventoryMessageHandler: FC<InventoryMessageHandlerProps> = props =
CreateMessageHook(TradingOpenFailedEvent, onTradingOpenFailedEvent);
CreateMessageHook(TradingOtherNotAllowedEvent, onTradingOtherNotAllowedEvent);
CreateMessageHook(TradingYouAreNotAllowedEvent, onTradingYouAreNotAllowedEvent);
CreateMessageHook(UnseenItemsEvent, onUnseenItemsEvent);
return null;
}

View File

@ -10,6 +10,9 @@ import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, Nitro
import { LocalizeText } from '../../utils/LocalizeText';
import { isObjectMoverRequested, setObjectMoverRequested } from './common/InventoryUtilities';
import { TradeState } from './common/TradeState';
import { IUnseenItemTracker } from './common/unseen/IUnseenItemTracker';
import { UnseenItemCategory } from './common/unseen/UnseenItemCategory';
import { UnseenItemTracker } from './common/unseen/UnseenItemTracker';
import { InventoryContextProvider } from './context/InventoryContext';
import { InventoryMessageHandler } from './InventoryMessageHandler';
import { InventoryTabs, InventoryViewProps } from './InventoryView.types';
@ -23,18 +26,20 @@ import { InventoryFurnitureView } from './views/furniture/InventoryFurnitureView
import { InventoryPetView } from './views/pet/InventoryPetView';
import { InventoryTradeView } from './views/trade/InventoryTradeView';
const tabs = [ InventoryTabs.FURNITURE, InventoryTabs.BOTS, InventoryTabs.PETS, InventoryTabs.BADGES ];
const TABS = [ InventoryTabs.FURNITURE, InventoryTabs.BOTS, InventoryTabs.PETS, InventoryTabs.BADGES ];
const UNSEEN_CATEGORIES = [ UnseenItemCategory.FURNI, UnseenItemCategory.BOT, UnseenItemCategory.PET, UnseenItemCategory.BADGE ];
export const InventoryView: FC<InventoryViewProps> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ currentTab, setCurrentTab ] = useState<string>(tabs[0]);
const [ currentTab, setCurrentTab ] = useState<string>(TABS[0]);
const [ roomSession, setRoomSession ] = useState<IRoomSession>(null);
const [ roomPreviewer, setRoomPreviewer ] = useState<RoomPreviewer>(null);
const [ furnitureState, dispatchFurnitureState ] = useReducer(InventoryFurnitureReducer, initialInventoryFurniture);
const [ botState, dispatchBotState ] = useReducer(InventoryBotReducer, initialInventoryBot);
const [ petState, dispatchPetState ] = useReducer(InventoryPetReducer, initialInventoryPet);
const [ badgeState, dispatchBadgeState ] = useReducer(InventoryBadgeReducer, initialInventoryBadge);
const [ unseenTracker ] = useState<IUnseenItemTracker>(new UnseenItemTracker());
const close = useCallback(() =>
{
@ -119,6 +124,45 @@ export const InventoryView: FC<InventoryViewProps> = props =>
useRoomSessionManagerEvent(RoomSessionEvent.CREATED, onRoomSessionEvent);
useRoomSessionManagerEvent(RoomSessionEvent.ENDED, onRoomSessionEvent);
const resetTrackerForTab = useCallback((name: string) =>
{
const tabIndex = TABS.indexOf(name);
if(tabIndex === -1) return;
const unseenCategory = UNSEEN_CATEGORIES[tabIndex];
if(unseenCategory === -1) return;
const count = unseenTracker.getCount(unseenCategory);
if(!count) return;
unseenTracker.resetCategory(unseenCategory);
switch(unseenCategory)
{
case UnseenItemCategory.FURNI:
for(const groupItem of furnitureState.groupItems) groupItem.hasUnseenItems = false;
return;
}
}, [ furnitureState.groupItems, unseenTracker ]);
const switchTab = useCallback((prevTab: string, nextTab: string) =>
{
if(nextTab) setCurrentTab(nextTab);
resetTrackerForTab(prevTab);
}, [ resetTrackerForTab ]);
useEffect(() =>
{
if(isVisible) return;
if(currentTab) resetTrackerForTab(currentTab);
}, [ currentTab, isVisible, resetTrackerForTab ]);
useEffect(() =>
{
setRoomPreviewer(new RoomPreviewer(GetRoomEngine(), ++RoomPreviewer.PREVIEW_COUNTER));
@ -143,7 +187,7 @@ export const InventoryView: FC<InventoryViewProps> = props =>
}, [ furnitureState.tradeData, isVisible ]);
return (
<InventoryContextProvider value={ { furnitureState, dispatchFurnitureState, botState, dispatchBotState, petState, dispatchPetState, badgeState, dispatchBadgeState } }>
<InventoryContextProvider value={ { furnitureState, dispatchFurnitureState, botState, dispatchBotState, petState, dispatchPetState, badgeState, dispatchBadgeState, unseenTracker } }>
<InventoryMessageHandler />
{ isVisible &&
<NitroCardView className="nitro-inventory">
@ -151,10 +195,12 @@ export const InventoryView: FC<InventoryViewProps> = props =>
{ !furnitureState.tradeData &&
<>
<NitroCardTabsView>
{ tabs.map((name, index) =>
{ TABS.map((name, index) =>
{
const unseenCount = unseenTracker.getCount(UNSEEN_CATEGORIES[index]);
return (
<NitroCardTabsItemView key={ index } isActive={ (currentTab === name) } onClick={ event => setCurrentTab(name) }>
<NitroCardTabsItemView key={ index } isActive={ (currentTab === name) } onClick={ event => switchTab(currentTab, name) } count={ unseenCount }>
{ LocalizeText(name) }
</NitroCardTabsItemView>
);

View File

@ -0,0 +1,26 @@
export class BadgeItem
{
private _code: string;
private _isUnseen: boolean;
constructor(code: string)
{
this._code = code;
this._isUnseen = false;
}
public get code(): string
{
return this._code;
}
public get isUnseen(): boolean
{
return this._isUnseen;
}
public set isUnseen(flag: boolean)
{
this._isUnseen = flag;
}
}

View File

@ -4,6 +4,8 @@ import { InventoryEvent } from '../../../events';
import { dispatchUiEvent } from '../../../hooks/events/ui/ui-event';
import { BotItem } from './BotItem';
import { getPlacingItemId, setObjectMoverRequested, setPlacingItemId } from './InventoryUtilities';
import { IUnseenItemTracker } from './unseen/IUnseenItemTracker';
import { UnseenItemCategory } from './unseen/UnseenItemCategory';
export function cancelRoomObjectPlacement(): void
{
@ -45,7 +47,7 @@ function getAllItemIds(botItems: BotItem[]): number[]
return itemIds;
}
export function processBotFragment(set: BotItem[], fragment: BotData[]): BotItem[]
export function processBotFragment(set: BotItem[], fragment: BotData[], unseenTracker: IUnseenItemTracker): BotItem[]
{
const existingIds = getAllItemIds(set);
const addedDatas: BotData[] = [];
@ -55,12 +57,19 @@ export function processBotFragment(set: BotItem[], fragment: BotData[]): BotItem
for(const itemId of existingIds)
{
let remove = true;
for(const botData of fragment)
{
if(botData.id === itemId) continue;
if(botData.id === itemId)
{
remove = false;
removedIds.push(itemId);
break;
}
}
if(remove) removedIds.push(itemId);
}
const emptyExistingSet = (existingIds.length === 0);
@ -69,7 +78,7 @@ export function processBotFragment(set: BotItem[], fragment: BotData[]): BotItem
for(const botData of addedDatas)
{
addSingleBotItem(botData, set, true);
addSingleBotItem(botData, set, unseenTracker.isUnseen(UnseenItemCategory.BOT, botData.id));
}
return set;
@ -109,6 +118,8 @@ export function addSingleBotItem(botData: BotData, set: BotItem[], unseen: boole
if(unseen)
{
botItem.isUnseen = true;
set.unshift(botItem);
}
else

View File

@ -7,6 +7,8 @@ import { FurniCategory } from './FurniCategory';
import { FurnitureItem } from './FurnitureItem';
import { GroupItem } from './GroupItem';
import { getPlacingItemId, setObjectMoverRequested, setPlacingItemId } from './InventoryUtilities';
import { IUnseenItemTracker } from './unseen/IUnseenItemTracker';
import { UnseenItemCategory } from './unseen/UnseenItemCategory';
export function attemptItemPlacement(groupItem: GroupItem, flag: boolean = false): boolean
{
@ -121,7 +123,7 @@ function getAllItemIds(groupItems: GroupItem[]): number[]
return itemIds;
}
export function processFurniFragment(set: GroupItem[], fragment: Map<number, FurnitureListItemParser>): GroupItem[]
export function processFurniFragment(set: GroupItem[], fragment: Map<number, FurnitureListItemParser>, unseenTracker: IUnseenItemTracker): GroupItem[]
{
const existingIds = getAllItemIds(set);
const addedIds: number[] = [];
@ -143,7 +145,7 @@ export function processFurniFragment(set: GroupItem[], fragment: Map<number, Fur
const item = new FurnitureItem(parser);
addFurnitureItem(set, item, true);
addFurnitureItem(set, item, unseenTracker.isUnseen(UnseenItemCategory.FURNI, id));
}
return set;
@ -186,23 +188,19 @@ export function removeFurniItemById(id: number, set: GroupItem[]): GroupItem
return null;
}
export function addFurnitureItem(set: GroupItem[], item: FurnitureItem, flag: boolean): void
export function addFurnitureItem(set: GroupItem[], item: FurnitureItem, unseen: boolean): void
{
let groupItem: GroupItem = null;
if(!item.isGroupable)
{
groupItem = addSingleFurnitureItem(set, item, flag);
addSingleFurnitureItem(set, item, unseen);
}
else
{
groupItem = addGroupableFurnitureItem(set, item, flag);
addGroupableFurnitureItem(set, item, unseen);
}
if(!flag) groupItem.hasUnseenItems = true;
}
function addSingleFurnitureItem(set: GroupItem[], item: FurnitureItem, flag: boolean): GroupItem
function addSingleFurnitureItem(set: GroupItem[], item: FurnitureItem, unseen: boolean): GroupItem
{
const groupItems: GroupItem[] = [];
@ -216,10 +214,9 @@ function addSingleFurnitureItem(set: GroupItem[], item: FurnitureItem, flag: boo
if(groupItem.getItemById(item.id)) return groupItem;
}
const unseen = flag; //this.isFurnitureUnseen(item);
const groupItem = createGroupItem(item.type, item.category, item.stuffData, item.extra, unseen);
const groupItem = createGroupItem(item.type, item.category, item.stuffData, item.extra);
groupItem.push(item, unseen);
groupItem.push(item);
if(unseen)
{
@ -272,11 +269,9 @@ function addGroupableFurnitureItem(set: GroupItem[], item: FurnitureItem, unseen
}
}
// unseen = unseen; //this.isFurnitureUnseen(item);
if(existingGroup)
{
existingGroup.push(item, unseen);
existingGroup.push(item);
if(unseen)
{
@ -292,9 +287,9 @@ function addGroupableFurnitureItem(set: GroupItem[], item: FurnitureItem, unseen
return existingGroup;
}
existingGroup = createGroupItem(item.type, item.category, item.stuffData, item.extra, unseen);
existingGroup = createGroupItem(item.type, item.category, item.stuffData, item.extra);
existingGroup.push(item, unseen);
existingGroup.push(item);
if(unseen)
{
@ -310,7 +305,7 @@ function addGroupableFurnitureItem(set: GroupItem[], item: FurnitureItem, unseen
return existingGroup;
}
export function createGroupItem(type: number, category: number, stuffData: IObjectData, extra: number = NaN, flag: boolean = false): GroupItem
export function createGroupItem(type: number, category: number, stuffData: IObjectData, extra: number = NaN): GroupItem
{
// const iconImage: HTMLImageElement = null;

View File

@ -117,7 +117,7 @@ export class GroupItem
return items;
}
public push(item: FurnitureItem, unseen: boolean = false): void
public push(item: FurnitureItem): void
{
const items = [ ...this._items ];

View File

@ -4,6 +4,8 @@ import { InventoryEvent } from '../../../events';
import { dispatchUiEvent } from '../../../hooks/events/ui/ui-event';
import { getPlacingItemId, setObjectMoverRequested, setPlacingItemId } from './InventoryUtilities';
import { PetItem } from './PetItem';
import { IUnseenItemTracker } from './unseen/IUnseenItemTracker';
import { UnseenItemCategory } from './unseen/UnseenItemCategory';
export function cancelRoomObjectPlacement(): void
{
@ -75,7 +77,7 @@ function getAllItemIds(petItems: PetItem[]): number[]
return itemIds;
}
export function processPetFragment(set: PetItem[], fragment: Map<number, PetData>): PetItem[]
export function processPetFragment(set: PetItem[], fragment: Map<number, PetData>, unseenTracker: IUnseenItemTracker): PetItem[]
{
const existingIds = getAllItemIds(set);
const addedIds: number[] = [];
@ -95,7 +97,7 @@ export function processPetFragment(set: PetItem[], fragment: Map<number, PetData
if(!parser) continue;
addSinglePetItem(parser, set, true);
addSinglePetItem(parser, set, unseenTracker.isUnseen(UnseenItemCategory.PET, parser.id));
}
return set;
@ -135,6 +137,8 @@ export function addSinglePetItem(petData: PetData, set: PetItem[], unseen: boole
if(unseen)
{
petItem.isUnseen = true;
set.unshift(petItem);
}
else

View File

@ -0,0 +1,13 @@
export interface IUnseenItemTracker
{
dispose(): void;
resetCategory(category: number): boolean;
resetItems(category: number, itemIds: number[]): boolean;
resetCategoryIfEmpty(category: number): boolean;
isUnseen(category: number, itemId: number): boolean;
removeUnseen(category: number, itemId: number): boolean;
getIds(category: number): number[];
getCount(category: number): number;
getFullCount(): number;
addItems(category: number, itemIds: number[]): void;
}

View File

@ -0,0 +1,10 @@
export class UnseenItemCategory
{
public static FURNI: number = 1;
public static RENTABLE: number = 2;
public static PET: number = 3;
public static BADGE: number = 4;
public static BOT: number = 5;
public static GAMES: number = 6;
public static _Str_12463: number[] = [ UnseenItemCategory.FURNI, UnseenItemCategory.RENTABLE, UnseenItemCategory.PET, UnseenItemCategory.BADGE, UnseenItemCategory.BOT ];
}

View File

@ -0,0 +1,145 @@
import { UnseenResetCategoryComposer, UnseenResetItemsComposer } from 'nitro-renderer';
import { UnseenItemTrackerUpdateEvent } from '../../../../events';
import { dispatchUiEvent, SendMessageHook } from '../../../../hooks';
import { IUnseenItemTracker } from './IUnseenItemTracker';
export class UnseenItemTracker implements IUnseenItemTracker
{
private _unseenItems: Map<number, number[]> = new Map();
public dispose(): void
{
this._unseenItems.clear();
}
public resetCategory(category: number): boolean
{
if(!this.getCount(category)) return false;
this._unseenItems.delete(category);
this.dispatchUpdateEvent();
this.sendResetCategoryMessage(category);
return true;
}
public resetItems(category: number, itemIds: number[]): boolean
{
if(!this.getCount(category)) return false;
const existing = this._unseenItems.get(category);
for(const itemId of itemIds)
{
existing.splice(existing.indexOf(itemId), 1);
}
this.dispatchUpdateEvent();
this.sendResetItemsMessage(category, itemIds);
return true;
}
public resetCategoryIfEmpty(category: number): boolean
{
if(this.getCount(category)) return false;
this._unseenItems.delete(category);
this.dispatchUpdateEvent();
this.sendResetCategoryMessage(category);
return true;
}
public isUnseen(category: number, itemId: number): boolean
{
if(!this._unseenItems.get(category)) return false;
const items = this._unseenItems.get(category);
return (items.indexOf(itemId) >= 0);
}
public removeUnseen(category: number, itemId: number): boolean
{
if(!this._unseenItems.get(category)) return false;
const items = this._unseenItems.get(category);
const index = items.indexOf(itemId);
if(index === -1) return false;
items.splice(index, 1);
this.dispatchUpdateEvent();
return true;
}
public getIds(category: number): number[]
{
if(!this._unseenItems) return [];
return this._unseenItems.get(category);
}
public getCount(category: number): number
{
if(!this._unseenItems.get(category)) return 0;
return this._unseenItems.get(category).length;
}
public getFullCount(): number
{
let count = 0;
for(const key of this._unseenItems.keys())
{
count += this.getCount(key);
}
return count;
}
public addItems(category: number, itemIds: number[]): void
{
if(!itemIds) return;
let unseenItems = this._unseenItems.get(category);
if(!unseenItems)
{
unseenItems = [];
this._unseenItems.set(category, unseenItems);
}
for(const itemId of itemIds)
{
if(unseenItems.indexOf(itemId) === -1) unseenItems.push(itemId);
}
this.dispatchUpdateEvent();
}
private dispatchUpdateEvent(): void
{
dispatchUiEvent(new UnseenItemTrackerUpdateEvent(this.getFullCount()));
}
private sendResetCategoryMessage(category: number): void
{
SendMessageHook(new UnseenResetCategoryComposer(category));
}
private sendResetItemsMessage(category: number, itemIds: number[]): void
{
SendMessageHook(new UnseenResetItemsComposer(category, ...itemIds));
}
}

View File

@ -9,7 +9,8 @@ const InventoryContext = createContext<IInventoryContext>({
petState: null,
dispatchPetState: null,
badgeState: null,
dispatchBadgeState: null
dispatchBadgeState: null,
unseenTracker: null
});
export const InventoryContextProvider: FC<InventoryContextProps> = props =>

View File

@ -1,4 +1,5 @@
import { Dispatch, ProviderProps } from 'react';
import { IUnseenItemTracker } from '../common/unseen/IUnseenItemTracker';
import { IInventoryBadgeAction, IInventoryBadgeState } from '../reducers/InventoryBadgeReducer';
import { IInventoryBotAction, IInventoryBotState } from '../reducers/InventoryBotReducer';
import { IInventoryFurnitureAction, IInventoryFurnitureState } from '../reducers/InventoryFurnitureReducer';
@ -14,6 +15,7 @@ export interface IInventoryContext
dispatchPetState: Dispatch<IInventoryPetAction>;
badgeState: IInventoryBadgeState;
dispatchBadgeState: Dispatch<IInventoryBadgeAction>;
unseenTracker: IUnseenItemTracker;
}
export interface InventoryContextProps extends ProviderProps<IInventoryContext>

View File

@ -2,6 +2,7 @@ import { BotData } from 'nitro-renderer';
import { Reducer } from 'react';
import { BotItem } from '../common/BotItem';
import { addSingleBotItem, processBotFragment, removeBotItemById } from '../common/BotUtilities';
import { IUnseenItemTracker } from '../common/unseen/IUnseenItemTracker';
export interface IInventoryBotState
{
@ -19,6 +20,7 @@ export interface IInventoryBotAction
botId?: number;
botData?: BotData;
fragment?: BotData[];
unseenTracker?: IUnseenItemTracker;
}
}
@ -62,7 +64,7 @@ export const InventoryBotReducer: Reducer<IInventoryBotState, IInventoryBotActio
case InventoryBotActions.PROCESS_FRAGMENT: {
const botItems = [ ...state.botItems ];
processBotFragment(botItems, action.payload.fragment);
processBotFragment(botItems, action.payload.fragment, (action.payload.unseenTracker || null));
return { ...state, botItems };
}

View File

@ -6,6 +6,7 @@ import { GroupItem } from '../common/GroupItem';
import { TradeState } from '../common/TradeState';
import { TradeUserData } from '../common/TradeUserData';
import { parseTradeItems } from '../common/TradingUtilities';
import { IUnseenItemTracker } from '../common/unseen/IUnseenItemTracker';
export interface IInventoryFurnitureState
{
@ -33,6 +34,7 @@ export interface IInventoryFurnitureAction
tradeState?: number;
userId?: number;
tradeParser?: TradingListItemParser;
unseenTracker?: IUnseenItemTracker;
}
}
@ -82,7 +84,7 @@ export const InventoryFurnitureReducer: Reducer<IInventoryFurnitureState, IInven
case InventoryFurnitureActions.PROCESS_FRAGMENT: {
const groupItems = [ ...state.groupItems ];
processFurniFragment(groupItems, (action.payload.fragment || null));
processFurniFragment(groupItems, (action.payload.fragment || null), (action.payload.unseenTracker || null));
return { ...state, groupItems };
}
@ -137,7 +139,7 @@ export const InventoryFurnitureReducer: Reducer<IInventoryFurnitureState, IInven
{
const furniture = new FurnitureItem(item);
addFurnitureItem(groupItems, furniture, false);
addFurnitureItem(groupItems, furniture, true);
}
}

View File

@ -2,6 +2,7 @@ import { PetData } from 'nitro-renderer';
import { Reducer } from 'react';
import { PetItem } from '../common/PetItem';
import { addSinglePetItem, processPetFragment, removePetItemById } from '../common/PetUtilities';
import { IUnseenItemTracker } from '../common/unseen/IUnseenItemTracker';
export interface IInventoryPetState
{
@ -19,6 +20,7 @@ export interface IInventoryPetAction
petId?: number;
petData?: PetData;
fragment?: Map<number, PetData>;
unseenTracker?: IUnseenItemTracker;
}
}
@ -62,7 +64,7 @@ export const InventoryPetReducer: Reducer<IInventoryPetState, IInventoryPetActio
case InventoryPetActions.PROCESS_FRAGMENT: {
const petItems = [ ...state.petItems ];
processPetFragment(petItems, (action.payload.fragment || null));
processPetFragment(petItems, (action.payload.fragment || null), (action.payload.unseenTracker || null));
return { ...state, petItems };
}

View File

@ -1,5 +1,5 @@
import { MouseEventType } from 'nitro-renderer';
import { FC, MouseEvent, useCallback, useState } from 'react';
import { FC, MouseEvent, useCallback, useEffect, useState } from 'react';
import { NitroCardGridItemView } from '../../../../../layout/card/grid/item/NitroCardGridItemView';
import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView';
import { attemptBotPlacement } from '../../../common/BotUtilities';
@ -37,8 +37,15 @@ export const InventoryBotItemView: FC<InventoryBotItemViewProps> = props =>
}
}, [ isActive, isMouseDown, botItem, dispatchBotState ]);
useEffect(() =>
{
if(!isActive) return;
botItem.isUnseen = false;
}, [ isActive, botItem ]);
return (
<NitroCardGridItemView itemActive={ isActive } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent }>
<NitroCardGridItemView itemActive={ isActive } itemUnseen={ botItem.isUnseen } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent }>
<AvatarImageView figure={ botItem.botData.figure } direction={ 3 } headOnly={ true } />
</NitroCardGridItemView>
);

View File

@ -17,7 +17,7 @@ import { InventoryFurnitureSearchView } from './search/InventoryFurnitureSearchV
export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
{
const { roomSession = null, roomPreviewer = null } = props;
const { furnitureState = null, dispatchFurnitureState = null } = useInventoryContext();
const { furnitureState = null, dispatchFurnitureState = null, unseenTracker = null } = useInventoryContext();
const { needsFurniUpdate = false, groupItem = null, groupItems = [] } = furnitureState;
const [ filteredGroupItems, setFilteredGroupItems ] = useState<GroupItem[]>(groupItems);

View File

@ -1,5 +1,5 @@
import { MouseEventType } from 'nitro-renderer';
import { FC, MouseEvent, useCallback, useState } from 'react';
import { FC, MouseEvent, useCallback, useEffect, useState } from 'react';
import { NitroCardGridItemView } from '../../../../../layout/card/grid/item/NitroCardGridItemView';
import { attemptItemPlacement } from '../../../common/FurnitureUtilities';
import { useInventoryContext } from '../../../context/InventoryContext';
@ -37,7 +37,14 @@ export const InventoryFurnitureItemView: FC<InventoryFurnitureItemViewProps> = p
}
}, [ isActive, isMouseDown, groupItem, dispatchFurnitureState ]);
useEffect(() =>
{
if(!isActive) return;
groupItem.hasUnseenItems = false;
}, [ isActive, groupItem ]);
const count = groupItem.getUnlockedCount();
return <NitroCardGridItemView className={ !count ? 'opacity-0-5 ' : '' } itemImage={ groupItem.iconUrl } itemCount={ count } itemActive={ isActive } itemUnique={ groupItem.stuffData.isUnique } itemUniqueNumber={ groupItem.stuffData.uniqueNumber } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } />;
return <NitroCardGridItemView className={ !count ? 'opacity-0-5 ' : '' } itemImage={ groupItem.iconUrl } itemCount={ count } itemActive={ isActive } itemUnique={ groupItem.stuffData.isUnique } itemUniqueNumber={ groupItem.stuffData.uniqueNumber } itemUnseen={ groupItem.hasUnseenItems } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } />;
}

View File

@ -1,5 +1,5 @@
import { MouseEventType } from 'nitro-renderer';
import { FC, MouseEvent, useCallback, useState } from 'react';
import { FC, MouseEvent, useCallback, useEffect, useState } from 'react';
import { NitroCardGridItemView } from '../../../../../layout/card/grid/item/NitroCardGridItemView';
import { PetImageView } from '../../../../shared/pet-image/PetImageView';
import { attemptPetPlacement } from '../../../common/PetUtilities';
@ -37,8 +37,15 @@ export const InventoryPetItemView: FC<InventoryPetItemViewProps> = props =>
}
}, [ isActive, isMouseDown, petItem, dispatchPetState ]);
useEffect(() =>
{
if(!isActive) return;
petItem.isUnseen = false;
}, [ isActive, petItem ]);
return (
<NitroCardGridItemView itemActive={ isActive } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent }>
<NitroCardGridItemView itemActive={ isActive } itemUnseen={ petItem.isUnseen } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent }>
<PetImageView figure={ petItem.petData.figureString } direction={ 3 } headOnly={ true } />
</NitroCardGridItemView>
);

View File

@ -6,9 +6,8 @@ import { StartRoomSession } from '../../api/nitro/session/StartRoomSession';
import { useRoomEngineEvent } from '../../hooks/events/nitro/room/room-engine-event';
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
import { RoomView } from '../room/RoomView';
import { RoomHostViewProps } from './RoomHostView.types';
export const RoomHostView: FC<RoomHostViewProps> = props =>
export const RoomHostView: FC<{}> = props =>
{
const [ roomSession, setRoomSession ] = useState<IRoomSession>(null);

View File

@ -1,4 +0,0 @@
export interface RoomHostViewProps
{
}

View File

@ -4,12 +4,14 @@ import { GetRoomEngine } from '../../api';
import { UseMountEffect } from '../../hooks';
import { CreateEventDispatcherHook, useRoomEngineEvent } from '../../hooks/events';
import { useRoomContext } from './context/RoomContext';
import { RoomWidgetUpdateBackgroundColorPreviewEvent } from './events';
import { RoomWidgetUpdateRoomViewEvent } from './events/RoomWidgetUpdateRoomViewEvent';
export const RoomColorView: FC<{}> = props =>
{
const [ roomBackground, setRoomBackground ] = useState<NitroSprite>(null);
const [ roomBackgroundColor, setRoomBackgroundColor ] = useState(0x000000);
const [ roomBackgroundColor, setRoomBackgroundColor ] = useState(0);
const [ originalRoomBackgroundColor, setOriginalRoomBackgroundColor ] = useState(0);
const [ roomFilter, setRoomFilter ] = useState<NitroAdjustmentFilter>(null);
const [ roomFilterColor, setRoomFilterColor ] = useState(-1);
const { roomSession = null, canvasId = -1, eventDispatcher = null } = useRoomContext();
@ -37,20 +39,25 @@ export const RoomColorView: FC<{}> = props =>
return background;
}, [ roomBackground, getRenderingCanvas ]);
const updateRoomBackground = useCallback(() =>
const updateRoomBackground = useCallback((color: number) =>
{
const background = getRoomBackground();
if(!background) return;
background.tint = roomBackgroundColor;
if(color === undefined) color = 0x000000;
background.tint = color;
background.width = Nitro.instance.width;
background.height = Nitro.instance.height;
}, [ roomBackgroundColor, getRoomBackground ]);
}, [ getRoomBackground ]);
const updateRoomBackgroundColor = useCallback((hue: number, saturation: number, lightness: number) =>
const updateRoomBackgroundColor = useCallback((hue: number, saturation: number, lightness: number, original: boolean = false) =>
{
setRoomBackgroundColor(ColorConverter.hslToRGB(((((hue & 0xFF) << 16) + ((saturation & 0xFF) << 8)) + (lightness & 0xFF))));
const newColor = ColorConverter.hslToRGB(((((hue & 0xFF) << 16) + ((saturation & 0xFF) << 8)) + (lightness & 0xFF)));
setRoomBackgroundColor(newColor);
if(original) setOriginalRoomBackgroundColor(newColor);
const background = getRoomBackground();
@ -62,7 +69,7 @@ export const RoomColorView: FC<{}> = props =>
}
else
{
updateRoomBackground();
updateRoomBackground(newColor);
background.visible = true;
}
@ -89,26 +96,27 @@ export const RoomColorView: FC<{}> = props =>
return filter;
}, [ roomFilter, getRenderingCanvas ]);
const updateRoomFilter = useCallback(() =>
const updateRoomFilter = useCallback((color: number) =>
{
const colorMatrix = getRoomFilter();
if(!colorMatrix) return;
const r = ((roomFilterColor >> 16) & 0xFF);
const g = ((roomFilterColor >> 8) & 0xFF);
const b = (roomFilterColor & 0xFF);
const r = ((color >> 16) & 0xFF);
const g = ((color >> 8) & 0xFF);
const b = (color & 0xFF);
colorMatrix.red = (r / 255);
colorMatrix.green = (g / 255);
colorMatrix.blue = (b / 255);
}, [ roomFilterColor, getRoomFilter ]);
}, [ getRoomFilter ]);
const updateRoomFilterColor = useCallback((color: number, brightness: number) =>
{
setRoomFilterColor(ColorConverter.hslToRGB(((ColorConverter.rgbToHSL(color) & 0xFFFF00) + brightness)));
const newColor = ColorConverter.hslToRGB(((ColorConverter.rgbToHSL(color) & 0xFFFF00) + brightness));
updateRoomFilter();
setRoomFilterColor(newColor);
updateRoomFilter(newColor);
}, [ updateRoomFilter ]);
const onRoomEngineEvent = useCallback((event: RoomEngineEvent) =>
@ -120,15 +128,15 @@ export const RoomColorView: FC<{}> = props =>
case RoomObjectHSLColorEnabledEvent.ROOM_BACKGROUND_COLOR: {
const hslColorEvent = (event as RoomObjectHSLColorEnabledEvent);
if(hslColorEvent.enable) updateRoomBackgroundColor(hslColorEvent.hue, hslColorEvent.saturation, hslColorEvent.lightness);
else updateRoomBackgroundColor(0, 0, 0);
if(hslColorEvent.enable) updateRoomBackgroundColor(hslColorEvent.hue, hslColorEvent.saturation, hslColorEvent.lightness, true);
else updateRoomBackgroundColor(0, 0, 0, true);
return;
}
case RoomBackgroundColorEvent.ROOM_COLOR: {
const colorEvent = (event as RoomBackgroundColorEvent);
if(colorEvent.bgOnly) updateRoomFilterColor(0xFF0000, 0xFF);
if(colorEvent.bgOnly) updateRoomFilterColor(0x000000, 0xFF);
else updateRoomFilterColor(colorEvent.color, colorEvent.brightness);
return;
@ -141,11 +149,32 @@ export const RoomColorView: FC<{}> = props =>
const onRoomWidgetUpdateRoomViewEvent = useCallback((event: RoomWidgetUpdateRoomViewEvent) =>
{
updateRoomBackground();
}, [ updateRoomBackground ]);
updateRoomBackground(roomBackgroundColor);
}, [ roomBackgroundColor, updateRoomBackground ]);
CreateEventDispatcherHook(RoomWidgetUpdateRoomViewEvent.SIZE_CHANGED, eventDispatcher, onRoomWidgetUpdateRoomViewEvent);
const onRoomWidgetUpdateBackgroundColorPreviewEvent = useCallback((event: RoomWidgetUpdateBackgroundColorPreviewEvent) =>
{
switch(event.type)
{
case RoomWidgetUpdateBackgroundColorPreviewEvent.PREVIEW: {
updateRoomBackgroundColor(event.hue, event.saturation, event.lightness);
return;
}
case RoomWidgetUpdateBackgroundColorPreviewEvent.CLEAR_PREVIEW: {
const color = originalRoomBackgroundColor;
setRoomBackgroundColor(color);
updateRoomBackground(color);
return;
}
}
}, [ originalRoomBackgroundColor, updateRoomBackgroundColor, updateRoomBackground ]);
CreateEventDispatcherHook(RoomWidgetUpdateBackgroundColorPreviewEvent.PREVIEW, eventDispatcher, onRoomWidgetUpdateBackgroundColorPreviewEvent);
CreateEventDispatcherHook(RoomWidgetUpdateBackgroundColorPreviewEvent.CLEAR_PREVIEW, eventDispatcher, onRoomWidgetUpdateBackgroundColorPreviewEvent);
UseMountEffect(updateRoomBackground);
return null;

View File

@ -0,0 +1,35 @@
import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent';
export class RoomWidgetUpdateBackgroundColorPreviewEvent extends RoomWidgetUpdateEvent
{
public static PREVIEW = 'RWUBCPE_PREVIEW';
public static CLEAR_PREVIEW = 'RWUBCPE_CLEAR_PREVIEW';
private _hue: number;
private _saturation: number;
private _lightness: number;
constructor(type: string, hue: number = 0, saturation: number = 0, lightness: number = 0)
{
super(type);
this._hue = hue;
this._saturation = saturation;
this._lightness = lightness;
}
public get hue(): number
{
return this._hue;
}
public get saturation(): number
{
return this._saturation;
}
public get lightness(): number
{
return this._lightness;
}
}

View File

@ -3,6 +3,7 @@ export * from './RoomWidgetFloodControlEvent';
export * from './RoomWidgetObjectNameEvent';
export * from './RoomWidgetRoomEngineUpdateEvent';
export * from './RoomWidgetRoomObjectUpdateEvent';
export * from './RoomWidgetUpdateBackgroundColorPreviewEvent';
export * from './RoomWidgetUpdateChatEvent';
export * from './RoomWidgetUpdateChatInputContentEvent';
export * from './RoomWidgetUpdateDanceStatusEvent';

View File

@ -1,4 +1,4 @@
import { RoomEngineDimmerStateEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineUseProductEvent, RoomId, RoomObjectCategory, RoomObjectOperationType, RoomSessionChatEvent, RoomSessionDanceEvent, RoomSessionDimmerPresetsEvent, RoomSessionDoorbellEvent, RoomSessionErrorMessageEvent, RoomSessionEvent, RoomSessionFriendRequestEvent, RoomSessionPetInfoUpdateEvent, RoomSessionPresentEvent, RoomSessionUserBadgesEvent, RoomZoomEvent } from 'nitro-renderer';
import { RoomEngineDimmerStateEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineTriggerWidgetEvent, RoomEngineUseProductEvent, RoomId, RoomObjectCategory, RoomObjectOperationType, RoomSessionChatEvent, RoomSessionDanceEvent, RoomSessionDimmerPresetsEvent, RoomSessionDoorbellEvent, RoomSessionErrorMessageEvent, RoomSessionEvent, RoomSessionFriendRequestEvent, RoomSessionPetInfoUpdateEvent, RoomSessionPresentEvent, RoomSessionUserBadgesEvent, RoomZoomEvent } from 'nitro-renderer';
import { FC, useCallback } from 'react';
import { CanManipulateFurniture, GetRoomEngine, IsFurnitureSelectionDisabled, ProcessRoomObjectOperation } from '../../../api';
import { useRoomEngineEvent, useRoomSessionManagerEvent } from '../../../hooks/events';
@ -119,6 +119,8 @@ export const RoomWidgetsView: FC<RoomWidgetViewProps> = props =>
break;
case RoomEngineUseProductEvent.USE_PRODUCT_FROM_INVENTORY:
case RoomEngineUseProductEvent.USE_PRODUCT_FROM_ROOM:
case RoomEngineTriggerWidgetEvent.OPEN_WIDGET:
case RoomEngineTriggerWidgetEvent.CLOSE_WIDGET:
widgetHandler.processEvent(event);
break;
}
@ -145,6 +147,8 @@ export const RoomWidgetsView: FC<RoomWidgetViewProps> = props =>
useRoomEngineEvent(RoomEngineObjectEvent.MOUSE_LEAVE, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineUseProductEvent.USE_PRODUCT_FROM_INVENTORY, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineUseProductEvent.USE_PRODUCT_FROM_ROOM, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineTriggerWidgetEvent.OPEN_WIDGET, onRoomEngineObjectEvent);
useRoomEngineEvent(RoomEngineTriggerWidgetEvent.CLOSE_WIDGET, onRoomEngineObjectEvent);
const onRoomSessionEvent = useCallback((event: RoomSessionEvent) =>
{

View File

@ -1,5 +1,5 @@
import { IRoomCameraWidgetSelectedEffect } from 'nitro-renderer/src/nitro/camera/IRoomCameraWidgetSelectedEffect';
import { ProviderProps } from 'react';
import { IRoomCameraWidgetSelectedEffect } from '../../../../../../../nitro-renderer/src/nitro/camera/IRoomCameraWidgetSelectedEffect';
export interface ICameraWidgetContext
{

View File

@ -1,6 +1,7 @@
import { FC } from 'react';
import { FurnitureBackgroundColorView } from './background-color/FurnitureBackgroundColorView';
import { FurnitureContextMenuView } from './context-menu/FurnitureContextMenuView';
import { FurnitureCustomStackHeightView } from './custom-stack-height/FurnitureCustomStackHeightView';
import { FurnitureDimmerView } from './dimmer/FurnitureDimmerView';
import { FurnitureExchangeCreditView } from './exchange-credit/FurnitureExchangeCreditView';
import { FurnitureFriendFurniView } from './friend-furni/FurnitureFriendFurniView';
@ -17,6 +18,7 @@ export const FurnitureWidgetsView: FC<{}> = props =>
<div className="position-absolute nitro-room-widgets t-0 l-0">
<FurnitureBackgroundColorView />
<FurnitureContextMenuView />
<FurnitureCustomStackHeightView />
<FurnitureDimmerView />
<FurnitureFriendFurniView />
<FurnitureExchangeCreditView />

View File

@ -1,21 +1,29 @@
import { NitroEvent, RoomControllerLevel, RoomEngineObjectEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from 'nitro-renderer';
import { FC, useCallback, useState } from 'react';
import { ApplyTonerComposer, RoomControllerLevel, RoomEngineObjectEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from 'nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import ReactSlider from 'react-slider';
import { GetRoomEngine, GetSessionDataManager } from '../../../../../api';
import { SendMessageHook } from '../../../../../hooks';
import { CreateEventDispatcherHook, useRoomEngineEvent } from '../../../../../hooks/events';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
import { LocalizeText } from '../../../../../utils/LocalizeText';
import { useRoomContext } from '../../../context/RoomContext';
import { RoomWidgetRoomObjectUpdateEvent } from '../../../events';
import { RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateBackgroundColorPreviewEvent } from '../../../events';
export const FurnitureBackgroundColorView: FC<{}> = props =>
{
const [ furniId, setFurniId ] = useState(-1);
const [ objectId, setObjectId ] = useState(-1);
const [ hue, setHue ] = useState(0);
const [ saturation, setSaturation ] = useState(0);
const [ light, setLight ] = useState(0);
const [ lightness, setLightness ] = useState(0);
const { roomSession = null, eventDispatcher = null } = useRoomContext();
const close = useCallback(() =>
{
eventDispatcher.dispatchEvent(new RoomWidgetUpdateBackgroundColorPreviewEvent(RoomWidgetUpdateBackgroundColorPreviewEvent.CLEAR_PREVIEW));
setObjectId(-1);
}, [ eventDispatcher ]);
const canOpenBackgroundToner = useCallback(() =>
{
const isRoomOwner = roomSession.isRoomOwner;
@ -25,49 +33,98 @@ export const FurnitureBackgroundColorView: FC<{}> = props =>
return (isRoomOwner || hasLevel || isGodMode);
}, [ roomSession ]);
const onNitroEvent = useCallback((event: NitroEvent) =>
const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) =>
{
switch(event.type)
{
case RoomEngineTriggerWidgetEvent.REQUEST_BACKGROUND_COLOR: {
if(!canOpenBackgroundToner()) return;
const roomEngineObjectEvent = (event as RoomEngineObjectEvent);
const roomObject = GetRoomEngine().getRoomObject(roomEngineObjectEvent.roomId, roomEngineObjectEvent.objectId, roomEngineObjectEvent.category);
const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category);
const model = roomObject.model;
setFurniId(roomObject.id);
setObjectId(roomObject.instanceId);
setHue(parseInt(model.getValue(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_HUE)));
setSaturation(parseInt(model.getValue(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_SATURATION)));
setLight(parseInt(model.getValue(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_LIGHTNESS)));
setObjectId(roomObject.id);
setHue(parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_HUE)));
setSaturation(parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_SATURATION)));
setLightness(parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_LIGHTNESS)));
return;
}
case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: {
const widgetEvent = (event as RoomWidgetRoomObjectUpdateEvent);
if(objectId !== event.objectId) return;
setObjectId(prevValue =>
{
if(prevValue === widgetEvent.id) return null;
return prevValue;
});
close();
return;
}
}
}, [ canOpenBackgroundToner ]);
}, [ objectId, canOpenBackgroundToner, close ]);
useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_BACKGROUND_COLOR, onNitroEvent);
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onNitroEvent);
useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_BACKGROUND_COLOR, onRoomEngineObjectEvent);
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onRoomEngineObjectEvent);
const processAction = useCallback((name: string) =>
{
switch(name)
{
case 'apply':
SendMessageHook(new ApplyTonerComposer(objectId, hue, saturation, lightness));
break;
case 'toggle':
roomSession.useMultistateItem(objectId);
break;
}
}, [ roomSession, objectId, hue, saturation, lightness ]);
useEffect(() =>
{
if(objectId === -1) return;
eventDispatcher.dispatchEvent(new RoomWidgetUpdateBackgroundColorPreviewEvent(RoomWidgetUpdateBackgroundColorPreviewEvent.PREVIEW, hue, saturation, lightness));
}, [ eventDispatcher, objectId, hue, saturation, lightness ]);
if(objectId === -1) return null;
return (
<NitroCardView simple={ true }>
<NitroCardHeaderView headerText={ LocalizeText('widget.backgroundcolor.title') } onCloseClick={ event => setObjectId(-1) } />
<NitroCardView>
<NitroCardHeaderView headerText={ LocalizeText('widget.backgroundcolor.title') } onCloseClick={ close } />
<NitroCardContentView>
background toner
<div className="form-group">
<label className="fw-bold text-black">{ LocalizeText('widget.backgroundcolor.hue') }</label>
<ReactSlider
className={ 'nitro-slider' }
min={ 0 }
max={ 360 }
value={ hue }
onChange={ event => setHue(event) }
thumbClassName={ 'thumb degree' }
renderThumb={ (props, state) => <div {...props}>{ state.valueNow }</div> } />
</div>
<div className="form-group">
<label className="fw-bold text-black">{ LocalizeText('widget.backgroundcolor.saturation') }</label>
<ReactSlider
className={ 'nitro-slider' }
min={ 0 }
max={ 100 }
value={ saturation }
onChange={ event => setSaturation(event) }
thumbClassName={ 'thumb percent' }
renderThumb={ (props, state) => <div {...props}>{ state.valueNow }</div> } />
</div>
<div className="form-group mb-2">
<label className="fw-bold text-black">{ LocalizeText('widget.backgroundcolor.lightness') }</label>
<ReactSlider
className={ 'nitro-slider' }
min={ 0 }
max={ 100 }
value={ lightness }
onChange={ event => setLightness(event) }
thumbClassName={ 'thumb percent' }
renderThumb={ (props, state) => <div {...props}>{ state.valueNow }</div> } />
</div>
<div className="d-flex form-group justify-content-between align-items-center">
<button type="button" className="btn btn-primary" onClick={ event => processAction('toggle') }>{ LocalizeText('widget.backgroundcolor.button.on') }</button>
<button type="button" className="btn btn-primary" onClick={ event => processAction('apply') }>{ LocalizeText('widget.backgroundcolor.button.apply') }</button>
</div>
</NitroCardContentView>
</NitroCardView>
);

View File

@ -0,0 +1,49 @@
import { RoomEngineObjectEvent } from 'nitro-renderer';
import { FC, useCallback, useState } from 'react';
import { CreateEventDispatcherHook } from '../../../../../hooks/events';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
import { LocalizeText } from '../../../../../utils/LocalizeText';
import { useRoomContext } from '../../../context/RoomContext';
import { RoomWidgetRoomObjectUpdateEvent } from '../../../events';
export const FurnitureCustomStackHeightView: FC<{}> = props =>
{
const [ objectId, setObjectId ] = useState(-1);
const { roomSession = null, eventDispatcher = null } = useRoomContext();
const close = useCallback(() =>
{
setObjectId(-1);
}, []);
const onRoomEngineObjectEvent = useCallback((event: RoomEngineObjectEvent) =>
{
switch(event.type)
{
// case RoomEngineTriggerWidgetEvent.REQUEST_CUSTOM_STACK_HEIGHT: {
// setObjectId(event.objectId);
// return;
// }
case RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED: {
if(objectId !== event.objectId) return;
close();
return;
}
}
}, [ objectId, close ]);
//useRoomEngineEvent(RoomEngineTriggerWidgetEvent.REQUEST_CUSTOM_STACK_HEIGHT, onRoomEngineObjectEvent);
CreateEventDispatcherHook(RoomWidgetRoomObjectUpdateEvent.FURNI_REMOVED, eventDispatcher, onRoomEngineObjectEvent);
if(objectId === -1) return null;
return (
<NitroCardView>
<NitroCardHeaderView headerText={ LocalizeText('widget.backgroundcolor.title') } onCloseClick={ close } />
<NitroCardContentView>
custom stack height
</NitroCardContentView>
</NitroCardView>
);
}

View File

@ -3,14 +3,13 @@ import { CreateEventDispatcherHook } from '../../../../hooks/events/event-dispat
import { useRoomContext } from '../../context/RoomContext';
import { RoomWidgetRoomObjectUpdateEvent, RoomWidgetUpdateEvent, RoomWidgetUpdateInfostandEvent, RoomWidgetUpdateInfostandFurniEvent, RoomWidgetUpdateInfostandPetEvent, RoomWidgetUpdateInfostandRentableBotEvent, RoomWidgetUpdateInfostandUserEvent } from '../../events';
import { RoomWidgetRoomObjectMessage } from '../../messages';
import { InfoStandWidgetViewProps } from './InfoStandWidgetView.types';
import { InfoStandWidgetBotView } from './views/bot/InfoStandWidgetBotView';
import { InfoStandWidgetFurniView } from './views/furni/InfoStandWidgetFurniView';
import { InfoStandWidgetPetView } from './views/pet/InfoStandWidgetPetView';
import { InfoStandWidgetRentableBotView } from './views/rentable-bot/InfoStandWidgetRentableBotView';
import { InfoStandWidgetUserView } from './views/user/InfoStandWidgetUserView';
export const InfoStandWidgetView: FC<InfoStandWidgetViewProps> = props =>
export const InfoStandWidgetView: FC<{}> = props =>
{
const { eventDispatcher = null, widgetHandler = null } = useRoomContext();
const [ infoStandEvent, setInfoStandEvent ] = useState<RoomWidgetUpdateInfostandEvent>(null);

View File

@ -1,6 +0,0 @@
import { RoomWidgetProps } from '../RoomWidgets.types';
export interface InfoStandWidgetViewProps extends RoomWidgetProps
{
}

View File

@ -1,5 +1,4 @@
import { IGetImageListener, ImageResult, TextureUtils, Vector3d } from 'nitro-renderer';
import { RenderTexture } from 'pixi.js';
import { FC, useCallback, useEffect, useState } from 'react';
import { GetRoomEngine } from '../../../api';
import { ProductTypeEnum } from '../../catalog/common/ProductTypeEnum';
@ -17,7 +16,7 @@ export const FurniImageView: FC<FurniImageViewProps> = props =>
const furniType = type.toLocaleLowerCase();
const listener: IGetImageListener = {
imageReady: (id: number, texture: RenderTexture, image: HTMLImageElement) =>
imageReady: (id, texture, image) =>
{
if(!image && texture)
{

View File

@ -1,4 +1,4 @@
import { ColorConverter, IRoomRenderingCanvas, Nitro } from 'nitro-renderer';
import { ColorConverter, IRoomRenderingCanvas, Nitro, TextureUtils } from 'nitro-renderer';
import { createRef, FC, useCallback, useEffect, useState } from 'react';
import { RoomPreviewerViewProps } from './RoomPreviewerView.types';
@ -18,7 +18,7 @@ export const RoomPreviewerView: FC<RoomPreviewerViewProps> = props =>
if(!renderingCanvas.canvasUpdated) return;
elementRef.current.style.backgroundImage = `url(${ Nitro.instance.renderer.extract.base64(renderingCanvas.master) })`;
elementRef.current.style.backgroundImage = `url(${ TextureUtils.generateImageUrl(renderingCanvas.master) })`;
}, [ roomPreviewer, renderingCanvas, elementRef ]);
const setupPreviewer = useCallback(() =>

View File

@ -13,7 +13,7 @@
box-shadow: inset 0px 5px lighten(rgba($dark,.6),2.5), inset 0 -4px darken(rgba($dark,.6),4);
#toolbar-chat-input-container {
margin-left: 25px;
margin: 0 10px;
}
.navigation-items {
@ -31,6 +31,7 @@
cursor: pointer;
width: 50px;
margin: 0 1px;
position: relative;
.toolbar-avatar {
height: 50px;
@ -47,6 +48,7 @@
.icon,
.toolbar-avatar {
position: relative;
transition: transform .2s ease-out;
&:hover, &.active {
@ -119,6 +121,7 @@
max-width: 120px;
max-height: 150px;
z-index: 500;
filter: drop-shadow(2px 1px 0 rgba($white, 1)) drop-shadow(-2px 1px 0 rgba($white, 1)) drop-shadow(0 -2px 0 rgba($white, 1));
}
@import './me/ToolbarMeView';

View File

@ -1,8 +1,10 @@
import { Dispose, DropBounce, EaseOut, JumpBy, Motions, NitroToolbarAnimateIconEvent, Queue, Wait } from 'nitro-renderer';
import { UserInfoEvent } from 'nitro-renderer/src/nitro/communication/messages/incoming/user/data/UserInfoEvent';
import { UserInfoDataParser } from 'nitro-renderer/src/nitro/communication/messages/parser/user/data/UserInfoDataParser';
import { FC, useCallback, useState } from 'react';
import { AvatarEditorEvent, CatalogEvent, FriendListEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent } from '../../events';
import { dispatchUiEvent } from '../../hooks/events/ui/ui-event';
import { AvatarEditorEvent, CatalogEvent, FriendListEvent, InventoryEvent, NavigatorEvent, RoomWidgetCameraEvent, UnseenItemTrackerUpdateEvent } from '../../events';
import { useRoomEngineEvent } from '../../hooks';
import { dispatchUiEvent, useUiEvent } from '../../hooks/events/ui/ui-event';
import { CreateMessageHook } from '../../hooks/messages/message-event';
import { TransitionAnimation } from '../../layout/transitions/TransitionAnimation';
import { TransitionAnimationTypes } from '../../layout/transitions/TransitionAnimation.types';
@ -16,8 +18,8 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null);
const [ isMeExpanded, setMeExpanded ] = useState(false);
const [ unseenInventoryCount, setUnseenInventoryCount ] = useState(0);
const unseenInventoryCount = 0;
const unseenFriendListCount = 0;
const unseenAchievementsCount = 0;
@ -28,6 +30,56 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
setUserInfo(parser.userInfo);
}, []);
CreateMessageHook(UserInfoEvent, onUserInfoEvent);
const onUnseenItemTrackerUpdateEvent = useCallback((event: UnseenItemTrackerUpdateEvent) =>
{
setUnseenInventoryCount(event.count);
}, []);
useUiEvent(UnseenItemTrackerUpdateEvent.UPDATE_COUNT, onUnseenItemTrackerUpdateEvent);
const animationIconToToolbar = useCallback((iconName: string, image: HTMLImageElement, x: number, y: number) =>
{
const target = (document.body.getElementsByClassName(iconName)[0] as HTMLElement);
if(!target) return;
image.className = 'toolbar-icon-animation';
image.style.visibility = 'visible';
image.style.left = (x + 'px');
image.style.top = (y + 'px');
document.body.append(image);
const targetBounds = target.getBoundingClientRect();
const imageBounds = image.getBoundingClientRect();
const left = (imageBounds.x - targetBounds.x);
const top = (imageBounds.y - targetBounds.y);
const squared = Math.sqrt(((left * left) + (top * top)));
const wait = (500 - Math.abs(((((1 / squared) * 100) * 500) * 0.5)));
const height = 20;
const motionName = (`ToolbarBouncing[${ iconName }]`);
if(!Motions.getMotionByTag(motionName))
{
Motions.runMotion(new Queue(new Wait((wait + 8)), new DropBounce(target, 400, 12))).tag = motionName;
}
const motion = new Queue(new EaseOut(new JumpBy(image, wait, ((targetBounds.x - imageBounds.x) + height), (targetBounds.y - imageBounds.y), 100, 1), 1), new Dispose(image));
Motions.runMotion(motion);
}, []);
const onNitroToolbarAnimateIconEvent = useCallback((event: NitroToolbarAnimateIconEvent) =>
{
animationIconToToolbar('icon-inventory', event.image, event.x, event.y);
}, [ animationIconToToolbar ]);
useRoomEngineEvent(NitroToolbarAnimateIconEvent.ANIMATE_ICON, onNitroToolbarAnimateIconEvent);
const handleToolbarItemClick = useCallback((item: string) =>
{
switch(item)
@ -54,8 +106,6 @@ export const ToolbarView: FC<ToolbarViewProps> = props =>
}
}, []);
CreateMessageHook(UserInfoEvent, onUserInfoEvent);
return (
<div className="nitro-toolbar-container">
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isMeExpanded } timeout={ 300 }>