diff --git a/src/hooks/inventory/useInventoryFurni.reducers.ts b/src/hooks/inventory/useInventoryFurni.reducers.ts new file mode 100644 index 0000000..af0dfb9 --- /dev/null +++ b/src/hooks/inventory/useInventoryFurni.reducers.ts @@ -0,0 +1,227 @@ +import { CreateLinkEvent, FurnitureListAddOrUpdateEvent, FurnitureListEvent, FurnitureListItemParser, FurnitureListRemovedEvent } from '@nitrots/nitro-renderer'; +import { CloneObject, FurnitureItem, GroupItem, UnseenItemCategory, addFurnitureItem, attemptItemPlacement, cancelRoomObjectPlacement, getAllItemIds, getPlacingItemId, mergeFurniFragments } from '../../api'; + +/** + * Pure reducers for furniture inventory state. Each takes the current + * GroupItem[] state plus the inbound event plus a context object carrying + * the cross-cutting helpers (unseen tracker, ui-event dispatcher). + * + * Side effects (CreateLinkEvent, attemptItemPlacement, dispatchAdded, + * cancelRoomObjectPlacement) are intentionally kept here to preserve the + * exact behavior of the original useInventoryFurni — they fire when the + * state transition demands them. The original code embedded them inside + * setGroupItems(prev => ...) and we mirror that. + */ + +export interface FurniReducerContext { + isUnseen: (category: number, id: number) => boolean; + dispatchAdded: (id: number, type: number, category: number) => void; + fragments: { current: Map[] | null }; +} + +export const applyFurnitureListAddOrUpdate = ( + state: GroupItem[], + event: FurnitureListAddOrUpdateEvent, + ctx: FurniReducerContext +): GroupItem[] => +{ + const parser = event.getParser(); + const newValue = [ ...state ]; + + for(const item of parser.items) + { + let i = 0; + let groupItem: GroupItem = null; + + while(i < newValue.length) + { + const group = newValue[i]; + + let j = 0; + + while(j < group.items.length) + { + const furniture = group.items[j]; + + if(furniture.id === item.itemId) + { + furniture.update(item); + + const newFurniture = [ ...group.items ]; + + newFurniture[j] = furniture; + + group.items = newFurniture; + + groupItem = group; + + break; + } + + j++; + } + + if(groupItem) break; + + i++; + } + + if(groupItem) + { + groupItem.hasUnseenItems = true; + + newValue[i] = CloneObject(groupItem); + } + else + { + const furniture = new FurnitureItem(item); + + addFurnitureItem(newValue, furniture, ctx.isUnseen(UnseenItemCategory.FURNI, item.itemId)); + + ctx.dispatchAdded(furniture.id, furniture.type, furniture.category); + } + } + + return newValue; +}; + +export const applyFurnitureList = ( + state: GroupItem[], + event: FurnitureListEvent, + ctx: FurniReducerContext +): GroupItem[] => +{ + const parser = event.getParser(); + + if(!ctx.fragments.current) ctx.fragments.current = new Array(parser.totalFragments); + + const fragment = mergeFurniFragments(parser.fragment, parser.totalFragments, parser.fragmentNumber, ctx.fragments.current); + + if(!fragment) return state; + + const newValue = [ ...state ]; + const existingIds = getAllItemIds(newValue); + + for(const existingId of existingIds) + { + if(fragment.get(existingId)) continue; + + let index = 0; + + while(index < newValue.length) + { + const group = newValue[index]; + const item = group.remove(existingId); + + if(!item) + { + index++; + + continue; + } + + if(getPlacingItemId() === item.ref) + { + cancelRoomObjectPlacement(); + + if(!attemptItemPlacement(group)) + { + CreateLinkEvent('inventory/show'); + } + } + + if(group.getTotalCount() <= 0) + { + newValue.splice(index, 1); + + group.dispose(); + } + + break; + } + } + + for(const itemId of fragment.keys()) + { + if(existingIds.indexOf(itemId) >= 0) continue; + + const parserItem = fragment.get(itemId); + + if(!parserItem) continue; + + const item = new FurnitureItem(parserItem); + + addFurnitureItem(newValue, item, ctx.isUnseen(UnseenItemCategory.FURNI, itemId)); + + ctx.dispatchAdded(item.id, item.type, item.category); + } + + ctx.fragments.current = null; + + return newValue; +}; + +export const applyFurnitureListRemoved = ( + state: GroupItem[], + event: FurnitureListRemovedEvent +): GroupItem[] => +{ + const parser = event.getParser(); + const newValue = [ ...state ]; + + let index = 0; + + while(index < newValue.length) + { + const group = newValue[index]; + const item = group.remove(parser.itemId); + + if(!item) + { + index++; + + continue; + } + + if(getPlacingItemId() === item.ref) + { + cancelRoomObjectPlacement(); + + if(!attemptItemPlacement(group)) CreateLinkEvent('inventory/show'); + } + + if(group.getTotalCount() <= 0) + { + newValue.splice(index, 1); + + group.dispose(); + } + + break; + } + + return newValue; +}; + +export const clearUnseenFlags = (state: GroupItem[]): GroupItem[] => +{ + const newValue = [ ...state ]; + + for(const newGroup of newValue) newGroup.hasUnseenItems = false; + + return newValue; +}; + +export const refreshGroupItemsLocalization = (state: GroupItem[]): GroupItem[] => +{ + if(!state?.length) return state; + + return state.map(groupItem => + { + const nextGroupItem = groupItem.clone(); + + nextGroupItem.refreshLocalization(); + + return nextGroupItem; + }); +}; diff --git a/src/hooks/inventory/useInventoryFurni.ts b/src/hooks/inventory/useInventoryFurni.ts index 520b9e4..e9aaa82 100644 --- a/src/hooks/inventory/useInventoryFurni.ts +++ b/src/hooks/inventory/useInventoryFurni.ts @@ -1,19 +1,19 @@ -import { CreateLinkEvent, FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListItemParser, FurnitureListRemovedEvent, FurniturePostItPlacedEvent } from '@nitrots/nitro-renderer'; -import { useEffect, useState } from 'react'; +import { FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListItemParser, FurnitureListRemovedEvent } from '@nitrots/nitro-renderer'; +import { useEffect, useRef, useState } from 'react'; import { useBetween } from 'use-between'; -import { CloneObject, DispatchUiEvent, FurnitureItem, GroupItem, SendMessageComposer, UnseenItemCategory, addFurnitureItem, attemptItemPlacement, cancelRoomObjectPlacement, getAllItemIds, getPlacingItemId, mergeFurniFragments } from '../../api'; +import { DispatchUiEvent, GroupItem, SendMessageComposer, UnseenItemCategory } from '../../api'; import { InventoryFurniAddedEvent } from '../../events'; import { useMessageEvent } from '../events'; import { useSharedVisibility } from '../useSharedVisibility'; import { useInventoryUnseenTracker } from './useInventoryUnseenTracker'; - -let furniMsgFragments: Map[] = null; +import { applyFurnitureList, applyFurnitureListAddOrUpdate, applyFurnitureListRemoved, clearUnseenFlags, FurniReducerContext, refreshGroupItemsLocalization } from './useInventoryFurni.reducers'; const useInventoryFurniState = () => { const [ needsUpdate, setNeedsUpdate ] = useState(true); const [ groupItems, setGroupItems ] = useState([]); const [ selectedItem, setSelectedItem ] = useState(null); + const fragmentsRef = useRef[] | null>(null); const { isVisible = false, activate = null, deactivate = null } = useSharedVisibility(); const { isUnseen = null, resetCategory = null } = useInventoryUnseenTracker(); @@ -52,199 +52,30 @@ const useInventoryFurniState = () => return null; }; + const buildContext = (): FurniReducerContext => ({ + isUnseen, + dispatchAdded: (id, type, category) => DispatchUiEvent(new InventoryFurniAddedEvent(id, type, category)), + fragments: fragmentsRef + }); + useMessageEvent(FurnitureListAddOrUpdateEvent, event => { - const parser = event.getParser(); - - setGroupItems(prevValue => - { - const newValue = [ ...prevValue ]; - - for(const item of parser.items) - { - let i = 0; - let groupItem: GroupItem = null; - - while(i < newValue.length) - { - const group = newValue[i]; - - let j = 0; - - while(j < group.items.length) - { - const furniture = group.items[j]; - - if(furniture.id === item.itemId) - { - furniture.update(item); - - const newFurniture = [ ...group.items ]; - - newFurniture[j] = furniture; - - group.items = newFurniture; - - groupItem = group; - - break; - } - - j++; - } - - if(groupItem) break; - - i++; - } - - if(groupItem) - { - groupItem.hasUnseenItems = true; - - newValue[i] = CloneObject(groupItem); - } - else - { - const furniture = new FurnitureItem(item); - - addFurnitureItem(newValue, furniture, isUnseen(UnseenItemCategory.FURNI, item.itemId)); - - DispatchUiEvent(new InventoryFurniAddedEvent(furniture.id, furniture.type, furniture.category)); - } - } - - return newValue; - }); + setGroupItems(prev => applyFurnitureListAddOrUpdate(prev, event, buildContext())); }); useMessageEvent(FurnitureListEvent, event => { - const parser = event.getParser(); - - if(!furniMsgFragments) furniMsgFragments = new Array(parser.totalFragments); - - const fragment = mergeFurniFragments(parser.fragment, parser.totalFragments, parser.fragmentNumber, furniMsgFragments); - - if(!fragment) return; - - setGroupItems(prevValue => - { - const newValue = [ ...prevValue ]; - const existingIds = getAllItemIds(newValue); - - for(const existingId of existingIds) - { - if(fragment.get(existingId)) continue; - - let index = 0; - - while(index < newValue.length) - { - const group = newValue[index]; - const item = group.remove(existingId); - - if(!item) - { - index++; - - continue; - } - - if(getPlacingItemId() === item.ref) - { - cancelRoomObjectPlacement(); - - if(!attemptItemPlacement(group)) - { - CreateLinkEvent('inventory/show'); - } - } - - if(group.getTotalCount() <= 0) - { - newValue.splice(index, 1); - - group.dispose(); - } - - break; - } - } - - for(const itemId of fragment.keys()) - { - if(existingIds.indexOf(itemId) >= 0) continue; - - const parser = fragment.get(itemId); - - if(!parser) continue; - - const item = new FurnitureItem(parser); - - addFurnitureItem(newValue, item, isUnseen(UnseenItemCategory.FURNI, itemId)); - - DispatchUiEvent(new InventoryFurniAddedEvent(item.id, item.type, item.category)); - - } - - return newValue; - }); - - furniMsgFragments = null; + setGroupItems(prev => applyFurnitureList(prev, event, buildContext())); }); - useMessageEvent(FurnitureListInvalidateEvent, event => + useMessageEvent(FurnitureListInvalidateEvent, () => { setNeedsUpdate(true); }); useMessageEvent(FurnitureListRemovedEvent, event => { - const parser = event.getParser(); - - setGroupItems(prevValue => - { - const newValue = [ ...prevValue ]; - - let index = 0; - - while(index < newValue.length) - { - const group = newValue[index]; - const item = group.remove(parser.itemId); - - if(!item) - { - index++; - - continue; - } - - if(getPlacingItemId() === item.ref) - { - cancelRoomObjectPlacement(); - - if(!attemptItemPlacement(group)) CreateLinkEvent('inventory/show'); - } - - if(group.getTotalCount() <= 0) - { - newValue.splice(index, 1); - - group.dispose(); - } - - break; - } - - return newValue; - }); - }); - - useMessageEvent(FurniturePostItPlacedEvent, event => - { - + setGroupItems(prev => applyFurnitureListRemoved(prev, event)); }); useEffect(() => @@ -271,14 +102,7 @@ const useInventoryFurniState = () => { if(resetCategory(UnseenItemCategory.FURNI)) { - setGroupItems(prevValue => - { - const newValue = [ ...prevValue ]; - - for(const newGroup of newValue) newGroup.hasUnseenItems = false; - - return newValue; - }); + setGroupItems(prev => clearUnseenFlags(prev)); } }; }, [ isVisible, resetCategory ]); @@ -296,19 +120,7 @@ const useInventoryFurniState = () => { const refreshFurnitureLocalization = () => { - setGroupItems(prevValue => - { - if(!prevValue?.length) return prevValue; - - return prevValue.map(groupItem => - { - const nextGroupItem = groupItem.clone(); - - nextGroupItem.refreshLocalization(); - - return nextGroupItem; - }); - }); + setGroupItems(prev => refreshGroupItemsLocalization(prev)); setSelectedItem(prevValue => {