diff --git a/Assets/PerfectWorld/Scene/LoginScene.unity b/Assets/PerfectWorld/Scene/LoginScene.unity index 4de3721598..d6589e3bd1 100644 --- a/Assets/PerfectWorld/Scene/LoginScene.unity +++ b/Assets/PerfectWorld/Scene/LoginScene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc028fc3c6b7fe9d9851f84da3d784923ccfb38af77605dd57c1e7e12e4c5817 -size 113080 +oid sha256:08dd99327890c9a20d09b1faadef16ad55b5a276d5080d744bfdc2f4191dceac +size 111628 diff --git a/Assets/PerfectWorld/Scripts/Sound/SFXManager.cs b/Assets/PerfectWorld/Scripts/Sound/SFXManager.cs index f98c969b14..bcdcef95da 100644 --- a/Assets/PerfectWorld/Scripts/Sound/SFXManager.cs +++ b/Assets/PerfectWorld/Scripts/Sound/SFXManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Cysharp.Threading.Tasks; using UnityEngine; +using UnityEngine.Audio; using BrewMonster.Scripts; namespace BrewMonster.Scripts @@ -24,10 +25,23 @@ namespace BrewMonster.Scripts /// [SerializeField] private AudioSource _moveSoundSource; + /// + /// Mixer group that all skill SFX are routed through. Assign in the Inspector. + /// + [SerializeField] private AudioMixerGroup _sfxMixerGroup; + + /// + /// Number of pooled AudioSources available for concurrent skill SFX playback. + /// + [SerializeField] private int _sfxPoolSize = 8; + + private readonly List _sfxPool = new(); + protected override void Initialize() { base.Initialize(); LoadSoundTable(); + BuildSfxPool(); } // ──────────────────────────────────────────────────────────────────── @@ -81,6 +95,37 @@ namespace BrewMonster.Scripts return path; } + // ──────────────────────────────────────────────────────────────────── + // Skill SFX pool + // ──────────────────────────────────────────────────────────────────── + + private void BuildSfxPool() + { + for (int i = 0; i < _sfxPoolSize; i++) + { + var child = new GameObject($"SFXPool_{i}"); + child.transform.SetParent(transform); + var src = child.AddComponent(); + src.playOnAwake = false; + src.outputAudioMixerGroup = _sfxMixerGroup; + _sfxPool.Add(src); + } + } + + /// World position the source is moved to before playback. + /// 0 = fully 2D, 1 = fully 3D positional. + private AudioSource GetPooledSource(Vector3 worldPos, float spatialBlend = 0f) + { + AudioSource chosen = null; + foreach (var src in _sfxPool) + if (!src.isPlaying) { chosen = src; break; } + if (chosen == null) chosen = _sfxPool[0]; // fallback: steal oldest + + chosen.transform.position = worldPos; + chosen.spatialBlend = spatialBlend; + return chosen; + } + // ──────────────────────────────────────────────────────────────────── // Public API // ──────────────────────────────────────────────────────────────────── @@ -114,14 +159,14 @@ namespace BrewMonster.Scripts if (mgr.TryGetCachedAudioClip(address, out var clip) && clip != null) { - AudioSource.PlayClipAtPoint(clip, Vector3.zero, SkillSfxVolume); + GetPooledSource(worldPos).PlayOneShot(clip, SkillSfxVolume); return; } await mgr.WaitUntilInitializedAsync(); var loaded = await mgr.LoadAudioClipAsync(address); if (loaded != null) - AudioSource.PlayClipAtPoint(loaded, Vector3.zero, SkillSfxVolume); + GetPooledSource(worldPos).PlayOneShot(loaded, SkillSfxVolume); } /// diff --git a/Assets/Prefabs/UI/Music.prefab b/Assets/Prefabs/UI/Music.prefab new file mode 100644 index 0000000000..3348bdb751 --- /dev/null +++ b/Assets/Prefabs/UI/Music.prefab @@ -0,0 +1,415 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2838129733766203984 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3636302681040170949} + - component: {fileID: 4656951194032224} + - component: {fileID: 1928072503138413728} + m_Layer: 0 + m_Name: SFX + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3636302681040170949 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2838129733766203984} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4292995824318243454} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4656951194032224 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2838129733766203984} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4cfa292fff0815d40b82f32256b3f2cc, type: 3} + m_Name: + m_EditorClassIdentifier: + _moveSoundSource: {fileID: 1928072503138413728} + _sfxMixerGroup: {fileID: 217038053835239290, guid: 9c6a7598ca0dfcd4fa51470ebbdd7549, type: 2} + _sfxPoolSize: 8 +--- !u!82 &1928072503138413728 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2838129733766203984} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 217038053835239290, guid: 9c6a7598ca0dfcd4fa51470ebbdd7549, type: 2} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!1 &6634120867767479402 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4292995824318243454} + - component: {fileID: 3455241641456728159} + - component: {fileID: 8780839799255791761} + - component: {fileID: 8959945188973705721} + - component: {fileID: 6104956088685348673} + - component: {fileID: 8206320370371461788} + m_Layer: 0 + m_Name: Music + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4292995824318243454 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6634120867767479402} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3636302681040170949} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3455241641456728159 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6634120867767479402} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ab7ebb437fbd3fb41a7300a381fcab00, type: 3} + m_Name: + m_EditorClassIdentifier: + bgmSource: {fileID: 8780839799255791761} + _bgmMixerGroup: {fileID: 1439606312574676259, guid: 9c6a7598ca0dfcd4fa51470ebbdd7549, type: 2} + _ambienceMixerGroup: {fileID: 661067059137401939, guid: 9c6a7598ca0dfcd4fa51470ebbdd7549, type: 2} + _sfxMixerGroup: {fileID: 217038053835239290, guid: 9c6a7598ca0dfcd4fa51470ebbdd7549, type: 2} +--- !u!82 &8780839799255791761 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6634120867767479402} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 1 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!82 &8959945188973705721 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6634120867767479402} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!81 &6104956088685348673 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6634120867767479402} + m_Enabled: 1 +--- !u!114 &8206320370371461788 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6634120867767479402} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f520c80aab01bbc45aa22010a90dfd66, type: 3} + m_Name: + m_EditorClassIdentifier: + _worldMusicDB: {fileID: 11400000, guid: 7602c1f71697aae42a7751212c5144dc, type: 2} diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 00d1f5cd6d..818e0af5c5 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -54,8 +54,8 @@ namespace BrewMonster private bool m_bJumpInWater = false; public A3DVECTOR3 m_vVelocity; // Velocity - - + + bool m_bChangingFace; // true, host is changing face private int m_iRoleCreateTime; private int m_iRoleLastLoginTime; // Role last login time @@ -185,7 +185,7 @@ namespace BrewMonster private UnityEngine.InputSystem.Keyboard m_cachedKeyboard; int[] targetsCastSkill; - + public bool IsChangingFace() { return m_bChangingFace; @@ -367,8 +367,8 @@ namespace BrewMonster m_iAccountTotalCash = RoleInfo.cash_add; EC_Game.GetGameRun().AddPlayerName(m_PlayerInfo.cid, m_strName); - - if (!await LoadPlayerSkeleton(true)) + + if (!await LoadPlayerSkeleton(true)) { BMLogger.LogError($"HoangDev CECHostPlayer::LoadResources, Failed to load skeleton. {m_strName}"); return false; @@ -487,7 +487,7 @@ namespace BrewMonster public void ProcessMessage(in ECMSG Msg) { var msg = (int)Msg.dwMsg; - //Debug.LogError("HoangDev : ProcessMessageProcessMessageProcessMessage " + msg); + //Debug.LogError("HoangDev : ProcessMessageProcessMessageProcessMessage " + msg); switch (msg) { case EC_MsgDef.MSG_HST_CORRECTPOS: OnMsgHstCorrectPos(Msg); break; @@ -878,7 +878,7 @@ namespace BrewMonster NotifyServerForceAttack(true); } } - + #if UNITY_EDITOR /// /// Cycles through learned skills by removing all shortcuts and adding 2 new skills to slots 0 and 1. @@ -891,7 +891,7 @@ namespace BrewMonster { return m_bEnterGame; } - + void SetLevel2(int level2, bool bFirstTime) { int lastLevel2 = m_BasicProps.iLevel2; @@ -905,7 +905,7 @@ namespace BrewMonster { return m_pWorkMan.IsMovingToPosition(); }*/ - + public bool IsPosCollideFree(A3DVECTOR3 vTargetPos) { bool bAvailable = (false); @@ -1502,7 +1502,7 @@ namespace BrewMonster // SetPlayerModel(); //Debug.LogError("Pos Character = " + pos); } - + /// Use host's m_pvp (we update it from S2C duel packets). Base IsInDuel() reads CECPlayer.m_pvp which is never set. public new bool IsInDuel() { return m_pvp.iDuelState == Duel_state.DUEL_ST_INDUEL; } @@ -1536,7 +1536,7 @@ namespace BrewMonster //bool bResult = false; float fRange = 0.0f; //string reasonStr = iReason == 1 ? "melee" : (iReason == 2 ? "cast magic" : (iReason == 3 ? "talk" : $"unknown({iReason})")); - + switch (iReason) { case 1: // melee @@ -1769,7 +1769,7 @@ namespace BrewMonster return fSpeedSev; } - + public void PrepareNPCService(int idSev) { @@ -2077,9 +2077,9 @@ namespace BrewMonster return bRet; } - + public int GetMaxLevelSofar() { return Math.Max(m_ReincarnationTome.max_level, m_BasicProps.iLevel); } - + public bool CanUseProjectile(CECIvtrArrow pArrow) { if (pArrow == null) @@ -2660,7 +2660,7 @@ namespace BrewMonster return idNewSel; } - + //public float GetSwimSpeedSev() //{ // float fSpeedSev = GetSwimSpeed(); @@ -3021,7 +3021,7 @@ namespace BrewMonster // cursorType = CursorType.Pickup; // else if (CanGatherMatter(pMatter)) // cursorType = pMatter.IsMonsterSpiritMine() ? CursorType.Swallow : CursorType.Dig; - // + // // if (cursorType != CursorType.Normal) // m_idCurHover = idHitObject; // } @@ -3382,7 +3382,7 @@ namespace BrewMonster BUBBLE_HPWARN, BUBBLE_MPWARN, BUBBLE_REBOUND, // ·´µ¯ - BUBBLE_BEAT_BACK, // ·´»÷ + BUBBLE_BEAT_BACK, // ·´»÷ BUBBLE_ADD, // ÎüѪµÄ¼ÓºÅ BUBBLE_DODGE_DEBUFF, BUBBLE_REALMEXP, @@ -3781,7 +3781,7 @@ namespace BrewMonster // Get key object(NPC..) coordinates public A3DVECTOR3 GetObjectCoordinates(int idTarget, out List TargetCoord, ref bool bInTable) { - + TargetCoord = new List(); A3DVECTOR3 vDestPos = new A3DVECTOR3(0, 0, 0); @@ -3816,7 +3816,7 @@ namespace BrewMonster string strCurMap = pInstance.GetPath() ?? string.Empty; // �ȼ��ͬһ��ͼ���Ƿ���Ҫ���ҵ���Ʒ bool bHasObjectInCurrentInstance = originalCoords.Any(coord => coord.strMap == strCurMap); - + // Iterate over original list and build filtered TargetCoord list for (int i = 0; i < iCount; i++) { @@ -3848,10 +3848,10 @@ namespace BrewMonster if (instCoord[j].strMap == strCurMap) { TargetCoord.Add(instCoord[j]); - + // Check if this is the nearest target float tempDist = (instCoord[j].vPos - GetPos()).Magnitude(); - + if (tempDist < fMinDist) { fMinDist = tempDist; @@ -3869,7 +3869,7 @@ namespace BrewMonster public int GetRealmSubLevel() { return m_RealmLevel % 100; } public static int GetRealmLayer(int realmLevel) { return realmLevel > 0 ? (realmLevel + 9) / 10 : 0; } public static int GetRealmSubLevel(int realmLevel) { return realmLevel > 0 ? (realmLevel % 10 > 0 ? realmLevel % 10 : 10) : 0; } - + // // Calculate distance to an object and optionally retrieve the object reference // 计算到对象的距离,并可选地获取对象引用 @@ -3981,13 +3981,13 @@ namespace BrewMonster // } //} - } + } //// ID checking helper methods //private bool ISNPCID(int id) => ((id & 0x80000000) != 0) && ((id & 0x40000000) == 0); //private bool ISPLAYERID(int id) => id != 0 && (id & 0x80000000) == 0; //private bool ISMATTERID(int id) => ((id) & 0xC0000000) == 0xC0000000; - + // Release object public void Release() { @@ -4227,7 +4227,14 @@ namespace BrewMonster { if (id == _curMoveSndId) return; _curMoveSndId = id; - SFXManager.Instance?.PlayMoveSoundAsync(id).Forget(); + if(id > 0) + { + SFXManager.Instance?.PlayMoveSoundAsync(id).Forget(); + } + /*else + { + SFXManager.Instance?.StopMoveSoundAsync().Forget(); + }*/ } /// @@ -4254,8 +4261,8 @@ namespace BrewMonster return trace.GetTraceReason() == Trace_reason.TRACE_SPELL; } } - - + + }