2023-10-19 15:00:19 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using Cysharp.Threading.Tasks;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
using System.Collections.Generic;
|
2023-10-19 15:00:19 +08:00
|
|
|
|
using PhxhSDK;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
using UnityEngine;
|
2023-10-19 15:00:19 +08:00
|
|
|
|
using Constants = Framework.Constants;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
using Object = UnityEngine.Object;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
|
|
|
|
|
|
namespace Gameplay.Character
|
|
|
|
|
|
{
|
|
|
|
|
|
public class CharacterAnim
|
|
|
|
|
|
{
|
|
|
|
|
|
|
2023-10-19 15:00:19 +08:00
|
|
|
|
public readonly Character owner;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
|
|
|
|
|
|
public string targetUpAnim;
|
|
|
|
|
|
|
|
|
|
|
|
public string targetDownAnim;
|
|
|
|
|
|
|
|
|
|
|
|
public string targetFullAnim;
|
|
|
|
|
|
|
|
|
|
|
|
public bool noFade = false;
|
|
|
|
|
|
|
|
|
|
|
|
public string currentUpAnim { get; private set; }
|
|
|
|
|
|
public string currentDownAnim { get; private set; }
|
|
|
|
|
|
public string currentFullAnim { get; private set; }
|
|
|
|
|
|
|
2023-10-19 15:00:19 +08:00
|
|
|
|
public readonly Animator rawAnim;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
|
|
|
|
|
|
private bool _lastIsFullControl = false;
|
|
|
|
|
|
public bool isFullControl = false;
|
|
|
|
|
|
|
|
|
|
|
|
private Dictionary<int, LTAnimLayerInfo> _layerDict;
|
|
|
|
|
|
|
|
|
|
|
|
private float _splitScaleNormalShoot = 1;
|
|
|
|
|
|
public float splitScaleNormalShoot
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _splitScaleNormalShoot;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
2023-10-19 15:00:19 +08:00
|
|
|
|
if (Math.Abs(_splitScaleNormalShoot - value) < 0.01f) return;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
_splitScaleNormalShoot = value;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.AttackScaleNormal, _splitScaleNormalShoot);
|
2024-04-11 18:14:10 +08:00
|
|
|
|
if (owner.weapon != null && owner.weapon.weaponAnim.rawAnim != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
owner.weapon.weaponAnim.rawAnim.SetFloat(EAnimParamConst.AttackScaleNormal, _splitScaleNormalShoot);
|
|
|
|
|
|
}
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private float _splitScaleLowShoot = 1;
|
|
|
|
|
|
public float splitScaleLowShoot
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _splitScaleLowShoot;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
2023-10-19 15:00:19 +08:00
|
|
|
|
if (Math.Abs(_splitScaleLowShoot - value) < 0.01f) return;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
_splitScaleLowShoot = value;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.AttackScaleLow, _splitScaleLowShoot);
|
2024-04-11 18:14:10 +08:00
|
|
|
|
if (owner.weapon != null && owner.weapon.weaponAnim.rawAnim != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
owner.weapon.weaponAnim.rawAnim.SetFloat(EAnimParamConst.AttackScaleLow, _splitScaleLowShoot);
|
|
|
|
|
|
}
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private float _splitScaleNormalReload = 1;
|
|
|
|
|
|
public float splitScaleNormalReload
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _splitScaleNormalReload;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
2023-10-19 15:00:19 +08:00
|
|
|
|
if (Math.Abs(_splitScaleNormalReload - value) < 0.01f) return;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
_splitScaleNormalReload = value;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.ReloadScaleNormal, _splitScaleNormalReload);
|
2024-04-11 18:14:10 +08:00
|
|
|
|
if (owner.weapon != null && owner.weapon.weaponAnim.rawAnim != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
owner.weapon.weaponAnim.rawAnim.SetFloat(EAnimParamConst.ReloadScaleNormal, _splitScaleNormalReload);
|
|
|
|
|
|
}
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
private float _splitScaleLowReload = 1;
|
|
|
|
|
|
public float splitScaleLowReload
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _splitScaleLowReload;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
2023-10-19 15:00:19 +08:00
|
|
|
|
if (Math.Abs(_splitScaleLowReload - value) < 0.01f) return;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
_splitScaleLowReload = value;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.ReloadScaleLow, _splitScaleLowReload);
|
2024-04-11 18:14:10 +08:00
|
|
|
|
if (owner.weapon != null && owner.weapon.weaponAnim.rawAnim != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
owner.weapon.weaponAnim.rawAnim.SetFloat(EAnimParamConst.ReloadScaleLow, _splitScaleLowReload);
|
|
|
|
|
|
}
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private float _splitScaleNormalMove = 1;
|
|
|
|
|
|
public float splitScaleNormalMove
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _splitScaleNormalMove;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
2023-10-19 15:00:19 +08:00
|
|
|
|
if (Math.Abs(_splitScaleNormalMove - value) < 0.01f) return;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
_splitScaleNormalMove = value;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.MoveScaleNormal, _splitScaleNormalMove);
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
private float _splitScaleLowMove = 1;
|
|
|
|
|
|
public float splitScaleLowMove
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _splitScaleLowMove;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
2023-10-19 15:00:19 +08:00
|
|
|
|
if (Math.Abs(_splitScaleLowMove - value) < 0.01f) return;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
_splitScaleLowMove = value;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.MoveScaleLow, _splitScaleLowMove);
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public float NORMAL_MOVESPEED = 3f;
|
|
|
|
|
|
public float LOW_MOVESPEED = 0.6f;
|
|
|
|
|
|
|
|
|
|
|
|
public CharacterAnim(Character owner)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.owner = owner;
|
|
|
|
|
|
rawAnim = owner.RawAnimator;
|
2024-08-01 12:26:34 +08:00
|
|
|
|
// var animatorInst = Object.Instantiate(rawAnim.runtimeAnimatorController);
|
|
|
|
|
|
// var overrideController = animatorInst as AnimatorOverrideController;
|
|
|
|
|
|
// if (overrideController != null)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// // 把基础animator也clone一份
|
|
|
|
|
|
// var baseController = overrideController.runtimeAnimatorController;
|
|
|
|
|
|
// var cloneController = Object.Instantiate(baseController);
|
|
|
|
|
|
// overrideController.runtimeAnimatorController = cloneController;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// rawAnim.runtimeAnimatorController = animatorInst;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
|
|
|
|
|
|
_lastIsFullControl = false;
|
|
|
|
|
|
isFullControl = false;
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Full, 0);
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Up, 1);
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Down, 1);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-11 17:45:07 +08:00
|
|
|
|
public void ResetFloats()
|
|
|
|
|
|
{
|
|
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.AttackScaleNormal, _splitScaleNormalShoot);
|
|
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.AttackScaleLow, _splitScaleLowShoot);
|
|
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.ReloadScaleNormal, _splitScaleNormalReload);
|
|
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.ReloadScaleLow, _splitScaleLowReload);
|
|
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.MoveScaleNormal, _splitScaleNormalMove);
|
|
|
|
|
|
rawAnim.SetFloat(EAnimParamConst.MoveScaleLow, _splitScaleLowMove);
|
2024-04-11 18:14:10 +08:00
|
|
|
|
|
2024-05-17 12:24:55 +08:00
|
|
|
|
if (owner.weapon.weaponAnim != null && owner.weapon.weaponAnim.rawAnim != null)
|
2024-04-11 18:14:10 +08:00
|
|
|
|
{
|
|
|
|
|
|
owner.weapon.weaponAnim.rawAnim.SetFloat(EAnimParamConst.AttackScaleNormal, _splitScaleNormalShoot);
|
|
|
|
|
|
owner.weapon.weaponAnim.rawAnim.SetFloat(EAnimParamConst.AttackScaleLow, _splitScaleLowShoot);
|
|
|
|
|
|
owner.weapon.weaponAnim.rawAnim.SetFloat(EAnimParamConst.ReloadScaleNormal, _splitScaleNormalReload);
|
|
|
|
|
|
owner.weapon.weaponAnim.rawAnim.SetFloat(EAnimParamConst.ReloadScaleLow, _splitScaleLowReload);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-11 17:45:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-22 19:08:45 +08:00
|
|
|
|
public async UniTask Init()
|
|
|
|
|
|
{
|
|
|
|
|
|
await _InitAnimInfo();
|
|
|
|
|
|
ResetAnim();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async UniTask _InitAnimInfo()
|
|
|
|
|
|
{
|
|
|
|
|
|
_layerDict = new Dictionary<int, LTAnimLayerInfo>();
|
|
|
|
|
|
|
2023-12-20 13:47:28 +08:00
|
|
|
|
var fileName = owner.runtimeData.configData.nameId;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
var animInfoPath = string.Format(Constants.ANIMATOR_CONFIG_FORMAT_PATH, fileName);
|
2023-10-19 15:00:19 +08:00
|
|
|
|
var loadAsset = await AssetManager.Instance.LoadAssetAsync<TextAsset>(animInfoPath);
|
2023-08-22 19:08:45 +08:00
|
|
|
|
if (loadAsset == null)
|
|
|
|
|
|
{
|
2023-10-19 15:00:19 +08:00
|
|
|
|
DebugUtil.LogError("[AnimatorInfoExporter] Load animator info failed: {0}", animInfoPath);
|
2023-08-22 19:08:45 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var json = loadAsset.text;
|
|
|
|
|
|
var result = JsonUtility.FromJson<SaveAnimatorData>(json);
|
2024-04-30 16:00:13 +08:00
|
|
|
|
|
|
|
|
|
|
// 立即释放资源
|
|
|
|
|
|
AssetManager.Instance.Unload(animInfoPath);
|
|
|
|
|
|
|
2023-08-22 19:08:45 +08:00
|
|
|
|
var layers = result.layers;
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历每个 Layer,获取其中的所有动画状态信息
|
|
|
|
|
|
for (var i = 0; i < layers.Count; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
var layer = layers[i];
|
|
|
|
|
|
GetAllStates(layer, i);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 该方法需要在动画速度发生改变时进行调用
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void ResetAnim()
|
|
|
|
|
|
{
|
2023-10-19 15:00:19 +08:00
|
|
|
|
ResetReloadAnim();
|
|
|
|
|
|
ResetShootAnim();
|
|
|
|
|
|
}
|
2023-08-22 19:08:45 +08:00
|
|
|
|
|
2023-10-19 15:00:19 +08:00
|
|
|
|
public void ResetReloadAnim()
|
|
|
|
|
|
{
|
2023-08-22 19:08:45 +08:00
|
|
|
|
// reload时间
|
|
|
|
|
|
var reloadTime = owner.fightData.reloadTime;
|
|
|
|
|
|
var reloadAnimLength = GetStateLength(EAnimUpState.S_RELOAD, EAnimStateIndex.Up);
|
|
|
|
|
|
splitScaleNormalReload = reloadAnimLength / reloadTime;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
// rawAnim.SetFloat(EAnimParamConst.ReloadScaleNormal, splitScaleNormalReload);
|
|
|
|
|
|
|
2023-08-22 19:08:45 +08:00
|
|
|
|
var cReloadAnimLength = GetStateLength(EAnimUpState.S_C_RELOAD, EAnimStateIndex.Up);
|
|
|
|
|
|
splitScaleLowReload = cReloadAnimLength / reloadTime;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
// rawAnim.SetFloat(EAnimParamConst.ReloadScaleLow, splitScaleLowReload);
|
2023-10-19 15:00:19 +08:00
|
|
|
|
}
|
2023-08-22 19:08:45 +08:00
|
|
|
|
|
2023-10-19 15:00:19 +08:00
|
|
|
|
public void ResetShootAnim()
|
|
|
|
|
|
{
|
2023-08-22 19:08:45 +08:00
|
|
|
|
// 开枪时间
|
|
|
|
|
|
var shootTime = owner.fightData.shootCd;
|
|
|
|
|
|
var shootAnimLength = GetStateLength(EAnimUpState.S_ATTACK, EAnimStateIndex.Up);
|
|
|
|
|
|
splitScaleNormalShoot = shootAnimLength / shootTime;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
// rawAnim.SetFloat(EAnimParamConst.AttackScaleNormal, splitScaleNormalShoot);
|
|
|
|
|
|
|
2023-08-22 19:08:45 +08:00
|
|
|
|
var cShootAnimLength = GetStateLength(EAnimUpState.S_C_ATTACK, EAnimStateIndex.Up);
|
|
|
|
|
|
splitScaleLowShoot = cShootAnimLength / shootTime;
|
2024-04-11 17:45:07 +08:00
|
|
|
|
// rawAnim.SetFloat(EAnimParamConst.AttackScaleLow, splitScaleLowShoot);
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-11 17:45:07 +08:00
|
|
|
|
private void GetAllStates(SaveLayerData stateMachine,
|
|
|
|
|
|
int layerIndex)
|
2023-08-22 19:08:45 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 获取 stateMachine 中的所有动画状态信息
|
|
|
|
|
|
var states = stateMachine.states;
|
|
|
|
|
|
var newLayer = new LTAnimLayerInfo(layerIndex);
|
|
|
|
|
|
_layerDict.Add(layerIndex, newLayer);
|
|
|
|
|
|
// 遍历每个动画状态信息并处理
|
|
|
|
|
|
foreach (var state in states)
|
|
|
|
|
|
{
|
|
|
|
|
|
var newStateInfo = new LTAnimStateInfo();
|
|
|
|
|
|
newStateInfo.stateName = state.stateName;
|
|
|
|
|
|
newStateInfo.clipName = state.clipName;
|
|
|
|
|
|
newStateInfo.clipLength = state.clipLength;
|
|
|
|
|
|
|
|
|
|
|
|
newLayer.stateMap.Add(state.stateName, newStateInfo);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ViewUpdate(float dt)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isFullControl != _lastIsFullControl)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 控制状态发生改变
|
|
|
|
|
|
if (isFullControl)
|
|
|
|
|
|
{
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Full, 1);
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Up, 0);
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Down, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Full, 0);
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Up, 1);
|
|
|
|
|
|
rawAnim.SetLayerWeight(EAnimStateIndex.Down, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
_lastIsFullControl = isFullControl;
|
2023-12-18 16:19:47 +08:00
|
|
|
|
currentDownAnim = null;
|
|
|
|
|
|
currentUpAnim = null;
|
|
|
|
|
|
currentFullAnim = null;
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (currentFullAnim != targetFullAnim)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentFullAnim == null || noFade)
|
|
|
|
|
|
{
|
|
|
|
|
|
rawAnim.Play(targetFullAnim, EAnimStateIndex.Full, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
rawAnim.CrossFade(targetFullAnim, 0.1f, EAnimStateIndex.Full, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
currentFullAnim = targetFullAnim;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (currentUpAnim != targetUpAnim)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentUpAnim == null || noFade)
|
|
|
|
|
|
{
|
|
|
|
|
|
rawAnim.Play(targetUpAnim, EAnimStateIndex.Up, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
rawAnim.CrossFade(targetUpAnim, 0.1f, EAnimStateIndex.Up, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
currentUpAnim = targetUpAnim;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (currentDownAnim != targetDownAnim)
|
|
|
|
|
|
{
|
|
|
|
|
|
// TODO: 下半身融合看着比较奇怪,暂时不用
|
2023-11-13 18:13:52 +08:00
|
|
|
|
if (currentDownAnim == null || noFade)
|
2023-08-22 19:08:45 +08:00
|
|
|
|
{
|
|
|
|
|
|
rawAnim.Play(targetDownAnim, EAnimStateIndex.Down, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2023-11-13 18:13:52 +08:00
|
|
|
|
rawAnim.CrossFade(targetDownAnim, 0.05f, EAnimStateIndex.Down, 0);
|
2023-08-22 19:08:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
currentDownAnim = targetDownAnim;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-11 17:45:07 +08:00
|
|
|
|
public float GetStateLength(string stateName,
|
|
|
|
|
|
int layerIndex)
|
2023-08-22 19:08:45 +08:00
|
|
|
|
{
|
|
|
|
|
|
var layer = _layerDict[layerIndex];
|
|
|
|
|
|
var stateInfo = layer.stateMap[stateName];
|
|
|
|
|
|
return stateInfo.clipLength;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//public void SetStateSpeed(string stateName, int layerIndex, float speed)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// var layer = _layerDict[layerIndex];
|
|
|
|
|
|
// var stateInfo = layer.stateMap[stateName];
|
|
|
|
|
|
// stateInfo.rawState.speed = speed;
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|