import util from "./util.js" import InternalError from "./error/InternalError.js" import EAvatarRelationRank from "./enum/EAvatarRelationRank.js" import AvatarGroup from "./enum/AvatarGroup.js" import MotionType from "./enum/MotionType.js" import MotionAnimtion from "./enum/MotionAnimtion.js" import Queue from "./Queue.js" import Logger from "./Logger.js" import CoreBroadcastType from "./enum/CoreBroadcastType.js" import {tracker} from "./Tracker.js" import {xverseAvatarTools} from "./XverseAvatarTools.js" import ComponentItemType from "./enum/ComponentItemType.js" const logger = new Logger('xverse-avatar') export default class XverseAvatar extends EventEmitter { constructor({userId: s, isHost: c, room: _, avatarId: b, isSelf: k, group: j=AvatarGroup.Npc}) { super(); this.xAvatar = null; this._isHost = !1; this._room = null; this._withModel = !1; this._userId = null; this.group = AvatarGroup.User; this.state = "idle"; this.isLoading = !0; this._isMoving = !1; this._isRotating = !1; this._failed = !1; this.disconnected = !1; this._avatarId = null; this.prioritySync = !1; this.priority = EAvatarRelationRank.Stranger; this.micStatus = 0; this._avatarModel = null; this._motionType = MotionType.Walk; this.isSelf = !1; this._lastAnimTraceId = ""; this.statusSyncQueue = new Queue(this); this.extraInfo = {}; this.attachedEffects = new Set; this.currentPathName = ""; this.setPosition = s=>{ var c, _; !this._room.signal.isUpdatedYUV || (_ = (c = this.xAvatar) == null ? void 0 : c.movementComponent) == null || _.updatePosition(xverseAvatarTools.positionPrecisionProtect(s), !0) } this.setRotation = s=>{ var c; !this._room.signal.isUpdatedYUV || (c = this.xAvatar) == null || c.setRotation(xverseAvatarTools.rotationPrecisionProtect(s)) }; this.stopAnimation = ()=>{ var s, c; (c = (s = this.xAvatar) == null ? void 0 : s.controller) == null || c.stopAnimation() }; this.avatarComponentsSync = s=>{ s = s.map(c=>({ type: c.type, id: c.id })), this._room.actionsHandler.avatarComponentsSync(s) }; (this, "hide", ()=>{ var s; if ((s = this.xAvatar) != null && s.hide()) return Promise.resolve(`avatar: ${this.userId} hide success`); { const c = `avatar: ${this.userId} hide failed ${!this.xAvatar && "without instance: xAvatar"}`; return logger.warn(c), Promise.reject(c) } } ); this.show = async()=>{ var s; return (s = this.xAvatar) == null ? void 0 : s.show() }; this.sayTimer = null; this._userId = s, this._room = _, this.isSelf = k || !1, this._withModel = !!b, this._isHost = c || !1, this._avatarId = b, this.group = j, this._room.modelManager.getAvatarModel(b).then($=>{ $ && (this._avatarModel = $) } ), k && this._room.pathManager.currentAttitude && (this._motionType = this._room.pathManager.currentAttitude) } get avatarId() { return this._avatarId } get isRender() { var s; return !!((s = this.xAvatar) != null && s.isRender) } get isHidden() { var s; return !!((s = this.xAvatar) != null && s.isHide) } get motionType() { return this._motionType } set motionType(s) { this._motionType = s } get nickname() { var s; return (s = this.xAvatar) == null ? void 0 : s.nickName } get words() { var s; return (s = this.xAvatar) == null ? void 0 : s.words } get isHost() { return this._isHost } get failed() { return this._failed } get scale() { var s; return (s = this.xAvatar) == null ? void 0 : s.scale } get animations() { return this.animationList.map(c=>c.name) } get animationList() { const s = this._avatarModel.componentList.find(c=>c.type === ComponentItemType.ANIMATION); return s ? s == null ? void 0 : s.unitList : [] } get pandentList() { const s = this._avatarModel.componentList.find(c=>c.type === ComponentItemType.PENDANT); return s ? s == null ? void 0 : s.unitList : [] } _getAnimationIdByName(s) { var _; const c = (_ = this.animationList) == null ? void 0 : _.find(b=>b.name === s); return c ? c.id : "" } _getPandentIdByName(s) { var _; const c = (_ = this.pandentList) == null ? void 0 : _.find(b=>b.name === s); return c ? c.id : "" } get position() { var s; return (s = this.xAvatar) == null ? void 0 : s.position } get rotation() { var s; return (s = this.xAvatar) == null ? void 0 : s.rotation } get pose() { return { position: this.position, angle: this.rotation } } get id() { return this.userId } get isMoving() { return this._isMoving } set isMoving(s) { this._isMoving = s, this.state = s ? "moving" : "idle" } get isRotating() { return this._isRotating } set isRotating(s) { this._isRotating = s, this.state = s ? "rotating" : "idle" } get withModel() { return this._withModel } get avatarComponents() { var s; return JSON.parse(JSON.stringify(((s = this.xAvatar) == null ? void 0 : s.clothesList) || [])) } get userId() { return this._userId } get removeWhenDisconnected() { return this.extraInfo && this.extraInfo.removeWhenDisconnected !== void 0 ? this.extraInfo.removeWhenDisconnected : !0 } setConnectionStatus(s) { this.disconnected !== s && (this.disconnected = s, s ? this.emit("disconnected") : this.emit("reconnected"), logger.warn(`avatar ${this.userId} status changed, disconnected:`, s)) } setScale(s) { var c; this.scale !== s && ((c = this.xAvatar) == null || c.setScale(s > 0 ? s : 1)) } async playAnimation(s) { const {animationName: c, loop: _, extra: b, noSync: k=!1} = s || {}; if (this.isSelf) { if (this.isMoving) try { await this.stopMoving() } catch (j) { return logger.error(`stopMoving error before playAnimation ${c}`, j), Promise.reject(`stopMoving error before playAnimation ${c}`) } if (!k) { const j = { info: { userId: this.userId, animation: c, loop: _, extra: encodeURIComponent(b || "") }, broadcastType: CoreBroadcastType.PlayAnimation }; this._room.avatarManager.broadcast.broadcast({ data: j }) } } return this.isSelf && (this.emit("animationStart", { animationName: c, extra: safeDecodeURIComponent(b || "") }), tracker.trackEvent("playAnimation", { animation: c })), this._playAnimation(c, _).then(()=>{ this.isSelf && this.emit("animationEnd", { animationName: c, extra: safeDecodeURIComponent(b || "") }) } ) } async _playAnimation(s, c=!0, _=!1, b="Idle") { var $; const k = this._getAnimationIdByName(s); if (!this._room.signal.isUpdatedYUV) return; if (this.state !== "idle" && !_) return logger.warn(this.userId, "_playAnimation", s, "failed, state is not idle"), Promise.resolve(); const j = Date.now(); try { if (!(($ = this.xAvatar) != null && $.controller)) return Promise.reject(new InternalError(`[avatar: ${this.userId}] Play animation failed: ${s}, no controller`)); this.isSelf && setTimeout(()=>{ logger.infoAndReportMeasurement({ tag: s, startTime: j, value: 0, metric: "playAnimationStart" }) } ); const _e = util.uuid(); if (this._lastAnimTraceId = _e, await this.xAvatar.controller.playAnimation(k, c), _e === this._lastAnimTraceId && !this.isMoving && !c && s !== b) { const et = this._getAnimationIdByName(b); this.xAvatar.controller.playAnimation(et, c).catch(tt=>{ logger.error(`[avatar: ${this.userId}] Play animation failed [force idle]`, tt) } ) } return this.isSelf && logger.infoAndReportMeasurement({ tag: s, startTime: j, extra: { loop: c }, metric: "playAnimationEnd" }), Promise.resolve(_e) } catch (_e) { return logger.error(`[avatar: ${this.userId}] Play animation failed: ${s}`, _e), this.isSelf && logger.infoAndReportMeasurement({ tag: s, startTime: j, metric: "playAnimationEnd", error: _e, extra: { loop: c } }), Promise.reject(_e) } } async changeComponents(s) { var _e; const {mode: c, endAnimation: _=""} = s || {} , b = Date.now() , k = JSON.parse(JSON.stringify(s.avatarComponents)); let j = avatarComponentsValidate(k, this._avatarModel); const $ = Date.now(); return (_e = this._room) == null || _e.stats.functionTimeConsumingAdd("avatarComponentsValidate", $ - b), !ChangeComponentsMode[c] && !j && (j = new ParamError(`changeComponents failed, mode: ${c} is invalid`)), j ? (logger.error(j), Promise.reject(j)) : this._changeComponents({ avatarComponents: k, mode: c, endAnimation: _ }).then(()=>{ this.isSelf && c !== ChangeComponentsMode.Preview && this.avatarComponentsSync(this.avatarComponents) } ) } async _changeComponents(s) { var k; const {avatarComponents: c=[], mode: _} = s || {} , b = Date.now(); try { if (!this.xAvatar) return Promise.reject(new InternalError("changeComponents failed, without instance: xAvatar")); const j = await xverseAvatarTools.avatarComponentsModify(this._avatarModel, c, this._room) , $ = [] , _e = await xverseAvatarTools.avatarComponentsParser(this._avatarModel, j, this.avatarComponents, this._room); if (_e.length === 0) return this.avatarComponents; await this.beforeChangeComponentsHook(s); for (const et of _e) { const {id: tt, type: rt} = et; rt !== ComponentItemType.ANIMATION && $.push((k = this.xAvatar) == null ? void 0 : k.addComponent(tt, rt)) } return await Promise.all($), this.emit("componentsChanged", { components: this.avatarComponents, mode: _ }), this.isSelf && (logger.infoAndReportMeasurement({ tag: "changeComponents", startTime: b, metric: "changeComponents", extra: { inputComponents: c, finalComponents: this.avatarComponents, mode: ChangeComponentsMode[_] } }), tracker.trackEvent("changeComponents", { code: Codes.Success, components: _e.map(({id: et, type: tt})=>({ id: et, type: tt })), mode: ChangeComponentsMode[_] })), this.avatarComponents } catch (j) { return this.isSelf && (logger.infoAndReportMeasurement({ tag: "changeComponents", startTime: b, metric: "changeComponents", error: j, extra: { inputComponents: c, finalComponents: this.avatarComponents, mode: ChangeComponentsMode[_] } }), tracker.trackEvent("changeComponents", { code: -1, components: c, mode: ChangeComponentsMode[_] })), Promise.reject(j) } } async beforeChangeComponentsHook(s) {} turnTo(s) { if (this._room.viewMode === "observer") { this._room.sceneManager.cameraComponent.MainCamera.setTarget(ue4Position2Xverse(s.point)); return } return this._room.actionsHandler.turnTo(s).then(()=>{ this.emit("viewChanged", { extra: (s == null ? void 0 : s.extra) || "" }) } ) } async moveTo(s) { const {point: c, extra: _=""} = s || {}; if (!this.position) return Promise.reject(new ParamError("avatar position is empty")); if (typeof _ != "string" || typeof _ == "string" && _.length > 64) { const j = "extra shoud be string which length less than 64"; return logger.warn(j), Promise.reject(new ParamError(j)) } const k = getDistance(this.position, c) / 100 > 100 ? MotionType.Run : MotionType.Walk; return this._room.actionsHandler.moveTo({ point: c, motionType: k, extra: _ }) } async stopMoving() { if (!!this.isMoving) return this._room.actionsHandler.stopMoving() } rotateTo(s) { return this._room.actionsHandler.rotateTo(s) } setRayCast(s) { this.xAvatar && (this.xAvatar.isRayCastEnable = s) } say(s, c, _) { logger.debug("invoke avatar say", s, c, _); let b, k, j, $; if (typeof c == "object" ? (b = c.duration, k = c.background, j = c.fontSize, $ = c.fontColor) : typeof c == "number" && (b = c), this.sayTimer && window.clearTimeout(this.sayTimer), !this.xAvatar) { logger.error("say failed, without instance: xAvatar"); return } const _e = Object.assign({ scale: this.xAvatar.scale, isUser: this.group === AvatarGroup.User, background: k, fontsize: j, fontcolor: $ }, _ || {}); this.xAvatar.say(s, _e), !(b === void 0 || b <= 0) && (this.sayTimer = window.setTimeout(()=>{ this.silent() } , b)) } silent() { var s; if (!this.xAvatar) { logger.error("silent failed, without instance: xAvatar"); return } (s = this.xAvatar) == null || s.silent() } setNickname(s) { return this._room.actionsHandler.setNickName(encodeURIComponent(s)) } _setNickname(s, c={}) { var b, k; if (!s) return; const _ = safeDecodeURIComponent(s); ((b = this.xAvatar) == null ? void 0 : b.nickName) !== _ && (this.isSelf && (this._room.updateCurrentNetworkOptions({ nickname: _ }), this._room.options.nickname = _), (k = this.xAvatar) == null || k.setNickName(_, { scale: this.xAvatar.scale, ...c })) } _move(s) { var rt; const c = this.getMotionAnimtion("walk") , _ = this._getAnimationIdByName(c) , {start: b, end: k, walkSpeed: j, moveAnimation: $=_, inter: _e=[], enforceRaycast: et, backToIdle: tt} = s || {}; return (rt = this.xAvatar) == null ? void 0 : rt.move(b, k, j, $, _e, et, tt).then(()=>{ this.isMoving = !1 } ) } moveHermite(s) { var et; const {start: c, end: _, moveAnimation: b=this.getMotionAnimtion("walk"), duration: k, tension: j, enforceRaycast: $, backToIdle: _e} = s || {}; return (et = this.xAvatar) == null ? void 0 : et.moveHermite(c, _, k, j, b, $, _e) } move(s) { return this._move(s) } setPickBoxScale(s=1) { return this.xAvatar ? (this.xAvatar.setPickBoxScale(s), !0) : (logger.error("setPickBoxScale failed, without instance: xAvatar"), !1) } transfer(s) { const {player: c, camera: _, pathId: b} = s; return this._room.actionsHandler.transfer({ renderType: RenderType.RotationVideo, player: c, camera: _, pathId: b, tag: "transfer" }) } avatarLoadedHook() {} avatarStartMovingHook() {} avatarStopMovingHook() {} async statusSync(s) { var c, _, b, k, j; try { if ((c = s.event) != null && c.rotateEvent) { const {angle: $} = s.event.rotateEvent , _e = this.motionType === MotionType.Run ? "Running" : "Walking"; this.rotation && (this.rotation.yaw = this.rotation.yaw % 360, $.yaw - this.rotation.yaw > 180 && ($.yaw = 180 - $.yaw), this.isRotating = !0, await this.xAvatar.rotateTo($, this.rotation, _e).then(()=>{ this._playAnimation("Idle", !0), this.isRotating = !1 } )) } if (((k = (b = (_ = s.event) == null ? void 0 : _.points) == null ? void 0 : b.length) != null ? k : 0) > 1) { this.isMoving = !0, s.playerState.attitude && (this._motionType = s.playerState.attitude); const $ = this.getMotionAnimtion(this.motionType === MotionType.Run ? "run" : "walk") , _e = this._room.skin.pathList.find(tt=>tt.id === this.currentPathName) , et = ((_e == null ? void 0 : _e.step) || 7.5) * 25; this.position && await this._move({ start: this.position, end: s.event.points[s.event.points.length - 1], walkSpeed: et, moveAnimation: $, inter: (j = s.event) == null ? void 0 : j.points.slice(0, -1) }).then(()=>{ this.avatarStopMovingHook() } ) } } catch { return } } removeAttachedEffects() { this.attachedEffects.forEach((s,c)=>{ this._room.effectManager.removeEffect(c) } ), this.attachedEffects.clear() } removeAttachedEffect(s) { this.attachedEffects.delete(s), this._room.effectManager.removeEffect(s) } getMotionAnimtion(s) { return MotionAnimtion[s] || MotionAnimtion.idle } faceTo({point: s, rotateSpeed: c=.1}) { return this.xAvatar.faceTo(s, c) } }