feat: 젬 반복 시전 로직 및 테스트 프리셋 추가
- SkillGemData에 카테고리, 시전 속도 배율, 추가 반복 횟수 필드를 추가함 - SkillLoadoutEntry가 젬 합산 기준 최종 속도와 반복 횟수를 계산하도록 확장함 - SkillController가 반복 횟수만큼 스킬을 재시전하고 시작 효과와 OnEffect를 매 반복에 다시 적용하도록 수정함 - 연속 젬과 반복 젬 테스트 프리셋을 추가하고 디버그 메뉴에 적용 및 계산 로그 경로를 보강함 - 공격형 테스트 젬 자산과 추가 대미지 이펙트를 정리하고 무젬 35, 반복 젬 70 피해를 검증함
This commit is contained in:
@@ -44,6 +44,7 @@ namespace Colosseum.Editor
|
||||
private const string CrushGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_파쇄.asset";
|
||||
private const string ChallengerGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_도전자.asset";
|
||||
private const string GuardianGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_수호.asset";
|
||||
private const string RepeatGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_연속.asset";
|
||||
private const string EdgeGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_예리함.asset";
|
||||
private const string ImpactGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_충격.asset";
|
||||
private const string BreachGemPath = SkillGemFolderPath + "/Data_SkillGem_Player_관통.asset";
|
||||
@@ -53,6 +54,7 @@ namespace Colosseum.Editor
|
||||
private const string TankGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_탱커_젬테스트.asset";
|
||||
private const string SupportGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_지원_젬테스트.asset";
|
||||
private const string DpsGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_딜러_젬테스트.asset";
|
||||
private const string RepeatGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_반복젬테스트.asset";
|
||||
private const string TankDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_탱커_복합젬테스트.asset";
|
||||
private const string SupportDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_지원_복합젬테스트.asset";
|
||||
private const string DpsDualGemPresetPath = LoadoutPresetFolderPath + "/Data_LoadoutPreset_Player_딜러_복합젬테스트.asset";
|
||||
@@ -522,6 +524,12 @@ namespace Colosseum.Editor
|
||||
ApplyLoadoutPreset(DpsGemPresetPath, "딜러 젬");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Apply Repeat Gem Loadout")]
|
||||
private static void ApplyRepeatGemLoadout()
|
||||
{
|
||||
ApplyLoadoutPreset(RepeatGemPresetPath, "반복 젬");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Apply Tank Dual Gem Loadout")]
|
||||
private static void ApplyTankDualGemLoadout()
|
||||
{
|
||||
@@ -580,48 +588,77 @@ namespace Colosseum.Editor
|
||||
CrushGemPath,
|
||||
"파쇄",
|
||||
"고위력 기술의 단일 피해를 강화하는 테스트용 젬",
|
||||
SkillGemCategory.Attack,
|
||||
1.15f,
|
||||
1.1f,
|
||||
1f,
|
||||
0,
|
||||
damageEffect);
|
||||
|
||||
CreateOrUpdateGemAsset(
|
||||
ChallengerGemPath,
|
||||
"도전자",
|
||||
"고위력 기술에 위협 선점 기능을 얹는 테스트용 젬",
|
||||
SkillGemCategory.Threat,
|
||||
1f,
|
||||
1f,
|
||||
1f,
|
||||
0,
|
||||
tauntEffect);
|
||||
|
||||
CreateOrUpdateGemAsset(
|
||||
GuardianGemPath,
|
||||
"수호",
|
||||
"고위력 기술에 보호막 보조를 얹는 테스트용 젬",
|
||||
SkillGemCategory.Support,
|
||||
1.05f,
|
||||
1.1f,
|
||||
1f,
|
||||
0,
|
||||
shieldEffect);
|
||||
|
||||
CreateOrUpdateGemAsset(
|
||||
RepeatGemPath,
|
||||
"연속",
|
||||
"붙은 스킬을 한 번 더 반복 시전하는 테스트용 젬",
|
||||
SkillGemCategory.Efficiency,
|
||||
1.2f,
|
||||
1.15f,
|
||||
1.1f,
|
||||
1,
|
||||
null);
|
||||
|
||||
CreateOrUpdateGemAsset(
|
||||
EdgeGemPath,
|
||||
"예리함",
|
||||
"고정 추가 피해를 부여하는 테스트용 공격 젬",
|
||||
SkillGemCategory.Attack,
|
||||
1f,
|
||||
1f,
|
||||
1f,
|
||||
0,
|
||||
edgeDamageEffect);
|
||||
|
||||
CreateOrUpdateGemAsset(
|
||||
ImpactGemPath,
|
||||
"충격",
|
||||
"중간 고정 추가 피해를 부여하는 테스트용 공격 젬",
|
||||
SkillGemCategory.Attack,
|
||||
1f,
|
||||
1f,
|
||||
1f,
|
||||
0,
|
||||
impactDamageEffect);
|
||||
|
||||
CreateOrUpdateGemAsset(
|
||||
BreachGemPath,
|
||||
"관통",
|
||||
"높은 고정 추가 피해를 부여하는 테스트용 공격 젬",
|
||||
SkillGemCategory.Attack,
|
||||
1f,
|
||||
1f,
|
||||
1f,
|
||||
0,
|
||||
breachDamageEffect);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
@@ -653,6 +690,7 @@ namespace Colosseum.Editor
|
||||
SkillGemData crushGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(CrushGemPath);
|
||||
SkillGemData challengerGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ChallengerGemPath);
|
||||
SkillGemData guardianGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(GuardianGemPath);
|
||||
SkillGemData repeatGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(RepeatGemPath);
|
||||
SkillGemData edgeGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(EdgeGemPath);
|
||||
SkillGemData impactGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(ImpactGemPath);
|
||||
SkillGemData breachGem = AssetDatabase.LoadAssetAtPath<SkillGemData>(BreachGemPath);
|
||||
@@ -698,6 +736,19 @@ namespace Colosseum.Editor
|
||||
CreateEntry(gemTestSkill, crushGem),
|
||||
CreateEntry(evadeSkill)));
|
||||
|
||||
CreateOrUpdatePresetAsset(
|
||||
RepeatGemPresetPath,
|
||||
"반복 젬 테스트",
|
||||
"연속 젬을 사용하는 반복 시전 검증 프리셋",
|
||||
CreateLoadoutEntries(
|
||||
CreateEntry(slashSkill),
|
||||
CreateEntry(pierceSkill),
|
||||
CreateEntry(spinSkill),
|
||||
CreateEntry(dashSkill),
|
||||
CreateEntry(projectileSkill),
|
||||
CreateEntry(gemTestSkill, repeatGem),
|
||||
CreateEntry(evadeSkill)));
|
||||
|
||||
CreateOrUpdatePresetAsset(
|
||||
TankDualGemPresetPath,
|
||||
"탱커 복합 젬 테스트",
|
||||
@@ -854,6 +905,8 @@ namespace Colosseum.Editor
|
||||
|
||||
float resolvedManaCost = loadoutEntry.GetResolvedManaCost();
|
||||
float resolvedCooldown = loadoutEntry.GetResolvedCooldown();
|
||||
float resolvedAnimationSpeed = loadoutEntry.GetResolvedAnimationSpeed();
|
||||
int resolvedRepeatCount = loadoutEntry.GetResolvedRepeatCount();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("[Debug] 6번 슬롯 계산값 | ");
|
||||
@@ -863,8 +916,13 @@ namespace Colosseum.Editor
|
||||
builder.Append(resolvedManaCost.ToString("0.###"));
|
||||
builder.Append(" | Cooldown=");
|
||||
builder.Append(resolvedCooldown.ToString("0.###"));
|
||||
builder.Append(" | Speed=");
|
||||
builder.Append(resolvedAnimationSpeed.ToString("0.###"));
|
||||
builder.Append(" | Repeat=");
|
||||
builder.Append(resolvedRepeatCount);
|
||||
builder.Append(" | GemSlots=");
|
||||
builder.Append(loadoutEntry.SocketedGems.Count);
|
||||
AppendGemCategorySummary(builder, loadoutEntry);
|
||||
|
||||
Debug.Log(builder.ToString());
|
||||
}
|
||||
@@ -1098,7 +1156,16 @@ namespace Colosseum.Editor
|
||||
EditorUtility.SetDirty(skill);
|
||||
}
|
||||
|
||||
private static void CreateOrUpdateGemAsset(string assetPath, string gemName, string description, float manaCostMultiplier, float cooldownMultiplier, SkillEffect triggeredEffect)
|
||||
private static void CreateOrUpdateGemAsset(
|
||||
string assetPath,
|
||||
string gemName,
|
||||
string description,
|
||||
SkillGemCategory category,
|
||||
float manaCostMultiplier,
|
||||
float cooldownMultiplier,
|
||||
float castSpeedMultiplier,
|
||||
int additionalRepeatCount,
|
||||
SkillEffect triggeredEffect)
|
||||
{
|
||||
SkillGemData gem = AssetDatabase.LoadAssetAtPath<SkillGemData>(assetPath);
|
||||
if (gem == null)
|
||||
@@ -1115,8 +1182,11 @@ namespace Colosseum.Editor
|
||||
SerializedObject serializedGem = new SerializedObject(gem);
|
||||
serializedGem.FindProperty("gemName").stringValue = gemName;
|
||||
serializedGem.FindProperty("description").stringValue = description;
|
||||
serializedGem.FindProperty("category").enumValueIndex = (int)category;
|
||||
serializedGem.FindProperty("manaCostMultiplier").floatValue = manaCostMultiplier;
|
||||
serializedGem.FindProperty("cooldownMultiplier").floatValue = cooldownMultiplier;
|
||||
serializedGem.FindProperty("castSpeedMultiplier").floatValue = castSpeedMultiplier;
|
||||
serializedGem.FindProperty("additionalRepeatCount").intValue = additionalRepeatCount;
|
||||
|
||||
SerializedProperty castStartEffectsProperty = serializedGem.FindProperty("castStartEffects");
|
||||
castStartEffectsProperty.arraySize = 0;
|
||||
@@ -1253,6 +1323,33 @@ namespace Colosseum.Editor
|
||||
builder.Append("]");
|
||||
}
|
||||
|
||||
private static void AppendGemCategorySummary(StringBuilder builder, SkillLoadoutEntry loadoutEntry)
|
||||
{
|
||||
if (builder == null || loadoutEntry == null || loadoutEntry.SocketedGems == null)
|
||||
return;
|
||||
|
||||
bool hasGem = false;
|
||||
StringBuilder categoryBuilder = new StringBuilder();
|
||||
for (int i = 0; i < loadoutEntry.SocketedGems.Count; i++)
|
||||
{
|
||||
SkillGemData gem = loadoutEntry.SocketedGems[i];
|
||||
if (gem == null)
|
||||
continue;
|
||||
|
||||
if (hasGem)
|
||||
categoryBuilder.Append(", ");
|
||||
|
||||
categoryBuilder.Append(gem.Category);
|
||||
hasGem = true;
|
||||
}
|
||||
|
||||
if (!hasGem)
|
||||
return;
|
||||
|
||||
builder.Append(" | Category=");
|
||||
builder.Append(categoryBuilder);
|
||||
}
|
||||
|
||||
private static void CastOwnedPlayerSkillAsServer(ulong ownerClientId, int slotIndex)
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
|
||||
@@ -56,14 +56,15 @@ namespace Colosseum.Skills
|
||||
private SkillLoadoutEntry currentLoadoutEntry;
|
||||
private readonly List<SkillEffect> currentCastStartEffects = new();
|
||||
private readonly Dictionary<int, List<SkillEffect>> currentTriggeredEffects = new();
|
||||
private bool skillEndRequested; // OnSkillEnd 이벤트 호출 여부
|
||||
private bool waitingForEndAnimation; // EndAnimation 종료 대기 중
|
||||
private int currentRepeatCount = 1;
|
||||
private int currentIterationIndex = 0;
|
||||
|
||||
// 쿨타임 추적
|
||||
private Dictionary<SkillData, float> cooldownTracker = new Dictionary<SkillData, float>();
|
||||
|
||||
|
||||
public bool IsExecutingSkill => currentSkill != null && !skillEndRequested;
|
||||
public bool IsExecutingSkill => currentSkill != null;
|
||||
public bool IsPlayingAnimation => currentSkill != null;
|
||||
public bool IsInEndAnimation => waitingForEndAnimation;
|
||||
public bool UsesRootMotion => currentSkill != null && currentSkill.UseRootMotion;
|
||||
@@ -105,7 +106,7 @@ namespace Colosseum.Skills
|
||||
{
|
||||
if (debugMode) Debug.Log($"[Skill] EndAnimation complete: {currentSkill.SkillName}");
|
||||
RestoreBaseController();
|
||||
currentSkill = null;
|
||||
ClearCurrentSkillState();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -113,6 +114,9 @@ namespace Colosseum.Skills
|
||||
// 애니메이션 종료 시 처리 (OnSkillEnd 여부와 관계없이 애니메이션 끝까지 재생)
|
||||
if (stateInfo.normalizedTime >= 1f)
|
||||
{
|
||||
if (TryStartNextIteration())
|
||||
return;
|
||||
|
||||
if (currentSkill.EndClip != null)
|
||||
{
|
||||
// EndAnimation 재생 후 종료 대기
|
||||
@@ -125,7 +129,7 @@ namespace Colosseum.Skills
|
||||
// EndAnimation 없으면 바로 종료
|
||||
if (debugMode) Debug.Log($"[Skill] Animation complete: {currentSkill.SkillName}");
|
||||
RestoreBaseController();
|
||||
currentSkill = null;
|
||||
ClearCurrentSkillState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,26 +176,18 @@ namespace Colosseum.Skills
|
||||
|
||||
currentLoadoutEntry = loadoutEntry != null ? loadoutEntry.CreateCopy() : SkillLoadoutEntry.CreateTemporary(skill);
|
||||
currentSkill = skill;
|
||||
skillEndRequested = false;
|
||||
waitingForEndAnimation = false;
|
||||
lastCancelReason = SkillCancelReason.None;
|
||||
BuildResolvedEffects(currentLoadoutEntry);
|
||||
currentRepeatCount = currentLoadoutEntry.GetResolvedRepeatCount();
|
||||
currentIterationIndex = 0;
|
||||
|
||||
if (debugMode) Debug.Log($"[Skill] Cast: {skill.SkillName}");
|
||||
|
||||
// 쿨타임 시작
|
||||
StartCooldown(skill, currentLoadoutEntry.GetResolvedCooldown());
|
||||
|
||||
TriggerCastStartEffects();
|
||||
|
||||
// 스킬 애니메이션 재생
|
||||
if (skill.SkillClip != null && animator != null)
|
||||
{
|
||||
animator.speed = skill.AnimationSpeed;
|
||||
PlaySkillClip(skill.SkillClip);
|
||||
}
|
||||
|
||||
TriggerImmediateSelfEffectsIfNeeded();
|
||||
StartCurrentIteration();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -263,6 +259,51 @@ namespace Colosseum.Skills
|
||||
loadoutEntry.CollectTriggeredEffects(currentTriggeredEffects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 스킬의 반복 차수 하나를 시작합니다.
|
||||
/// </summary>
|
||||
private void StartCurrentIteration()
|
||||
{
|
||||
if (currentSkill == null)
|
||||
return;
|
||||
|
||||
currentIterationIndex++;
|
||||
waitingForEndAnimation = false;
|
||||
|
||||
if (debugMode && currentRepeatCount > 1)
|
||||
{
|
||||
Debug.Log($"[Skill] Iteration {currentIterationIndex}/{currentRepeatCount}: {currentSkill.SkillName}");
|
||||
}
|
||||
|
||||
TriggerCastStartEffects();
|
||||
|
||||
if (currentSkill.SkillClip != null && animator != null)
|
||||
{
|
||||
float resolvedAnimationSpeed = currentLoadoutEntry != null
|
||||
? currentLoadoutEntry.GetResolvedAnimationSpeed()
|
||||
: currentSkill.AnimationSpeed;
|
||||
animator.speed = resolvedAnimationSpeed;
|
||||
PlaySkillClip(currentSkill.SkillClip);
|
||||
}
|
||||
|
||||
TriggerImmediateSelfEffectsIfNeeded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 반복 시전이 남아 있으면 다음 차수를 시작합니다.
|
||||
/// </summary>
|
||||
private bool TryStartNextIteration()
|
||||
{
|
||||
if (currentSkill == null)
|
||||
return false;
|
||||
|
||||
if (currentIterationIndex >= currentRepeatCount)
|
||||
return false;
|
||||
|
||||
StartCurrentIteration();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스킬 클립으로 Override Controller 생성 후 재생
|
||||
/// </summary>
|
||||
@@ -426,9 +467,7 @@ namespace Colosseum.Skills
|
||||
return;
|
||||
}
|
||||
|
||||
skillEndRequested = true;
|
||||
|
||||
if (debugMode) Debug.Log($"[Skill] End requested: {currentSkill.SkillName} (will complete after animation)");
|
||||
if (debugMode) Debug.Log($"[Skill] End event received: {currentSkill.SkillName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -445,12 +484,7 @@ namespace Colosseum.Skills
|
||||
Debug.Log($"[Skill] Cancelled: {currentSkill.SkillName} / reason={reason}");
|
||||
|
||||
RestoreBaseController();
|
||||
currentSkill = null;
|
||||
currentLoadoutEntry = null;
|
||||
currentCastStartEffects.Clear();
|
||||
currentTriggeredEffects.Clear();
|
||||
skillEndRequested = false;
|
||||
waitingForEndAnimation = false;
|
||||
ClearCurrentSkillState();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -485,5 +519,19 @@ namespace Colosseum.Skills
|
||||
{
|
||||
cooldownTracker.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 실행 중인 스킬 상태를 정리합니다.
|
||||
/// </summary>
|
||||
private void ClearCurrentSkillState()
|
||||
{
|
||||
currentSkill = null;
|
||||
currentLoadoutEntry = null;
|
||||
currentCastStartEffects.Clear();
|
||||
currentTriggeredEffects.Clear();
|
||||
waitingForEndAnimation = false;
|
||||
currentRepeatCount = 1;
|
||||
currentIterationIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,20 @@ using UnityEngine;
|
||||
|
||||
namespace Colosseum.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 젬의 주 역할 분류입니다.
|
||||
/// </summary>
|
||||
public enum SkillGemCategory
|
||||
{
|
||||
Common,
|
||||
Attack,
|
||||
Threat,
|
||||
Defense,
|
||||
Support,
|
||||
Control,
|
||||
Efficiency,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 젬 효과가 발동될 애니메이션 이벤트 인덱스와 효과 목록입니다.
|
||||
/// </summary>
|
||||
@@ -31,12 +45,18 @@ namespace Colosseum.Skills
|
||||
[TextArea(2, 4)]
|
||||
[SerializeField] private string description;
|
||||
[SerializeField] private Sprite icon;
|
||||
[Tooltip("젬의 주 역할 분류")]
|
||||
[SerializeField] private SkillGemCategory category = SkillGemCategory.Common;
|
||||
|
||||
[Header("기본 수치 보정")]
|
||||
[Tooltip("장착 시 마나 비용 배율")]
|
||||
[Min(0f)] [SerializeField] private float manaCostMultiplier = 1f;
|
||||
[Tooltip("장착 시 쿨타임 배율")]
|
||||
[Min(0f)] [SerializeField] private float cooldownMultiplier = 1f;
|
||||
[Tooltip("장착 시 스킬 애니메이션 재생 속도 배율")]
|
||||
[Min(0.1f)] [SerializeField] private float castSpeedMultiplier = 1f;
|
||||
[Tooltip("기반 스킬 시전을 몇 회 더 반복할지 정의합니다. 현재는 계산/표시용으로만 사용됩니다.")]
|
||||
[Min(0)] [SerializeField] private int additionalRepeatCount = 0;
|
||||
|
||||
[Header("추가 효과")]
|
||||
[Tooltip("시전 시작 시 즉시 발동하는 추가 효과")]
|
||||
@@ -47,8 +67,11 @@ namespace Colosseum.Skills
|
||||
public string GemName => gemName;
|
||||
public string Description => description;
|
||||
public Sprite Icon => icon;
|
||||
public SkillGemCategory Category => category;
|
||||
public float ManaCostMultiplier => manaCostMultiplier;
|
||||
public float CooldownMultiplier => cooldownMultiplier;
|
||||
public float CastSpeedMultiplier => castSpeedMultiplier;
|
||||
public int AdditionalRepeatCount => additionalRepeatCount;
|
||||
public IReadOnlyList<SkillEffect> CastStartEffects => castStartEffects;
|
||||
public IReadOnlyList<SkillGemTriggeredEffectEntry> TriggeredEffects => triggeredEffects;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,48 @@ namespace Colosseum.Skills
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public float GetResolvedAnimationSpeed()
|
||||
{
|
||||
if (baseSkill == null)
|
||||
return 0f;
|
||||
|
||||
float resolved = baseSkill.AnimationSpeed;
|
||||
if (socketedGems == null)
|
||||
return resolved;
|
||||
|
||||
for (int i = 0; i < socketedGems.Length; i++)
|
||||
{
|
||||
SkillGemData gem = socketedGems[i];
|
||||
if (gem == null)
|
||||
continue;
|
||||
|
||||
resolved *= gem.CastSpeedMultiplier;
|
||||
}
|
||||
|
||||
return Mathf.Max(0.05f, resolved);
|
||||
}
|
||||
|
||||
public int GetResolvedRepeatCount()
|
||||
{
|
||||
if (baseSkill == null)
|
||||
return 0;
|
||||
|
||||
int resolved = 1;
|
||||
if (socketedGems == null)
|
||||
return resolved;
|
||||
|
||||
for (int i = 0; i < socketedGems.Length; i++)
|
||||
{
|
||||
SkillGemData gem = socketedGems[i];
|
||||
if (gem == null)
|
||||
continue;
|
||||
|
||||
resolved += gem.AdditionalRepeatCount;
|
||||
}
|
||||
|
||||
return Mathf.Max(1, resolved);
|
||||
}
|
||||
|
||||
public void CollectCastStartEffects(List<SkillEffect> destination)
|
||||
{
|
||||
if (destination == null)
|
||||
|
||||
Reference in New Issue
Block a user