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(data: any): void { if(!data) return; this._geometry = new AvatarModelGeometry(data); } public initActions(assets: IAssetManager, data: any): void { if(!data) return; this._actionManager = new AvatarActionManager(assets, data); this._defaultAction = this._actionManager.getDefaultAction(); } public updateActions(data: any): void { this._actionManager.updateActions(data); this._defaultAction = this._actionManager.getDefaultAction(); } public initPartSets(data: any): boolean { if(!data) return false; if(this._partSetsData.parse(data)) { this._partSetsData.getPartDefinition('ri').appendToFigure = true; this._partSetsData.getPartDefinition('li').appendToFigure = true; return true; } return false; } public initAnimation(data: any): boolean { if(!data) return false; return this._animationData.parse(data); } public initFigureData(data: IFigureData): boolean { if(!data) return false; return this._figureData.parse(data); } public injectFigureData(data: IFigureData): void { this._figureData.injectJSON(data); } public registerAnimations(assets: IAssetManager, prefix: string = 'fx', maxCount: number = 200): void { let index = 0; while(index < maxCount) { const collection = assets.getCollection((prefix + 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(figureContainer: IAvatarFigureContainer, partType: string, colorIndex: number = 0): IPartColor { const colorIds = figureContainer.getPartColorIds(partType); if(!colorIds || (colorIds.length < colorIndex)) return null; const setType = this._figureData.getSetType(partType); if(setType == null) return null; const palette = this._figureData.getPalette(setType.paletteID); if(!palette) return null; return palette.getColor(colorIds[colorIndex]); } public getBodyPartData(animation: string, frameCount: number, spriteId: string): AvatarAnimationLayerData { return this._animationManager.getLayerData(animation, frameCount, spriteId) as AvatarAnimationLayerData; } public getAnimation(name: string): Animation { return this._animationManager.getAnimation(name); } public getActionDefinition(id: string): ActionDefinition { return this._actionManager.getActionDefinition(id); } public getActionDefinitionWithState(state: string): ActionDefinition { return this._actionManager.getActionDefinitionWithState(state); } public isMainAvatarSet(setType: string): boolean { return this._geometry.isMainAvatarSet(setType); } public sortActions(actions: IActiveActionData[]): IActiveActionData[] { return this._actionManager.sortActions(actions); } public maxFrames(actions: IActiveActionData[]): number { let maxFrameCount = 0; for(const action of actions) { maxFrameCount = Math.max(maxFrameCount, this._animationData.getFrameCount(action.definition)); } return maxFrameCount; } public getMandatorySetTypeIds(gender: string, clubLevel: number): string[] { if(!this._mandatorySetTypeIds[gender]) { this._mandatorySetTypeIds[gender] = []; } if(this._mandatorySetTypeIds[gender][clubLevel]) { return this._mandatorySetTypeIds[gender][clubLevel]; } this._mandatorySetTypeIds[gender][clubLevel] = this._figureData.getMandatorySetTypeIds(gender, clubLevel); return this._mandatorySetTypeIds[gender][clubLevel]; } public getDefaultPartSet(partType: string, gender: string): IFigurePartSet { return this._figureData.getDefaultPartSet(partType, gender); } public getCanvasOffsets(actions: IActiveActionData[], scale: string, direction: number): number[] { return this._actionManager.getCanvasOffsets(actions, scale, direction); } public getCanvas(scale: string, geometryType: string): AvatarCanvas { return this._geometry.getCanvas(scale, geometryType); } public removeDynamicItems(avatar: IAvatarImage): void { this._geometry.removeDynamicItems(avatar); } public getActiveBodyPartIds(action: IActiveActionData, avatar: IAvatarImage): string[] { let partTypeIds: string[] = []; const bodyPartIds: string[] = []; const geometryType = action.definition.geometryType; if(action.definition.isAnimation) { const animationKey = ((action.definition.state + '.') + action.actionParameter); const animation = this._animationManager.getAnimation(animationKey); if(animation) { partTypeIds = animation.getAnimatedBodyPartIds(0, action.overridingAction); if(animation.hasAddData()) { const dynamicPart = { id: '', x: 0, y: 0, z: 0, radius: 0.01, nx: 0, ny: 0, nz: -1, double: 1 }; const partSetDefinition = { setType: '' }; for(const addData of animation.addData) { const bodyPart = this._geometry.getBodyPart(geometryType, addData.align); if(bodyPart) { dynamicPart.id = addData.id; bodyPart.addPart(dynamicPart, avatar); partSetDefinition.setType = addData.id; const partDefinition = this._partSetsData.addPartDefinition(partSetDefinition); partDefinition.appendToFigure = true; if(addData.base === '') partDefinition.staticId = 1; if(bodyPartIds.indexOf(bodyPart.id) === -1) bodyPartIds.push(bodyPart.id); } } } } for(const partTypeId of partTypeIds) { const bodyPart = this._geometry.getBodyPart(geometryType, partTypeId); if(bodyPart && (bodyPartIds.indexOf(bodyPart.id) === -1)) bodyPartIds.push(bodyPart.id); } if(bodyPartIds.length === 0) { partTypeIds = this._partSetsData.getActiveParts(action.definition); for(const partType of partTypeIds) { const bodyPart = this._geometry.getBodyPartOfItem(geometryType, partType, avatar); if(bodyPart && (bodyPartIds.indexOf(bodyPart.id) === -1)) bodyPartIds.push(bodyPart.id); } } } else { partTypeIds = this._partSetsData.getActiveParts(action.definition); for(const partType of partTypeIds) { const bodyPart = this._geometry.getBodyPartOfItem(geometryType, partType, avatar); if(bodyPart && (bodyPartIds.indexOf(bodyPart.id) === -1)) bodyPartIds.push(bodyPart.id); } } return bodyPartIds; } public getBodyPartsUnordered(avatarSet: string): string[] { return this._geometry.getBodyPartIdsInAvatarSet(avatarSet); } public getBodyParts(avatarSet: string, geometryType: string, direction: number): string[] { const angle = AvatarDirectionAngle.DIRECTION_TO_ANGLE[direction]; return this._geometry.getBodyPartsAtAngle(avatarSet, angle, geometryType); } public getFrameBodyPartOffset(action: IActiveActionData, direction: number, frameCount: number, bodyPartId: string): Point { const animationAction = this._animationData.getAction(action.definition); if(animationAction) return animationAction.getFrameBodyPartOffset(direction, frameCount, bodyPartId); return AnimationAction.DEFAULT_OFFSET; } public getParts(bodyPartId: string, figureContainer: IAvatarFigureContainer, action: IActiveActionData, geometryType: string, direction: number, removes: string[], avatar: IAvatarImage, itemOverrides: Map = null): AvatarImagePartContainer[] { const effectAnimation: Animation = null; let actionDefinition: IActionDefinition = null; let animationFrames: AvatarAnimationFrame[] = []; let partColor: IPartColor = null; if(!action) return []; const activePartTypes = this._partSetsData.getActiveParts(action.definition); const partContainers: AvatarImagePartContainer[] = []; let defaultFrames: any[] = [0]; const animationAction = this._animationData.getAction(action.definition); if(action.definition.isAnimation) { const animationKey = ((action.definition.state + '.') + action.actionParameter); const spriteAnimation = this._animationManager.getAnimation(animationKey); if(spriteAnimation) { defaultFrames = this.getPopulatedArray(spriteAnimation.frameCount(action.overridingAction)); for(const animatedPartId of spriteAnimation.getAnimatedBodyPartIds(0, action.overridingAction)) { if(animatedPartId === bodyPartId) { const geometryBodyPart = this._geometry.getBodyPart(geometryType, animatedPartId); if(geometryBodyPart) { for(const dynamicPart of geometryBodyPart.getDynamicParts(avatar)) { activePartTypes.push(dynamicPart.id); } } } } } } const visiblePartTypes = this._geometry.getParts(geometryType, bodyPartId, direction, activePartTypes, avatar); const figurePartTypeIds = figureContainer.getPartTypeIds(); const mainAction = avatar?.getMainAction?.(); const isSittingPosture = (mainAction?.definition?.assetPartDefinition === 'sit') || (action.definition.assetPartDefinition === 'sit'); for(const figurePartType of figurePartTypeIds) { if(itemOverrides) { if(itemOverrides.get(figurePartType)) continue; } const partSetId = figureContainer.getPartSetId(figurePartType); const partColorIds = figureContainer.getPartColorIds(figurePartType); const setType = this._figureData.getSetType(figurePartType); if(setType) { const palette = this._figureData.getPalette(setType.paletteID); if(palette) { const figurePartSet = setType.getPartSet(partSetId); if(figurePartSet) { removes = removes.concat(figurePartSet.hiddenLayers); let petHasVisibleSit = false; if(isSittingPosture && figurePartType === 'pt') { for(const fp of figurePartSet.parts) { if(fp.type === 'pt') { for(const dir of ['0', '2']) { const assetName = 'h_sit_pt_' + fp.id + '_' + dir + '_0'; const testAsset = this._renderManager.getAssetByName(assetName); if(testAsset && testAsset.width > 1 && testAsset.height > 1 && testAsset.source === assetName) { const stdName = 'h_std_pt_' + fp.id + '_' + dir + '_0'; const stdAsset = this._renderManager.getAssetByName(stdName); if(!stdAsset || stdAsset.source !== assetName) { petHasVisibleSit = true; break; } } } break; } } } for(const figurePart of figurePartSet.parts) { if(isSittingPosture && figurePartType === 'pt') { if(petHasVisibleSit && figurePart.type !== 'pt') continue; if(!petHasVisibleSit) continue; } if(visiblePartTypes.indexOf(figurePart.type) > -1) { if(animationAction) { const animationPart = animationAction.getPart(figurePart.type); if(animationPart) { animationFrames = animationPart.frames; } else { animationFrames = defaultFrames; } } else { animationFrames = defaultFrames; } actionDefinition = action.definition; if(activePartTypes.indexOf(figurePart.type) === -1) actionDefinition = this._defaultAction; const partDefinition = this._partSetsData.getPartDefinition(figurePart.type); let flippedPartType = (!partDefinition) ? figurePart.type : partDefinition.flippedSetType; if(!flippedPartType || (flippedPartType === '')) flippedPartType = figurePart.type; if(partColorIds && (partColorIds.length > (figurePart.colorLayerIndex - 1))) { partColor = palette.getColor(partColorIds[(figurePart.colorLayerIndex - 1)]); } const isColorable = (figurePart.colorLayerIndex > 0); const container = new AvatarImagePartContainer(bodyPartId, figurePart.type, figurePart.id.toString(), partColor, animationFrames, actionDefinition, isColorable, figurePart.paletteMap, flippedPartType); partContainers.push(container); } } } } } } const sortedContainers: AvatarImagePartContainer[] = []; for(const visiblePartType of visiblePartTypes) { let overrideColor: IPartColor = null; let partFound = false; const hasItemOverride = ((itemOverrides) && (itemOverrides.get(visiblePartType))); for(const container of partContainers) { if(container.partType === visiblePartType) { if(hasItemOverride) { overrideColor = container.color; } else { partFound = true; if(removes.indexOf(visiblePartType) === -1) sortedContainers.push(container); } } } if(!partFound) { if(hasItemOverride) { const itemId = itemOverrides.get(visiblePartType); let charCodeSum = 0; let charIndex = 0; while(charIndex < itemId.length) { charCodeSum = (charCodeSum + itemId.charCodeAt(charIndex)); charIndex++; } if(animationAction) { const animationPart = animationAction.getPart(visiblePartType); if(animationPart) { animationFrames = animationPart.frames; } else { animationFrames = defaultFrames; } } else { animationFrames = defaultFrames; } const container = new AvatarImagePartContainer(bodyPartId, visiblePartType, itemId, overrideColor, animationFrames, action.definition, (!(overrideColor == null)), -1, visiblePartType, false, 1); sortedContainers.push(container); } else { if(activePartTypes.indexOf(visiblePartType) > -1) { const ownerBodyPart = this._geometry.getBodyPartOfItem(geometryType, visiblePartType, avatar); if(bodyPartId !== ownerBodyPart.id) { // } else { const partDefinition = this._partSetsData.getPartDefinition(visiblePartType); let isBlended = false; let blendFactor = 1; if(partDefinition.appendToFigure) { let partId = '1'; if(action.actionParameter !== '') { partId = action.actionParameter; } if(partDefinition.hasStaticId()) { partId = partDefinition.staticId.toString(); } if(effectAnimation != null) { const addData = effectAnimation.getAddData(visiblePartType); if(addData) { isBlended = addData.isBlended; blendFactor = addData.blend; } } if(animationAction) { const animationPart = animationAction.getPart(visiblePartType); if(animationPart) { animationFrames = animationPart.frames; } else { animationFrames = defaultFrames; } } else { animationFrames = defaultFrames; } const container = new AvatarImagePartContainer(bodyPartId, visiblePartType, partId, null, animationFrames, action.definition, false, -1, visiblePartType, isBlended, blendFactor); sortedContainers.push(container); } } } } } } return sortedContainers; } private getPopulatedArray(count: number): number[] { const result: number[] = []; let index = 0; while(index < count) { result.push(index); index++; } return result; } public getItemIds(): string[] { if(this._actionManager) { const params = this._actionManager.getActionDefinition('CarryItem').params; const itemIds = []; for(const value of params.values()) itemIds.push(value); return itemIds; } 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; } }