SkillControl.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System;
  5. namespace YLBattle
  6. {
  7. /// <summary>
  8. /// 技能控制器
  9. /// </summary>
  10. //[RequireComponent(typeof(SkillReady))]
  11. //[RequireComponent(typeof(SkillShoot))]
  12. //[RequireComponent(typeof(SkillExplode))]
  13. public partial class SkillControl : MonoBehaviour
  14. {
  15. /// <summary>
  16. /// 技能准备.
  17. /// </summary>
  18. private SkillReady skillReady;
  19. /// <summary>
  20. /// 技能发射.
  21. /// </summary>
  22. private SkillShoot skillShoot;
  23. /// <summary>
  24. /// 技能爆炸.
  25. /// </summary>
  26. private SkillExplode skillExplode;
  27. /// <summary>
  28. /// 地图中心位置(特效 type=2 敌方)
  29. /// </summary>
  30. private Vector3 mMapCenter = Vector3.zero;
  31. /// <summary>
  32. /// 地图中心位置(特效 type=2 己方)
  33. /// </summary>
  34. private Vector3 mUICenter = Vector3.zero;
  35. /// <summary>
  36. /// 实例
  37. /// </summary>
  38. public static SkillControl mInstance;
  39. /// <summary>
  40. /// 数据交互接口
  41. /// </summary>
  42. private IBattleGlobalOper mBattleFeildAdapter = null;
  43. /// <summary>
  44. /// 子弹字典
  45. /// </summary>
  46. private Dictionary<string, ProjectileBase> ProjectileBases = new Dictionary<string, ProjectileBase>();
  47. /// <summary>
  48. /// 爆炸字典
  49. /// </summary>
  50. private Dictionary<string, ExplodeBase> ExplodeBases = new Dictionary<string, ExplodeBase>();
  51. /// <summary>
  52. /// 准备阶段 技能字典
  53. /// </summary>
  54. public Dictionary<string, GameObject> mCommonBaseObjs = new Dictionary<string, GameObject>();
  55. /// <summary>
  56. /// 画布
  57. /// </summary>
  58. private Canvas m_Canvas = null;
  59. /// <summary>
  60. ///
  61. /// </summary>
  62. public Canvas SkillCanvas
  63. {
  64. get { return m_Canvas; }
  65. }
  66. /// <summary>
  67. ///坐标
  68. /// </summary>
  69. private RectTransform rectTransform;
  70. /// <summary>
  71. /// 获取单键
  72. /// </summary>
  73. /// <returns>单键实例</returns>
  74. public static SkillControl Instance
  75. {
  76. get
  77. {
  78. if (mInstance == null)
  79. {
  80. GameObject obj = GameObject.Find("SkillControl");
  81. if (obj != null)
  82. {
  83. mInstance = obj.AddComponent<SkillControl>();
  84. }
  85. }
  86. return mInstance;
  87. }
  88. }
  89. /// <summary>
  90. /// Start
  91. /// </summary>
  92. private void Start()
  93. {
  94. this.gameObject.AddComponent<SkillReady>();
  95. this.gameObject.AddComponent<SkillShoot>();
  96. this.gameObject.AddComponent<SkillExplode>();
  97. skillReady = GetComponent<SkillReady>();
  98. skillShoot = GetComponent<SkillShoot>();
  99. skillExplode = GetComponent<SkillExplode>();
  100. m_Canvas = GetComponent<Canvas>();
  101. rectTransform = m_Canvas.transform as RectTransform;
  102. this.mMapCenter = transform.Find("MapEffectPoint").transform.position;
  103. this.mUICenter = transform.Find("UIEffectPoint").transform.position;
  104. }
  105. /// <summary>
  106. /// 设置数据交互接口.
  107. /// </summary>
  108. /// <param name="op">Op.</param>
  109. public void SetOp(IBattleGlobalOper op)
  110. {
  111. mBattleFeildAdapter = op;
  112. }
  113. /// <summary>
  114. ///
  115. /// </summary>
  116. /// <param name="resName"></param>
  117. /// <param name="pos"></param>
  118. public void createPosEffect(string resName, Vector3 pos, Action<GameObject> ac)
  119. {
  120. ResourceHelper.Instance.LoadAssetBundle(resName, ab =>
  121. {
  122. if (ab != null)
  123. {
  124. GameObject comUIEff = (GameObject)Instantiate(ab.LoadAsset(resName));
  125. comUIEff.SetActive(false);
  126. this.TranformHandle(comUIEff.transform, pos);
  127. if (ac != null)
  128. {
  129. ac.Invoke(comUIEff);
  130. }
  131. }
  132. });
  133. }
  134. /// <summary>
  135. /// 在目标位置(UI层)上创建特效
  136. /// </summary>
  137. /// <param name="Id">目标Id</param>
  138. /// <param name="resName">资源名称</param>
  139. /// <param name="posType">位置类型 0=transform脚底 1=shoot/hit 2=指定位置</param>
  140. public void createPosEffect(string Id, string resName, Vector3 pos, int posType = 0, float time = 1.0f)
  141. {
  142. Fighter target = FightingManager.Instance.GetFighter(Id);
  143. #region 加载准备阶段光效
  144. ResourceHelper.Instance.LoadAssetBundle(resName, ab =>
  145. {
  146. if (ab != null)
  147. {
  148. GameObject comUIEff = (GameObject)Instantiate(ab.LoadAsset(resName));
  149. Vector3 targetPos = Vector3.zero;
  150. if (posType == 2)
  151. {
  152. targetPos = WorldToGUI(pos);
  153. }
  154. else if (target != null)
  155. {
  156. if (posType == 1)
  157. {
  158. if (target.mTeam == EBattleTeam.REDTEAM)
  159. {
  160. targetPos = WorldToGUI(target.mBodyCenterPos);
  161. }
  162. else
  163. {
  164. targetPos = target.mBodyCenterPos;
  165. }
  166. }
  167. else
  168. {
  169. if (target.mTeam == EBattleTeam.REDTEAM)
  170. {
  171. targetPos = WorldToGUI(target.mBodyRootPos);
  172. }
  173. else
  174. {
  175. targetPos = target.mBodyRootPos;
  176. }
  177. }
  178. }
  179. this.TranformHandle(comUIEff.transform, targetPos);
  180. ExplodeBase exloreScript = comUIEff.GetComponent<ExplodeBase>();
  181. if (exloreScript != null)
  182. {
  183. exloreScript.enabled = false;
  184. }
  185. ///(用于非战斗的定点位置爆炸跟踪使用)
  186. ExplodeTraceBase explode = comUIEff.GetComponent<ExplodeTraceBase>();
  187. if (explode == null)
  188. {
  189. explode = comUIEff.AddComponent<ExplodeTraceBase>();
  190. }
  191. explode.lifeTime = time;
  192. explode.TargetId = Id;
  193. explode.posType = posType;
  194. }
  195. else
  196. {
  197. LogHelper.LogWarning("特效模版数据有误!!! " + resName);
  198. }
  199. });
  200. #endregion
  201. }
  202. /// <summary>
  203. /// 创建UI屏幕特效(例如,boss出现[全屏])
  204. /// </summary>
  205. /// <param name="Id"></param>
  206. /// <param name="resName"></param>
  207. public void createUIScreenEffect(string Id, string resName)
  208. {
  209. ResourceHelper.Instance.LoadAssetBundle(resName, ab =>
  210. {
  211. if (ab != null)
  212. {
  213. GameObject uiEff = (GameObject)Instantiate(ab.LoadAsset(Id));
  214. uiEff.transform.SetParent(transform);
  215. uiEff.transform.position = Vector3.zero;
  216. uiEff.transform.rotation = Quaternion.identity;
  217. uiEff.transform.localScale = new Vector3(1, 1, 1);
  218. this.mCommonBaseObjs.Add(Id, uiEff);
  219. }
  220. }
  221. );
  222. }
  223. /// <summary>
  224. /// Tranform的处理,3D->UI
  225. /// </summary>
  226. /// <param name="model"></param>
  227. /// <param name="trgPos"></param>
  228. public void TranformHandle(Transform model, Vector3 trgPos)
  229. {
  230. Transform[] all = model.GetComponentsInChildren<Transform>();
  231. ///默认UI相机照射所有特效
  232. ///如果是bomb在敌人身上(3D)身上,则使用3D摄像机;
  233. foreach (Transform rc in all)
  234. {
  235. rc.gameObject.layer = LayerMask.NameToLayer("UI"); //指定Layer
  236. }
  237. model.transform.SetParent(transform);
  238. model.transform.position = trgPos;
  239. model.transform.rotation = Quaternion.identity;
  240. model.transform.localScale = new Vector3(1, 1, 1);
  241. }
  242. /// <summary>
  243. /// 创建子弹.
  244. /// </summary>
  245. /// <param name="_bulletLoadParam">子弹参数</param>
  246. public void createProjectile(BulletLoadParam _bulletLoadParam)
  247. {
  248. ///发起者
  249. Fighter tempCaster = FightingManager.Instance.GetFighter(_bulletLoadParam.uid);
  250. if (tempCaster == null)
  251. {
  252. LogHelper.Log("发起者 id=" + _bulletLoadParam.uid + " 不存在(死亡),反射失败");
  253. return;
  254. }
  255. Vector3 point = tempCaster.mBodyCenterPos;
  256. #region 弹道设定
  257. ///无弹道->模拟
  258. if (_bulletLoadParam.traject == EBulletTrajectoryType.EBULLET_TRAJECT_NONE)
  259. {
  260. CreateNoneTrajectBullet(_bulletLoadParam, point);
  261. return;
  262. }
  263. CreatePrebBullet(_bulletLoadParam, point, tempCaster.mTeam == EBattleTeam.BLUETEAM);
  264. #endregion
  265. }
  266. /// <summary>
  267. /// 查找Transform
  268. /// </summary>
  269. /// <param name="tran">父级</param>
  270. /// <param name="name">名称</param>
  271. /// <returns>Transform</returns>
  272. private Transform FindTransform(Transform tran, string name)
  273. {
  274. Transform[] trans = tran.GetComponentsInChildren<Transform>();
  275. for (int i = 0; i < trans.Length; i++)
  276. {
  277. if (trans[i].name.Trim().IndexOf(name.Trim(), 0, trans[i].name.Trim().Length) != -1)
  278. {
  279. return trans[i];
  280. }
  281. }
  282. return null;
  283. }
  284. /// <summary>
  285. /// 创建外部预置物体类型子弹
  286. /// </summary>
  287. /// <param name="_bulletloadParam"></param>
  288. /// <param name="muzzplePos">发射点</param>
  289. void CreatePrebBullet(BulletLoadParam _bulletLoadParam, Vector3 muzzplePos, bool isScreen)
  290. {
  291. ///弹道效果
  292. GameObject projectileEff = null;
  293. ResourceHelper.Instance.LoadAssetBundle(_bulletLoadParam.res + "_sending", ab =>
  294. {
  295. if (ab != null)
  296. {
  297. projectileEff = (GameObject)Instantiate(ab.LoadAsset(_bulletLoadParam.res));
  298. }
  299. else
  300. {
  301. projectileEff = new GameObject();
  302. projectileEff.name = _bulletLoadParam.tid + "_sending";
  303. }
  304. this.TranformHandle(projectileEff.transform, muzzplePos);
  305. AddProjectileComponent(_bulletLoadParam, projectileEff);
  306. });
  307. }
  308. /// <summary>
  309. /// 子弹增加轨道组件
  310. /// </summary>
  311. /// <param name="_bulletLoadParam"></param>
  312. /// <param name="projectileEff"></param>
  313. public void AddProjectileComponent(BulletLoadParam _bulletLoadParam, GameObject projectileEff)
  314. {
  315. ProjectileBase Projectile = null;
  316. switch (_bulletLoadParam.traject)
  317. {
  318. /// 无弹道,直接出现在目标位置处
  319. case EBulletTrajectoryType.EBULLET_TRAJECT_NONE:
  320. ///不会走到这里滴~~
  321. Debug.LogError("擦,真没数据啊。。。。。");
  322. break;
  323. /// 抛物线子弹
  324. case EBulletTrajectoryType.EBULLET_TRAJECT_PARABOLA:
  325. Projectile = projectileEff.AddComponent<ProjectileArcGUI>() as ProjectileArcGUI;
  326. break;
  327. case EBulletTrajectoryType.EBULLET_TRAJECT_LINE:
  328. Projectile = projectileEff.AddComponent<ProjectileArcLine>() as ProjectileArcLine;
  329. break;
  330. /// 屏幕
  331. }
  332. ///创建子弹完毕,隐藏,待shoot调用
  333. if (Projectile != null)
  334. {
  335. projectileEff.SetActive(false);///关闭对象显示
  336. Projectile.bulletLoadParam = _bulletLoadParam;
  337. if (ProjectileBases.ContainsKey(_bulletLoadParam.id) == false)
  338. {
  339. ProjectileBases.Add(_bulletLoadParam.id, Projectile);
  340. }
  341. else
  342. {
  343. ProjectileBases[_bulletLoadParam.id] = Projectile;
  344. }
  345. }
  346. }
  347. /// <summary>
  348. /// 创建无弹道轨迹的子弹
  349. /// </summary>
  350. /// <param name="_bulletloadParam"></param>
  351. /// <param name="muzzplePos">发射点</param>
  352. void CreateNoneTrajectBullet(BulletLoadParam _bulletloadParam, Vector3 muzzplePos)
  353. {
  354. GameObject projNoneObj = new GameObject(_bulletloadParam.res + "+_ProjectileNONE");
  355. projNoneObj.transform.SetParent(transform);
  356. ProjectileBase ProjectileNONE = projNoneObj.AddComponent<ProjectileBase>();
  357. ProjectileNONE.isHit = true;
  358. ProjectileNONE.bulletLoadParam = _bulletloadParam;
  359. if (ProjectileBases.ContainsKey(_bulletloadParam.id))
  360. {
  361. ProjectileBases[_bulletloadParam.id] = ProjectileNONE;
  362. }
  363. else
  364. {
  365. ProjectileBases.Add(_bulletloadParam.id, ProjectileNONE);
  366. }
  367. }
  368. /// <summary>
  369. /// 创建发射阶段效果.
  370. /// </summary>
  371. /// <param name="id">目标</param>
  372. /// <param name="_targetPoint">目标位置</param>
  373. /// <param name="_targetTransform">目标trans</param>
  374. /// <param name="isMapProjectile">是否为(地图坐标型)全屏特效</param>
  375. public void createSkillShoot(string id, Vector3 _targetPoint, string targetID, bool isMapProjectile, int posType)
  376. {
  377. Fighter temp = FightingManager.Instance.GetFighter(targetID);//获取目标对象
  378. if (temp == null && isMapProjectile == false)
  379. {
  380. ///角色已死亡
  381. return;
  382. }
  383. ///根据id获取子弹对象
  384. if (ProjectileBases.ContainsKey(id))
  385. {
  386. ProjectileBase _projectileBase = ProjectileBases[id];
  387. _projectileBase.bulletLoadParam.targetId = targetID;
  388. ///bulletlaunch时设定是否为地图定点子弹,主要根据其子弹目标类型
  389. ///地图坐标型子弹.
  390. _projectileBase.isMapProjectile = isMapProjectile;
  391. _projectileBase.posType = posType;
  392. ///无弹道直接爆炸/全屏特效无坐标
  393. if (_projectileBase.bulletLoadParam.traject == EBulletTrajectoryType.EBULLET_TRAJECT_NONE ||
  394. _targetPoint == Vector3.zero) //||
  395. // isMapProjectile == true)
  396. {
  397. createSkillExplode(_targetPoint, _projectileBase.bulletLoadParam, isMapProjectile, posType);
  398. }
  399. else
  400. {
  401. skillShoot.Shoot(_projectileBase, _targetPoint, temp != null ? temp.transform : null);//发射弹道
  402. }
  403. }
  404. else
  405. {
  406. SkillControl.Instance.removeExplode(id);
  407. }
  408. }
  409. /// <summary>
  410. /// 创建爆炸阶段效果.
  411. /// </summary>
  412. /// <param name="point">目标位置</param>
  413. /// <param name="_bulletLoadParam">参数</param>
  414. /// <param name="isMapProjectile">是否为场景坐标轨迹型子弹</param>
  415. /// <param name="posType">位置类型</param>
  416. public void createSkillExplode(Vector3 point, BulletLoadParam _bulletLoadParam, bool isMapProjectile, int posType = 0)
  417. {
  418. ///发起者
  419. Fighter temp = FightingManager.Instance.GetFighter(_bulletLoadParam.uid);
  420. ///加载爆炸光效
  421. ResourceHelper.Instance.LoadAssetBundle(_bulletLoadParam.res + "_bomb", ab =>
  422. {
  423. GameObject explodeEff = null;
  424. ExplodeBase newExplode = null;
  425. if (ab != null)
  426. {
  427. if (temp == null)
  428. {
  429. ///角色已死亡
  430. return;
  431. }
  432. explodeEff = (GameObject)Instantiate(ab.LoadAsset(_bulletLoadParam.res));
  433. newExplode = explodeEff.GetComponent<ExplodeBase>();
  434. }
  435. else
  436. {
  437. explodeEff = new GameObject();
  438. explodeEff.name = _bulletLoadParam.res + "_bomb";
  439. }
  440. if (newExplode == null)
  441. {
  442. newExplode = explodeEff.AddComponent<ExplodeBase>();
  443. }
  444. ///子弹参数
  445. newExplode.bulletId = _bulletLoadParam.id;
  446. newExplode.OnDestoryAction = removeExplode;
  447. //***************************位置处理***************************
  448. //Debug.LogError("子弹模板:"+_bulletLoadParam.tid);
  449. if (posType == 3)
  450. ///全屏特效 屏幕中心
  451. {
  452. if (_bulletLoadParam.team == EBattleTeam.BLUETEAM)
  453. {
  454. this.TranformHandle(explodeEff.transform, this.mUICenter);
  455. }
  456. else
  457. {
  458. this.TranformHandle(explodeEff.transform, this.mMapCenter);
  459. }
  460. }
  461. else if (posType == 4)
  462. {
  463. this.TranformHandle(explodeEff.transform, Vector3.zero);
  464. }
  465. else if (posType == 2) //地图跟随
  466. {
  467. if (_bulletLoadParam.team == EBattleTeam.BLUETEAM)
  468. {
  469. this.TranformHandle(explodeEff.transform, this.mUICenter);
  470. }
  471. else
  472. {
  473. this.TranformHandle(explodeEff.transform, WorldToGUI(FightingMap.Instance.MapPos));
  474. ///如果是蓝队,则会出现镜头转向时,特效被旋转偏移
  475. ///子弹(附加)追踪脚本
  476. ExplodeTrace newExplodeTrace = explodeEff.AddComponent<ExplodeTrace>();
  477. newExplodeTrace.SetMapFollow((int)_bulletLoadParam.hitpoint, this.rectTransform, this.m_Canvas);
  478. newExplodeTrace.gameObject.SetActive(false);
  479. }
  480. }
  481. else//角色跟随
  482. {
  483. this.TranformHandle(explodeEff.transform, point);
  484. ///如果是蓝队,则会出现镜头转向时,特效被旋转偏移
  485. ///子弹(附加)追踪脚本
  486. ExplodeTrace newExplodeTrace = explodeEff.AddComponent<ExplodeTrace>();
  487. newExplodeTrace.SetFighterFollow(_bulletLoadParam.id, _bulletLoadParam.targetId, (int)_bulletLoadParam.hitpoint);
  488. newExplodeTrace.gameObject.SetActive(false);
  489. }
  490. //***************************位置处理结束 ***************************
  491. if (!ExplodeBases.ContainsKey(_bulletLoadParam.id))
  492. {
  493. ExplodeBases.Add(_bulletLoadParam.id, newExplode);
  494. }
  495. else
  496. {
  497. LogHelper.LogError("error: uid=" + _bulletLoadParam.uid +
  498. "、tid " + _bulletLoadParam.tid +
  499. "、targetId" + _bulletLoadParam.targetId +
  500. "、id" + _bulletLoadParam.id
  501. );
  502. }
  503. Fighter trgFighter = FightingManager.Instance.GetFighter(_bulletLoadParam.targetId);
  504. if ((trgFighter != null && !trgFighter.IsDead) || isMapProjectile)
  505. {
  506. //LogHelper.LogError(_bulletLoadParam.tid + ".............................bomb");
  507. skillExplode.create(_bulletLoadParam.tid, explodeEff);
  508. }
  509. else
  510. {
  511. LogHelper.Log("角色死亡 " + _bulletLoadParam.targetId + "/" + _bulletLoadParam.tid + ".............................bomb");
  512. //if (!isMapProjectile)
  513. //{
  514. SkillControl.Instance.removeExplode(_bulletLoadParam.id);
  515. return;
  516. //}
  517. }
  518. if (_bulletLoadParam.audio.Length >= 3)
  519. {
  520. AudioManager.Instance.PlaySkillSoundEffect(_bulletLoadParam.audio[2]);
  521. }
  522. else
  523. {
  524. //LogHelper.LogError("audio :"+_bulletLoadParam.tid+" 没有音效数据[cfg_bullet无此模板数据,请添加]");
  525. }
  526. //LogHelper.LogError(_bulletLoadParam.tid + ".............................bomb");
  527. ///通知子弹碰撞
  528. if (mBattleFeildAdapter != null)
  529. {
  530. mBattleFeildAdapter.OnBulletCollideEvent(_bulletLoadParam.id, _bulletLoadParam.uid);
  531. }
  532. });
  533. }
  534. /// <summary>
  535. ///
  536. /// </summary>
  537. /// <returns></returns>
  538. public Vector3 WorldToGUI(Vector3 orgPos)
  539. {
  540. Vector2 pos;
  541. Vector3 screenPos = CameraManager.Instance.SenceCamara.WorldToScreenPoint(orgPos);
  542. RectTransformUtility.ScreenPointToLocalPointInRectangle(this.rectTransform,
  543. screenPos, CameraManager.Instance.SenceUICamera, out pos);
  544. return pos;
  545. }
  546. /// <summary>
  547. /// 清理当前运行时的技能数据
  548. /// </summary>
  549. public void ClearRuningSkill()
  550. {
  551. /// 子弹字典
  552. List<ProjectileBase> projectileBases = new List<ProjectileBase>(ProjectileBases.Values);
  553. for (int i = 0; i < projectileBases.Count; i++)
  554. {
  555. Destroy(projectileBases[i].gameObject);
  556. }
  557. ProjectileBases.Clear();
  558. ///爆炸字典
  559. List<ExplodeBase> explodeBase = new List<ExplodeBase>(ExplodeBases.Values);
  560. for (int i = 0; i < explodeBase.Count; i++)
  561. {
  562. Destroy(explodeBase[i].gameObject);
  563. }
  564. ExplodeBases.Clear();
  565. }
  566. /// <summary>
  567. /// 清除所有技能特效
  568. /// </summary>
  569. public void ClearAllSkill()
  570. {
  571. this.ClearRuningSkill();
  572. List<GameObject> commReses = new List<GameObject>(mCommonBaseObjs.Values);
  573. /// 准备阶段
  574. for (int i = 0; i < commReses.Count; i++)
  575. {
  576. Destroy(commReses[i]);
  577. }
  578. mCommonBaseObjs.Clear();
  579. }
  580. }
  581. }