2024-10-16 00:03:41 +08:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace XFFSM
|
|
|
|
|
{
|
|
|
|
|
public class FSMController : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
#region 字段
|
|
|
|
|
|
|
|
|
|
[SerializeField]
|
|
|
|
|
private List<RuntimeFSMController> _runtimeFSMController = new List<RuntimeFSMController>();
|
|
|
|
|
|
|
|
|
|
internal object userData;
|
|
|
|
|
|
|
|
|
|
private List<RuntimeFSMControllerInstance> Instances = new List<RuntimeFSMControllerInstance>();
|
|
|
|
|
|
|
|
|
|
private List<RuntimeFSMController> runtimeControllers = new List<RuntimeFSMController>();
|
|
|
|
|
|
|
|
|
|
[SerializeField]
|
|
|
|
|
[Tooltip("是否在OnDisable时重置状态机")]
|
|
|
|
|
[Header("是否在OnDisable时重置状态机")]
|
|
|
|
|
private bool resetOnDisable = true;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 事件
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 初始化完成的回调
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Action onInitFinish;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 状态改变的回调 参数1:状态机名称 参数2:当前状态
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Action<string,string> onStateChange;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 属性
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 当前状态机执行的状态配置文件列表
|
|
|
|
|
/// </summary>
|
|
|
|
|
public List<RuntimeFSMController> RuntimeFSMController
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (Application.isPlaying)
|
|
|
|
|
{
|
|
|
|
|
return runtimeControllers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _runtimeFSMController;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 状态机是否初始化完成
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool Initialized { get; private set; } = false;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 生命周期
|
|
|
|
|
|
2024-10-22 19:10:15 +08:00
|
|
|
public void DoUpdate()
|
2024-10-16 00:03:41 +08:00
|
|
|
{
|
|
|
|
|
if (!Initialized)
|
|
|
|
|
return;
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
// 如果没有启动 就启动一下
|
|
|
|
|
if(!item.Started)
|
|
|
|
|
item.StartUp();
|
|
|
|
|
|
|
|
|
|
item.Update();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-22 19:10:15 +08:00
|
|
|
// protected void LateUpdate()
|
|
|
|
|
// {
|
|
|
|
|
// if (!Initialized)
|
|
|
|
|
// return;
|
|
|
|
|
// foreach (var item in Instances)
|
|
|
|
|
// {
|
|
|
|
|
// item.LateUpdate();
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// protected void FixedUpdate()
|
|
|
|
|
// {
|
|
|
|
|
// if (!Initialized)
|
|
|
|
|
// return;
|
|
|
|
|
// foreach (var item in Instances)
|
|
|
|
|
// {
|
|
|
|
|
// item.FixedUpdate();
|
|
|
|
|
// }
|
|
|
|
|
// }
|
2024-10-16 00:03:41 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDestroy()
|
|
|
|
|
{
|
|
|
|
|
if (!Initialized)
|
|
|
|
|
return;
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnDisable()
|
|
|
|
|
{
|
|
|
|
|
if (!Initialized)
|
|
|
|
|
return;
|
|
|
|
|
if (resetOnDisable)
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 方法
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
public void Init(object userData)
|
|
|
|
|
{
|
|
|
|
|
if (Initialized)
|
|
|
|
|
return;
|
|
|
|
|
Instances.Clear();
|
|
|
|
|
runtimeControllers.Clear();
|
|
|
|
|
this.userData = userData;
|
|
|
|
|
|
|
|
|
|
foreach (var item in _runtimeFSMController)
|
|
|
|
|
{
|
|
|
|
|
if (item == null) continue;
|
|
|
|
|
|
|
|
|
|
RuntimeFSMControllerInstance instance = new RuntimeFSMControllerInstance(item, this);
|
|
|
|
|
instance.StartUp();
|
|
|
|
|
runtimeControllers.Add(instance.RuntimeFSMController);
|
|
|
|
|
Instances.Add(instance);
|
|
|
|
|
}
|
|
|
|
|
Initialized = true;
|
|
|
|
|
onInitFinish?.Invoke();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 设置bool类型参数的值
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">参数名称</param>
|
|
|
|
|
/// <param name="v">值</param>
|
|
|
|
|
public void SetBool(string name, bool v)
|
|
|
|
|
{
|
|
|
|
|
if (Initialized)
|
|
|
|
|
{
|
|
|
|
|
if (!IsContainParameter(name, ParameterType.Bool))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("未查询到参数:{0} 类型:{1}",name, ParameterType.Bool);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.SetBool(name, v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
onInitFinish += () =>
|
|
|
|
|
{
|
|
|
|
|
if (!IsContainParameter(name, ParameterType.Bool))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("未查询到参数:{0} 类型:{1}", name, ParameterType.Bool);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.SetBool(name, v);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 设置float类型参数的值
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">参数名称</param>
|
|
|
|
|
/// <param name="v">值</param>
|
|
|
|
|
public void SetFloat(string name, float v)
|
|
|
|
|
{
|
|
|
|
|
if (Initialized)
|
|
|
|
|
{
|
|
|
|
|
if (!IsContainParameter(name, ParameterType.Float))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("未查询到参数:{0} 类型:{1}", name, ParameterType.Float);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.SetFloat(name, v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
onInitFinish += () =>
|
|
|
|
|
{
|
|
|
|
|
if (!IsContainParameter(name, ParameterType.Float))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("未查询到参数:{0} 类型:{1}", name, ParameterType.Float);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.SetFloat(name, v);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 设置Int类型参数的值
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">参数名称</param>
|
|
|
|
|
/// <param name="v">值</param>
|
|
|
|
|
public void SetInt(string name, int v)
|
|
|
|
|
{
|
|
|
|
|
if (Initialized)
|
|
|
|
|
{
|
|
|
|
|
if (!IsContainParameter(name, ParameterType.Int))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("未查询到参数:{0} 类型:{1}", name, ParameterType.Int);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.SetInt(name, v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
onInitFinish += () =>
|
|
|
|
|
{
|
|
|
|
|
if (!IsContainParameter(name, ParameterType.Int))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("未查询到参数:{0} 类型:{1}", name, ParameterType.Int);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.SetInt(name, v);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 触发Trigger
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">Trigger名称</param>
|
|
|
|
|
public void SetTrigger(string name)
|
|
|
|
|
{
|
|
|
|
|
if (Initialized)
|
|
|
|
|
{
|
|
|
|
|
if (!IsContainParameter(name, ParameterType.Trigger))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("未查询到参数:{0} 类型:{1}", name, ParameterType.Trigger);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
item.SetTrigger(name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
onInitFinish += () =>
|
|
|
|
|
{
|
|
|
|
|
if (!IsContainParameter(name, ParameterType.Trigger))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("未查询到参数:{0} 类型:{1}", name, ParameterType.Trigger);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.SetTrigger(name);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 还原Trigger
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">Trigger名称</param>
|
|
|
|
|
public void ResetTrigger(string name) {
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
item.ResetTrigger(name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取Bool类型参数的值
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">参数名称</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public bool GetBool(string name)
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
if (item.GetParamter(name) == 1)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取Float类型参数的值
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">参数名称</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public float GetFloat(string name )
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
float v = item.GetParamter(name);
|
|
|
|
|
if (v != 0)
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取Int类型参数的值
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">参数名称</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public int GetInt(string name)
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
int v = (int)(item.GetParamter(name));
|
|
|
|
|
if (v != 0)
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取Trigger类型参数的值
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="name">参数名称</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public bool GetTrigger(string name )
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
if (item.GetTriggerCount(name) > 0 || item.GetParamter(name) == 1)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsContainParameter(string name,ParameterType type)
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
if(item.parameters.ContainsKey(name) && item.parameters[name].parameterType == type)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 运行时添加并执行状态配置文件
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="controller"></param>
|
|
|
|
|
public void AddRuntimeFSMController(RuntimeFSMController controller) {
|
|
|
|
|
if (_runtimeFSMController.Contains(controller))
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarningFormat("状态机:{0}已经执行,请勿重复添加!", controller.name);
|
|
|
|
|
return; // 说明已经添加了该状态机
|
|
|
|
|
}
|
|
|
|
|
_runtimeFSMController.Add(controller);
|
|
|
|
|
|
|
|
|
|
// 如果没有初始化完成 不需要处理 在初始化的时候会处理
|
|
|
|
|
if (Initialized)
|
|
|
|
|
{
|
|
|
|
|
// 如果初始化完成了 启动这个状态
|
|
|
|
|
RuntimeFSMControllerInstance controllerInstance = new RuntimeFSMControllerInstance(controller, this);
|
|
|
|
|
controllerInstance.StartUp();
|
|
|
|
|
runtimeControllers.Add(controllerInstance.RuntimeFSMController);
|
|
|
|
|
Instances.Add(controllerInstance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 运行时移除状态机配置文件
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="index">配置文件下标</param>
|
|
|
|
|
public void RemoveRuntimeFSMController(int index) {
|
|
|
|
|
if (index < 0 || index >= RuntimeFSMController.Count) return;
|
|
|
|
|
RuntimeFSMController controller = RuntimeFSMController[index];
|
|
|
|
|
RuntimeFSMController.RemoveAt(index);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < Instances.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
if (Instances[i].RuntimeFSMController == controller)
|
|
|
|
|
{
|
|
|
|
|
Instances[i].Close();
|
|
|
|
|
Instances.Remove(Instances[i]);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取当前正在执行的状态信息
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="index">状态配置下标</param>
|
|
|
|
|
/// <param name="subStateName">子状态机名称(默认:空)</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public FSMStateNode GetCurrentStateInfo(int index,string subStateName = "")
|
|
|
|
|
{
|
|
|
|
|
if (index < 0 || index >= Instances.Count) return null;
|
|
|
|
|
return Instances[index].GetCurrentState(subStateName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public FSMStateNode GetCurrentStateInfo(string controllerName, string subStateName = "") {
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(controllerName))
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
foreach (var item in Instances)
|
|
|
|
|
{
|
|
|
|
|
if (item.name.Equals(controllerName))
|
|
|
|
|
return item.GetCurrentState(subStateName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取当前的状态切换信息
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="controller"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public FSMTransition GetCurrentTransition(int index)
|
|
|
|
|
{
|
|
|
|
|
if (index < 0 || index >= Instances.Count) return null;
|
|
|
|
|
return Instances[index].currentTransition;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 静态字段
|
|
|
|
|
|
|
|
|
|
private static Dictionary<string,FSMController> fsms = new Dictionary<string,FSMController>();
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 静态属性
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static Transform FSMParent { get; set; }
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 静态方法
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[RuntimeInitializeOnLoadMethod]
|
|
|
|
|
static void InitFSMController()
|
|
|
|
|
{
|
|
|
|
|
fsms.Clear();
|
|
|
|
|
|
|
|
|
|
GameObject obj = new GameObject("FSMControllers");
|
|
|
|
|
DontDestroyOnLoad(obj);
|
|
|
|
|
FSMParent = obj.transform;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 启动状态(适用于没有具体游戏物体的状态管理,例如:游戏状态)
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fsmName">状态机名称</param>
|
|
|
|
|
/// <param name="fsm">状态配置文件</param>
|
|
|
|
|
/// <param name="userData">自定义参数或数据</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static FSMController StartupFSM(string fsmName,RuntimeFSMController fsm,object userData = null)
|
|
|
|
|
{
|
|
|
|
|
if (fsms.ContainsKey(fsmName)) return null;
|
|
|
|
|
GameObject obj = new GameObject(fsmName);
|
|
|
|
|
obj.transform.SetParent(FSMParent,false);
|
|
|
|
|
FSMController controller = obj.AddComponent<FSMController>();
|
|
|
|
|
controller.userData = userData;
|
|
|
|
|
controller.AddRuntimeFSMController(fsm);
|
|
|
|
|
fsms.Add(fsmName, controller);
|
|
|
|
|
return controller;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 查询通过FSMController.StartupFSM启动的状态机
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fsmName">状态机名称</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static FSMController GetFSM(string fsmName)
|
|
|
|
|
{
|
|
|
|
|
if(fsms.ContainsKey(fsmName))
|
|
|
|
|
return fsms[fsmName];
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 移除通过FSMController.StartupFSM启动的状态机
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fsmName">状态机名称</param>
|
|
|
|
|
public static void RemoveFSM(string fsmName)
|
|
|
|
|
{
|
|
|
|
|
if (!fsms.ContainsKey(fsmName)) return;
|
|
|
|
|
FSMController controller = fsms[fsmName];
|
|
|
|
|
fsms.Remove(fsmName);
|
|
|
|
|
Destroy(controller.gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|