From d95eb9df1d1e6dcdda11ec9520e895dd488f1ed1 Mon Sep 17 00:00:00 2001 From: "DESKTOP-VVOCIJO\\PC" Date: Fri, 22 May 2026 16:17:35 +0900 Subject: [PATCH] =?UTF-8?q?2026-05-22=20=EB=B3=B4=EC=8A=A4=EC=8A=A4?= =?UTF-8?q?=ED=82=AC=20:=20=EC=A7=80=EC=A7=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/01_Scenes/BossScene.unity | 4 +- .../Enemy/Skills/EarthquakeSkill.cs | 43 ++++++++++ .../Enemy/Skills/EarthquakeSkill.cs.meta | 2 + Assets/02_Scripts/Enemy/Skills/HazardSkill.cs | 13 +-- .../02_Scripts/Enemy/Skills/SkillSupport.cs | 7 +- Assets/02_Scripts/Managers/GameManager.cs | 29 +++++++ .../02_Scripts/Managers/GameManager.cs.meta | 2 + .../02_Scripts/Managers/SceneLoadManager.cs | 17 ++-- Assets/02_Scripts/Player/PlayerController.cs | 85 +++++++++++++++++-- .../ColorMan/Prefabs/BossMan.prefab | 4 +- .../WhiteMan/Animations/Knockback.anim | 4 +- .../BossSkills/EarthquakeSkill.meta} | 2 +- .../EarthquakeSkill/EarthquakeSkill.prefab | 3 + .../EarthquakeSkill.prefab.meta} | 4 +- Assets/_Recovery/0 (1).unity | 3 - Assets/_Recovery/0 (1).unity.meta | 7 -- Assets/_Recovery/0.unity | 3 - ProjectSettings/PackageManagerSettings.asset | 4 +- 18 files changed, 186 insertions(+), 50 deletions(-) create mode 100644 Assets/02_Scripts/Enemy/Skills/EarthquakeSkill.cs create mode 100644 Assets/02_Scripts/Enemy/Skills/EarthquakeSkill.cs.meta create mode 100644 Assets/02_Scripts/Managers/GameManager.cs create mode 100644 Assets/02_Scripts/Managers/GameManager.cs.meta rename Assets/{_Recovery.meta => 08_Objects/BossSkills/EarthquakeSkill.meta} (77%) create mode 100644 Assets/08_Objects/BossSkills/EarthquakeSkill/EarthquakeSkill.prefab rename Assets/{_Recovery/0.unity.meta => 08_Objects/BossSkills/EarthquakeSkill/EarthquakeSkill.prefab.meta} (63%) delete mode 100644 Assets/_Recovery/0 (1).unity delete mode 100644 Assets/_Recovery/0 (1).unity.meta delete mode 100644 Assets/_Recovery/0.unity diff --git a/Assets/01_Scenes/BossScene.unity b/Assets/01_Scenes/BossScene.unity index 2d3fcbf..d3d36be 100644 --- a/Assets/01_Scenes/BossScene.unity +++ b/Assets/01_Scenes/BossScene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcdb11f64dc076062b4ffa0b2557263b43322fbb4c48ba48d3a1290b3d6c46d3 -size 183433 +oid sha256:283e74c5afcf8eef64badc969dc9886c0b72efda21f60fea04931d0a29779a3c +size 188514 diff --git a/Assets/02_Scripts/Enemy/Skills/EarthquakeSkill.cs b/Assets/02_Scripts/Enemy/Skills/EarthquakeSkill.cs new file mode 100644 index 0000000..580a462 --- /dev/null +++ b/Assets/02_Scripts/Enemy/Skills/EarthquakeSkill.cs @@ -0,0 +1,43 @@ +using System.Threading; +using UnityEngine; + +public class EarthquakeSkill : BossSkill +{ + [Header("EarthquakeSkill")] + [SerializeField] private float _windup = 0.5f; // 애니 시작 ~ 피해 판정 ON + [SerializeField] private float _activeDuration = 1.5f; // 피해 판정 유지 시간 + [SerializeField] private float _recovery = 0.6f; // 판정 OFF 후 후딜 + [SerializeField] private int _damage = 15; + [SerializeField] private float _liftForce = 0.5f; // 지면 피격 시 띄우는 세기 + + protected override async Awaitable RunSkill(CancellationToken token) + { + try + { + // 선딜 + await Awaitable.WaitForSecondsAsync(_windup, token); + + Earthquake(); + + // 후딜 + await Awaitable.WaitForSecondsAsync(_recovery, token); + } + finally + { + + } + } + + private void Earthquake() + { + PlayerController player = GameManager.Instance.LocalPlayer; + if (player == null) return; + + // 플레이어가 지면에 닿아 있을 때만 발동. 공중이면 아무 효과 없음. + if (!player.IsGrounded) return; + + // 지면 접촉 중: 살짝 띄우면서 데미지. + player.Launch(new Vector2(0f, _liftForce)); + player.TakeDamage(_damage); + } +} diff --git a/Assets/02_Scripts/Enemy/Skills/EarthquakeSkill.cs.meta b/Assets/02_Scripts/Enemy/Skills/EarthquakeSkill.cs.meta new file mode 100644 index 0000000..a6941fe --- /dev/null +++ b/Assets/02_Scripts/Enemy/Skills/EarthquakeSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d27a723f9206afc49a060c2875ccf3dc \ No newline at end of file diff --git a/Assets/02_Scripts/Enemy/Skills/HazardSkill.cs b/Assets/02_Scripts/Enemy/Skills/HazardSkill.cs index 1a98b0f..2a42dbe 100644 --- a/Assets/02_Scripts/Enemy/Skills/HazardSkill.cs +++ b/Assets/02_Scripts/Enemy/Skills/HazardSkill.cs @@ -19,14 +19,10 @@ public class HazardSkill : BossSkill [SerializeField] private GameObject _hazardHitboxOrigin; [SerializeField] private int _hitBoxDamage = 15; - private SkillSupport support; - private void Awake() { if (_animator == null) _animator = GetComponentInChildren(); - - support = Object.FindFirstObjectByType(); } protected override async Awaitable RunSkill(CancellationToken token) @@ -64,12 +60,7 @@ protected override async Awaitable RunSkill(CancellationToken token) private void PredictHazards(int dirChoice) { - if(support == null) - { - Debug.Log("Null이니"); - } - - SkillPredict[] predicts = dirChoice switch { 0 => support.DownHazardPredicts, 1=> support.RightHazardPredicts, _=> null}; + SkillPredict[] predicts = dirChoice switch { 0 => GameManager.Instance.SkillSupporter.DownHazardPredicts, 1=> GameManager.Instance.SkillSupporter.RightHazardPredicts, _=> null}; for (int i = 0; i < predicts.Length; i++) { @@ -80,7 +71,7 @@ private void PredictHazards(int dirChoice) private void FireHazards(int dirChoice) { - Transform[] hazardPoss = dirChoice switch { 0 => support.DownHazardFirePos, 1=> support.RightHazardFirePos, _=> null}; + Transform[] hazardPoss = dirChoice switch { 0 => GameManager.Instance.SkillSupporter.DownHazardFirePos, 1=> GameManager.Instance.SkillSupporter.RightHazardFirePos, _=> null}; Vector2 attackDir = dirChoice switch {0 => Vector2.down,1=>Vector2.right,_=>Vector2.left}; if (hazardPoss == null) return; diff --git a/Assets/02_Scripts/Enemy/Skills/SkillSupport.cs b/Assets/02_Scripts/Enemy/Skills/SkillSupport.cs index 7c24648..0893211 100644 --- a/Assets/02_Scripts/Enemy/Skills/SkillSupport.cs +++ b/Assets/02_Scripts/Enemy/Skills/SkillSupport.cs @@ -8,7 +8,12 @@ public class SkillSupport : MonoBehaviour [HideInInspector] public SkillPredict[] DownHazardPredicts; [HideInInspector] public SkillPredict[] RightHazardPredicts; - void Start() + private void Awake() + { + + } + + private void Start() { GameObject[] shineDownStartPoss = GameObject.FindGameObjectsWithTag("ShineDownStartPos"); GameObject[] shineRightStartPoss = GameObject.FindGameObjectsWithTag("ShineRightStartPos"); diff --git a/Assets/02_Scripts/Managers/GameManager.cs b/Assets/02_Scripts/Managers/GameManager.cs new file mode 100644 index 0000000..5076958 --- /dev/null +++ b/Assets/02_Scripts/Managers/GameManager.cs @@ -0,0 +1,29 @@ +using UnityEngine; + +public class GameManager : MonoBehaviour,ISceneInitializable +{ + public static GameManager Instance; + + public SkillSupport SkillSupporter {get; private set;} + public PlayerController LocalPlayer {get; private set;} + + + private void Awake() + { + if (Instance == null) + { + Instance = this; //만들어진 자신을 인스턴스로 설정 + } + else + { + Destroy(gameObject); //이미 인스턴스가 있으면 자신을 파괴 + } + } + + public void OnSceneLoaded() + { + Debug.Log("aaaa"); + SkillSupporter = Object.FindFirstObjectByType(); + LocalPlayer = Object.FindFirstObjectByType(); + } +} diff --git a/Assets/02_Scripts/Managers/GameManager.cs.meta b/Assets/02_Scripts/Managers/GameManager.cs.meta new file mode 100644 index 0000000..35cc2f0 --- /dev/null +++ b/Assets/02_Scripts/Managers/GameManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5b2c0a8cf5c55bc4da93c75f61309de6 \ No newline at end of file diff --git a/Assets/02_Scripts/Managers/SceneLoadManager.cs b/Assets/02_Scripts/Managers/SceneLoadManager.cs index ec9bc07..901c985 100644 --- a/Assets/02_Scripts/Managers/SceneLoadManager.cs +++ b/Assets/02_Scripts/Managers/SceneLoadManager.cs @@ -31,19 +31,18 @@ private void Update() //씬이 로드되었을때 호출 private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { - if(scene.name == "GameScene") - { - MonoBehaviour[] allObjs = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None); - foreach (var obj in allObjs) + MonoBehaviour[] allObjs = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None); + + foreach (var obj in allObjs) + { + if (obj is ISceneInitializable initializable) { - if (obj is ISceneInitializable initializable) - { - //씬에서 ISceneInitializable 인터페이스를 가진 오브젝트의 초기화 로직을 실행 - initializable.OnSceneLoaded(); - } + //씬에서 ISceneInitializable 인터페이스를 가진 오브젝트의 초기화 로직을 실행 + initializable.OnSceneLoaded(); } } + } public void SetSceneLoadingProgressValue(float value) diff --git a/Assets/02_Scripts/Player/PlayerController.cs b/Assets/02_Scripts/Player/PlayerController.cs index da1b1f3..8e257c2 100644 --- a/Assets/02_Scripts/Player/PlayerController.cs +++ b/Assets/02_Scripts/Player/PlayerController.cs @@ -27,6 +27,13 @@ public class PlayerController : MonoBehaviour,IDamageable private string _activeBaseState; // 현재 재생 중인 locomotion State (중복 Play 방지용) private bool _isInActionAnimation; // 액션 애니메이션 재생 중인지 (locomotion 잠시 양보) + // 피격 + [Header("Hit Animation")] + [SerializeField] private string _knockbackAnimationState = "Knockback"; + [SerializeField] private float _knockbackDuration = 1f; // 넉백 경직 시간 (Knockback 클립 길이에 맞춰 설정) + private float _hitstunTimer; // 경직 남은 최소 시간 + private bool _isStunned; // 경직 상태 (공중이면 착지까지 연장) + // ─── 점프 단계별 애니메이션 ─────────────────────────────────────────── // 점프를 4단계(Rise/Mid/Fall/Land)로 분리해서 vy에 따라 자동 전환. [Header("Jump Animation")] @@ -253,19 +260,21 @@ private void FixedUpdate() TickComboWindow(); // 콤보 윈도우 카운트다운 // 입력 잠금이 없고, (액션 중이 아니거나 액션이 이동을 허용할 때) 좌우 입력으로 velocity 갱신. - if (!IsMovementLocked() && (!IsActionActive() || _actionAllowsMovement)) + // 경직 중에는 이동 입력을 무시한다 (Launch로 받은 속도 + 중력만 작용). + if (!IsStunned() && !IsMovementLocked() && (!IsActionActive() || _actionAllowsMovement)) { // 백페달이면 느린 속도. 액션 중에도 동일하게 적용 (이동사격 = Down 변형 + CanMoveDuringAction). float moveSpeed = IsBackpedaling() ? _backpedalSpeed : _moveSpeed; _rb.linearVelocity = new Vector2(_moveInputX * moveSpeed, _rb.linearVelocity.y); } - if (!IsFacingLocked() && (!IsActionActive() || _actionAllowsTurn)) + if (!IsStunned() && !IsFacingLocked() && (!IsActionActive() || _actionAllowsTurn)) UpdateFacingFromMoveInput(); //중력적용 ApplyGravity(); ResolveKinematicCollisions(); + TickHitstun(); // 피격 경직 타이머 카운트다운 UpdateLocomotionAnimation(); @@ -307,7 +316,7 @@ private void OnMoveInput(Vector2 value) { _moveInputX = value.x == 0f ? 0f : Mathf.Sign(value.x); _moveInputY = value.y == 0f ? 0f : Mathf.Sign(value.y); - if (_facingLockTimer <= 0f && (!IsActionActive() || _actionAllowsTurn)) + if (!IsStunned() && _facingLockTimer <= 0f && (!IsActionActive() || _actionAllowsTurn)) UpdateFacingFromMoveInput(); } @@ -332,6 +341,8 @@ private bool IsBackpedaling() // 점프 우선순위: 지상 점프 > 공중(2단) 점프 private void OnJumpInput() { + if (IsStunned()) return; // 경직 중 점프 불가 + if (_isGrounded) { PerformJump(); @@ -374,6 +385,8 @@ private void OnGrabSmashInput() // 3) 그 외에는 즉시 ExecuteComboInput으로 발화 private void HandleComboInput(ComboInputType input) { + if (IsStunned()) return; // 경직 중 공격 불가 + // 콤보 연계(현재 노드의 트랜지션) 가능 여부 — 연계는 애니메이션 중에도 즉시 캔슬-체인. bool canChain = CanTransitionFromCurrentNode(input); @@ -509,6 +522,7 @@ private bool IsWeaponRequirementMet(ActionData data) private void ExecuteMotionNode(ComboNode root) { + if (IsStunned()) return; // 경직 중 대시/구르기 불가 if (root == null || root.Action == null) return; if (IsMotionOnCooldown(root.Action)) return; @@ -652,6 +666,7 @@ private async Awaitable MotionRoutine(ActionData data, CancellationToken token) private async void PerformGroundPound() { + if (IsStunned()) return; // 경직 중 그라운드 파운드 불가 if (_groundPoundData == null) return; CancelAttack(); @@ -1531,7 +1546,9 @@ private bool TryGetGroundHit(float checkDistance, out RaycastHit2D closestHit) float inset = Mathf.Min(Mathf.Max(_groundCheckRayInset, 0f), bounds.extents.x); float leftX = bounds.min.x + inset; float rightX = bounds.max.x - inset; - float originY = bounds.min.y; + float originLift = bounds.size.y + Mathf.Max(_collisionSkinWidth, 0f) + 0.01f; + float originY = bounds.min.y + originLift; + float rayDistance = checkDistance + originLift; float closestDistance = float.PositiveInfinity; bool hasHit = false; @@ -1541,7 +1558,7 @@ private bool TryGetGroundHit(float checkDistance, out RaycastHit2D closestHit) Vector2 origin = new Vector2(Mathf.Lerp(leftX, rightX, t), originY); _castResults.Clear(); - int hitCount = Physics2D.Raycast(origin, Vector2.down, filter, _castResults, checkDistance); + int hitCount = Physics2D.Raycast(origin, Vector2.down, filter, _castResults, rayDistance); for (int j = 0; j < hitCount; j++) { RaycastHit2D hit = _castResults[j]; @@ -1695,6 +1712,64 @@ private void DrawTimelineBar(ActionData data, float elapsed) GUI.color = Color.white; } + // 외부(보스 스킬 등)에서 플레이어의 지면 접촉 여부를 조회. + public bool IsGrounded => _isGrounded; + + // 외부에서 플레이어를 강제로 띄우거나 밀어낼 때 사용 (지진 스킬 등). + // 넉백 = 지정 속도로 띄우고 넉백 모션으로 경직시킨다. 키네마틱 RB라 속도 직접 설정. + public void Launch(Vector2 velocity) + { + if (_rb == null) return; + _rb.linearVelocity = velocity; + EnterHitstun(_knockbackAnimationState, _knockbackDuration); + } + + // 피격 경직 진행 여부. 경직 중에는 이동/방향전환/점프/공격/대시 입력을 모두 잠근다. + private bool IsStunned() => _isStunned; + + // 피격 경직(범용). 지정 모션을 재생하고 duration초 동안 입력을 잠근다. 재호출 시 갱신(refresh). + // 진행 중이던 플레이어 액션은 취소한다 (피격이 우선). + // duration은 호출자가 모션 클립 길이에 맞춰 넘긴다 (넉백은 _knockbackDuration). + // 일반 경직 / 넉백 등 종류별로 animationState만 바꿔 호출하면 된다. + public void EnterHitstun(string animationState, float duration) + { + _isStunned = true; + _hitstunTimer = duration; // 경직 시간 (재호출 시 갱신/refresh) + + CancelAttack(); // 진행 중이던 공격/모션을 끊는다 (피격이 우선) + CancelMotion(); + + // 경직 모션은 액션 애니메이션 취급 — _isInActionAnimation으로 locomotion이 못 덮게 막는다. + if (_anim != null && !string.IsNullOrEmpty(animationState)) + { + _isInActionAnimation = true; + _activeBaseState = null; + _anim.speed = 1f; + _anim.Play(animationState); + _anim.Update(0f); + } + } + + // 경직 타이머 카운트다운. 클립을 다 재생했고(타이머 종료) + 착지했을 때만 경직을 해제한다. + // 넉백으로 공중에 떠 있는 동안엔 경직/모션을 유지 → 점프 모션으로 안 끊긴다. + private void TickHitstun() + { + if (!_isStunned) return; + + if (_hitstunTimer > 0f) + _hitstunTimer -= Time.fixedDeltaTime; + + Debug.Log($"_hitstunTimer : {_hitstunTimer}, _isGrounded : {_isGrounded} "); + + if (_hitstunTimer <= 0f && _isGrounded && !IsActionActive()) + { + _isStunned = false; + _isInActionAnimation = false; + _activeBaseState = null; + _hitstunTimer = 0f; + } + } + public void TakeDamage(int amount, Vector2 hitVelocity = default, string hitReactionAnimationState = null, Vector2? hitTargetPosition = null, bool correctHitTargetY = false, int hitPositionSolidMask = 0, float hitPositionCorrectionDuration = 0, float hitStunDuration = -1f) { if (_health == null || _health.IsDead) return; diff --git a/Assets/03_Character/ColorMan/Prefabs/BossMan.prefab b/Assets/03_Character/ColorMan/Prefabs/BossMan.prefab index 9b720ef..d63f0bb 100644 --- a/Assets/03_Character/ColorMan/Prefabs/BossMan.prefab +++ b/Assets/03_Character/ColorMan/Prefabs/BossMan.prefab @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e783b1bab5b52965c400c5a50d9cc4d63c14a93a15a660ce9be5ccb11fada4f -size 14680 +oid sha256:7a5e25b7d9c41474c9e3ed2f5dc9b79987a055ecb100f1728d65c3666504e9d8 +size 14830 diff --git a/Assets/03_Character/WhiteMan/Animations/Knockback.anim b/Assets/03_Character/WhiteMan/Animations/Knockback.anim index e3b84d2..13f72d7 100644 --- a/Assets/03_Character/WhiteMan/Animations/Knockback.anim +++ b/Assets/03_Character/WhiteMan/Animations/Knockback.anim @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84ace6584e36b9b56981d4d673be6930ee796c97ff05fef336d62ac9a67d451f -size 2670 +oid sha256:242cce74c8a05b287e473332832bab96932854daf39e964f6737ca0d80c83435 +size 3896 diff --git a/Assets/_Recovery.meta b/Assets/08_Objects/BossSkills/EarthquakeSkill.meta similarity index 77% rename from Assets/_Recovery.meta rename to Assets/08_Objects/BossSkills/EarthquakeSkill.meta index 3556f61..e7930de 100644 --- a/Assets/_Recovery.meta +++ b/Assets/08_Objects/BossSkills/EarthquakeSkill.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 5ca885ea8137440448a536a8e408352c +guid: e8e33019b52d4774fb6be20b28f1c2eb folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/08_Objects/BossSkills/EarthquakeSkill/EarthquakeSkill.prefab b/Assets/08_Objects/BossSkills/EarthquakeSkill/EarthquakeSkill.prefab new file mode 100644 index 0000000..ec425ea --- /dev/null +++ b/Assets/08_Objects/BossSkills/EarthquakeSkill/EarthquakeSkill.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12592007b31b53ee3bc56ad37194d5f856d4267b1508361f91aa63d7660ceb13 +size 1541 diff --git a/Assets/_Recovery/0.unity.meta b/Assets/08_Objects/BossSkills/EarthquakeSkill/EarthquakeSkill.prefab.meta similarity index 63% rename from Assets/_Recovery/0.unity.meta rename to Assets/08_Objects/BossSkills/EarthquakeSkill/EarthquakeSkill.prefab.meta index b20b55d..cf63bf9 100644 --- a/Assets/_Recovery/0.unity.meta +++ b/Assets/08_Objects/BossSkills/EarthquakeSkill/EarthquakeSkill.prefab.meta @@ -1,6 +1,6 @@ fileFormatVersion: 2 -guid: b6e71f45f6a6eb047a72d095e2960e73 -DefaultImporter: +guid: 8206c9420439a754e9e9088c718c0902 +PrefabImporter: externalObjects: {} userData: assetBundleName: diff --git a/Assets/_Recovery/0 (1).unity b/Assets/_Recovery/0 (1).unity deleted file mode 100644 index 11a42c9..0000000 --- a/Assets/_Recovery/0 (1).unity +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5cda17a47a8ded28f04b0cba9af87bbfc817a5821a790ba1e439ebf90932761a -size 115927 diff --git a/Assets/_Recovery/0 (1).unity.meta b/Assets/_Recovery/0 (1).unity.meta deleted file mode 100644 index ee366a5..0000000 --- a/Assets/_Recovery/0 (1).unity.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 509cab3a1e54dbc4c880461706c573ee -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Recovery/0.unity b/Assets/_Recovery/0.unity deleted file mode 100644 index 1bf2152..0000000 --- a/Assets/_Recovery/0.unity +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:03ff4c8c580ae1647bfbcc225c4ff6953addd148672db0830714158f3c0a0f33 -size 79277 diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset index 7658c79..6fc7ffc 100644 --- a/ProjectSettings/PackageManagerSettings.asset +++ b/ProjectSettings/PackageManagerSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7dabba589abe7fc2b45bd3d3fba3fe9be97043492703f716da2d9131c13a807 -size 1003 +oid sha256:c3dfbdd80d0817be1b0e5e832f7c33d4cefa070461267193b0bbfb015091ae47 +size 1048