Add pet inventory

This commit is contained in:
Bill 2021-04-29 04:02:14 -04:00
parent 428f4bb2c8
commit 7476452476
12 changed files with 480 additions and 0 deletions

View File

@ -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<number, PetData>;
}
}
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<IInventoryPetState, IInventoryPetAction> = (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;
}
}

View File

@ -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;
}
}

View File

@ -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<number, PetData>, totalFragments: number, fragmentNumber: number, fragments: Map<number, PetData>[])
{
if(totalFragments === 1) return fragment;
fragments[fragmentNumber] = fragment;
for(const frag of fragments)
{
if(!frag) return null;
}
const merged: Map<number, PetData> = 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<number, PetData>): 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;
}

View File

@ -0,0 +1,2 @@
@import './item/InventoryPetItemView';
@import './results/InventoryPetResultsView';

View File

@ -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<InventoryPetViewProps> = 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<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';
roomPreviewer.reset(false);
roomPreviewer.updateRoomWallsAndFloorVisibility(true, true);
roomPreviewer.updateObjectRoom(floorType, wallType, landscapeType);
roomPreviewer.addPetIntoRoom(petData.figureData.figuredata);
}, [ roomPreviewer, petItem ]);
if(!petItems || !petItems.length)
{
return (
<div className="row h-100">
<div className="col-5 d-flex justify-content-center align-items-center">
<div className="empty-image"></div>
</div>
<div className="d-flex flex-column col justify-content-center">
<div className="h5 m-0 text-black fw-bold m-0 mb-2">
{ LocalizeText('inventory.empty.pets.title') }
</div>
<div className="h6 text-black">{ LocalizeText('inventory.empty.pets.desc') }</div>
</div>
</div>
);
}
return (
<div className="row h-100">
<div className="col-7">
<InventoryPetResultsView petItems={ petItems } />
</div>
<div className="d-flex flex-column col-5 justify-space-between">
<RoomPreviewerView roomPreviewer={ roomPreviewer } height={ 140 } />
{ petItem && <div className="d-flex flex-column flex-grow-1">
<p className="flex-grow-1 fs-6 text-black my-2">{ petItem.petData.name }</p>
{ !!roomSession && <button type="button" className="btn btn-success" onClick={ event => attemptPetPlacement(petItem) }>{ LocalizeText('inventory.furni.placetoroom') }</button> }
</div> }
</div>
</div>
);
}

View File

@ -0,0 +1,7 @@
import { IRoomSession, RoomPreviewer } from 'nitro-renderer';
export interface InventoryPetViewProps
{
roomSession: IRoomSession;
roomPreviewer: RoomPreviewer;
}

View File

@ -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;
}
}
}

View File

@ -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<InventoryPetItemViewProps> = 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 (
<div className="col pe-1 pb-1 inventory-pet-item-container">
<div className={ 'position-relative border border-2 rounded inventory-pet-item cursor-pointer ' + (isActive ? 'active' : '') } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent }>
<PetImageView figure={ petItem.petData.figureData.figuredata } direction={ 3 } headOnly={ true } />
</div>
</div>
);
}

View File

@ -0,0 +1,6 @@
import { PetItem } from '../../../utils/PetItem';
export interface InventoryPetItemViewProps
{
petItem: PetItem;
}

View File

@ -0,0 +1,5 @@
.pet-item-container {
height: 226px;
max-height: 226px;
overflow-y: auto;
}

View File

@ -0,0 +1,17 @@
import { FC } from 'react';
import { InventoryPetItemView } from '../item/InventoryPetItemView';
import { InventoryPetResultsViewProps } from './InventoryPetResultsView.types';
export const InventoryPetResultsView: FC<InventoryPetResultsViewProps> = props =>
{
const { petItems = [] } = props;
return (
<div className="row row-cols-5 align-content-start g-0 pet-item-container">
{ (petItems && petItems.length && petItems.map((item, index) =>
{
return <InventoryPetItemView key={ index } petItem={ item } />
})) || null }
</div>
);
}

View File

@ -0,0 +1,6 @@
import { PetItem } from '../../../utils/PetItem';
export interface InventoryPetResultsViewProps
{
petItems: PetItem[];
}