| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.AI;
- using DG.Tweening;
- /* 动物组件-野狼 */
- public class Wolf : TargetAnimal
- {
- //动画播放器
- AnimationPlayer ap;
- //寻路代理
- NavMeshAgent agent;
- //血量
- [System.NonSerialized] public int hp = 1;
- void Awake()
- {
- wolfSet.Add(this);
- state = new State();
- ap = GetComponent<AnimationPlayer>();
- agent = GetComponent<NavMeshAgent>();
- this.onlineHandler.InitOnAwake(this);
- if (GameMgr.ins) {
- GameObject stoneObstacle = GameMgr.ins.transform.Find("StoneObstacle").gameObject;
- if (!stoneObstacle.activeSelf) stoneObstacle.SetActive(true);
- }
- }
- void Start()
- {
- if (onlineHandler.isMirror) return;
- initAniListener();
- }
- static int _avoidancePriority = 0;
- static int avoidancePriority {
- get {
- if (_avoidancePriority < 50) {
- _avoidancePriority++;
- } else {
- _avoidancePriority = 1;
- }
- return _avoidancePriority;
- }
- }
- void Update()
- {
- this.onlineHandler.Update();
- if (this.onlineHandler.isMirror) {
- UpdateOutline();
- return;
- }
- //寻路过程监测
- if (HasCloseToDestination()) {
- OnReachDestination();
- }
- UpdateAutoStrategy();
- UpdateAction();
- UpdateOutline();
- }
- void OnDestroy() {
- wolfSet.Remove(this);
- WolfActGrid.ins.areaMatrix.releaseOccupy(this);
- }
- //可选皮肤材质
- [SerializeField] Material[] materials;
- [SerializeField] Material[] outlineMaterials;
- [System.NonSerialized] public int colorType = -1;
- public void ChangeColorByType(int type) {
- colorType = type;
- if (skinnedMeshRenderer == null) skinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
- baseMaterial = materials[type - 1];
- outlineMaterial = outlineMaterials[type - 1];
- skinnedMeshRenderer.material = baseMaterial;
- }
- Material baseMaterial;
- Material outlineMaterial;
- SkinnedMeshRenderer skinnedMeshRenderer = null;
- bool hasOutLine = false;
- void UpdateOutline() {
- Camera camera = Camera.main;
- if (!camera) return;
- float distance = Vector3.Distance(this.transform.position, camera.transform.position);
- if (distance > 20) {
- if (!hasOutLine) {
- hasOutLine = true;
- skinnedMeshRenderer.material = outlineMaterial;
- }
- } else {
- if (hasOutLine) {
- hasOutLine = false;
- skinnedMeshRenderer.material = baseMaterial;
- }
- }
- }
- public override void OnHit(Arrow arrow, Vector3 hitPoint, string partName)
- {
- arrow.Head().position = hitPoint + arrow.transform.forward * 0.1f;
- arrow.Hit();
- if (onlineHandler.isMirror) {
- onlineHandler.onHitData = onlineHandler.uid.ToString() + "," + partName;
- return;
- }
- OnHitLogic(arrow, partName);
- }
- public void OnHitLogic(Arrow arrow, string partName) {
- int hurtValue = 0;
- if (partName == "Leg" || partName == "Tail") {
- hurtValue = 2;
- }
- else if (partName == "Body") {
- hurtValue = 3;
- }
- else if (partName == "Head") {
- hurtValue = 10;
- }
- hp -= hurtValue;
- GameEventCenter.ins.onTargetAnimalHurt?.Invoke(this, hurtValue);
- if (hp > 0) {
- Hurt();
- } else {
- Die(arrow);
- }
- }
- void Die(Arrow arrow) {
- if (state.dead) return;
- if (arrow != null) {
- arrow.onDoNextShoot += delegate() {
- Destroy(this.gameObject);
- };
- } else {
- //需要借住关卡的GameMode来清除
- onlineHandler.onDoNextShootWillDestroy = true;
- }
- state.ResetActionState();
- state.dead = true;
- this.agent.enabled = false;
- onDie?.Invoke(this);
- AudioMgr.ins.PlayAnimalEffect("wolf_die", AudioMgr.GetAudioSource(this.gameObject));
- this.onlineHandler.deadID++;
- }
- void Hurt() {
- if (!state.attacking) {
- if (Random.value < 0.2f) {
- CancelLockTarget();
- state.ResetActionState();
- state.lockingTarget = true;
- needAmbush = false;
- } else {
- //未锁定目标阶段时被击退,因为可能会再次满足z路径条件,因此需要重置Z路径记录
- if (!state.lockingTarget) ResetZPathRecord();
- CancelLockTarget(1);
- RunAwayFromHunter();
- }
- }
- AudioMgr.ins.PlayAnimalEffect("wolf_injured", AudioMgr.GetAudioSource(this.gameObject));
- this.onlineHandler.injuredID++;
- }
- void SetAgentStopped(bool value) { //要捕捉错误,不然会报错
- try { this.agent.isStopped = value; } catch (System.Exception) {}
- }
-
- //启动寻路
- void SetDestination(Vector3 pos) {
- state.ResetActionState();
- state.moving = true;
- SetAgentStopped(false);
- if (useRunbackPoint) {
- useRunbackPoint = false;
- WolfActGrid.ins.areaMatrix.releaseOccupy(this);
- } else {
- bool occupySuccess = WolfActGrid.ins.areaMatrix.occupyPos(pos, this);
- if (!occupySuccess) {
- pos = transform.position;
- }
- }
- this.agent.destination = pos;
- }
- //寻路结束
- void OnReachDestination() {
- if (!state.moving) return;
- SetAgentStopped(true);
- state.ResetActionState();
- }
- //是否已经接近目的地(寻路完成判断)
- bool HasCloseToDestination() {
- if (!state.moving) return true;
- if (Vector3.Distance(this.agent.nextPosition, this.agent.destination) < 0.25f) {
- return true;
- }
- if (state.movingTime > 0.1f && this.agent.velocity.magnitude < 0.05f) {
- return true;
- }
- return false;
- }
- int lastAutoType;
- float willStayTime;
- bool willAttack = false;
- bool hasRunAround = false;
- int needRunAroundCount = 0;
- bool hasRunToHunter = false;
- //取消锁定状态
- //cancelType==1时,表示触发逃跑
- void CancelLockTarget(int cancelType = 0) {
- if (!state.lockingTarget) return;
- state.lockingTarget = false;
- if (cancelType == 1) state.lockingTarget = true;
- RandomWillStayTime();
- willAttack = false;
- hasRunAround = false;
- if (cancelType == 1) hasRunAround = true;
- needRunAroundCount = 0;
- if (cancelType == 1) needRunAroundCount = 2;
- hasRunToHunter = false;
- if (cancelType == 1) hasRunToHunter = true;
- }
- //帧更新逻辑-自动策略
- void UpdateAutoStrategy() {
- if (state.dead) return;
- if (state.lockingTarget) {
- // if (state.attacking) {
- // this.transform.LookAt(this.hunterPosition);
- // }
- if (!hasRunToHunter) {
- hasRunToHunter = true;
- RunToAttackHunter(true);
- willAttack = true;
- return;
- }
- if (!state.moving && !state.attacking) {
- if (willAttack) {
- willAttack = false;
- Attack();
- needRunAroundCount = 3;
- return;
- }
- if (needRunAroundCount > 0) {
- needRunAroundCount--;
- useRunbackPoint = !hasRunAround;
- RunAroundHunter(!hasRunAround);
- hasRunAround = true;
- return;
- }
- if (hasRunAround) {
- hasRunAround = false;
- RunToAttackHunter(false);
- }
- willAttack = true;
- return;
- }
- return;
- }
- //以下为未锁定目标时的自动策略
- if (state.staying) {
- if (state.stayingTime > willStayTime) {
- MoveSlowlyInZPath();
- }
- return;
- }
- if (state.moving) {
- lastAutoType = 2;
- }
- if (!state.staying && !state.moving) {
- if (lastAutoType == 1 || lastAutoType == 0) {
- MoveSlowlyInZPath();
- } else {
- if (!canCreateZPath && zPathPoints.Count == 0) {
- MoveSlowlyInZPath();//内含判断,状态将会转化为锁定目标
- } else {
- RandomWillStayTime();
- Stay(true);
- }
- }
- }
- }
- void RandomWillStayTime() {
- this.willStayTime = Random.value * 4 + 2;
- // this.willStayTime = 0;
- }
- void LookAtHunter() {
- this.transform.LookAt(hunterPosition);
- }
- bool needAmbush = true;
- //攻击
- void Attack() {
- state.ResetActionState();
- state.attacking = true;
- curAnimIndex = -1; /*注意:这样可避免狼两次使用同一攻击动作而第二次无法播放的情况 */
- int hurtValue = 2;
- if (canAttackID == "A") {
- int avoidancePriority = this.agent.avoidancePriority;
- float baseoffset = this.agent.baseOffset;
- Vector3 hunterPos = hunterPosition;
- Vector3 jumpPoint = default; //起跳点
- Vector3 landPoint = default; //落地点
- Vector3 displace = default; //位移
- Sequence seq = DOTween.Sequence();
- //伏击动作 + 转头
- Quaternion ambushQuaStart = transform.rotation;
- Vector3 rotateDir = hunterPos - transform.position; rotateDir.y = 0;
- Quaternion ambushQuaEnd = Quaternion.FromToRotation(Vector3.forward, rotateDir);
- float needRotateAngle = Quaternion.Angle(ambushQuaStart, ambushQuaEnd);
- this.agent.avoidancePriority = 0;
- if (needRotateAngle > 20) {
- state.ambushRotating = true;
- seq.Append(DOTween.To(() => 0f, value => {
- transform.rotation = Quaternion.Lerp(ambushQuaStart, ambushQuaEnd, value);
- }, 1f, needRotateAngle / 180f * 0.5f));
- } else {
- ambushQuaEnd = ambushQuaStart;
- }
- seq.AppendCallback(delegate() {
- state.ambushRotating = false;
- if (needAmbush) state.ambushing = true;
- });
- if (needAmbush) {
- seq.Append(DOTween.To(() => 0f, value => {
- this.transform.rotation = ambushQuaEnd;
- }, 1f, 2f));
- }
- seq.AppendCallback(delegate() {
- if (state.dead) {
- seq.Kill();
- return;
- }
- needAmbush = true;
- state.ambushing = false;
- state.attackA = true;
- #region //起点和终点计算
- jumpPoint = this.transform.position;
- hunterPos.y = jumpPoint.y;
- Vector3 deltaPointer = jumpPoint - hunterPos;
- landPoint = hunterPos + deltaPointer.normalized * 1f;
- displace = landPoint - jumpPoint;
- #endregion
- });
- //跳跃
- seq.Append(DOTween.To(() => 0f, value => {
- this.transform.position = jumpPoint + displace * value;
- LookAtHunter();
- if (value < 0.5) {
- this.agent.baseOffset = baseoffset + value;
- } else {
- this.agent.baseOffset = baseoffset + (1 - value);
- if (!state.dead && state.attackA) {
- state.attackA = false;
- playAniJumpDown();
- }
- }
- }, 1f, 0.8f));
- seq.AppendCallback(delegate() {
- this.agent.avoidancePriority = avoidancePriority;
- this.transform.position = landPoint;
- LookAtHunter();
- if (!state.dead) onAttack?.Invoke(this, hurtValue);
- });
- seq.Append(DOTween.To(() => 0f, value => {
- this.transform.position = landPoint;
- LookAtHunter();
- }, 1f, 0.3f));
- seq.AppendCallback(delegate() {
- if (!state.dead) stopAniJumpDown(); //停止动画,则动画自带的位移也停止变化
- this.transform.position = landPoint;
- LookAtHunter();
- });
- seq.Append(DOTween.To(() => 0f, value => {
- this.transform.position = landPoint;
- LookAtHunter();
- }, 1f, 0.1f)); //通过dotween是它不被动画的位移影响;
- seq.AppendCallback(delegate() {
- state.ResetActionState();
- });
- } else if (canAttackID == "B") {
- state.attackB = true;
- hurtValue = 3;
- onAttack?.Invoke(this, hurtValue);
- }
- }
- //逃跑远离猎人
- void RunAwayFromHunter()
- {
- Vector3 backVec = GetPointerHunterToMe();
- SetDestination(transform.position + backVec.normalized * 6);
- state.moveQuickly = true;
- this.agent.speed = 5f;
- }
- //跑到能攻击玩家的坐标点
- string canAttackID = null;
- void RunToAttackHunter(bool quickly) {
- float needDistance;
- Vector3 displace = GetPointerHunterToMe().normalized;
- // if (Random.value < 0.33f) {
- // needDistance = 2;
- // canAttackID = "B";
- // } else {
- needDistance = 11.5f;
- canAttackID = "A";
- // }
- Vector3 newPos = animalsBaseT.position + displace * needDistance;
- SetDestination(newPos);
- state.moveSlowly = !quickly;
- state.moveQuickly = quickly;
- agent.speed = quickly ? 5f : 0.8f;
- }
- //在敌人身边奔跑徘徊
- bool useRunbackPoint = false;
- void RunAroundHunter(bool quickly) {
- Vector3 newPos;
- if (useRunbackPoint) {
- newPos = WolfActGrid.ins.GetRunBackPointAfterPounce();
- } else {
- float baseDistance = 13;
- float baseDistanceMoveRange = 5;
- Vector3 standardPointer = animalsBaseT.forward;
- Vector3 pointerWithLen = standardPointer.normalized * (baseDistance + Random.value * baseDistanceMoveRange);
- pointerWithLen = Quaternion.AngleAxis(Random.Range(-14f, 14f), Vector3.up) * pointerWithLen;
- newPos = animalsBaseT.position + pointerWithLen;
- }
- SetDestination(newPos);
- state.moveSlowly = !quickly;
- state.moveQuickly = quickly;
- agent.speed = quickly ? 5f : 0.8f;
- }
- //Z字型路径
- Queue<Vector3> zPathPoints = new Queue<Vector3>();
- bool canCreateZPath = true;
- void ResetZPathRecord() {
- zPathPoints.Clear();
- canCreateZPath = true;
- }
- void MoveSlowlyInZPath() {
- if (zPathPoints.Count > 0) {
- SetDestination(zPathPoints.Dequeue());
- state.moveSlowly = true;
- agent.speed = 0.8f;
- return;
- }
- if (!canCreateZPath) {
- state.lockingTarget = true;
- return;
- }
- //构建Z字型路径
- zPathPoints.Clear();
- Vector3 standardVec = animalsBaseT.forward;
- Vector3 hunterPos = animalsBaseT.position;
- Vector3 myPos = this.transform.position;
- float minDistance = 13;
- float maxDistance = 25;
- while (true) {
- myPos.y = hunterPos.y;
- float distance = Vector3.Distance(myPos, hunterPos);
- if (distance > minDistance) {
- Vector3 vecToMyPos = myPos - hunterPos;
- //判断位置在猎人左边还是右边
- float crossY = Vector3.Cross(standardVec, vecToMyPos).y;
- float nextAngle;
- if (crossY >= 0) { //在右边
- nextAngle = Random.Range(-14f, -10f);//下次就向左边走
- } else { //在左边
- nextAngle = Random.Range(10f, 14f);;//下次就向右边走
- }
- float nextDistance = distance - Random.Range(3.5f, 4.5f);
- if (nextDistance > maxDistance) {
- nextDistance = maxDistance;
- }
- if (nextDistance < minDistance) {
- nextDistance = minDistance;
- }
- if (Mathf.Abs(nextDistance - distance) < 1.2f) {
- break;
- }
- Vector3 displace = (Quaternion.AngleAxis(nextAngle, Vector3.up) * standardVec) * nextDistance;
- myPos = hunterPos + displace;
- zPathPoints.Enqueue(myPos);
- // Debug.Log(Quaternion.AngleAxis(-180f, Vector3.up) * (myPos - hunterPos));
- } else {
- break;
- }
- }
- canCreateZPath = false;
- }
- //停留
- void Stay(bool correct = false) {
- if (state.moving || correct) {
- this.agent.destination = this.agent.nextPosition;
- SetAgentStopped(true);
- }
- state.ResetActionState();
- state.staying = true;
- }
- //动画播放
- [System.NonSerialized] public int curAnimIndex = -1;
- void playAniStay() {
- ap.play(curAnimIndex = 7, WrapMode.Loop);
- }
- bool isAniStay() {
- return curAnimIndex == 7;
- }
- void playAniMoveSlowly() {
- ap.play(curAnimIndex = 5, WrapMode.Loop);
- }
- bool isAniMoveSlowly() {
- return curAnimIndex == 5;
- }
- void playAniMoveQuickly() {
- ap.play(curAnimIndex = 4, WrapMode.Loop);
- }
- bool isAniMoveQuickly() {
- return curAnimIndex == 4;
- }
- void playAniAmbush() {
- ap.play(curAnimIndex = 3, WrapMode.Loop);
- }
- bool isAniAmbush() {
- return curAnimIndex == 3;
- }
- void playAniAttakA() {
- ap.play(curAnimIndex = 1, WrapMode.Once);
- }
- bool isAniAttakA() {
- return curAnimIndex == 1;
- }
- void playAniJumpDown() {
- ap.play(curAnimIndex = 6, WrapMode.Once);
- }
- void stopAniJumpDown() {
- ap.StopAnimation(6);
- }
- void playAniAttakB() {
- ap.play(curAnimIndex = 0, WrapMode.Once);
- }
- bool isAniAttakB() {
- return curAnimIndex == 0;
- }
- void playAniDie() {
- ap.play(curAnimIndex = 2, WrapMode.Once);
- }
- bool isAniDie() {
- return curAnimIndex == 2;
- }
- void initAniListener() {
- this.ap.completeCallback = delegate(AnimationPlayerCompleteResult res) {
- if (res.index == 0) {
- this.state.ResetActionState();
- }
- };
- }
- //帧更新逻辑-通过状态更新动作动画
- void UpdateAction() {
- if (state.staying) {
- state.stayingTime += Time.deltaTime;
- if (!isAniStay()) playAniStay();
- }
- else if (state.moving) {
- state.movingTime += Time.deltaTime;
- if (state.moveSlowly && !isAniMoveSlowly()) playAniMoveSlowly();
- if (state.moveQuickly && !isAniMoveQuickly()) playAniMoveQuickly();
- }
- else if (state.attacking) {
- if (state.ambushRotating && !isAniMoveQuickly()) playAniMoveQuickly();
- if (state.ambushing && !isAniAmbush()) playAniAmbush();
- if (state.attackA && !isAniAttakA()) playAniAttakA();
- if (state.attackB && !isAniAttakB()) playAniAttakB();
- }
- else if (state.dead) {
- if (!isAniDie()) playAniDie();
- }
- }
- //状态
- [System.Serializable]
- public class State {
- //动作区
- public bool staying = false;
- public bool moving = false;
- public bool moveSlowly = false; //慢走
- public bool moveQuickly = false; //跑动
- public bool attacking = false;
- public bool ambushRotating = false; //扑击前的转身
- public bool ambushing = false; //扑击前的伏击状态
- public bool attackA = false; //扑击
- public bool attackB = false; //撕咬
- public bool dead = false;
- public float stayingTime = 0;
- public float movingTime = 0;
- //特定区
- public bool lockingTarget = false; //锁定目标,只有锁定目标后,才能调用攻击接口,这是给自己的规定
- //重置动作区状态
- public void ResetActionState() {
- this.staying = false;
- this.moving = false;
- this.moveSlowly = false;
- this.moveQuickly = false;
- this.attacking = false;
- this.ambushRotating = false;
- this.ambushing = false;
- this.attackA = false;
- this.attackB = false;
- this.dead = false;
- this.stayingTime = 0;
- this.movingTime = 0;
- }
- }
- [SerializeField] public State state;
- //委托
- public System.Action<Wolf> onDie;
- public System.Action<Wolf, int> onAttack;
- #region 联机附加部分
- public static HashSet<Wolf> wolfSet = new HashSet<Wolf>();
- public OnlineHandler onlineHandler = new OnlineHandler();
- public class OnlineHandler {
- Wolf animal;
- public int uid;
- public bool isMirror;
- public WolfSyncData outputSyncData;
- public void InitOnAwake(Wolf animal) {
- this.animal = animal;
- if (isMirror) {
- GameObject.Destroy(this.animal.agent);
- }
- }
- public void Update() {
- if (isMirror) {
- if (inputSyncData == null) return;
- if (!hasUpdateBySyncData) {
- this.animal.transform.position = syncPosition;
- this.animal.transform.rotation = syncRotation;
- hasUpdateBySyncData = true;
- } else {
- this.animal.transform.position = Vector3.Lerp(this.animal.transform.position, syncPosition, Time.deltaTime * 15);
- this.animal.transform.rotation = Quaternion.Lerp(this.animal.transform.rotation, syncRotation, Time.deltaTime * 15);
- }
- if (this.animal.colorType != inputSyncData.ct) {
- this.animal.ChangeColorByType(inputSyncData.ct);
- }
- if (inputSyncData.ai != this.animal.curAnimIndex) {
- this.animal.curAnimIndex = inputSyncData.ai;
- if (this.animal.isAniStay()) {
- this.animal.playAniStay();
- } else if (this.animal.isAniMoveSlowly()) {
- this.animal.playAniMoveSlowly();
- } else if (this.animal.isAniMoveQuickly()) {
- this.animal.playAniMoveQuickly();
- } else if (this.animal.isAniAmbush()) {
- this.animal.playAniAmbush();
- } else if (this.animal.curAnimIndex >= 0) {
- this.animal.ap.play(this.animal.curAnimIndex, WrapMode.Once);
- }
- }
- if (inputSyncData.ii != injuredID) {
- injuredID = inputSyncData.ii;
- AudioMgr.ins.PlayAnimalEffect("wolf_injured", AudioMgr.GetAudioSource(this.animal.gameObject));
- }
- if (inputSyncData.di != deadID) {
- deadID = inputSyncData.di;
- AudioMgr.ins.PlayAnimalEffect("wolf_die", AudioMgr.GetAudioSource(this.animal.gameObject));
- }
- return;
- }
- if (GlobalData.pkMatchType == PKMatchType.OnlinePK) {
- if (outputSyncData == null) outputSyncData = new WolfSyncData();
- outputSyncData.SetData(this.animal);
- }
- }
- private WolfSyncData _inputSyncData;
- public WolfSyncData inputSyncData {
- get {
- return _inputSyncData;
- }
- set {
- _inputSyncData = value;
- uid = value.id;
- syncRotation.x = value.rx;
- syncRotation.y = value.ry;
- syncRotation.z = value.rz;
- syncRotation.w = value.rw;
- syncPosition.x = value.px;
- syncPosition.y = value.py;
- syncPosition.z = value.pz;
- }
- }
- private Quaternion syncRotation;
- private Vector3 syncPosition;
- private bool hasUpdateBySyncData = false;
- public bool isInvalid = false; //外部运算用的
- public string onHitData = null;
- public bool onDoNextShootWillDestroy = false;
- public int injuredID = 0;
- public int deadID = 0;
- }
- #endregion
- public override int GetOnlineID() {
- return onlineHandler.uid;
- }
- }
|