Move to Renderer V2

This commit is contained in:
duckietm
2024-04-03 09:27:56 +02:00
parent 110c3ad393
commit b3134ce50b
4080 changed files with 115593 additions and 66375 deletions
@@ -0,0 +1,65 @@
import { IAssetManager, IAvatarAssetDownloadLibrary } from '@nitrots/api';
import { AvatarRenderLibraryEvent, GetEventDispatcher, NitroEventType } from '@nitrots/events';
export class AvatarAssetDownloadLibrary implements IAvatarAssetDownloadLibrary
{
private static NOT_LOADED: number = 0;
private static LOADING: number = 1;
private static LOADED: number = 2;
private _state: number = AvatarAssetDownloadLibrary.NOT_LOADED;
private _libraryName: string;
private _revision: string;
private _downloadUrl: string;
private _assetManager: IAssetManager;
constructor(libraryName: string, revision: string, downloadUrl: string, assetManager: IAssetManager)
{
this._libraryName = libraryName;
this._revision = revision;
this._downloadUrl = downloadUrl;
this._assetManager = assetManager;
this._downloadUrl = this._downloadUrl.replace(/%libname%/gi, this._libraryName);
this._downloadUrl = this._downloadUrl.replace(/%revision%/gi, this._revision);
this.checkIsLoaded();
}
public async downloadAsset(): Promise<void>
{
if(!this._assetManager || (this._state === AvatarAssetDownloadLibrary.LOADING) || (this._state === AvatarAssetDownloadLibrary.LOADED)) return;
if(!this.checkIsLoaded())
{
this._state = AvatarAssetDownloadLibrary.LOADING;
const status = await this._assetManager.downloadAsset(this._downloadUrl);
if(!status) throw new Error('Could not download asset');
}
if(this.checkIsLoaded()) GetEventDispatcher().dispatchEvent(new AvatarRenderLibraryEvent(NitroEventType.AVATAR_ASSET_DOWNLOADED, this));
}
private checkIsLoaded(): boolean
{
const asset = this._assetManager.getCollection(this._libraryName);
if(!asset) return false;
this._state = AvatarAssetDownloadLibrary.LOADED;
return true;
}
public get libraryName(): string
{
return this._libraryName;
}
public get isLoaded(): boolean
{
return (this._state === AvatarAssetDownloadLibrary.LOADED);
}
}
@@ -0,0 +1,246 @@
import { IAssetManager, IAvatarFigureContainer, IAvatarImageListener } from '@nitrots/api';
import { GetConfiguration } from '@nitrots/configuration';
import { AvatarRenderLibraryEvent, GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events';
import { AvatarAssetDownloadLibrary } from './AvatarAssetDownloadLibrary';
import { AvatarStructure } from './AvatarStructure';
export class AvatarAssetDownloadManager
{
private _assets: IAssetManager;
private _structure: AvatarStructure;
private _missingMandatoryLibs: string[] = [];
private _figureMap: Map<string, AvatarAssetDownloadLibrary[]> = new Map();
private _figureListeners: Map<string, IAvatarImageListener[]> = new Map();
private _incompleteFigures: Map<string, AvatarAssetDownloadLibrary[]> = new Map();
private _currentDownloads: AvatarAssetDownloadLibrary[] = [];
private _libraryNames: string[] = [];
constructor(assets: IAssetManager, structure: AvatarStructure)
{
this._assets = assets;
this._structure = structure;
}
public async init(): Promise<void>
{
this._missingMandatoryLibs = GetConfiguration().getValue<string[]>('avatar.mandatory.libraries');
const url = GetConfiguration().getValue<string>('avatar.figuremap.url');
if(!url || !url.length) throw new Error('Invalid figure map url');
const response = await fetch(url);
if(response.status !== 200) throw new Error('Invalid figure map file');
const responseData = await response.json();
this.processFigureMap(responseData.libraries);
GetEventDispatcher().addEventListener(NitroEventType.AVATAR_ASSET_DOWNLOADED, (event: AvatarRenderLibraryEvent) => this.onLibraryLoaded(event));
await this.processMissingLibraries();
}
private processFigureMap(data: any): void
{
if(!data) return;
const downloadUrl = GetConfiguration().getValue<string>('avatar.asset.url');
for(const library of data)
{
if(!library) continue;
const libraryName = (library.id as string);
const revision = (library.revision || '');
if(this._libraryNames.indexOf(libraryName) >= 0) continue;
this._libraryNames.push(libraryName);
const downloadLibrary = new AvatarAssetDownloadLibrary(libraryName, revision, downloadUrl, this._assets);
for(const part of library.parts)
{
const id = (part.id as string);
const type = (part.type as string);
const partString = (type + ':' + id);
let existing = this._figureMap.get(partString);
if(!existing) existing = [];
existing.push(downloadLibrary);
this._figureMap.set(partString, existing);
}
}
}
private async processMissingLibraries(): Promise<void>
{
const promises: Promise<void>[] = [];
this._missingMandatoryLibs.forEach(value =>
{
const libraries = this._figureMap.get(value);
if(libraries) for(const library of libraries) promises.push(library.downloadAsset());
});
this._missingMandatoryLibs = [];
await Promise.all(promises);
}
private onLibraryLoaded(event: AvatarRenderLibraryEvent): void
{
if(!event || !event.library) return;
const loadedFigures: string[] = [];
for(const [figure, libraries] of this._incompleteFigures.entries())
{
let isReady = true;
for(const library of libraries)
{
if(!library || library.isLoaded) continue;
isReady = false;
break;
}
if(isReady)
{
loadedFigures.push(figure);
const listeners = this._figureListeners.get(figure);
if(listeners)
{
for(const listener of listeners)
{
if(!listener || listener.disposed) continue;
listener.resetFigure(figure);
}
}
this._figureListeners.delete(figure);
GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.AVATAR_ASSET_LOADED));
}
}
for(const figure of loadedFigures)
{
if(!figure) continue;
this._incompleteFigures.delete(figure);
}
let index = 0;
while(index < this._currentDownloads.length)
{
const download = this._currentDownloads[index];
if(download)
{
if(download.libraryName === event.library.libraryName) this._currentDownloads.splice(index, 1);
}
index++;
}
}
public isAvatarFigureContainerReady(container: IAvatarFigureContainer): boolean
{
return !this.getAvatarFigurePendingLibraries(container).length;
}
private getAvatarFigurePendingLibraries(container: IAvatarFigureContainer): AvatarAssetDownloadLibrary[]
{
const pendingLibraries: AvatarAssetDownloadLibrary[] = [];
if(!container || !this._structure) return pendingLibraries;
const figureData = this._structure.figureData;
if(!figureData) return pendingLibraries;
const setKeys = container.getPartTypeIds();
for(const key of setKeys)
{
const set = figureData.getSetType(key);
if(!set) continue;
const figurePartSet = set.getPartSet(container.getPartSetId(key));
if(!figurePartSet) continue;
for(const part of figurePartSet.parts)
{
if(!part) continue;
const name = (part.type + ':' + part.id);
const existing = this._figureMap.get(name);
if(existing === undefined) continue;
for(const library of existing)
{
if(!library || library.isLoaded) continue;
if(pendingLibraries.indexOf(library) >= 0) continue;
pendingLibraries.push(library);
}
}
}
return pendingLibraries;
}
public downloadAvatarFigure(container: IAvatarFigureContainer, listener: IAvatarImageListener): void
{
const figure = container.getFigureString();
const pendingLibraries = this.getAvatarFigurePendingLibraries(container);
if(pendingLibraries && pendingLibraries.length)
{
if(listener && !listener.disposed)
{
let listeners = this._figureListeners.get(figure);
if(!listeners)
{
listeners = [];
this._figureListeners.set(figure, listeners);
}
listeners.push(listener);
}
this._incompleteFigures.set(figure, pendingLibraries);
for(const library of pendingLibraries)
{
if(!library) continue;
library.downloadAsset();
}
}
else
{
if(listener && !listener.disposed) listener.resetFigure(figure);
}
}
}
@@ -0,0 +1,116 @@
import { IAvatarFigureContainer } from '@nitrots/api';
export class AvatarFigureContainer implements IAvatarFigureContainer
{
private _parts: Map<string, Map<string, any>>;
constructor(figure: string)
{
this._parts = new Map();
this.parseFigure(figure);
}
public getPartTypeIds(): IterableIterator<string>
{
return this.partSets().keys();
}
public hasPartType(k: string): boolean
{
return !!this.partSets().get(k);
}
public getPartSetId(k: string): number
{
const existing = this.partSets().get(k);
if(!existing) return 0;
return existing.get('setid');
}
public getPartColorIds(k: string): number[]
{
const existing = this.partSets().get(k);
if(!existing) return null;
return existing.get('colorids');
}
public updatePart(setType: string, partSetId: number, colorIds: number[]): void
{
const set: Map<string, any> = new Map();
set.set('type', setType);
set.set('setid', partSetId);
set.set('colorids', colorIds);
const existingSets = this.partSets();
existingSets.delete(setType);
existingSets.set(setType, set);
}
public removePart(k: string): void
{
this.partSets().delete(k);
}
public getFigureString(): string
{
const parts: string[] = [];
for(const key of this.partSets().keys())
{
if(!key) continue;
let setParts = [];
setParts.push(key);
setParts.push(this.getPartSetId(key));
setParts = setParts.concat(this.getPartColorIds(key));
parts.push(setParts.join('-'));
}
return parts.join('.');
}
private partSets(): Map<string, Map<string, any>>
{
if(!this._parts) this._parts = new Map();
return this._parts;
}
private parseFigure(figure: string): void
{
if(!figure) figure = '';
for(const part of figure.split('.'))
{
const pieces = part.split('-');
if(pieces.length >= 2)
{
const type = pieces[0];
const setId = parseInt(pieces[1]);
const colors = [];
let index = 2;
while(index < pieces.length)
{
colors.push(parseInt(pieces[index]));
index++;
}
this.updatePart(type, setId, colors);
}
}
}
}
+764
View File
@@ -0,0 +1,764 @@
import { AvatarAction, AvatarDirectionAngle, AvatarScaleType, AvatarSetType, IActiveActionData, IAnimationLayerData, IAvatarDataContainer, IAvatarEffectListener, IAvatarFigureContainer, IAvatarImage, IPartColor, ISpriteDataContainer } from '@nitrots/api';
import { GetRenderer, GetTexturePool, GetTickerTime, PaletteMapFilter, TextureUtils } from '@nitrots/utils';
import { ColorMatrixFilter, Container, RenderTexture, Sprite, Texture } from 'pixi.js';
import { AvatarFigureContainer } from './AvatarFigureContainer';
import { AvatarStructure } from './AvatarStructure';
import { EffectAssetDownloadManager } from './EffectAssetDownloadManager';
import { ActiveActionData } from './actions';
import { AssetAliasCollection } from './alias';
import { AvatarImageCache } from './cache';
import { AvatarCanvas } from './structure';
export class AvatarImage implements IAvatarImage, IAvatarEffectListener
{
private static CHANNELS_EQUAL: string = 'CHANNELS_EQUAL';
private static CHANNELS_UNIQUE: string = 'CHANNELS_UNIQUE';
private static CHANNELS_RED: string = 'CHANNELS_RED';
private static CHANNELS_GREEN: string = 'CHANNELS_GREEN';
private static CHANNELS_BLUE: string = 'CHANNELS_BLUE';
private static CHANNELS_DESATURATED: string = 'CHANNELS_DESATURATED';
private static DEFAULT_ACTION: string = 'Default';
private static DEFAULT_DIRECTION: number = 2;
private static DEFAULT_AVATAR_SET: string = AvatarSetType.FULL;
protected _mainDirection: number;
protected _headDirection: number;
protected _mainAction: IActiveActionData;
protected _disposed: boolean = false;
protected _canvasOffsets: number[] = [];
protected _cache: AvatarImageCache;
protected _avatarSpriteData: IAvatarDataContainer;
protected _actions: ActiveActionData[] = [];
protected _activeTexture: Texture = null;
private _defaultAction: IActiveActionData = null;
private _frameCounter: number = 0;
private _directionOffset: number = 0;
private _changes: boolean = true;
private _sprites: ISpriteDataContainer[];
private _isAnimating: boolean = false;
private _animationHasResetOnToggle: boolean = false;
private _actionsSorted: boolean = false;
private _sortedActions: IActiveActionData[];
private _lastActionsString: string = null;
private _currentActionsString: string = null;
private _effectIdInUse: number = -1;
private _animationFrameCount: number = -1;
private _cachedBodyParts: string[] = [];
private _cachedBodyPartsDirection: number = -1;
private _cachedBodyPartsGeometryType: string = null;
private _cachedBodyPartsAvatarSet: string = null;
constructor(
private _structure: AvatarStructure,
private _assets: AssetAliasCollection,
private _figure: AvatarFigureContainer,
private _scale: string,
private _effectManager: EffectAssetDownloadManager,
private _effectListener: IAvatarEffectListener = null)
{
if(!this._figure) this._figure = new AvatarFigureContainer('hr-893-45.hd-180-2.ch-210-66.lg-270-82.sh-300-91.wa-2007-.ri-1-');
if(!this._scale) this._scale = AvatarScaleType.LARGE;
this._cache = new AvatarImageCache(this._structure, this, this._assets, this._scale);
this.setDirection(AvatarImage.DEFAULT_AVATAR_SET, AvatarImage.DEFAULT_DIRECTION);
this._defaultAction = new ActiveActionData(AvatarAction.POSTURE_STAND);
this._defaultAction.definition = this._structure.getActionDefinition(AvatarImage.DEFAULT_ACTION);
this.resetActions();
this._animationFrameCount = 0;
}
public dispose(): void
{
if(this._disposed) return;
this._structure = null;
this._assets = null;
this._mainAction = null;
this._figure = null;
this._avatarSpriteData = null;
this._actions = null;
if(this._activeTexture)
{
GetTexturePool().putTexture(this._activeTexture);
this._activeTexture = null;
}
if(this._cache)
{
this._cache.dispose();
this._cache = null;
}
this._canvasOffsets = null;
this._disposed = true;
}
public get disposed(): boolean
{
return this._disposed;
}
public getFigure(): IAvatarFigureContainer
{
return this._figure;
}
public getScale(): string
{
return this._scale;
}
public getPartColor(k: string): IPartColor
{
return this._structure.getPartColor(this._figure, k);
}
public setDirection(avatarPart: string, direction: number): void
{
direction += this._directionOffset;
if(direction < AvatarDirectionAngle.MIN_DIRECTION)
{
direction = AvatarDirectionAngle.MAX_DIRECTION + (direction + 1);
}
else if(direction > AvatarDirectionAngle.MAX_DIRECTION)
{
direction -= (AvatarDirectionAngle.MAX_DIRECTION + 1);
}
if(this._structure.isMainAvatarSet(avatarPart)) this._mainDirection = direction;
// Special handling for head direction, including prevention checks for turning
if(avatarPart === AvatarSetType.HEAD || avatarPart === AvatarSetType.FULL)
{
if(avatarPart === AvatarSetType.HEAD && this.isHeadTurnPreventedByAction()) direction = this._mainDirection;
this._headDirection = direction;
}
this._cache.setDirection(avatarPart, direction);
this._changes = true;
}
public setDirectionAngle(k: string, _arg_2: number): void
{
this.setDirection(k, Math.floor(_arg_2 / 45));
}
public getSprites(): ISpriteDataContainer[]
{
return this._sprites;
}
public getCanvasOffsets(): number[]
{
return this._canvasOffsets;
}
public getLayerData(k: ISpriteDataContainer): IAnimationLayerData
{
return this._structure.getBodyPartData(k.animation.id, this._frameCounter, k.id);
}
public updateAnimationByFrames(k: number = 1): void
{
this._frameCounter += k;
this._changes = true;
}
public resetAnimationFrameCounter(): void
{
this._frameCounter = 0;
this._changes = true;
}
private getBodyParts(avatarSet: string, geometryType: string, direction: number): string[]
{
const shouldUpdateCache = direction !== this._cachedBodyPartsDirection || geometryType !== this._cachedBodyPartsGeometryType || avatarSet !== this._cachedBodyPartsAvatarSet;
if(shouldUpdateCache)
{
this._cachedBodyPartsDirection = direction;
this._cachedBodyPartsGeometryType = geometryType;
this._cachedBodyPartsAvatarSet = avatarSet;
this._cachedBodyParts = this._structure.getBodyParts(avatarSet, geometryType, direction);
}
return this._cachedBodyParts;
}
private buildAvatarContainer(avatarCanvas: AvatarCanvas, setType: string): Container
{
const bodyParts = this.getBodyParts(setType, this._mainAction.definition.geometryType, this._mainDirection);
const container = new Container();
let partCount = (bodyParts.length - 1);
while(partCount >= 0)
{
const set = bodyParts[partCount];
const part = this._cache.getImageContainer(set, this._frameCounter);
if(part)
{
const partCacheContainer = part.image;
if(partCacheContainer)
{
const partContainer = new Container();
partContainer.addChild(partCacheContainer);
const point = part.regPoint.clone();
point.x += avatarCanvas.offset.x;
point.y += avatarCanvas.offset.y;
point.x += avatarCanvas.regPoint.x;
point.y += avatarCanvas.regPoint.y;
partContainer.position.set(point.x, point.y);
container.addChild(partContainer);
}
}
partCount--;
}
container.filters = [];
if(this._avatarSpriteData)
{
if(this._avatarSpriteData.colorTransform)
{
if(container.filters === undefined || container.filters === null) container.filters = [ this._avatarSpriteData.colorTransform ];
else if(Array.isArray(container.filters)) container.filters = [ ...container.filters, this._avatarSpriteData.colorTransform ];
else container.filters = [ container.filters, this._avatarSpriteData.colorTransform ];
}
if(this._avatarSpriteData.paletteIsGrayscale)
{
this.convertToGrayscale(container);
const paletteMapFilter = new PaletteMapFilter({
palette: this._avatarSpriteData.reds,
channel: PaletteMapFilter.CHANNEL_RED
});
if(container.filters === undefined || container.filters === null) container.filters = [ paletteMapFilter ];
else if(Array.isArray(container.filters)) container.filters = [ ...container.filters, paletteMapFilter ];
else container.filters = [ container.filters, paletteMapFilter ];
}
}
return container;
}
public processAsTexture(setType: string, hightlight: boolean): Texture
{
if(!this._changes) return this._activeTexture;
if(!this._mainAction) return null;
if(!this._actionsSorted) this.endActionAppends();
const avatarCanvas = this._structure.getCanvas(this._scale, this._mainAction.definition.geometryType);
if(!avatarCanvas) return null;
const container = this.buildAvatarContainer(avatarCanvas, setType);
if(this._activeTexture && ((this._activeTexture.width !== avatarCanvas.width) || (this._activeTexture.height !== avatarCanvas.height)))
{
GetTexturePool().putTexture(this._activeTexture);
this._activeTexture = null;
}
if(!this._activeTexture) this._activeTexture = GetTexturePool().getTexture(avatarCanvas.width, avatarCanvas.height);
if(!this._activeTexture) return null;
GetRenderer().render({
target: this._activeTexture,
container: container,
clear: true
});
container.destroy();
//@ts-ignore
this._activeTexture.source.hitMap = null;
this._changes = false;
return this._activeTexture;
}
public processAsImageUrl(setType: string, scale: number = 1): string
{
const texture = this.processAsTexture(setType, false);
const canvas = GetRenderer().texture.generateCanvas(texture);
const url = canvas.toDataURL('image/png');
return url;
}
public processAsContainer(setType: string): Container
{
if(!this._mainAction) return null;
if(!this._actionsSorted) this.endActionAppends();
const avatarCanvas = this._structure.getCanvas(this._scale, this._mainAction.definition.geometryType);
if(!avatarCanvas) return null;
return this.buildAvatarContainer(avatarCanvas, setType);
}
public applyPalette(texture: RenderTexture, reds: number[] = [], greens: number[] = [], blues: number[] = []): RenderTexture
{
const textureCanvas = TextureUtils.generateCanvas(texture);
const textureCtx = textureCanvas.getContext('2d');
const textureImageData = textureCtx.getImageData(0, 0, textureCanvas.width, textureCanvas.height);
const data = textureImageData.data;
for(let i = 0; i < data.length; i += 4)
{
if(reds.length == 256)
{
let paletteColor = reds[data[i]];
if(paletteColor === undefined) paletteColor = 0;
data[i] = ((paletteColor >> 16) & 0xFF);
data[i + 1] = ((paletteColor >> 8) & 0xFF);
data[i + 2] = (paletteColor & 0xFF);
}
if(greens.length == 256)
{
let paletteColor = greens[data[i + 1]];
if(paletteColor === undefined) paletteColor = 0;
data[i] = ((paletteColor >> 16) & 0xFF);
data[i + 1] = ((paletteColor >> 8) & 0xFF);
data[i + 2] = (paletteColor & 0xFF);
}
if(blues.length == 256)
{
let paletteColor = greens[data[i + 2]];
if(paletteColor === undefined) paletteColor = 0;
data[i] = ((paletteColor >> 16) & 0xFF);
data[i + 1] = ((paletteColor >> 8) & 0xFF);
data[i + 2] = (paletteColor & 0xFF);
}
}
textureCtx.putImageData(textureImageData, 0, 0);
const newTexture = new Sprite(Texture.from(textureCanvas));
TextureUtils.writeToTexture(newTexture, texture, true);
return texture;
}
public getDirection(): number
{
return this._mainDirection;
}
public initActionAppends(): void
{
this._actions = [];
this._actionsSorted = false;
this._currentActionsString = '';
}
public endActionAppends(): void
{
if(!this.sortActions()) return;
for(const k of this._sortedActions)
{
if(k.actionType === AvatarAction.EFFECT)
{
if(!this._effectManager.isAvatarEffectReady(parseInt(k.actionParameter))) this._effectManager.downloadAvatarEffect(parseInt(k.actionParameter), this);
}
}
this.resetActions();
this.setActionsToParts();
}
public appendAction(k: string, ..._args: any[]): boolean
{
let _local_3 = '';
this._actionsSorted = false;
if(_args && (_args.length > 0)) _local_3 = _args[0];
if((_local_3 !== undefined) && (_local_3 !== null)) _local_3 = _local_3.toString();
switch(k)
{
case AvatarAction.POSTURE:
switch(_local_3)
{
case AvatarAction.POSTURE_LAY:
case AvatarAction.POSTURE_WALK:
case AvatarAction.POSTURE_STAND:
case AvatarAction.POSTURE_SWIM:
case AvatarAction.POSTURE_FLOAT:
case AvatarAction.POSTURE_SIT:
case AvatarAction.SNOWWAR_RUN:
case AvatarAction.SNOWWAR_DIE_FRONT:
case AvatarAction.SNOWWAR_DIE_BACK:
case AvatarAction.SNOWWAR_PICK:
case AvatarAction.SNOWWAR_THROW:
if((_local_3 === AvatarAction.POSTURE_LAY) || (_local_3 === AvatarAction.POSTURE_LAY) || (_local_3 === AvatarAction.POSTURE_LAY))
{
if(_local_3 === AvatarAction.POSTURE_LAY)
{
if(this._mainDirection == 0)
{
this.setDirection(AvatarSetType.FULL, 4);
}
else
{
this.setDirection(AvatarSetType.FULL, 2);
}
}
}
this.addActionData(_local_3);
break;
}
break;
case AvatarAction.GESTURE:
switch(_local_3)
{
case AvatarAction.GESTURE_AGGRAVATED:
case AvatarAction.GESTURE_SAD:
case AvatarAction.GESTURE_SMILE:
case AvatarAction.GESTURE_SURPRISED:
this.addActionData(_local_3);
break;
}
break;
case AvatarAction.EFFECT:
case AvatarAction.DANCE:
case AvatarAction.TALK:
case AvatarAction.EXPRESSION_WAVE:
case AvatarAction.SLEEP:
case AvatarAction.SIGN:
case AvatarAction.EXPRESSION_RESPECT:
case AvatarAction.EXPRESSION_BLOW_A_KISS:
case AvatarAction.EXPRESSION_LAUGH:
case AvatarAction.EXPRESSION_CRY:
case AvatarAction.EXPRESSION_IDLE:
case AvatarAction.EXPRESSION_SNOWBOARD_OLLIE:
case AvatarAction.EXPRESSION_SNOWBORD_360:
case AvatarAction.EXPRESSION_RIDE_JUMP:
if(_local_3 === AvatarAction.EFFECT)
{
if((((((_local_3 === '33') || (_local_3 === '34')) || (_local_3 === '35')) || (_local_3 === '36')) || (_local_3 === '38')) || (_local_3 === '39'))
{
//
}
}
this.addActionData(k, _local_3);
break;
case AvatarAction.CARRY_OBJECT:
case AvatarAction.USE_OBJECT: {
const _local_4 = this._structure.getActionDefinitionWithState(k);
if(_local_4) _local_3 = _local_4.getParameterValue(_local_3);
this.addActionData(k, _local_3);
break;
}
}
return true;
}
protected addActionData(actionType: string, actionParameter: string = ''): void
{
if(!this._actions) this._actions = [];
const actionExists = this._actions.some(action =>
action.actionType === actionType && action.actionParameter === actionParameter
);
if(!actionExists) this._actions.push(new ActiveActionData(actionType, actionParameter, this._frameCounter));
}
public isAnimating(): boolean
{
return (this._isAnimating) || (this._animationFrameCount > 1);
}
private resetActions(): boolean
{
this._animationHasResetOnToggle = false;
this._isAnimating = false;
this._sprites = [];
this._avatarSpriteData = null;
this._directionOffset = 0;
this._structure.removeDynamicItems(this);
this._mainAction = this._defaultAction;
this._mainAction.definition = this._defaultAction.definition;
this.resetBodyPartCache(this._defaultAction);
return true;
}
private isHeadTurnPreventedByAction(): boolean
{
if(!this._sortedActions) return false;
for(const action of this._sortedActions)
{
const actionDefinition = this._structure.getActionDefinitionWithState(action.actionType);
if(actionDefinition != null && actionDefinition.getPreventHeadTurn(action.actionParameter)) return true;
}
return false;
}
private sortActions(): boolean
{
let hasChanges = false;
let hasEffectAction = false;
let effectChanged = false;
this._currentActionsString = '';
this._sortedActions = this._structure.sortActions(this._actions);
this._animationFrameCount = this._structure.maxFrames(this._sortedActions);
if(!this._sortedActions)
{
this._canvasOffsets = [0, 0, 0];
if(this._lastActionsString !== '')
{
hasChanges = true;
this._lastActionsString = '';
}
}
else
{
this._canvasOffsets = this._structure.getCanvasOffsets(this._sortedActions, this._scale, this._mainDirection);
for(const action of this._sortedActions)
{
this._currentActionsString += action.actionType + action.actionParameter;
if(action.actionType === AvatarAction.EFFECT)
{
const effectId = parseInt(action.actionParameter);
if(this._effectIdInUse !== effectId) effectChanged = true;
this._effectIdInUse = effectId;
hasEffectAction = true;
}
}
if(!hasEffectAction)
{
if(this._effectIdInUse > -1) effectChanged = true;
this._effectIdInUse = -1;
}
if(effectChanged) this._cache.disposeInactiveActions();
if(this._lastActionsString != this._currentActionsString)
{
hasChanges = true;
this._lastActionsString = this._currentActionsString;
}
}
this._actionsSorted = true;
return hasChanges;
}
private setActionsToParts(): void
{
if(!this._sortedActions) return;
const currentTime = GetTickerTime();
const actionTypes: string[] = [];
for(const action of this._sortedActions) actionTypes.push(action.actionType);
for(const action of this._sortedActions)
{
if(action && action.definition && action.definition.isAnimation)
{
const animation = this._structure.getAnimation(`${action.definition.state}.${action.actionParameter}`);
if(animation && animation.hasOverriddenActions())
{
const overriddenActionNames = animation.overriddenActionNames();
if(overriddenActionNames)
{
for(const overriddenActionName of overriddenActionNames)
{
if(actionTypes.includes(overriddenActionName)) action.overridingAction = animation.overridingAction(overriddenActionName);
}
}
}
if(animation && animation.resetOnToggle) this._animationHasResetOnToggle = true;
}
}
for(const action of this._sortedActions)
{
if(action && action.definition)
{
if(action.definition.isAnimation && action.actionParameter === '') action.actionParameter = '1';
this.setActionToParts(action, currentTime);
if(action.definition.isAnimation)
{
this._isAnimating = action.definition.isAnimated(action.actionParameter);
const animation = this._structure.getAnimation(`${action.definition.state}.${action.actionParameter}`);
if(animation)
{
this._sprites = [...this._sprites, ...animation.spriteData];
if(animation.hasDirectionData()) this._directionOffset = animation.directionData.offset;
if(animation.hasAvatarData()) this._avatarSpriteData = animation.avatarData;
}
}
}
}
}
private setActionToParts(action: IActiveActionData, currentTime: number): void
{
if(!action || !action.definition || action.definition.assetPartDefinition === '') return;
if(action.definition.isMain)
{
this._mainAction = action;
this._cache.setGeometryType(action.definition.geometryType);
}
this._cache.setAction(action, currentTime);
this._changes = true;
}
private resetBodyPartCache(action: IActiveActionData): void
{
if(!action || action.definition.assetPartDefinition === '') return;
if(action.definition.isMain)
{
this._mainAction = action;
this._cache.setGeometryType(action.definition.geometryType);
}
this._cache.resetBodyPartCache(action);
this._changes = true;
}
private convertToGrayscale(container: Container, channel: string = 'CHANNELS_EQUAL'): Container
{
let redWeight = 0.33;
let greenWeight = 0.33;
let blueWeight = 0.33;
switch(channel)
{
case AvatarImage.CHANNELS_UNIQUE:
redWeight = 0.3;
greenWeight = 0.59;
blueWeight = 0.11;
break;
case AvatarImage.CHANNELS_RED:
redWeight = 1;
greenWeight = 0;
blueWeight = 0;
break;
case AvatarImage.CHANNELS_GREEN:
redWeight = 0;
greenWeight = 1;
blueWeight = 0;
break;
case AvatarImage.CHANNELS_BLUE:
redWeight = 0;
greenWeight = 0;
blueWeight = 1;
break;
case AvatarImage.CHANNELS_DESATURATED:
redWeight = 0.3086;
greenWeight = 0.6094;
blueWeight = 0.082;
break;
}
const filter = new ColorMatrixFilter();
filter.matrix = [
redWeight, greenWeight, blueWeight, 0, 0, // Red channel
redWeight, greenWeight, blueWeight, 0, 0, // Green channel
redWeight, greenWeight, blueWeight, 0, 0, // Blue channel
0, 0, 0, 1, 0 // Alpha channel
];
if(container.filters === undefined || container.filters === null) container.filters = [ filter ];
else if(Array.isArray(container.filters)) container.filters = [ ...container.filters, filter ];
else container.filters = [ container.filters, filter ];
return container;
}
public isPlaceholder(): boolean
{
return false;
}
public get animationHasResetOnToggle(): boolean
{
return this._animationHasResetOnToggle;
}
public resetEffect(effect: number): void
{
if(effect === this._effectIdInUse)
{
this.resetActions();
this.setActionsToParts();
this._animationHasResetOnToggle = true;
this._changes = true;
if(this._effectListener) this._effectListener.resetEffect(effect);
}
}
}
@@ -0,0 +1,88 @@
import { Container, Point } from 'pixi.js';
export class AvatarImageBodyPartContainer
{
private _image: Container;
private _regPoint: Point;
private _offset: Point;
private _isCacheable: boolean;
constructor(k: Container, _arg_2: Point, _arg_3: boolean)
{
this._image = k;
this._regPoint = _arg_2;
this._offset = new Point(0, 0);
this._regPoint = _arg_2;
this._isCacheable = _arg_3;
this.cleanPoints();
}
public dispose(): void
{
if(this._image)
{
this._image.destroy({
children: true
});
}
this._image = null;
this._regPoint = null;
this._offset = null;
}
private cleanPoints(): void
{
// this._regPoint.x = this._regPoint.x;
// this._regPoint.y = this._regPoint.y;
// this._offset.x = this._offset.x;
// this._offset.y = this._offset.y;
}
public setRegPoint(k: Point): void
{
this._regPoint = k;
this.cleanPoints();
}
public get image(): Container
{
return this._image;
}
public set image(k: Container)
{
if(this._image && (this._image !== k))
{
this._image.destroy({
children: true
});
}
this._image = k;
}
public get regPoint(): Point
{
const clone = this._regPoint.clone();
clone.x += this._offset.x;
clone.y += this._offset.y;
return clone;
}
public set offset(k: Point)
{
this._offset = k;
this.cleanPoints();
}
public get isCacheable(): boolean
{
return this._isCacheable;
}
}
@@ -0,0 +1,133 @@
import { IActionDefinition, IPartColor } from '@nitrots/api';
import { AvatarAnimationFrame } from './structure';
export class AvatarImagePartContainer
{
private _bodyPartId: string;
private _partType: string;
private _flippedPartType: string;
private _partId: string;
private _color: IPartColor;
private _frames: AvatarAnimationFrame[];
private _action: IActionDefinition;
private _isColorable: boolean;
private _isBlendable: boolean;
private _paletteMapId: number;
constructor(bodyPartId: string, partType: string, partId: string, partColor: IPartColor, frames: AvatarAnimationFrame[], action: IActionDefinition, isColorable: boolean, paletteMapId: number, flippedPartType: string = '', isBlendable: boolean = false, _arg_11: number = 1)
{
this._bodyPartId = bodyPartId;
this._partType = partType;
this._partId = partId;
this._color = partColor;
this._frames = frames;
this._action = action;
this._isColorable = isColorable;
this._paletteMapId = paletteMapId;
this._flippedPartType = flippedPartType;
this._isBlendable = isBlendable;
if(this._partType === 'ey') this._isColorable = false;
}
public getFrameIndex(k: number): number
{
if(!this._frames || !this._frames.length) return 0;
const frameNumber = (k % this._frames.length);
if(this._frames[frameNumber] instanceof AvatarAnimationFrame)
{
return this._frames[frameNumber].number;
}
return frameNumber;
}
public getFrameDefinition(k: number): AvatarAnimationFrame
{
const frameNumber = (k % this._frames.length);
if(this._frames && (this._frames.length > frameNumber))
{
if(this._frames[frameNumber] instanceof AvatarAnimationFrame)
{
return this._frames[frameNumber];
}
}
return null;
}
public getCacheableKey(k: number): string
{
const frameNumber = (k % this._frames.length);
if(this._frames && (this._frames.length > frameNumber))
{
if(this._frames[frameNumber] instanceof AvatarAnimationFrame)
{
const frame = this._frames[frameNumber];
return (this.partId + ':' + frame.assetPartDefinition + ':' + frame.number);
}
}
return (this.partId + ':' + frameNumber);
}
public get bodyPartId(): string
{
return this._bodyPartId;
}
public get partType(): string
{
return this._partType;
}
public get partId(): string
{
return this._partId;
}
public get color(): IPartColor
{
return this._color;
}
public get action(): IActionDefinition
{
return this._action;
}
public get isColorable(): boolean
{
return this._isColorable;
}
public set isColorable(k: boolean)
{
this._isColorable = k;
}
public get paletteMapId(): number
{
return this._paletteMapId;
}
public get flippedPartType(): string
{
return this._flippedPartType;
}
public get isBlendable(): boolean
{
return this._isBlendable;
}
public toString(): string
{
return [this._bodyPartId, this._partType, this._partId].join(':');
}
}
+292
View File
@@ -0,0 +1,292 @@
import { AvatarSetType, IAssetManager, IAvatarEffectListener, IAvatarFigureContainer, IAvatarImage, IAvatarImageListener, IAvatarRenderManager, IFigureData, IFigurePartSet, IGraphicAsset, IStructureData } from '@nitrots/api';
import { GetAssetManager } from '@nitrots/assets';
import { GetConfiguration } from '@nitrots/configuration';
import { GetEventDispatcher, NitroEventType } from '@nitrots/events';
import { AvatarAssetDownloadManager } from './AvatarAssetDownloadManager';
import { AvatarFigureContainer } from './AvatarFigureContainer';
import { AvatarImage } from './AvatarImage';
import { AvatarStructure } from './AvatarStructure';
import { EffectAssetDownloadManager } from './EffectAssetDownloadManager';
import { FigureDataContainer } from './FigureDataContainer';
import { PlaceHolderAvatarImage } from './PlaceHolderAvatarImage';
import { AssetAliasCollection } from './alias';
import { HabboAvatarAnimations } from './data/HabboAvatarAnimations';
import { HabboAvatarGeometry } from './data/HabboAvatarGeometry';
import { HabboAvatarPartSets } from './data/HabboAvatarPartSets';
export class AvatarRenderManager implements IAvatarRenderManager
{
private static DEFAULT_FIGURE: string = 'hd-99999-99999';
private _structure: AvatarStructure = new AvatarStructure(this);
private _aliasCollection: AssetAliasCollection = new AssetAliasCollection(this, GetAssetManager());
private _avatarAssetDownloadManager: AvatarAssetDownloadManager = new AvatarAssetDownloadManager(GetAssetManager(), this._structure);
private _effectAssetDownloadManager: EffectAssetDownloadManager = new EffectAssetDownloadManager(GetAssetManager(), this._structure);
private _placeHolderFigure: AvatarFigureContainer = new AvatarFigureContainer(AvatarRenderManager.DEFAULT_FIGURE);
public async init(): Promise<void>
{
this._structure?.initGeometry(HabboAvatarGeometry.geometry);
this._structure?.initPartSets(HabboAvatarPartSets.partSets);
await this.loadActions();
this._structure?.initAnimation(HabboAvatarAnimations.animations);
await this.loadFigureData();
this._aliasCollection.init();
GetEventDispatcher().addEventListener(NitroEventType.AVATAR_ASSET_LOADED, () => this._aliasCollection.reset());
GetEventDispatcher().addEventListener(NitroEventType.AVATAR_EFFECT_LOADED, () => this._aliasCollection.reset());
await this._avatarAssetDownloadManager.init();
await this._effectAssetDownloadManager.init();
}
private async loadActions(): Promise<void>
{
const defaultActions = GetConfiguration().getValue<string>('avatar.default.actions');
if(defaultActions) this._structure.initActions(GetAssetManager(), defaultActions);
const url = GetConfiguration().getValue<string>('avatar.actions.url');
if(!url || !url.length) throw new Error('Invalid avatar action url');
const response = await fetch(url);
if(response.status !== 200) throw new Error('Invalid avatar action file');
this._structure.updateActions(await response.json());
}
private async loadFigureData(): Promise<void>
{
const defaultFigureData = GetConfiguration().getValue<IFigureData>('avatar.default.figuredata');
if(defaultFigureData) this._structure?.initFigureData(defaultFigureData);
const url = GetConfiguration().getValue<string>('avatar.figuredata.url');
if(!url || !url.length) throw new Error('Invalid figure data url');
const response = await fetch(url);
if(response.status !== 200) throw new Error('Invalid figure data file');
this._structure.figureData.appendJSON(await response.json());
this._structure.init();
}
public createFigureContainer(figure: string): IAvatarFigureContainer
{
return new AvatarFigureContainer(figure);
}
public isFigureContainerReady(container: IAvatarFigureContainer): boolean
{
if(!this._avatarAssetDownloadManager) return false;
return this._avatarAssetDownloadManager.isAvatarFigureContainerReady(container);
}
public createAvatarImage(figure: string, size: string, gender: string, listener: IAvatarImageListener = null, effectListener: IAvatarEffectListener = null): IAvatarImage
{
if(!this._structure || !this._avatarAssetDownloadManager) return null;
const figureContainer = new AvatarFigureContainer(figure);
if(gender) this.validateAvatarFigure(figureContainer, gender);
if(this._avatarAssetDownloadManager.isAvatarFigureContainerReady(figureContainer))
{
return new AvatarImage(this._structure, this._aliasCollection, figureContainer, size, this._effectAssetDownloadManager, effectListener);
}
this._avatarAssetDownloadManager.downloadAvatarFigure(figureContainer, listener);
return new PlaceHolderAvatarImage(this._structure, this._aliasCollection, this._placeHolderFigure, size, this._effectAssetDownloadManager);
}
public downloadAvatarFigure(container: IAvatarFigureContainer, listener: IAvatarImageListener): void
{
if(!this._avatarAssetDownloadManager) return;
this._avatarAssetDownloadManager.downloadAvatarFigure(container, listener);
}
private validateAvatarFigure(container: AvatarFigureContainer, gender: string): boolean
{
let isValid = false;
const typeIds = this._structure.getMandatorySetTypeIds(gender, 2);
if(typeIds)
{
const figureData = this._structure.figureData;
for(const id of typeIds)
{
if(!container.hasPartType(id))
{
const figurePartSet = this._structure.getDefaultPartSet(id, gender);
if(figurePartSet)
{
container.updatePart(id, figurePartSet.id, [0]);
isValid = true;
}
}
else
{
const setType = figureData.getSetType(id);
if(setType)
{
const figurePartSet = setType.getPartSet(container.getPartSetId(id));
if(!figurePartSet)
{
const partSet = this._structure.getDefaultPartSet(id, gender);
if(partSet)
{
container.updatePart(id, partSet.id, [0]);
isValid = true;
}
}
}
}
}
}
return !(isValid);
}
public getFigureClubLevel(container: IAvatarFigureContainer, gender: string, searchParts: string[] = null): number
{
if(!this._structure) return 0;
const figureData = this._structure.figureData;
const parts = Array.from(container.getPartTypeIds());
let clubLevel = 0;
for(const part of parts)
{
const set = figureData.getSetType(part);
if(!set) continue;
const setId = container.getPartSetId(part);
const partSet = set.getPartSet(setId);
if(partSet)
{
clubLevel = Math.max(partSet.clubLevel, clubLevel);
const palette = figureData.getPalette(set.paletteID);
const colors = container.getPartColorIds(part);
for(const colorId of colors)
{
const color = palette.getColor(colorId);
if(!color) continue;
clubLevel = Math.max(color.clubLevel, clubLevel);
}
}
}
if(!searchParts) searchParts = this._structure.getBodyPartsUnordered(AvatarSetType.FULL);
for(const part of searchParts)
{
const set = figureData.getSetType(part);
if(!set) continue;
if(parts.indexOf(part) === -1) clubLevel = Math.max(set.optionalFromClubLevel(gender), clubLevel);
}
return clubLevel;
}
public isValidFigureSetForGender(setId: number, gender: string): boolean
{
const structure = this.structureData;
const partSet = structure.getFigurePartSet(setId);
return !!(partSet && ((partSet.gender.toUpperCase() === 'U') || (partSet.gender.toUpperCase() === gender.toUpperCase())));
}
public getFigureStringWithFigureIds(figure: string, gender: string, _arg_3: number[]): string
{
const container = new FigureDataContainer();
container.loadAvatarData(figure, gender);
const partSets: IFigurePartSet[] = this.resolveFigureSets(_arg_3);
for(const partSet of partSets)
{
container.savePartData(partSet.type, partSet.id, container.getColourIds(partSet.type));
}
return container.getFigureString();
}
private resolveFigureSets(setIds: number[]): IFigurePartSet[]
{
const structure = this.structureData;
const partSets: IFigurePartSet[] = [];
for(const setId of setIds)
{
const partSet = structure.getFigurePartSet(setId);
if(partSet) partSets.push(partSet);
}
return partSets;
}
public getMandatoryAvatarPartSetIds(k: string, _arg_2: number): string[]
{
if(!this._structure) return null;
return this._structure.getMandatorySetTypeIds(k, _arg_2);
}
public getAssetByName(name: string): IGraphicAsset
{
return this._aliasCollection.getAsset(name);
}
public get assets(): IAssetManager
{
return GetAssetManager();
}
public get structure(): AvatarStructure
{
return this._structure;
}
public get structureData(): IStructureData
{
if(this._structure) return this._structure.figureData;
return null;
}
public get downloadManager(): AvatarAssetDownloadManager
{
return this._avatarAssetDownloadManager;
}
}
+619
View File
@@ -0,0 +1,619 @@
import { AvatarDirectionAngle, IActionDefinition, IActiveActionData, IAssetAnimation, IAssetManager, IAvatarFigureContainer, IAvatarImage, IAvatarRenderManager, IFigureData, IFigurePartSet, IPartColor, IStructureData } from '@nitrots/api';
import { Point } from 'pixi.js';
import { AvatarImagePartContainer } from './AvatarImagePartContainer';
import { AvatarRenderManager } from './AvatarRenderManager';
import { ActionDefinition, AvatarActionManager } from './actions';
import { Animation, AnimationManager, AvatarAnimationLayerData } from './animation';
import { AvatarModelGeometry } from './geometry';
import { AnimationAction, AvatarAnimationData, AvatarAnimationFrame, AvatarCanvas, FigureSetData, PartSetsData } from './structure';
export class AvatarStructure
{
private _renderManager: AvatarRenderManager;
private _geometry: AvatarModelGeometry;
private _figureData: FigureSetData;
private _partSetsData: PartSetsData;
private _animationData: AvatarAnimationData;
private _animationManager: AnimationManager;
private _mandatorySetTypeIds: { [index: string]: { [index: number]: string[] } };
private _actionManager: AvatarActionManager;
private _defaultAction: IActionDefinition;
constructor(renderManager: AvatarRenderManager)
{
this._renderManager = renderManager;
this._geometry = null;
this._figureData = new FigureSetData();
this._partSetsData = new PartSetsData();
this._animationData = new AvatarAnimationData();
this._animationManager = new AnimationManager();
this._mandatorySetTypeIds = {};
this._actionManager = null;
this._defaultAction = null;
}
public init(): void
{
}
public initGeometry(k: any): void
{
if(!k) return;
this._geometry = new AvatarModelGeometry(k);
}
public initActions(k: IAssetManager, _arg_2: any): void
{
if(!_arg_2) return;
this._actionManager = new AvatarActionManager(k, _arg_2);
this._defaultAction = this._actionManager.getDefaultAction();
}
public updateActions(data: any): void
{
this._actionManager.updateActions(data);
this._defaultAction = this._actionManager.getDefaultAction();
}
public initPartSets(k: any): boolean
{
if(!k) return false;
if(this._partSetsData.parse(k))
{
this._partSetsData.getPartDefinition('ri').appendToFigure = true;
this._partSetsData.getPartDefinition('li').appendToFigure = true;
return true;
}
return false;
}
public initAnimation(k: any): boolean
{
if(!k) return false;
return this._animationData.parse(k);
}
public initFigureData(k: IFigureData): boolean
{
if(!k) return false;
return this._figureData.parse(k);
}
public injectFigureData(data: IFigureData): void
{
this._figureData.injectJSON(data);
}
public registerAnimations(k: IAssetManager, _arg_2: string = 'fx', _arg_3: number = 200): void
{
let index = 0;
while(index < _arg_3)
{
const collection = k.getCollection((_arg_2 + index));
if(collection)
{
const animationData = collection.data;
this._animationManager.registerAnimation(this, animationData.animations);
}
index++;
}
}
public registerAnimation(data: { [index: string]: IAssetAnimation }): void
{
this._animationManager.registerAnimation(this, data);
}
public getPartColor(k: IAvatarFigureContainer, _arg_2: string, _arg_3: number = 0): IPartColor
{
const _local_4 = k.getPartColorIds(_arg_2);
if((!(_local_4)) || (_local_4.length < _arg_3)) return null;
const _local_5 = this._figureData.getSetType(_arg_2);
if(_local_5 == null) return null;
const _local_6 = this._figureData.getPalette(_local_5.paletteID);
if(!_local_6) return null;
return _local_6.getColor(_local_4[_arg_3]);
}
public getBodyPartData(animation: string, frameCount: number, spriteId: string): AvatarAnimationLayerData
{
return this._animationManager.getLayerData(animation, frameCount, spriteId) as AvatarAnimationLayerData;
}
public getAnimation(k: string): Animation
{
return this._animationManager.getAnimation(k) as Animation;
}
public getActionDefinition(k: string): ActionDefinition
{
return this._actionManager.getActionDefinition(k);
}
public getActionDefinitionWithState(k: string): ActionDefinition
{
return this._actionManager.getActionDefinitionWithState(k);
}
public isMainAvatarSet(k: string): boolean
{
return this._geometry.isMainAvatarSet(k);
}
public sortActions(k: IActiveActionData[]): IActiveActionData[]
{
return this._actionManager.sortActions(k);
}
public maxFrames(k: IActiveActionData[]): number
{
let _local_2 = 0;
for(const _local_3 of k)
{
_local_2 = Math.max(_local_2, this._animationData.getFrameCount(_local_3.definition));
}
return _local_2;
}
public getMandatorySetTypeIds(k: string, _arg_2: number): string[]
{
if(!this._mandatorySetTypeIds[k])
{
this._mandatorySetTypeIds[k] = [];
}
if(this._mandatorySetTypeIds[k][_arg_2])
{
return this._mandatorySetTypeIds[k][_arg_2];
}
this._mandatorySetTypeIds[k][_arg_2] = this._figureData.getMandatorySetTypeIds(k, _arg_2);
return this._mandatorySetTypeIds[k][_arg_2];
}
public getDefaultPartSet(k: string, _arg_2: string): IFigurePartSet
{
return this._figureData.getDefaultPartSet(k, _arg_2);
}
public getCanvasOffsets(k: IActiveActionData[], _arg_2: string, _arg_3: number): number[]
{
return this._actionManager.getCanvasOffsets(k, _arg_2, _arg_3);
}
public getCanvas(k: string, _arg_2: string): AvatarCanvas
{
return this._geometry.getCanvas(k, _arg_2);
}
public removeDynamicItems(k: IAvatarImage): void
{
this._geometry.removeDynamicItems(k);
}
public getActiveBodyPartIds(k: IActiveActionData, _arg_2: IAvatarImage): string[]
{
let _local_3: string[] = [];
const _local_4: string[] = [];
const _local_5 = k.definition.geometryType;
if(k.definition.isAnimation)
{
const _local_7 = ((k.definition.state + '.') + k.actionParameter);
const _local_8 = this._animationManager.getAnimation(_local_7);
if(_local_8)
{
_local_3 = _local_8.getAnimatedBodyPartIds(0, k.overridingAction);
if(_local_8.hasAddData())
{
const _local_11 = {
id: '',
x: 0,
y: 0,
z: 0,
radius: 0.01,
nx: 0,
ny: 0,
nz: -1,
double: 1
};
const _local_12 = {
setType: ''
};
for(const _local_13 of _local_8.addData)
{
const _local_6 = this._geometry.getBodyPart(_local_5, _local_13.align);
if(_local_6)
{
_local_11.id = _local_13.id;
_local_6.addPart(_local_11, _arg_2);
_local_12.setType = _local_13.id;
const _local_10 = this._partSetsData.addPartDefinition(_local_12);
_local_10.appendToFigure = true;
if(_local_13.base === '') _local_10.staticId = 1;
if(_local_4.indexOf(_local_6.id) === -1) _local_4.push(_local_6.id);
}
}
}
}
for(const _local_9 of _local_3)
{
const _local_6 = this._geometry.getBodyPart(_local_5, _local_9);
if(_local_6 && (_local_4.indexOf(_local_6.id) === -1)) _local_4.push(_local_6.id);
}
}
else
{
_local_3 = this._partSetsData.getActiveParts(k.definition);
for(const _local_14 of _local_3)
{
const _local_6 = this._geometry.getBodyPartOfItem(_local_5, _local_14, _arg_2);
if(_local_6 && (_local_4.indexOf(_local_6.id) === -1)) _local_4.push(_local_6.id);
}
}
return _local_4;
}
public getBodyPartsUnordered(k: string): string[]
{
return this._geometry.getBodyPartIdsInAvatarSet(k);
}
public getBodyParts(k: string, _arg_2: string, _arg_3: number): string[]
{
const _local_4 = AvatarDirectionAngle.DIRECTION_TO_ANGLE[_arg_3];
return this._geometry.getBodyPartsAtAngle(k, _local_4, _arg_2);
}
public getFrameBodyPartOffset(k: IActiveActionData, _arg_2: number, _arg_3: number, _arg_4: string): Point
{
const _local_5 = this._animationData.getAction(k.definition);
if(_local_5) return _local_5.getFrameBodyPartOffset(_arg_2, _arg_3, _arg_4);
return AnimationAction.DEFAULT_OFFSET;
}
public getParts(k: string, _arg_2: IAvatarFigureContainer, _arg_3: IActiveActionData, _arg_4: string, _arg_5: number, removes: string[], _arg_7: IAvatarImage, _arg_8: Map<string, string> = null): AvatarImagePartContainer[]
{
const _local_10: Animation = null;
let _local_34: IActionDefinition = null;
let _local_20: AvatarAnimationFrame[] = [];
let _local_36: IPartColor = null;
if(!_arg_3 == null) return [];
const _local_9 = this._partSetsData.getActiveParts(_arg_3.definition);
const _local_11: AvatarImagePartContainer[] = [];
let _local_14: any[] = [0];
const _local_15 = this._animationData.getAction(_arg_3.definition);
if(_arg_3.definition.isAnimation)
{
const _local_24 = ((_arg_3.definition.state + '.') + _arg_3.actionParameter);
const _local_10 = this._animationManager.getAnimation(_local_24);
if(_local_10)
{
_local_14 = this.getPopulatedArray(_local_10.frameCount(_arg_3.overridingAction));
for(const _local_25 of _local_10.getAnimatedBodyPartIds(0, _arg_3.overridingAction))
{
if(_local_25 === k)
{
const _local_26 = this._geometry.getBodyPart(_arg_4, _local_25);
if(_local_26)
{
for(const _local_27 of _local_26.getDynamicParts(_arg_7))
{
_local_9.push(_local_27.id);
}
}
}
}
}
}
const _local_16 = this._geometry.getParts(_arg_4, k, _arg_5, _local_9, _arg_7);
const _local_21 = _arg_2.getPartTypeIds();
for(const _local_17 of _local_21)
{
if(_arg_8)
{
if(_arg_8.get(_local_17)) continue;
}
const _local_28 = _arg_2.getPartSetId(_local_17);
const _local_29 = _arg_2.getPartColorIds(_local_17);
const _local_30 = this._figureData.getSetType(_local_17);
if(_local_30)
{
const _local_31 = this._figureData.getPalette(_local_30.paletteID);
if(_local_31)
{
const _local_32 = _local_30.getPartSet(_local_28);
if(_local_32)
{
removes = removes.concat(_local_32.hiddenLayers);
for(const _local_33 of _local_32.parts)
{
if(_local_16.indexOf(_local_33.type) > -1)
{
if(_local_15)
{
const _local_19 = _local_15.getPart(_local_33.type);
if(_local_19)
{
_local_20 = _local_19.frames;
}
else
{
_local_20 = _local_14;
}
}
else
{
_local_20 = _local_14;
}
_local_34 = _arg_3.definition;
if(_local_9.indexOf(_local_33.type) === -1) _local_34 = this._defaultAction;
const _local_13 = this._partSetsData.getPartDefinition(_local_33.type);
let _local_35 = (!_local_13) ? _local_33.type : _local_13.flippedSetType;
if(!_local_35 || (_local_35 === '')) _local_35 = _local_33.type;
if(_local_29 && (_local_29.length > (_local_33.colorLayerIndex - 1)))
{
_local_36 = _local_31.getColor(_local_29[(_local_33.colorLayerIndex - 1)]);
}
const _local_37 = (_local_33.colorLayerIndex > 0);
const _local_18 = new AvatarImagePartContainer(k, _local_33.type, _local_33.id.toString(), _local_36, _local_20, _local_34, _local_37, _local_33.paletteMap, _local_35);
_local_11.push(_local_18);
}
}
}
}
}
}
const _local_22: AvatarImagePartContainer[] = [];
for(const _local_12 of _local_16)
{
let _local_39: IPartColor = null;
let _local_38 = false;
const _local_40 = ((_arg_8) && (_arg_8.get(_local_12)));
for(const _local_23 of _local_11)
{
if(_local_23.partType === _local_12)
{
if(_local_40)
{
_local_39 = _local_23.color;
}
else
{
_local_38 = true;
if(removes.indexOf(_local_12) === -1) _local_22.push(_local_23);
}
}
}
if(!_local_38)
{
if(_local_40)
{
const _local_41 = _arg_8.get(_local_12);
let _local_42 = 0;
let _local_43 = 0;
while(_local_43 < _local_41.length)
{
_local_42 = (_local_42 + _local_41.charCodeAt(_local_43));
_local_43++;
}
if(_local_15)
{
const _local_19 = _local_15.getPart(_local_12);
if(_local_19)
{
_local_20 = _local_19.frames;
}
else
{
_local_20 = _local_14;
}
}
else
{
_local_20 = _local_14;
}
const _local_18 = new AvatarImagePartContainer(k, _local_12, _local_41, _local_39, _local_20, _arg_3.definition, (!(_local_39 == null)), -1, _local_12, false, 1);
_local_22.push(_local_18);
}
else
{
if(_local_9.indexOf(_local_12) > -1)
{
const _local_44 = this._geometry.getBodyPartOfItem(_arg_4, _local_12, _arg_7);
if(k !== _local_44.id)
{
//
}
else
{
const _local_13 = this._partSetsData.getPartDefinition(_local_12);
let _local_45 = false;
let _local_46 = 1;
if(_local_13.appendToFigure)
{
let _local_47 = '1';
if(_arg_3.actionParameter !== '')
{
_local_47 = _arg_3.actionParameter;
}
if(_local_13.hasStaticId())
{
_local_47 = _local_13.staticId.toString();
}
if(_local_10 != null)
{
const _local_48 = _local_10.getAddData(_local_12);
if(_local_48)
{
_local_45 = _local_48.isBlended;
_local_46 = _local_48.blend;
}
}
if(_local_15)
{
const _local_19 = _local_15.getPart(_local_12);
if(_local_19)
{
_local_20 = _local_19.frames;
}
else
{
_local_20 = _local_14;
}
}
else
{
_local_20 = _local_14;
}
const _local_18 = new AvatarImagePartContainer(k, _local_12, _local_47, null, _local_20, _arg_3.definition, false, -1, _local_12, _local_45, _local_46);
_local_22.push(_local_18);
}
}
}
}
}
}
return _local_22;
}
private getPopulatedArray(k: number): number[]
{
const _local_2: number[] = [];
let index = 0;
while(index < k)
{
_local_2.push(index);
index++;
}
return _local_2;
}
public getItemIds(): string[]
{
if(this._actionManager)
{
const k = this._actionManager.getActionDefinition('CarryItem').params;
const _local_2 = [];
for(const _local_3 of k.values()) _local_2.push(_local_3);
return _local_2;
}
return [];
}
public get renderManager(): IAvatarRenderManager
{
return this._renderManager;
}
public get figureData(): IStructureData
{
return this._figureData;
}
public get partData(): PartSetsData
{
return this._partSetsData;
}
public get animationManager(): AnimationManager
{
return this._animationManager;
}
}
@@ -0,0 +1,75 @@
import { IAssetAnimation, IAssetManager, IEffectAssetDownloadLibrary } from '@nitrots/api';
import { AvatarRenderEffectLibraryEvent, GetEventDispatcher, NitroEventType } from '@nitrots/events';
export class EffectAssetDownloadLibrary implements IEffectAssetDownloadLibrary
{
public static DOWNLOAD_COMPLETE: string = 'EADL_DOWNLOAD_COMPLETE';
private static NOT_LOADED: number = 0;
private static LOADING: number = 1;
private static LOADED: number = 2;
private _state: number = EffectAssetDownloadLibrary.NOT_LOADED;
private _libraryName: string;
private _revision: string;
private _downloadUrl: string;
private _assetManager: IAssetManager;
private _animation: { [index: string]: IAssetAnimation } = null;
constructor(libraryName: string, revision: string, downloadUrl: string, assetManager: IAssetManager)
{
this._libraryName = libraryName;
this._revision = revision;
this._downloadUrl = downloadUrl;
this._assetManager = assetManager;
this._downloadUrl = this._downloadUrl.replace(/%libname%/gi, this._libraryName);
this._downloadUrl = this._downloadUrl.replace(/%revision%/gi, this._revision);
this.checkIsLoaded();
}
public async downloadAsset(): Promise<void>
{
if(!this._assetManager || (this._state === EffectAssetDownloadLibrary.LOADING) || (this._state === EffectAssetDownloadLibrary.LOADED)) return;
if(!this.checkIsLoaded())
{
this._state = EffectAssetDownloadLibrary.LOADING;
const status = await this._assetManager.downloadAsset(this._downloadUrl);
if(!status) throw new Error('Could not download asset');
}
if(this.checkIsLoaded()) GetEventDispatcher().dispatchEvent(new AvatarRenderEffectLibraryEvent(NitroEventType.AVATAR_EFFECT_DOWNLOADED, this));
}
private checkIsLoaded(): boolean
{
const asset = this._assetManager.getCollection(this._libraryName);
if(!asset) return false;
this._state = EffectAssetDownloadLibrary.LOADED;
this._animation = asset.data.animations;
return true;
}
public get libraryName(): string
{
return this._libraryName;
}
public get animation(): { [index: string]: IAssetAnimation }
{
return this._animation;
}
public get isLoaded(): boolean
{
return (this._state === EffectAssetDownloadLibrary.LOADED);
}
}
@@ -0,0 +1,206 @@
import { IAssetManager, IAvatarEffectListener } from '@nitrots/api';
import { GetConfiguration } from '@nitrots/configuration';
import { AvatarRenderEffectLibraryEvent, GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events';
import { AvatarStructure } from './AvatarStructure';
import { EffectAssetDownloadLibrary } from './EffectAssetDownloadLibrary';
export class EffectAssetDownloadManager
{
private _assets: IAssetManager;
private _structure: AvatarStructure;
private _missingMandatoryLibs: string[] = [];
private _effectMap: Map<string, EffectAssetDownloadLibrary[]> = new Map();
private _effectListeners: Map<string, IAvatarEffectListener[]> = new Map();
private _incompleteEffects: Map<string, EffectAssetDownloadLibrary[]> = new Map();
private _currentDownloads: EffectAssetDownloadLibrary[] = [];
private _libraryNames: string[] = [];
constructor(assets: IAssetManager, structure: AvatarStructure)
{
this._assets = assets;
this._structure = structure;
}
public async init(): Promise<void>
{
this._missingMandatoryLibs = GetConfiguration().getValue<string[]>('avatar.mandatory.effect.libraries');
const url = GetConfiguration().getValue<string>('avatar.effectmap.url');
if(!url || !url.length) throw new Error('Invalid effect map url');
const response = await fetch(url);
if(response.status !== 200) throw new Error('Invalid effect map file');
const responseData = await response.json();
this.processEffectMap(responseData.effects);
GetEventDispatcher().addEventListener(NitroEventType.AVATAR_EFFECT_DOWNLOADED, (event: AvatarRenderEffectLibraryEvent) => this.onLibraryLoaded(event));
await this.processMissingLibraries();
}
private processEffectMap(data: any): void
{
if(!data) return;
const downloadUrl = GetConfiguration().getValue<string>('avatar.asset.effect.url');
for(const effect of data)
{
if(!effect) continue;
const id = (effect.id as string);
const libraryName = (effect.lib as string);
const revision = (effect.revision || '');
if(this._libraryNames.indexOf(libraryName) >= 0) continue;
this._libraryNames.push(libraryName);
const downloadLibrary = new EffectAssetDownloadLibrary(libraryName, revision, downloadUrl, this._assets);
let existing = this._effectMap.get(id);
if(!existing) existing = [];
existing.push(downloadLibrary);
this._effectMap.set(id, existing);
}
}
private async processMissingLibraries(): Promise<void>
{
const promises: Promise<void>[] = [];
this._missingMandatoryLibs.forEach(value =>
{
const libraries = this._effectMap.get(value);
if(libraries) for(const library of libraries) promises.push(library.downloadAsset());
});
this._missingMandatoryLibs = [];
await Promise.all(promises);
}
private onLibraryLoaded(event: AvatarRenderEffectLibraryEvent): void
{
if(!event || !event.library) return;
const loadedEffects: string[] = [];
this._structure.registerAnimation(event.library.animation);
for(const [id, libraries] of this._incompleteEffects.entries())
{
let isReady = true;
for(const library of libraries)
{
if(!library || library.isLoaded) continue;
isReady = false;
break;
}
if(isReady)
{
loadedEffects.push(id);
const listeners = this._effectListeners.get(id);
for(const listener of listeners)
{
if(!listener || listener.disposed) continue;
listener.resetEffect(parseInt(id));
}
this._effectListeners.delete(id);
GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.AVATAR_EFFECT_LOADED));
}
}
for(const id of loadedEffects) this._incompleteEffects.delete(id);
let index = 0;
while(index < this._currentDownloads.length)
{
const download = this._currentDownloads[index];
if(download)
{
if(download.libraryName === event.library.libraryName) this._currentDownloads.splice(index, 1);
}
index++;
}
}
public isAvatarEffectReady(effect: number): boolean
{
return !this.getAvatarEffectPendingLibraries(effect).length;
}
private getAvatarEffectPendingLibraries(id: number): EffectAssetDownloadLibrary[]
{
const pendingLibraries: EffectAssetDownloadLibrary[] = [];
if(!this._structure) return pendingLibraries;
const libraries = this._effectMap.get(id.toString());
if(libraries)
{
for(const library of libraries)
{
if(!library || library.isLoaded) continue;
if(pendingLibraries.indexOf(library) === -1) pendingLibraries.push(library);
}
}
return pendingLibraries;
}
public downloadAvatarEffect(id: number, listener: IAvatarEffectListener): void
{
const pendingLibraries = this.getAvatarEffectPendingLibraries(id);
if(pendingLibraries && pendingLibraries.length)
{
if(listener && !listener.disposed)
{
let listeners = this._effectListeners.get(id.toString());
if(!listeners) listeners = [];
listeners.push(listener);
this._effectListeners.set(id.toString(), listeners);
}
this._incompleteEffects.set(id.toString(), pendingLibraries);
for(const library of pendingLibraries)
{
if(!library) continue;
library.downloadAsset();
}
}
else
{
if(listener && !listener.disposed) listener.resetEffect(id);
}
}
}
+241
View File
@@ -0,0 +1,241 @@
export class FigureDataContainer
{
private static MALE: string = 'M';
private static FEMALE: string = 'F';
private static UNISEX: string = 'U';
private static SCALE: string = 'h';
private static STD: string = 'std';
private static DEFAULT_FRAME: string = '0';
private static HD: string = 'hd';
private static HAIR: string = 'hr';
private static HAT: string = 'ha';
private static HEAD_ACCESSORIES: string = 'he';
private static EYE_ACCESSORIES: string = 'ea';
private static FACE_ACCESSORIES: string = 'fa';
private static JACKET: string = 'cc';
private static SHIRT: string = 'ch';
private static CHEST_ACCESSORIES: string = 'ca';
private static CHEST_PRINTS: string = 'cp';
private static TROUSERS: string = 'lg';
private static SHOES: string = 'sh';
private static TROUSER_ACCESSORIES: string = 'wa';
private static BLOCKED_FX_TYPES: number[] = [28, 29, 30, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 68];
private _data: Map<string, number>;
private _colors: Map<string, number[]>;
private _gender: string = 'M';
private _isDisposed: boolean;
private _avatarEffectType: number = -1;
public loadAvatarData(figure: string, gender: string): void
{
this._data = new Map();
this._colors = new Map();
this._gender = gender;
this.parseFigureString(figure);
}
public dispose(): void
{
this._data = null;
this._colors = null;
this._isDisposed = true;
}
public get disposed(): boolean
{
return this._isDisposed;
}
private parseFigureString(k: string): void
{
if(!k) return;
for(const set of k.split('.'))
{
const _local_3 = set.split('-');
if(_local_3.length > 0)
{
const part = _local_3[0];
const setId = parseInt(_local_3[1]);
const colors: number[] = [];
let i = 2;
while(i < _local_3.length)
{
colors.push(parseInt(_local_3[i]));
i++;
}
if(!colors.length) colors.push(0);
this.savePartSetId(part, setId, false);
this.savePartSetColourId(part, colors, false);
}
}
}
public hasSetType(k: string): boolean
{
return !!this._data.get(k);
}
public getPartSetId(k: string): number
{
if(this.hasSetType(k)) return this._data.get(k);
return -1;
}
public getColourIds(k: string): number[]
{
if(this._colors.get(k)) return this._colors.get(k);
return [];
}
public getFigureString(): string
{
let figure = '';
const sets: string[] = [];
for(const [key, value] of this._data.entries())
{
let set = ((key + '-') + value);
const colors = this._colors.get(key);
if(colors) for(const color of colors) set = (set + ('-' + color));
sets.push(set);
}
let i = 0;
while(i < sets.length)
{
figure = (figure + sets[i]);
if(i < (sets.length - 1)) figure = (figure + '.');
i++;
}
return figure;
}
public savePartData(k: string, _arg_2: number, _arg_3: number[], _arg_4: boolean = false): void
{
this.savePartSetId(k, _arg_2, _arg_4);
this.savePartSetColourId(k, _arg_3, _arg_4);
}
private savePartSetId(k: string, _arg_2: number, _arg_3: boolean = true): void
{
switch(k)
{
case FigureDataContainer.HD:
case FigureDataContainer.HAIR:
case FigureDataContainer.HAT:
case FigureDataContainer.HEAD_ACCESSORIES:
case FigureDataContainer.EYE_ACCESSORIES:
case FigureDataContainer.FACE_ACCESSORIES:
case FigureDataContainer.SHIRT:
case FigureDataContainer.JACKET:
case FigureDataContainer.CHEST_ACCESSORIES:
case FigureDataContainer.CHEST_PRINTS:
case FigureDataContainer.TROUSERS:
case FigureDataContainer.SHOES:
case FigureDataContainer.TROUSER_ACCESSORIES:
if(_arg_2 >= 0)
{
this._data.set(k, _arg_2);
}
else
{
this._data.delete(k);
}
}
}
public savePartSetColourId(k: string, _arg_2: number[], _arg_3: boolean = true): void
{
switch(k)
{
case FigureDataContainer.HD:
case FigureDataContainer.HAIR:
case FigureDataContainer.HAT:
case FigureDataContainer.HEAD_ACCESSORIES:
case FigureDataContainer.EYE_ACCESSORIES:
case FigureDataContainer.FACE_ACCESSORIES:
case FigureDataContainer.SHIRT:
case FigureDataContainer.JACKET:
case FigureDataContainer.CHEST_ACCESSORIES:
case FigureDataContainer.CHEST_PRINTS:
case FigureDataContainer.TROUSERS:
case FigureDataContainer.SHOES:
case FigureDataContainer.TROUSER_ACCESSORIES:
this._colors.set(k, _arg_2);
return;
}
}
public getFigureStringWithFace(k: number): string
{
const partSets: string[] = [FigureDataContainer.HD];
let figure = '';
const sets: string[] = [];
for(const part of partSets)
{
const colors = this._colors.get(part);
if(colors)
{
let setId = this._data.get(part);
if(part === FigureDataContainer.HD) setId = k;
let set = ((part + '-') + setId);
if(setId >= 0)
{
let i = 0;
while(i < colors.length)
{
set = (set + ('-' + colors[i]));
i++;
}
}
sets.push(set);
}
}
let i = 0;
while(i < sets.length)
{
figure = (figure + sets[i]);
if(i < (sets.length - 1)) figure = (figure + '.');
i++;
}
return figure;
}
public get gender(): string
{
return this._gender;
}
}
@@ -0,0 +1,5 @@
import { AvatarRenderManager } from './AvatarRenderManager';
const avatarRenderManager = new AvatarRenderManager();
export const GetAvatarRenderManager = () => avatarRenderManager;
@@ -0,0 +1,18 @@
import { AssetAliasCollection } from './alias';
import { AvatarFigureContainer } from './AvatarFigureContainer';
import { AvatarImage } from './AvatarImage';
import { AvatarStructure } from './AvatarStructure';
import { EffectAssetDownloadManager } from './EffectAssetDownloadManager';
export class PlaceHolderAvatarImage extends AvatarImage
{
constructor(k: AvatarStructure, _arg_2: AssetAliasCollection, _arg_3: AvatarFigureContainer, _arg_4: string, _arg_5: EffectAssetDownloadManager)
{
super(k, _arg_2, _arg_3, _arg_4, _arg_5, null);
}
public isPlaceholder(): boolean
{
return true;
}
}
@@ -0,0 +1,220 @@
import { IActionDefinition } from '@nitrots/api';
import { ActionType } from './ActionType';
export class ActionDefinition implements IActionDefinition
{
private _id: string;
private _state: string;
private _precedence: number;
private _activePartSet: string;
private _assetPartDefinition: string;
private _lay: string;
private _geometryType: string;
private _isMain: boolean;
private _isDefault: boolean;
private _isAnimation: boolean;
private _startFromFrameZero: boolean;
private _prevents: string[];
private _preventHeadTurn: boolean;
private _types: Map<number, ActionType> = new Map();
private _params: Map<string, string> = new Map();
private _defaultParameterValue: string = '';
private _canvasOffsets: Map<string, Map<number, number[]>> = new Map();
constructor(data: any)
{
this._id = data.id;
this._state = data.state;
this._precedence = data.precedence;
this._activePartSet = data.activePartSet;
this._assetPartDefinition = data.assetPartDefinition;
this._lay = data.lay;
this._geometryType = data.geometryType;
this._isMain = data.main || false;
this._isDefault = data.isDefault || false;
this._isAnimation = data.animation || false;
this._startFromFrameZero = data.startFromFrameZero || false;
this._prevents = data.prevents || [];
this._preventHeadTurn = data.preventHeadTurn || false;
if(data.params && (data.params.length > 0))
{
for(const param of data.params)
{
if(!param) continue;
if(param.id === 'default') this._defaultParameterValue = param.value;
else this._params.set(param.id, param.value);
}
}
if(data.types && (data.types.length > 0))
{
for(const type of data.types)
{
if(!type) continue;
const action = new ActionType(type);
this._types.set(action.id, action);
}
}
}
public setOffsets(k: string, _arg_2: number, _arg_3: number[]): void
{
if(!this._canvasOffsets) this._canvasOffsets = new Map();
let existing = this._canvasOffsets.get(k);
if(!existing)
{
existing = new Map();
this._canvasOffsets.set(k, existing);
}
existing.set(_arg_2, _arg_3);
}
public getOffsets(k: string, _arg_2: number): number[]
{
if(!this._canvasOffsets) return null;
const existing = this._canvasOffsets.get(k);
if(!existing) return null;
return existing.get(_arg_2);
}
public getType(id: string): ActionType
{
if(!id) return null;
const existing = this._types.get(parseInt(id));
if(!existing) return null;
return existing;
}
public getParameterValue(id: string): string
{
if(!id) return '';
const existing = this._params.get(id);
if(!existing) return this._defaultParameterValue;
return existing;
}
public getPrevents(type: string): string[]
{
return this._prevents.concat(this.getTypePrevents(type));
}
private getTypePrevents(type: string): string[]
{
if(!type) return [];
const existing = this._types.get(parseInt(type));
if(!existing) return [];
return existing.prevents;
}
public getPreventHeadTurn(k: string): boolean
{
if(!k) return this._preventHeadTurn;
const type = this.getType(k);
if(!type) return this._preventHeadTurn;
return type.preventHeadTurn;
}
public isAnimated(k: string): boolean
{
if(!k) return true;
const type = this.getType(k);
if(!type) return true;
return type.isAnimated;
}
public get id(): string
{
return this._id;
}
public get state(): string
{
return this._state;
}
public get precedence(): number
{
return this._precedence;
}
public get activePartSet(): string
{
return this._activePartSet;
}
public get assetPartDefinition(): string
{
return this._assetPartDefinition;
}
public get lay(): string
{
return this._lay;
}
public get geometryType(): string
{
return this._geometryType;
}
public get isMain(): boolean
{
return this._isMain;
}
public get isDefault(): boolean
{
return this._isDefault;
}
public get isAnimation(): boolean
{
return this._isAnimation;
}
public get startFromFrameZero(): boolean
{
return this._startFromFrameZero;
}
public get prevents(): string[]
{
return this._prevents;
}
public get preventHeadTurn(): boolean
{
return this._preventHeadTurn;
}
public get params(): Map<string, string>
{
return this._params;
}
}
+44
View File
@@ -0,0 +1,44 @@
export class ActionType
{
private _id: number;
private _value: number;
private _prevents: string[];
private _preventHeadTurn: boolean;
private _isAnimated: boolean;
constructor(data: any)
{
this._id = parseInt(data.id);
this._value = parseInt(data.id);
this._prevents = data.prevents || [];
this._preventHeadTurn = data.preventHeadTurn || false;
this._isAnimated = true;
if((data.animated !== undefined) && (data.animated === false)) this._isAnimated = false;
}
public get id(): number
{
return this._id;
}
public get value(): number
{
return this._value;
}
public get prevents(): string[]
{
return this._prevents;
}
public get preventHeadTurn(): boolean
{
return this._preventHeadTurn;
}
public get isAnimated(): boolean
{
return this._isAnimated;
}
}
@@ -0,0 +1,73 @@
import { IActionDefinition, IActiveActionData } from '@nitrots/api';
export class ActiveActionData implements IActiveActionData
{
private _actionType: string;
private _actionParameter: string;
private _definition: IActionDefinition;
private _startFrame: number;
private _overridingAction: string;
constructor(action: string, parameter: string = '', startFrame: number = 0)
{
this._actionType = action || '';
this._actionParameter = parameter || '';
this._definition = null;
this._startFrame = startFrame || 0;
this._overridingAction = null;
}
public dispose(): void
{
this._actionType = null;
this._actionParameter = null;
this._definition = null;
}
public get id(): string
{
if(!this._definition) return '';
return this._definition.id + '_' + this._actionParameter;
}
public get actionType(): string
{
return this._actionType;
}
public get actionParameter(): string
{
return this._actionParameter;
}
public set actionParameter(parameter: string)
{
this._actionParameter = parameter;
}
public get definition(): IActionDefinition
{
return this._definition;
}
public set definition(definition: IActionDefinition)
{
this._definition = definition;
}
public get startFrame(): number
{
return this._startFrame;
}
public get overridingAction(): string
{
return this._overridingAction;
}
public set overridingAction(action: string)
{
this._overridingAction = action;
}
}
@@ -0,0 +1,186 @@
import { IActiveActionData, IAssetManager } from '@nitrots/api';
import { ActionDefinition } from './ActionDefinition';
export class AvatarActionManager
{
private _assets: IAssetManager;
private _actions: Map<string, ActionDefinition>;
private _defaultAction: ActionDefinition;
constructor(k: IAssetManager, data: any)
{
this._assets = k;
this._actions = new Map();
this._defaultAction = null;
this.updateActions(data);
}
public updateActions(data: any): void
{
if(!data) return;
for(const action of data.actions)
{
if(!action || !action.state) continue;
const definition = new ActionDefinition(action);
this._actions.set(definition.state, definition);
}
if(data.actionOffsets) this.parseActionOffsets(data.actionOffsets);
}
private parseActionOffsets(offsets: any): void
{
if(!offsets || !offsets.length) return;
for(const offset of offsets)
{
const action = this._actions.get(offset.action);
if(!action) continue;
for(const canvasOffset of offset.offsets)
{
const size = (canvasOffset.size || '');
const direction = canvasOffset.direction;
if((size === '') || (direction === undefined)) continue;
const x = (canvasOffset.x || 0);
const y = (canvasOffset.y || 0);
const z = (canvasOffset.z || 0);
action.setOffsets(size, direction, [x, y, z]);
}
}
}
public getActionDefinition(id: string): ActionDefinition
{
if(!id) return null;
for(const action of this._actions.values())
{
if(!action || (action.id !== id)) continue;
return action;
}
return null;
}
public getActionDefinitionWithState(state: string): ActionDefinition
{
const existing = this._actions.get(state);
if(!existing) return null;
return existing;
}
public getDefaultAction(): ActionDefinition
{
if(this._defaultAction) return this._defaultAction;
for(const action of this._actions.values())
{
if(!action || !action.isDefault) continue;
this._defaultAction = action;
return action;
}
return null;
}
public getCanvasOffsets(k: IActiveActionData[], _arg_2: string, _arg_3: number): number[]
{
let canvasOffsets: number[] = [];
for(const activeAction of k)
{
if(!activeAction) continue;
const action = this._actions.get(activeAction.actionType);
const offsets = action && action.getOffsets(_arg_2, _arg_3);
if(offsets) canvasOffsets = offsets;
}
return canvasOffsets;
}
public sortActions(actions: IActiveActionData[]): IActiveActionData[]
{
if(!actions) return null;
actions = this.filterActions(actions);
const validatedActions: IActiveActionData[] = [];
for(const action of actions)
{
if(!action) continue;
const definition = this._actions.get(action.actionType);
if(!definition) continue;
action.definition = definition;
validatedActions.push(action);
}
validatedActions.sort(this.sortByPrecedence);
return validatedActions;
}
private filterActions(actions: IActiveActionData[]): IActiveActionData[]
{
let preventions: string[] = [];
const activeActions: IActiveActionData[] = [];
for(const action of actions)
{
if(!action) continue;
const localAction = this._actions.get(action.actionType);
if(localAction) preventions = preventions.concat(localAction.getPrevents(action.actionParameter));
}
for(const action of actions)
{
if(!action) continue;
let actionType = action.actionType;
if(action.actionType === 'fx') actionType = (actionType + ('.' + action.actionParameter));
if(preventions.indexOf(actionType) >= 0) continue;
activeActions.push(action);
}
return activeActions;
}
private sortByPrecedence(actionOne: IActiveActionData, actionTwo: IActiveActionData): number
{
if(!actionOne || !actionTwo) return 0;
const precedenceOne = actionOne.definition.precedence;
const precedenceTwo = actionTwo.definition.precedence;
if(precedenceOne < precedenceTwo) return 1;
if(precedenceOne > precedenceTwo) return -1;
return 0;
}
}
+4
View File
@@ -0,0 +1,4 @@
export * from './ActionDefinition';
export * from './ActionType';
export * from './ActiveActionData';
export * from './AvatarActionManager';
+37
View File
@@ -0,0 +1,37 @@
import { IAssetAlias } from '@nitrots/api';
export class AssetAlias
{
private _name: string;
private _link: string;
private _flipH: boolean;
private _flipV: boolean;
constructor(name: string, alias: IAssetAlias)
{
this._name = name;
this._link = alias.link;
this._flipH = alias.flipH;
this._flipV = alias.flipV;
}
public get name(): string
{
return this._name;
}
public get link(): string
{
return this._link;
}
public get flipH(): boolean
{
return this._flipH;
}
public get flipV(): boolean
{
return this._flipV;
}
}
@@ -0,0 +1,89 @@
import { IAssetManager, IGraphicAsset } from '@nitrots/api';
import { AvatarRenderManager } from '../AvatarRenderManager';
import { AssetAlias } from './AssetAlias';
export class AssetAliasCollection
{
private _assets: IAssetManager;
private _aliases: Map<string, AssetAlias>;
private _avatarRenderManager: AvatarRenderManager;
private _missingAssetNames: string[];
constructor(k: AvatarRenderManager, _arg_2: IAssetManager)
{
this._avatarRenderManager = k;
this._aliases = new Map();
this._assets = _arg_2;
this._missingAssetNames = [];
}
public dispose(): void
{
this._assets = null;
this._aliases = null;
}
public reset(): void
{
this.init();
}
public init(): void
{
for(const collection of this._assets.collections.values())
{
if(!collection) continue;
const aliases = collection.data && collection.data.aliases;
if(!aliases) continue;
for(const name in aliases)
{
const alias = aliases[name];
if(!alias) continue;
this._aliases.set(name, new AssetAlias(name, alias));
}
}
}
public hasAlias(k: string): boolean
{
const alias = this._aliases.get(k);
if(alias) return true;
return false;
}
public getAssetName(k: string): string
{
let _local_2 = k;
let _local_3 = 5;
while(this.hasAlias(_local_2) && (_local_3 >= 0))
{
const _local_4 = this._aliases.get(_local_2);
_local_2 = _local_4.link;
_local_3--;
}
return _local_2;
}
public getAsset(name: string): IGraphicAsset
{
if(!this._assets) return null;
name = this.getAssetName(name);
const asset = this._assets.getAsset(name);
if(!asset) return null;
return asset;
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from './AssetAlias';
export * from './AssetAliasCollection';
@@ -0,0 +1,61 @@
import { IAssetAnimationAdd } from '@nitrots/api';
export class AddDataContainer
{
private _id: string;
private _align: string;
private _base: string;
private _ink: number;
private _blend: number;
constructor(k: IAssetAnimationAdd)
{
this._id = k.id || '';
this._align = k.align || '';
this._base = k.base || '';
this._ink = k.ink || 0;
this._blend = 0;
const _local_2 = k.blend;
if(_local_2)
{
if(_local_2.length > 0)
{
this._blend = parseInt(_local_2);
if(this._blend > 1) this._blend = (this._blend / 100);
}
}
}
public get id(): string
{
return this._id;
}
public get align(): string
{
return this._align;
}
public get base(): string
{
return this._base;
}
public get ink(): number
{
return this._ink;
}
public get blend(): number
{
return this._blend;
}
public get isBlended(): boolean
{
return this._blend !== 1;
}
}
+311
View File
@@ -0,0 +1,311 @@
import { IAnimation, IAssetAnimation, IAssetAnimationFrame } from '@nitrots/api';
import { AvatarStructure } from '../AvatarStructure';
import { AddDataContainer } from './AddDataContainer';
import { AvatarAnimationLayerData } from './AvatarAnimationLayerData';
import { AvatarDataContainer } from './AvatarDataContainer';
import { DirectionDataContainer } from './DirectionDataContainer';
import { SpriteDataContainer } from './SpriteDataContainer';
export class Animation implements IAnimation
{
private static EMPTY_ARRAY: any[] = [];
private _id: string;
private _description: string;
private _frames: AvatarAnimationLayerData[][];
private _spriteData: SpriteDataContainer[];
private _avatarData: AvatarDataContainer;
private _directionData: DirectionDataContainer;
private _removeData: string[];
private _addData: AddDataContainer[];
private _overriddenActions: Map<string, string>;
private _overrideFrames: Map<string, AvatarAnimationLayerData[][]>;
private _resetOnToggle: boolean;
constructor(k: AvatarStructure, _arg_2: IAssetAnimation)
{
this._id = _arg_2.name;
this._description = this._id;
this._frames = [];
this._spriteData = null;
this._avatarData = null;
this._directionData = null;
this._removeData = null;
this._addData = null;
this._overriddenActions = null;
this._overrideFrames = null;
this._resetOnToggle = (_arg_2.resetOnToggle || false);
if(_arg_2.sprites && _arg_2.sprites.length)
{
this._spriteData = [];
for(const sprite of _arg_2.sprites) this._spriteData.push(new SpriteDataContainer(this, sprite));
}
if(_arg_2.avatars && _arg_2.avatars.length) this._avatarData = new AvatarDataContainer(_arg_2.avatars[0]);
if(_arg_2.directions && _arg_2.directions.length) this._directionData = new DirectionDataContainer(_arg_2.directions[0]);
if(_arg_2.removes && _arg_2.removes.length)
{
this._removeData = [];
for(const remove of _arg_2.removes) this._removeData.push(remove.id);
}
if(_arg_2.adds && _arg_2.adds.length)
{
this._addData = [];
for(const add of _arg_2.adds) this._addData.push(new AddDataContainer(add));
}
if(_arg_2.overrides && _arg_2.overrides.length)
{
this._overrideFrames = new Map();
this._overriddenActions = new Map();
for(const override of _arg_2.overrides)
{
const name = override.name;
const value = override.override;
this._overriddenActions.set(value, name);
const frames: AvatarAnimationLayerData[][] = [];
this.parseFrames(frames, override.frames, k);
this._overrideFrames.set(name, frames);
}
}
this.parseFrames(this._frames, _arg_2.frames, k);
}
private parseFrames(frames: AvatarAnimationLayerData[][], _arg_2: IAssetAnimationFrame[], _arg_3: AvatarStructure): void
{
if(!_arg_2 || !_arg_2.length) return;
for(const frame of _arg_2)
{
let repeats = 1;
if(frame.repeats && (frame.repeats > 1)) repeats = frame.repeats;
let index = 0;
while(index < repeats)
{
const layers: AvatarAnimationLayerData[] = [];
if(frame.bodyparts && frame.bodyparts.length)
{
for(const bodyPart of frame.bodyparts)
{
const definition = _arg_3.getActionDefinition(bodyPart.action);
const layer = new AvatarAnimationLayerData(bodyPart, AvatarAnimationLayerData.BODYPART, definition);
layers.push(layer);
}
}
if(frame.fxs && frame.fxs.length)
{
for(const fx of frame.fxs)
{
const definition = _arg_3.getActionDefinition(fx.action);
const layer = new AvatarAnimationLayerData(fx, AvatarAnimationLayerData.FX, definition);
layers.push(layer);
}
}
frames.push(layers);
index++;
}
}
}
public frameCount(k: string = null): number
{
if(!k) return this._frames.length;
if(this._overrideFrames)
{
const _local_2 = this._overrideFrames.get(k);
if(_local_2) return _local_2.length;
}
return 0;
}
public hasOverriddenActions(): boolean
{
if(!this._overriddenActions) return false;
return (this._overriddenActions.size > 0);
}
public overriddenActionNames(): string[]
{
if(!this._overriddenActions) return null;
const keys: string[] = [];
for(const key of this._overriddenActions.keys()) keys.push(key);
return keys;
}
public overridingAction(k: string): string
{
if(!this._overriddenActions) return null;
return this._overriddenActions.get(k);
}
private getFrame(frameCount: number, _arg_2: string = null): AvatarAnimationLayerData[]
{
if(frameCount < 0) frameCount = 0;
let layers: AvatarAnimationLayerData[] = [];
if(!_arg_2)
{
if(this._frames.length > 0)
{
layers = this._frames[(frameCount % this._frames.length)];
}
}
else
{
const overrideLayers = this._overrideFrames.get(_arg_2);
if(overrideLayers && (overrideLayers.length > 0))
{
layers = overrideLayers[(frameCount % overrideLayers.length)];
}
}
return layers;
}
public getAnimatedBodyPartIds(k: number, _arg_2: string = null): string[]
{
const _local_3: string[] = [];
for(const layer of this.getFrame(k, _arg_2))
{
if(layer.type === AvatarAnimationLayerData.BODYPART)
{
_local_3.push(layer.id);
}
else if(layer.type === AvatarAnimationLayerData.FX)
{
if(this._addData && this._addData.length)
{
for(const _local_5 of this._addData)
{
if(_local_5.id === layer.id) _local_3.push(_local_5.align);
}
}
}
}
return _local_3;
}
public getLayerData(frameCount: number, spriteId: string, _arg_3: string = null): AvatarAnimationLayerData
{
for(const layer of this.getFrame(frameCount, _arg_3))
{
if(layer.id === spriteId) return layer;
if(layer.type === AvatarAnimationLayerData.FX)
{
if(this._addData && this._addData.length)
{
for(const addData of this._addData)
{
if(((addData.align === spriteId) && (addData.id === layer.id))) return layer;
}
}
}
}
return null;
}
public hasAvatarData(): boolean
{
return this._avatarData !== null;
}
public hasDirectionData(): boolean
{
return this._directionData !== null;
}
public hasAddData(): boolean
{
return this._addData !== null;
}
public getAddData(k: string): AddDataContainer
{
if(this._addData)
{
for(const _local_2 of this._addData)
{
if(_local_2.id === k) return _local_2;
}
}
return null;
}
public get id(): string
{
return this._id;
}
public get spriteData(): SpriteDataContainer[]
{
return this._spriteData || Animation.EMPTY_ARRAY;
}
public get avatarData(): AvatarDataContainer
{
return this._avatarData;
}
public get directionData(): DirectionDataContainer
{
return this._directionData;
}
public get removeData(): string[]
{
return this._removeData || Animation.EMPTY_ARRAY;
}
public get addData(): AddDataContainer[]
{
return this._addData || Animation.EMPTY_ARRAY;
}
public toString(): string
{
return this._description;
}
public get resetOnToggle(): boolean
{
return this._resetOnToggle;
}
}
@@ -0,0 +1,49 @@
import { IAnimation, IAnimationLayerData, IAnimationManager, IAssetAnimation } from '@nitrots/api';
import { AvatarStructure } from '../AvatarStructure';
import { Animation } from './Animation';
export class AnimationManager implements IAnimationManager
{
private _animations: Map<string, Animation>;
constructor()
{
this._animations = new Map();
}
public registerAnimation(structure: AvatarStructure, _arg_2: { [index: string]: IAssetAnimation }): boolean
{
if(!_arg_2) return false;
const animationData = _arg_2[Object.keys(_arg_2)[0]];
const animation = new Animation(structure, animationData);
this._animations.set(animationData.name, animation);
return true;
}
public getAnimation(animation: string): Animation
{
const existing = this._animations.get(animation);
if(!existing) return null;
return existing;
}
public getLayerData(animation: string, frameCount: number, spriteId: string): IAnimationLayerData
{
const existing = this.getAnimation(animation);
if(!existing) return null;
return existing.getLayerData(frameCount, spriteId);
}
public get animations(): Map<string, IAnimation>
{
return this._animations;
}
}
@@ -0,0 +1,109 @@
import { IActionDefinition, IActiveActionData, IAnimationLayerData, IAssetAnimationFramePart } from '@nitrots/api';
import { ActiveActionData } from '../actions';
export class AvatarAnimationLayerData implements IAnimationLayerData
{
public static BODYPART: string = 'bodypart';
public static FX: string = 'fx';
private _id: string;
private _action: IActiveActionData;
private _animationFrame: number;
private _dx: number;
private _dy: number;
private _dz: number;
private _directionOffset: number;
private _type: string;
private _base: string;
private _items: Map<string, string>;
constructor(k: IAssetAnimationFramePart, _arg_2: string, _arg_3: IActionDefinition)
{
this._id = k.id;
this._animationFrame = (k.frame || 0);
this._dx = (k.dx || 0);
this._dy = (k.dy || 0);
this._dz = (k.dz || 0);
this._directionOffset = (k.dd || 0);
this._type = _arg_2;
this._base = (k.base || '');
this._items = new Map();
if(k.items) for(const _local_4 of k.items) this._items.set(_local_4.id, _local_4.base);
let _local_5 = '';
if(this._base !== '') _local_5 = this.baseAsInt().toString();
if(_arg_3)
{
this._action = new ActiveActionData(_arg_3.state, this.base);
this._action.definition = _arg_3;
}
}
public get items(): Map<string, string>
{
return this._items;
}
private baseAsInt(): number
{
let k = 0;
let index = 0;
while(index < this._base.length)
{
k = (k + this._base.charCodeAt(index));
index++;
}
return k;
}
public get id(): string
{
return this._id;
}
public get animationFrame(): number
{
return this._animationFrame;
}
public get dx(): number
{
return this._dx;
}
public get dy(): number
{
return this._dy;
}
public get dz(): number
{
return this._dz;
}
public get dd(): number
{
return this._directionOffset;
}
public get type(): string
{
return this._type;
}
public get base(): string
{
return this._base;
}
public get action(): IActiveActionData
{
return this._action;
}
}
@@ -0,0 +1,136 @@
import { IAssetAnimationAvatar, IAvatarDataContainer } from '@nitrots/api';
import { AdjustmentFilter } from 'pixi-filters';
export class AvatarDataContainer implements IAvatarDataContainer
{
private _ink: number;
private _foreGround: number;
private _backGround: number;
private _colorTransform: AdjustmentFilter;
private _rgb: number;
private _r: number;
private _g: number;
private _b: number;
private _redMultiplier: number;
private _greenMultiplier: number;
private _blueMultiplier: number;
private _alphaMultiplier: number;
private _colorMap: Map<string, number[]>;
private _paletteIsGrayscale: boolean;
constructor(k: IAssetAnimationAvatar)
{
this._ink = k.ink;
let foreground = k.foreground;
let background = k.background;
foreground = foreground.replace('#', '');
background = background.replace('#', '');
this._foreGround = parseInt(foreground, 16);
this._backGround = parseInt(background, 16);
this._colorTransform = null;
this._rgb = parseInt(foreground, 16);
this._r = ((this._rgb >> 16) & 0xFF);
this._g = ((this._rgb >> 8) & 0xFF);
this._b = ((this._rgb >> 0) & 0xFF);
this._redMultiplier = ((this._r / 0xFF) * 1);
this._greenMultiplier = ((this._g / 0xFF) * 1);
this._blueMultiplier = ((this._b / 0xFF) * 1);
this._alphaMultiplier = 1;
this._paletteIsGrayscale = true;
if(this._ink === 37)
{
this._alphaMultiplier = 0.5;
this._paletteIsGrayscale = false;
}
this._colorTransform = new AdjustmentFilter({ red: (this._r / 255), green: (this._g / 255), blue: (this._b / 255), alpha: this._alphaMultiplier });
this._colorMap = this.generatePaletteMapForGrayscale(this._backGround, this._foreGround);
}
public get ink(): number
{
return this._ink;
}
public get colorTransform(): AdjustmentFilter
{
return this._colorTransform;
}
public get reds(): number[]
{
return this._colorMap.get('reds');
}
public get greens(): number[]
{
return this._colorMap.get('greens');
}
public get blues(): number[]
{
return this._colorMap.get('blues');
}
public get alphas(): number[]
{
return this._colorMap.get('alphas');
}
public get paletteIsGrayscale(): boolean
{
return this._paletteIsGrayscale;
}
private generatePaletteMapForGrayscale(background: number, foreground: number): Map<string, number[]>
{
const alphaBackground = ((background >> 24) & 0xFF);
const redBackground = ((background >> 16) & 0xFF);
const greenBackground = ((background >> 8) & 0xFF);
const blueBackground = ((background >> 0) & 0xFF);
const alphaForeground = ((foreground >> 24) & 0xFF);
const redForeground = ((foreground >> 16) & 0xFF);
const greenForeground = ((foreground >> 8) & 0xFF);
const blueForeground = ((foreground >> 0) & 0xFF);
const alphaDifference = ((alphaForeground - alphaBackground) / 0xFF);
const redDifference = ((redForeground - redBackground) / 0xFF);
const greenDifference = ((greenForeground - greenBackground) / 0xFF);
const blueDifference = ((blueForeground - blueBackground) / 0xFF);
const _local_15: Map<string, number[]> = new Map();
const _local_16: number[] = [];
const _local_17: number[] = [];
const _local_18: number[] = [];
const _local_19: number[] = [];
let _local_20 = alphaBackground;
let _local_21 = redBackground;
let _local_22 = greenBackground;
let _local_23 = blueBackground;
for(let i = 0; i < 256; i++)
{
if((((_local_21 == redBackground) && (_local_22 == greenBackground)) && (_local_23 == blueBackground)))
{
_local_20 = 0;
}
_local_20 = (_local_20 + alphaDifference);
_local_21 = (_local_21 + redDifference);
_local_22 = (_local_22 + greenDifference);
_local_23 = (_local_23 + blueDifference);
_local_19.push((_local_20 << 24));
_local_16.push(((((_local_20 << 24) | (_local_21 << 16)) | (_local_22 << 8)) | _local_23));
_local_17.push(((((_local_20 << 24) | (_local_21 << 16)) | (_local_22 << 8)) | _local_23));
_local_18.push(((((_local_20 << 24) | (_local_21 << 16)) | (_local_22 << 8)) | _local_23));
}
_local_15.set('alphas', _local_16);
_local_15.set('reds', _local_16);
_local_15.set('greens', _local_17);
_local_15.set('blues', _local_18);
return _local_15;
}
}
@@ -0,0 +1,16 @@
import { IAssetAnimationDirection } from '@nitrots/api';
export class DirectionDataContainer
{
private _offset: number;
constructor(k: IAssetAnimationDirection)
{
this._offset = k.offset;
}
public get offset(): number
{
return this._offset;
}
}
@@ -0,0 +1,94 @@
import { IAnimation, IAssetAnimationSprite, ISpriteDataContainer } from '@nitrots/api';
export class SpriteDataContainer implements ISpriteDataContainer
{
private _animation: IAnimation;
private _id: string;
private _ink: number;
private _member: string;
private _hasDirections: boolean;
private _hasStaticY: boolean;
private _dx: number[];
private _dy: number[];
private _dz: number[];
constructor(k: IAnimation, _arg_2: IAssetAnimationSprite)
{
this._animation = k;
this._id = _arg_2.id;
this._ink = _arg_2.ink;
this._member = _arg_2.member;
this._hasStaticY = _arg_2.staticY ? true : false;
this._hasDirections = _arg_2.directions ? true : false;
this._dx = [];
this._dy = [];
this._dz = [];
const directions = _arg_2.directionList;
if(directions && directions.length)
{
for(const direction of directions)
{
const id = direction.id;
if(id === undefined) continue;
this._dx[id] = (direction.dx || 0);
this._dy[id] = (direction.dy || 0);
this._dz[id] = (direction.dz || 0);
}
}
}
public getDirectionOffsetX(k: number): number
{
if(k < this._dx.length) return this._dx[k];
return 0;
}
public getDirectionOffsetY(k: number): number
{
if(k < this._dy.length) return this._dy[k];
return 0;
}
public getDirectionOffsetZ(k: number): number
{
if(k < this._dz.length) return this._dz[k];
return 0;
}
public get animation(): IAnimation
{
return this._animation;
}
public get id(): string
{
return this._id;
}
public get ink(): number
{
return this._ink;
}
public get member(): string
{
return this._member;
}
public get hasDirections(): boolean
{
return this._hasDirections;
}
public get hasStaticY(): boolean
{
return this._hasStaticY;
}
}
+7
View File
@@ -0,0 +1,7 @@
export * from './AddDataContainer';
export * from './Animation';
export * from './AnimationManager';
export * from './AvatarAnimationLayerData';
export * from './AvatarDataContainer';
export * from './DirectionDataContainer';
export * from './SpriteDataContainer';
+57
View File
@@ -0,0 +1,57 @@
import { GetTickerTime } from '@nitrots/utils';
import { AvatarImageDirectionCache } from './AvatarImageDirectionCache';
export class AvatarImageActionCache
{
private _cache: Map<string, AvatarImageDirectionCache>;
private _lastAccessTime: number;
constructor()
{
this._cache = new Map();
this.setLastAccessTime(GetTickerTime());
}
public dispose(): void
{
this.debugInfo('[dispose]');
if(!this._cache) return;
for(const direction of this._cache.values())
{
if(direction) direction.dispose();
}
this._cache.clear();
}
public getDirectionCache(k: number): AvatarImageDirectionCache
{
const existing = this._cache.get(k.toString());
if(!existing) return null;
return existing;
}
public updateDirectionCache(k: number, _arg_2: AvatarImageDirectionCache): void
{
this._cache.set(k.toString(), _arg_2);
}
public setLastAccessTime(k: number): void
{
this._lastAccessTime = k;
}
public getLastAccessTime(): number
{
return this._lastAccessTime;
}
private debugInfo(k: string): void
{
}
}
+96
View File
@@ -0,0 +1,96 @@
import { IActiveActionData } from '@nitrots/api';
import { AvatarImageActionCache } from './AvatarImageActionCache';
export class AvatarImageBodyPartCache
{
private _cache: Map<string, AvatarImageActionCache>;
private _currentAction: IActiveActionData;
private _currentDirection: number;
private _disposed: boolean;
constructor()
{
this._cache = new Map();
}
public setAction(k: IActiveActionData, _arg_2: number): void
{
if(!this._currentAction) this._currentAction = k;
const _local_3 = this.getActionCache(this._currentAction);
if(_local_3) _local_3.setLastAccessTime(_arg_2);
this._currentAction = k;
}
public dispose(): void
{
if(!this._disposed)
{
if(!this._cache) return;
this.disposeActions(0, 2147483647);
this._cache.clear();
this._cache = null;
this._disposed = true;
}
}
public disposeActions(k: number, _arg_2: number): void
{
if(!this._cache || this._disposed) return;
for(const [key, cache] of this._cache.entries())
{
if(!cache) continue;
const _local_3 = cache.getLastAccessTime();
if((_arg_2 - _local_3) >= k)
{
cache.dispose();
this._cache.delete(key);
}
}
}
public getAction(): IActiveActionData
{
return this._currentAction;
}
public setDirection(k: number): void
{
this._currentDirection = k;
}
public getDirection(): number
{
return this._currentDirection;
}
public getActionCache(k: IActiveActionData = null): AvatarImageActionCache
{
if(!this._currentAction) return null;
if(!k) k = this._currentAction;
if(k.overridingAction) return this._cache.get(k.overridingAction);
return this._cache.get(k.id);
}
public updateActionCache(k: IActiveActionData, _arg_2: AvatarImageActionCache): void
{
if(k.overridingAction) this._cache.set(k.overridingAction, _arg_2);
else this._cache.set(k.id, _arg_2);
}
private debugInfo(k: string): void
{
}
}
+444
View File
@@ -0,0 +1,444 @@
import { AvatarDirectionAngle, AvatarFigurePartType, AvatarScaleType, GeometryType, IActiveActionData, IAvatarImage } from '@nitrots/api';
import { GetTickerTime } from '@nitrots/utils';
import { Container, Matrix, Point, Rectangle, Sprite, Texture } from 'pixi.js';
import { AvatarImageBodyPartContainer } from '../AvatarImageBodyPartContainer';
import { AvatarImagePartContainer } from '../AvatarImagePartContainer';
import { AvatarStructure } from '../AvatarStructure';
import { AssetAliasCollection } from '../alias';
import { AvatarAnimationLayerData } from '../animation';
import { AvatarCanvas } from '../structure';
import { AvatarImageActionCache } from './AvatarImageActionCache';
import { AvatarImageBodyPartCache } from './AvatarImageBodyPartCache';
import { AvatarImageDirectionCache } from './AvatarImageDirectionCache';
import { ImageData } from './ImageData';
export class AvatarImageCache
{
private static DEFAULT_MAX_CACHE_STORAGE_TIME_MS: number = 60000;
private _structure: AvatarStructure;
private _avatar: IAvatarImage;
private _assets: AssetAliasCollection;
private _scale: string;
private _cache: Map<string, AvatarImageBodyPartCache>;
private _canvas: AvatarCanvas;
private _disposed: boolean;
private _geometryType: string;
private _unionImages: ImageData[];
private _matrix: Matrix;
constructor(k: AvatarStructure, _arg_2: IAvatarImage, _arg_3: AssetAliasCollection, _arg_4: string)
{
this._structure = k;
this._avatar = _arg_2;
this._assets = _arg_3;
this._scale = _arg_4;
this._cache = new Map();
this._canvas = null;
this._disposed = false;
this._unionImages = [];
this._matrix = new Matrix();
}
public dispose(): void
{
if(this._disposed) return;
this._structure = null;
this._avatar = null;
this._assets = null;
this._canvas = null;
this._disposed = true;
if(this._cache)
{
for(const cache of this._cache.values())
{
if(!cache) continue;
cache.dispose();
}
this._cache = null;
}
if(this._unionImages)
{
for(const image of this._unionImages)
{
if(!image) continue;
image.dispose();
}
this._unionImages = [];
}
}
public disposeInactiveActions(k: number = 60000): void
{
const time = GetTickerTime();
if(this._cache)
{
for(const cache of this._cache.values())
{
if(!cache) continue;
cache.disposeActions(k, time);
}
}
}
public resetBodyPartCache(k: IActiveActionData): void
{
if(this._cache)
{
for(const cache of this._cache.values())
{
if(!cache) continue;
cache.setAction(k, 0);
}
}
}
public setDirection(k: string, _arg_2: number): void
{
const parts = this._structure.getBodyPartsUnordered(k);
if(parts)
{
for(const part of parts)
{
const actionCache = this.getBodyPartCache(part);
if(!actionCache) continue;
actionCache.setDirection(_arg_2);
}
}
}
public setAction(k: IActiveActionData, _arg_2: number): void
{
const _local_3 = this._structure.getActiveBodyPartIds(k, this._avatar);
for(const _local_4 of _local_3)
{
const _local_5 = this.getBodyPartCache(_local_4);
if(_local_5) _local_5.setAction(k, _arg_2);
}
}
public setGeometryType(k: string): void
{
if(this._geometryType === k) return;
if((((this._geometryType === GeometryType.SITTING) && (k === GeometryType.VERTICAL)) || ((this._geometryType === GeometryType.VERTICAL) && (k === GeometryType.SITTING)) || ((this._geometryType === GeometryType.SNOWWARS_HORIZONTAL) && (k = GeometryType.SNOWWARS_HORIZONTAL))))
{
this._geometryType = k;
this._canvas = null;
return;
}
this.disposeInactiveActions(0);
this._geometryType = k;
this._canvas = null;
}
public getImageContainer(key: string, frameNumber: number, forceRefresh: boolean = false): AvatarImageBodyPartContainer
{
const bodyPartCache = this.getBodyPartCache(key) || new AvatarImageBodyPartCache();
this._cache.set(key, bodyPartCache);
let direction = bodyPartCache.getDirection();
let action = bodyPartCache.getAction();
let adjustedFrameCount = frameNumber;
if(action.definition.startFromFrameZero) adjustedFrameCount -= action.startFrame;
let adjustedAction = action;
let removeData: string[] = [];
let items: Map<string, string> = new Map();
const positionOffset = new Point();
if(action.definition.isAnimation)
{
let adjustedDirection = direction;
const animation = this._structure.getAnimation(((action.definition.state + '.') + action.actionParameter));
const animationFrameOffset = (frameNumber - action.startFrame);
if(animation)
{
const layerData = animation.getLayerData(animationFrameOffset, key, action.overridingAction);
if(layerData)
{
adjustedDirection = (direction + layerData.dd + 8) % 8;
positionOffset.x = this._scale === AvatarScaleType.LARGE ? layerData.dx : layerData.dx / 2;
positionOffset.y = this._scale === AvatarScaleType.LARGE ? layerData.dy : layerData.dy / 2;
adjustedFrameCount = layerData.animationFrame;
if(layerData.action) action = layerData.action;
if(layerData.type === AvatarAnimationLayerData.BODYPART)
{
if(layerData.action) adjustedAction = layerData.action;
direction = adjustedDirection;
}
else if(layerData.type === AvatarAnimationLayerData.FX) direction = adjustedDirection;
items = layerData.items;
}
removeData = animation.removeData;
}
}
let actionCache = bodyPartCache.getActionCache(adjustedAction);
if(!actionCache || forceRefresh)
{
actionCache = new AvatarImageActionCache();
bodyPartCache.updateActionCache(adjustedAction, actionCache);
}
let directionCache = actionCache.getDirectionCache(direction);
if(!directionCache || forceRefresh)
{
const partList = this._structure.getParts(key, this._avatar.getFigure(), adjustedAction, this._geometryType, direction, removeData, this._avatar, items);
directionCache = new AvatarImageDirectionCache(partList);
actionCache.updateDirectionCache(direction, directionCache);
}
let imageContainer = directionCache.getImageContainer(adjustedFrameCount);
if(!imageContainer || forceRefresh)
{
const partList = directionCache.getPartList();
imageContainer = this.renderBodyPart(direction, partList, adjustedFrameCount, action);
if(imageContainer && !forceRefresh)
{
if(imageContainer.isCacheable) directionCache.updateImageContainer(imageContainer, adjustedFrameCount);
}
else
{
return null;
}
}
const offset = this._structure.getFrameBodyPartOffset(adjustedAction, direction, adjustedFrameCount, key);
positionOffset.x += offset.x;
positionOffset.y += offset.y;
imageContainer.offset = positionOffset;
return imageContainer;
}
public getBodyPartCache(k: string): AvatarImageBodyPartCache
{
let existing = this._cache.get(k);
if(!existing)
{
existing = new AvatarImageBodyPartCache();
this._cache.set(k, existing);
}
return existing;
}
private renderBodyPart(direction: number, containers: AvatarImagePartContainer[], frameCount: number, action: IActiveActionData): AvatarImageBodyPartContainer
{
if(!containers || !containers.length) return null;
if(!this._canvas)
{
this._canvas = this._structure.getCanvas(this._scale, this._geometryType);
if(!this._canvas) return null;
}
const isFlipped = AvatarDirectionAngle.DIRECTION_IS_FLIPPED[direction] || false;
let assetPartDefinition = action.definition.assetPartDefinition;
let isCacheable = true;
let containerIndex = (containers.length - 1);
while(containerIndex >= 0)
{
const container = containers[containerIndex];
let color = 16777215;
if(!((direction == 7) && ((container.partType === 'fc') || (container.partType === 'ey'))))
{
if(!((container.partType === 'ri') && !container.partId))
{
const partId = container.partId;
const animationFrame = container.getFrameDefinition(frameCount);
let partType = container.partType;
let frameNumber = 0;
if(animationFrame)
{
frameNumber = animationFrame.number;
if((animationFrame.assetPartDefinition) && (animationFrame.assetPartDefinition !== '')) assetPartDefinition = animationFrame.assetPartDefinition;
}
else frameNumber = container.getFrameIndex(frameCount);
let assetDirection = direction;
let flipH = false;
if(isFlipped)
{
if(((assetPartDefinition === 'wav') && (((partType === AvatarFigurePartType.LEFT_HAND) || (partType === AvatarFigurePartType.LEFT_SLEEVE)) || (partType === AvatarFigurePartType.LEFT_COAT_SLEEVE))) || ((assetPartDefinition === 'drk') && (((partType === AvatarFigurePartType.RIGHT_HAND) || (partType === AvatarFigurePartType.RIGHT_SLEEVE)) || (partType === AvatarFigurePartType.RIGHT_COAT_SLEEVE))) || ((assetPartDefinition === 'blw') && (partType === AvatarFigurePartType.RIGHT_HAND)) || ((assetPartDefinition === 'sig') && (partType === AvatarFigurePartType.LEFT_HAND)) || ((assetPartDefinition === 'respect') && (partType === AvatarFigurePartType.LEFT_HAND)) || (partType === AvatarFigurePartType.RIGHT_HAND_ITEM) || (partType === AvatarFigurePartType.LEFT_HAND_ITEM) || (partType === AvatarFigurePartType.CHEST_PRINT))
{
flipH = true;
}
else
{
if(direction === 4) assetDirection = 2;
else if(direction === 5) assetDirection = 1;
else if(direction === 6) assetDirection = 0;
if(container.flippedPartType !== partType) partType = container.flippedPartType;
}
}
let assetName = (this._scale + '_' + assetPartDefinition + '_' + partType + '_' + partId + '_' + assetDirection + '_' + frameNumber);
let asset = this._assets.getAsset(assetName);
if(!asset)
{
assetName = (this._scale + '_std_' + partType + '_' + partId + '_' + assetDirection + '_0');
asset = this._assets.getAsset(assetName);
}
if(asset)
{
const texture = asset.texture;
if(!texture || !texture.source)
{
isCacheable = false;
}
else
{
if(container.isColorable && container.color) color = container.color.rgb;
const offset = new Point(-(asset.x), -(asset.y));
if(flipH) offset.x = (offset.x + ((this._scale === AvatarScaleType.LARGE) ? 65 : 31));
this._unionImages.push(new ImageData(texture, asset.rectangle, offset, flipH, color));
}
}
}
}
containerIndex--;
}
if(!this._unionImages.length) return null;
const imageData = this.createUnionImage(this._unionImages, isFlipped);
const canvasOffset = ((this._scale === AvatarScaleType.LARGE) ? (this._canvas.height - 16) : (this._canvas.height - 8));
const offset = new Point(-(imageData.regPoint.x), (canvasOffset - imageData.regPoint.y));
if(isFlipped && (assetPartDefinition !== 'lay')) offset.x = (offset.x + ((this._scale === AvatarScaleType.LARGE) ? 67 : 31));
let imageIndex = (this._unionImages.length - 1);
while(imageIndex >= 0)
{
const _local_17 = this._unionImages.pop();
if(_local_17) _local_17.dispose();
imageIndex--;
}
return new AvatarImageBodyPartContainer(imageData.container, offset, isCacheable);
}
private convertColorToHex(k: number): string
{
let _local_2: string = (k * 0xFF).toString(16);
if(_local_2.length < 2)
{
_local_2 = ('0' + _local_2);
}
return _local_2;
}
private createUnionImage(k: ImageData[], isFlipped: boolean): ImageData
{
const bounds = new Rectangle();
for(const data of k) data && bounds.enlarge(data.offsetRect);
const point = new Point(-(bounds.x), -(bounds.y));
const container = new Container();
const sprite = new Sprite(Texture.EMPTY);
sprite.width = bounds.width;
sprite.height = bounds.height;
container.addChild(sprite);
for(const data of k)
{
if(!data) continue;
const regPoint = point.clone();
regPoint.x -= data.regPoint.x;
regPoint.y -= data.regPoint.y;
if(isFlipped) regPoint.x = (container.width - (regPoint.x + data.rect.width));
if(isFlipped != data.flipH)
{
this._matrix.a = -1;
this._matrix.tx = ((data.rect.x + data.rect.width) + regPoint.x);
this._matrix.ty = (regPoint.y - data.rect.y);
}
else
{
this._matrix.a = 1;
this._matrix.tx = (regPoint.x - data.rect.x);
this._matrix.ty = (regPoint.y - data.rect.y);
}
const sprite = new Sprite(data.texture);
sprite.tint = data.colorTransform;
sprite.setFromMatrix(this._matrix);
container.addChild(sprite);
}
return new ImageData(null, container.getLocalBounds().rectangle, point, isFlipped, null, container);
}
}
+59
View File
@@ -0,0 +1,59 @@
import { AvatarImageBodyPartContainer } from '../AvatarImageBodyPartContainer';
import { AvatarImagePartContainer } from '../AvatarImagePartContainer';
export class AvatarImageDirectionCache
{
private _partList: AvatarImagePartContainer[];
private _images: Map<string, AvatarImageBodyPartContainer>;
constructor(k: AvatarImagePartContainer[])
{
this._partList = k;
this._images = new Map();
}
public dispose(): void
{
for(const image of this._images.values()) image && image.dispose();
this._images = null;
}
public getPartList(): AvatarImagePartContainer[]
{
return this._partList;
}
public getImageContainer(k: number): AvatarImageBodyPartContainer
{
const existing = this._images.get(this.getCacheKey(k));
if(!existing) return null;
return existing;
}
public updateImageContainer(k: AvatarImageBodyPartContainer, _arg_2: number): void
{
const name = this.getCacheKey(_arg_2);
const existing = this._images.get(name);
if(existing) existing.dispose();
this._images.set(name, k);
}
private getCacheKey(k: number): string
{
let name = '';
for(const part of this._partList) name += (part.getCacheableKey(k) + '/');
return name;
}
private debugInfo(k: string): void
{
}
}
+65
View File
@@ -0,0 +1,65 @@
import { Container, Point, Rectangle, Texture } from 'pixi.js';
export class ImageData
{
private _texture: Texture;
private _container: Container;
private _rect: Rectangle;
private _regPoint: Point;
private _flipH: boolean;
private _colorTransform: number;
constructor(texture: Texture, rectangle: Rectangle, _arg_3: Point, flipH: boolean, color: number, container: Container = null)
{
this._texture = texture;
this._container = container;
this._rect = rectangle;
this._regPoint = _arg_3;
this._flipH = flipH;
this._colorTransform = color;
if(flipH) this._regPoint.x = (-(this._regPoint.x) + rectangle.width);
}
public dispose(): void
{
this._texture = null;
this._regPoint = null;
this._colorTransform = null;
}
public get texture(): Texture
{
return this._texture;
}
public get container(): Container
{
return this._container;
}
public get rect(): Rectangle
{
return this._rect;
}
public get regPoint(): Point
{
return this._regPoint;
}
public get flipH(): boolean
{
return this._flipH;
}
public get colorTransform(): number
{
return this._colorTransform;
}
public get offsetRect(): Rectangle
{
return new Rectangle(-(this._regPoint.x), -(this._regPoint.y), this._rect.width, this._rect.height);
}
}
+5
View File
@@ -0,0 +1,5 @@
export * from './AvatarImageActionCache';
export * from './AvatarImageBodyPartCache';
export * from './AvatarImageCache';
export * from './AvatarImageDirectionCache';
export * from './ImageData';
@@ -0,0 +1,827 @@
export const HabboAvatarAnimations = {
'animations': [
{
'id': 'Move',
'parts': [
{
'setType': 'bd',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'bds',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'ss',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'lg',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'sh',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'lh',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'lhs',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'ls',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'lc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'rh',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'rhs',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'rs',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'rc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
},
{
'setType': 'ch',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wlk'
},
{
'number': 1,
'assetPartDefinition': 'wlk'
},
{
'number': 2,
'assetPartDefinition': 'wlk'
},
{
'number': 3,
'assetPartDefinition': 'wlk'
}
]
}
]
},
{
'id': 'Wave',
'parts': [
{
'setType': 'lh',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav'
},
{
'number': 1,
'assetPartDefinition': 'wav'
}
]
},
{
'setType': 'lhs',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav'
},
{
'number': 1,
'assetPartDefinition': 'wav'
}
]
},
{
'setType': 'ls',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav'
},
{
'number': 1,
'assetPartDefinition': 'wav'
}
]
},
{
'setType': 'lc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav'
},
{
'number': 1,
'assetPartDefinition': 'wav'
}
]
},
{
'setType': 'ch',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav'
},
{
'number': 1,
'assetPartDefinition': 'wav'
},
{
'number': 2,
'assetPartDefinition': 'wav'
},
{
'number': 3,
'assetPartDefinition': 'wav'
}
]
}
]
},
{
'id': 'Talk',
'parts': [
{
'setType': 'hd',
'frames': [
{
'number': 0,
'assetPartDefinition': 'spk'
},
{
'number': 1,
'assetPartDefinition': 'spk'
}
]
},
{
'setType': 'fc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'spk'
},
{
'number': 1,
'assetPartDefinition': 'spk'
}
]
},
{
'setType': 'fa',
'frames': [
{
'number': 0,
'assetPartDefinition': 'spk'
},
{
'number': 1,
'assetPartDefinition': 'spk'
}
]
}
]
},
{
'id': 'Sign',
'parts': [
{
'setType': 'lh',
'frames': [
{
'number': 0,
'assetPartDefinition': 'sig'
}
]
},
{
'setType': 'li',
'frames': [
{
'number': 0,
'assetPartDefinition': 'sig'
}
]
},
{
'setType': 'ls',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav'
}
]
},
{
'setType': 'lc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav'
}
]
}
]
},
{
'id': 'Respect',
'parts': [
{
'setType': 'lh',
'frames': [
{
'number': 0,
'assetPartDefinition': 'respect',
'repeats': 15
},
{
'number': 1,
'assetPartDefinition': 'respect',
'repeats': 15
}
]
},
{
'setType': 'ls',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav',
'repeats': 15
},
{
'number': 1,
'assetPartDefinition': 'wav',
'repeats': 15
}
]
},
{
'setType': 'lc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'wav',
'repeats': 15
},
{
'number': 1,
'assetPartDefinition': 'wav',
'repeats': 15
}
]
}
]
},
{
'id': 'Blow',
'parts': [
{
'setType': 'rh',
'frames': [
{
'number': 0,
'assetPartDefinition': 'blw',
'repeats': 10
},
{
'number': 1,
'assetPartDefinition': 'blw',
'repeats': 10
}
]
},
{
'setType': 'rs',
'frames': [
{
'number': 0,
'assetPartDefinition': 'drk'
}
]
},
{
'setType': 'rc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'drk'
}
]
},
{
'setType': 'ri',
'frames': [
{
'number': 0,
'assetPartDefinition': ''
}
]
},
{
'setType': 'ey',
'frames': [
{
'number': 0,
'assetPartDefinition': 'std',
'repeats': 10
},
{
'number': 0,
'assetPartDefinition': 'eyb',
'repeats': 10
}
]
},
{
'setType': 'fc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'std',
'repeats': 10
},
{
'number': 0,
'assetPartDefinition': 'blw',
'repeats': 10
}
]
}
]
},
{
'id': 'Laugh',
'parts': [
{
'setType': 'rh',
'frames': [
{
'number': 0,
'assetPartDefinition': 'blw'
}
]
},
{
'setType': 'rs',
'frames': [
{
'number': 0,
'assetPartDefinition': 'drk'
}
]
},
{
'setType': 'rc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'drk'
}
]
},
{
'setType': 'ri',
'frames': [
{
'number': 0,
'assetPartDefinition': ''
}
]
},
{
'setType': 'ey',
'frames': [
{
'number': 0,
'assetPartDefinition': 'std',
'repeats': 2
}
]
},
{
'setType': 'fc',
'frames': [
{
'number': 0,
'assetPartDefinition': 'sml'
}
]
}
],
'offsets': {
'frames': [
{
'id': 0,
'directions': [
{
'id': 0,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 1
}
]
},
{
'id': 1,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 1
}
]
},
{
'id': 2,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 1
}
]
},
{
'id': 3,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 1
}
]
},
{
'id': 4,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 1
}
]
},
{
'id': 5,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 1
}
]
},
{
'id': 6,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 1
}
]
},
{
'id': 7,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 1
}
]
}
]
},
{
'id': 1,
'directions': [
{
'id': 0,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 0
}
]
},
{
'id': 1,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 0
}
]
},
{
'id': 2,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 0
}
]
},
{
'id': 3,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 0
}
]
},
{
'id': 4,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 0
}
]
},
{
'id': 5,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 0
}
]
},
{
'id': 6,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 0
}
]
},
{
'id': 7,
'bodyParts': [
{
'id': 'head',
'dx': 0,
'dy': 0
}
]
}
]
}
]
}
}
]
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,418 @@
export const HabboAvatarPartSets = {
'partSets': {
'partSet': [
{
'setType': 'ri',
'flippedSetType': 'ri'
},
{
'setType': 'ri',
'flippedSetType': 'ri'
},
{
'setType': 'rh',
'flippedSetType': 'lh'
},
{
'setType': 'rhs',
'flippedSetType': 'lhs'
},
{
'setType': 'rs',
'swim': '0',
'flippedSetType': 'ls'
},
{
'setType': 'rc',
'flippedSetType': 'lc'
},
{
'setType': 'bd'
},
{
'setType': 'bds'
},
{
'setType': 'ss'
},
{
'setType': 'sh'
},
{
'setType': 'lg'
},
{
'setType': 'ch'
},
{
'setType': 'cp'
},
{
'setType': 'cc'
},
{
'setType': 'hd'
},
{
'setType': 'fc'
},
{
'setType': 'ey'
},
{
'setType': 'hr'
},
{
'setType': 'hrb',
'removeSetType': 'hr'
},
{
'setType': 'li',
'flippedSetType': 'li'
},
{
'setType': 'lh',
'flippedSetType': 'rh'
},
{
'setType': 'lhs',
'flippedSetType': 'rhs'
},
{
'setType': 'ls',
'flippedSetType': 'rs'
},
{
'setType': 'lc',
'flippedSetType': 'rc'
},
{
'setType': 'wa'
},
{
'setType': 'ea'
},
{
'setType': 'ca'
},
{
'setType': 'fa'
},
{
'setType': 'ha'
},
{
'setType': 'he'
}
],
'activePartSets': [
{
'id': 'figure',
'activeParts': [
{
'setType': 'rh'
},
{
'setType': 'rh'
},
{
'setType': 'rhs'
},
{
'setType': 'rs'
},
{
'setType': 'rc'
},
{
'setType': 'bd'
},
{
'setType': 'bds'
},
{
'setType': 'ss'
},
{
'setType': 'sh'
},
{
'setType': 'lg'
},
{
'setType': 'ch'
},
{
'setType': 'cp'
},
{
'setType': 'cc'
},
{
'setType': 'wa'
},
{
'setType': 'hd'
},
{
'setType': 'fc'
},
{
'setType': 'ey'
},
{
'setType': 'hr'
},
{
'setType': 'hrb'
},
{
'setType': 'lh'
},
{
'setType': 'lhs'
},
{
'setType': 'ls'
},
{
'setType': 'lc'
},
{
'setType': 'ea'
},
{
'setType': 'ca'
},
{
'setType': 'fa'
},
{
'setType': 'ha'
},
{
'setType': 'he'
}
]
},
{
'id': 'head',
'activeParts': [
{
'setType': 'hd'
},
{
'setType': 'fc'
},
{
'setType': 'ey'
},
{
'setType': 'hr'
},
{
'setType': 'hrb'
},
{
'setType': 'ea'
},
{
'setType': 'fa'
},
{
'setType': 'ha'
},
{
'setType': 'he'
}
]
},
{
'id': 'speak',
'activeParts': [
{
'setType': 'hd'
},
{
'setType': 'hr'
},
{
'setType': 'hrb'
},
{
'setType': 'fc'
},
{
'setType': 'fa'
},
{
'setType': 'ha'
}
]
},
{
'id': 'gesture',
'activeParts': [
{
'setType': 'ey'
},
{
'setType': 'fc'
}
]
},
{
'id': 'eye',
'activeParts': [
{
'setType': 'ey'
}
]
},
{
'id': 'handRight',
'activeParts': [
{
'setType': 'rh'
},
{
'setType': 'rhs'
},
{
'setType': 'rs'
},
{
'setType': 'rc'
},
{
'setType': 'ri'
}
]
},
{
'id': 'handRightAndHead',
'activeParts': [
{
'setType': 'rh'
},
{
'setType': 'rhs'
},
{
'setType': 'rs'
},
{
'setType': 'rc'
},
{
'setType': 'ri'
},
{
'setType': 'ey'
},
{
'setType': 'fc'
},
{
'setType': 'hd'
}
]
},
{
'id': 'handLeft',
'activeParts': [
{
'setType': 'lh'
},
{
'setType': 'lhs'
},
{
'setType': 'ls'
},
{
'setType': 'lc'
},
{
'setType': 'li'
}
]
},
{
'id': 'walk',
'activeParts': [
{
'setType': 'bd'
},
{
'setType': 'bds'
},
{
'setType': 'ss'
},
{
'setType': 'lg'
},
{
'setType': 'lh'
},
{
'setType': 'lhs'
},
{
'setType': 'rh'
},
{
'setType': 'rhs'
},
{
'setType': 'ls'
},
{
'setType': 'lc'
},
{
'setType': 'rs'
},
{
'setType': 'rc'
},
{
'setType': 'sh'
}
]
},
{
'id': 'sit',
'activeParts': [
{
'setType': 'bd'
},
{
'setType': 'bds'
},
{
'setType': 'ss'
},
{
'setType': 'lg'
},
{
'setType': 'sh'
},
{
'setType': 'cc'
}
]
},
{
'id': 'itemRight',
'activeParts': [
{
'setType': 'ri'
}
]
}
]
}
};
@@ -0,0 +1,287 @@
import { IAvatarImage } from '@nitrots/api';
import { Vector3d } from '@nitrots/utils';
import { Matrix4x4 } from '@nitrots/utils/src/Matrix4x4';
import { AvatarCanvas } from '../structure';
import { AvatarSet } from './AvatarSet';
import { GeometryBodyPart } from './GeometryBodyPart';
export class AvatarModelGeometry
{
private _camera: Vector3d;
private _avatarSet: AvatarSet;
private _geometryTypes: Map<string, Map<string, GeometryBodyPart>>;
private _itemIdToBodyPartMap: Map<string, Map<string, GeometryBodyPart>>;
private _transformation: Matrix4x4;
private _canvases: Map<string, Map<string, AvatarCanvas>>;
constructor(k: any)
{
this._camera = new Vector3d(0, 0, 10);
this._avatarSet = new AvatarSet(k.avatarSets[0]);
this._geometryTypes = new Map();
this._itemIdToBodyPartMap = new Map();
this._transformation = new Matrix4x4();
this._canvases = new Map();
const camera = k.camera;
if(camera)
{
this._camera.x = parseFloat(camera.x);
this._camera.y = parseFloat(camera.y);
this._camera.z = parseFloat(camera.z);
}
if(k.canvases && (k.canvases.length > 0))
{
for(const canvas of k.canvases)
{
if(!canvas) continue;
const scale = canvas.scale;
const geometries = new Map();
if(canvas.geometries && (canvas.geometries.length > 0))
{
for(const geometry of canvas.geometries)
{
if(!geometry) continue;
const avatarCanvas = new AvatarCanvas(geometry, scale);
geometries.set(avatarCanvas.id, avatarCanvas);
}
}
this._canvases.set(scale, geometries);
}
}
if(k.types && (k.types.length > 0))
{
for(const type of k.types)
{
if(!type) continue;
const bodyParts: Map<string, GeometryBodyPart> = new Map();
const itemIds: Map<string, GeometryBodyPart> = new Map();
if(type.bodyParts && (type.bodyParts.length > 0))
{
for(const bodyPart of type.bodyParts)
{
if(!bodyPart) continue;
const geometryBodyPart = new GeometryBodyPart(bodyPart);
bodyParts.set(geometryBodyPart.id, geometryBodyPart);
for(const part of geometryBodyPart.getPartIds(null))
{
itemIds.set(part, geometryBodyPart);
}
}
}
this._geometryTypes.set(type.id, bodyParts);
this._itemIdToBodyPartMap.set(type.id, itemIds);
}
}
}
public removeDynamicItems(k: IAvatarImage): void
{
for(const geometry of this._geometryTypes.values())
{
if(!geometry) continue;
for(const part of geometry.values())
{
if(!part) continue;
part.removeDynamicParts(k);
}
}
}
public getBodyPartIdsInAvatarSet(k: string): string[]
{
const avatarSet = this._avatarSet.findAvatarSet(k);
if(!avatarSet) return [];
return avatarSet.getBodyParts();
}
public isMainAvatarSet(k: string): boolean
{
const avatarSet = this._avatarSet.findAvatarSet(k);
if(!avatarSet) return false;
return avatarSet.isMain;
}
public getCanvas(k: string, _arg_2: string): AvatarCanvas
{
const canvas = this._canvases.get(k);
if(!canvas) return null;
return (canvas.get(_arg_2) || null);
}
private typeExists(k: string): boolean
{
const existing = this._geometryTypes.get(k);
if(existing) return true;
return false;
}
private hasBodyPart(k: string, _arg_2: string): boolean
{
if(this.typeExists(k))
{
const existing = this._geometryTypes.get(k);
if(existing && existing.get(_arg_2)) return true;
}
return false;
}
private getBodyPartIDs(k: string): string[]
{
const parts = this.getBodyPartsOfType(k);
const types = [];
if(parts)
{
for(const part of parts.values())
{
if(!part) continue;
types.push(part.id);
}
}
return types;
}
private getBodyPartsOfType(k: string): Map<string, GeometryBodyPart>
{
if(this.typeExists(k)) return this._geometryTypes.get(k);
return new Map();
}
public getBodyPart(k: string, _arg_2: string): GeometryBodyPart
{
return (this.getBodyPartsOfType(k).get(_arg_2) || null);
}
public getBodyPartOfItem(k: string, _arg_2: string, _arg_3: IAvatarImage): GeometryBodyPart
{
const itemIds = this._itemIdToBodyPartMap.get(k);
if(itemIds)
{
const part = itemIds.get(_arg_2);
if(part) return part;
const parts = this.getBodyPartsOfType(k);
if(parts)
{
for(const part of parts.values())
{
if(!part) continue;
if(part.hasPart(_arg_2, _arg_3)) return part;
}
}
}
return null;
}
private getBodyPartsInAvatarSet(k: Map<string, GeometryBodyPart>, _arg_2: string): GeometryBodyPart[]
{
const parts = this.getBodyPartIdsInAvatarSet(_arg_2);
const geometryParts = [];
for(const part of parts)
{
if(!part) continue;
const bodyPart = k.get(part);
if(bodyPart)
{
geometryParts.push(bodyPart);
}
}
return geometryParts;
}
public getBodyPartsAtAngle(k: string, _arg_2: number, _arg_3: string): string[]
{
if(!_arg_3) return [];
const geometryParts = this.getBodyPartsOfType(_arg_3);
const parts = this.getBodyPartsInAvatarSet(geometryParts, k);
const sets: [number, GeometryBodyPart][] = [];
const ids: string[] = [];
this._transformation = Matrix4x4.getYRotationMatrix(_arg_2);
for(const part of parts.values())
{
if(!part) continue;
part.applyTransform(this._transformation);
sets.push([part.getDistance(this._camera), part]);
}
sets.sort((a, b) =>
{
const partA = a[0];
const partB = b[0];
if(partA < partB) return -1;
if(partA > partB) return 1;
return 0;
});
for(const set of sets)
{
if(!set) continue;
ids.push(set[1].id);
}
return ids;
}
public getParts(k: string, _arg_2: string, _arg_3: number, _arg_4: any[], _arg_5: IAvatarImage): string[]
{
if(this.hasBodyPart(k, _arg_2))
{
const part = this.getBodyPartsOfType(k).get(_arg_2);
this._transformation = Matrix4x4.getYRotationMatrix(_arg_3);
return part.getParts(this._transformation, this._camera, _arg_4, _arg_5);
}
return [];
}
}
+92
View File
@@ -0,0 +1,92 @@
export class AvatarSet
{
private _id: string;
private _isMain: boolean;
private _avatarSets: Map<string, AvatarSet>;
private _bodyParts: string[];
private _allBodyParts: string[];
constructor(k: any)
{
this._id = k.id;
this._isMain = k.main || false;
this._avatarSets = new Map();
this._bodyParts = [];
this._allBodyParts = [];
if(k.avatarSets && (k.avatarSets.length > 0))
{
for(const avatarSet of k.avatarSets)
{
if(!avatarSet) continue;
const set = new AvatarSet(avatarSet);
this._avatarSets.set(set.id, set);
}
}
if(k.bodyParts && (k.bodyParts.length > 0))
{
for(const bodyPart of k.bodyParts)
{
if(!bodyPart) continue;
this._bodyParts.push(bodyPart.id);
}
}
let bodyParts = this._bodyParts.concat();
for(const avatarSet of this._avatarSets.values())
{
if(!avatarSet) continue;
bodyParts = bodyParts.concat(avatarSet.getBodyParts());
}
this._allBodyParts = bodyParts;
}
public findAvatarSet(k: string): AvatarSet
{
if(k === this._id) return this;
for(const avatarSet of this._avatarSets.values())
{
if(!avatarSet) continue;
if(!avatarSet.findAvatarSet(k)) continue;
return avatarSet;
}
return null;
}
public getBodyParts(): string[]
{
return this._allBodyParts.concat();
}
public get id(): string
{
return this._id;
}
public get isMain(): boolean
{
if(this._isMain) return true;
for(const avatarSet of this._avatarSets.values())
{
if(!avatarSet) continue;
if(!avatarSet.isMain) continue;
return true;
}
return false;
}
}
@@ -0,0 +1,192 @@
import { IAvatarImage } from '@nitrots/api';
import { Matrix4x4, Node3D, Vector3d } from '@nitrots/utils';
import { GeometryItem } from './GeometryItem';
export class GeometryBodyPart extends Node3D
{
private _id: string;
private _radius: number;
private _parts: Map<string, GeometryItem>;
private _dynamicParts: Map<IAvatarImage, { [index: string]: GeometryItem }>;
constructor(k: any)
{
super(parseFloat(k.x), parseFloat(k.y), parseFloat(k.z));
this._id = k.id;
this._radius = parseFloat(k.radius);
this._parts = new Map();
this._dynamicParts = new Map();
if(k.items && (k.items.length > 0))
{
for(const item of k.items)
{
if(!item) continue;
const geometryItem = new GeometryItem(item);
this._parts.set(geometryItem.id, geometryItem);
}
}
}
public getDynamicParts(k: IAvatarImage): GeometryItem[]
{
const existing = this._dynamicParts.get(k);
const parts: GeometryItem[] = [];
if(existing)
{
for(const index in existing)
{
const item = existing[index];
if(!item) continue;
parts.push(item);
}
}
return parts;
}
public getPartIds(k: IAvatarImage): string[]
{
const ids: string[] = [];
for(const part of this._parts.values())
{
if(!part) continue;
ids.push(part.id);
}
if(k)
{
const existing = this._dynamicParts.get(k);
if(existing)
{
for(const index in existing)
{
const part = existing[index];
if(!part) continue;
ids.push(part.id);
}
}
}
return ids;
}
public removeDynamicParts(k: IAvatarImage): boolean
{
this._dynamicParts.delete(k);
return true;
}
public addPart(k: any, _arg_2: IAvatarImage): boolean
{
if(this.hasPart(k.id, _arg_2)) return false;
let existing = this._dynamicParts.get(_arg_2);
if(!existing)
{
existing = {};
this._dynamicParts.set(_arg_2, existing);
}
existing[k.id] = new GeometryItem(k, true);
return true;
}
public hasPart(k: string, _arg_2: IAvatarImage): boolean
{
let existingPart = (this._parts.get(k) || null);
if(!existingPart && (this._dynamicParts.get(_arg_2) !== undefined))
{
existingPart = (this._dynamicParts.get(_arg_2)[k] || null);
}
return (existingPart !== null);
}
public getParts(k: Matrix4x4, _arg_2: Vector3d, _arg_3: any[], _arg_4: IAvatarImage): string[]
{
const parts: [number, GeometryItem][] = [];
for(const part of this._parts.values())
{
if(!part) continue;
part.applyTransform(k);
parts.push([part.getDistance(_arg_2), part]);
}
const existingDynamic = this._dynamicParts.get(_arg_4);
if(existingDynamic)
{
for(const index in existingDynamic)
{
const part = existingDynamic[index];
if(!part) continue;
part.applyTransform(k);
parts.push([part.getDistance(_arg_2), part]);
}
}
parts.sort((a, b) =>
{
const partA = a[0];
const partB = b[0];
if(partA < partB) return -1;
if(partA > partB) return 1;
return 0;
});
const partIds: string[] = [];
for(const part of parts)
{
if(!part) continue;
partIds.push(part[1].id);
}
return partIds;
}
public getDistance(k: Vector3d): number
{
const _local_2 = Math.abs(((k.z - this.transformedLocation.z) - this._radius));
const _local_3 = Math.abs(((k.z - this.transformedLocation.z) + this._radius));
return Math.min(_local_2, _local_3);
}
public get id(): string
{
return this._id;
}
public get radius(): number
{
return this._radius;
}
}
@@ -0,0 +1,54 @@
import { Node3D, Vector3d } from '@nitrots/utils';
export class GeometryItem extends Node3D
{
private _id: string;
private _radius: number;
private _normal: Vector3d;
private _isDoubleSided: boolean;
private _isDynamic: boolean;
constructor(k: any, _arg_2: boolean = false)
{
super(parseFloat(k.x), parseFloat(k.y), parseFloat(k.z));
this._id = k.id;
this._radius = parseFloat(k.radius);
this._normal = new Vector3d(parseFloat(k.nx), parseFloat(k.ny), parseFloat(k.nz));
this._isDoubleSided = k.double || false;
this._isDynamic = _arg_2;
}
public getDistance(k: Vector3d): number
{
const _local_2 = Math.abs(((k.z - this.transformedLocation.z) - this._radius));
const _local_3 = Math.abs(((k.z - this.transformedLocation.z) + this._radius));
return Math.min(_local_2, _local_3);
}
public get id(): string
{
return this._id;
}
public get normal(): Vector3d
{
return this._normal;
}
public get isDoubleSided(): boolean
{
return this._isDoubleSided;
}
public toString(): string
{
return ((((this._id + ': ') + this.location) + ' - ') + this.transformedLocation);
}
public get isDynamic(): boolean
{
return this._isDynamic;
}
}
+4
View File
@@ -0,0 +1,4 @@
export * from './AvatarModelGeometry';
export * from './AvatarSet';
export * from './GeometryBodyPart';
export * from './GeometryItem';
+25
View File
@@ -0,0 +1,25 @@
export * from './AvatarAssetDownloadLibrary';
export * from './AvatarAssetDownloadManager';
export * from './AvatarFigureContainer';
export * from './AvatarImage';
export * from './AvatarImageBodyPartContainer';
export * from './AvatarImagePartContainer';
export * from './AvatarRenderManager';
export * from './AvatarStructure';
export * from './EffectAssetDownloadLibrary';
export * from './EffectAssetDownloadManager';
export * from './FigureDataContainer';
export * from './GetAvatarRenderManager';
export * from './PlaceHolderAvatarImage';
export * from './actions';
export * from './alias';
export * from './animation';
export * from './cache';
export * from './data/HabboAvatarAnimations';
export * from './data/HabboAvatarGeometry';
export * from './data/HabboAvatarPartSets';
export * from './geometry';
export * from './structure';
export * from './structure/animation';
export * from './structure/figure';
export * from './structure/parts';
@@ -0,0 +1,57 @@
import { IActionDefinition, IFigureSetData } from '@nitrots/api';
import { AnimationAction } from './animation';
export class AvatarAnimationData implements IFigureSetData
{
private _actions: Map<string, AnimationAction>;
constructor()
{
this._actions = new Map();
}
public parse(data: any): boolean
{
if(data && (data.length > 0))
{
for(const animation of data)
{
if(!animation) continue;
const newAnimation = new AnimationAction(animation);
this._actions.set(newAnimation.id, newAnimation);
}
}
return true;
}
public appendJSON(k: any): boolean
{
for(const _local_2 of k.action)
{
this._actions.set(_local_2.id, new AnimationAction(_local_2));
}
return true;
}
public getAction(action: IActionDefinition): AnimationAction
{
const existing = this._actions.get(action.id);
if(!existing) return null;
return existing;
}
public getFrameCount(k: IActionDefinition): number
{
const animationAction = this.getAction(k);
if(!animationAction) return 0;
return animationAction.frameCount;
}
}
@@ -0,0 +1,47 @@
import { AvatarScaleType } from '@nitrots/api';
import { Point } from 'pixi.js';
export class AvatarCanvas
{
private _id: string;
private _width: number;
private _height: number;
private _offset: Point;
private _regPoint: Point;
constructor(k: any, _arg_2: string)
{
this._id = k.id;
this._width = k.width;
this._height = k.height;
this._offset = new Point(k.dx, k.dy);
if(_arg_2 == AvatarScaleType.LARGE) this._regPoint = new Point(((this._width - 64) / 2), 0);
else this._regPoint = new Point(((this._width - 32) / 2), 0);
}
public get width(): number
{
return this._width;
}
public get height(): number
{
return this._height;
}
public get offset(): Point
{
return this._offset;
}
public get id(): string
{
return this._id;
}
public get regPoint(): Point
{
return this._regPoint;
}
}
@@ -0,0 +1,129 @@
import { IFigureData, IFigurePartSet, IFigureSetData, IPalette, ISetType, IStructureData } from '@nitrots/api';
import { Palette, SetType } from './figure';
export class FigureSetData implements IFigureSetData, IStructureData
{
private _palettes: Map<string, Palette>;
private _setTypes: Map<string, SetType>;
constructor()
{
this._palettes = new Map();
this._setTypes = new Map();
}
public dispose(): void
{
}
public parse(data: IFigureData): boolean
{
if(!data) return false;
for(const palette of data.palettes)
{
const newPalette = new Palette(palette);
if(!newPalette) continue;
this._palettes.set(newPalette.id.toString(), newPalette);
}
for(const set of data.setTypes)
{
const newSet = new SetType(set);
if(!newSet) continue;
this._setTypes.set(newSet.type, newSet);
}
return true;
}
public injectJSON(data: IFigureData): void
{
for(const setType of data.setTypes)
{
const existingSetType = this._setTypes.get(setType.type);
if(existingSetType) existingSetType.cleanUp(setType);
else this._setTypes.set(setType.type, new SetType(setType));
}
this.appendJSON(data);
}
public appendJSON(data: IFigureData): boolean
{
if(!data) return false;
for(const palette of data.palettes)
{
const id = palette.id.toString();
const existingPalette = this._palettes.get(id);
if(!existingPalette) this._palettes.set(id, new Palette(palette));
else existingPalette.append(palette);
}
for(const setType of data.setTypes)
{
const type = setType.type;
const existingSetType = this._setTypes.get(type);
if(!existingSetType) this._setTypes.set(type, new SetType(setType));
else existingSetType.append(setType);
}
return false;
}
public getMandatorySetTypeIds(k: string, _arg_2: number): string[]
{
const types: string[] = [];
for(const set of this._setTypes.values())
{
if(!set || !set.isMandatory(k, _arg_2)) continue;
types.push(set.type);
}
return types;
}
public getDefaultPartSet(type: string, gender: string): IFigurePartSet
{
const setType = this._setTypes.get(type);
if(!setType) return null;
return setType.getDefaultPartSet(gender);
}
public getSetType(k: string): ISetType
{
return (this._setTypes.get(k) || null);
}
public getPalette(k: number): IPalette
{
return (this._palettes.get(k.toString()) || null);
}
public getFigurePartSet(k: number): IFigurePartSet
{
for(const set of this._setTypes.values())
{
const partSet = set.getPartSet(k);
if(!partSet) continue;
return partSet;
}
return null;
}
}
@@ -0,0 +1,118 @@
import { IActionDefinition, IFigureSetData } from '@nitrots/api';
import { ActionDefinition } from '../actions';
import { ActivePartSet, PartDefinition } from './parts';
export class PartSetsData implements IFigureSetData
{
private _parts: Map<string, PartDefinition>;
private _activePartSets: Map<string, ActivePartSet>;
constructor()
{
this._parts = new Map();
this._activePartSets = new Map();
}
public parse(data: any): boolean
{
if(data.partSet && (data.partSet.length > 0))
{
for(const part of data.partSet)
{
if(!part) continue;
this._parts.set(part.setType, new PartDefinition(part));
}
}
if(data.activePartSets && (data.activePartSets.length > 0))
{
for(const activePart of data.activePartSets)
{
if(!activePart) continue;
this._activePartSets.set(activePart.id, new ActivePartSet(activePart));
}
}
return true;
}
public appendJSON(data: any): boolean
{
if(data.partSet && (data.partSet.length > 0))
{
for(const part of data.partSet)
{
if(!part) continue;
this._parts.set(part.setType, new PartDefinition(part));
}
}
if(data.activePartSets && (data.activePartSets.length > 0))
{
for(const activePart of data.activePartSets)
{
if(!activePart) continue;
this._activePartSets.set(activePart.id, new ActivePartSet(activePart));
}
}
return false;
}
public getActiveParts(k: IActionDefinition): string[]
{
const activePartSet = this._activePartSets.get(k.activePartSet);
if(!activePartSet) return [];
return activePartSet.parts;
}
public getPartDefinition(part: string): PartDefinition
{
const existing = this._parts.get(part);
if(!existing) return null;
return existing;
}
public addPartDefinition(k: any): PartDefinition
{
const _local_2 = k.setType as string;
let existing = this._parts.get(_local_2);
if(!existing)
{
existing = new PartDefinition(k);
this._parts.set(_local_2, existing);
}
return existing;
}
public getActivePartSet(k: ActionDefinition): ActivePartSet
{
const existing = this._activePartSets.get(k.activePartSet);
if(!existing) return null;
return existing;
}
public get parts(): Map<string, PartDefinition>
{
return this._parts;
}
public get activePartSets(): Map<string, ActivePartSet>
{
return this._activePartSets;
}
}
@@ -0,0 +1,138 @@
import { Point } from 'pixi.js';
import { AnimationActionPart } from './AnimationActionPart';
export class AnimationAction
{
public static DEFAULT_OFFSET: Point = new Point(0, 0);
private _id: string;
private _actionParts: Map<string, AnimationActionPart>;
private _bodyPartOffsets: Map<number, Map<number, Map<string, Point>>>;
private _frameCount: number;
private _frameIndexes: number[];
constructor(data: any)
{
this._id = data.id;
this._actionParts = new Map();
this._bodyPartOffsets = new Map();
this._frameCount = 0;
this._frameIndexes = [];
if(data.parts && (data.parts.length > 0))
{
for(const part of data.parts)
{
if(!part) continue;
const newPart = new AnimationActionPart(part);
this._actionParts.set(part.setType, newPart);
this._frameCount = Math.max(this._frameCount, newPart.frames.length);
}
}
if(data.offsets && data.offsets.frames && (data.offsets.frames.length > 0))
{
for(const frame of data.offsets.frames)
{
if(!frame) continue;
const frameId = frame.id;
this._frameCount = Math.max(this._frameCount, frameId);
const directions: Map<number, Map<string, Point>> = new Map();
this._bodyPartOffsets.set(frameId, directions);
if(frame.directions && (frame.directions.length > 0))
{
for(const direction of frame.directions)
{
if(!direction) continue;
const directionId = direction.id;
const offsets: Map<string, Point> = new Map();
directions.set(directionId, offsets);
if(direction.bodyParts && (direction.bodyParts.length > 0))
{
for(const part of direction.bodyParts)
{
if(!part) continue;
const partId = part.id;
let dx = 0;
let dy = 0;
if(part.dx !== undefined) dx = part.dx;
if(part.dy !== undefined) dy = part.dy;
offsets.set(partId, new Point(dx, dy));
}
}
}
}
this._frameIndexes.push(frameId);
if(frame.repeats !== undefined)
{
let repeats = frame.repeats || 0;
if(repeats > 1) while(--repeats > 0) this._frameIndexes.push(frameId);
}
}
}
}
public getPart(type: string): AnimationActionPart
{
if(!type) return null;
const existing = this._actionParts.get(type);
if(!existing) return null;
return existing;
}
public getFrameBodyPartOffset(frameId: number, frameCount: number, partId: string): Point
{
const frameIndex = (frameCount % this._frameIndexes.length);
const frameNumber = this._frameIndexes[frameIndex];
const offsets = this._bodyPartOffsets.get(frameNumber);
if(!offsets) return AnimationAction.DEFAULT_OFFSET;
const frameOffset = offsets.get(frameId);
if(!frameOffset) return AnimationAction.DEFAULT_OFFSET;
const offset = frameOffset.get(partId);
if(!offset) return AnimationAction.DEFAULT_OFFSET;
return offset;
}
public get id(): string
{
return this._id;
}
public get parts(): Map<string, AnimationActionPart>
{
return this._actionParts;
}
public get frameCount(): number
{
return this._frameCount;
}
}
@@ -0,0 +1,30 @@
import { AvatarAnimationFrame } from './AvatarAnimationFrame';
export class AnimationActionPart
{
private _frames: AvatarAnimationFrame[];
constructor(data: any)
{
this._frames = [];
if(data.frames && (data.frames.length > 0))
{
for(const frame of data.frames)
{
if(!frame) continue;
this._frames.push(new AvatarAnimationFrame(frame));
let repeats = frame.repeats || 0;
if(repeats > 1) while(--repeats > 0) this._frames.push(this._frames[(this._frames.length - 1)]);
}
}
}
public get frames(): AvatarAnimationFrame[]
{
return this._frames;
}
}
@@ -0,0 +1,21 @@
export class AvatarAnimationFrame
{
private _number: number;
private _assetPartDefinition: string;
constructor(data: any)
{
this._number = data.number;
this._assetPartDefinition = data.assetPartDefinition || null;
}
public get number(): number
{
return this._number;
}
public get assetPartDefinition(): string
{
return this._assetPartDefinition;
}
}
@@ -0,0 +1,3 @@
export * from './AnimationAction';
export * from './AnimationActionPart';
export * from './AvatarAnimationFrame';
@@ -0,0 +1,53 @@
import { IFigureDataPart, IFigurePart } from '@nitrots/api';
export class FigurePart implements IFigurePart
{
private _id: number;
private _type: string;
private _breed: number;
private _index: number;
private _colorLayerIndex: number;
private _paletteMapId: number;
constructor(data: IFigureDataPart)
{
if(!data) throw new Error('invalid_data');
this._id = data.id;
this._type = data.type;
this._index = data.index;
this._colorLayerIndex = data.colorindex;
this._paletteMapId = -1;
this._breed = -1;
}
public get id(): number
{
return this._id;
}
public get type(): string
{
return this._type;
}
public get breed(): number
{
return this._breed;
}
public get index(): number
{
return this._index;
}
public get colorLayerIndex(): number
{
return this._colorLayerIndex;
}
public get paletteMap(): number
{
return this._paletteMapId;
}
}
@@ -0,0 +1,134 @@
import { IFigureDataSet, IFigurePart, IFigurePartSet } from '@nitrots/api';
import { FigurePart } from './FigurePart';
export class FigurePartSet implements IFigurePartSet
{
private _id: number;
private _type: string;
private _gender: string;
private _clubLevel: number;
private _isColorable: boolean;
private _isSelectable: boolean;
private _parts: IFigurePart[];
private _hiddenLayers: string[];
private _isPreSelectable: boolean;
private _isSellable: boolean;
constructor(type: string, data: IFigureDataSet)
{
if(!type || !data) throw new Error('invalid_data');
this._id = data.id;
this._type = type;
this._gender = data.gender;
this._clubLevel = data.club;
this._isColorable = data.colorable;
this._isSelectable = data.selectable;
this._parts = [];
this._hiddenLayers = [];
this._isPreSelectable = data.preselectable;
this._isSellable = data.sellable;
for(const part of data.parts)
{
const newPart = new FigurePart(part);
const partIndex = this.getPartIndex(newPart);
if(partIndex !== -1) this._parts.splice(partIndex, 0, newPart);
else this._parts.push(newPart);
}
if(data.hiddenLayers)
{
for(const hiddenLayer of data.hiddenLayers) this._hiddenLayers.push(hiddenLayer.partType);
}
}
public dispose(): void
{
this._parts = null;
this._hiddenLayers = null;
}
private getPartIndex(part: FigurePart): number
{
const totalParts = this._parts.length;
if(!totalParts) return -1;
for(let i = 0; i < totalParts; i++)
{
const existingPart = this._parts[i];
if(!existingPart) continue;
if(existingPart.type !== part.type || existingPart.index > part.index) continue;
return i;
}
return -1;
}
public getPart(k: string, _arg_2: number): IFigurePart
{
for(const part of this._parts)
{
if((part.type !== k) || (part.id !== _arg_2)) continue;
return part;
}
return null;
}
public get id(): number
{
return this._id;
}
public get type(): string
{
return this._type;
}
public get gender(): string
{
return this._gender;
}
public get clubLevel(): number
{
return this._clubLevel;
}
public get isColorable(): boolean
{
return this._isColorable;
}
public get isSelectable(): boolean
{
return this._isSelectable;
}
public get parts(): IFigurePart[]
{
return this._parts;
}
public get hiddenLayers(): string[]
{
return this._hiddenLayers;
}
public get isPreSelectable(): boolean
{
return this._isPreSelectable;
}
public get isSellable(): boolean
{
return this._isSellable;
}
}
@@ -0,0 +1,46 @@
import { IAdvancedMap, IFigureDataPalette, IPalette, IPartColor } from '@nitrots/api';
import { AdvancedMap } from '@nitrots/utils';
import { PartColor } from './PartColor';
export class Palette implements IPalette
{
private _id: number;
private _colors: IAdvancedMap<string, IPartColor>;
constructor(data: IFigureDataPalette)
{
if(!data) throw new Error('invalid_data');
this._id = data.id;
this._colors = new AdvancedMap();
this.append(data);
}
public append(data: IFigureDataPalette): void
{
for(const color of data.colors)
{
const newColor = new PartColor(color);
this._colors.add(color.id.toString(), newColor);
}
}
public getColor(id: number): IPartColor
{
if((id === undefined) || id < 0) return null;
return (this._colors.getValue(id.toString()) || null);
}
public get id(): number
{
return this._id;
}
public get colors(): IAdvancedMap<string, IPartColor>
{
return this._colors;
}
}
@@ -0,0 +1,46 @@
import { IFigureDataColor, IPartColor } from '@nitrots/api';
export class PartColor implements IPartColor
{
private _id: number;
private _index: number;
private _clubLevel: number;
private _isSelectable: boolean;
private _rgb: number;
constructor(data: IFigureDataColor)
{
if(!data) throw new Error('invalid_data');
this._id = data.id;
this._index = data.index;
this._clubLevel = (data.club || 0);
this._isSelectable = data.selectable;
this._rgb = parseInt('0x' + data.hexCode, 16);
}
public get id(): number
{
return this._id;
}
public get index(): number
{
return this._index;
}
public get clubLevel(): number
{
return this._clubLevel;
}
public get isSelectable(): boolean
{
return this._isSelectable;
}
public get rgb(): number
{
return this._rgb;
}
}
@@ -0,0 +1,104 @@
import { IAdvancedMap, IFigureDataSetType, IFigurePartSet, ISetType } from '@nitrots/api';
import { AdvancedMap } from '@nitrots/utils';
import { FigurePartSet } from './FigurePartSet';
export class SetType implements ISetType
{
private _type: string;
private _paletteId: number;
private _isMandatory: { [index: string]: boolean[] };
private _partSets: IAdvancedMap<string, IFigurePartSet>;
constructor(data: IFigureDataSetType)
{
if(!data) throw new Error('invalid_data');
this._type = data.type;
this._paletteId = data.paletteId;
this._isMandatory = {};
this._isMandatory['F'] = [data.mandatory_f_0, data.mandatory_f_1];
this._isMandatory['M'] = [data.mandatory_m_0, data.mandatory_m_1];
this._partSets = new AdvancedMap();
this.append(data);
}
public dispose(): void
{
for(const set of this._partSets.getValues())
{
const partSet = set as FigurePartSet;
partSet.dispose();
}
this._partSets = null;
}
public cleanUp(data: IFigureDataSetType): void
{
for(const set of data.sets)
{
const setId = set.id.toString();
const partSet = (this._partSets.getValue(setId) as FigurePartSet);
if(partSet)
{
partSet.dispose();
this._partSets.remove(setId);
}
}
}
public append(setType: IFigureDataSetType): void
{
if(!setType || !setType.sets) return;
for(const set of setType.sets) this._partSets.add(set.id.toString(), new FigurePartSet(this._type, set));
}
public getDefaultPartSet(gender: string): IFigurePartSet
{
for(const set of this._partSets.getValues())
{
if(!set) continue;
if((set.clubLevel === 0) && ((set.gender === gender) || (set.gender === 'U'))) return set;
}
return null;
}
public getPartSet(k: number): IFigurePartSet
{
return this._partSets.getValue(k.toString());
}
public get type(): string
{
return this._type;
}
public get paletteID(): number
{
return this._paletteId;
}
public isMandatory(k: string, _arg_2: number): boolean
{
return this._isMandatory[k.toUpperCase()][Math.min(_arg_2, 1)];
}
public optionalFromClubLevel(k: string): number
{
const _local_2 = this._isMandatory[k.toUpperCase()];
return _local_2.indexOf(false);
}
public get partSets(): IAdvancedMap<string, IFigurePartSet>
{
return this._partSets;
}
}
@@ -0,0 +1,5 @@
export * from './FigurePart';
export * from './FigurePartSet';
export * from './Palette';
export * from './PartColor';
export * from './SetType';
+7
View File
@@ -0,0 +1,7 @@
export * from './AvatarAnimationData';
export * from './AvatarCanvas';
export * from './FigureSetData';
export * from './PartSetsData';
export * from './animation';
export * from './figure';
export * from './parts';
@@ -0,0 +1,26 @@
export class ActivePartSet
{
private _id: string;
private _parts: string[];
constructor(data: any)
{
this._id = data.id;
this._parts = [];
if(data.activeParts && (data.activeParts.length > 0))
{
for(const part of data.activeParts)
{
if(!part) continue;
this._parts.push(part.setType);
}
}
}
public get parts(): string[]
{
return this._parts;
}
}
@@ -0,0 +1,64 @@
export class PartDefinition
{
private _setType: string;
private _flippedSetType: string;
private _removeSetType: string;
private _appendToFigure: boolean;
private _staticId: number;
constructor(data: any)
{
if(!data) throw new Error('invalid_data');
this._setType = data.setType;
this._flippedSetType = data.flippedSetType || null;
this._removeSetType = data.removeSetType || null;
this._appendToFigure = false;
this._staticId = -1;
}
public hasStaticId(): boolean
{
return this._staticId >= 0;
}
public get staticId(): number
{
return this._staticId;
}
public set staticId(k: number)
{
this._staticId = k;
}
public get setType(): string
{
return this._setType;
}
public get flippedSetType(): string
{
return this._flippedSetType;
}
public set flippedSetType(type: string)
{
this._flippedSetType = type;
}
public get removeSetType(): string
{
return this._removeSetType;
}
public get appendToFigure(): boolean
{
return this._appendToFigure;
}
public set appendToFigure(flag: boolean)
{
this._appendToFigure = flag;
}
}
@@ -0,0 +1,2 @@
export * from './ActivePartSet';
export * from './PartDefinition';