1143 lines
46 KiB
C#
1143 lines
46 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
|
||
[ExecuteAlways]
|
||
/// <summary>
|
||
/// AllyCombatant �������Ѿ�������ʱ GameObject������Ϊ ally_01..ally_05����
|
||
/// ʵ�� ICombatant �ӿ��Թ� EffectSystem ���á�
|
||
/// - �� teamUIController ��ȡ UI ���ò�ͬ����ʾѪ��/����
|
||
/// - ����ʱ�� TeamCharacterDataInfo �� AllyHero_SO ����������/�����Ȼ�������
|
||
/// - ��ʼ����ֵΪ�������ֵ����ʼ����Ϊ 0
|
||
/// </summary>
|
||
public class AllyCombatant : MonoBehaviour, ICombatant
|
||
{
|
||
public int slotIndex = 0; // 0..4 ��Ӧ ally_01..ally_05
|
||
|
||
// runtime stats
|
||
public int maxHP = 100;
|
||
public int currentHP = 100;
|
||
public int maxMana = 100;
|
||
public int currentMana = 0;
|
||
public float damageResistance = 0f; // 0..1
|
||
public float scoreEfficiency = 1f;
|
||
|
||
private bool isDead = false; // new: dead flag
|
||
public BeatmapManager bmm;
|
||
|
||
// add attack stat to be available at runtime
|
||
[Tooltip("����������������ʱʹ�ã������ AllyHero_SO.levelStats[0].attack ��䣨������ڣ�")]
|
||
public int attack = 0;
|
||
|
||
// scoring fields
|
||
[Header("Scoring")]
|
||
[Tooltip("Base score value for a perfect hit on this track")]
|
||
public int baseTrackScore = 1000;
|
||
|
||
// Editable multipliers for each judge quality (exposed for designers)
|
||
[Tooltip("Score multiplier for Perfect (default 1.0)")]
|
||
public float perfectRatio = 1f;
|
||
[Tooltip("Score multiplier for Great (e.g. 0.75)")]
|
||
public float greatRatio = 0.75f;
|
||
[Tooltip("Score multiplier for Good (e.g. 0.5)")]
|
||
public float goodRatio = 0.5f;
|
||
[Tooltip("Score multiplier for Miss (e.g. 0)")]
|
||
public float missRatio = 0f;
|
||
|
||
[Tooltip("��ɫ��ǰ����÷֣��ۼӣ�����ʾ�� UI �� currentScore �ı���")]
|
||
public int currentScore = 0;
|
||
|
||
[Tooltip("����������ޣ�����Ϊ int.MaxValue - 1")]
|
||
public int maxTrackScore = int.MaxValue - 1;
|
||
|
||
// When true, InitializeStatsFromData() will overwrite inspector values on Start.
|
||
// Set to false if you want to keep manual inspector values when entering Play mode.
|
||
public bool allowOverwriteFromSO = true;
|
||
|
||
// UI refs (resolved from teamUIController)
|
||
private Image characterImage;
|
||
private Image healthImage;
|
||
private Image fadeHealthImage;
|
||
private Image manaImage;
|
||
private Image fadeManaImage;
|
||
private TextMeshProUGUI nameText;
|
||
private TextMeshProUGUI healthRateText;
|
||
private TextMeshProUGUI currentScoreText;
|
||
private Image hurtRedImage;
|
||
|
||
private Coroutine fadeHealthCoroutine;
|
||
private Coroutine fadeManaCoroutine;
|
||
private List<Buff> activeBuffs = new List<Buff>();
|
||
|
||
// Track skills that are currently active because HP percent condition is satisfied.
|
||
private HashSet<string> _activeHpPercentSkills = new HashSet<string>();
|
||
|
||
// Track applied reversible effects for HP-percent skills so we can revert them on leave
|
||
private Dictionary<string, AppliedEffect> _appliedHpPercentEffects = new Dictionary<string, AppliedEffect>();
|
||
|
||
private class AppliedEffect
|
||
{
|
||
public EffectType effectType;
|
||
public float amount;
|
||
public AppliedEffect(EffectType t, float a) { effectType = t; amount = a; }
|
||
}
|
||
|
||
private void Awake()
|
||
{
|
||
// ensure GameObject name matches pattern so EffectSystem can find it
|
||
gameObject.name = $"ally_0{Mathf.Clamp(slotIndex + 1, 1, 5)}";
|
||
if (_activeHpPercentSkills == null) _activeHpPercentSkills = new HashSet<string>();
|
||
if (_appliedHpPercentEffects == null) _appliedHpPercentEffects = new Dictionary<string, AppliedEffect>();
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// Resolve UI and optionally pull data from SO
|
||
ResolveUIReferences();
|
||
if (allowOverwriteFromSO)
|
||
InitializeStatsFromData();
|
||
|
||
// �ؼ������볡ʱӦ��Ѫ/0 ����
|
||
// ֮ǰ����ֻ�� Clamp���ᱣ�� prefab/������ inspector �ϵľ� currentHP�������볡����Ѫ��
|
||
isDead = false;
|
||
currentHP = Mathf.Max(1, maxHP);
|
||
currentMana = 0;
|
||
|
||
UpdateUIImmediate();
|
||
// Ensure score UI shows initial value
|
||
UpdateScoreUI();
|
||
|
||
// Evaluate HP-percent triggers on start after a short delay to ensure singletons are ready
|
||
if (Application.isPlaying)
|
||
{
|
||
StartCoroutine(InitialTriggerCheckCoroutine());
|
||
}
|
||
}
|
||
|
||
private IEnumerator InitialTriggerCheckCoroutine()
|
||
{
|
||
// Wait for singletons to be ready
|
||
while (SkillBuilder.Instance == null || teamUIController.Instance == null)
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
// Wait until teamUIController has finished loading ally SOs if they are async
|
||
// or just wait a couple of frames to be sure
|
||
yield return null;
|
||
yield return null;
|
||
|
||
// Re-run initialization to ensure we have the correct MaxHP and skills from the loaded team
|
||
if (allowOverwriteFromSO)
|
||
{
|
||
InitializeStatsFromData();
|
||
currentHP = Mathf.Max(1, maxHP);
|
||
UpdateUIImmediate();
|
||
}
|
||
|
||
// Final wait to ensure SkillBuilder's internal mapping is ready
|
||
while (SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex) == null)
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
// Perform the initial HP-percentage trigger check (oldHP = currentHP to trigger "ENTER" if condition met)
|
||
EvaluateHPPercentageTriggers(currentHP);
|
||
}
|
||
|
||
// Allow editing in inspector to immediately reflect on UI (editor only)
|
||
private void OnValidate()
|
||
{
|
||
// Avoid heavy editor-only operations, but resolve UI references so inspector changes show
|
||
ResolveUIReferences();
|
||
// Clamp values to valid ranges
|
||
if (maxHP < 1) maxHP = 1;
|
||
if (maxMana < 1) maxMana = 1;
|
||
currentHP = Mathf.Clamp(currentHP, 0, maxHP);
|
||
currentMana = Mathf.Clamp(currentMana, 0, maxMana);
|
||
|
||
// Do not start coroutines from OnValidate - update immediately
|
||
UpdateUIImmediate();
|
||
|
||
// If user adjusts currentHP or currentMana in inspector while in Play mode, attempt to trigger behaviors
|
||
if (Application.isPlaying && SkillBuilder.Instance != null)
|
||
{
|
||
EvaluateHPPercentageTriggers(currentHP);
|
||
TryCastOnFullMana();
|
||
}
|
||
}
|
||
|
||
private void ResolveUIReferences()
|
||
{
|
||
// Try singleton first, fallback to scene search (works in editor)
|
||
var ui = teamUIController.Instance ?? FindObjectOfType<teamUIController>();
|
||
if (ui == null) return;
|
||
switch (slotIndex)
|
||
{
|
||
case 0:
|
||
characterImage = ui.teammate01_characterImage;
|
||
healthImage = ui.teammate01_healthImage;
|
||
fadeHealthImage = ui.teammate01_fadehealthImage;
|
||
manaImage = ui.teammate01_manaImage;
|
||
fadeManaImage = ui.teammate01_fadeManaImage;
|
||
nameText = ui.teammate01_nameText;
|
||
healthRateText = ui.teammate01_healthRate;
|
||
currentScoreText = ui.teammate01_current_scoreText;
|
||
hurtRedImage = ui.teammate01_hurtRedImage;
|
||
break;
|
||
case 1:
|
||
characterImage = ui.teammate02_characterImage;
|
||
healthImage = ui.teammate02_healthImage;
|
||
fadeHealthImage = ui.teammate02_fadehealthImage;
|
||
manaImage = ui.teammate02_manaImage;
|
||
fadeManaImage = ui.teammate02_fadeManaImage;
|
||
nameText = ui.teammate02_nameText;
|
||
healthRateText = ui.teammate02_healthRate;
|
||
currentScoreText = ui.teammate02_current_scoreText;
|
||
hurtRedImage = ui.teammate02_hurtRedImage;
|
||
break;
|
||
case 2:
|
||
characterImage = ui.teammate03_characterImage;
|
||
healthImage = ui.teammate03_healthImage;
|
||
fadeHealthImage = ui.teammate03_fadehealthImage;
|
||
manaImage = ui.teammate03_manaImage;
|
||
fadeManaImage = ui.teammate03_fadeManaImage;
|
||
nameText = ui.teammate03_nameText;
|
||
healthRateText = ui.teammate03_healthRate;
|
||
currentScoreText = ui.teammate03_current_scoreText;
|
||
hurtRedImage = ui.teammate03_hurtRedImage;
|
||
break;
|
||
case 3:
|
||
characterImage = ui.teammate04_characterImage;
|
||
healthImage = ui.teammate04_healthImage;
|
||
fadeHealthImage = ui.teammate04_fadehealthImage;
|
||
manaImage = ui.teammate04_manaImage;
|
||
fadeManaImage = ui.teammate04_fadeManaImage;
|
||
nameText = ui.teammate04_nameText;
|
||
healthRateText = ui.teammate04_healthRate;
|
||
currentScoreText = ui.teammate04_current_scoreText;
|
||
hurtRedImage = ui.teammate04_hurtRedImage;
|
||
break;
|
||
case 4:
|
||
characterImage = ui.teammate05_characterImage;
|
||
healthImage = ui.teammate05_healthImage;
|
||
fadeHealthImage = ui.teammate05_fadehealthImage;
|
||
manaImage = ui.teammate05_manaImage;
|
||
fadeManaImage = ui.teammate05_fadeManaImage;
|
||
nameText = ui.teammate05_nameText;
|
||
healthRateText = ui.teammate05_healthRate;
|
||
currentScoreText = ui.teammate05_current_scoreText;
|
||
hurtRedImage = ui.teammate05_hurtRedImage;
|
||
break;
|
||
}
|
||
|
||
// If the assigned TMP field is null, attempt to resolve from the UI parent's object (teamUIController keeps objectFather references)
|
||
if (currentScoreText == null)
|
||
{
|
||
GameObject parent = null;
|
||
switch (slotIndex)
|
||
{
|
||
case 0: parent = ui.objectFather_ally01; break;
|
||
case 1: parent = ui.objectFather_ally02; break;
|
||
case 2: parent = ui.objectFather_ally03; break;
|
||
case 3: parent = ui.objectFather_ally04; break;
|
||
case 4: parent = ui.objectFather_ally05; break;
|
||
}
|
||
if (parent != null)
|
||
{
|
||
var tmp = parent.GetComponentInChildren<TextMeshProUGUI>(true);
|
||
if (tmp != null)
|
||
{
|
||
currentScoreText = tmp;
|
||
// also assign back to teamUIController so other systems can find it
|
||
switch (slotIndex)
|
||
{
|
||
case 0: ui.teammate01_current_scoreText = tmp; break;
|
||
case 1: ui.teammate02_current_scoreText = tmp; break;
|
||
case 2: ui.teammate03_current_scoreText = tmp; break;
|
||
case 3: ui.teammate04_current_scoreText = tmp; break;
|
||
case 4: ui.teammate05_current_scoreText = tmp; break;
|
||
}
|
||
Debug.Log($"[AllyCombatant] Resolved currentScoreText for slot {slotIndex+1} from parent {parent.name} -> {tmp.gameObject.name}");
|
||
}
|
||
else
|
||
{
|
||
// legacy UnityEngine.UI.Text fallback
|
||
var legacy = parent.GetComponentInChildren<Text>(true);
|
||
if (legacy != null)
|
||
{
|
||
var go = legacy.gameObject;
|
||
var created = go.GetComponent<TextMeshProUGUI>() ?? go.AddComponent<TextMeshProUGUI>();
|
||
created.text = legacy.text;
|
||
currentScoreText = created;
|
||
switch (slotIndex)
|
||
{
|
||
case 0: ui.teammate01_current_scoreText = created; break;
|
||
case 1: ui.teammate02_current_scoreText = created; break;
|
||
case 2: ui.teammate03_current_scoreText = created; break;
|
||
case 3: ui.teammate04_current_scoreText = created; break;
|
||
case 4: ui.teammate05_current_scoreText = created; break;
|
||
}
|
||
Debug.LogWarning($"[AllyCombatant] Found legacy Text for slot {slotIndex+1} under {parent.name}. Added/used TMP component on {go.name} and copied text.");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void InitializeStatsFromData()
|
||
{
|
||
// Try TeamCharacterDataInfo first
|
||
var arr = teamUIController.Instance?.GetCurrentAllySOs();
|
||
if (arr != null && slotIndex >= 0 && slotIndex < arr.Length)
|
||
{
|
||
var so = arr[slotIndex];
|
||
if (so != null)
|
||
{
|
||
// TeamCharacterDataInfo has limited fields; use CharacterMaxHealth if present
|
||
if (so.CharacterMaxHealth > 0)
|
||
{
|
||
maxHP = so.CharacterMaxHealth;
|
||
}
|
||
if (!string.IsNullOrEmpty(so.CharacterName) && nameText != null)
|
||
{
|
||
nameText.text = so.CharacterName;
|
||
}
|
||
if (characterImage != null && so.CharacterImage_gameplay != null)
|
||
{
|
||
characterImage.sprite = so.CharacterImage_gameplay.sprite;
|
||
}
|
||
}
|
||
}
|
||
|
||
// If more detailed info exists in AllyHero_SO, try to load by ally ID
|
||
int id = -1;
|
||
if (teamUIController.Instance != null && teamUIController.Instance.allySlotIds != null && slotIndex < teamUIController.Instance.allySlotIds.Count)
|
||
id = teamUIController.Instance.allySlotIds[slotIndex];
|
||
|
||
if (id > 0)
|
||
{
|
||
var all = Resources.LoadAll<AllyHero_SO>("");
|
||
foreach (var a in all)
|
||
{
|
||
if (a != null && a.ally_heroID == id)
|
||
{
|
||
// �ؼ�����ͬһ�� AllyHero_SO �������λʹ��ʱ������ֱ���� a.ally_currentEXP����������
|
||
// ���Դ���Ҵ浵/PlayerPrefs ��ȡ������λ���ľ���ֵ��
|
||
// Լ��������selected_heroSlot0{slotIndex+1}_exp������ selected_heroSlot01_exp����
|
||
int expKeySlot = Mathf.Clamp(slotIndex + 1, 1, 5);
|
||
string expKey = $"selected_heroSlot0{expKeySlot}_exp";
|
||
int slotExp = a.ally_currentEXP;
|
||
if (PlayerPrefs.HasKey(expKey))
|
||
slotExp = PlayerPrefs.GetInt(expKey, a.ally_currentEXP);
|
||
|
||
// pick effective level based on per-slot EXP
|
||
if (a.levelStats != null && a.levelStats.Count > 0)
|
||
{
|
||
var eff = GetEffectiveLevelForExp(a, slotExp);
|
||
if (eff != null)
|
||
{
|
||
maxHP = eff.maxHP;
|
||
maxMana = eff.maxMana;
|
||
damageResistance = eff.damageResistance;
|
||
scoreEfficiency = eff.scoreEfficiency;
|
||
attack = eff.attack;
|
||
}
|
||
else
|
||
{
|
||
var lvl = a.levelStats[0];
|
||
maxHP = lvl.maxHP;
|
||
maxMana = lvl.maxMana;
|
||
damageResistance = lvl.damageResistance;
|
||
scoreEfficiency = lvl.scoreEfficiency;
|
||
attack = lvl.attack;
|
||
}
|
||
}
|
||
|
||
if (nameText != null) nameText.text = a.ally_heroName;
|
||
if (characterImage != null && a.ally_heroProfile != null) characterImage.sprite = a.ally_heroProfile;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ���ݴ��� EXP ����ȼ������� SO �ϵ� ally_currentEXP����������λ����״̬��
|
||
private static AllyHero_SO.AllyLevelInfo GetEffectiveLevelForExp(AllyHero_SO so, int exp)
|
||
{
|
||
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return null;
|
||
AllyHero_SO.AllyLevelInfo best = null;
|
||
foreach (var lvl in so.levelStats)
|
||
{
|
||
if (lvl == null) continue;
|
||
if (exp >= lvl.requiredEXP)
|
||
{
|
||
if (best == null || lvl.requiredEXP >= best.requiredEXP)
|
||
best = lvl;
|
||
}
|
||
}
|
||
return best ?? so.levelStats[0];
|
||
}
|
||
|
||
// Scoring API
|
||
// Modified to return the amount added so callers can track per-track contributions
|
||
public int AddScoreForJudge(string judge)
|
||
{
|
||
int add = 0;
|
||
float multiplier = 0f;
|
||
switch (judge)
|
||
{
|
||
case "Perfect":
|
||
multiplier = perfectRatio;
|
||
break;
|
||
case "Great":
|
||
multiplier = greatRatio;
|
||
break;
|
||
case "Good":
|
||
multiplier = goodRatio;
|
||
break;
|
||
case "Miss":
|
||
multiplier = missRatio;
|
||
break;
|
||
default:
|
||
multiplier = 0f;
|
||
break;
|
||
}
|
||
|
||
// Use perNoteScore from BeatmapManager as the base score for each note hit
|
||
int baseScore = bmm != null ? bmm.perNoteScore : baseTrackScore;
|
||
add = Mathf.FloorToInt(baseScore * multiplier);
|
||
|
||
int before = currentScore;
|
||
currentScore = Mathf.Clamp(currentScore + add, 0, maxTrackScore);
|
||
int actuallyAdded = currentScore - before;
|
||
UpdateScoreUI();
|
||
// update global total
|
||
ScoreManager.Instance?.RecalculateTotal();
|
||
return actuallyAdded;
|
||
}
|
||
|
||
// Add an arbitrary score delta directly (used by skills to modify single judge or grant/penalize score)
|
||
public void AddScoreDirect(int delta)
|
||
{
|
||
currentScore = Mathf.Clamp(currentScore + delta, 0, maxTrackScore);
|
||
UpdateScoreUI();
|
||
ScoreManager.Instance?.RecalculateTotal();
|
||
}
|
||
|
||
private void UpdateScoreUI()
|
||
{
|
||
string value = $"{currentScore}/{maxTrackScore}";
|
||
if (currentScoreText != null)
|
||
{
|
||
currentScoreText.text = value;
|
||
Debug.Log($"[AllyCombatant] Updated slot {slotIndex+1} score UI -> {value} (target {currentScoreText.gameObject.name})");
|
||
return;
|
||
}
|
||
|
||
// As a fallback, try to write directly to teamUIController.Instance fields if available
|
||
var ui = teamUIController.Instance;
|
||
if (ui != null)
|
||
{
|
||
switch (slotIndex)
|
||
{
|
||
case 0:
|
||
if (ui.teammate01_current_scoreText != null) { ui.teammate01_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate01_current_scoreText: {value}"); }
|
||
break;
|
||
case 1:
|
||
if (ui.teammate02_current_scoreText != null) { ui.teammate02_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate02_current_scoreText: {value}"); }
|
||
break;
|
||
case 2:
|
||
if (ui.teammate03_current_scoreText != null) { ui.teammate03_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate03_current_scoreText: {value}"); }
|
||
break;
|
||
case 3:
|
||
if (ui.teammate04_current_scoreText != null) { ui.teammate04_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate04_current_scoreText: {value}"); }
|
||
break;
|
||
case 4:
|
||
if (ui.teammate05_current_scoreText != null) { ui.teammate05_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate05_current_scoreText: {value}"); }
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Public setters that update UI and optionally animate fade bars
|
||
public void SetCurrentHP(int hp, bool animateFade = true)
|
||
{
|
||
if (isDead) return; // dead characters cannot change HP
|
||
int old = currentHP;
|
||
currentHP = Mathf.Clamp(hp, 0, maxHP);
|
||
if (currentHP <= 0) isDead = true;
|
||
UpdateHealthVisuals(old, animateFade);
|
||
|
||
// Trigger HP changed events
|
||
int delta = currentHP - old;
|
||
if (delta > 0)
|
||
{
|
||
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnHPHealed);
|
||
}
|
||
else if (delta < 0)
|
||
{
|
||
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnHPLost);
|
||
}
|
||
|
||
// Evaluate HP-percentage based trigger transitions (entry/exit)
|
||
EvaluateHPPercentageTriggers(old);
|
||
}
|
||
|
||
public void ModifyHP(int delta, bool animateFade = true)
|
||
{
|
||
if (isDead && delta > 0)
|
||
{
|
||
// cannot heal a dead unit
|
||
return;
|
||
}
|
||
SetCurrentHP(currentHP + delta, animateFade);
|
||
}
|
||
|
||
public void SetMaxHP(int newMax, bool keepCurrentRatio = true)
|
||
{
|
||
if (newMax < 1) newMax = 1;
|
||
int oldMax = maxHP;
|
||
int oldHP = currentHP;
|
||
float ratio = maxHP > 0 ? (float)currentHP / maxHP : 1f;
|
||
maxHP = newMax;
|
||
if (keepCurrentRatio)
|
||
currentHP = Mathf.Clamp(Mathf.RoundToInt(ratio * maxHP), 0, maxHP);
|
||
else
|
||
currentHP = Mathf.Clamp(currentHP, 0, maxHP);
|
||
|
||
UpdateUIImmediate();
|
||
|
||
// Max HP change affects percentage triggers
|
||
EvaluateHPPercentageTriggers(oldHP);
|
||
}
|
||
|
||
public void SetCurrentMana(int mana, bool animateFade = true, bool flashVisuals = true)
|
||
{
|
||
if (isDead) return; // dead characters cannot change mana
|
||
int old = currentMana;
|
||
currentMana = Mathf.Clamp(mana, 0, maxMana);
|
||
UpdateManaVisuals(old, animateFade, flashVisuals);
|
||
TryCastOnFullMana();
|
||
|
||
// Trigger mana changed events
|
||
int delta = currentMana - old;
|
||
if (delta > 0)
|
||
{
|
||
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnManaGained);
|
||
}
|
||
else if (delta < 0)
|
||
{
|
||
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnManaLost);
|
||
}
|
||
}
|
||
|
||
public void ModifyMana(int delta, bool animateFade = true, bool flashVisuals = true)
|
||
{
|
||
if (isDead && delta > 0)
|
||
{
|
||
// dead units cannot gain mana
|
||
return;
|
||
}
|
||
SetCurrentMana(currentMana + delta, animateFade, flashVisuals);
|
||
}
|
||
|
||
public void SetMaxMana(int newMax, bool keepCurrentRatio = true)
|
||
{
|
||
if (newMax < 1) newMax = 1;
|
||
int oldMax = maxMana;
|
||
int oldMana = currentMana;
|
||
float ratio = maxMana > 0 ? (float)currentMana / maxMana : 1f;
|
||
maxMana = newMax;
|
||
if (keepCurrentRatio)
|
||
currentMana = Mathf.Clamp(Mathf.RoundToInt(ratio * maxMana), 0, maxMana);
|
||
else
|
||
currentMana = Mathf.Clamp(currentMana, 0, maxMana);
|
||
|
||
UpdateUIImmediate();
|
||
// Mana doesn't currently have percentage triggers, but for consistency:
|
||
// EvaluateHPPercentageTriggers(currentHP); // Not needed for mana but keep in mind
|
||
}
|
||
|
||
private void UpdateHealthVisuals(int oldHP, bool animateFade)
|
||
{
|
||
// main bar should snap quickly to new value, while fade bar lerps towards it
|
||
if (healthImage != null)
|
||
{
|
||
float newFill = maxHP > 0 ? (float)currentHP / maxHP : 0f;
|
||
// snap main bar quickly for visual pop
|
||
healthImage.fillAmount = Mathf.MoveTowards(healthImage.fillAmount, newFill, 1f);
|
||
}
|
||
|
||
if (healthRateText != null)
|
||
healthRateText.text = $"{currentHP}/{maxHP}";
|
||
|
||
if (fadeHealthCoroutine != null) StopCoroutine(fadeHealthCoroutine);
|
||
if (fadeHealthImage != null)
|
||
{
|
||
float target = healthImage != null ? healthImage.fillAmount : maxHP > 0 ? (float)currentHP / maxHP : 0f;
|
||
if (animateFade && Application.isPlaying)
|
||
fadeHealthCoroutine = StartCoroutine(FadeHealthCoroutine(target));
|
||
else
|
||
fadeHealthImage.fillAmount = target;
|
||
}
|
||
|
||
// HP change flash effects
|
||
if (hurtRedImage != null && Application.isPlaying)
|
||
{
|
||
var ui = teamUIController.Instance;
|
||
if (ui != null)
|
||
{
|
||
if (currentHP < oldHP)
|
||
{
|
||
// Damage flash (Red)
|
||
if (!isDead)
|
||
StartCoroutine(ui.AllyImageFlash(hurtRedImage, ui.hurtColor));
|
||
}
|
||
else if (currentHP > oldHP)
|
||
{
|
||
// Heal flash (Green)
|
||
StartCoroutine(ui.AllyImageFlash(hurtRedImage, ui.healColor));
|
||
}
|
||
}
|
||
}
|
||
|
||
// if dead, trigger grayscale effect on character image
|
||
if (isDead && characterImage != null && Application.isPlaying)
|
||
{
|
||
var ui = teamUIController.Instance;
|
||
if (ui != null)
|
||
{
|
||
// 移除原有的红闪常亮逻辑,改为调用黑白渐变效果
|
||
StartCoroutine(ui.AllyDeathGrayscale(characterImage));
|
||
}
|
||
}
|
||
}
|
||
|
||
private void UpdateManaVisuals(int oldMana, bool animateFade, bool flashVisuals = true)
|
||
{
|
||
// main bar snaps, fade bar lerps to new value for visual effect
|
||
if (manaImage != null)
|
||
{
|
||
float newFill = maxMana > 0 ? (float)currentMana / maxMana : 0f;
|
||
manaImage.fillAmount = Mathf.MoveTowards(manaImage.fillAmount, newFill, 1f);
|
||
}
|
||
|
||
if (fadeManaCoroutine != null) StopCoroutine(fadeManaCoroutine);
|
||
if (fadeManaImage != null)
|
||
{
|
||
float target = manaImage != null ? manaImage.fillAmount : maxMana > 0 ? (float)currentMana / maxMana : 0f;
|
||
if (animateFade && Application.isPlaying)
|
||
fadeManaCoroutine = StartCoroutine(FadeManaCoroutine(target));
|
||
else
|
||
fadeManaImage.fillAmount = target;
|
||
}
|
||
|
||
// Mana recovery flash effect
|
||
if (flashVisuals && currentMana > oldMana && hurtRedImage != null && Application.isPlaying)
|
||
{
|
||
var ui = teamUIController.Instance;
|
||
if (ui != null)
|
||
{
|
||
StartCoroutine(ui.AllyImageFlash(hurtRedImage, ui.manaColor));
|
||
}
|
||
}
|
||
}
|
||
|
||
private void UpdateUIImmediate()
|
||
{
|
||
if (healthImage != null)
|
||
{
|
||
healthImage.fillAmount = maxHP > 0 ? (float)currentHP / maxHP : 0f;
|
||
}
|
||
if (fadeHealthImage != null)
|
||
{
|
||
// keep fade bar equal to main bar immediately
|
||
fadeHealthImage.fillAmount = healthImage != null ? healthImage.fillAmount : maxHP > 0 ? (float)currentHP / maxHP : 0f;
|
||
}
|
||
if (manaImage != null)
|
||
{
|
||
manaImage.fillAmount = maxMana > 0 ? (float)currentMana / maxMana : 0f;
|
||
}
|
||
if (fadeManaImage != null)
|
||
{
|
||
fadeManaImage.fillAmount = manaImage != null ? manaImage.fillAmount : maxMana > 0 ? (float)currentMana / maxMana : 0f;
|
||
}
|
||
if (healthRateText != null)
|
||
{
|
||
healthRateText.text = $"{currentHP}/{maxHP}";
|
||
}
|
||
}
|
||
|
||
private IEnumerator FadeHealthCoroutine(float targetFill)
|
||
{
|
||
if (fadeHealthImage == null) yield break;
|
||
float start = fadeHealthImage.fillAmount;
|
||
float duration = 0.6f;
|
||
float t = 0f;
|
||
while (t < duration)
|
||
{
|
||
t += Time.deltaTime;
|
||
fadeHealthImage.fillAmount = Mathf.Lerp(start, targetFill, t / duration);
|
||
yield return null;
|
||
}
|
||
fadeHealthImage.fillAmount = targetFill;
|
||
}
|
||
|
||
private IEnumerator FadeManaCoroutine(float targetFill)
|
||
{
|
||
if (fadeManaImage == null) yield break;
|
||
float start = fadeManaImage.fillAmount;
|
||
float duration = 0.4f;
|
||
float t = 0f;
|
||
while (t < duration)
|
||
{
|
||
t += Time.deltaTime;
|
||
fadeManaImage.fillAmount = Mathf.Lerp(start, targetFill, t / duration);
|
||
yield return null;
|
||
}
|
||
fadeManaImage.fillAmount = targetFill;
|
||
}
|
||
|
||
public void ReceiveDamage(float amount, GameObject source)
|
||
{
|
||
float effective = amount * (1f - damageResistance);
|
||
int delta = Mathf.CeilToInt(effective);
|
||
ModifyHP(-delta, true);
|
||
}
|
||
|
||
public void ReceiveHeal(float amount, GameObject source)
|
||
{
|
||
int delta = Mathf.CeilToInt(amount);
|
||
ModifyHP(delta, true);
|
||
}
|
||
|
||
public void ApplyBuff(Buff buff, GameObject source)
|
||
{
|
||
if (buff == null) return;
|
||
activeBuffs.Add(buff);
|
||
// simple immediate apply: multiply stats
|
||
if (buff.attackMultiplier != 1f) { attack = Mathf.RoundToInt(attack * buff.attackMultiplier); }
|
||
if (buff.scoreMultiplier != 1f) scoreEfficiency *= buff.scoreMultiplier;
|
||
// UI/visual could be added
|
||
}
|
||
|
||
public void RemoveBuff(string buffId)
|
||
{
|
||
var b = activeBuffs.Find(x => x.buffId == buffId);
|
||
if (b != null)
|
||
{
|
||
activeBuffs.Remove(b);
|
||
if (b.scoreMultiplier != 1f) scoreEfficiency /= b.scoreMultiplier;
|
||
// cannot easily restore attack without storing original; keep simple
|
||
}
|
||
}
|
||
|
||
private void TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger when)
|
||
{
|
||
if (!Application.isPlaying) return;
|
||
if (SkillBuilder.Instance == null) return;
|
||
|
||
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
|
||
if (so == null) return;
|
||
|
||
// If equipped groups exist, iterate them first
|
||
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
|
||
{
|
||
foreach (var gid in so.equippedSkillGroupIDs)
|
||
{
|
||
if (gid == 0) continue;
|
||
SkillGroup group = null;
|
||
for (int k = 0; k < so.skillGroups.Length; k++)
|
||
{
|
||
var g = so.skillGroups[k];
|
||
if (g != null && g.skillGroupID == gid) { group = g; break; }
|
||
}
|
||
if (group == null) continue;
|
||
foreach (var def in group.skills)
|
||
{
|
||
if (def == null) continue;
|
||
if (def.triggerCondition != when) continue;
|
||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Fallback: check primary group
|
||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||
if (fallbackGroup != null)
|
||
{
|
||
foreach (var def in fallbackGroup.skills)
|
||
{
|
||
if (def == null) continue;
|
||
if (def.triggerCondition != when) continue;
|
||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Final fallback: iterate availableSkills
|
||
if (so.availableSkills != null)
|
||
{
|
||
foreach (var def in so.availableSkills)
|
||
{
|
||
if (def == null) continue;
|
||
if (def.triggerCondition != when) continue;
|
||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void TryCastOnFullMana()
|
||
{
|
||
if (currentMana >= maxMana && Application.isPlaying)
|
||
{
|
||
// Ensure SkillBuilder singleton exists before attempting to cast.
|
||
if (SkillBuilder.Instance == null)
|
||
{
|
||
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1} cannot cast right now: SkillBuilder.Instance is null. Will not attempt cast.");
|
||
return;
|
||
}
|
||
|
||
int prevMana = currentMana;
|
||
// Attempt to cast primary skill configured in SO via SkillBuilder
|
||
CastSkill();
|
||
// Reset mana after casting
|
||
currentMana = 0;
|
||
// Update visuals using previous mana as old value so fade works
|
||
UpdateManaVisuals(prevMana, true);
|
||
}
|
||
}
|
||
|
||
private void CastSkill()
|
||
{
|
||
// Use SkillBuilder to invoke the primary skill defined in the AllyHero_SO for this slot
|
||
if (SkillBuilder.Instance == null)
|
||
{
|
||
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1} cannot cast: SkillBuilder.Instance is null.");
|
||
return;
|
||
}
|
||
|
||
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
|
||
if (so == null)
|
||
{
|
||
// Can't locate SO for this slot; attempt best-effort: call UsePrimarySkillForSlot which will log details
|
||
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1}: AllyHero_SO not found for slot. Falling back to UsePrimarySkillForSlot.");
|
||
SkillBuilder.Instance.UsePrimarySkillForSlot(slotIndex, -1f, null);
|
||
return;
|
||
}
|
||
|
||
bool anyTriggered = false;
|
||
|
||
// First: equipped groups
|
||
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
|
||
{
|
||
foreach (int gid in so.equippedSkillGroupIDs)
|
||
{
|
||
if (gid == 0) continue;
|
||
SkillGroup group = null;
|
||
for (int k = 0; k < so.skillGroups.Length; k++) { var g = so.skillGroups[k]; if (g != null && g.skillGroupID == gid) { group = g; break; } }
|
||
if (group == null) continue;
|
||
foreach (var skill in group.skills)
|
||
{
|
||
if (skill == null) continue;
|
||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
|
||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast skill from group: {skill.skillId} (OnManaFull)");
|
||
anyTriggered = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Second: primary group
|
||
if (!anyTriggered)
|
||
{
|
||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||
if (fallbackGroup != null)
|
||
{
|
||
foreach (var skill in fallbackGroup.skills)
|
||
{
|
||
if (skill == null) continue;
|
||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
|
||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast skill from primary group: {skill.skillId} (OnManaFull)");
|
||
anyTriggered = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Third: availableSkills / primary skill fallback
|
||
if (!anyTriggered)
|
||
{
|
||
// try primary skill specifically
|
||
var def = so.GetPrimarySkill();
|
||
if (def != null && def.triggerCondition == SkillDefinition.SkillTrigger.OnManaFull)
|
||
{
|
||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast primary skill '{def.skillId}' due to ManaFull.");
|
||
anyTriggered = true;
|
||
}
|
||
else if (so.availableSkills != null)
|
||
{
|
||
foreach (var skill in so.availableSkills)
|
||
{
|
||
if (skill == null) continue;
|
||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
|
||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast available skill: {skill.skillId} (OnManaFull)");
|
||
anyTriggered = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!anyTriggered)
|
||
{
|
||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1}: no skills configured for OnManaFull.");
|
||
}
|
||
}
|
||
|
||
private void CastSkillGroup(SkillGroup group, int slotIndex)
|
||
{
|
||
if (group == null) return;
|
||
// For auto-casting triggered by ManaFull, only cast skills that are configured to trigger on ManaFull.
|
||
for (int i = 0; i < group.skills.Length; i++)
|
||
{
|
||
var skill = group.skills[i];
|
||
if (skill == null) continue;
|
||
// Only cast skills whose triggerCondition matches OnManaFull when this method is invoked from auto-cast path
|
||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull)
|
||
{
|
||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} skill '{skill.skillId}' skipped: triggerCondition={skill.triggerCondition}.");
|
||
continue;
|
||
}
|
||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast skill from group: {skill.skillId}");
|
||
}
|
||
}
|
||
|
||
public void TriggerOnManaFull()
|
||
{
|
||
if (currentMana >= maxMana)
|
||
{
|
||
TryCastOnFullMana();
|
||
}
|
||
}
|
||
|
||
[ContextMenu("Test Fill Mana")]
|
||
private void TestFillMana()
|
||
{
|
||
SetCurrentMana(maxMana, true);
|
||
}
|
||
|
||
// New: Evaluate HP-percent triggers and handle enter/leave transitions
|
||
private void EvaluateHPPercentageTriggers(int oldHP)
|
||
{
|
||
if (!Application.isPlaying) return;
|
||
if (SkillBuilder.Instance == null) return;
|
||
|
||
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
|
||
if (so == null) return;
|
||
|
||
float oldPct = (maxHP > 0) ? (float)oldHP / (float)maxHP : 0f;
|
||
float currPct = (maxHP > 0) ? (float)currentHP / (float)maxHP : 0f;
|
||
|
||
Action<SkillDefinition> processDef = (def) =>
|
||
{
|
||
if (def == null) return;
|
||
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnHPAbovePercent && def.triggerCondition != SkillDefinition.SkillTrigger.OnHPBelowPercent)
|
||
return;
|
||
|
||
bool wasActive = _activeHpPercentSkills.Contains(def.skillId);
|
||
bool nowActive = false;
|
||
if (def.triggerCondition == SkillDefinition.SkillTrigger.OnHPAbovePercent)
|
||
nowActive = currPct >= def.hpTriggerPercent;
|
||
else if (def.triggerCondition == SkillDefinition.SkillTrigger.OnHPBelowPercent)
|
||
nowActive = currPct <= def.hpTriggerPercent;
|
||
|
||
// Evaluate formula into amount using similar variables as SkillBuilder
|
||
float amount = 0f;
|
||
var vars = new Dictionary<string, float>();
|
||
vars["slot"] = slotIndex;
|
||
vars["attack"] = attack;
|
||
if (so != null && so.levelStats != null && so.levelStats.Count > 0)
|
||
{
|
||
var eff = so.GetEffectiveLevelForCurrentEXP();
|
||
var lvl = eff ?? so.levelStats[0];
|
||
vars["maxHP"] = lvl.maxHP;
|
||
vars["maxMana"] = lvl.maxMana;
|
||
vars["damageResistance"] = lvl.damageResistance;
|
||
vars["scoreEfficiency"] = lvl.scoreEfficiency;
|
||
vars["attack"] = lvl.attack;
|
||
}
|
||
else
|
||
{
|
||
vars["maxHP"] = maxHP;
|
||
vars["maxMana"] = maxMana;
|
||
vars["damageResistance"] = damageResistance;
|
||
vars["scoreEfficiency"] = scoreEfficiency;
|
||
}
|
||
vars["ally_currentEXP"] = so != null ? so.ally_currentEXP : 0f;
|
||
|
||
if (!string.IsNullOrWhiteSpace(def.formula))
|
||
{
|
||
if (!SkillDefinition.TryEvaluateFormula(def.formula, vars, out float fresult))
|
||
amount = 0f;
|
||
else
|
||
amount = fresult;
|
||
}
|
||
|
||
// Decide if this effect type is reversible (state-based)
|
||
bool isReversible = IsEffectReversible(def.effectType);
|
||
|
||
if (!isReversible)
|
||
{
|
||
// For non-reversible effects (like Damage or instant Heal), we don't track state
|
||
// Just trigger it once when the condition is first met
|
||
if (!wasActive && nowActive)
|
||
{
|
||
_activeHpPercentSkills.Add(def.skillId);
|
||
EffectSystem.Instance.ApplyEffect(def.defaultSelector, def.effectType, amount, 0f, this.gameObject, null);
|
||
}
|
||
else if (wasActive && !nowActive)
|
||
{
|
||
_activeHpPercentSkills.Remove(def.skillId);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!wasActive && nowActive)
|
||
{
|
||
// ENTER condition: Apply effect and record amount
|
||
_activeHpPercentSkills.Add(def.skillId);
|
||
EffectSystem.Instance.ApplyEffect(def.defaultSelector, def.effectType, amount, 0f, this.gameObject, null);
|
||
_appliedHpPercentEffects[def.skillId] = new AppliedEffect(def.effectType, amount);
|
||
}
|
||
else if (wasActive && !nowActive)
|
||
{
|
||
// LEAVE condition: Revert effect using recorded amount
|
||
_activeHpPercentSkills.Remove(def.skillId);
|
||
if (_appliedHpPercentEffects.TryGetValue(def.skillId, out var applied))
|
||
{
|
||
ApplyInverseEffect(def.defaultSelector, applied.effectType, applied.amount);
|
||
_appliedHpPercentEffects.Remove(def.skillId);
|
||
}
|
||
}
|
||
};
|
||
|
||
// iterate equipped groups -> primary group -> availableSkills (same as other triggers)
|
||
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
|
||
{
|
||
foreach (var gid in so.equippedSkillGroupIDs)
|
||
{
|
||
if (gid == 0) continue;
|
||
SkillGroup group = null;
|
||
for (int k = 0; k < so.skillGroups.Length; k++)
|
||
{
|
||
var g = so.skillGroups[k];
|
||
if (g != null && g.skillGroupID == gid) { group = g; break; }
|
||
}
|
||
if (group == null) continue;
|
||
foreach (var def in group.skills)
|
||
{
|
||
processDef(def);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||
if (fallbackGroup != null)
|
||
{
|
||
foreach (var def in fallbackGroup.skills)
|
||
{
|
||
processDef(def);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (so.availableSkills != null)
|
||
{
|
||
foreach (var def in so.availableSkills)
|
||
{
|
||
processDef(def);
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool IsEffectReversible(EffectType type)
|
||
{
|
||
switch (type)
|
||
{
|
||
case EffectType.IncreaseAttack:
|
||
case EffectType.DecreaseAttack:
|
||
case EffectType.IncreaseMaxHP:
|
||
case EffectType.DecreaseMaxHP:
|
||
case EffectType.IncreaseMaxMana:
|
||
case EffectType.DecreaseMaxMana:
|
||
case EffectType.IncreaseScoreEfficiency:
|
||
case EffectType.DecreaseScoreEfficiency:
|
||
case EffectType.IncreaseDamageResistance:
|
||
case EffectType.DecreaseDamageResistance:
|
||
case EffectType.ScoreMultiplier:
|
||
case EffectType.AddScore:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private void ApplyInverseEffect(Selector selector, EffectType type, float amount)
|
||
{
|
||
if (EffectSystem.Instance == null) return;
|
||
|
||
switch (type)
|
||
{
|
||
case EffectType.IncreaseAttack:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseAttack, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.DecreaseAttack:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseAttack, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.IncreaseMaxHP:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseMaxHP, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.DecreaseMaxHP:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseMaxHP, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.IncreaseMaxMana:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseMaxMana, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.DecreaseMaxMana:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseMaxMana, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.IncreaseScoreEfficiency:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseScoreEfficiency, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.DecreaseScoreEfficiency:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseScoreEfficiency, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.IncreaseDamageResistance:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseDamageResistance, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.DecreaseDamageResistance:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseDamageResistance, amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.ScoreMultiplier:
|
||
if (Mathf.Abs(amount) > 0.0001f)
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.ScoreMultiplier, 1f / amount, 0f, this.gameObject, null);
|
||
break;
|
||
case EffectType.AddScore:
|
||
EffectSystem.Instance.ApplyEffect(selector, EffectType.AddScore, -amount, 0f, this.gameObject, null);
|
||
break;
|
||
default:
|
||
// Fallback: apply same type with negative amount if it's likely a numeric stat
|
||
EffectSystem.Instance.ApplyEffect(selector, type, -amount, 0f, this.gameObject, null);
|
||
break;
|
||
}
|
||
}
|
||
}
|