diff --git a/src/views/inventory/reducers/InventoryPetReducer.tsx b/src/views/inventory/reducers/InventoryPetReducer.tsx new file mode 100644 index 00000000..93269c0c --- /dev/null +++ b/src/views/inventory/reducers/InventoryPetReducer.tsx @@ -0,0 +1,86 @@ +import { PetData } from 'nitro-renderer'; +import { Reducer } from 'react'; +import { PetItem } from '../utils/PetItem'; +import { addSinglePetItem, processPetFragment, removePetItemById } from '../utils/PetUtilities'; + +export interface IInventoryPetState +{ + needsPetUpdate: boolean; + petItem: PetItem; + petItems: PetItem[]; +} + +export interface IInventoryPetAction +{ + type: string; + payload: { + flag?: boolean; + petItem?: PetItem; + petId?: number; + petData?: PetData; + fragment?: Map; + } +} + +export class InventoryPetActions +{ + public static SET_NEEDS_UPDATE: string = 'IPA_SET_NEEDS_UPDATE'; + public static SET_PET_ITEM: string = 'IPA_SET_PET_ITEM'; + public static PROCESS_FRAGMENT: string = 'IPA_PROCESS_FRAGMENT'; + public static ADD_PET: string = 'IPA_ADD_PET'; + public static REMOVE_PET: string = 'IPA_REMOVE_PET'; +} + +export const initialInventoryPet: IInventoryPetState = { + needsPetUpdate: true, + petItem: null, + petItems: [] +} + +export const inventoryPetReducer: Reducer = (state, action) => +{ + switch(action.type) + { + case InventoryPetActions.SET_NEEDS_UPDATE: + return { ...state, needsPetUpdate: (action.payload.flag || false) }; + case InventoryPetActions.SET_PET_ITEM: { + let petItem = (action.payload.petItem || state.petItem || null); + + let index = 0; + + if(petItem) + { + const foundIndex = state.petItems.indexOf(petItem); + + if(foundIndex > -1) index = foundIndex; + } + + petItem = (state.petItems[index] || null); + + return { ...state, petItem }; + } + case InventoryPetActions.PROCESS_FRAGMENT: { + const petItems = [ ...state.petItems ]; + + processPetFragment(petItems, (action.payload.fragment || null)); + + return { ...state, petItems }; + } + case InventoryPetActions.ADD_PET: { + const petItems = [ ...state.petItems ]; + + addSinglePetItem(action.payload.petData, petItems, true); + + return { ...state, petItems }; + } + case InventoryPetActions.REMOVE_PET: { + const petItems = [ ...state.petItems ]; + + removePetItemById(action.payload.petId, petItems); + + return { ...state, petItems }; + } + default: + return state; + } +} diff --git a/src/views/inventory/utils/PetItem.ts b/src/views/inventory/utils/PetItem.ts new file mode 100644 index 00000000..25d5b95f --- /dev/null +++ b/src/views/inventory/utils/PetItem.ts @@ -0,0 +1,45 @@ +import { PetData } from 'nitro-renderer'; + +export class PetItem +{ + private _petData: PetData; + private _selected: boolean; + private _isUnseen: boolean; + + constructor(petData: PetData) + { + this._petData = petData; + this._selected = false; + this._isUnseen = false; + } + + public get id(): number + { + return this._petData.id; + } + + public get petData(): PetData + { + return this._petData; + } + + public get selected(): boolean + { + return this._selected; + } + + public set selected(flag: boolean) + { + this._selected = flag; + } + + public get isUnseen(): boolean + { + return this._isUnseen; + } + + public set isUnseen(flag: boolean) + { + this._isUnseen = flag; + } +} diff --git a/src/views/inventory/utils/PetUtilities.ts b/src/views/inventory/utils/PetUtilities.ts new file mode 100644 index 00000000..5be3d367 --- /dev/null +++ b/src/views/inventory/utils/PetUtilities.ts @@ -0,0 +1,146 @@ +import { PetData, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType } from 'nitro-renderer'; +import { GetRoomEngine, GetRoomSessionManager } from '../../../api'; +import { InventoryEvent } from '../../../events'; +import { dispatchUiEvent } from '../../../hooks/events/ui/ui-event'; +import { getPlacingItemId, setObjectMoverRequested, setPlacingItemId } from './InventoryUtilities'; +import { PetItem } from './PetItem'; + +export function cancelRoomObjectPlacement(): void +{ + if(getPlacingItemId() === -1) return; + + GetRoomEngine().cancelRoomObjectPlacement(); + + setPlacingItemId(-1); + setObjectMoverRequested(false); +} + +export function attemptPetPlacement(petItem: PetItem, flag: boolean = false): boolean +{ + const petData = petItem.petData; + + if(!petData) return false; + + const session = GetRoomSessionManager().getSession(1); + + if(!session) return false; + + if(!session.isRoomOwner) + { + if(!session.allowPets) return false; + } + + dispatchUiEvent(new InventoryEvent(InventoryEvent.HIDE_INVENTORY)); + + if(GetRoomEngine().processRoomObjectPlacement(RoomObjectPlacementSource.INVENTORY, -(petData.id), RoomObjectCategory.UNIT, RoomObjectType.PET, petData.figureData.figuredata)) + { + setPlacingItemId(petData.id); + setObjectMoverRequested(true); + } + + return true; +} + +export function mergePetFragments(fragment: Map, totalFragments: number, fragmentNumber: number, fragments: Map[]) +{ + if(totalFragments === 1) return fragment; + + fragments[fragmentNumber] = fragment; + + for(const frag of fragments) + { + if(!frag) return null; + } + + const merged: Map = new Map(); + + for(const frag of fragments) + { + for(const [ key, value ] of frag) merged.set(key, value); + + frag.clear(); + } + + fragments = null; + + return merged; +} + +function getAllItemIds(petItems: PetItem[]): number[] +{ + const itemIds: number[] = []; + + for(const petItem of petItems) itemIds.push(petItem.id); + + return itemIds; +} + +export function processPetFragment(set: PetItem[], fragment: Map): PetItem[] +{ + const existingIds = getAllItemIds(set); + const addedIds: number[] = []; + const removedIds: number[] = []; + + for(const key of fragment.keys()) (existingIds.indexOf(key) === -1) && addedIds.push(key); + + for(const itemId of existingIds) (!fragment.get(itemId)) && removedIds.push(itemId); + + const emptyExistingSet = (existingIds.length === 0); + + for(const id of removedIds) removePetItemById(id, set); + + for(const id of addedIds) + { + const parser = fragment.get(id); + + if(!parser) continue; + + addSinglePetItem(parser, set, true); + } + + return set; +} + +export function removePetItemById(id: number, set: PetItem[]): PetItem +{ + let index = 0; + + while(index < set.length) + { + const petItem = set[index]; + + if(petItem && (petItem.id === id)) + { + if(getPlacingItemId() === petItem.id) + { + cancelRoomObjectPlacement(); + + setTimeout(() => dispatchUiEvent(new InventoryEvent(InventoryEvent.SHOW_INVENTORY)), 1); + } + + set.splice(index, 1); + + return petItem; + } + + index++; + } + + return null; +} + +export function addSinglePetItem(petData: PetData, set: PetItem[], unseen: boolean = true): PetItem +{ + const petItem = new PetItem(petData); + + if(unseen) + { + set.unshift(petItem); + } + else + { + set.push(petItem); + } + + return petItem; +} diff --git a/src/views/inventory/views/pet/InventoryPetView.scss b/src/views/inventory/views/pet/InventoryPetView.scss new file mode 100644 index 00000000..7afd161a --- /dev/null +++ b/src/views/inventory/views/pet/InventoryPetView.scss @@ -0,0 +1,2 @@ +@import './item/InventoryPetItemView'; +@import './results/InventoryPetResultsView'; diff --git a/src/views/inventory/views/pet/InventoryPetView.tsx b/src/views/inventory/views/pet/InventoryPetView.tsx new file mode 100644 index 00000000..8e5fa8af --- /dev/null +++ b/src/views/inventory/views/pet/InventoryPetView.tsx @@ -0,0 +1,97 @@ +import { RequestPetsComposer, RoomObjectVariable } from 'nitro-renderer'; +import { FC, useEffect } from 'react'; +import { GetRoomEngine } from '../../../../api'; +import { SendMessageHook } from '../../../../hooks/messages/message-event'; +import { LocalizeText } from '../../../../utils/LocalizeText'; +import { RoomPreviewerView } from '../../../room-previewer/RoomPreviewerView'; +import { useInventoryContext } from '../../context/InventoryContext'; +import { InventoryPetActions } from '../../reducers/InventoryPetReducer'; +import { attemptPetPlacement } from '../../utils/PetUtilities'; +import { InventoryPetViewProps } from './InventoryPetView.types'; +import { InventoryPetResultsView } from './results/InventoryPetResultsView'; + +export const InventoryPetView: FC = props => +{ + const { roomSession = null, roomPreviewer = null } = props; + const { petState = null, dispatchPetState = null } = useInventoryContext(); + const { needsPetUpdate = false, petItem = null, petItems = [] } = petState; + + useEffect(() => + { + if(needsPetUpdate) + { + dispatchPetState({ + type: InventoryPetActions.SET_NEEDS_UPDATE, + payload: { + flag: false + } + }); + + SendMessageHook(new RequestPetsComposer()); + } + else + { + dispatchPetState({ + type: InventoryPetActions.SET_PET_ITEM, + payload: { + petItem: null + } + }); + } + + }, [ needsPetUpdate, petItems, dispatchPetState ]); + + useEffect(() => + { + if(!petItem || !roomPreviewer) return; + + const petData = petItem.petData; + + const roomEngine = GetRoomEngine(); + + let wallType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE); + let floorType = roomEngine.getRoomInstanceVariable(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE); + let landscapeType = roomEngine.getRoomInstanceVariable(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'; + + roomPreviewer.reset(false); + roomPreviewer.updateRoomWallsAndFloorVisibility(true, true); + roomPreviewer.updateObjectRoom(floorType, wallType, landscapeType); + roomPreviewer.addPetIntoRoom(petData.figureData.figuredata); + }, [ roomPreviewer, petItem ]); + + if(!petItems || !petItems.length) + { + return ( +
+
+
+
+
+
+ { LocalizeText('inventory.empty.pets.title') } +
+
{ LocalizeText('inventory.empty.pets.desc') }
+
+
+ ); + } + + return ( +
+
+ +
+
+ + { petItem &&
+

{ petItem.petData.name }

+ { !!roomSession && } +
} +
+
+ ); +} diff --git a/src/views/inventory/views/pet/InventoryPetView.types.ts b/src/views/inventory/views/pet/InventoryPetView.types.ts new file mode 100644 index 00000000..316b2850 --- /dev/null +++ b/src/views/inventory/views/pet/InventoryPetView.types.ts @@ -0,0 +1,7 @@ +import { IRoomSession, RoomPreviewer } from 'nitro-renderer'; + +export interface InventoryPetViewProps +{ + roomSession: IRoomSession; + roomPreviewer: RoomPreviewer; +} diff --git a/src/views/inventory/views/pet/item/InventoryPetItemView.scss b/src/views/inventory/views/pet/item/InventoryPetItemView.scss new file mode 100644 index 00000000..43c9dc93 --- /dev/null +++ b/src/views/inventory/views/pet/item/InventoryPetItemView.scss @@ -0,0 +1,17 @@ +.inventory-pet-item-container { + height: 48px; + max-height: 48px; + + .inventory-pet-item { + width: 100%; + height: 100%; + border-color: $muted !important; + background-color: #CDD3D9; + overflow: hidden; + + &.active { + border-color: $white !important; + background-color: #ECECEC; + } + } +} diff --git a/src/views/inventory/views/pet/item/InventoryPetItemView.tsx b/src/views/inventory/views/pet/item/InventoryPetItemView.tsx new file mode 100644 index 00000000..ddb79211 --- /dev/null +++ b/src/views/inventory/views/pet/item/InventoryPetItemView.tsx @@ -0,0 +1,46 @@ +import { MouseEventType } from 'nitro-renderer'; +import { FC, MouseEvent, useCallback, useState } from 'react'; +import { PetImageView } from '../../../../pet-image/PetImageView'; +import { useInventoryContext } from '../../../context/InventoryContext'; +import { InventoryPetActions } from '../../../reducers/InventoryPetReducer'; +import { attemptPetPlacement } from '../../../utils/PetUtilities'; +import { InventoryPetItemViewProps } from './InventoryPetItemView.types'; + +export const InventoryPetItemView: FC = props => +{ + const { petItem } = props; + const { petState = null, dispatchPetState = null } = useInventoryContext(); + const [ isMouseDown, setMouseDown ] = useState(false); + const isActive = (petState.petItem === petItem); + + const onMouseEvent = useCallback((event: MouseEvent) => + { + switch(event.type) + { + case MouseEventType.MOUSE_DOWN: + dispatchPetState({ + type: InventoryPetActions.SET_PET_ITEM, + payload: { petItem } + }); + + setMouseDown(true); + return; + case MouseEventType.MOUSE_UP: + setMouseDown(false); + return; + case MouseEventType.ROLL_OUT: + if(!isMouseDown || !isActive) return; + + attemptPetPlacement(petItem); + return; + } + }, [ isActive, isMouseDown, petItem, dispatchPetState ]); + + return ( +
+
+ +
+
+ ); +} diff --git a/src/views/inventory/views/pet/item/InventoryPetItemView.types.ts b/src/views/inventory/views/pet/item/InventoryPetItemView.types.ts new file mode 100644 index 00000000..7e1d2e3b --- /dev/null +++ b/src/views/inventory/views/pet/item/InventoryPetItemView.types.ts @@ -0,0 +1,6 @@ +import { PetItem } from '../../../utils/PetItem'; + +export interface InventoryPetItemViewProps +{ + petItem: PetItem; +} diff --git a/src/views/inventory/views/pet/results/InventoryPetResultsView.scss b/src/views/inventory/views/pet/results/InventoryPetResultsView.scss new file mode 100644 index 00000000..30675765 --- /dev/null +++ b/src/views/inventory/views/pet/results/InventoryPetResultsView.scss @@ -0,0 +1,5 @@ +.pet-item-container { + height: 226px; + max-height: 226px; + overflow-y: auto; +} diff --git a/src/views/inventory/views/pet/results/InventoryPetResultsView.tsx b/src/views/inventory/views/pet/results/InventoryPetResultsView.tsx new file mode 100644 index 00000000..48df1f29 --- /dev/null +++ b/src/views/inventory/views/pet/results/InventoryPetResultsView.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { InventoryPetItemView } from '../item/InventoryPetItemView'; +import { InventoryPetResultsViewProps } from './InventoryPetResultsView.types'; + +export const InventoryPetResultsView: FC = props => +{ + const { petItems = [] } = props; + + return ( +
+ { (petItems && petItems.length && petItems.map((item, index) => + { + return + })) || null } +
+ ); +} diff --git a/src/views/inventory/views/pet/results/InventoryPetResultsView.types.ts b/src/views/inventory/views/pet/results/InventoryPetResultsView.types.ts new file mode 100644 index 00000000..a2eb62ef --- /dev/null +++ b/src/views/inventory/views/pet/results/InventoryPetResultsView.types.ts @@ -0,0 +1,6 @@ +import { PetItem } from '../../../utils/PetItem'; + +export interface InventoryPetResultsViewProps +{ + petItems: PetItem[]; +}