feat: 빈사/부활 시스템 및 ReviveEffect 추가
- PlayerNetworkController에 Revive(healthPercent) 메서드와 OnRevived 이벤트 추가 - ReviveEffect 스킬 이펙트 구현 (Area/Ally 타겟팅, healthPercent로 체력 복구) - GameManager가 OnRevived 구독하여 alivePlayers 동적 복구 - GameManager.Update에서 나중에 스폰된 플레이어 자동 구독 (MPP 대응) - SkillCancelReason에 Revive 추가 - 부활 스킬/이펙트 ScriptableObject 에셋 생성 (치유 클립 임시 사용) - PlayerSkillDebugMenu에 즉사/부활/리스폰/스폰/Client1 스킬 디버그 메뉴 추가 - PlayerAbnormalityDebugHUD에 부활 버튼 추가 - DebugExecuteSkillAsServer에 실패 원인 로그 추가 - AGENTS.md에 코드 변경 후 force reload 규칙 추가
This commit is contained in:
@@ -131,6 +131,11 @@ namespace Colosseum.Player
|
||||
{
|
||||
RequestRespawnRpc();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("부활", GUILayout.Height(24f)))
|
||||
{
|
||||
RequestReviveRpc();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(6f);
|
||||
@@ -409,6 +414,21 @@ namespace Colosseum.Player
|
||||
networkController.Respawn();
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
private void RequestReviveRpc()
|
||||
{
|
||||
if (networkController == null)
|
||||
return;
|
||||
|
||||
if (!networkController.IsDead)
|
||||
{
|
||||
Debug.LogWarning("[AbnormalityDebugHUD] 부활: 사망 상태가 아닙니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
networkController.Revive(0.3f);
|
||||
}
|
||||
|
||||
private bool ShouldEnableDebugHud()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@@ -83,6 +83,7 @@ namespace Colosseum.Player
|
||||
public event Action<PlayerNetworkController> OnDeath;
|
||||
public event Action<bool> OnDeathStateChanged;
|
||||
public event Action<PlayerNetworkController> OnRespawned;
|
||||
public event Action<PlayerNetworkController> OnRevived;
|
||||
public event Action OnPassiveSelectionChanged;
|
||||
|
||||
public float CurrentHealth => currentHealth.Value;
|
||||
@@ -249,7 +250,8 @@ namespace Colosseum.Player
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 사망 처리 (서버에서만 실행)
|
||||
/// 사망(빈사) 처리 (서버에서만 실행).
|
||||
/// HP가 0 이하가 되면 호출되며, 부활 스킬로 복귀 가능한 빈사 상태로 전환합니다.
|
||||
/// </summary>
|
||||
private void HandleDeath()
|
||||
{
|
||||
@@ -362,6 +364,36 @@ namespace Colosseum.Player
|
||||
Debug.Log($"[Player] Player {OwnerClientId} respawned!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 빈사 상태에서 부활 (서버에서만 실행)
|
||||
/// </summary>
|
||||
/// <param name="healthPercent">부활 시 체력 비율 (0~1)</param>
|
||||
public void Revive(float healthPercent = 0.3f)
|
||||
{
|
||||
if (!IsServer || !isDead.Value)
|
||||
return;
|
||||
|
||||
isDead.Value = false;
|
||||
float revivedHealth = Mathf.Max(1f, MaxHealth * Mathf.Clamp01(healthPercent));
|
||||
currentHealth.Value = revivedHealth;
|
||||
|
||||
PlayerMovement movement = GetComponent<PlayerMovement>();
|
||||
if (movement != null)
|
||||
{
|
||||
movement.enabled = true;
|
||||
}
|
||||
|
||||
PlayerSkillInput skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillInput != null)
|
||||
{
|
||||
skillInput.enabled = true;
|
||||
}
|
||||
|
||||
OnRevived?.Invoke(this);
|
||||
|
||||
Debug.Log($"[Player] Player {OwnerClientId} revived! HP={revivedHealth:F0}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서버 기준으로 패시브 프리셋을 적용합니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Colosseum.Player
|
||||
"Assets/_Game/Data/Skills/Data_Skill_Player_보호막.asset",
|
||||
"Assets/_Game/Data/Skills/Data_Skill_Player_방어태세.asset",
|
||||
"Assets/_Game/Data/Skills/Data_Skill_Player_투사체.asset",
|
||||
"Assets/_Game/Data/Skills/Data_Skill_Player_구르기.asset",
|
||||
"Assets/_Game/Data/Skills/Data_Skill_Player_부활.asset",
|
||||
};
|
||||
|
||||
private static readonly string[] DpsLoadoutPaths =
|
||||
@@ -566,27 +566,48 @@ namespace Colosseum.Player
|
||||
public bool DebugExecuteSkillAsServer(int slotIndex)
|
||||
{
|
||||
if (!IsServer)
|
||||
{
|
||||
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: 서버가 아님 (IsServer=false)");
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureRuntimeReferences();
|
||||
|
||||
if (slotIndex < 0 || slotIndex >= skillSlots.Length)
|
||||
{
|
||||
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: 슬롯 인덱스 범위 초과 (slotIndex={slotIndex}, length={skillSlots.Length})");
|
||||
return false;
|
||||
}
|
||||
|
||||
SkillLoadoutEntry loadoutEntry = GetSkillLoadout(slotIndex);
|
||||
SkillData skill = loadoutEntry != null ? loadoutEntry.BaseSkill : null;
|
||||
if (skill == null)
|
||||
{
|
||||
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: 슬롯 {slotIndex}이 비어있음");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actionState != null && !actionState.CanStartSkill(skill))
|
||||
{
|
||||
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: CanStartSkill=false (IsDead={actionState.IsDead}, CanUseSkills={actionState.CanUseSkills})");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skillController == null || skillController.IsExecutingSkill || skillController.IsOnCooldown(skill))
|
||||
{
|
||||
string reason = skillController == null ? "skillController=null" :
|
||||
skillController.IsExecutingSkill ? "스킬 실행 중" :
|
||||
skillController.IsOnCooldown(skill) ? "쿨다운 중" : "";
|
||||
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: {reason}");
|
||||
return false;
|
||||
}
|
||||
|
||||
float actualManaCost = GetActualManaCost(loadoutEntry);
|
||||
if (networkController != null && networkController.Mana < actualManaCost)
|
||||
{
|
||||
Debug.LogWarning($"[Debug] DebugExecuteSkillAsServer 실패: MP 부족 (need={actualManaCost:F1}, have={networkController.Mana:F1})");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (networkController != null && actualManaCost > 0f)
|
||||
{
|
||||
@@ -594,6 +615,7 @@ namespace Colosseum.Player
|
||||
}
|
||||
|
||||
BroadcastSkillExecutionRpc(slotIndex);
|
||||
Debug.Log($"[Debug] DebugExecuteSkillAsServer 성공: Slot={slotIndex}, Skill={skill.SkillName}");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user