Files
Nitro_Render_V3/packages/room/src/object/visualization/avatar/AvatarVisualization.ts
T
2026-03-02 16:52:08 +01:00

1256 lines
39 KiB
TypeScript

import { AlphaTolerance, AvatarAction, AvatarGuideStatus, AvatarSetType, IAdvancedMap, IAvatarEffectListener, IAvatarImage, IAvatarImageListener, IGraphicAsset, IObjectVisualizationData, IRoomGeometry, IRoomObject, IRoomObjectModel, RoomObjectSpriteType, RoomObjectVariable } from '@nitrots/api';
import { AdvancedMap, GetRenderer } from '@nitrots/utils';
import { Container, RenderTexture, Sprite, Texture } from 'pixi.js';
import { RoomObjectSpriteVisualization } from '../RoomObjectSpriteVisualization';
import { RoomWindowReflectionState } from '../RoomWindowReflectionState';
import { AvatarVisualizationData } from './AvatarVisualizationData';
import { ExpressionAdditionFactory, FloatingIdleZAddition, GameClickTargetAddition, GuideStatusBubbleAddition, IAvatarAddition, MutedBubbleAddition, NumberBubbleAddition, TypingBubbleAddition } from './additions';
export class AvatarVisualization extends RoomObjectSpriteVisualization implements IAvatarImageListener, IAvatarEffectListener
{
private static AVATAR: string = 'avatar';
private static FLOATING_IDLE_Z_ID: number = 1;
private static TYPING_BUBBLE_ID: number = 2;
private static EXPRESSION_ID: number = 3;
private static NUMBER_BUBBLE_ID: number = 4;
private static GAME_CLICK_TARGET_ID: number = 5;
private static MUTED_BUBBLE_ID: number = 6;
private static GUIDE_BUBBLE_ID: number = 7;
private static OWN_USER_ID: number = 4;
private static UPDATE_TIME_INCREASER: number = 41;
private static AVATAR_LAYER_ID: number = 0;
private static SHADOW_LAYER_ID: number = 1;
private static SNOWBOARDING_EFFECT: number = 97;
private static INITIAL_RESERVED_SPRITES: number = 2;
private static ANIMATION_FRAME_UPDATE_INTERVAL: number = 2;
private static DEFAULT_CANVAS_OFFSETS: number[] = [0, 0, 0];
private static MAX_EFFECT_CACHE: number = 2;
private static SPRITE_INDEX_AVATAR: number = 0;
private static BASE_Y_SCALE: number = 1000;
private static AVATAR_SPRITE_DEFAULT_DEPTH: number = -0.01;
private static AVATAR_OWN_DEPTH_ADJUST: number = 0.001;
private static AVATAR_SPRITE_LAYING_DEPTH: number = -0.409;
protected _data: AvatarVisualizationData;
private _avatarImage: IAvatarImage;
private _cachedAvatars: IAdvancedMap<string, IAvatarImage>;
private _cachedAvatarEffects: IAdvancedMap<string, IAvatarImage>;
private _shadow: IGraphicAsset;
private _lastUpdate: number;
private _disposed: boolean;
private _figure: string;
private _gender: string;
private _direction: number;
private _headDirection: number;
private _posture: string;
private _postureParameter: string;
private _canStandUp: boolean;
private _postureOffset: number;
private _verticalOffset: number;
private _angle: number;
private _headAngle: number;
private _talk: boolean;
private _expression: number;
private _sleep: boolean;
private _blink: boolean;
private _gesture: number;
private _sign: number;
private _highlightEnabled: boolean;
private _highlight: boolean;
private _dance: number;
private _effect: number;
private _carryObject: number;
private _useObject: number;
private _ownUser: boolean;
private _isLaying: boolean;
private _layInside: boolean;
private _isAnimating: boolean;
private _extraSpritesStartIndex: number;
private _forcedAnimFrames: number;
private _updatesUntilFrameUpdate: number;
private _isAvatarReady: boolean;
private _needsUpdate: boolean;
private _geometryUpdateCounter: number;
private _reflectionVerticalOffset: number;
private _reflectionOppositeTexture: Texture;
private _reflectionOppositeDirection: number;
private _reflectionOppositeBaseTexture: Texture;
private _additions: Map<number, IAvatarAddition>;
constructor()
{
super();
this._data = null;
this._avatarImage = null;
this._cachedAvatars = new AdvancedMap();
this._cachedAvatarEffects = new AdvancedMap();
this._shadow = null;
this._lastUpdate = -1000;
this._disposed = false;
this._figure = null;
this._gender = null;
this._direction = -1;
this._headDirection = -1;
this._posture = '';
this._postureParameter = '';
this._canStandUp = false;
this._postureOffset = 0;
this._verticalOffset = 0;
this._angle = -1;
this._headAngle = -1;
this._talk = false;
this._expression = 0;
this._sleep = false;
this._blink = false;
this._gesture = 0;
this._sign = -1;
this._highlightEnabled = false;
this._highlight = false;
this._dance = 0;
this._effect = 0;
this._carryObject = 0;
this._useObject = 0;
this._ownUser = false;
this._isLaying = false;
this._layInside = false;
this._isAnimating = false;
this._extraSpritesStartIndex = AvatarVisualization.INITIAL_RESERVED_SPRITES;
this._forcedAnimFrames = 0;
this._updatesUntilFrameUpdate = 0;
this._isAvatarReady = false;
this._needsUpdate = false;
this._geometryUpdateCounter = -1;
this._reflectionVerticalOffset = 0;
this._reflectionOppositeTexture = null;
this._reflectionOppositeDirection = -1;
this._reflectionOppositeBaseTexture = null;
this._additions = new Map();
}
public initialize(data: IObjectVisualizationData): boolean
{
if(!(data instanceof AvatarVisualizationData)) return false;
this._data = data;
this.createSprites(AvatarVisualization.INITIAL_RESERVED_SPRITES);
super.initialize(data);
return true;
}
public dispose(): void
{
if(this._disposed) return;
super.dispose();
if(this._avatarImage) this._avatarImage.dispose();
if(this._reflectionOppositeTexture)
{
this._reflectionOppositeTexture.destroy(true);
this._reflectionOppositeTexture = null;
}
if(this.object) RoomWindowReflectionState.removeAvatar(this.object.id);
this._shadow = null;
this._disposed = true;
}
public update(geometry: IRoomGeometry, time: number, update: boolean, skipUpdate: boolean): void
{
if(!this.object || !geometry || !this._data) return;
if(time < (this._lastUpdate + AvatarVisualization.UPDATE_TIME_INCREASER)) return;
this._lastUpdate += AvatarVisualization.UPDATE_TIME_INCREASER;
if((this._lastUpdate + AvatarVisualization.UPDATE_TIME_INCREASER) < time) this._lastUpdate = (time - AvatarVisualization.UPDATE_TIME_INCREASER);
const model = this.object.model;
const scale = geometry.scale;
const effect = this._effect;
let didScaleUpdate = false;
let didEffectUpdate = false;
let otherUpdate = false;
let objectUpdate = false;
const updateModel = this.updateModel(model, scale);
if((updateModel || (scale !== this._scale)) || !this._avatarImage)
{
if(scale !== this._scale)
{
didScaleUpdate = true;
this.updateScale(scale);
}
if(effect !== this._effect) didEffectUpdate = true;
if(didScaleUpdate || !this._avatarImage || didEffectUpdate)
{
this._avatarImage = this.createAvatarImage(scale, this._effect);
if(!this._avatarImage) return;
otherUpdate = true;
const sprite = this.getSprite(AvatarVisualization.AVATAR_LAYER_ID);
if((sprite && this._avatarImage) && this._avatarImage.isPlaceholder())
{
sprite.alpha = 150;
}
else if(sprite)
{
sprite.alpha = 255;
}
}
if(!this._avatarImage) return;
if(didEffectUpdate && this._avatarImage.animationHasResetOnToggle) this._avatarImage.resetAnimationFrameCounter();
this.updateShadow(scale);
objectUpdate = this.updateObject(this.object, geometry, update, true);
this.processActionsForAvatar(this._avatarImage);
if(this._additions)
{
let index = this._extraSpritesStartIndex;
for(const addition of this._additions.values())
{
addition.update(this.getSprite(index++), scale);
}
}
this._scale = scale;
}
else
{
objectUpdate = this.updateObject(this.object, geometry, update);
}
if(this._additions)
{
let index = this._extraSpritesStartIndex;
for(const addition of this._additions.values())
{
if(addition.animate(this.getSprite(index++))) this.updateSpriteCounter++;
}
}
const update1 = (objectUpdate || updateModel || didScaleUpdate);
const update2 = ((this._isAnimating || (this._forcedAnimFrames > 0)) && update);
if(update1) this._forcedAnimFrames = AvatarVisualization.ANIMATION_FRAME_UPDATE_INTERVAL;
if(update1 || update2)
{
this.updateSpriteCounter++;
this._forcedAnimFrames--;
this._updatesUntilFrameUpdate--;
if((((this._updatesUntilFrameUpdate <= 0) || didScaleUpdate) || updateModel) || otherUpdate)
{
this._avatarImage.updateAnimationByFrames(1);
this._updatesUntilFrameUpdate = AvatarVisualization.ANIMATION_FRAME_UPDATE_INTERVAL;
}
else
{
this.updateWindowReflectionSource();
return;
}
let _local_20 = this._avatarImage.getCanvasOffsets();
if(!_local_20 || (_local_20.length < 3)) _local_20 = AvatarVisualization.DEFAULT_CANVAS_OFFSETS;
const sprite = this.getSprite(AvatarVisualization.SPRITE_INDEX_AVATAR);
if(sprite)
{
const highlightEnabled = ((this.object.model.getValue<number>(RoomObjectVariable.FIGURE_HIGHLIGHT_ENABLE) === 1) && (this.object.model.getValue<number>(RoomObjectVariable.FIGURE_HIGHLIGHT) === 1));
const avatarImage = this._avatarImage.processAsTexture(AvatarSetType.FULL, highlightEnabled);
if(avatarImage)
{
sprite.texture = avatarImage;
if(highlightEnabled)
{
// sprite.filters = [
// new GlowFilter({
// color: 0xFFFFFF,
// distance: 6
// })
// ];
}
else
{
sprite.filters = [];
}
}
if(sprite.texture)
{
sprite.offsetX = ((((-1 * scale) / 2) + _local_20[0]) - ((sprite.texture.width - scale) / 2));
sprite.offsetY = (((-(sprite.texture.height) + (scale / 4)) + _local_20[1]) + this._postureOffset);
}
if(this._isLaying)
{
if(this._layInside) sprite.relativeDepth = -0.5;
else sprite.relativeDepth = (AvatarVisualization.AVATAR_SPRITE_LAYING_DEPTH + _local_20[2]);
}
else
{
sprite.relativeDepth = (AvatarVisualization.AVATAR_SPRITE_DEFAULT_DEPTH + _local_20[2]);
}
if(this._ownUser)
{
sprite.relativeDepth -= AvatarVisualization.AVATAR_OWN_DEPTH_ADJUST;
sprite.spriteType = RoomObjectSpriteType.AVATAR_OWN;
}
else
{
sprite.spriteType = RoomObjectSpriteType.AVATAR;
}
}
const typingBubble = this.getAddition(AvatarVisualization.TYPING_BUBBLE_ID) as TypingBubbleAddition;
if(typingBubble)
{
if(!this._isLaying) typingBubble.relativeDepth = ((AvatarVisualization.AVATAR_SPRITE_DEFAULT_DEPTH - 0.01) + _local_20[2]);
else typingBubble.relativeDepth = ((AvatarVisualization.AVATAR_SPRITE_LAYING_DEPTH - 0.01) + _local_20[2]);
}
this._isAnimating = this._avatarImage.isAnimating();
let _local_21 = AvatarVisualization.INITIAL_RESERVED_SPRITES;
const direction = this._avatarImage.getDirection();
for(const spriteData of this._avatarImage.getSprites())
{
if(spriteData.id === AvatarVisualization.AVATAR)
{
const sprite = this.getSprite(AvatarVisualization.SPRITE_INDEX_AVATAR);
if(sprite)
{
const layerData = this._avatarImage.getLayerData(spriteData);
let offsetX = spriteData.getDirectionOffsetX(direction);
let offsetY = spriteData.getDirectionOffsetY(direction);
if(layerData)
{
offsetX += layerData.dx;
offsetY += layerData.dy;
}
if(scale < 48)
{
offsetX /= 2;
offsetY /= 2;
}
if(!this._canStandUp)
{
sprite.offsetX += offsetX;
sprite.offsetY += offsetY;
}
}
}
else
{
const sprite = this.getSprite(_local_21);
if(sprite)
{
sprite.alphaTolerance = AlphaTolerance.MATCH_NOTHING;
sprite.visible = true;
const layerData = this._avatarImage.getLayerData(spriteData);
let frameNumber = 0;
let offsetX = spriteData.getDirectionOffsetX(direction);
let offsetY = spriteData.getDirectionOffsetY(direction);
const offsetZ = spriteData.getDirectionOffsetZ(direction);
let dd = 0;
if(spriteData.hasDirections) dd = direction;
if(layerData)
{
frameNumber = layerData.animationFrame;
offsetX += layerData.dx;
offsetY += layerData.dy;
dd += layerData.dd;
}
if(scale < 48)
{
offsetX /= 2;
offsetY /= 2;
}
if(dd < 0) dd += 8;
else
{
if(dd > 7) dd -= 8;
}
const assetName = ((((((this._avatarImage.getScale() + '_') + spriteData.member) + '_') + dd) + '_') + frameNumber);
const asset = this._avatarImage.getAsset(assetName);
if(!asset) continue;
sprite.texture = asset.texture;
sprite.offsetX = ((asset.offsetX - (scale / 2)) + offsetX);
sprite.offsetY = (asset.offsetY + offsetY);
sprite.flipH = asset.flipH;
if(spriteData.hasStaticY)
{
sprite.offsetY += ((this._verticalOffset * scale) / (2 * AvatarVisualization.BASE_Y_SCALE));
}
else
{
sprite.offsetY += this._postureOffset;
}
if(this._isLaying)
{
sprite.relativeDepth = (AvatarVisualization.AVATAR_SPRITE_LAYING_DEPTH - ((0.001 * this.totalSprites) * offsetZ));
}
else
{
sprite.relativeDepth = (AvatarVisualization.AVATAR_SPRITE_DEFAULT_DEPTH - ((0.001 * this.totalSprites) * offsetZ));
}
if(spriteData.ink === 33) sprite.blendMode = 'add';
else sprite.blendMode = 'normal';
}
_local_21++;
}
}
const avatarSprite = this.getSprite(AvatarVisualization.SPRITE_INDEX_AVATAR);
if(avatarSprite?.texture)
{
const baseOffsetY = (-(avatarSprite.texture.height) + (scale / 4));
this._reflectionVerticalOffset = avatarSprite.offsetY - baseOffsetY;
}
this.updateWindowReflectionSource();
}
}
private createAvatarImage(scale: number, effectId: number): IAvatarImage
{
let cachedImage: IAvatarImage = null;
let imageName = 'avatarImage' + scale.toString();
if(!effectId)
{
cachedImage = this._cachedAvatars.getValue(imageName);
}
else
{
imageName += '-' + effectId;
cachedImage = this._cachedAvatarEffects.getValue(imageName);
}
if(!cachedImage)
{
cachedImage = this._data.createAvatarImage(this._figure, scale, this._gender, this, this);
if(cachedImage)
{
if(!effectId)
{
this._cachedAvatars.add(imageName, cachedImage);
}
else
{
if(this._cachedAvatarEffects.length >= AvatarVisualization.MAX_EFFECT_CACHE)
{
const cached = this._cachedAvatarEffects.remove(this._cachedAvatarEffects.getKey(0));
if(cached) cached.dispose();
}
this._cachedAvatarEffects.add(imageName, cachedImage);
}
}
}
return cachedImage;
}
protected updateObject(object: IRoomObject, geometry: IRoomGeometry, update: boolean, _arg_4: boolean = false): boolean
{
if((!_arg_4 && (this.updateObjectCounter === object.updateCounter)) && (this._geometryUpdateCounter === geometry.updateId)) return false;
let direction = (object.getDirection().x - geometry.direction.x);
let headDirection = (this._headDirection - geometry.direction.x);
if(this._posture === 'float') headDirection = direction;
direction = (((direction % 360) + 360) % 360);
headDirection = (((headDirection % 360) + 360) % 360);
if((this._posture === 'sit') && this._canStandUp)
{
direction -= ((direction % 90) - 45);
headDirection -= ((headDirection % 90) - 45);
}
if((direction !== this._angle) || _arg_4)
{
update = true;
this._angle = direction;
direction = (direction - (135 - 22.5));
direction = ((direction + 360) % 360);
this._avatarImage.setDirectionAngle(AvatarSetType.FULL, direction);
}
if((headDirection !== this._headAngle) || _arg_4)
{
update = true;
this._headAngle = headDirection;
if(this._headAngle !== this._angle)
{
headDirection = (headDirection - (135 - 22.5));
headDirection = ((headDirection + 360) % 360);
this._avatarImage.setDirectionAngle(AvatarSetType.HEAD, headDirection);
}
}
this._geometryUpdateCounter = geometry.updateId;
this.updateObjectCounter = this.object.updateCounter;
return update;
}
protected updateModel(model: IRoomObjectModel, scale: number): boolean
{
if(!model) return false;
if(this.updateModelCounter === model.updateCounter) return false;
let needsUpdate = false;
const talk = (model.getValue<number>(RoomObjectVariable.FIGURE_TALK) > 0);
if(talk !== this._talk)
{
this._talk = talk;
needsUpdate = true;
}
const expression = model.getValue<number>(RoomObjectVariable.FIGURE_EXPRESSION);
if(expression !== this._expression)
{
this._expression = expression;
needsUpdate = true;
}
const sleep = (model.getValue<number>(RoomObjectVariable.FIGURE_SLEEP) > 0);
if(sleep !== this._sleep)
{
this._sleep = sleep;
needsUpdate = true;
}
const blink = (model.getValue<number>(RoomObjectVariable.FIGURE_BLINK) > 0);
if(blink !== this._blink)
{
this._blink = blink;
needsUpdate = true;
}
const gesture = (model.getValue<number>(RoomObjectVariable.FIGURE_GESTURE) || 0);
if(gesture !== this._gesture)
{
this._gesture = gesture;
needsUpdate = true;
}
const posture = model.getValue<string>(RoomObjectVariable.FIGURE_POSTURE);
if(posture !== this._posture)
{
this._posture = posture;
needsUpdate = true;
}
const postureParameter = model.getValue<string>(RoomObjectVariable.FIGURE_POSTURE_PARAMETER);
if(postureParameter !== this._postureParameter)
{
this._postureParameter = postureParameter;
needsUpdate = true;
}
const canStandUp = model.getValue<boolean>(RoomObjectVariable.FIGURE_CAN_STAND_UP);
if(canStandUp !== this._canStandUp)
{
this._canStandUp = canStandUp;
needsUpdate = true;
}
const verticalOffset = (model.getValue<number>(RoomObjectVariable.FIGURE_VERTICAL_OFFSET) * AvatarVisualization.BASE_Y_SCALE);
if(verticalOffset !== this._verticalOffset)
{
this._verticalOffset = verticalOffset;
needsUpdate = true;
}
const dance = (model.getValue<number>(RoomObjectVariable.FIGURE_DANCE) || 0);
if(dance !== this._dance)
{
this._dance = dance;
needsUpdate = true;
}
const effect = (model.getValue<number>(RoomObjectVariable.FIGURE_EFFECT) || 0);
if(effect !== this._effect)
{
this._effect = effect;
needsUpdate = true;
}
const carryObject = (model.getValue<number>(RoomObjectVariable.FIGURE_CARRY_OBJECT) || 0);
if(carryObject !== this._carryObject)
{
this._carryObject = carryObject;
needsUpdate = true;
}
const useObject = (model.getValue<number>(RoomObjectVariable.FIGURE_USE_OBJECT) || 0);
if(useObject !== this._useObject)
{
this._useObject = useObject;
needsUpdate = true;
}
const headDirection = model.getValue<number>(RoomObjectVariable.HEAD_DIRECTION);
if(headDirection !== this._headDirection)
{
this._headDirection = headDirection;
needsUpdate = true;
}
if((this._carryObject > 0) && (useObject > 0))
{
if(this._useObject !== this._carryObject)
{
this._useObject = this._carryObject;
needsUpdate = true;
}
}
else
{
if(this._useObject !== 0)
{
this._useObject = 0;
needsUpdate = true;
}
}
let idleAddition = this.getAddition(AvatarVisualization.FLOATING_IDLE_Z_ID);
if(this._sleep)
{
if(!idleAddition) idleAddition = this.addAddition(new FloatingIdleZAddition(AvatarVisualization.FLOATING_IDLE_Z_ID, this));
needsUpdate = true;
}
else
{
if(idleAddition) this.removeAddition(AvatarVisualization.FLOATING_IDLE_Z_ID);
}
const isMuted = (model.getValue<number>(RoomObjectVariable.FIGURE_IS_MUTED) > 0);
let mutedAddition = this.getAddition(AvatarVisualization.MUTED_BUBBLE_ID);
if(isMuted)
{
if(!mutedAddition) mutedAddition = this.addAddition(new MutedBubbleAddition(AvatarVisualization.MUTED_BUBBLE_ID, this));
needsUpdate = true;
}
else
{
if(mutedAddition)
{
this.removeAddition(AvatarVisualization.MUTED_BUBBLE_ID);
needsUpdate = true;
}
const isTyping = (model.getValue<number>(RoomObjectVariable.FIGURE_IS_TYPING) > 0);
let typingAddition = this.getAddition(AvatarVisualization.TYPING_BUBBLE_ID);
if(isTyping)
{
if(!typingAddition) typingAddition = this.addAddition(new TypingBubbleAddition(AvatarVisualization.TYPING_BUBBLE_ID, this));
needsUpdate = true;
}
else
{
if(typingAddition)
{
this.removeAddition(AvatarVisualization.TYPING_BUBBLE_ID);
needsUpdate = true;
}
}
}
const guideStatusValue = (model.getValue<number>(RoomObjectVariable.FIGURE_GUIDE_STATUS) || 0);
if(guideStatusValue !== AvatarGuideStatus.NONE)
{
this.removeAddition(AvatarVisualization.GUIDE_BUBBLE_ID);
this.addAddition(new GuideStatusBubbleAddition(AvatarVisualization.GUIDE_BUBBLE_ID, this, guideStatusValue));
needsUpdate = true;
}
else
{
if(this.getAddition(AvatarVisualization.GUIDE_BUBBLE_ID))
{
this.removeAddition(AvatarVisualization.GUIDE_BUBBLE_ID);
needsUpdate = true;
}
}
const isPlayingGame = (model.getValue<number>(RoomObjectVariable.FIGURE_IS_PLAYING_GAME) > 0);
let gameClickAddition = this.getAddition(AvatarVisualization.GAME_CLICK_TARGET_ID);
if(isPlayingGame)
{
if(!gameClickAddition) gameClickAddition = this.addAddition(new GameClickTargetAddition(AvatarVisualization.GAME_CLICK_TARGET_ID));
needsUpdate = true;
}
else
{
if(gameClickAddition) this.removeAddition(AvatarVisualization.GAME_CLICK_TARGET_ID);
}
const numberValue = model.getValue<number>(RoomObjectVariable.FIGURE_NUMBER_VALUE);
let numberAddition = this.getAddition(AvatarVisualization.NUMBER_BUBBLE_ID);
if(numberValue > 0)
{
if(!numberAddition) numberAddition = this.addAddition(new NumberBubbleAddition(AvatarVisualization.NUMBER_BUBBLE_ID, numberValue, this));
needsUpdate = true;
}
else
{
if(numberAddition) this.removeAddition(AvatarVisualization.NUMBER_BUBBLE_ID);
}
let expressionAddition = this.getAddition(AvatarVisualization.EXPRESSION_ID);
if(this._expression > 0)
{
if(!expressionAddition)
{
expressionAddition = ExpressionAdditionFactory.getExpressionAddition(AvatarVisualization.EXPRESSION_ID, this._expression, this);
if(expressionAddition) this.addAddition(expressionAddition);
}
}
else
{
if(expressionAddition) this.removeAddition(AvatarVisualization.EXPRESSION_ID);
}
this.updateScale(scale);
const gender = model.getValue<string>(RoomObjectVariable.GENDER);
if(gender !== this._gender)
{
this._gender = gender;
needsUpdate = true;
}
if(this.updateFigure(model.getValue<string>(RoomObjectVariable.FIGURE))) needsUpdate = true;
let sign = model.getValue<number>(RoomObjectVariable.FIGURE_SIGN);
if(sign === null) sign = -1;
if(this._sign !== sign)
{
this._sign = sign;
needsUpdate = true;
}
const highlightEnabled = (model.getValue<number>(RoomObjectVariable.FIGURE_HIGHLIGHT_ENABLE) > 0);
if(highlightEnabled !== this._highlightEnabled)
{
this._highlightEnabled = highlightEnabled;
needsUpdate = true;
}
if(this._highlightEnabled)
{
const highlight = (model.getValue<number>(RoomObjectVariable.FIGURE_HIGHLIGHT) > 0);
if(highlight !== this._highlight)
{
this._highlight = highlight;
needsUpdate = true;
}
}
const ownUser = (model.getValue<number>(RoomObjectVariable.OWN_USER) > 0);
if(ownUser !== this._ownUser)
{
this._ownUser = ownUser;
needsUpdate = true;
}
this.updateModelCounter = model.updateCounter;
return needsUpdate;
}
protected setDirection(direction: number): void
{
if(this._direction === direction) return;
this._direction = direction;
this._needsUpdate = true;
}
private updateScale(scale: number): void
{
if(scale < 48) this._blink = false;
if((this._posture === 'sit') || (this._posture === 'lay'))
{
this._postureOffset = (scale / 2);
}
else
{
this._postureOffset = 0;
}
this._layInside = false;
this._isLaying = false;
if(this._posture === 'lay')
{
this._isLaying = true;
const _local_2 = parseInt(this._postureParameter);
if(_local_2 < 0) this._layInside = true;
}
}
private processActionsForAvatar(avatar: IAvatarImage): void
{
if(!avatar) return;
avatar.initActionAppends();
avatar.appendAction(AvatarAction.POSTURE, this._posture, this._postureParameter);
if(this._gesture > 0) this._avatarImage.appendAction(AvatarAction.GESTURE, AvatarAction.getGesture(this._gesture));
if(this._dance > 0) this._avatarImage.appendAction(AvatarAction.DANCE, this._dance);
if(this._sign > -1) this._avatarImage.appendAction(AvatarAction.SIGN, this._sign);
if(this._carryObject > 0) this._avatarImage.appendAction(AvatarAction.CARRY_OBJECT, this._carryObject);
if(this._useObject > 0) this._avatarImage.appendAction(AvatarAction.USE_OBJECT, this._useObject);
if(this._talk) this._avatarImage.appendAction(AvatarAction.TALK);
if(this._sleep)
{
this._avatarImage.appendAction(AvatarAction.SLEEP);
}
else if(this._blink)
{
this._avatarImage.appendAction(AvatarAction.BLINK);
this._avatarImage.appendAction(AvatarAction.SLEEP);
}
if(this._expression > 0)
{
const expression = AvatarAction.getExpression(this._expression);
if(expression !== '')
{
switch(expression)
{
case AvatarAction.DANCE:
this._avatarImage.appendAction(AvatarAction.DANCE, 2);
break;
default:
this._avatarImage.appendAction(expression);
break;
}
}
}
if(this._effect > 0) this._avatarImage.appendAction(AvatarAction.EFFECT, this._effect);
avatar.endActionAppends();
this._isAnimating = avatar.isAnimating();
let spriteCount = AvatarVisualization.INITIAL_RESERVED_SPRITES;
for(const sprite of this._avatarImage.getSprites())
{
if(sprite.id !== AvatarVisualization.AVATAR) spriteCount++;
}
if(spriteCount !== this.totalSprites) this.createSprites(spriteCount);
this._extraSpritesStartIndex = spriteCount;
if(this._additions)
{
for(const addition of this._additions.values()) this.createSprite();
}
}
private updateFigure(figure: string): boolean
{
if(this._figure === figure) return false;
this._figure = figure;
this.clearAvatar();
return true;
}
public resetFigure(figure: string): void
{
this.clearAvatar();
}
public resetEffect(effect: number): void
{
this.clearAvatar();
}
private cloneTexture(texture: Texture): Texture
{
if(!texture) return null;
const width = Math.max(1, Math.ceil(texture.width));
const height = Math.max(1, Math.ceil(texture.height));
const target = RenderTexture.create({ width, height });
const sprite = new Sprite(texture);
const container = new Container();
container.addChild(sprite);
GetRenderer().render({
target,
container,
clear: true
});
container.destroy({ children: true });
return target;
}
private updateWindowReflectionSource(): void
{
if(!this.object) return;
const sprite = this.getSprite(AvatarVisualization.SPRITE_INDEX_AVATAR);
if(sprite?.texture)
{
const currentDirection = this._avatarImage?.getDirection();
let oppositeTexture = sprite.texture;
if((currentDirection !== undefined) && this._avatarImage)
{
const oppositeDirection = ((currentDirection + 4) % 8);
if(oppositeDirection !== currentDirection)
{
const highlightEnabled = ((this.object.model.getValue<number>(RoomObjectVariable.FIGURE_HIGHLIGHT_ENABLE) === 1) && (this.object.model.getValue<number>(RoomObjectVariable.FIGURE_HIGHLIGHT) === 1));
this._avatarImage.setDirection(AvatarSetType.FULL, oppositeDirection);
const renderedOpposite = (this._avatarImage.processAsTexture(AvatarSetType.FULL, highlightEnabled) || sprite.texture);
if((this._reflectionOppositeDirection !== currentDirection) || (this._reflectionOppositeBaseTexture !== sprite.texture) || !this._reflectionOppositeTexture)
{
if(this._reflectionOppositeTexture)
{
this._reflectionOppositeTexture.destroy(true);
this._reflectionOppositeTexture = null;
}
this._reflectionOppositeTexture = this.cloneTexture(renderedOpposite);
this._reflectionOppositeDirection = currentDirection;
this._reflectionOppositeBaseTexture = sprite.texture;
}
oppositeTexture = (this._reflectionOppositeTexture || renderedOpposite);
// Restore the live avatar direction and refresh the current texture so
// movement updates do not keep showing the opposite-facing texture.
this._avatarImage.setDirection(AvatarSetType.FULL, currentDirection);
sprite.texture = (this._avatarImage.processAsTexture(AvatarSetType.FULL, highlightEnabled) || sprite.texture);
}
}
RoomWindowReflectionState.setAvatar(this.object.id, sprite.texture, this.object.getLocation(), this._reflectionVerticalOffset, this.object.getDirection().x, oppositeTexture);
return;
}
RoomWindowReflectionState.removeAvatar(this.object.id);
}
private clearAvatar(): void
{
const sprite = this.getSprite(AvatarVisualization.AVATAR_LAYER_ID);
if(sprite)
{
sprite.texture = Texture.EMPTY;
sprite.alpha = 255;
}
for(const avatar of this._cachedAvatars.getValues()) avatar && avatar.dispose();
for(const avatar of this._cachedAvatarEffects.getValues()) avatar && avatar.dispose();
this._cachedAvatars.reset();
this._cachedAvatarEffects.reset();
if(this._reflectionOppositeTexture)
{
this._reflectionOppositeTexture.destroy(true);
this._reflectionOppositeTexture = null;
}
this._reflectionOppositeDirection = -1;
this._reflectionOppositeBaseTexture = null;
this._avatarImage = null;
if(this.object) RoomWindowReflectionState.removeAvatar(this.object.id);
}
private getAddition(id: number): IAvatarAddition
{
if(!this._additions) return null;
const existing = this._additions.get(id);
if(!existing) return null;
return existing;
}
private addAddition(addition: IAvatarAddition): IAvatarAddition
{
const existing = this.getAddition(addition.id);
if(existing) return;
this._additions.set(addition.id, addition);
return addition;
}
private removeAddition(id: number): void
{
const addition = this.getAddition(id);
if(!addition) return;
this._additions.delete(addition.id);
addition.dispose();
}
private updateShadow(scale: number): void
{
this._shadow = null;
const sprite = this.getSprite(AvatarVisualization.SHADOW_LAYER_ID);
if(!sprite) return;
let hasShadow = (((this._posture === 'mv') || (this._posture === 'std')) || ((this._posture === 'sit') && this._canStandUp));
if(this._effect === AvatarVisualization.SNOWBOARDING_EFFECT) hasShadow = false;
if(hasShadow)
{
sprite.visible = true;
if(!this._shadow || (scale !== this._scale))
{
let offsetX = 0;
let offsetY = 0;
if(scale < 48)
{
sprite.libraryAssetName = 'sh_std_sd_1_0_0';
this._shadow = this._avatarImage.getAsset(sprite.libraryAssetName);
offsetX = -8;
offsetY = ((this._canStandUp) ? 6 : -3);
}
else
{
sprite.libraryAssetName = 'h_std_sd_1_0_0';
this._shadow = this._avatarImage.getAsset(sprite.libraryAssetName);
offsetX = -17;
offsetY = ((this._canStandUp) ? 10 : -7);
}
if(this._shadow)
{
sprite.texture = this._shadow.texture;
sprite.offsetX = offsetX;
sprite.offsetY = offsetY;
sprite.alpha = 50;
sprite.relativeDepth = 1;
}
else
{
sprite.visible = false;
}
}
}
else
{
this._shadow = null;
sprite.visible = false;
}
}
public get direction(): number
{
return this._direction;
}
public get posture(): string
{
return this._posture;
}
public get angle(): number
{
return this._angle;
}
public get disposed(): boolean
{
return this._disposed;
}
}