Merge branch 'main' of http://59.18.227.134:30008/dal4segno/Northbound
This commit is contained in:
27643
Assets/Animations/Monster Attack 1.anim
Normal file
27643
Assets/Animations/Monster Attack 1.anim
Normal file
File diff suppressed because it is too large
Load Diff
8
Assets/Animations/Monster Attack 1.anim.meta
Normal file
8
Assets/Animations/Monster Attack 1.anim.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c4b0e294c51551499712a4aa2161713
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -120,7 +120,7 @@ AnimatorState:
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 5005478036900451222, guid: bbfa9cc7ae2f16448b3adb4300f439e9, type: 3}
|
||||
m_Motion: {fileID: 7400000, guid: 5c4b0e294c51551499712a4aa2161713, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
|
||||
@@ -564,6 +564,7 @@ RectTransform:
|
||||
m_Children:
|
||||
- {fileID: 1053830688}
|
||||
- {fileID: 1458057163}
|
||||
- {fileID: 462053114}
|
||||
m_Father: {fileID: 860732961}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
@@ -591,6 +592,11 @@ MonoBehaviour:
|
||||
buildingDescriptionText: {fileID: 22654986}
|
||||
buildingCostText: {fileID: 426416072}
|
||||
maxSlots: 8
|
||||
--- !u!224 &462053114 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
m_PrefabInstance: {fileID: 829812757}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!4 &500303526 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 922888705413710451, guid: 5662d0b0d0eb5f54290edd8dd0980b57, type: 3}
|
||||
@@ -1261,6 +1267,151 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 576429380}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1001 &829812757
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 457600247}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2206383192760330694, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2206383192760330694, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2206383192760330694, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2206383192760330694, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4388086439464652406, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4388086439464652406, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4388086439464652406, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4388086439464652406, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5112555873318329611, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: RespawnPanel
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 34.253292
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5292594068365800785, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7942973741237334281, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7942973741237334281, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7942973741237334281, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7942973741237334281, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 9257920ba4a6256499ad89eeb7d7098a, type: 3}
|
||||
--- !u!1 &860732960
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -85,6 +85,12 @@ namespace Northbound
|
||||
);
|
||||
|
||||
private GameObject _cachedTargetPlayer;
|
||||
private Animator _animator;
|
||||
private Unity.Netcode.Components.NetworkAnimator _networkAnimator;
|
||||
private bool _isAttacking = false;
|
||||
private GameObject _pendingAttackTarget;
|
||||
private float _attackStartTime;
|
||||
private const float ATTACK_TIMEOUT = 1f; // 애니메이션 이벤트 미발생 시 타임아웃
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
@@ -92,14 +98,16 @@ namespace Northbound
|
||||
|
||||
_agent = GetComponent<NavMeshAgent>();
|
||||
_enemyUnit = GetComponent<EnemyUnit>();
|
||||
_animator = GetComponent<Animator>();
|
||||
_networkAnimator = GetComponent<Unity.Netcode.Components.NetworkAnimator>();
|
||||
_originPosition = transform.position;
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
// NavMeshAgent 초기 설정
|
||||
_agent.speed = moveSpeed;
|
||||
_agent.acceleration = 8f;
|
||||
_agent.angularSpeed = 120f;
|
||||
_agent.acceleration = 100f; // 높은 가속도로 즉시 정지 가능
|
||||
_agent.angularSpeed = 360f; // 빠른 회전
|
||||
_agent.stoppingDistance = attackRange * 0.9f; // 공격 범위까지 더 가까이 이동
|
||||
_agent.autoBraking = true;
|
||||
_agent.updateRotation = true;
|
||||
@@ -165,19 +173,15 @@ namespace Northbound
|
||||
if (_isRecalculatingPath) return; // 코루틴 대기 중이면 중단
|
||||
if (_coreTransform == null) { FindCore(); return; }
|
||||
|
||||
// 0. 경로 계산 중이거나 이동 중이 아니면 아무것도 하지 않음
|
||||
if (_agent.pathPending) return; // 경로 계산 중
|
||||
// 0. 경로 계산 중이면 대기
|
||||
if (_agent.pathPending) return;
|
||||
|
||||
// 0.5. 실제로 이동 중인지 확인 (velocity로 판단)
|
||||
if (_agent.hasPath && _agent.velocity.sqrMagnitude > 0.01f)
|
||||
// 0.5. 코어 콜라이더 표면까지의 거리로 도달 확인 (이동 중 여부와 관계없이 항상 체크)
|
||||
if (GetDistanceToCoreSurface() <= attackRange)
|
||||
{
|
||||
// 이동 중이면 코어 콜라이더 표면까지의 거리로 도달 확인
|
||||
if (GetDistanceToCoreSurface() <= attackRange)
|
||||
{
|
||||
SetTargetPlayer(_coreTransform.gameObject);
|
||||
TransitionToState(EnemyAIState.Attack);
|
||||
return;
|
||||
}
|
||||
SetTargetPlayer(_coreTransform.gameObject);
|
||||
TransitionToState(EnemyAIState.Attack);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Player 감지 (코어로 가는 도중에도 Player/건물을 타겟팅)
|
||||
@@ -223,7 +227,20 @@ namespace Northbound
|
||||
return;
|
||||
}
|
||||
|
||||
float distanceToPlayer = Vector3.Distance(transform.position, targetPlayer.transform.position);
|
||||
// 코어 타겟인지 확인
|
||||
bool isCoreTarget = (targetPlayer == _coreTransform?.gameObject);
|
||||
|
||||
// 거리 계산 - 코어는 콜라이더 표면까지의 거리 사용
|
||||
float distanceToPlayer;
|
||||
if (isCoreTarget)
|
||||
{
|
||||
distanceToPlayer = GetDistanceToCoreSurface();
|
||||
}
|
||||
else
|
||||
{
|
||||
distanceToPlayer = GetDistanceToTarget(targetPlayer);
|
||||
}
|
||||
|
||||
Vector3 chaseReferencePoint = (aiType == TeamType.Monster) ? _chaseStartPosition : _originPosition;
|
||||
float distanceFromReference = Vector3.Distance(transform.position, chaseReferencePoint);
|
||||
|
||||
@@ -247,6 +264,19 @@ namespace Northbound
|
||||
|
||||
private void UpdateAttack()
|
||||
{
|
||||
// 공격 타임아웃 체크 (애니메이션 이벤트가 발생하지 않은 경우)
|
||||
if (_isAttacking && Time.time - _attackStartTime > ATTACK_TIMEOUT)
|
||||
{
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Debug.LogWarning($"[EnemyAIController] 공격 타임아웃 - 애니메이션 이벤트 미발생: {gameObject.name}");
|
||||
}
|
||||
// 타임아웃 시 공격 실행 후 상태 리셋
|
||||
PerformAttack();
|
||||
_isAttacking = false;
|
||||
_pendingAttackTarget = null;
|
||||
}
|
||||
|
||||
GameObject target = GetTargetPlayer();
|
||||
if (target == null)
|
||||
{
|
||||
@@ -434,7 +464,10 @@ namespace Northbound
|
||||
|
||||
private void AttackTarget(GameObject target)
|
||||
{
|
||||
// 1. 타겟의 자식, 본인, 부모 순으로 샅샅이 뒤져서 IDamageable을 찾습니다.
|
||||
// 이미 공격 중이면 대기
|
||||
if (_isAttacking) return;
|
||||
|
||||
// 1. 타겟의 자식, 본인, 부모 순으로 샅샛이 뒤져서 IDamageable을 찾습니다.
|
||||
IDamageable damageable = target.GetComponentInChildren<IDamageable>();
|
||||
if (damageable == null) damageable = target.GetComponent<IDamageable>();
|
||||
if (damageable == null) damageable = target.GetComponentInParent<IDamageable>();
|
||||
@@ -444,18 +477,96 @@ namespace Northbound
|
||||
// 2. 공격 쿨타임 체크
|
||||
if (Time.time - _lastAttackTime >= attackInterval)
|
||||
{
|
||||
damageable.TakeDamage(attackDamage, NetworkObjectId);
|
||||
_lastAttackTime = Time.time;
|
||||
OnAttackPerformed?.Invoke(target);
|
||||
_isAttacking = true;
|
||||
_pendingAttackTarget = target;
|
||||
_attackStartTime = Time.time;
|
||||
|
||||
// 애니메이션 재생 (서버에서 NetworkAnimator로 동기화)
|
||||
if (_networkAnimator != null)
|
||||
{
|
||||
_networkAnimator.SetTrigger("Attack");
|
||||
}
|
||||
else if (_animator != null)
|
||||
{
|
||||
_animator.SetTrigger("Attack");
|
||||
// 애니메이션은 있지만 NetworkAnimator가 없으면 즉시 공격 (이벤트 미지원)
|
||||
PerformAttack();
|
||||
_isAttacking = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 애니메이션이 없으면 즉시 공격 실행
|
||||
PerformAttack();
|
||||
_isAttacking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 공격할 수 없는 대상이면 상태를 해제합니다.
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Debug.LogWarning($"[EnemyAIController] IDamageable을 찾을 수 없음: {target.name}");
|
||||
}
|
||||
OnLostTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 애니메이션 이벤트: 공격 타격 시점에 호출
|
||||
/// </summary>
|
||||
public void OnAttackHit()
|
||||
{
|
||||
PerformAttack();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 애니메이션 이벤트: 공격 완료 시 호출
|
||||
/// </summary>
|
||||
public void OnAttackComplete()
|
||||
{
|
||||
_isAttacking = false;
|
||||
_pendingAttackTarget = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 실제 공격 수행 (데미지 적용)
|
||||
/// </summary>
|
||||
private void PerformAttack()
|
||||
{
|
||||
if (_pendingAttackTarget == null)
|
||||
{
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Debug.LogWarning("[EnemyAIController] PerformAttack: 타겟이 없음");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
IDamageable damageable = _pendingAttackTarget.GetComponentInChildren<IDamageable>();
|
||||
if (damageable == null) damageable = _pendingAttackTarget.GetComponent<IDamageable>();
|
||||
if (damageable == null) damageable = _pendingAttackTarget.GetComponentInParent<IDamageable>();
|
||||
|
||||
if (damageable != null && !damageable.IsDead())
|
||||
{
|
||||
damageable.TakeDamage(attackDamage, NetworkObjectId);
|
||||
OnAttackPerformed?.Invoke(_pendingAttackTarget);
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Debug.Log($"[EnemyAIController] {gameObject.name}이(가) {_pendingAttackTarget.name}에게 {attackDamage} 데미지 적용");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Debug.LogWarning($"[EnemyAIController] PerformAttack: 대상이 유효하지 않음 - {_pendingAttackTarget.name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities & Distance
|
||||
@@ -499,6 +610,8 @@ namespace Northbound
|
||||
case EnemyAIState.Attack:
|
||||
_agent.isStopped = true;
|
||||
_agent.ResetPath();
|
||||
// 즉시 정지를 위해 velocity 초기화
|
||||
_agent.velocity = Vector3.zero;
|
||||
break;
|
||||
case EnemyAIState.MoveToCore:
|
||||
_agent.isStopped = false;
|
||||
@@ -629,6 +742,57 @@ namespace Northbound
|
||||
Gizmos.DrawLine(transform.position, transform.position + right);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// 상태 및 타겟 정보 표시
|
||||
string stateInfo = $"State: {_currentState.Value}";
|
||||
string targetInfo = "Target: None";
|
||||
|
||||
if (_cachedTargetPlayer != null)
|
||||
{
|
||||
targetInfo = $"Target: {_cachedTargetPlayer.name}";
|
||||
}
|
||||
else if (_targetPlayerId.Value != 0)
|
||||
{
|
||||
targetInfo = $"Target ID: {_targetPlayerId.Value}";
|
||||
}
|
||||
else if (_coreTransform != null && _currentState.Value == EnemyAIState.MoveToCore)
|
||||
{
|
||||
targetInfo = "Target: Core (moving)";
|
||||
}
|
||||
|
||||
string attackInfo = _isAttacking ? " [ATTACKING]" : "";
|
||||
string distanceInfo = "";
|
||||
|
||||
if (_coreTransform != null)
|
||||
{
|
||||
float distToCore = GetDistanceToCoreSurface();
|
||||
distanceInfo = $"\nDistToCore: {distToCore:F1} (Range: {attackRange})";
|
||||
}
|
||||
|
||||
string fullInfo = $"{gameObject.name}\n{stateInfo}\n{targetInfo}{attackInfo}{distanceInfo}";
|
||||
|
||||
UnityEditor.Handles.Label(
|
||||
transform.position + Vector3.up * 3f,
|
||||
fullInfo,
|
||||
new GUIStyle(GUI.skin.label) { fontSize = 12, normal = { textColor = Color.white } }
|
||||
);
|
||||
|
||||
// 타겟으로 선 그리기
|
||||
if (_cachedTargetPlayer != null)
|
||||
{
|
||||
Gizmos.color = Color.magenta;
|
||||
Gizmos.DrawLine(transform.position + Vector3.up, _cachedTargetPlayer.transform.position + Vector3.up);
|
||||
}
|
||||
else if (_coreTransform != null && _currentState.Value == EnemyAIState.MoveToCore)
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawLine(transform.position + Vector3.up, _coreTransform.position + Vector3.up);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user