mirror of
https://github.com/billsonnn/nitro-react.git
synced 2025-01-18 13:26:27 +01:00
Avatar editor updates
This commit is contained in:
parent
2c77d752f7
commit
cf12075382
17
src/api/avatar/AvatarEditorColorSorter.ts
Normal file
17
src/api/avatar/AvatarEditorColorSorter.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { IPartColor } from '@nitrots/nitro-renderer';
|
||||
|
||||
export const AvatarEditorColorSorter = (a: IPartColor, b: IPartColor) =>
|
||||
{
|
||||
const clubLevelA = (!a ? -1 : a.clubLevel);
|
||||
const clubLevelB = (!b ? -1 : b.clubLevel);
|
||||
|
||||
if(clubLevelA < clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA > clubLevelB) return 1;
|
||||
|
||||
if(a.index < b.index) return -1;
|
||||
|
||||
if(a.index > b.index) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class AvatarEditorGridColorItem
|
||||
{
|
||||
private _partColor: IPartColor;
|
||||
private _isDisabled: boolean;
|
||||
private _isHC: boolean;
|
||||
private _isSelected: boolean;
|
||||
private _notifier: () => void;
|
||||
|
||||
constructor(partColor: IPartColor, isDisabled: boolean = false)
|
||||
{
|
||||
this._partColor = partColor;
|
||||
this._isDisabled = isDisabled;
|
||||
this._isHC = (this._partColor.clubLevel > 0);
|
||||
this._isSelected = false;
|
||||
}
|
||||
|
||||
public dispose(): void
|
||||
{
|
||||
this._partColor = null;
|
||||
}
|
||||
|
||||
public get partColor(): IPartColor
|
||||
{
|
||||
return this._partColor;
|
||||
}
|
||||
|
||||
public get color(): string
|
||||
{
|
||||
return ColorConverter.int2rgb(this._partColor.rgb);
|
||||
}
|
||||
|
||||
public get isDisabled(): boolean
|
||||
{
|
||||
return this._isDisabled;
|
||||
}
|
||||
|
||||
public get isHC(): boolean
|
||||
{
|
||||
return this._isHC;
|
||||
}
|
||||
|
||||
public get isSelected(): boolean
|
||||
{
|
||||
return this._isSelected;
|
||||
}
|
||||
|
||||
public set isSelected(flag: boolean)
|
||||
{
|
||||
this._isSelected = flag;
|
||||
|
||||
if(this.notify) this.notify();
|
||||
}
|
||||
|
||||
public get notify(): () => void
|
||||
{
|
||||
return this._notifier;
|
||||
}
|
||||
|
||||
public set notify(notifier: () => void)
|
||||
{
|
||||
this._notifier = notifier;
|
||||
}
|
||||
}
|
@ -1,336 +0,0 @@
|
||||
import { AvatarFigurePartType, GetAvatarRenderManager, IAvatarImageListener, IAvatarRenderManager, IFigurePart, IFigurePartSet, IGraphicAsset, IPartColor, NitroAlphaFilter, NitroContainer, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
export class AvatarEditorGridPartItem implements IAvatarImageListener
|
||||
{
|
||||
private static ALPHA_FILTER: NitroAlphaFilter = new NitroAlphaFilter({ alpha: 0.2 });
|
||||
private static THUMB_DIRECTIONS: number[] = [ 2, 6, 0, 4, 3, 1 ];
|
||||
private static DRAW_ORDER: string[] = [
|
||||
AvatarFigurePartType.LEFT_HAND_ITEM,
|
||||
AvatarFigurePartType.LEFT_HAND,
|
||||
AvatarFigurePartType.LEFT_SLEEVE,
|
||||
AvatarFigurePartType.LEFT_COAT_SLEEVE,
|
||||
AvatarFigurePartType.BODY,
|
||||
AvatarFigurePartType.SHOES,
|
||||
AvatarFigurePartType.LEGS,
|
||||
AvatarFigurePartType.CHEST,
|
||||
AvatarFigurePartType.CHEST_ACCESSORY,
|
||||
AvatarFigurePartType.COAT_CHEST,
|
||||
AvatarFigurePartType.CHEST_PRINT,
|
||||
AvatarFigurePartType.WAIST_ACCESSORY,
|
||||
AvatarFigurePartType.RIGHT_HAND,
|
||||
AvatarFigurePartType.RIGHT_SLEEVE,
|
||||
AvatarFigurePartType.RIGHT_COAT_SLEEVE,
|
||||
AvatarFigurePartType.HEAD,
|
||||
AvatarFigurePartType.FACE,
|
||||
AvatarFigurePartType.EYES,
|
||||
AvatarFigurePartType.HAIR,
|
||||
AvatarFigurePartType.HAIR_BIG,
|
||||
AvatarFigurePartType.FACE_ACCESSORY,
|
||||
AvatarFigurePartType.EYE_ACCESSORY,
|
||||
AvatarFigurePartType.HEAD_ACCESSORY,
|
||||
AvatarFigurePartType.HEAD_ACCESSORY_EXTRA,
|
||||
AvatarFigurePartType.RIGHT_HAND_ITEM,
|
||||
];
|
||||
|
||||
private _renderManager: IAvatarRenderManager;
|
||||
private _partSet: IFigurePartSet;
|
||||
private _partColors: IPartColor[];
|
||||
private _useColors: boolean;
|
||||
private _isDisabled: boolean;
|
||||
private _thumbContainer: NitroContainer;
|
||||
private _imageUrl: string;
|
||||
private _maxColorIndex: number;
|
||||
private _isValidFigure: boolean;
|
||||
private _isHC: boolean;
|
||||
private _isSellable: boolean;
|
||||
private _isClear: boolean;
|
||||
private _isSelected: boolean;
|
||||
private _disposed: boolean;
|
||||
private _isInitalized: boolean;
|
||||
private _notifier: () => void;
|
||||
|
||||
constructor(partSet: IFigurePartSet, partColors: IPartColor[], useColors: boolean = true, isDisabled: boolean = false)
|
||||
{
|
||||
this._renderManager = GetAvatarRenderManager();
|
||||
this._partSet = partSet;
|
||||
this._partColors = partColors;
|
||||
this._useColors = useColors;
|
||||
this._isDisabled = isDisabled;
|
||||
this._thumbContainer = null;
|
||||
this._imageUrl = null;
|
||||
this._maxColorIndex = 0;
|
||||
this._isValidFigure = false;
|
||||
this._isHC = false;
|
||||
this._isSellable = false;
|
||||
this._isClear = false;
|
||||
this._isSelected = false;
|
||||
this._disposed = false;
|
||||
this._isInitalized = false;
|
||||
|
||||
if(partSet)
|
||||
{
|
||||
const colors = partSet.parts;
|
||||
|
||||
for(const color of colors) this._maxColorIndex = Math.max(this._maxColorIndex, color.colorLayerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public init(): void
|
||||
{
|
||||
if(this._isInitalized) return;
|
||||
|
||||
this._isInitalized = true;
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
public dispose(): void
|
||||
{
|
||||
if(this._disposed) return;
|
||||
|
||||
this._renderManager = null;
|
||||
this._partSet = null;
|
||||
this._partColors = null;
|
||||
this._imageUrl = null;
|
||||
this._disposed = true;
|
||||
this._isInitalized = false;
|
||||
|
||||
if(this._thumbContainer)
|
||||
{
|
||||
this._thumbContainer.destroy();
|
||||
|
||||
this._thumbContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public update(): void
|
||||
{
|
||||
this.updateThumbVisualization();
|
||||
}
|
||||
|
||||
private analyzeFigure(): boolean
|
||||
{
|
||||
if(!this._renderManager || !this._partSet || !this._partSet.parts || !this._partSet.parts.length) return false;
|
||||
|
||||
const figureContainer = this._renderManager.createFigureContainer(((this.partSet.type + '-') + this.partSet.id));
|
||||
|
||||
if(!this._renderManager.isFigureContainerReady(figureContainer))
|
||||
{
|
||||
this._renderManager.downloadAvatarFigure(figureContainer, this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this._isValidFigure = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private renderThumb(): NitroContainer
|
||||
{
|
||||
if(!this._renderManager || !this._partSet) return null;
|
||||
|
||||
if(!this._isValidFigure)
|
||||
{
|
||||
if(!this.analyzeFigure()) return null;
|
||||
}
|
||||
|
||||
const parts = this._partSet.parts.concat().sort(this.sortByDrawOrder);
|
||||
const container = new NitroContainer();
|
||||
|
||||
for(const part of parts)
|
||||
{
|
||||
if(!part) continue;
|
||||
|
||||
let asset: IGraphicAsset = null;
|
||||
let direction = 0;
|
||||
let hasAsset = false;
|
||||
|
||||
while(!hasAsset && (direction < AvatarEditorGridPartItem.THUMB_DIRECTIONS.length))
|
||||
{
|
||||
const assetName = ((((((((((FigureData.SCALE + '_') + FigureData.STD) + '_') + part.type) + '_') + part.id) + '_') + AvatarEditorGridPartItem.THUMB_DIRECTIONS[direction]) + '_') + FigureData.DEFAULT_FRAME);
|
||||
|
||||
asset = this._renderManager.getAssetByName(assetName);
|
||||
|
||||
if(asset && asset.texture)
|
||||
{
|
||||
hasAsset = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
direction++;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasAsset) continue;
|
||||
|
||||
const x = asset.offsetX;
|
||||
const y = asset.offsetY;
|
||||
let partColor: IPartColor = null;
|
||||
|
||||
if(this._useColors && (part.colorLayerIndex > 0))
|
||||
{
|
||||
const color = this._partColors[(part.colorLayerIndex - 1)];
|
||||
|
||||
if(color) partColor = color;
|
||||
}
|
||||
|
||||
const sprite = new NitroSprite(asset.texture);
|
||||
|
||||
sprite.position.set(x, y);
|
||||
|
||||
if(partColor) sprite.tint = partColor.rgb;
|
||||
|
||||
container.addChild(sprite);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private async updateThumbVisualization(): Promise<void>
|
||||
{
|
||||
if(!this._isInitalized) return;
|
||||
|
||||
let container = this._thumbContainer;
|
||||
|
||||
if(!container) container = this.renderThumb();
|
||||
|
||||
if(!container) return;
|
||||
|
||||
if(this._partSet)
|
||||
{
|
||||
this._isHC = (this._partSet.clubLevel > 0);
|
||||
this._isSellable = this._partSet.isSellable;
|
||||
}
|
||||
else
|
||||
{
|
||||
this._isHC = false;
|
||||
this._isSellable = false;
|
||||
}
|
||||
|
||||
if(this._isDisabled) this.setAlpha(container, 0.2);
|
||||
|
||||
this._imageUrl = await TextureUtils.generateImageUrl(container);
|
||||
|
||||
if(this.notify) this.notify();
|
||||
}
|
||||
|
||||
private setAlpha(container: NitroContainer, alpha: number): NitroContainer
|
||||
{
|
||||
container.filters = [ AvatarEditorGridPartItem.ALPHA_FILTER ];
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private sortByDrawOrder(a: IFigurePart, b: IFigurePart): number
|
||||
{
|
||||
const indexA = AvatarEditorGridPartItem.DRAW_ORDER.indexOf(a.type);
|
||||
const indexB = AvatarEditorGridPartItem.DRAW_ORDER.indexOf(b.type);
|
||||
|
||||
if(indexA < indexB) return -1;
|
||||
|
||||
if(indexA > indexB) return 1;
|
||||
|
||||
if(a.index < b.index) return -1;
|
||||
|
||||
if(a.index > b.index) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public resetFigure(figure: string): void
|
||||
{
|
||||
if(!this.analyzeFigure()) return;
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
public get disposed(): boolean
|
||||
{
|
||||
return this._disposed;
|
||||
}
|
||||
|
||||
public get id(): number
|
||||
{
|
||||
if(!this._partSet) return -1;
|
||||
|
||||
return this._partSet.id;
|
||||
}
|
||||
|
||||
public get partSet(): IFigurePartSet
|
||||
{
|
||||
return this._partSet;
|
||||
}
|
||||
|
||||
public set partColors(partColors: IPartColor[])
|
||||
{
|
||||
this._partColors = partColors;
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
public get isDisabled(): boolean
|
||||
{
|
||||
return this._isDisabled;
|
||||
}
|
||||
|
||||
public set thumbContainer(container: NitroContainer)
|
||||
{
|
||||
this._thumbContainer = container;
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
public get imageUrl(): string
|
||||
{
|
||||
return this._imageUrl;
|
||||
}
|
||||
|
||||
public get maxColorIndex(): number
|
||||
{
|
||||
return this._maxColorIndex;
|
||||
}
|
||||
|
||||
public get isHC(): boolean
|
||||
{
|
||||
return this._isHC;
|
||||
}
|
||||
|
||||
public get isSellable(): boolean
|
||||
{
|
||||
return this._isSellable;
|
||||
}
|
||||
|
||||
public get isClear(): boolean
|
||||
{
|
||||
return this._isClear;
|
||||
}
|
||||
|
||||
public set isClear(flag: boolean)
|
||||
{
|
||||
this._isClear = flag;
|
||||
}
|
||||
|
||||
public get isSelected(): boolean
|
||||
{
|
||||
return this._isSelected;
|
||||
}
|
||||
|
||||
public set isSelected(flag: boolean)
|
||||
{
|
||||
this._isSelected = flag;
|
||||
|
||||
if(this.notify) this.notify();
|
||||
}
|
||||
|
||||
public get notify(): () => void
|
||||
{
|
||||
return this._notifier;
|
||||
}
|
||||
|
||||
public set notify(notifier: () => void)
|
||||
{
|
||||
this._notifier = notifier;
|
||||
}
|
||||
}
|
35
src/api/avatar/AvatarEditorPartSorter.ts
Normal file
35
src/api/avatar/AvatarEditorPartSorter.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { IFigurePartSet } from '@nitrots/nitro-renderer';
|
||||
|
||||
export const AvatarEditorPartSorter = (hcFirst: boolean) =>
|
||||
{
|
||||
return (a: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }, b: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }) =>
|
||||
{
|
||||
const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel);
|
||||
const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel);
|
||||
const isSellableA = (!a.partSet ? false : a.partSet.isSellable);
|
||||
const isSellableB = (!b.partSet ? false : b.partSet.isSellable);
|
||||
|
||||
if(isSellableA && !isSellableB) return 1;
|
||||
|
||||
if(isSellableB && !isSellableA) return -1;
|
||||
|
||||
if(hcFirst)
|
||||
{
|
||||
if(clubLevelA > clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA < clubLevelB) return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(clubLevelA < clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA > clubLevelB) return 1;
|
||||
}
|
||||
|
||||
if(a.partSet.id < b.partSet.id) return -1;
|
||||
|
||||
if(a.partSet.id > b.partSet.id) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetAssetManager, GetAvatarRenderManager, IFigurePart, IGraphicAsset, IPartColor, NitroAlphaFilter, NitroContainer, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { FigureData } from './FigureData';
|
||||
import { AvatarFigurePartType, AvatarScaleType, AvatarSetType, GetAssetManager, GetAvatarRenderManager, IFigurePart, IGraphicAsset, IPartColor, NitroAlphaFilter, NitroContainer, NitroRectangle, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { IAvatarEditorCategoryPartItem } from './IAvatarEditorCategoryPartItem';
|
||||
|
||||
export class AvatarEditorThumbnailsHelper
|
||||
@ -69,7 +68,7 @@ export class AvatarEditorThumbnailsHelper
|
||||
|
||||
while(!hasAsset && (direction < AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS.length))
|
||||
{
|
||||
const assetName = `${ FigureData.SCALE }_${ FigureData.STD }_${ part.type }_${ part.id }_${ AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS[direction] }_${ FigureData.DEFAULT_FRAME }`;
|
||||
const assetName = `${ AvatarFigurePartType.SCALE }_${ AvatarFigurePartType.STD }_${ part.type }_${ part.id }_${ AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS[direction] }_${ AvatarFigurePartType.DEFAULT_FRAME }`;
|
||||
|
||||
asset = GetAssetManager().getAsset(assetName);
|
||||
|
||||
@ -150,19 +149,23 @@ export class AvatarEditorThumbnailsHelper
|
||||
const resetFigure = async (figure: string) =>
|
||||
{
|
||||
const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, { resetFigure, dispose: null, disposed: false });
|
||||
|
||||
if(avatarImage.isPlaceholder()) return;
|
||||
|
||||
const texture = avatarImage.processAsTexture(AvatarSetType.HEAD, false);
|
||||
const sprite = new NitroSprite(texture);
|
||||
|
||||
if(isDisabled) sprite.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ];
|
||||
|
||||
const imageUrl = await TextureUtils.generateImageUrl({
|
||||
target: sprite
|
||||
target: sprite,
|
||||
frame: new NitroRectangle(0, 0, texture.width, texture.height)
|
||||
});
|
||||
|
||||
sprite.destroy();
|
||||
avatarImage.dispose();
|
||||
|
||||
if(!avatarImage.isPlaceholder()) AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl);
|
||||
AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl);
|
||||
|
||||
resolve(imageUrl);
|
||||
}
|
||||
|
@ -1,277 +0,0 @@
|
||||
import { GetAvatarRenderManager, IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { GetClubMemberLevel, GetConfigurationValue } from '../nitro';
|
||||
import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem';
|
||||
import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem';
|
||||
import { CategoryBaseModel } from './CategoryBaseModel';
|
||||
import { CategoryData } from './CategoryData';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
export class AvatarEditorUtilities
|
||||
{
|
||||
private static MAX_PALETTES: number = 2;
|
||||
|
||||
public static CURRENT_FIGURE: FigureData = null;
|
||||
public static FIGURE_SET_IDS: number[] = [];
|
||||
public static BOUND_FURNITURE_NAMES: string[] = [];
|
||||
|
||||
public static getGender(gender: string): string
|
||||
{
|
||||
switch(gender)
|
||||
{
|
||||
case FigureData.MALE:
|
||||
case 'm':
|
||||
case 'M':
|
||||
gender = FigureData.MALE;
|
||||
break;
|
||||
case FigureData.FEMALE:
|
||||
case 'f':
|
||||
case 'F':
|
||||
gender = FigureData.FEMALE;
|
||||
break;
|
||||
default:
|
||||
gender = FigureData.MALE;
|
||||
}
|
||||
|
||||
return gender;
|
||||
}
|
||||
|
||||
public static hasFigureSetId(setId: number): boolean
|
||||
{
|
||||
return (this.FIGURE_SET_IDS.indexOf(setId) >= 0);
|
||||
}
|
||||
|
||||
public static createCategory(model: CategoryBaseModel, name: string): CategoryData
|
||||
{
|
||||
if(!model || !name || !this.CURRENT_FIGURE) return null;
|
||||
|
||||
const partItems: AvatarEditorGridPartItem[] = [];
|
||||
const colorItems: AvatarEditorGridColorItem[][] = [];
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < this.MAX_PALETTES)
|
||||
{
|
||||
colorItems.push([]);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
const setType = GetAvatarRenderManager().structureData.getSetType(name);
|
||||
|
||||
if(!setType) return null;
|
||||
|
||||
const palette = GetAvatarRenderManager().structureData.getPalette(setType.paletteID);
|
||||
|
||||
if(!palette) return null;
|
||||
|
||||
let colorIds = this.CURRENT_FIGURE.getColorIds(name);
|
||||
|
||||
if(!colorIds) colorIds = [];
|
||||
|
||||
const partColors: IPartColor[] = new Array(colorIds.length);
|
||||
const clubItemsDimmed = this.clubItemsDimmed;
|
||||
const clubMemberLevel = GetClubMemberLevel();
|
||||
|
||||
for(const partColor of palette.colors.getValues())
|
||||
{
|
||||
if(partColor.isSelectable && (clubItemsDimmed || (clubMemberLevel >= partColor.clubLevel)))
|
||||
{
|
||||
let i = 0;
|
||||
|
||||
while(i < this.MAX_PALETTES)
|
||||
{
|
||||
const isDisabled = (clubMemberLevel < partColor.clubLevel);
|
||||
const colorItem = new AvatarEditorGridColorItem(partColor, isDisabled);
|
||||
|
||||
colorItems[i].push(colorItem);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if(name !== FigureData.FACE)
|
||||
{
|
||||
let i = 0;
|
||||
|
||||
while(i < colorIds.length)
|
||||
{
|
||||
if(partColor.id === colorIds[i]) partColors[i] = partColor;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mandatorySetIds: string[] = [];
|
||||
|
||||
if(clubItemsDimmed)
|
||||
{
|
||||
mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, clubMemberLevel);
|
||||
}
|
||||
|
||||
const isntMandatorySet = (mandatorySetIds.indexOf(name) === -1);
|
||||
|
||||
if(isntMandatorySet)
|
||||
{
|
||||
const partItem = new AvatarEditorGridPartItem(null, null, false);
|
||||
|
||||
partItem.isClear = true;
|
||||
|
||||
partItems.push(partItem);
|
||||
}
|
||||
|
||||
const usesColors = (name !== FigureData.FACE);
|
||||
const partSets = setType.partSets;
|
||||
const totalPartSets = partSets.length;
|
||||
|
||||
i = (totalPartSets - 1);
|
||||
|
||||
while(i >= 0)
|
||||
{
|
||||
const partSet = partSets.getWithIndex(i);
|
||||
|
||||
let isValidGender = false;
|
||||
|
||||
if(partSet.gender === FigureData.UNISEX)
|
||||
{
|
||||
isValidGender = true;
|
||||
}
|
||||
|
||||
else if(partSet.gender === this.CURRENT_FIGURE.gender)
|
||||
{
|
||||
isValidGender = true;
|
||||
}
|
||||
|
||||
if(partSet.isSelectable && isValidGender && (clubItemsDimmed || (clubMemberLevel >= partSet.clubLevel)))
|
||||
{
|
||||
const isDisabled = (clubMemberLevel < partSet.clubLevel);
|
||||
|
||||
let isValid = true;
|
||||
|
||||
if(partSet.isSellable) isValid = this.hasFigureSetId(partSet.id);
|
||||
|
||||
if(isValid) partItems.push(new AvatarEditorGridPartItem(partSet, partColors, usesColors, isDisabled));
|
||||
}
|
||||
|
||||
i--;
|
||||
}
|
||||
|
||||
partItems.sort(this.clubItemsFirst ? this.clubSorter : this.noobSorter);
|
||||
|
||||
// if(this._forceSellableClothingVisibility || GetNitroInstance().getConfiguration<boolean>("avatareditor.support.sellablefurni", false))
|
||||
// {
|
||||
// _local_31 = (this._manager.windowManager.assets.getAssetByName("camera_zoom_in") as BitmapDataAsset);
|
||||
// _local_32 = (_local_31.content as BitmapData).clone();
|
||||
// _local_33 = (AvatarEditorView._Str_6802.clone() as IWindowContainer);
|
||||
// _local_33.name = AvatarEditorGridView.GET_MORE;
|
||||
// _local_7 = new AvatarEditorGridPartItem(_local_33, k, null, null, false);
|
||||
// _local_7._Str_3093 = _local_32;
|
||||
// _local_3.push(_local_7);
|
||||
// }
|
||||
|
||||
i = 0;
|
||||
|
||||
while(i < this.MAX_PALETTES)
|
||||
{
|
||||
colorItems[i].sort(this.colorSorter);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return new CategoryData(name, partItems, colorItems);
|
||||
}
|
||||
|
||||
public static clubSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number
|
||||
{
|
||||
const clubLevelA = (!a.partSet ? 9999999999 : a.partSet.clubLevel);
|
||||
const clubLevelB = (!b.partSet ? 9999999999 : b.partSet.clubLevel);
|
||||
const isSellableA = (!a.partSet ? false : a.partSet.isSellable);
|
||||
const isSellableB = (!b.partSet ? false : b.partSet.isSellable);
|
||||
|
||||
if(isSellableA && !isSellableB) return 1;
|
||||
|
||||
if(isSellableB && !isSellableA) return -1;
|
||||
|
||||
if(clubLevelA > clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA < clubLevelB) return 1;
|
||||
|
||||
if(a.partSet.id > b.partSet.id) return -1;
|
||||
|
||||
if(a.partSet.id < b.partSet.id) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static colorSorter(a: AvatarEditorGridColorItem, b: AvatarEditorGridColorItem): number
|
||||
{
|
||||
const clubLevelA = (!a.partColor ? -1 : a.partColor.clubLevel);
|
||||
const clubLevelB = (!b.partColor ? -1 : b.partColor.clubLevel);
|
||||
|
||||
if(clubLevelA < clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA > clubLevelB) return 1;
|
||||
|
||||
if(a.partColor.index < b.partColor.index) return -1;
|
||||
|
||||
if(a.partColor.index > b.partColor.index) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static noobSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number
|
||||
{
|
||||
const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel);
|
||||
const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel);
|
||||
const isSellableA = (!a.partSet ? false : a.partSet.isSellable);
|
||||
const isSellableB = (!b.partSet ? false : b.partSet.isSellable);
|
||||
|
||||
if(isSellableA && !isSellableB) return 1;
|
||||
|
||||
if(isSellableB && !isSellableA) return -1;
|
||||
|
||||
if(clubLevelA < clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA > clubLevelB) return 1;
|
||||
|
||||
if(a.partSet.id < b.partSet.id) return -1;
|
||||
|
||||
if(a.partSet.id > b.partSet.id) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static avatarSetFirstSelectableColor(name: string): number
|
||||
{
|
||||
const setType = GetAvatarRenderManager().structureData.getSetType(name);
|
||||
|
||||
if(!setType) return -1;
|
||||
|
||||
const palette = GetAvatarRenderManager().structureData.getPalette(setType.paletteID);
|
||||
|
||||
if(!palette) return -1;
|
||||
|
||||
for(const color of palette.colors.getValues())
|
||||
{
|
||||
if(!color.isSelectable || (GetClubMemberLevel() < color.clubLevel)) continue;
|
||||
|
||||
return color.id;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static get clubItemsFirst(): boolean
|
||||
{
|
||||
return GetConfigurationValue<boolean>('avatareditor.show.clubitems.first', true);
|
||||
}
|
||||
|
||||
public static get clubItemsDimmed(): boolean
|
||||
{
|
||||
return GetConfigurationValue<boolean>('avatareditor.show.clubitems.dimmed', true);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
import { AvatarEditorFigureCategory, AvatarScaleType, AvatarSetType, GetAvatarRenderManager } from '@nitrots/nitro-renderer';
|
||||
import { AvatarEditorUtilities } from './AvatarEditorUtilities';
|
||||
import { CategoryBaseModel } from './CategoryBaseModel';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
export class BodyModel extends CategoryBaseModel
|
||||
{
|
||||
private _imageCallBackHandled: boolean = false;
|
||||
|
||||
public init(): void
|
||||
{
|
||||
super.init();
|
||||
|
||||
this.addCategory(FigureData.FACE);
|
||||
|
||||
this._isInitalized = true;
|
||||
}
|
||||
|
||||
public selectColor(category: string, colorIndex: number, paletteId: number): void
|
||||
{
|
||||
super.selectColor(category, colorIndex, paletteId);
|
||||
|
||||
this.updateSelectionsFromFigure(FigureData.FACE);
|
||||
}
|
||||
|
||||
protected updateSelectionsFromFigure(name: string): void
|
||||
{
|
||||
if(!this._categories || !AvatarEditorUtilities.CURRENT_FIGURE) return;
|
||||
|
||||
const category = this._categories.get(name);
|
||||
|
||||
if(!category) return;
|
||||
|
||||
const setId = AvatarEditorUtilities.CURRENT_FIGURE.getPartSetId(name);
|
||||
|
||||
let colorIds = AvatarEditorUtilities.CURRENT_FIGURE.getColorIds(name);
|
||||
|
||||
if(!colorIds) colorIds = [];
|
||||
|
||||
category.selectPartId(setId);
|
||||
category.selectColorIds(colorIds);
|
||||
|
||||
for(const part of category.parts)
|
||||
{
|
||||
const resetFigure = (figure: string) =>
|
||||
{
|
||||
const figureString = AvatarEditorUtilities.CURRENT_FIGURE.getFigureStringWithFace(part.id);
|
||||
const avatarImage = GetAvatarRenderManager().createAvatarImage(figureString, AvatarScaleType.LARGE, null, { resetFigure, dispose: null, disposed: false });
|
||||
|
||||
const sprite = avatarImage.processAsContainer(AvatarSetType.HEAD);
|
||||
|
||||
if(sprite)
|
||||
{
|
||||
sprite.y = 10;
|
||||
|
||||
part.thumbContainer = sprite;
|
||||
|
||||
setTimeout(() => avatarImage.dispose(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
resetFigure(null);
|
||||
}
|
||||
}
|
||||
|
||||
public get canSetGender(): boolean
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public get name(): string
|
||||
{
|
||||
return AvatarEditorFigureCategory.GENERIC;
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
import { AvatarEditorUtilities } from './AvatarEditorUtilities';
|
||||
import { CategoryData } from './CategoryData';
|
||||
import { IAvatarEditorCategoryModel } from './IAvatarEditorCategoryModel';
|
||||
|
||||
export class CategoryBaseModel implements IAvatarEditorCategoryModel
|
||||
{
|
||||
protected _categories: Map<string, CategoryData>;
|
||||
protected _isInitalized: boolean;
|
||||
protected _maxPaletteCount: number;
|
||||
private _disposed: boolean;
|
||||
|
||||
constructor()
|
||||
{
|
||||
this._isInitalized = false;
|
||||
this._maxPaletteCount = 0;
|
||||
}
|
||||
|
||||
public dispose(): void
|
||||
{
|
||||
this._categories = null;
|
||||
this._disposed = true;
|
||||
}
|
||||
|
||||
public get disposed(): boolean
|
||||
{
|
||||
return this._disposed;
|
||||
}
|
||||
|
||||
public init(): void
|
||||
{
|
||||
if(!this._categories) this._categories = new Map();
|
||||
}
|
||||
|
||||
public reset(): void
|
||||
{
|
||||
this._isInitalized = false;
|
||||
|
||||
if(this._categories)
|
||||
{
|
||||
for(const category of this._categories.values()) (category && category.dispose());
|
||||
}
|
||||
|
||||
this._categories = new Map();
|
||||
}
|
||||
|
||||
protected addCategory(name: string): void
|
||||
{
|
||||
let existing = this._categories.get(name);
|
||||
|
||||
if(existing) return;
|
||||
|
||||
existing = AvatarEditorUtilities.createCategory(this, name);
|
||||
|
||||
if(!existing) return;
|
||||
|
||||
this._categories.set(name, existing);
|
||||
|
||||
this.updateSelectionsFromFigure(name);
|
||||
}
|
||||
|
||||
protected updateSelectionsFromFigure(figure: string): void
|
||||
{
|
||||
const category = this._categories.get(figure);
|
||||
|
||||
if(!category) return;
|
||||
|
||||
const setId = AvatarEditorUtilities.CURRENT_FIGURE.getPartSetId(figure);
|
||||
|
||||
let colorIds = AvatarEditorUtilities.CURRENT_FIGURE.getColorIds(figure);
|
||||
|
||||
if(!colorIds) colorIds = [];
|
||||
|
||||
category.selectPartId(setId);
|
||||
category.selectColorIds(colorIds);
|
||||
}
|
||||
|
||||
public hasClubSelectionsOverLevel(level: number): boolean
|
||||
{
|
||||
if(!this._categories) return false;
|
||||
|
||||
for(const category of this._categories.values())
|
||||
{
|
||||
if(!category) continue;
|
||||
|
||||
if(category.hasClubSelectionsOverLevel(level)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public hasInvalidSelectedItems(ownedItems: number[]): boolean
|
||||
{
|
||||
if(!this._categories) return false;
|
||||
|
||||
for(const category of this._categories.values())
|
||||
{
|
||||
if(category.hasInvalidSelectedItems(ownedItems)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public stripClubItemsOverLevel(level: number): boolean
|
||||
{
|
||||
if(!this._categories) return false;
|
||||
|
||||
let didStrip = false;
|
||||
|
||||
for(const [ name, category ] of this._categories.entries())
|
||||
{
|
||||
let isValid = false;
|
||||
|
||||
if(category.stripClubItemsOverLevel(level)) isValid = true;
|
||||
|
||||
if(category.stripClubColorsOverLevel(level)) isValid = true;
|
||||
|
||||
if(isValid)
|
||||
{
|
||||
const partItem = category.getCurrentPart();
|
||||
|
||||
if(partItem && AvatarEditorUtilities.CURRENT_FIGURE)
|
||||
{
|
||||
AvatarEditorUtilities.CURRENT_FIGURE.savePartData(name, partItem.id, category.getSelectedColorIds(), true);
|
||||
}
|
||||
|
||||
didStrip = true;
|
||||
}
|
||||
}
|
||||
|
||||
return didStrip;
|
||||
}
|
||||
|
||||
public stripInvalidSellableItems(): boolean
|
||||
{
|
||||
if(!this._categories) return false;
|
||||
|
||||
let didStrip = false;
|
||||
|
||||
for(const [ name, category ] of this._categories.entries())
|
||||
{
|
||||
const isValid = false;
|
||||
|
||||
// if(category._Str_8360(this._Str_2278.manager.inventory)) _local_6 = true;
|
||||
|
||||
if(isValid)
|
||||
{
|
||||
const partItem = category.getCurrentPart();
|
||||
|
||||
if(partItem && AvatarEditorUtilities.CURRENT_FIGURE)
|
||||
{
|
||||
AvatarEditorUtilities.CURRENT_FIGURE.savePartData(name, partItem.id, category.getSelectedColorIds(), true);
|
||||
}
|
||||
|
||||
didStrip = true;
|
||||
}
|
||||
}
|
||||
|
||||
return didStrip;
|
||||
}
|
||||
|
||||
public selectPart(category: string, partIndex: number): void
|
||||
{
|
||||
const categoryData = this._categories.get(category);
|
||||
|
||||
if(!categoryData) return;
|
||||
|
||||
const selectedPartIndex = categoryData.selectedPartIndex;
|
||||
|
||||
categoryData.selectPartIndex(partIndex);
|
||||
|
||||
const partItem = categoryData.getCurrentPart();
|
||||
|
||||
if(!partItem) return;
|
||||
|
||||
if(partItem.isDisabled)
|
||||
{
|
||||
categoryData.selectPartIndex(selectedPartIndex);
|
||||
|
||||
// open hc window
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._maxPaletteCount = partItem.maxColorIndex;
|
||||
|
||||
AvatarEditorUtilities.CURRENT_FIGURE.savePartData(category, partItem.id, categoryData.getSelectedColorIds(), true);
|
||||
}
|
||||
|
||||
public selectColor(category: string, colorIndex: number, paletteId: number): void
|
||||
{
|
||||
const categoryData = this._categories.get(category);
|
||||
|
||||
if(!categoryData) return;
|
||||
|
||||
const paletteIndex = categoryData.getCurrentColorIndex(paletteId);
|
||||
|
||||
categoryData.selectColorIndex(colorIndex, paletteId);
|
||||
|
||||
const colorItem = categoryData.getSelectedColor(paletteId);
|
||||
|
||||
if(colorItem.isDisabled)
|
||||
{
|
||||
categoryData.selectColorIndex(paletteIndex, paletteId);
|
||||
|
||||
// open hc window
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarEditorUtilities.CURRENT_FIGURE.savePartSetColourId(category, categoryData.getSelectedColorIds(), true);
|
||||
}
|
||||
|
||||
public getCategoryData(category: string): CategoryData
|
||||
{
|
||||
if(!this._isInitalized) this.init();
|
||||
|
||||
if(!this._categories) return null;
|
||||
|
||||
return this._categories.get(category);
|
||||
}
|
||||
|
||||
public get categories(): Map<string, CategoryData>
|
||||
{
|
||||
return this._categories;
|
||||
}
|
||||
|
||||
public get canSetGender(): boolean
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public get maxPaletteCount(): number
|
||||
{
|
||||
return (this._maxPaletteCount || 1);
|
||||
}
|
||||
|
||||
public set maxPaletteCount(count: number)
|
||||
{
|
||||
this._maxPaletteCount = count;
|
||||
}
|
||||
|
||||
public get name(): string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,487 +0,0 @@
|
||||
import { IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem';
|
||||
import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem';
|
||||
|
||||
export class CategoryData
|
||||
{
|
||||
private _name: string;
|
||||
private _parts: AvatarEditorGridPartItem[];
|
||||
private _palettes: AvatarEditorGridColorItem[][];
|
||||
private _selectedPartIndex: number = -1;
|
||||
private _paletteIndexes: number[];
|
||||
|
||||
constructor(name: string, partItems: AvatarEditorGridPartItem[], colorItems: AvatarEditorGridColorItem[][])
|
||||
{
|
||||
this._name = name;
|
||||
this._parts = partItems;
|
||||
this._palettes = colorItems;
|
||||
this._selectedPartIndex = -1;
|
||||
}
|
||||
|
||||
private static defaultColorId(palettes: AvatarEditorGridColorItem[], clubLevel: number): number
|
||||
{
|
||||
if(!palettes || !palettes.length) return -1;
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < palettes.length)
|
||||
{
|
||||
const colorItem = palettes[i];
|
||||
|
||||
if(colorItem.partColor && (colorItem.partColor.clubLevel <= clubLevel))
|
||||
{
|
||||
return colorItem.partColor.id;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public init(): void
|
||||
{
|
||||
for(const part of this._parts)
|
||||
{
|
||||
if(!part) continue;
|
||||
|
||||
part.init();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void
|
||||
{
|
||||
if(this._parts)
|
||||
{
|
||||
for(const part of this._parts) part.dispose();
|
||||
|
||||
this._parts = null;
|
||||
}
|
||||
|
||||
if(this._palettes)
|
||||
{
|
||||
for(const palette of this._palettes) for(const colorItem of palette) colorItem.dispose();
|
||||
|
||||
this._palettes = null;
|
||||
}
|
||||
|
||||
this._selectedPartIndex = -1;
|
||||
this._paletteIndexes = null;
|
||||
}
|
||||
|
||||
public selectPartId(partId: number): void
|
||||
{
|
||||
if(!this._parts) return;
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < this._parts.length)
|
||||
{
|
||||
const partItem = this._parts[i];
|
||||
|
||||
if(partItem.id === partId)
|
||||
{
|
||||
this.selectPartIndex(i);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public selectColorIds(colorIds: number[]): void
|
||||
{
|
||||
if(!colorIds || !this._palettes) return;
|
||||
|
||||
this._paletteIndexes = new Array(colorIds.length);
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < this._palettes.length)
|
||||
{
|
||||
const palette = this.getPalette(i);
|
||||
|
||||
if(palette)
|
||||
{
|
||||
let colorId = 0;
|
||||
|
||||
if(colorIds.length > i)
|
||||
{
|
||||
colorId = colorIds[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
const colorItem = palette[0];
|
||||
|
||||
if(colorItem && colorItem.partColor) colorId = colorItem.partColor.id;
|
||||
}
|
||||
|
||||
let j = 0;
|
||||
|
||||
while(j < palette.length)
|
||||
{
|
||||
const colorItem = palette[j];
|
||||
|
||||
if(colorItem.partColor.id === colorId)
|
||||
{
|
||||
this._paletteIndexes[i] = j;
|
||||
|
||||
colorItem.isSelected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
colorItem.isSelected = false;
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
this.updatePartColors();
|
||||
}
|
||||
|
||||
public selectPartIndex(partIndex: number): AvatarEditorGridPartItem
|
||||
{
|
||||
if(!this._parts) return null;
|
||||
|
||||
if((this._selectedPartIndex >= 0) && (this._parts.length > this._selectedPartIndex))
|
||||
{
|
||||
const partItem = this._parts[this._selectedPartIndex];
|
||||
|
||||
if(partItem) partItem.isSelected = false;
|
||||
}
|
||||
|
||||
if(this._parts.length > partIndex)
|
||||
{
|
||||
const partItem = this._parts[partIndex];
|
||||
|
||||
if(partItem)
|
||||
{
|
||||
partItem.isSelected = true;
|
||||
|
||||
this._selectedPartIndex = partIndex;
|
||||
|
||||
return partItem;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public selectColorIndex(colorIndex: number, paletteId: number): AvatarEditorGridColorItem
|
||||
{
|
||||
const palette = this.getPalette(paletteId);
|
||||
|
||||
if(!palette) return null;
|
||||
|
||||
if(palette.length <= colorIndex) return null;
|
||||
|
||||
this.deselectColorIndex(this._paletteIndexes[paletteId], paletteId);
|
||||
|
||||
this._paletteIndexes[paletteId] = colorIndex;
|
||||
|
||||
const colorItem = palette[colorIndex];
|
||||
|
||||
if(!colorItem) return null;
|
||||
|
||||
colorItem.isSelected = true;
|
||||
|
||||
this.updatePartColors();
|
||||
|
||||
return colorItem;
|
||||
}
|
||||
|
||||
public getCurrentColorIndex(k: number): number
|
||||
{
|
||||
return this._paletteIndexes[k];
|
||||
}
|
||||
|
||||
private deselectColorIndex(colorIndex: number, paletteIndex: number): void
|
||||
{
|
||||
const palette = this.getPalette(paletteIndex);
|
||||
|
||||
if(!palette) return;
|
||||
|
||||
if(palette.length <= colorIndex) return;
|
||||
|
||||
const colorItem = palette[colorIndex];
|
||||
|
||||
if(!colorItem) return;
|
||||
|
||||
colorItem.isSelected = false;
|
||||
}
|
||||
|
||||
public getSelectedColorIds(): number[]
|
||||
{
|
||||
if(!this._paletteIndexes || !this._paletteIndexes.length) return null;
|
||||
|
||||
if(!this._palettes || !this._palettes.length) return null;
|
||||
|
||||
const palette = this._palettes[0];
|
||||
|
||||
if(!palette || (!palette.length)) return null;
|
||||
|
||||
const colorItem = palette[0];
|
||||
|
||||
if(!colorItem || !colorItem.partColor) return null;
|
||||
|
||||
const colorId = colorItem.partColor.id;
|
||||
const colorIds: number[] = [];
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < this._paletteIndexes.length)
|
||||
{
|
||||
const paletteSet = this._palettes[i];
|
||||
|
||||
if(!((!(paletteSet)) || (paletteSet.length <= i)))
|
||||
{
|
||||
if(paletteSet.length > this._paletteIndexes[i])
|
||||
{
|
||||
const color = paletteSet[this._paletteIndexes[i]];
|
||||
|
||||
if(color && color.partColor)
|
||||
{
|
||||
colorIds.push(color.partColor.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
colorIds.push(colorId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
colorIds.push(colorId);
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
const partItem = this.getCurrentPart();
|
||||
|
||||
if(!partItem) return null;
|
||||
|
||||
return colorIds.slice(0, Math.max(partItem.maxColorIndex, 1));
|
||||
}
|
||||
|
||||
private getSelectedColors(): IPartColor[]
|
||||
{
|
||||
const partColors: IPartColor[] = [];
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < this._paletteIndexes.length)
|
||||
{
|
||||
const colorItem = this.getSelectedColor(i);
|
||||
|
||||
if(colorItem)
|
||||
{
|
||||
partColors.push(colorItem.partColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
partColors.push(null);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return partColors;
|
||||
}
|
||||
|
||||
public getSelectedColor(paletteId: number): AvatarEditorGridColorItem
|
||||
{
|
||||
const palette = this.getPalette(paletteId);
|
||||
|
||||
if(!palette || (palette.length <= this._paletteIndexes[paletteId])) return null;
|
||||
|
||||
return palette[this._paletteIndexes[paletteId]];
|
||||
}
|
||||
|
||||
public getSelectedColorId(paletteId: number): number
|
||||
{
|
||||
const colorItem = this.getSelectedColor(paletteId);
|
||||
|
||||
if(colorItem && (colorItem.partColor)) return colorItem.partColor.id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public getPalette(paletteId: number): AvatarEditorGridColorItem[]
|
||||
{
|
||||
if(!this._paletteIndexes || !this._palettes || (this._palettes.length <= paletteId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._palettes[paletteId];
|
||||
}
|
||||
|
||||
public getCurrentPart(): AvatarEditorGridPartItem
|
||||
{
|
||||
return this._parts[this._selectedPartIndex] as AvatarEditorGridPartItem;
|
||||
}
|
||||
|
||||
private updatePartColors(): void
|
||||
{
|
||||
const partColors = this.getSelectedColors();
|
||||
|
||||
for(const partItem of this._parts)
|
||||
{
|
||||
if(partItem) partItem.partColors = partColors;
|
||||
}
|
||||
}
|
||||
|
||||
public hasClubSelectionsOverLevel(level: number): boolean
|
||||
{
|
||||
let hasInvalidSelections = false;
|
||||
|
||||
const partColors = this.getSelectedColors();
|
||||
|
||||
if(partColors)
|
||||
{
|
||||
let i = 0;
|
||||
|
||||
while(i < partColors.length)
|
||||
{
|
||||
const partColor = partColors[i];
|
||||
|
||||
if(partColor && (partColor.clubLevel > level)) hasInvalidSelections = true;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
const partItem = this.getCurrentPart();
|
||||
|
||||
if(partItem && partItem.partSet)
|
||||
{
|
||||
const partSet = partItem.partSet;
|
||||
|
||||
if(partSet && (partSet.clubLevel > level)) hasInvalidSelections = true;
|
||||
}
|
||||
|
||||
return hasInvalidSelections;
|
||||
}
|
||||
|
||||
public hasInvalidSelectedItems(ownedItems: number[]): boolean
|
||||
{
|
||||
const part = this.getCurrentPart();
|
||||
|
||||
if(!part) return false;
|
||||
|
||||
const partSet = part.partSet;
|
||||
|
||||
if(!partSet || !partSet.isSellable) return;
|
||||
|
||||
return (ownedItems.indexOf(partSet.id) > -1);
|
||||
}
|
||||
|
||||
public stripClubItemsOverLevel(level: number): boolean
|
||||
{
|
||||
const partItem = this.getCurrentPart();
|
||||
|
||||
if(partItem && partItem.partSet)
|
||||
{
|
||||
const partSet = partItem.partSet;
|
||||
|
||||
if(partSet.clubLevel > level)
|
||||
{
|
||||
const newPartItem = this.selectPartIndex(0);
|
||||
|
||||
if(newPartItem && !newPartItem.partSet) this.selectPartIndex(1);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public stripClubColorsOverLevel(level: number): boolean
|
||||
{
|
||||
const colorIds: number[] = [];
|
||||
const partColors = this.getSelectedColors();
|
||||
const colorItems = this.getPalette(0);
|
||||
|
||||
let didStrip = false;
|
||||
|
||||
const colorId = CategoryData.defaultColorId(colorItems, level);
|
||||
|
||||
if(colorId === -1) return false;
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < partColors.length)
|
||||
{
|
||||
const partColor = partColors[i];
|
||||
|
||||
if(!partColor)
|
||||
{
|
||||
colorIds.push(colorId);
|
||||
|
||||
didStrip = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(partColor.clubLevel > level)
|
||||
{
|
||||
colorIds.push(colorId);
|
||||
|
||||
didStrip = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
colorIds.push(partColor.id);
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if(didStrip) this.selectColorIds(colorIds);
|
||||
|
||||
return didStrip;
|
||||
}
|
||||
|
||||
// public stripInvalidSellableItems(k:IHabboInventory): boolean
|
||||
// {
|
||||
// var _local_3:IFigurePartSet;
|
||||
// var _local_4:AvatarEditorGridPartItem;
|
||||
// var _local_2:AvatarEditorGridPartItem = this._Str_6315();
|
||||
// if (((_local_2) && (_local_2.partSet)))
|
||||
// {
|
||||
// _local_3 = _local_2.partSet;
|
||||
// if (((_local_3.isSellable) && (!(k._Str_14439(_local_3.id)))))
|
||||
// {
|
||||
// _local_4 = this._Str_8066(0);
|
||||
// if (((!(_local_4 == null)) && (_local_4.partSet == null)))
|
||||
// {
|
||||
// this._Str_8066(1);
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
public get name(): string
|
||||
{
|
||||
return this._name;
|
||||
}
|
||||
|
||||
public get parts(): AvatarEditorGridPartItem[]
|
||||
{
|
||||
return this._parts;
|
||||
}
|
||||
|
||||
public get selectedPartIndex(): number
|
||||
{
|
||||
return this._selectedPartIndex;
|
||||
}
|
||||
}
|
@ -1,287 +0,0 @@
|
||||
import { AvatarEditorUtilities } from './AvatarEditorUtilities';
|
||||
|
||||
export class FigureData
|
||||
{
|
||||
private static DEFAULT_DIRECTION: number = 4;
|
||||
|
||||
public static MALE: string = 'M';
|
||||
public static FEMALE: string = 'F';
|
||||
public static UNISEX: string = 'U';
|
||||
public static SCALE: string = 'h';
|
||||
public static STD: string = 'std';
|
||||
public static DEFAULT_FRAME: string = '0';
|
||||
public static FACE: string = 'hd';
|
||||
public static HAIR: string = 'hr';
|
||||
public static HAT: string = 'ha';
|
||||
public static HEAD_ACCESSORIES: string = 'he';
|
||||
public static EYE_ACCESSORIES: string = 'ea';
|
||||
public static FACE_ACCESSORIES: string = 'fa';
|
||||
public static JACKET: string = 'cc';
|
||||
public static SHIRT: string = 'ch';
|
||||
public static CHEST_ACCESSORIES: string = 'ca';
|
||||
public static CHEST_PRINTS: string = 'cp';
|
||||
public static TROUSERS: string = 'lg';
|
||||
public static SHOES: string = 'sh';
|
||||
public static TROUSER_ACCESSORIES: string = 'wa';
|
||||
public static SET_TYPES = [ FigureData.FACE, FigureData.HAIR, FigureData.HAT, FigureData.HEAD_ACCESSORIES, FigureData.EYE_ACCESSORIES, FigureData.FACE_ACCESSORIES, FigureData.JACKET, FigureData.SHIRT, FigureData.CHEST_ACCESSORIES, FigureData.CHEST_PRINTS, FigureData.TROUSERS, FigureData.SHOES, FigureData.TROUSERS ];
|
||||
|
||||
private _data: Map<string, number>;
|
||||
private _colors: Map<string, number[]>;
|
||||
private _gender: string = 'M';
|
||||
private _direction: number = FigureData.DEFAULT_DIRECTION;
|
||||
private _avatarEffectType: number = -1;
|
||||
private _notifier: () => void = null;
|
||||
|
||||
public loadAvatarData(figureString: string, gender: string): void
|
||||
{
|
||||
this._data = new Map();
|
||||
this._colors = new Map();
|
||||
this._gender = gender;
|
||||
|
||||
this.parseFigureString(figureString);
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private parseFigureString(figure: string): void
|
||||
{
|
||||
if(!figure) return;
|
||||
|
||||
const sets = figure.split('.');
|
||||
|
||||
if(!sets || !sets.length) return;
|
||||
|
||||
for(const set of sets)
|
||||
{
|
||||
const parts = set.split('-');
|
||||
|
||||
if(!parts.length) continue;
|
||||
|
||||
const setType = parts[0];
|
||||
const setId = parseInt(parts[1]);
|
||||
const colorIds: number[] = [];
|
||||
|
||||
let offset = 2;
|
||||
|
||||
while(offset < parts.length)
|
||||
{
|
||||
colorIds.push(parseInt(parts[offset]));
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
if(!colorIds.length) colorIds.push(0);
|
||||
|
||||
this.savePartSetId(setType, setId, false);
|
||||
this.savePartSetColourId(setType, colorIds, false);
|
||||
}
|
||||
}
|
||||
|
||||
public getPartSetId(setType: string): number
|
||||
{
|
||||
const existing = this._data.get(setType);
|
||||
|
||||
if(existing !== undefined) return existing;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public getColorIds(setType: string): number[]
|
||||
{
|
||||
const existing = this._colors.get(setType);
|
||||
|
||||
if(existing !== undefined) return existing;
|
||||
|
||||
return [ AvatarEditorUtilities.avatarSetFirstSelectableColor(setType) ];
|
||||
}
|
||||
|
||||
public getFigureString(): string
|
||||
{
|
||||
let figureString = '';
|
||||
const setParts: string[] = [];
|
||||
|
||||
for(const [ setType, setId ] of this._data.entries())
|
||||
{
|
||||
const colorIds = this._colors.get(setType);
|
||||
|
||||
let setPart = ((setType + '-') + setId);
|
||||
|
||||
if(colorIds && colorIds.length)
|
||||
{
|
||||
let i = 0;
|
||||
|
||||
while(i < colorIds.length)
|
||||
{
|
||||
setPart = (setPart + ('-' + colorIds[i]));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
setParts.push(setPart);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < setParts.length)
|
||||
{
|
||||
figureString = (figureString + setParts[i]);
|
||||
|
||||
if(i < (setParts.length - 1)) figureString = (figureString + '.');
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return figureString;
|
||||
}
|
||||
|
||||
public savePartData(setType: string, partId: number, colorIds: number[], update: boolean = false): void
|
||||
{
|
||||
this.savePartSetId(setType, partId, update);
|
||||
this.savePartSetColourId(setType, colorIds, update);
|
||||
}
|
||||
|
||||
private savePartSetId(setType: string, partId: number, update: boolean = true): void
|
||||
{
|
||||
switch(setType)
|
||||
{
|
||||
case FigureData.FACE:
|
||||
case FigureData.HAIR:
|
||||
case FigureData.HAT:
|
||||
case FigureData.HEAD_ACCESSORIES:
|
||||
case FigureData.EYE_ACCESSORIES:
|
||||
case FigureData.FACE_ACCESSORIES:
|
||||
case FigureData.SHIRT:
|
||||
case FigureData.JACKET:
|
||||
case FigureData.CHEST_ACCESSORIES:
|
||||
case FigureData.CHEST_PRINTS:
|
||||
case FigureData.TROUSERS:
|
||||
case FigureData.SHOES:
|
||||
case FigureData.TROUSER_ACCESSORIES:
|
||||
if(partId >= 0)
|
||||
{
|
||||
this._data.set(setType, partId);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._data.delete(setType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(update) this.updateView();
|
||||
}
|
||||
|
||||
public savePartSetColourId(setType: string, colorIds: number[], update: boolean = true): void
|
||||
{
|
||||
switch(setType)
|
||||
{
|
||||
case FigureData.FACE:
|
||||
case FigureData.HAIR:
|
||||
case FigureData.HAT:
|
||||
case FigureData.HEAD_ACCESSORIES:
|
||||
case FigureData.EYE_ACCESSORIES:
|
||||
case FigureData.FACE_ACCESSORIES:
|
||||
case FigureData.SHIRT:
|
||||
case FigureData.JACKET:
|
||||
case FigureData.CHEST_ACCESSORIES:
|
||||
case FigureData.CHEST_PRINTS:
|
||||
case FigureData.TROUSERS:
|
||||
case FigureData.SHOES:
|
||||
case FigureData.TROUSER_ACCESSORIES:
|
||||
this._colors.set(setType, colorIds);
|
||||
break;
|
||||
}
|
||||
|
||||
if(update) this.updateView();
|
||||
}
|
||||
|
||||
public getFigureStringWithFace(k: number, override = true): string
|
||||
{
|
||||
let figureString = '';
|
||||
|
||||
const setTypes: string[] = [ FigureData.FACE ];
|
||||
const figureSets: string[] = [];
|
||||
|
||||
for(const setType of setTypes)
|
||||
{
|
||||
const colors = this._colors.get(setType);
|
||||
|
||||
if(!colors) continue;
|
||||
|
||||
let setId = this._data.get(setType);
|
||||
|
||||
if((setType === FigureData.FACE) && override) setId = k;
|
||||
|
||||
let figureSet = ((setType + '-') + setId);
|
||||
|
||||
if(setId >= 0)
|
||||
{
|
||||
let i = 0;
|
||||
|
||||
while(i < colors.length)
|
||||
{
|
||||
figureSet = (figureSet + ('-' + colors[i]));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
figureSets.push(figureSet);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < figureSets.length)
|
||||
{
|
||||
figureString = (figureString + figureSets[i]);
|
||||
|
||||
if(i < (figureSets.length - 1)) figureString = (figureString + '.');
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return figureString;
|
||||
}
|
||||
|
||||
public updateView(): void
|
||||
{
|
||||
if(this.notify) this.notify();
|
||||
}
|
||||
|
||||
public get gender(): string
|
||||
{
|
||||
return this._gender;
|
||||
}
|
||||
|
||||
public get direction(): number
|
||||
{
|
||||
return this._direction;
|
||||
}
|
||||
|
||||
public set direction(direction: number)
|
||||
{
|
||||
this._direction = direction;
|
||||
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
public set avatarEffectType(k: number)
|
||||
{
|
||||
this._avatarEffectType = k;
|
||||
}
|
||||
|
||||
public get avatarEffectType(): number
|
||||
{
|
||||
return this._avatarEffectType;
|
||||
}
|
||||
|
||||
public get notify(): () => void
|
||||
{
|
||||
return this._notifier;
|
||||
}
|
||||
|
||||
public set notify(notifier: () => void)
|
||||
{
|
||||
this._notifier = notifier;
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
import { AvatarFigureContainer, GetAvatarRenderManager, IFigurePartSet, IPalette, IPartColor, SetType } from '@nitrots/nitro-renderer';
|
||||
import { Randomizer } from '../utils';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
function getTotalColors(partSet: IFigurePartSet): number
|
||||
{
|
||||
const parts = partSet.parts;
|
||||
|
||||
let totalColors = 0;
|
||||
|
||||
for(const part of parts) totalColors = Math.max(totalColors, part.colorLayerIndex);
|
||||
|
||||
return totalColors;
|
||||
}
|
||||
|
||||
function getRandomSetTypes(requiredSets: string[], options: string[]): string[]
|
||||
{
|
||||
options = options.filter(option => (requiredSets.indexOf(option) === -1));
|
||||
|
||||
return [ ...requiredSets, ...Randomizer.getRandomElements(options, (Randomizer.getRandomNumber(options.length) + 1)) ];
|
||||
}
|
||||
|
||||
function getRandomPartSet(setType: SetType, gender: string, clubLevel: number = 0, figureSetIds: number[] = []): IFigurePartSet
|
||||
{
|
||||
if(!setType) return null;
|
||||
|
||||
const options = setType.partSets.getValues().filter(option =>
|
||||
{
|
||||
if(!option.isSelectable || ((option.gender !== 'U') && (option.gender !== gender)) || (option.clubLevel > clubLevel) || (option.isSellable && (figureSetIds.indexOf(option.id) === -1))) return null;
|
||||
|
||||
return option;
|
||||
});
|
||||
|
||||
if(!options || !options.length) return null;
|
||||
|
||||
return Randomizer.getRandomElement(options);
|
||||
}
|
||||
|
||||
function getRandomColors(palette: IPalette, partSet: IFigurePartSet, clubLevel: number = 0): IPartColor[]
|
||||
{
|
||||
if(!palette) return [];
|
||||
|
||||
const options = palette.colors.getValues().filter(option =>
|
||||
{
|
||||
if(!option.isSelectable || (option.clubLevel > clubLevel)) return null;
|
||||
|
||||
return option;
|
||||
});
|
||||
|
||||
if(!options || !options.length) return null;
|
||||
|
||||
return Randomizer.getRandomElements(options, getTotalColors(partSet));
|
||||
}
|
||||
|
||||
export function generateRandomFigure(figureData: FigureData, gender: string, clubLevel: number = 0, figureSetIds: number[] = [], ignoredSets: string[] = []): string
|
||||
{
|
||||
const structure = GetAvatarRenderManager().structure;
|
||||
const figureContainer = new AvatarFigureContainer('');
|
||||
const requiredSets = getRandomSetTypes(structure.getMandatorySetTypeIds(gender, clubLevel), FigureData.SET_TYPES);
|
||||
|
||||
for(const setType of ignoredSets)
|
||||
{
|
||||
const partSetId = figureData.getPartSetId(setType);
|
||||
const colors = figureData.getColorIds(setType);
|
||||
|
||||
figureContainer.updatePart(setType, partSetId, colors);
|
||||
}
|
||||
|
||||
for(const type of requiredSets)
|
||||
{
|
||||
if(figureContainer.hasPartType(type)) continue;
|
||||
|
||||
const setType = (structure.figureData.getSetType(type) as SetType);
|
||||
const selectedSet = getRandomPartSet(setType, gender, clubLevel, figureSetIds);
|
||||
|
||||
if(!selectedSet) continue;
|
||||
|
||||
let selectedColors: number[] = [];
|
||||
|
||||
if(selectedSet.isColorable)
|
||||
{
|
||||
selectedColors = getRandomColors(structure.figureData.getPalette(setType.paletteID), selectedSet, clubLevel).map(color => color.id);
|
||||
}
|
||||
|
||||
figureContainer.updatePart(setType.type, selectedSet.id, selectedColors);
|
||||
}
|
||||
|
||||
return figureContainer.getFigureString();
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer';
|
||||
import { CategoryBaseModel } from './CategoryBaseModel';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
export class HeadModel extends CategoryBaseModel
|
||||
{
|
||||
public init(): void
|
||||
{
|
||||
super.init();
|
||||
|
||||
this.addCategory(FigureData.HAIR);
|
||||
this.addCategory(FigureData.HAT);
|
||||
this.addCategory(FigureData.HEAD_ACCESSORIES);
|
||||
this.addCategory(FigureData.EYE_ACCESSORIES);
|
||||
this.addCategory(FigureData.FACE_ACCESSORIES);
|
||||
|
||||
this._isInitalized = true;
|
||||
}
|
||||
|
||||
public get name(): string
|
||||
{
|
||||
return AvatarEditorFigureCategory.HEAD;
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import { CategoryData } from './CategoryData';
|
||||
|
||||
export interface IAvatarEditorCategoryModel
|
||||
{
|
||||
init(): void;
|
||||
dispose(): void;
|
||||
reset(): void;
|
||||
getCategoryData(category: string): CategoryData;
|
||||
selectPart(category: string, partIndex: number): void;
|
||||
selectColor(category: string, colorIndex: number, paletteId: number): void;
|
||||
hasClubSelectionsOverLevel(level: number): boolean;
|
||||
hasInvalidSelectedItems(ownedItems: number[]): boolean;
|
||||
stripClubItemsOverLevel(level: number): boolean;
|
||||
stripInvalidSellableItems(): boolean;
|
||||
categories: Map<string, CategoryData>;
|
||||
canSetGender: boolean;
|
||||
maxPaletteCount: number;
|
||||
name: string;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer';
|
||||
import { CategoryBaseModel } from './CategoryBaseModel';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
export class LegModel extends CategoryBaseModel
|
||||
{
|
||||
public init(): void
|
||||
{
|
||||
super.init();
|
||||
|
||||
this.addCategory(FigureData.TROUSERS);
|
||||
this.addCategory(FigureData.SHOES);
|
||||
this.addCategory(FigureData.TROUSER_ACCESSORIES);
|
||||
|
||||
this._isInitalized = true;
|
||||
}
|
||||
|
||||
public get name(): string
|
||||
{
|
||||
return AvatarEditorFigureCategory.LEGS;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer';
|
||||
import { CategoryBaseModel } from './CategoryBaseModel';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
export class TorsoModel extends CategoryBaseModel
|
||||
{
|
||||
public init(): void
|
||||
{
|
||||
super.init();
|
||||
|
||||
this.addCategory(FigureData.SHIRT);
|
||||
this.addCategory(FigureData.CHEST_PRINTS);
|
||||
this.addCategory(FigureData.JACKET);
|
||||
this.addCategory(FigureData.CHEST_ACCESSORIES);
|
||||
|
||||
this._isInitalized = true;
|
||||
}
|
||||
|
||||
public get name(): string
|
||||
{
|
||||
return AvatarEditorFigureCategory.TORSO;
|
||||
}
|
||||
}
|
@ -1,16 +1,6 @@
|
||||
export * from './AvatarEditorAction';
|
||||
export * from './AvatarEditorGridColorItem';
|
||||
export * from './AvatarEditorGridPartItem';
|
||||
export * from './AvatarEditorColorSorter';
|
||||
export * from './AvatarEditorPartSorter';
|
||||
export * from './AvatarEditorThumbnailsHelper';
|
||||
export * from './AvatarEditorUtilities';
|
||||
export * from './BodyModel';
|
||||
export * from './CategoryBaseModel';
|
||||
export * from './CategoryData';
|
||||
export * from './FigureData';
|
||||
export * from './FigureGenerator';
|
||||
export * from './HeadModel';
|
||||
export * from './IAvatarEditorCategory';
|
||||
export * from './IAvatarEditorCategoryModel';
|
||||
export * from './IAvatarEditorCategoryPartItem';
|
||||
export * from './LegModel';
|
||||
export * from './TorsoModel';
|
||||
|
@ -8,19 +8,20 @@ interface InfiniteGridProps<T = any>
|
||||
rows: T[];
|
||||
columnCount: number;
|
||||
overscan?: number;
|
||||
itemRender?: (item: T) => ReactElement;
|
||||
estimateSize?: number;
|
||||
itemRender?: (item: T, index?: number) => ReactElement;
|
||||
}
|
||||
|
||||
export const InfiniteGrid: FC<InfiniteGridProps> = props =>
|
||||
{
|
||||
const { rows = [], columnCount = 4, overscan = 5, itemRender = null } = props;
|
||||
const { rows = [], columnCount = 4, overscan = 5, estimateSize = 45, itemRender = null } = props;
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: Math.ceil(rows.length / columnCount),
|
||||
overscan,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 45,
|
||||
estimateSize: () => estimateSize
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
@ -58,7 +59,7 @@ export const InfiniteGrid: FC<InfiniteGridProps> = props =>
|
||||
style={ {
|
||||
display: 'grid',
|
||||
gap: '0.25rem',
|
||||
minHeight: virtualRow.index === 0 ? 45 : virtualRow.size,
|
||||
minHeight: virtualRow.index === 0 ? estimateSize : virtualRow.size,
|
||||
gridTemplateColumns: `repeat(${ columnCount }, 1fr)`
|
||||
} }>
|
||||
{ Array.from(Array(columnCount)).map((e,i) =>
|
||||
@ -67,8 +68,12 @@ export const InfiniteGrid: FC<InfiniteGridProps> = props =>
|
||||
|
||||
if(!item) return <Fragment
|
||||
key={ virtualRow.index + i + 'b' } />;
|
||||
|
||||
return itemRender(item);
|
||||
|
||||
return (
|
||||
<Fragment key={ i }>
|
||||
{ itemRender(item, i) }
|
||||
</Fragment>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
)) }
|
||||
|
@ -1,336 +0,0 @@
|
||||
.nitro-avatar-editor-spritesheet {
|
||||
background: url('@/assets/images/avatareditor/avatar-editor-spritesheet.png') transparent no-repeat;
|
||||
|
||||
&.arrow-left-icon {
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
background-position: -226px -131px;
|
||||
}
|
||||
|
||||
&.arrow-right-icon {
|
||||
width: 28px;
|
||||
height: 21px;
|
||||
background-position: -226px -162px;
|
||||
}
|
||||
|
||||
&.ca-icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-position: -226px -61px;
|
||||
|
||||
&.selected {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-position: -226px -96px;
|
||||
}
|
||||
}
|
||||
|
||||
&.cc-icon {
|
||||
width: 31px;
|
||||
height: 29px;
|
||||
background-position: -145px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 31px;
|
||||
height: 29px;
|
||||
background-position: -145px -44px;
|
||||
}
|
||||
}
|
||||
|
||||
&.ch-icon {
|
||||
width: 29px;
|
||||
height: 24px;
|
||||
background-position: -186px -39px;
|
||||
|
||||
&.selected {
|
||||
width: 29px;
|
||||
height: 24px;
|
||||
background-position: -186px -73px;
|
||||
}
|
||||
}
|
||||
|
||||
&.clear-icon {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
background-position: -145px -157px;
|
||||
}
|
||||
|
||||
&.cp-icon {
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
background-position: -145px -264px;
|
||||
|
||||
&.selected {
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
background-position: -186px -5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.ea-icon {
|
||||
width: 35px;
|
||||
height: 16px;
|
||||
background-position: -226px -193px;
|
||||
|
||||
&.selected {
|
||||
width: 35px;
|
||||
height: 16px;
|
||||
background-position: -226px -219px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fa-icon {
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
background-position: -186px -137px;
|
||||
|
||||
&.selected {
|
||||
width: 27px;
|
||||
height: 20px;
|
||||
background-position: -186px -107px;
|
||||
}
|
||||
}
|
||||
|
||||
&.female-icon {
|
||||
width: 18px;
|
||||
height: 27px;
|
||||
background-position: -186px -202px;
|
||||
|
||||
&.selected {
|
||||
width: 18px;
|
||||
height: 27px;
|
||||
background-position: -186px -239px;
|
||||
}
|
||||
}
|
||||
|
||||
&.ha-icon {
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
background-position: -226px -245px;
|
||||
|
||||
&.selected {
|
||||
width: 25px;
|
||||
height: 22px;
|
||||
background-position: -226px -277px;
|
||||
}
|
||||
}
|
||||
|
||||
&.he-icon {
|
||||
width: 31px;
|
||||
height: 27px;
|
||||
background-position: -145px -83px;
|
||||
|
||||
&.selected {
|
||||
width: 31px;
|
||||
height: 27px;
|
||||
background-position: -145px -120px;
|
||||
}
|
||||
}
|
||||
|
||||
&.hr-icon {
|
||||
width: 29px;
|
||||
height: 25px;
|
||||
background-position: -145px -194px;
|
||||
|
||||
&.selected {
|
||||
width: 29px;
|
||||
height: 25px;
|
||||
background-position: -145px -229px;
|
||||
}
|
||||
}
|
||||
|
||||
&.lg-icon {
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
background-position: -303px -45px;
|
||||
|
||||
&.selected {
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
background-position: -303px -75px;
|
||||
}
|
||||
}
|
||||
|
||||
&.loading-icon {
|
||||
width: 21px;
|
||||
height: 25px;
|
||||
background-position: -186px -167px;
|
||||
}
|
||||
|
||||
|
||||
&.male-icon {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background-position: -186px -276px;
|
||||
|
||||
&.selected {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background-position: -272px -5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.sellable-icon {
|
||||
width: 17px;
|
||||
height: 15px;
|
||||
background-position: -303px -105px;
|
||||
}
|
||||
|
||||
|
||||
&.sh-icon {
|
||||
width: 37px;
|
||||
height: 10px;
|
||||
background-position: -303px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 37px;
|
||||
height: 10px;
|
||||
background-position: -303px -25px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.spotlight-icon {
|
||||
width: 130px;
|
||||
height: 305px;
|
||||
background-position: -5px -5px;
|
||||
}
|
||||
|
||||
|
||||
&.wa-icon {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
background-position: -226px -5px;
|
||||
|
||||
&.selected {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
background-position: -226px -33px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-avatar-editor-wardrobe-figure-preview {
|
||||
background-color: $pale-sky;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
margin: 0 auto;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.avatar-shadow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 25px;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.20);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: $pale-sky;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.button-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-avatar-editor {
|
||||
width: $avatar-editor-width;
|
||||
height: $avatar-editor-height;
|
||||
|
||||
.category-item {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.figure-preview-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background-color: $pale-sky;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.arrow-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
bottom: 12px;
|
||||
z-index: 5;
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 50px;
|
||||
margin: 0 auto;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.avatar-spotlight {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.avatar-shadow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 15px;
|
||||
width: 70px;
|
||||
height: 30px;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.20);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: $pale-sky;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
import { AddLinkEventTracker, AvatarEditorFigureCategory, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FaDice, FaTrash, FaUndo } from 'react-icons/fa';
|
||||
import { AvatarEditorAction, LocalizeText } from '../../api';
|
||||
import { Button, ButtonGroup, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { AvatarEditorModelView } from './views/AvatarEditorModelView';
|
||||
|
||||
const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007';
|
||||
const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68';
|
||||
|
||||
export const AvatarEditorNewView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const { setIsVisible: setEditorVisibility, avatarModels, activeModelKey, setActiveModelKey } = useAvatarEditor();
|
||||
|
||||
const processAction = (action: string) =>
|
||||
{
|
||||
switch(action)
|
||||
{
|
||||
case AvatarEditorAction.ACTION_CLEAR:
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RESET:
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RANDOMIZE:
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_SAVE:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const linkTracker: ILinkEventTracker = {
|
||||
linkReceived: (url: string) =>
|
||||
{
|
||||
const parts = url.split('/');
|
||||
|
||||
if(parts.length < 2) return;
|
||||
|
||||
switch(parts[1])
|
||||
{
|
||||
case 'show':
|
||||
setIsVisible(true);
|
||||
return;
|
||||
case 'hide':
|
||||
setIsVisible(false);
|
||||
return;
|
||||
case 'toggle':
|
||||
setIsVisible(prevValue => !prevValue);
|
||||
return;
|
||||
}
|
||||
},
|
||||
eventUrlPrefix: 'avatar-editor/'
|
||||
};
|
||||
|
||||
AddLinkEventTracker(linkTracker);
|
||||
|
||||
return () => RemoveLinkEventTracker(linkTracker);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setEditorVisibility(isVisible)
|
||||
}, [ isVisible, setEditorVisibility ]);
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView uniqueKey="avatar-editor" className="nitro-avatar-editor">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardTabsView>
|
||||
{ Object.keys(avatarModels).map(modelKey =>
|
||||
{
|
||||
const isActive = (activeModelKey === modelKey);
|
||||
|
||||
return (
|
||||
<NitroCardTabsItemView key={ modelKey } isActive={ isActive } onClick={ event => setActiveModelKey(modelKey) }>
|
||||
{ LocalizeText(`avatareditor.category.${ modelKey }`) }
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
}) }
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
<Grid>
|
||||
<Column size={ 9 } overflow="hidden">
|
||||
{ ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) &&
|
||||
<AvatarEditorModelView name={ activeModelKey } categories={ avatarModels[activeModelKey] } /> }
|
||||
{ (activeModelKey === AvatarEditorFigureCategory.WARDROBE) }
|
||||
</Column>
|
||||
<Column size={ 3 } overflow="hidden">
|
||||
{ /* <AvatarEditorFigurePreviewView figureData={ figureData } /> */ }
|
||||
<Column grow gap={ 1 }>
|
||||
<ButtonGroup>
|
||||
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
|
||||
<FaUndo className="fa-icon" />
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_CLEAR) }>
|
||||
<FaTrash className="fa-icon" />
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }>
|
||||
<FaDice className="fa-icon" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Button className="w-100" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
|
||||
{ LocalizeText('avatareditor.save') }
|
||||
</Button>
|
||||
</Column>
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import { IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { FC, useRef } from 'react';
|
||||
import { IAvatarEditorCategory } from '../../../../api';
|
||||
import { AutoGrid } from '../../../../common';
|
||||
import { useAvatarEditor } from '../../../../hooks';
|
||||
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
|
||||
|
||||
export const AvatarEditorPaletteSetView: FC<{
|
||||
category: IAvatarEditorCategory,
|
||||
paletteIndex: number;
|
||||
}> = props =>
|
||||
{
|
||||
const { category = null, paletteIndex = -1 } = props;
|
||||
const paletteSet = category?.colorItems[paletteIndex] ?? null;
|
||||
const { selectedColors = null, selectEditorColor } = useAvatarEditor();
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isPartColorSelected = (partColor: IPartColor) =>
|
||||
{
|
||||
if(!category || !category.setType || !selectedColors || !selectedColors[category.setType] || !selectedColors[category.setType][paletteIndex]) return false;
|
||||
|
||||
const colorId = selectedColors[category.setType][paletteIndex];
|
||||
|
||||
return (colorId === partColor.id);
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoGrid innerRef={ elementRef } gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
|
||||
{ (paletteSet.length > 0) && paletteSet.map(item =>
|
||||
<AvatarEditorPaletteSetItem key={ item.id } setType={ category.setType } partColor={ item } isSelected={ isPartColorSelected(item) } onClick={ event => selectEditorColor(category.setType, paletteIndex, item.id) } />) }
|
||||
</AutoGrid>
|
||||
);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import { AvatarDirectionAngle } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { Base, Column, LayoutAvatarImageView } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
|
||||
const DEFAULT_DIRECTION: number = 4;
|
||||
|
||||
export const AvatarEditorFigurePreviewView: FC<{}> = props =>
|
||||
{
|
||||
const [ direction, setDirection ] = useState<number>(DEFAULT_DIRECTION);
|
||||
const { getFigureString = null } = useAvatarEditor();
|
||||
|
||||
const rotateFigure = (newDirection: number) =>
|
||||
{
|
||||
if(direction < AvatarDirectionAngle.MIN_DIRECTION)
|
||||
{
|
||||
newDirection = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1));
|
||||
}
|
||||
|
||||
if(direction > AvatarDirectionAngle.MAX_DIRECTION)
|
||||
{
|
||||
newDirection = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1));
|
||||
}
|
||||
|
||||
setDirection(newDirection);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column className="figure-preview-container" overflow="hidden" position="relative">
|
||||
<LayoutAvatarImageView figure={ getFigureString } direction={ direction } scale={ 2 } />
|
||||
<AvatarEditorIcon className="avatar-spotlight" icon="spotlight" />
|
||||
<Base className="avatar-shadow" />
|
||||
<Base className="arrow-container">
|
||||
<AvatarEditorIcon pointer icon="arrow-left" onClick={ event => rotateFigure(direction + 1) } />
|
||||
<AvatarEditorIcon pointer icon="arrow-right" onClick={ event => rotateFigure(direction - 1) } />
|
||||
</Base>
|
||||
</Column>
|
||||
);
|
||||
}
|
@ -1,15 +1,12 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../../../common';
|
||||
import { Base, BaseProps } from '../../common';
|
||||
|
||||
type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string;
|
||||
|
||||
export interface AvatarEditorIconProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
export const AvatarEditorIcon: FC<{
|
||||
icon: AvatarIconType;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export const AvatarEditorIcon: FC<AvatarEditorIconProps> = props =>
|
||||
} & BaseProps<HTMLDivElement>> = props =>
|
||||
{
|
||||
const { icon = null, selected = false, classNames = [], children = null, ...rest } = props;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { FigureData, IAvatarEditorCategory } from '../../../api';
|
||||
import { Column, Flex, Grid } from '../../../common';
|
||||
import { useAvatarEditor } from '../../../hooks';
|
||||
import { AvatarEditorFigureCategory, AvatarFigurePartType, FigureDataContainer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { IAvatarEditorCategory } from '../../api';
|
||||
import { Column, Flex, Grid } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
import { AvatarEditorFigureSetView } from './figure-set';
|
||||
import { AvatarEditorPaletteSetView } from './palette-set';
|
||||
@ -13,39 +13,35 @@ export const AvatarEditorModelView: FC<{
|
||||
}> = props =>
|
||||
{
|
||||
const { name = '', categories = [] } = props;
|
||||
const [ didChange, setDidChange ] = useState<boolean>(false);
|
||||
const [ activeSetType, setActiveSetType ] = useState<string>('');
|
||||
const { maxPaletteCount = 1 } = useAvatarEditor();
|
||||
const { maxPaletteCount = 1, gender = null, setGender = null, selectedColorParts = null, getFirstSelectableColor = null, selectEditorColor = null } = useAvatarEditor();
|
||||
|
||||
const activeCategory = useMemo(() =>
|
||||
{
|
||||
return categories.find(category => category.setType === activeSetType) ?? null;
|
||||
}, [ categories, activeSetType ]);
|
||||
|
||||
const setGender = (gender: string) =>
|
||||
const selectSet = useCallback((setType: string) =>
|
||||
{
|
||||
//
|
||||
}
|
||||
const selectedPalettes = selectedColorParts[setType];
|
||||
|
||||
if(!selectedPalettes || !selectedPalettes.length) selectEditorColor(setType, 0, getFirstSelectableColor(setType));
|
||||
|
||||
setActiveSetType(setType);
|
||||
}, [ getFirstSelectableColor, selectEditorColor, selectedColorParts ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!activeCategory) return;
|
||||
if(!categories || !categories.length || !didChange) return;
|
||||
|
||||
// we need to run this when we change which parts r selected
|
||||
/* for(const partItem of activeCategory.partItems)
|
||||
{
|
||||
if(!partItem || !part.isSelected) continue;
|
||||
|
||||
setMaxPaletteCount(part.maxColorIndex || 1);
|
||||
|
||||
break;
|
||||
} */
|
||||
}, [ activeCategory ])
|
||||
selectSet(categories[0]?.setType);
|
||||
setDidChange(false);
|
||||
}, [ categories, didChange, selectSet ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!categories || !categories.length) return;
|
||||
|
||||
setActiveSetType(categories[0]?.setType)
|
||||
setDidChange(true);
|
||||
}, [ categories ]);
|
||||
|
||||
if(!activeCategory) return null;
|
||||
@ -55,17 +51,17 @@ export const AvatarEditorModelView: FC<{
|
||||
<Column size={ 2 }>
|
||||
{ (name === AvatarEditorFigureCategory.GENERIC) &&
|
||||
<>
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.MALE) }>
|
||||
<AvatarEditorIcon icon="male" selected={ false } />
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(AvatarFigurePartType.MALE) }>
|
||||
<AvatarEditorIcon icon="male" selected={ gender === FigureDataContainer.MALE } />
|
||||
</Flex>
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.FEMALE) }>
|
||||
<AvatarEditorIcon icon="female" selected={ false } />
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(AvatarFigurePartType.FEMALE) }>
|
||||
<AvatarEditorIcon icon="female" selected={ gender === FigureDataContainer.FEMALE } />
|
||||
</Flex>
|
||||
</> }
|
||||
{ (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category =>
|
||||
{
|
||||
return (
|
||||
<Flex center pointer key={ category.setType } className="category-item" onClick={ event => setActiveSetType(category.setType) }>
|
||||
<Flex center pointer key={ category.setType } className="category-item" onClick={ event => selectSet(category.setType) }>
|
||||
<AvatarEditorIcon icon={ category.setType } selected={ (activeSetType === category.setType) } />
|
||||
</Flex>
|
||||
);
|
@ -1,12 +1,12 @@
|
||||
import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetAvatarRenderManager, GetSessionDataManager, GetWardrobeMessageComposer, IAvatarFigureContainer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FaDice, FaTrash, FaUndo } from 'react-icons/fa';
|
||||
import { AvatarEditorAction, AvatarEditorUtilities, BodyModel, FigureData, GetClubMemberLevel, GetConfigurationValue, HeadModel, IAvatarEditorCategoryModel, LegModel, LocalizeText, SendMessageComposer, TorsoModel, generateRandomFigure } from '../../api';
|
||||
import { AddLinkEventTracker, AvatarEditorFigureCategory, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker, UserFigureComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FaDice, FaRedo, FaTrash } from 'react-icons/fa';
|
||||
import { AvatarEditorAction, LocalizeText, SendMessageComposer } from '../../api';
|
||||
import { Button, ButtonGroup, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||
import { useMessageEvent } from '../../hooks';
|
||||
import { AvatarEditorFigurePreviewView } from './views/AvatarEditorFigurePreviewView';
|
||||
import { AvatarEditorModelView } from './views/AvatarEditorModelView';
|
||||
import { AvatarEditorWardrobeView } from './views/AvatarEditorWardrobeView';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
import { AvatarEditorFigurePreviewView } from './AvatarEditorFigurePreviewView';
|
||||
import { AvatarEditorModelView } from './AvatarEditorModelView';
|
||||
import { AvatarEditorWardrobeView } from './AvatarEditorWardrobeView';
|
||||
|
||||
const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007';
|
||||
const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68';
|
||||
@ -14,141 +14,29 @@ const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62
|
||||
export const AvatarEditorView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ figures, setFigures ] = useState<Map<string, FigureData>>(null);
|
||||
const [ figureData, setFigureData ] = useState<FigureData>(null);
|
||||
const [ categories, setCategories ] = useState<Map<string, IAvatarEditorCategoryModel>>(null);
|
||||
const [ activeCategory, setActiveCategory ] = useState<IAvatarEditorCategoryModel>(null);
|
||||
const [ figureSetIds, setFigureSetIds ] = useState<number[]>([]);
|
||||
const [ boundFurnitureNames, setBoundFurnitureNames ] = useState<string[]>([]);
|
||||
const [ savedFigures, setSavedFigures ] = useState<[ IAvatarFigureContainer, string ][]>([]);
|
||||
const [ isWardrobeVisible, setIsWardrobeVisible ] = useState(false);
|
||||
const [ lastFigure, setLastFigure ] = useState<string>(null);
|
||||
const [ lastGender, setLastGender ] = useState<string>(null);
|
||||
const [ needsReset, setNeedsReset ] = useState(true);
|
||||
const [ isInitalized, setIsInitalized ] = useState(false);
|
||||
const { setIsVisible: setEditorVisibility, avatarModels, activeModelKey, setActiveModelKey, loadAvatarData, getFigureStringWithFace, gender, figureSetIds = [], randomizeCurrentFigure = null, getFigureString = null } = useAvatarEditor();
|
||||
|
||||
const maxWardrobeSlots = useMemo(() => GetConfigurationValue<number>('avatar.wardrobe.max.slots', 10), []);
|
||||
|
||||
useMessageEvent<FigureSetIdsMessageEvent>(FigureSetIdsMessageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setFigureSetIds(parser.figureSetIds);
|
||||
setBoundFurnitureNames(parser.boundsFurnitureNames);
|
||||
});
|
||||
|
||||
useMessageEvent<UserWardrobePageEvent>(UserWardrobePageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
const savedFigures: [ IAvatarFigureContainer, string ][] = [];
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < maxWardrobeSlots)
|
||||
{
|
||||
savedFigures.push([ null, null ]);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
for(let [ index, [ look, gender ] ] of parser.looks.entries())
|
||||
{
|
||||
const container = GetAvatarRenderManager().createFigureContainer(look);
|
||||
|
||||
savedFigures[(index - 1)] = [ container, gender ];
|
||||
}
|
||||
|
||||
setSavedFigures(savedFigures);
|
||||
});
|
||||
|
||||
const selectCategory = useCallback((name: string) =>
|
||||
{
|
||||
if(!categories) return;
|
||||
|
||||
setActiveCategory(categories.get(name));
|
||||
}, [ categories ]);
|
||||
|
||||
const resetCategories = useCallback(() =>
|
||||
{
|
||||
const categories = new Map();
|
||||
|
||||
categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel());
|
||||
categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel());
|
||||
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel());
|
||||
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel());
|
||||
|
||||
setCategories(categories);
|
||||
}, []);
|
||||
|
||||
const setupFigures = useCallback(() =>
|
||||
{
|
||||
const figures: Map<string, FigureData> = new Map();
|
||||
|
||||
const maleFigure = new FigureData();
|
||||
const femaleFigure = new FigureData();
|
||||
|
||||
maleFigure.loadAvatarData(DEFAULT_MALE_FIGURE, FigureData.MALE);
|
||||
femaleFigure.loadAvatarData(DEFAULT_FEMALE_FIGURE, FigureData.FEMALE);
|
||||
|
||||
figures.set(FigureData.MALE, maleFigure);
|
||||
figures.set(FigureData.FEMALE, femaleFigure);
|
||||
|
||||
setFigures(figures);
|
||||
setFigureData(figures.get(FigureData.MALE));
|
||||
}, []);
|
||||
|
||||
const loadAvatarInEditor = useCallback((figure: string, gender: string, reset: boolean = true) =>
|
||||
{
|
||||
gender = AvatarEditorUtilities.getGender(gender);
|
||||
|
||||
let newFigureData = figureData;
|
||||
|
||||
if(gender !== newFigureData.gender) newFigureData = figures.get(gender);
|
||||
|
||||
if(figure !== newFigureData.getFigureString()) newFigureData.loadAvatarData(figure, gender);
|
||||
|
||||
if(newFigureData !== figureData) setFigureData(newFigureData);
|
||||
|
||||
if(reset)
|
||||
{
|
||||
setLastFigure(figureData.getFigureString());
|
||||
setLastGender(figureData.gender);
|
||||
}
|
||||
}, [ figures, figureData ]);
|
||||
|
||||
const processAction = useCallback((action: string) =>
|
||||
const processAction = (action: string) =>
|
||||
{
|
||||
switch(action)
|
||||
{
|
||||
case AvatarEditorAction.ACTION_CLEAR:
|
||||
loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false);
|
||||
resetCategories();
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RESET:
|
||||
loadAvatarInEditor(lastFigure, lastGender);
|
||||
resetCategories();
|
||||
loadAvatarData(GetSessionDataManager().figure, GetSessionDataManager().gender);
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_CLEAR:
|
||||
loadAvatarData(getFigureStringWithFace(0, false), gender);
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RANDOMIZE:
|
||||
const figure = generateRandomFigure(figureData, figureData.gender, GetClubMemberLevel(), figureSetIds, [ FigureData.FACE ]);
|
||||
|
||||
loadAvatarInEditor(figure, figureData.gender, false);
|
||||
resetCategories();
|
||||
randomizeCurrentFigure();
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_SAVE:
|
||||
SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString()));
|
||||
SendMessageComposer(new UserFigureComposer(gender, getFigureString));
|
||||
setIsVisible(false);
|
||||
return;
|
||||
}
|
||||
}, [ figureData, lastFigure, lastGender, figureSetIds, loadAvatarInEditor, resetCategories ])
|
||||
}
|
||||
|
||||
const setGender = useCallback((gender: string) =>
|
||||
{
|
||||
gender = AvatarEditorUtilities.getGender(gender);
|
||||
|
||||
setFigureData(figures.get(gender));
|
||||
}, [ figures ]);
|
||||
|
||||
/* useEffect(() =>
|
||||
useEffect(() =>
|
||||
{
|
||||
const linkTracker: ILinkEventTracker = {
|
||||
linkReceived: (url: string) =>
|
||||
@ -176,126 +64,44 @@ export const AvatarEditorView: FC<{}> = props =>
|
||||
AddLinkEventTracker(linkTracker);
|
||||
|
||||
return () => RemoveLinkEventTracker(linkTracker);
|
||||
}, []); */
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setSavedFigures(new Array(maxWardrobeSlots));
|
||||
}, [ maxWardrobeSlots ]);
|
||||
setEditorVisibility(isVisible)
|
||||
}, [ isVisible, setEditorVisibility ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isWardrobeVisible) return;
|
||||
|
||||
setActiveCategory(null);
|
||||
SendMessageComposer(new GetWardrobeMessageComposer());
|
||||
}, [ isWardrobeVisible ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!activeCategory) return;
|
||||
|
||||
setIsWardrobeVisible(false);
|
||||
}, [ activeCategory ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!categories) return;
|
||||
|
||||
selectCategory(AvatarEditorFigureCategory.GENERIC);
|
||||
}, [ categories, selectCategory ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!figureData) return;
|
||||
|
||||
AvatarEditorUtilities.CURRENT_FIGURE = figureData;
|
||||
|
||||
resetCategories();
|
||||
|
||||
return () => AvatarEditorUtilities.CURRENT_FIGURE = null;
|
||||
}, [ figureData, resetCategories ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
AvatarEditorUtilities.FIGURE_SET_IDS = figureSetIds;
|
||||
AvatarEditorUtilities.BOUND_FURNITURE_NAMES = boundFurnitureNames;
|
||||
|
||||
resetCategories();
|
||||
|
||||
return () =>
|
||||
{
|
||||
AvatarEditorUtilities.FIGURE_SET_IDS = null;
|
||||
AvatarEditorUtilities.BOUND_FURNITURE_NAMES = null;
|
||||
}
|
||||
}, [ figureSetIds, boundFurnitureNames, resetCategories ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible) return;
|
||||
|
||||
if(!figures)
|
||||
{
|
||||
setupFigures();
|
||||
|
||||
setIsInitalized(true);
|
||||
|
||||
return;
|
||||
}
|
||||
}, [ isVisible, figures, setupFigures ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible || !isInitalized || !needsReset) return;
|
||||
|
||||
loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender);
|
||||
setNeedsReset(false);
|
||||
}, [ isVisible, isInitalized, needsReset, loadAvatarInEditor ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(isVisible) return;
|
||||
|
||||
return () =>
|
||||
{
|
||||
setNeedsReset(true);
|
||||
}
|
||||
}, [ isVisible ]);
|
||||
|
||||
if(!isVisible || !figureData) return null;
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView uniqueKey="avatar-editor" className="nitro-avatar-editor">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardTabsView>
|
||||
{ categories && (categories.size > 0) && Array.from(categories.keys()).map(category =>
|
||||
{ Object.keys(avatarModels).map(modelKey =>
|
||||
{
|
||||
const isActive = (activeCategory && (activeCategory.name === category));
|
||||
const isActive = (activeModelKey === modelKey);
|
||||
|
||||
return (
|
||||
<NitroCardTabsItemView key={ category } isActive={ isActive } onClick={ event => selectCategory(category) }>
|
||||
{ LocalizeText(`avatareditor.category.${ category }`) }
|
||||
<NitroCardTabsItemView key={ modelKey } isActive={ isActive } onClick={ event => setActiveModelKey(modelKey) }>
|
||||
{ LocalizeText(`avatareditor.category.${ modelKey }`) }
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
}) }
|
||||
<NitroCardTabsItemView isActive={ isWardrobeVisible } onClick={ event => setIsWardrobeVisible(true) }>
|
||||
{ LocalizeText('avatareditor.category.wardrobe') }
|
||||
</NitroCardTabsItemView>
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
<Grid>
|
||||
<Column size={ 9 } overflow="hidden">
|
||||
{ (activeCategory && !isWardrobeVisible) &&
|
||||
<AvatarEditorModelView model={ activeCategory } gender={ figureData.gender } setGender={ setGender } /> }
|
||||
{ isWardrobeVisible &&
|
||||
<AvatarEditorWardrobeView figureData={ figureData } savedFigures={ savedFigures } setSavedFigures={ setSavedFigures } loadAvatarInEditor={ loadAvatarInEditor } /> }
|
||||
{ ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) &&
|
||||
<AvatarEditorModelView name={ activeModelKey } categories={ avatarModels[activeModelKey] } /> }
|
||||
{ (activeModelKey === AvatarEditorFigureCategory.WARDROBE) &&
|
||||
<AvatarEditorWardrobeView /> }
|
||||
</Column>
|
||||
<Column size={ 3 } overflow="hidden">
|
||||
<AvatarEditorFigurePreviewView figureData={ figureData } />
|
||||
<AvatarEditorFigurePreviewView />
|
||||
<Column grow gap={ 1 }>
|
||||
<ButtonGroup>
|
||||
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
|
||||
<FaUndo className="fa-icon" />
|
||||
<FaRedo className="fa-icon" />
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_CLEAR) }>
|
||||
<FaTrash className="fa-icon" />
|
||||
|
@ -1,19 +1,12 @@
|
||||
import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react';
|
||||
import { FigureData, GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../api';
|
||||
import { AutoGrid, Base, Button, Flex, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../../common';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api';
|
||||
import { Base, Button, Flex, InfiniteGrid, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../common';
|
||||
import { useAvatarEditor } from '../../hooks';
|
||||
|
||||
export interface AvatarEditorWardrobeViewProps
|
||||
export const AvatarEditorWardrobeView: FC<{}> = props =>
|
||||
{
|
||||
figureData: FigureData;
|
||||
savedFigures: [ IAvatarFigureContainer, string ][];
|
||||
setSavedFigures: Dispatch<SetStateAction<[ IAvatarFigureContainer, string][]>>;
|
||||
loadAvatarInEditor: (figure: string, gender: string, reset?: boolean) => void;
|
||||
}
|
||||
|
||||
export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props =>
|
||||
{
|
||||
const { figureData = null, savedFigures = [], setSavedFigures = null, loadAvatarInEditor = null } = props;
|
||||
const { savedFigures = [], setSavedFigures = null, loadAvatarData = null, getFigureString = null, gender = null } = useAvatarEditor();
|
||||
|
||||
const hcDisabled = GetConfigurationValue<boolean>('hc.disabled', false);
|
||||
|
||||
@ -23,38 +16,34 @@ export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props
|
||||
|
||||
const [ figure, gender ] = savedFigures[index];
|
||||
|
||||
loadAvatarInEditor(figure.getFigureString(), gender);
|
||||
}, [ savedFigures, loadAvatarInEditor ]);
|
||||
loadAvatarData(figure.getFigureString(), gender);
|
||||
}, [ savedFigures, loadAvatarData ]);
|
||||
|
||||
const saveFigureAtWardrobeIndex = useCallback((index: number) =>
|
||||
{
|
||||
if(!figureData || (index >= savedFigures.length) || (index < 0)) return;
|
||||
if((index >= savedFigures.length) || (index < 0)) return;
|
||||
|
||||
const newFigures = [ ...savedFigures ];
|
||||
|
||||
const figure = figureData.getFigureString();
|
||||
const gender = figureData.gender;
|
||||
const figure = getFigureString;
|
||||
|
||||
newFigures[index] = [ GetAvatarRenderManager().createFigureContainer(figure), gender ];
|
||||
|
||||
setSavedFigures(newFigures);
|
||||
SendMessageComposer(new SaveWardrobeOutfitMessageComposer((index + 1), figure, gender));
|
||||
}, [ figureData, savedFigures, setSavedFigures ]);
|
||||
}, [ getFigureString, gender, savedFigures, setSavedFigures ]);
|
||||
|
||||
const figures = useMemo(() =>
|
||||
{
|
||||
if(!savedFigures || !savedFigures.length) return [];
|
||||
|
||||
const items: JSX.Element[] = [];
|
||||
|
||||
savedFigures.forEach(([ figureContainer, gender ], index) =>
|
||||
return (
|
||||
<InfiniteGrid rows={ savedFigures } columnCount={ 5 } overscan={ 5 } estimateSize={ 140 } itemRender={ (item: [ IAvatarFigureContainer, string ], index: number) =>
|
||||
{
|
||||
const [ figureContainer, gender ] = item;
|
||||
|
||||
let clubLevel = 0;
|
||||
|
||||
if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender);
|
||||
|
||||
items.push(
|
||||
<LayoutGridItem key={ index } position="relative" overflow="hidden" className="nitro-avatar-editor-wardrobe-figure-preview">
|
||||
return (
|
||||
<LayoutGridItem position="relative" overflow="hidden" className="nitro-avatar-editor-wardrobe-figure-preview">
|
||||
{ figureContainer &&
|
||||
<LayoutAvatarImageView figure={ figureContainer.getFigureString() } gender={ gender } direction={ 2 } /> }
|
||||
<Base className="avatar-shadow" />
|
||||
@ -65,15 +54,7 @@ export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props
|
||||
<Button variant="link" fullWidth onClick={ event => wearFigureAtIndex(index) } disabled={ (clubLevel > GetClubMemberLevel()) }>{ LocalizeText('widget.generic_usable.button.use') }</Button> }
|
||||
</Flex>
|
||||
</LayoutGridItem>
|
||||
);
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [ savedFigures, hcDisabled, saveFigureAtWardrobeIndex, wearFigureAtIndex ]);
|
||||
|
||||
return (
|
||||
<AutoGrid columnCount={ 5 } columnMinWidth={ 80 } columnMinHeight={ 140 }>
|
||||
{ figures }
|
||||
</AutoGrid>
|
||||
)
|
||||
} } />
|
||||
);
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import { AvatarFigurePartType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorThumbnailsHelper, FigureData, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
import { useAvatarEditor } from '../../../../hooks';
|
||||
import { AvatarEditorThumbnailsHelper, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../common';
|
||||
import { useAvatarEditor } from '../../../hooks';
|
||||
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
||||
|
||||
export const AvatarEditorFigureSetItemView: FC<{
|
||||
@ -26,7 +27,7 @@ export const AvatarEditorFigureSetItemView: FC<{
|
||||
|
||||
let url: string = null;
|
||||
|
||||
if(setType === FigureData.FACE)
|
||||
if(setType === AvatarFigurePartType.HEAD)
|
||||
{
|
||||
url = await AvatarEditorThumbnailsHelper.buildForFace(getFigureStringWithFace(partItem.id), isHC);
|
||||
}
|
||||
@ -44,7 +45,7 @@ export const AvatarEditorFigureSetItemView: FC<{
|
||||
if(!partItem) return null;
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : assetUrl) } itemActive={ isSelected } style={ { width: '100%', 'flex': '1' } } { ...rest }>
|
||||
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : assetUrl) } itemActive={ isSelected } style={ { width: '100%', flex: '1', backgroundPosition: (setType === AvatarFigurePartType.HEAD) ? 'center -35px' : 'center' } } { ...rest }>
|
||||
{ !partItem.isClear && isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
|
||||
{ !partItem.isClear && partItem.partSet.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
|
@ -1,7 +1,7 @@
|
||||
import { FC, useRef } from 'react';
|
||||
import { IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../../../api';
|
||||
import { InfiniteGrid } from '../../../../common';
|
||||
import { useAvatarEditor } from '../../../../hooks';
|
||||
import { FC } from 'react';
|
||||
import { IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../../api';
|
||||
import { InfiniteGrid } from '../../../common';
|
||||
import { useAvatarEditor } from '../../../hooks';
|
||||
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
|
||||
|
||||
export const AvatarEditorFigureSetView: FC<{
|
||||
@ -10,26 +10,30 @@ export const AvatarEditorFigureSetView: FC<{
|
||||
{
|
||||
const { category = null } = props;
|
||||
const { selectedParts = null, selectEditorPart } = useAvatarEditor();
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) =>
|
||||
{
|
||||
if(!category || !category.setType || !selectedParts || !selectedParts[category.setType]) return false;
|
||||
if(!category || !category.setType || !selectedParts) return false;
|
||||
|
||||
if(!selectedParts[category.setType])
|
||||
{
|
||||
if(partItem.isClear) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const partId = selectedParts[category.setType];
|
||||
|
||||
return (partId === partItem.id);
|
||||
}
|
||||
|
||||
const columnCount = 3;
|
||||
|
||||
return (
|
||||
<InfiniteGrid rows={ category.partItems } columnCount={ columnCount } overscan={ 5 } itemRender={ (item: IAvatarEditorCategoryPartItem) =>
|
||||
<InfiniteGrid rows={ category.partItems } columnCount={ 3 } overscan={ 5 } itemRender={ (item: IAvatarEditorCategoryPartItem) =>
|
||||
{
|
||||
if(!item) return null;
|
||||
|
||||
return (
|
||||
<AvatarEditorFigureSetItemView key={ item.id } setType={ category.setType } partItem={ item } isSelected={ isPartItemSelected(item) } onClick={ event => selectEditorPart(category.setType, item.partSet?.id ?? -1) } />
|
||||
<AvatarEditorFigureSetItemView setType={ category.setType } partItem={ item } isSelected={ isPartItemSelected(item) } onClick={ event => selectEditorPart(category.setType, item.partSet?.id ?? -1) } />
|
||||
)
|
||||
} } />
|
||||
);
|
@ -1,6 +1,7 @@
|
||||
export * from './AvatarEditorFigurePreviewView';
|
||||
export * from './AvatarEditorIcon';
|
||||
export * from './AvatarEditorModelView';
|
||||
export * from './AvatarEditorView';
|
||||
export * from './AvatarEditorWardrobeView';
|
||||
export * from './figure-set';
|
||||
export * from './palette-set';
|
@ -1,17 +1,13 @@
|
||||
import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { GetConfigurationValue } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
import { GetConfigurationValue } from '../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../common';
|
||||
|
||||
export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps
|
||||
{
|
||||
export const AvatarEditorPaletteSetItem: FC<{
|
||||
setType: string;
|
||||
partColor: IPartColor;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
// its disabled if its hc and you dont have it
|
||||
export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = props =>
|
||||
} & LayoutGridItemProps> = props =>
|
||||
{
|
||||
const { setType = null, partColor = null, isSelected = false, ...rest } = props;
|
||||
|
@ -0,0 +1,35 @@
|
||||
import { IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { IAvatarEditorCategory } from '../../../api';
|
||||
import { InfiniteGrid } from '../../../common';
|
||||
import { useAvatarEditor } from '../../../hooks';
|
||||
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
|
||||
|
||||
export const AvatarEditorPaletteSetView: FC<{
|
||||
category: IAvatarEditorCategory,
|
||||
paletteIndex: number;
|
||||
}> = props =>
|
||||
{
|
||||
const { category = null, paletteIndex = -1 } = props;
|
||||
const { selectedColorParts = null, selectEditorColor = null } = useAvatarEditor();
|
||||
|
||||
const isPartColorSelected = (partColor: IPartColor) =>
|
||||
{
|
||||
if(!category || !category.setType || !selectedColorParts || !selectedColorParts[category.setType] || !selectedColorParts[category.setType][paletteIndex]) return false;
|
||||
|
||||
const selectedColorPart = selectedColorParts[category.setType][paletteIndex];
|
||||
|
||||
return (selectedColorPart.id === partColor.id);
|
||||
}
|
||||
|
||||
return (
|
||||
<InfiniteGrid rows={ category.colorItems[paletteIndex] } columnCount={ 5 } overscan={ 5 } itemRender={ (item: IPartColor) =>
|
||||
{
|
||||
if(!item) return null;
|
||||
|
||||
return (
|
||||
<AvatarEditorPaletteSetItem setType={ category.setType } partColor={ item } isSelected={ isPartColorSelected(item) } onClick={ event => selectEditorColor(category.setType, paletteIndex, item.id) } />
|
||||
)
|
||||
} } />
|
||||
);
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
import { AvatarDirectionAngle } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FigureData } from '../../../api';
|
||||
import { Base, Column, LayoutAvatarImageView } from '../../../common';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
|
||||
export interface AvatarEditorFigurePreviewViewProps
|
||||
{
|
||||
figureData: FigureData;
|
||||
}
|
||||
|
||||
export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProps> = props =>
|
||||
{
|
||||
const { figureData = null } = props;
|
||||
const [ updateId, setUpdateId ] = useState(-1);
|
||||
|
||||
const rotateFigure = (direction: number) =>
|
||||
{
|
||||
if(direction < AvatarDirectionAngle.MIN_DIRECTION)
|
||||
{
|
||||
direction = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1));
|
||||
}
|
||||
|
||||
if(direction > AvatarDirectionAngle.MAX_DIRECTION)
|
||||
{
|
||||
direction = (direction - (AvatarDirectionAngle.MAX_DIRECTION + 1));
|
||||
}
|
||||
|
||||
figureData.direction = direction;
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!figureData) return;
|
||||
|
||||
figureData.notify = () => setUpdateId(prevValue => (prevValue + 1));
|
||||
|
||||
return () =>
|
||||
{
|
||||
figureData.notify = null;
|
||||
}
|
||||
}, [ figureData ] );
|
||||
|
||||
return (
|
||||
<Column className="figure-preview-container" overflow="hidden" position="relative">
|
||||
<LayoutAvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } />
|
||||
<AvatarEditorIcon className="avatar-spotlight" icon="spotlight" />
|
||||
<Base className="avatar-shadow" />
|
||||
<Base className="arrow-container">
|
||||
<AvatarEditorIcon pointer icon="arrow-left" onClick={ event => rotateFigure(figureData.direction + 1) } />
|
||||
<AvatarEditorIcon pointer icon="arrow-right" onClick={ event => rotateFigure(figureData.direction - 1) } />
|
||||
</Base>
|
||||
</Column>
|
||||
);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../../../common';
|
||||
|
||||
type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string;
|
||||
|
||||
export interface AvatarEditorIconProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
icon: AvatarIconType;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export const AvatarEditorIcon: FC<AvatarEditorIconProps> = props =>
|
||||
{
|
||||
const { icon = null, selected = false, classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'nitro-avatar-editor-spritesheet' ];
|
||||
|
||||
if(icon && icon.length) newClassNames.push(icon + '-icon');
|
||||
|
||||
if(selected) newClassNames.push('selected');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ icon, selected, classNames ]);
|
||||
|
||||
return <Base classNames={ getClassNames } { ...rest } />
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react';
|
||||
import { CategoryData, FigureData, IAvatarEditorCategoryModel } from '../../../api';
|
||||
import { Column, Flex, Grid } from '../../../common';
|
||||
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||
import { AvatarEditorFigureSetView } from './figure-set/AvatarEditorFigureSetView';
|
||||
import { AvatarEditorPaletteSetView } from './palette-set/AvatarEditorPaletteSetView';
|
||||
export interface AvatarEditorModelViewProps
|
||||
{
|
||||
model: IAvatarEditorCategoryModel;
|
||||
gender: string;
|
||||
setGender: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
|
||||
{
|
||||
const { model = null, gender = null, setGender = null } = props;
|
||||
const [ activeCategory, setActiveCategory ] = useState<CategoryData>(null);
|
||||
const [ maxPaletteCount, setMaxPaletteCount ] = useState(1);
|
||||
|
||||
const selectCategory = useCallback((name: string) =>
|
||||
{
|
||||
const category = model.categories.get(name);
|
||||
|
||||
if(!category) return;
|
||||
|
||||
category.init();
|
||||
|
||||
setActiveCategory(category);
|
||||
|
||||
for(const part of category.parts)
|
||||
{
|
||||
if(!part || !part.isSelected) continue;
|
||||
|
||||
setMaxPaletteCount(part.maxColorIndex || 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}, [ model ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
model.init();
|
||||
|
||||
for(const name of model.categories.keys())
|
||||
{
|
||||
selectCategory(name);
|
||||
|
||||
break;
|
||||
}
|
||||
}, [ model, selectCategory ]);
|
||||
|
||||
if(!model || !activeCategory) return null;
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column size={ 2 }>
|
||||
{ model.canSetGender &&
|
||||
<>
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.MALE) }>
|
||||
<AvatarEditorIcon icon="male" selected={ (gender === FigureData.MALE) } />
|
||||
</Flex>
|
||||
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.FEMALE) }>
|
||||
<AvatarEditorIcon icon="female" selected={ (gender === FigureData.FEMALE) } />
|
||||
</Flex>
|
||||
</> }
|
||||
{ !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name =>
|
||||
{
|
||||
const category = model.categories.get(name);
|
||||
|
||||
return (
|
||||
<Flex center pointer key={ name } className="category-item" onClick={ event => selectCategory(name) }>
|
||||
<AvatarEditorIcon icon={ category.name } selected={ (activeCategory === category) } />
|
||||
</Flex>
|
||||
);
|
||||
}) }
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
<AvatarEditorFigureSetView model={ model } category={ activeCategory } setMaxPaletteCount={ setMaxPaletteCount } />
|
||||
</Column>
|
||||
<Column size={ 5 } overflow="hidden">
|
||||
{ (maxPaletteCount >= 1) &&
|
||||
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(0) } paletteIndex={ 0 } /> }
|
||||
{ (maxPaletteCount === 2) &&
|
||||
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(1) } paletteIndex={ 1 } /> }
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorGridPartItem, GetConfigurationValue } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
||||
|
||||
export interface AvatarEditorFigureSetItemViewProps extends LayoutGridItemProps
|
||||
{
|
||||
partItem: AvatarEditorGridPartItem;
|
||||
}
|
||||
|
||||
export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProps> = props =>
|
||||
{
|
||||
const { partItem = null, children = null, ...rest } = props;
|
||||
const [ updateId, setUpdateId ] = useState(-1);
|
||||
|
||||
const hcDisabled = GetConfigurationValue<boolean>('hc.disabled', false);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
|
||||
|
||||
partItem.notify = rerender;
|
||||
|
||||
return () => partItem.notify = null;
|
||||
}, [ partItem ]);
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } { ...rest }>
|
||||
{ !hcDisabled && partItem.isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
|
||||
{ partItem.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
|
||||
{ children }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from 'react';
|
||||
import { AvatarEditorGridPartItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api';
|
||||
import { AutoGrid } from '../../../../common';
|
||||
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
|
||||
|
||||
export interface AvatarEditorFigureSetViewProps
|
||||
{
|
||||
model: IAvatarEditorCategoryModel;
|
||||
category: CategoryData;
|
||||
setMaxPaletteCount: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props =>
|
||||
{
|
||||
const { model = null, category = null, setMaxPaletteCount = null } = props;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const selectPart = useCallback((item: AvatarEditorGridPartItem) =>
|
||||
{
|
||||
const index = category.parts.indexOf(item);
|
||||
|
||||
if(index === -1) return;
|
||||
|
||||
model.selectPart(category.name, index);
|
||||
|
||||
const partItem = category.getCurrentPart();
|
||||
|
||||
setMaxPaletteCount(partItem.maxColorIndex || 1);
|
||||
}, [ model, category, setMaxPaletteCount ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!model || !category || !elementRef || !elementRef.current) return;
|
||||
|
||||
elementRef.current.scrollTop = 0;
|
||||
}, [ model, category ]);
|
||||
|
||||
return (
|
||||
<AutoGrid innerRef={ elementRef } columnCount={ 3 } columnMinHeight={ 50 }>
|
||||
{ (category.parts.length > 0) && category.parts.map((item, index) =>
|
||||
<AvatarEditorFigureSetItemView key={ index } partItem={ item } onClick={ event => selectPart(item) } />) }
|
||||
</AutoGrid>
|
||||
);
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
export * from './AvatarEditorFigureSetItemView';
|
||||
export * from './AvatarEditorFigureSetView';
|
@ -1,32 +0,0 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorGridColorItem, GetConfigurationValue } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
|
||||
export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps
|
||||
{
|
||||
colorItem: AvatarEditorGridColorItem;
|
||||
}
|
||||
|
||||
export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = props =>
|
||||
{
|
||||
const { colorItem = null, children = null, ...rest } = props;
|
||||
const [ updateId, setUpdateId ] = useState(-1);
|
||||
|
||||
const hcDisabled = GetConfigurationValue<boolean>('hc.disabled', false);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
|
||||
|
||||
colorItem.notify = rerender;
|
||||
|
||||
return () => colorItem.notify = null;
|
||||
}, [ colorItem ]);
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemHighlight itemColor={ colorItem.color } itemActive={ colorItem.isSelected } className="clear-bg" { ...rest }>
|
||||
{ !hcDisabled && colorItem.isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||
{ children }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { FC, useCallback, useEffect, useRef } from 'react';
|
||||
import { AvatarEditorGridColorItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api';
|
||||
import { AutoGrid } from '../../../../common';
|
||||
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
|
||||
|
||||
export interface AvatarEditorPaletteSetViewProps
|
||||
{
|
||||
model: IAvatarEditorCategoryModel;
|
||||
category: CategoryData;
|
||||
paletteSet: AvatarEditorGridColorItem[];
|
||||
paletteIndex: number;
|
||||
}
|
||||
|
||||
export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = props =>
|
||||
{
|
||||
const { model = null, category = null, paletteSet = [], paletteIndex = -1 } = props;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const selectColor = useCallback((item: AvatarEditorGridColorItem) =>
|
||||
{
|
||||
const index = paletteSet.indexOf(item);
|
||||
|
||||
if(index === -1) return;
|
||||
|
||||
model.selectColor(category.name, index, paletteIndex);
|
||||
}, [ model, category, paletteSet, paletteIndex ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!model || !category || !elementRef || !elementRef.current) return;
|
||||
|
||||
elementRef.current.scrollTop = 0;
|
||||
}, [ model, category ]);
|
||||
|
||||
return (
|
||||
<AutoGrid innerRef={ elementRef } gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
|
||||
{ (paletteSet.length > 0) && paletteSet.map((item, index) =>
|
||||
<AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) }
|
||||
</AutoGrid>
|
||||
);
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
export * from './AvatarEditorPaletteSetItemView';
|
||||
export * from './AvatarEditorPaletteSetView';
|
@ -3,8 +3,7 @@ import { FC, useEffect, useState } from 'react';
|
||||
import { Base, TransitionAnimation, TransitionAnimationTypes } from '../../common';
|
||||
import { useNitroEvent } from '../../hooks';
|
||||
import { AchievementsView } from '../achievements/AchievementsView';
|
||||
import { AvatarEditorNewView } from '../avatar-editor-new/AvatarEditorView';
|
||||
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
|
||||
import { AvatarEditorView } from '../avatar-editor';
|
||||
import { CameraWidgetView } from '../camera/CameraWidgetView';
|
||||
import { CampaignView } from '../campaign/CampaignView';
|
||||
import { CatalogView } from '../catalog/CatalogView';
|
||||
@ -90,7 +89,6 @@ export const MainView: FC<{}> = props =>
|
||||
<ChatHistoryView />
|
||||
<WiredView />
|
||||
<AvatarEditorView />
|
||||
<AvatarEditorNewView />
|
||||
<AchievementsView />
|
||||
<NavigatorView />
|
||||
<InventoryView />
|
||||
|
@ -6,13 +6,13 @@ export const GetBubbleLayout = (item: NotificationBubbleItem, onClose: () => voi
|
||||
{
|
||||
if(!item) return null;
|
||||
|
||||
const props = { key: item.id, item, onClose };
|
||||
const props = { item, onClose };
|
||||
|
||||
switch(item.notificationType)
|
||||
{
|
||||
case NotificationBubbleType.CLUBGIFT:
|
||||
return <NotificationClubGiftBubbleView { ...props } />
|
||||
return <NotificationClubGiftBubbleView key={ item.id } { ...props } />
|
||||
default:
|
||||
return <NotificationDefaultBubbleView { ...props } />
|
||||
return <NotificationDefaultBubbleView key={ item.id } { ...props } />
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { GetAvatarRenderManager, GetSessionDataManager, RedeemItemClothingComposer, RoomObjectCategory, UserFigureComposer } from '@nitrots/nitro-renderer';
|
||||
import { AvatarFigurePartType, GetAvatarRenderManager, GetSessionDataManager, RedeemItemClothingComposer, RoomObjectCategory, UserFigureComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FigureData, FurniCategory, GetFurnitureDataForRoomObject, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { FurniCategory, GetFurnitureDataForRoomObject, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Base, Button, Column, Flex, LayoutAvatarImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../common';
|
||||
import { useRoom } from '../../../../../hooks';
|
||||
|
||||
@ -17,7 +17,7 @@ export const PurchasableClothingConfirmView: FC<PurchasableClothingConfirmViewPr
|
||||
{
|
||||
const { objectId = -1, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(MODE_DEFAULT);
|
||||
const [ gender, setGender ] = useState<string>(FigureData.MALE);
|
||||
const [ gender, setGender ] = useState<string>(AvatarFigurePartType.MALE);
|
||||
const [ newFigure, setNewFigure ] = useState<string>(null);
|
||||
const { roomSession = null } = useRoom();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetAvatarRenderManager, GetSessionDataManager, IFigurePartSet, IPartColor } from '@nitrots/nitro-renderer';
|
||||
import { AvatarEditorFigureCategory, AvatarFigureContainer, AvatarFigurePartType, FigureSetIdsMessageEvent, GetAvatarRenderManager, GetSessionDataManager, GetWardrobeMessageComposer, IAvatarFigureContainer, IFigurePartSet, IPalette, IPartColor, SetType, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useBetween } from 'use-between';
|
||||
import { AvatarEditorThumbnailsHelper, FigureData, GetClubMemberLevel, IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../api';
|
||||
import { AvatarEditorColorSorter, AvatarEditorPartSorter, AvatarEditorThumbnailsHelper, GetClubMemberLevel, GetConfigurationValue, IAvatarEditorCategory, IAvatarEditorCategoryPartItem, Randomizer, SendMessageComposer } from '../../api';
|
||||
import { useMessageEvent } from '../events';
|
||||
import { useFigureData } from './useFigureData';
|
||||
|
||||
@ -15,7 +15,8 @@ const useAvatarEditorState = () =>
|
||||
const [ maxPaletteCount, setMaxPaletteCount ] = useState<number>(1);
|
||||
const [ figureSetIds, setFigureSetIds ] = useState<number[]>([]);
|
||||
const [ boundFurnitureNames, setBoundFurnitureNames ] = useState<string[]>([]);
|
||||
const { gender, selectedParts, selectedColors, loadAvatarData, selectPart, selectColor, getFigureStringWithFace } = useFigureData();
|
||||
const [ savedFigures, setSavedFigures ] = useState<[ IAvatarFigureContainer, string ][]>(null);
|
||||
const { selectedColors, gender, setGender, loadAvatarData, selectPart, selectColor, getFigureString, getFigureStringWithFace, selectedParts } = useFigureData();
|
||||
|
||||
const activeModel = useMemo(() => (avatarModels[activeModelKey] ?? null), [ activeModelKey, avatarModels ]);
|
||||
|
||||
@ -57,7 +58,8 @@ const useAvatarEditorState = () =>
|
||||
|
||||
if(partItem.isClear)
|
||||
{
|
||||
// clear the part
|
||||
selectPart(setType, -1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -89,6 +91,109 @@ const useAvatarEditorState = () =>
|
||||
selectColor(setType, paletteId, colorId);
|
||||
}, [ activeModel, selectColor ]);
|
||||
|
||||
const getFirstSelectableColor = useCallback((setType: string) =>
|
||||
{
|
||||
const set = GetAvatarRenderManager().structureData.getSetType(setType);
|
||||
|
||||
if(!setType) return -1;
|
||||
|
||||
const palette = GetAvatarRenderManager().structureData.getPalette(set.paletteID);
|
||||
|
||||
if(!palette) return -1;
|
||||
|
||||
for(const color of palette.colors.getValues())
|
||||
{
|
||||
if(!color.isSelectable || (GetClubMemberLevel() < color.clubLevel)) continue;
|
||||
|
||||
return color.id;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}, []);
|
||||
|
||||
const randomizeCurrentFigure = useCallback((ignoredSets: string[] = []) =>
|
||||
{
|
||||
const structure = GetAvatarRenderManager().structure;
|
||||
const figureContainer = new AvatarFigureContainer('');
|
||||
|
||||
const getRandomSetTypes = (requiredSets: string[], options: string[]) =>
|
||||
{
|
||||
options = options.filter(option => (requiredSets.indexOf(option) === -1));
|
||||
|
||||
return [ ...requiredSets, ...Randomizer.getRandomElements(options, (Randomizer.getRandomNumber(options.length) + 1)) ];
|
||||
}
|
||||
|
||||
const requiredSets = getRandomSetTypes(structure.getMandatorySetTypeIds(gender, GetClubMemberLevel()), AvatarFigurePartType.FIGURE_SETS);
|
||||
|
||||
const getRandomPartSet = (setType: SetType, gender: string, clubLevel: number, figureSetIds: number[]) =>
|
||||
{
|
||||
const options = setType.partSets.getValues().filter(option =>
|
||||
{
|
||||
if(!option.isSelectable || ((option.gender !== 'U') && (option.gender !== gender)) || (option.clubLevel > clubLevel) || (option.isSellable && (figureSetIds.indexOf(option.id) === -1))) return null;
|
||||
|
||||
return option;
|
||||
});
|
||||
|
||||
if(!options || !options.length) return null;
|
||||
|
||||
return Randomizer.getRandomElement(options);
|
||||
}
|
||||
|
||||
const getRandomColors = (palette: IPalette, partSet: IFigurePartSet, clubLevel: number) =>
|
||||
{
|
||||
const options = palette.colors.getValues().filter(option =>
|
||||
{
|
||||
if(!option.isSelectable || (option.clubLevel > clubLevel)) return null;
|
||||
|
||||
return option;
|
||||
});
|
||||
|
||||
if(!options || !options.length) return null;
|
||||
|
||||
const getTotalColors = (partSet: IFigurePartSet) =>
|
||||
{
|
||||
const parts = partSet.parts;
|
||||
|
||||
let totalColors = 0;
|
||||
|
||||
for(const part of parts) totalColors = Math.max(totalColors, part.colorLayerIndex);
|
||||
|
||||
return totalColors;
|
||||
}
|
||||
|
||||
return Randomizer.getRandomElements(options, getTotalColors(partSet));
|
||||
}
|
||||
|
||||
for(const setType of ignoredSets)
|
||||
{
|
||||
const partSetId = selectedParts[setType];
|
||||
const colors = selectedColors[setType];
|
||||
|
||||
figureContainer.updatePart(setType, partSetId, colors);
|
||||
}
|
||||
|
||||
for(const type of requiredSets)
|
||||
{
|
||||
if(figureContainer.hasPartType(type)) continue;
|
||||
|
||||
const setType = (structure.figureData.getSetType(type) as SetType);
|
||||
const selectedSet = getRandomPartSet(setType, gender, GetClubMemberLevel(), figureSetIds);
|
||||
|
||||
if(!selectedSet) continue;
|
||||
|
||||
let selectedColors: number[] = [];
|
||||
|
||||
if(selectedSet.isColorable)
|
||||
{
|
||||
selectedColors = getRandomColors(structure.figureData.getPalette(setType.paletteID), selectedSet, GetClubMemberLevel()).map(color => color.id);
|
||||
}
|
||||
|
||||
figureContainer.updatePart(setType.type, selectedSet.id, selectedColors);
|
||||
}
|
||||
|
||||
loadAvatarData(figureContainer.getFigureString(), gender);
|
||||
}, [ figureSetIds, gender, loadAvatarData, selectedColors, selectedParts ]);
|
||||
|
||||
useMessageEvent<FigureSetIdsMessageEvent>(FigureSetIdsMessageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
@ -97,6 +202,30 @@ const useAvatarEditorState = () =>
|
||||
setBoundFurnitureNames(parser.boundsFurnitureNames);
|
||||
});
|
||||
|
||||
useMessageEvent<UserWardrobePageEvent>(UserWardrobePageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
const savedFigures: [ IAvatarFigureContainer, string ][] = [];
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < GetConfigurationValue<number>('avatar.wardrobe.max.slots', 10))
|
||||
{
|
||||
savedFigures.push([ null, null ]);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
for(let [ index, [ look, gender ] ] of parser.looks.entries())
|
||||
{
|
||||
const container = GetAvatarRenderManager().createFigureContainer(look);
|
||||
|
||||
savedFigures[(index - 1)] = [ container, gender ];
|
||||
}
|
||||
|
||||
setSavedFigures(savedFigures);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
AvatarEditorThumbnailsHelper.clearCache();
|
||||
@ -125,19 +254,6 @@ const useAvatarEditorState = () =>
|
||||
if(!partColor || !partColor.isSelectable) continue;
|
||||
|
||||
for(let i = 0; i < MAX_PALETTES; i++) colorItems[i].push(partColor);
|
||||
|
||||
// TODO - check what this does
|
||||
/* if(setType !== FigureData.FACE)
|
||||
{
|
||||
let i = 0;
|
||||
|
||||
while(i < colorIds.length)
|
||||
{
|
||||
if(partColor.id === colorIds[i]) partColors[i] = partColor;
|
||||
|
||||
i++;
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
let mandatorySetIds: string[] = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(gender, GetClubMemberLevel());
|
||||
@ -146,14 +262,14 @@ const useAvatarEditorState = () =>
|
||||
|
||||
if(isntMandatorySet) partItems.push({ id: -1, isClear: true });
|
||||
|
||||
const usesColor = (setType !== FigureData.FACE);
|
||||
const usesColor = (setType !== AvatarFigurePartType.HEAD);
|
||||
const partSets = set.partSets;
|
||||
|
||||
for(let i = (partSets.length); i >= 0; i--)
|
||||
{
|
||||
const partSet = partSets.getWithIndex(i);
|
||||
|
||||
if(!partSet || !partSet.isSelectable || ((partSet.gender !== gender) && (partSet.gender !== FigureData.UNISEX))) continue;
|
||||
if(!partSet || !partSet.isSelectable || ((partSet.gender !== gender) && (partSet.gender !== AvatarFigurePartType.UNISEX))) continue;
|
||||
|
||||
if(partSet.isSellable && figureSetIds.indexOf(partSet.id) === -1) continue;
|
||||
|
||||
@ -164,17 +280,17 @@ const useAvatarEditorState = () =>
|
||||
partItems.push({ id: partSet.id, partSet, usesColor, maxPaletteCount });
|
||||
}
|
||||
|
||||
partItems.sort(partSorter(false));
|
||||
partItems.sort(AvatarEditorPartSorter(false));
|
||||
|
||||
for(let i = 0; i < MAX_PALETTES; i++) colorItems[i].sort(colorSorter);
|
||||
for(let i = 0; i < MAX_PALETTES; i++) colorItems[i].sort(AvatarEditorColorSorter);
|
||||
|
||||
return { setType, partItems, colorItems };
|
||||
}
|
||||
|
||||
newAvatarModels[AvatarEditorFigureCategory.GENERIC] = [ FigureData.FACE ].map(setType => buildCategory(setType));
|
||||
newAvatarModels[AvatarEditorFigureCategory.HEAD] = [ FigureData.HAIR, FigureData.HAT, FigureData.HEAD_ACCESSORIES, FigureData.EYE_ACCESSORIES, FigureData.FACE_ACCESSORIES ].map(setType => buildCategory(setType));
|
||||
newAvatarModels[AvatarEditorFigureCategory.TORSO] = [ FigureData.SHIRT, FigureData.CHEST_PRINTS, FigureData.JACKET, FigureData.CHEST_ACCESSORIES ].map(setType => buildCategory(setType));
|
||||
newAvatarModels[AvatarEditorFigureCategory.LEGS] = [ FigureData.TROUSERS, FigureData.SHOES, FigureData.TROUSER_ACCESSORIES ].map(setType => buildCategory(setType));
|
||||
newAvatarModels[AvatarEditorFigureCategory.GENERIC] = [ AvatarFigurePartType.HEAD ].map(setType => buildCategory(setType));
|
||||
newAvatarModels[AvatarEditorFigureCategory.HEAD] = [ AvatarFigurePartType.HAIR, AvatarFigurePartType.HEAD_ACCESSORY, AvatarFigurePartType.HEAD_ACCESSORY_EXTRA, AvatarFigurePartType.EYE_ACCESSORY, AvatarFigurePartType.FACE_ACCESSORY ].map(setType => buildCategory(setType));
|
||||
newAvatarModels[AvatarEditorFigureCategory.TORSO] = [ AvatarFigurePartType.CHEST, AvatarFigurePartType.CHEST_PRINT, AvatarFigurePartType.COAT_CHEST, AvatarFigurePartType.CHEST_ACCESSORY ].map(setType => buildCategory(setType));
|
||||
newAvatarModels[AvatarEditorFigureCategory.LEGS] = [ AvatarFigurePartType.LEGS, AvatarFigurePartType.SHOES, AvatarFigurePartType.WAIST_ACCESSORY ].map(setType => buildCategory(setType));
|
||||
newAvatarModels[AvatarEditorFigureCategory.WARDROBE] = [];
|
||||
|
||||
setAvatarModels(newAvatarModels);
|
||||
@ -188,57 +304,15 @@ const useAvatarEditorState = () =>
|
||||
loadAvatarData(GetSessionDataManager().figure, GetSessionDataManager().gender);
|
||||
}, [ isVisible, loadAvatarData ]);
|
||||
|
||||
return { isVisible, setIsVisible, avatarModels, activeModelKey, setActiveModelKey, selectedParts, selectedColors, maxPaletteCount, selectedColorParts, selectEditorPart, selectEditorColor, getFigureStringWithFace };
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible || savedFigures) return;
|
||||
|
||||
setSavedFigures(new Array(GetConfigurationValue<number>('avatar.wardrobe.max.slots', 10)));
|
||||
SendMessageComposer(new GetWardrobeMessageComposer());
|
||||
}, [ isVisible, savedFigures ]);
|
||||
|
||||
return { isVisible, setIsVisible, avatarModels, activeModelKey, setActiveModelKey, maxPaletteCount, selectedColorParts, selectEditorColor, selectEditorPart, loadAvatarData, getFigureString, getFigureStringWithFace, selectedParts, gender, setGender, figureSetIds, randomizeCurrentFigure, savedFigures, setSavedFigures, getFirstSelectableColor };
|
||||
}
|
||||
|
||||
export const useAvatarEditor = () => useBetween(useAvatarEditorState);
|
||||
|
||||
const partSorter = (hcFirst: boolean) =>
|
||||
{
|
||||
return (a: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }, b: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }) =>
|
||||
{
|
||||
const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel);
|
||||
const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel);
|
||||
const isSellableA = (!a.partSet ? false : a.partSet.isSellable);
|
||||
const isSellableB = (!b.partSet ? false : b.partSet.isSellable);
|
||||
|
||||
if(isSellableA && !isSellableB) return 1;
|
||||
|
||||
if(isSellableB && !isSellableA) return -1;
|
||||
|
||||
if(hcFirst)
|
||||
{
|
||||
if(clubLevelA > clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA < clubLevelB) return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(clubLevelA < clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA > clubLevelB) return 1;
|
||||
}
|
||||
|
||||
if(a.partSet.id < b.partSet.id) return -1;
|
||||
|
||||
if(a.partSet.id > b.partSet.id) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const colorSorter = (a: IPartColor, b: IPartColor) =>
|
||||
{
|
||||
const clubLevelA = (!a ? -1 : a.clubLevel);
|
||||
const clubLevelB = (!b ? -1 : b.clubLevel);
|
||||
|
||||
if(clubLevelA < clubLevelB) return -1;
|
||||
|
||||
if(clubLevelA > clubLevelB) return 1;
|
||||
|
||||
if(a.index < b.index) return -1;
|
||||
|
||||
if(a.index > b.index) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { FigureData } from '../../api';
|
||||
import { AvatarFigurePartType } from '@nitrots/nitro-renderer';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
const useFigureDataState = () =>
|
||||
{
|
||||
const [ selectedParts, setSelectedParts ] = useState<{ [index: string]: number }>({});
|
||||
const [ selectedColors, setSelectedColors ] = useState<{ [index: string]: number[] }>({});
|
||||
const [ gender, setGender ] = useState<string>(FigureData.MALE);
|
||||
const [ gender, setGender ] = useState<string>(AvatarFigurePartType.MALE);
|
||||
|
||||
const loadAvatarData = useCallback((figureString: string, gender: string) =>
|
||||
{
|
||||
@ -62,7 +62,8 @@ const useFigureDataState = () =>
|
||||
{
|
||||
const newValue = { ...prevValue };
|
||||
|
||||
newValue[setType] = partId;
|
||||
if(partId === -1) delete newValue[setType];
|
||||
else newValue[setType] = partId;
|
||||
|
||||
return newValue;
|
||||
});
|
||||
@ -86,29 +87,64 @@ const useFigureDataState = () =>
|
||||
})
|
||||
}, []);
|
||||
|
||||
const getFigureString = useMemo(() =>
|
||||
{
|
||||
let figureString = '';
|
||||
|
||||
const partSets: string[] = [];
|
||||
const setTypes = Object.keys(selectedParts);
|
||||
|
||||
for(const setType of setTypes)
|
||||
{
|
||||
const partId = selectedParts[setType];
|
||||
|
||||
if(!partId) continue;
|
||||
|
||||
let setPart = `${ setType }-${ partId }`;
|
||||
|
||||
if(selectedColors[setType] && selectedColors[setType].length)
|
||||
{
|
||||
let i = 0;
|
||||
|
||||
while(i < selectedColors[setType].length)
|
||||
{
|
||||
setPart += `-${ selectedColors[setType][i] }`;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
partSets.push(setPart);
|
||||
}
|
||||
|
||||
for(const partSet of partSets)
|
||||
{
|
||||
figureString += partSet;
|
||||
|
||||
if(partSets.indexOf(partSet) < (partSets.length - 1)) figureString += '.';
|
||||
}
|
||||
|
||||
return figureString;
|
||||
}, [ selectedParts, selectedColors ]);
|
||||
|
||||
const getFigureStringWithFace = useCallback((overridePartId: number, override: boolean = true) =>
|
||||
{
|
||||
const figureSets = [ FigureData.FACE ].map(setType =>
|
||||
const figureSets = [ AvatarFigurePartType.HEAD ].map(setType =>
|
||||
{
|
||||
// Determine the part ID, with an option to override if the set type matches.
|
||||
let partId = (setType === FigureData.FACE && override) ? overridePartId : selectedParts[setType];
|
||||
let partId = (setType === AvatarFigurePartType.HEAD && override) ? overridePartId : selectedParts[setType];
|
||||
const colors = selectedColors[setType] || [];
|
||||
|
||||
// Construct the figure set string, including the type, part ID, and any colors.
|
||||
let figureSet = `${ setType }-${ partId }`;
|
||||
if (partId >= 0)
|
||||
{
|
||||
figureSet += colors.map(color => `-${ color }`).join('');
|
||||
}
|
||||
|
||||
if (partId >= 0) figureSet += colors.map(color => `-${ color }`).join('');
|
||||
|
||||
return figureSet;
|
||||
});
|
||||
|
||||
// Join all figure sets with '.', ensuring to only add '.' between items, not at the end.
|
||||
|
||||
return figureSets.join('.');
|
||||
}, [ selectedParts, selectedColors ]);
|
||||
|
||||
return { selectedParts, selectedColors, gender, loadAvatarData, selectPart, selectColor, getFigureStringWithFace };
|
||||
return { selectedParts, selectedColors, gender, setGender, loadAvatarData, selectPart, selectColor, getFigureString, getFigureStringWithFace };
|
||||
}
|
||||
|
||||
export const useFigureData = useFigureDataState;
|
||||
|
Loading…
Reference in New Issue
Block a user