Avatar randomizer

This commit is contained in:
Bill 2021-08-04 02:57:00 -04:00
parent f88efac443
commit 4370b20af7
6 changed files with 184 additions and 15 deletions

28
src/utils/Randomizer.ts Normal file
View File

@ -0,0 +1,28 @@
export class Randomizer
{
public static getRandomNumber(count: number): number
{
return Math.floor(Math.random() * count);
}
public static getRandomElement<T>(elements: T[]): T
{
return elements[this.getRandomNumber(elements.length)];
}
public static getRandomElements<T>(elements: T[], count: number): T[]
{
const result: T[] = new Array(count);
let len = elements.length;
const taken = new Array(len);
while(count--)
{
var x = this.getRandomNumber(len);
result[count] = elements[x in taken ? taken[x] : x];
taken[x] = --len in taken ? taken[len] : len;
}
return result;
}
}

View File

@ -3,3 +3,4 @@ export * from './LocalizeBadgeDescription';
export * from './LocalizeBageName';
export * from './LocalizeShortNumber';
export * from './LocalizeText';
export * from './Randomizer';

View File

@ -1,6 +1,6 @@
import { AvatarDirectionAngle, AvatarEditorFigureCategory, FigureSetIdsMessageEvent, UserFigureComposer, UserWardrobePageComposer, UserWardrobePageEvent } from 'nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { GetSessionDataManager } from '../../api';
import { GetClubMemberLevel, GetSessionDataManager } from '../../api';
import { AvatarEditorEvent } from '../../events/avatar-editor';
import { CreateMessageHook, SendMessageHook } from '../../hooks';
import { useUiEvent } from '../../hooks/events/ui/ui-event';
@ -10,6 +10,7 @@ import { AvatarEditorViewProps } from './AvatarEditorView.types';
import { AvatarEditorUtilities } from './common/AvatarEditorUtilities';
import { BodyModel } from './common/BodyModel';
import { FigureData } from './common/FigureData';
import { generateRandomFigure } from './common/FigureGenerator';
import { HeadModel } from './common/HeadModel';
import { IAvatarEditorCategoryModel } from './common/IAvatarEditorCategoryModel';
import { LegModel } from './common/LegModel';
@ -167,6 +168,14 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
resetCategories();
}, [ lastFigure, lastGender, loadAvatarInEditor, resetCategories ]);
const randomizeFigure = useCallback(() =>
{
const figure = generateRandomFigure(figureData, figureData.gender, GetClubMemberLevel(), figureSetIds, [ FigureData.FACE ]);
loadAvatarInEditor(figure, figureData.gender, false);
resetCategories();
}, [ figureData, figureSetIds, loadAvatarInEditor, resetCategories ]);
const rotateFigure = useCallback((direction: number) =>
{
if(direction < AvatarDirectionAngle.MIN_DIRECTION)
@ -308,6 +317,9 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
<button type="button" className="btn btn-sm btn-secondary" onClick={ clearFigure }>
<i className="fas fa-trash" />
</button>
<button type="button" className="btn btn-sm btn-secondary" onClick={ randomizeFigure }>
<i className="fas fa-dice" />
</button>
</div>
<button type="button" className="btn btn-success btn-sm w-100" onClick={ saveFigure }>{ LocalizeText('avatareditor.save') }</button>
</div>

View File

@ -72,7 +72,7 @@ export class AvatarEditorUtilities
const clubItemsDimmed = this.clubItemsDimmed;
const clubMemberLevel = GetClubMemberLevel();
for(const partColor of palette.colors.values())
for(const partColor of palette.colors.getValues())
{
if(partColor.isSelectable && (clubItemsDimmed || (clubMemberLevel >= partColor.clubLevel)))
{
@ -255,7 +255,7 @@ export class AvatarEditorUtilities
if(!palette) return -1;
for(const color of palette.colors.values())
for(const color of palette.colors.getValues())
{
if(!color.isSelectable || (GetClubMemberLevel() < color.clubLevel)) continue;

View File

@ -23,7 +23,7 @@ export class FigureData
public static TROUSERS: string = 'lg';
public static SHOES: string = 'sh';
public static TROUSER_ACCESSORIES: string = 'wa';
public static PREVIEW_AVATAR_DIRECTION: number = 4;
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[]>;
@ -76,9 +76,9 @@ export class FigureData
}
}
public getPartSetId(partSetId: string): number
public getPartSetId(setType: string): number
{
const existing = this._data.get(partSetId);
const existing = this._data.get(setType);
if(existing !== undefined) return existing;
@ -134,15 +134,15 @@ export class FigureData
return figureString;
}
public savePartData(k: string, _arg_2: number, _arg_3: number[], _arg_4: boolean = false): void
public savePartData(setType: string, partId: number, colorIds: number[], update: boolean = false): void
{
this.savePartSetId(k, _arg_2, _arg_4);
this.savePartSetColourId(k, _arg_3, _arg_4);
this.savePartSetId(setType, partId, update);
this.savePartSetColourId(setType, colorIds, update);
}
private savePartSetId(k: string, _arg_2: number, _arg_3: boolean = true): void
private savePartSetId(setType: string, partId: number, update: boolean = true): void
{
switch(k)
switch(setType)
{
case FigureData.FACE:
case FigureData.HAIR:
@ -157,18 +157,18 @@ export class FigureData
case FigureData.TROUSERS:
case FigureData.SHOES:
case FigureData.TROUSER_ACCESSORIES:
if(_arg_2 >= 0)
if(partId >= 0)
{
this._data.set(k, _arg_2);
this._data.set(setType, partId);
}
else
{
this._data.delete(k);
this._data.delete(setType);
}
break;
}
if(_arg_3) this.updateView();
if(update) this.updateView();
}
public savePartSetColourId(setType: string, colorIds: number[], update: boolean = true): void

View File

@ -0,0 +1,128 @@
import { AvatarFigureContainer, IFigurePartSet, IPalette, IPartColor, SetType } from 'nitro-renderer';
import { GetAvatarRenderManager } from '../../../api';
import { Randomizer } from '../../../utils';
import { FigureData } from './FigureData';
const RANDOM_TRIES: number = 20;
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();
let selectedSet: IFigurePartSet = null;
let randomTries = RANDOM_TRIES;
while(!selectedSet)
{
if(!randomTries) return setType.getDefaultPartSet(gender);
const randomSet = Randomizer.getRandomElement(options);
if(!randomSet.isSelectable || ((randomSet.gender !== 'U') && (randomSet.gender !== gender)) || (randomSet.clubLevel > clubLevel) || (randomSet.isSellable && (figureSetIds.indexOf(randomSet.id) === -1)))
{
randomTries--;
continue;
}
return randomSet;
}
return null;
}
function getRandomColor(options: IPartColor[], clubLevel: number = 0): IPartColor
{
const randomColor = Randomizer.getRandomElement(options);
if(!randomColor.isSelectable || (randomColor.clubLevel > clubLevel)) return null;
return randomColor;
}
function getRandomColors(palette: IPalette, partSet: IFigurePartSet, clubLevel: number = 0): number[]
{
if(!palette) return [];
const options = palette.colors.getValues();
let totalColors = getTotalColors(partSet);
let selectedColors: number[] = [];
let randomTries = RANDOM_TRIES;
while(totalColors)
{
if(!randomTries) break;
const randomColor = getRandomColor(options, clubLevel);
if(!randomColor)
{
randomTries--;
continue;
}
selectedColors.push(randomColor.id);
totalColors--;
}
return selectedColors;
}
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);
}
figureContainer.updatePart(setType.type, selectedSet.id, selectedColors);
}
return figureContainer.getFigureString();
}