spell refactor part - assets and prefabs

This commit is contained in:
2026-01-22 14:54:35 +01:00
parent 1beff44ada
commit 013dca60a5
36 changed files with 773 additions and 1034 deletions

View File

@@ -1,8 +1,5 @@
using Invector;
using Invector.vMelee;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Beyond
@@ -10,20 +7,15 @@ namespace Beyond
public class ShadowSlayerBarkHelper : MonoBehaviour
{
private const int fullyChargedBarkId = 24, emptySlayerBarkId = 14, normalSwordAttackBarkID = 23, powerBarkID = 22;
public vObjectDamage blastDamager, flamethrowerDamager;//, fireballDamager;
// Remove specific damagers, we don't need them anymore
// public vObjectDamage blastDamager, flamethrowerDamager;
public BarkManager barkManager;
// public bEquipArea weaponsArea;
public vMeleeManager meleeManager;
// public List<bEquipSlot> weaponSlots;
private vMeleeWeapon weapon;
public MagicAttacks magicAttacks;
public TutorialController emptySlayerTutorial;
// Start is called before the first frame update
private void Awake()
{
EnableShadowSlayerBarks();
@@ -31,48 +23,32 @@ namespace Beyond
private void EnableShadowSlayerBarks()
{
meleeManager.onDamageHit.AddListener(TryToPlayAttackBark);
if (meleeManager)
meleeManager.onDamageHit.AddListener(TryToPlayAttackBark);
blastDamager.onHit.AddListener(TryToPlayPowerBark);
flamethrowerDamager.onHit.AddListener(TryToPlayPowerBark);
magicAttacks.onHitFireball += TryToPlayPowerBark;
// fireballDamager.onHit.AddListener(TryToPlayPowerBark);
// NEW: Subscribe to the centralized Magic event
if (magicAttacks)
magicAttacks.OnSpellHit += OnMagicHit;
}
private void TryToPlayAttackBark(vHitInfo arg0)
private void OnDestroy()
{
CharacterVisibilityController visilibityController = arg0.targetCollider.GetComponent<CharacterVisibilityController>();
if (visilibityController)
{
weapon = meleeManager.rightWeapon;
bItemAttribute power = weapon.GetComponent<bMeleeEquipment>().power;
if (power != null && power.value > 0)
{
//attacking with fully charged slayer
barkManager.PlayBark(fullyChargedBarkId);
}
else if (power != null && power.value <= 0 && !emptySlayerTutorial.played)
{
emptySlayerTutorial.StartTutorial();
//attacking with empty slayer, change id to correct!
// barkManager.PlayBark(emptySlayerBarkId);
}
else if (power == null)
{
//attacking
barkManager.PlayBark(normalSwordAttackBarkID);
}
//not much of an improvement as bark handles playing already, just removes few check before
TryToRemoveAttackListener();
}
}
private void TryToRemoveAttackListener()
{
if (!(barkManager.CanBarkBeStillPlayed(fullyChargedBarkId) || !emptySlayerTutorial.played /*|| barkManager.CanBarkBeStillPlayed(emptySlayerBarkId) */ || barkManager.CanBarkBeStillPlayed(normalSwordAttackBarkID)))
{
if (magicAttacks)
magicAttacks.OnSpellHit -= OnMagicHit;
if (meleeManager)
meleeManager.onDamageHit.RemoveListener(TryToPlayAttackBark);
}
// ---------------------------------------------------------
// MAGIC LOGIC
// ---------------------------------------------------------
private void OnMagicHit(SpellDefinition spell, Collider hitTarget)
{
// Check if the spell is "Offensive_Power" (Fireball, Flamethrower, Blast)
if (spell.category == SpellCategory.Offensive_Power)
{
TryToPlayPowerBark(hitTarget);
}
}
@@ -81,25 +57,65 @@ namespace Beyond
CharacterVisibilityController visilibityController = arg0.GetComponent<CharacterVisibilityController>();
if (visilibityController)
{
barkManager.PlayBark(powerBarkID);
TryToRemovePowerBarkListener();
if (barkManager.CanBarkBeStillPlayed(powerBarkID))
{
barkManager.PlayBark(powerBarkID);
// We don't need to unsubscribe here anymore because the
// event is global and persistent on MagicAttacks.
// The 'CanBarkBeStillPlayed' check handles the "play once" logic.
}
}
}
private void TryToRemovePowerBarkListener()
// ---------------------------------------------------------
// MELEE LOGIC (Unchanged, just uncommented)
// ---------------------------------------------------------
private void TryToPlayAttackBark(vHitInfo arg0)
{
if (barkManager.CanBarkBeStillPlayed(powerBarkID))
CharacterVisibilityController visilibityController = arg0.targetCollider.GetComponent<CharacterVisibilityController>();
if (visilibityController)
{
blastDamager.onHit.RemoveListener(TryToPlayPowerBark);
flamethrowerDamager.onHit.RemoveListener(TryToPlayPowerBark);
magicAttacks.onHitFireball -= TryToPlayPowerBark;
//fireballDamager.onHit.RemoveListener(TryToPlayPowerBark);
var weapon = meleeManager.rightWeapon;
if (weapon == null) return;
bItemAttribute power = weapon.GetComponent<bMeleeEquipment>().power;
if (power != null && power.value > 0)
{
// Fully charged
barkManager.PlayBark(fullyChargedBarkId);
}
else if (power != null && power.value <= 0)
{
// Empty Slayer
if (emptySlayerTutorial != null && !emptySlayerTutorial.played)
{
emptySlayerTutorial.StartTutorial();
}
// barkManager.PlayBark(emptySlayerBarkId);
}
else if (power == null)
{
// Normal Sword
barkManager.PlayBark(normalSwordAttackBarkID);
}
// Cleanup listener if all barks are done
TryToRemoveAttackListener();
}
}
// Update is called once per frame
private void Update()
private void TryToRemoveAttackListener()
{
// If all relevant barks are exhausted/played, stop listening to melee hits to save performance
bool fullyChargedDone = !barkManager.CanBarkBeStillPlayed(fullyChargedBarkId);
bool normalDone = !barkManager.CanBarkBeStillPlayed(normalSwordAttackBarkID);
bool tutorialDone = (emptySlayerTutorial == null || emptySlayerTutorial.played);
if (fullyChargedDone && normalDone && tutorialDone)
{
meleeManager.onDamageHit.RemoveListener(TryToPlayAttackBark);
}
}
}
}

View File

@@ -21,26 +21,24 @@ namespace Beyond
private IEnumerator CastRoutine(MagicAttacks caster, Transform target)
{
// 1. Rotation (Only Push and Flame used rotation in your old script)
if (type != DurationType.Shield)
{
yield return caster.RotateTowardsTargetRoutine(target, rotationDuration);
}
// 2. Pre-cast Delay
if (preCastDelay > 0) yield return new WaitForSeconds(preCastDelay);
// ... (Rotation and Pre-Cast Delay logic) ...
// 3. Instantiate attached to player
// 3. Instantiate
GameObject instance = Instantiate(effectPrefab, caster.transform);
// Reset local position/rotation to ensure it aligns with player
instance.transform.localPosition = Vector3.zero;
instance.transform.localRotation = Quaternion.identity;
// Register Events (for Barks)
caster.RegisterSpellDamageEvents(instance, this);
// 4. Initialize Specific Logic
if (type == DurationType.Shield)
{
var shieldCtrl = instance.GetComponent<ShieldEffectController>();
if (shieldCtrl) shieldCtrl.InitializeEffect();
// --- TOGGLE FLAG ON ---
caster.IsShieldActive = true;
}
else if (type == DurationType.Flamethrower)
{
@@ -69,6 +67,9 @@ namespace Beyond
{
var shieldCtrl = instance.GetComponent<ShieldEffectController>();
if (shieldCtrl) shieldCtrl.DisableEffect();
// --- TOGGLE FLAG OFF ---
caster.IsShieldActive = false;
}
// 7. Post Delay

View File

@@ -1,504 +1,213 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Invector;
using Invector.vMelee;
using Invector.vCharacterController;
using Invector.vCharacterController.vActions;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.VFX;
using DG.Tweening;
using Invector.vCharacterController.AI.FSMBehaviour;
using Beyond;
namespace Beyond
{
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(vGenericAnimation))]
public class MagicAttacks : MonoBehaviour
{
public delegate void ActionDelegate();
[Serializable]
public class EffectDesc
{
public string name = "MagicPush";
public string secondaryName = "MagicPush Scroll";
public float delay = 0f;
public float startTime = 0f;
public float endTime = 0f;
public GameObject effectObject;
public string animClipName = "MagicPush";
public ActionDelegate del;
}
[Title("References")]
[SerializeField] private bEquipArea powersArea;
public List<EffectDesc> m_effects;
public EffectDesc selectedEffect;
public enum EffectType
{ MAGIC_PUSH, FLAME_THROWER, SCAN, NOONE, FIREBALL, SHIELD, SILENT_PEEK };
public EffectType m_selectedType = EffectType.NOONE;
public string currentSelectedSpellName = "";
private int currentSpellFaithCost = int.MaxValue;
private int equipAreaSelectedIndex = 0;
private Coroutine lastPushRoutine = null;
private ParticleSystem flame;
private BoxCollider flameDamager;
private bLockOn lockOn;
private const float fireballAimerThreshold = -1.0f;
private const float fireballAimerHeightAdjuster = 0.1f;
private const float fireballDamagerDuration = 0.3f;
private const float fireballTargetYPositionOffset = 0.75f;
private const int spellLayerIndex = 5;
private EffectDesc shield;
private EffectDesc silentPeek;
private ShieldEffectController shieldEffectController;
private ShieldCollisionController shieldCollisionController;
private bool canPlayNoFaithClip = true;
private bool canPlayCantDoClip = true;
private AutoTargetting _autoTargettingInstance;
public UnityAction<Collider> onHitFireball;
// Animation State
public bool isPlaying;
protected bool triggerOnce;
protected bThirdPersonInput tpInput;
internal bool shieldEffectIsActive;
private bool shieldAnimationIsActive;
[Tooltip("Input to trigger the custom animation")]
[Title("Input Settings")]
public GenericInput actionInput = new GenericInput("L", "L", "L");
[Tooltip("Name of the animation clip")]
public string animationClip;
[Tooltip("Where in the end of the animation will trigger the event OnEndAnimation")]
public float animationEnd = 0.8f;
[Title("Events")]
public UnityEvent OnPlayAnimation;
public UnityEvent OnEndAnimation;
public event System.Action<SpellDefinition, Collider> OnSpellHit;
// Internal State
private bThirdPersonInput tpInput;
private AutoTargetting _autoTargettingInstance;
public bool isPlaying { get; private set; }
private bool canPlayNoFaithClip = true;
[field: Sirenix.OdinInspector.ShowInInspector, Sirenix.OdinInspector.ReadOnly]
public bool IsShieldActive { get; set; }
// Constants
private const int spellLayerIndex = 5;
private void Awake()
{
tpInput = GetComponent<bThirdPersonInput>();
lockOn = GetComponent<bLockOn>();
if (Player.Instance != null)
{
_autoTargettingInstance = Player.Instance.AutoTarget;
if (_autoTargettingInstance == null)
{
Debug.LogWarning("MagicAttacks: AutoTargetting component not found on Player.Instance.AutoTarget.");
}
}
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
EffectDesc scan = m_effects[(int)EffectType.SCAN];
EffectDesc fireball = m_effects[(int)EffectType.FIREBALL];
shield = m_effects[(int)EffectType.SHIELD];
silentPeek = m_effects[(int)EffectType.SILENT_PEEK];
// Setup References
if (shield.effectObject)
{
shieldEffectController = shield.effectObject.GetComponent<ShieldEffectController>();
shieldCollisionController = shield.effectObject.GetComponentInChildren<ShieldCollisionController>();
shield.effectObject.SetActive(false);
}
if (mpush.effectObject != null)
{
mpush.effectObject.SetActive(false);
}
mpush.del = MagicPushAttack;
if (flameThrowe.effectObject)
{
flameThrowe.effectObject.GetComponent<ParticleSystem>().Stop();
var ps = flameThrowe.effectObject.GetComponentsInChildren<ParticleSystem>();
foreach (var p in ps) p.Stop();
flame = flameThrowe.effectObject.GetComponent<ParticleSystem>();
flameDamager = flameThrowe.effectObject.GetComponentInChildren<BoxCollider>();
}
flameThrowe.del = FlameThrowerAttack;
scan.del = Scan;
if (scan.effectObject) scan.effectObject.SetActive(false);
fireball.del = Fireball;
if (fireball.effectObject) fireball.effectObject.SetActive(false);
shield.del = Shield;
silentPeek.del = OnSilentPeek;
}
private void OnDisable()
{
if (shieldAnimationIsActive)
// Safety check: ensure shield flag is reset if player is disabled/respawning
IsShieldActive = false;
}
private void LateUpdate()
{
HandleInput();
MonitorAnimationState();
}
private void HandleInput()
{
// 1. Basic Checks
if (tpInput == null || tpInput.cc == null) return;
bool canCast = !isPlaying &&
!tpInput.cc.customAction &&
!tpInput.cc.IsAnimatorTag("special") &&
!tpInput.cc.IsAnimatorTag("LockMovement");
// 2. Trigger
if (actionInput.GetButtonDown() && canCast)
{
DisableShield();
TryCastCurrentSpell();
}
}
private void OnEnable()
private void TryCastCurrentSpell()
{
canPlayNoFaithClip = true;
canPlayCantDoClip = true;
}
// A. Get Item & Spell Definition
bItem selectedItem = GetEquippedSpellItem();
protected virtual void LateUpdate()
{
TriggerSpellAnimation();
AnimationBehaviour();
}
protected virtual void TriggerSpellAnimation()
{
bool playConditions = !isPlaying && tpInput != null && tpInput.cc != null &&
!(tpInput.cc.customAction || tpInput.cc.IsAnimatorTag("special") || tpInput.cc.IsAnimatorTag("LockMovement"));
if (actionInput.GetButtonDown() && playConditions)
TryToPlaySpellAnimation();
}
// ----------------------------------------------------------------------------------
// CORE CASTING LOGIC (COSTS & BONUSES)
// ----------------------------------------------------------------------------------
public void TryToPlaySpellAnimation()
{
selectedEffect = GetCurrentlySelectedPower();
if (selectedEffect == shield && shieldAnimationIsActive)
if (selectedItem == null)
{
TryToPlayCantDoThatYetClip();
// No item equipped in the selected slot
return;
}
if (selectedItem.spellDefinition == null)
{
Debug.LogWarning($"Item '{selectedItem.name}' is equipped but has no SpellDefinition assigned!");
return;
}
if (Player.Instance == null)
SpellDefinition spell = selectedItem.spellDefinition;
// B. Check Costs (Calculated by the Spell SO)
float cost = spell.GetFaithCost(Player.Instance);
if (Player.Instance.GetCurrentFaithValue() < cost)
{
Debug.LogError("Player.Instance is null. Cannot cast spell.");
PlayNoFaithFeedback();
return;
}
// --- 1. CALCULATE FINAL FAITH COST ---
float finalCost = currentSpellFaithCost;
bool isSilentPeek = selectedEffect == m_effects[(int)EffectType.SILENT_PEEK];
// C. Success! Consume Resources
Player.Instance.UpdateFaithCurrentValue(-cost);
// Effect: Bloom (General 20% Reduction)
if (Player.Instance.CurrentTrinketStats.effectBloom)
// Effect: Growth (Heal on Cast) - Global Trinket logic
if (Player.Instance.CurrentTrinketStats.effectGrowth)
{
finalCost *= 0.8f;
int healAmt = Mathf.Max(1, (int)(Player.Instance.MaxHealth * 0.02f));
Player.Instance.ThirdPersonController.ChangeHealth(healAmt);
}
// Effect: Angel Eye (Silent Peek is Free)
if (isSilentPeek && Player.Instance.CurrentTrinketStats.effectAngelEye)
// Handle Consumables (Scrolls)
if (selectedItem.destroyAfterUse)
{
finalCost = 0f;
// Uses the standard Invector logic to consume the item in the current slot
if (powersArea.currentSelectedSlot != null)
{
powersArea.UseItem(powersArea.currentSelectedSlot);
}
}
// Note: Zora's Focus (effectBreeze) logic for Magic Push cost was removed as requested.
// --- 2. CHECK & CONSUME ---
if (selectedEffect != null && finalCost <= Player.Instance.GetCurrentFaithValue())
// D. Snap Rotation (Instant snap before animation starts if needed)
if (_autoTargettingInstance != null && _autoTargettingInstance.CurrentTarget != null)
{
Player.Instance.UpdateFaithCurrentValue(-finalCost);
SnapLookTowardsAutoTarget();
}
// Effect: Growth (Heal on Cast)
if (Player.Instance.CurrentTrinketStats.effectGrowth)
// E. Play Animation
if (!string.IsNullOrEmpty(spell.animationClipName))
{
tpInput.cc.animator.CrossFadeInFixedTime(spell.animationClipName, 0.1f);
OnPlayAnimation.Invoke();
}
// F. Execute Spell Logic
// We pass 'this' (Monobehaviour) so the SO can start coroutines here.
Transform target = _autoTargettingInstance != null ?
(_autoTargettingInstance.CurrentTarget != null ? _autoTargettingInstance.CurrentTarget.transform : null)
: null;
spell.Cast(this, target);
}
// =================================================================================================
// HELPER METHODS (Called by SpellDefinitions)
// =================================================================================================
/// <summary>
/// Gets the currently equipped item in the Powers area.
/// </summary>
public bItem GetEquippedSpellItem()
{
if (powersArea == null) return null;
return powersArea.currentEquippedItem;
}
/// <summary>
/// Coroutine used by Spells to rotate the player towards the target over time.
/// </summary>
public IEnumerator RotateTowardsTargetRoutine(Transform target, float duration)
{
if (duration <= 0) yield break;
float timer = 0f;
while (timer < duration)
{
// Dynamic check: target might die or become null during rotation
Transform actualTarget = target;
// Fallback to AutoTarget if the passed target becomes null but a new one exists
if (actualTarget == null && _autoTargettingInstance != null && _autoTargettingInstance.CurrentTarget != null)
{
int healAmt = Mathf.Max(1, (int)(Player.Instance.MaxHealth * 0.02f));
Player.Instance.ThirdPersonController.ChangeHealth(healAmt);
actualTarget = _autoTargettingInstance.CurrentTarget.transform;
}
animationClip = selectedEffect.animClipName;
// Snap Rotation
if (_autoTargettingInstance != null && _autoTargettingInstance.CurrentTarget != null)
if (actualTarget != null)
{
SnapLookTowardsAutoTarget();
}
Vector3 directionToTarget = actualTarget.position - transform.position;
directionToTarget.y = 0f;
// Play Animation
if (tpInput != null && tpInput.cc != null && tpInput.cc.animator != null)
{
tpInput.cc.animator.CrossFadeInFixedTime(animationClip, 0.1f);
OnPlayAnimation.Invoke();
triggerOnce = true;
}
else
{
Debug.LogError("Cannot play spell animation: tpInput or its components are null.");
return;
}
// Invoke Logic (Coroutine)
selectedEffect.del?.Invoke();
// Handle Consumables (Scrolls)
if (powersArea.equipSlots[equipAreaSelectedIndex].item.destroyAfterUse)
{
if (selectedEffect == silentPeek)
if (directionToTarget.sqrMagnitude > 0.0001f)
{
// Logic handled inside coroutine
}
else
{
powersArea.UseItem(powersArea.equipSlots[equipAreaSelectedIndex]);
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized);
// Using a high speed to ensure we catch up, or use the AutoTargetting speed settings
float speed = (_autoTargettingInstance != null) ? _autoTargettingInstance.playerRotationSpeed : 10f;
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, Time.deltaTime * speed * 50f);
}
}
}
else if (selectedEffect != null)
{
TryToPlayNoEnoughFaithClip();
}
}
// ----------------------------------------------------------------------------------
// SPELL: MAGIC PUSH
// ----------------------------------------------------------------------------------
public void MagicPushAttack()
{
if (lastPushRoutine != null) StopCoroutine(lastPushRoutine);
lastPushRoutine = StartCoroutine(MagicPushCoroutine());
}
private IEnumerator MagicPushCoroutine()
{
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
yield return TurnTowardTargetCoroutine(mpush.startTime);
// Zora's Focus scaling logic removed from here as requested.
// Magic Push is now standard size.
mpush.effectObject.SetActive(false);
mpush.effectObject.SetActive(true);
// Apply Soulfire Damage
ApplySoulfireDamage(mpush.effectObject);
yield return new WaitForSeconds(mpush.delay);
yield return new WaitForSeconds(mpush.endTime);
mpush.effectObject.SetActive(false);
yield return null;
}
// ----------------------------------------------------------------------------------
// SPELL: FLAME THROWER
// ----------------------------------------------------------------------------------
public void FlameThrowerAttack()
{
StartCoroutine(FlameThrowerhCoroutine());
}
private IEnumerator FlameThrowerhCoroutine()
{
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
yield return TurnTowardTargetCoroutine(flameThrowe.startTime);
flameDamager.enabled = true;
ApplySoulfireDamage(flameThrowe.effectObject);
flame.Play();
yield return new WaitForSeconds(flameThrowe.endTime);
flame.Stop();
yield return new WaitForSeconds(flameThrowe.delay);
flameDamager.enabled = false;
yield return null;
}
// ----------------------------------------------------------------------------------
// SPELL: FIREBALL (Soulfire Damage)
// ----------------------------------------------------------------------------------
public void Fireball()
{
StartCoroutine(FireballCoroutine());
}
private IEnumerator FireballCoroutine()
{
EffectDesc fireballDesc = m_effects[(int)EffectType.FIREBALL];
yield return TurnTowardTargetCoroutine(fireballDesc.startTime);
var fireballClone = Instantiate(fireballDesc.effectObject, fireballDesc.effectObject.transform.position, fireballDesc.effectObject.transform.rotation);
fireballClone.SetActive(true);
// Apply Soulfire Damage to the Clone
ApplySoulfireDamage(fireballClone);
RFX4_PhysicsMotion fireballMotionController = fireballClone.GetComponentInChildren<RFX4_PhysicsMotion>();
if (fireballMotionController != null)
{
fireballMotionController.CollisionEnter += EnableBrieflyFireballDamager;
}
vObjectDamage fireballDamageComponent = fireballClone.GetComponentInChildren<vObjectDamage>();
if (fireballDamageComponent != null && onHitFireball != null)
{
fireballDamageComponent.onHit.AddListener(onHitFireball);
}
AimFireball(fireballClone);
Destroy(fireballClone, 10f);
yield return null;
}
// ----------------------------------------------------------------------------------
// SPELL: SHIELD (Calmness)
// ----------------------------------------------------------------------------------
public void Shield()
{
StopCoroutine(nameof(ShieldCoroutine));
StartCoroutine(nameof(ShieldCoroutine));
}
private IEnumerator ShieldCoroutine()
{
shieldAnimationIsActive = true;
yield return new WaitForSeconds(shield.startTime);
shieldEffectIsActive = true;
shieldEffectController.InitializeEffect();
shield.effectObject.SetActive(true);
shieldCollisionController.shieldCollider.enabled = true;
// Effect: Calmness (Shield lasts 50% longer)
float finalDuration = shield.endTime;
if (Player.Instance.CurrentTrinketStats.effectCalmness)
{
finalDuration *= 1.5f;
}
yield return new WaitForSeconds(finalDuration);
shieldEffectController.DisableEffect();
yield return new WaitForSeconds(shield.delay / 2f);
shieldEffectIsActive = false;
shieldCollisionController.shieldCollider.enabled = false;
yield return new WaitForSeconds(shield.delay / 2f);
shield.effectObject.SetActive(false);
shieldAnimationIsActive = false;
}
private void DisableShield()
{
shieldEffectIsActive = false;
if (shieldCollisionController != null && shieldCollisionController.shieldCollider != null)
shieldCollisionController.shieldCollider.enabled = false;
shieldAnimationIsActive = false;
if (shield != null && shield.effectObject != null)
shield.effectObject.SetActive(false);
}
// ----------------------------------------------------------------------------------
// SPELL: SILENT PEEK (Angel Eye & Zora's Focus)
// ----------------------------------------------------------------------------------
[Button]
private void OnSilentPeek()
{
StopCoroutine(nameof(SilentPeekCoroutine));
StartCoroutine(nameof(SilentPeekCoroutine));
}
private IEnumerator SilentPeekCoroutine()
{
EffectDesc peek = m_effects[(int)EffectType.SILENT_PEEK];
yield return new WaitForSeconds(peek.startTime);
// Effect: Zora's Focus (effectBreeze)
// User Note: "enhanced covert gaze radius and length", "keep it in not fully implemented version"
// Implementation: We check the flag, but currently do not apply Radius/Length changes.
if (Player.Instance.CurrentTrinketStats.effectBreeze)
{
// Placeholder for future logic:
// float extendedDuration = peek.endTime * 1.5f;
// float extendedRadius = currentRadius * 1.3f;
}
if (SilentPeekController.instance.IsActive())
{
SilentPeekController.instance.SetActive(false);
}
else
{
if (peek.effectObject != null)
{
peek.effectObject.SetActive(false);
peek.effectObject.SetActive(true);
yield return new WaitForSeconds(peek.delay);
}
SilentPeekController.instance.SetActive(true, powersArea.equipSlots[equipAreaSelectedIndex].item);
}
yield return null;
}
// ----------------------------------------------------------------------------------
// SPELL: SCAN
// ----------------------------------------------------------------------------------
[Button]
public void Scan()
{
StartCoroutine(ScanCoroutine());
}
private IEnumerator ScanCoroutine()
{
EffectDesc scan = m_effects[(int)EffectType.SCAN];
yield return new WaitForSeconds(scan.startTime);
float maxRange = 50f;
float speed = maxRange / (scan.endTime - scan.startTime);
int mask = 1 << LayerMask.NameToLayer("Triggers") | 1 << LayerMask.NameToLayer("HiddenObject");
if (scan.effectObject)
{
scan.effectObject.SetActive(true);
VisualEffect effect = scan.effectObject.GetComponent<VisualEffect>();
effect.Play();
}
float waveEffectTimer = 0f;
float waveEffectDuration = scan.endTime - scan.startTime;
while (waveEffectTimer < waveEffectDuration)
{
Shader.SetGlobalFloat("_WaveTime", speed * waveEffectTimer);
waveEffectTimer += Time.deltaTime;
timer += Time.deltaTime;
yield return null;
}
Shader.SetGlobalFloat("_WaveTime", 0f);
var colliders = Physics.OverlapSphere(transform.position, maxRange, mask);
foreach (var c in colliders)
{
var h = c.gameObject.GetComponent<IScannable>();
if (h != null) h.OnScanned();
}
if (scan.effectObject) scan.effectObject.SetActive(false);
yield return null;
}
// ----------------------------------------------------------------------------------
// HELPERS
// ----------------------------------------------------------------------------------
// Applies Trinket Damage Multiplier (Soulfire) to any vObjectDamage found on object
private void ApplySoulfireDamage(GameObject obj)
/// <summary>
/// Helper to apply Trinket Damage Multipliers (Soulfire) to any instantiated spell object.
/// </summary>
public void ApplyDamageModifiers(GameObject spellObject)
{
if (obj == null) return;
var damageComps = obj.GetComponentsInChildren<vObjectDamage>();
if (spellObject == null || Player.Instance == null) return;
float mult = Player.Instance.CurrentTrinketStats.soulfireDamageMult;
// Only apply if multiplier is significant
// Only apply if multiplier is significant (not 1.0)
if (Mathf.Abs(mult - 1f) > 0.01f)
{
var damageComps = spellObject.GetComponentsInChildren<vObjectDamage>();
foreach (var comp in damageComps)
{
comp.damage.damageValue = Mathf.RoundToInt(comp.damage.damageValue * mult);
@@ -506,275 +215,78 @@ namespace Beyond
}
}
public EffectDesc GetCurrentlySelectedPower()
// =================================================================================================
// INTERNAL LOGIC
// =================================================================================================
private void MonitorAnimationState()
{
if (powersArea == null || equipAreaSelectedIndex < 0 || equipAreaSelectedIndex >= powersArea.equipSlots.Count ||
powersArea.equipSlots[equipAreaSelectedIndex] == null || powersArea.equipSlots[equipAreaSelectedIndex].item == null)
{
currentSpellFaithCost = int.MaxValue;
currentSelectedSpellName = "";
return null;
}
if (tpInput == null || tpInput.cc == null || tpInput.cc.animator == null) return;
bItem selectedSpellItem = powersArea.equipSlots[equipAreaSelectedIndex].item;
currentSelectedSpellName = selectedSpellItem.name;
currentSpellFaithCost = selectedSpellItem.GetItemAttribute(bItemAttributes.Faith).value;
// Check if we are currently playing a spell animation on the specific layer
var stateInfo = tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex);
// We consider it "Playing" if the tag is Spell, or if a spell animation is active
// Note: Your SpellDefinitions define the Clip Name.
// A generic way is checking the Tag if you set "Spell" tag in Animator,
// OR checking if we are in a transition to a spell.
// Simplified check based on your old code logic:
// Assuming all Spell Animations have the tag "Spell" or "Action" in the Animator
isPlaying = tpInput.cc.IsAnimatorTag("Spell") ||
tpInput.cc.IsAnimatorTag("special") ||
tpInput.cc.customAction;
return m_effects.Find(effect => effect.name == selectedSpellItem.name || effect.secondaryName == selectedSpellItem.name);
}
public void SelectPowerBasedOnArea(int newIndex)
{
equipAreaSelectedIndex = newIndex;
selectedEffect = GetCurrentlySelectedPower();
}
private void TryToPlayCantDoThatYetClip()
{
if (!canPlayCantDoClip) return;
canPlayCantDoClip = false;
DOVirtual.DelayedCall(1f, () => canPlayCantDoClip = true);
var text = "Spell is already active";
if (bItemCollectionDisplay.Instance != null)
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
if (Player.Instance != null)
Player.Instance.PlayICantDoThatYet();
}
private void TryToPlayNoEnoughFaithClip()
{
if (!canPlayNoFaithClip) return;
canPlayNoFaithClip = false;
DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true);
var text = "Not enough Faith";
if (bItemCollectionDisplay.Instance != null)
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
if (Player.Instance != null)
Player.Instance.PlayNoFaithClip();
}
protected virtual void AnimationBehaviour()
{
if (tpInput == null || tpInput.cc == null || tpInput.cc.animator == null || string.IsNullOrEmpty(animationClip))
{
isPlaying = false;
return;
}
isPlaying = tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip) ||
tpInput.cc.animator.GetNextAnimatorStateInfo(spellLayerIndex).IsName(animationClip);
if (isPlaying)
{
if (tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip) &&
tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).normalizedTime >= animationEnd)
{
if (triggerOnce)
{
triggerOnce = false;
OnEndAnimation.Invoke();
}
}
}
else
{
if (triggerOnce) triggerOnce = false;
}
}
private IEnumerator TurnTowardTargetCoroutine(float maxDuration)
{
if (_autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null)
{
if (maxDuration > 0) yield return new WaitForSeconds(maxDuration);
yield break;
}
float timeElapsed = 0;
while (timeElapsed < maxDuration)
{
if (_autoTargettingInstance.CurrentTarget != null)
{
vFSMBehaviourController currentTarget = _autoTargettingInstance.CurrentTarget;
Transform playerTransform = transform;
float distSqr = (currentTarget.transform.position - playerTransform.position).sqrMagnitude;
if (distSqr <= _autoTargettingInstance.maxTargetingDistance * _autoTargettingInstance.maxTargetingDistance)
{
if (_autoTargettingInstance.IsTargetInAngle(playerTransform, currentTarget, _autoTargettingInstance.targetingAngleThreshold))
{
Vector3 directionToTarget = currentTarget.transform.position - playerTransform.position;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude > 0.0001f)
{
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized);
playerTransform.rotation = Quaternion.RotateTowards(playerTransform.rotation, targetRotation, Time.deltaTime * _autoTargettingInstance.playerRotationSpeed);
}
}
}
}
else
{
yield break;
}
timeElapsed += Time.deltaTime;
yield return null;
}
// Optional: Trigger OnEndAnimation if needed, though most logic is now in the Coroutine
}
private void SnapLookTowardsAutoTarget()
{
if (_autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null) return;
vFSMBehaviourController currentTarget = _autoTargettingInstance.CurrentTarget;
Transform playerTransform = transform;
Transform target = _autoTargettingInstance.CurrentTarget.transform;
Vector3 direction = target.position - transform.position;
direction.y = 0f;
float distSqr = (currentTarget.transform.position - playerTransform.position).sqrMagnitude;
if (distSqr > _autoTargettingInstance.maxTargetingDistance * _autoTargettingInstance.maxTargetingDistance) return;
if (!_autoTargettingInstance.IsTargetInAngle(playerTransform, currentTarget, _autoTargettingInstance.targetingAngleThreshold)) return;
Vector3 directionToTarget = currentTarget.transform.position - playerTransform.position;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude > 0.0001f)
if (direction.sqrMagnitude > 0.001f)
{
playerTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized);
transform.rotation = Quaternion.LookRotation(direction.normalized);
}
}
private void AimFireball(GameObject fireballClone)
private void PlayNoFaithFeedback()
{
Vector3 aimDirection = transform.forward;
if (!canPlayNoFaithClip) return;
canPlayNoFaithClip = false;
// Feedback
if (bItemCollectionDisplay.Instance != null)
bItemCollectionDisplay.Instance.FadeText("Not enough Faith", 4, 0.25f);
if (Player.Instance != null)
Player.Instance.PlayNoFaithClip();
if (_autoTargettingInstance != null && _autoTargettingInstance.CurrentTarget != null)
// Reset cooldown
DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true);
}
public void RegisterSpellDamageEvents(GameObject spellInstance, SpellDefinition spellDef)
{
if (spellInstance == null) return;
// Find all damage components on the spawned object
var damageComps = spellInstance.GetComponentsInChildren<Invector.vObjectDamage>();
foreach (var comp in damageComps)
{
vFSMBehaviourController autoTarget = _autoTargettingInstance.CurrentTarget;
Transform playerTransform = transform;
float distSqrToAutoTarget = (autoTarget.transform.position - playerTransform.position).sqrMagnitude;
if (distSqrToAutoTarget <= _autoTargettingInstance.maxTargetingDistance * _autoTargettingInstance.maxTargetingDistance &&
_autoTargettingInstance.IsTargetInAngle(playerTransform, autoTarget, _autoTargettingInstance.targetingAngleThreshold))
// Invector's vObjectDamage event gives us the Collider directly
comp.onHit.AddListener((Collider hitCollider) =>
{
Vector3 targetPosition = autoTarget.transform.position;
targetPosition.y += fireballTargetYPositionOffset;
aimDirection = (targetPosition - fireballClone.transform.position).normalized;
}
}
if (aimDirection == transform.forward && lockOn != null && lockOn.isLockingOn && lockOn.currentTarget != null)
{
Vector3 targetPosition = lockOn.currentTarget.position;
targetPosition.y += fireballTargetYPositionOffset;
aimDirection = (targetPosition - fireballClone.transform.position).normalized;
}
else if (aimDirection == transform.forward && lockOn != null)
{
List<Transform> closeEnemies = lockOn.GetNearbyTargets();
if (closeEnemies.Count > 0)
{
Transform bestFallbackTarget = null;
float minAngle = float.MaxValue;
foreach (var enemyTransform in closeEnemies)
{
Vector3 directionToEnemyFromPlayer = (enemyTransform.position - transform.position).normalized;
float angleToEnemy = Vector3.Angle(transform.forward, directionToEnemyFromPlayer);
if (Vector3.Dot(transform.forward, directionToEnemyFromPlayer) > fireballAimerThreshold)
{
if (angleToEnemy < minAngle)
{
minAngle = angleToEnemy;
bestFallbackTarget = enemyTransform;
}
}
}
if (bestFallbackTarget != null)
{
Vector3 targetPosition = bestFallbackTarget.position;
targetPosition.y += fireballTargetYPositionOffset;
aimDirection = (targetPosition - fireballClone.transform.position).normalized;
}
}
}
Vector3 finalAimDirection = new Vector3(aimDirection.x, aimDirection.y + fireballAimerHeightAdjuster, aimDirection.z);
if (finalAimDirection.sqrMagnitude > 0.001f)
{
fireballClone.transform.rotation = Quaternion.LookRotation(finalAimDirection.normalized);
// Forward the event to anyone listening to MagicAttacks
OnSpellHit?.Invoke(spellDef, hitCollider);
});
}
}
private void EnableBrieflyFireballDamager(object sender, RFX4_PhysicsMotion.RFX4_CollisionInfo e)
{
RFX4_PhysicsMotion rFX4_PhysicsMotion = (RFX4_PhysicsMotion)sender;
CapsuleCollider collider = rFX4_PhysicsMotion.GetComponentInChildren<CapsuleCollider>();
if (collider != null) StartCoroutine(EnableBrieflyFireballDamagerCoroutine(collider));
}
private IEnumerator EnableBrieflyFireballDamagerCoroutine(CapsuleCollider collider)
{
collider.enabled = true;
yield return new WaitForSeconds(fireballDamagerDuration);
collider.enabled = false;
}
//placeholder for future methods related to magic attacks
/// <summary>
/// Spells call this to rotate the player before firing
/// </summary>
public IEnumerator RotateTowardsTargetRoutine(Transform target, float duration)
{
if (target == null || duration <= 0) yield break;
float timer = 0f;
while (timer < duration)
{
if (target == null) yield break;
Vector3 dir = (target.position - transform.position);
dir.y = 0;
if (dir.sqrMagnitude > 0.01f)
{
Quaternion look = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.RotateTowards(transform.rotation, look, Time.deltaTime * 500f); // Fast rotation
}
timer += Time.deltaTime;
yield return null;
}
}
/// <summary>
/// Helper to apply Trinket damage (Soulfire) to an object
/// </summary>
public void ApplyDamageModifiers(GameObject spellObject)
{
float mult = Player.Instance.CurrentTrinketStats.soulfireDamageMult;
if (Mathf.Abs(mult - 1f) < 0.01f) return;
var damages = spellObject.GetComponentsInChildren<Invector.vObjectDamage>();
foreach (var d in damages)
{
d.damage.damageValue = Mathf.RoundToInt(d.damage.damageValue * mult);
}
}
public bItem GetEquippedSpellItem()
{
if (powersArea != null && powersArea.equipSlots.Count > 0)
{
// Assuming the logic uses the current selection
// If powersArea tracks selection internally, use powersArea.currentEquippedItem
return powersArea.currentEquippedItem;
}
return null;
}
}
}

View File

@@ -4,14 +4,20 @@ using Invector;
namespace Beyond
{
[CreateAssetMenu(menuName = "Magic/Spells/Projectile Spell")]
[CreateAssetMenu(menuName = "Magic/Spells/Projectile (Fireball)")]
public class ProjectileSpell : SpellDefinition
{
[Header("Projectile Settings")]
public GameObject projectilePrefab;
public float spawnDelay = 0.2f; // Sync with animation hand throw
public float lifeTime = 5f;
public float aimHeightOffset = 1.0f;
public float spawnDelay = 0.2f;
public float lifeTime = 10f;
[Header("Spawn Adjustments")]
[Tooltip("Position relative to Player Root. X=Right, Y=Up, Z=Forward.\nExample: (0, 1.5, 1.0) is Chest Height, 1m forward.")]
public Vector3 spawnOffset = new Vector3(0f, 1.5f, 1.0f);
[Tooltip("Offset for aiming logic only. Usually slightly higher than the target's pivot.")]
public float aimHeightOffset = 0.75f;
public override void Cast(MagicAttacks caster, Transform target)
{
@@ -23,21 +29,44 @@ namespace Beyond
// 1. Rotate
yield return caster.RotateTowardsTargetRoutine(target, rotationDuration);
// 2. Wait for animation point
yield return new WaitForSeconds(Mathf.Max(0, spawnDelay - rotationDuration));
// 2. Wait
float remainingDelay = spawnDelay - rotationDuration;
if (remainingDelay > 0) yield return new WaitForSeconds(remainingDelay);
// 3. Calculate Spawn Position (Relative to Player)
// TransformPoint converts local (Offset) to world space based on Player position/rotation
Vector3 spawnPos = caster.transform.TransformPoint(spawnOffset);
// 4. Calculate Rotation (Aiming)
Quaternion spawnRot = caster.transform.rotation; // Default forward
// 3. Spawn
Vector3 spawnPos = caster.transform.position + caster.transform.forward + Vector3.up * 1.5f;
GameObject obj = Instantiate(projectilePrefab, spawnPos, caster.transform.rotation);
// 4. Aim
if (target != null)
{
Vector3 aimDir = (target.position + Vector3.up * aimHeightOffset) - spawnPos;
obj.transform.rotation = Quaternion.LookRotation(aimDir);
Vector3 targetPos = target.position;
targetPos.y += aimHeightOffset;
Vector3 aimDir = (targetPos - spawnPos).normalized;
// Slight arc adjustment
aimDir.y += 0.1f;
if (aimDir.sqrMagnitude > 0.001f)
spawnRot = Quaternion.LookRotation(aimDir);
}
// 5. Apply Modifiers
// 5. Instantiate
GameObject obj = Instantiate(projectilePrefab, spawnPos, spawnRot);
// 6. Ignore Collision with Caster (Crucial!)
Collider projectileCollider = obj.GetComponentInChildren<Collider>();
Collider playerCollider = caster.GetComponent<Collider>();
if (projectileCollider != null && playerCollider != null)
{
Physics.IgnoreCollision(projectileCollider, playerCollider);
}
// 7. Register Events & Modifiers
caster.RegisterSpellDamageEvents(obj, this);
caster.ApplyDamageModifiers(obj);
Destroy(obj, lifeTime);

View File

@@ -3,10 +3,18 @@ using Sirenix.OdinInspector;
namespace Beyond
{
public enum SpellCategory
{
Utility,
Defensive,
Offensive_Power, // Used for Shadow Slayer / Fireball logic
Offensive_Light
}
public abstract class SpellDefinition : ScriptableObject
{
[Header("General Settings")]
public string spellName;
public SpellCategory category;
public string animationClipName;
[TextArea] public string description;

View File

@@ -0,0 +1,27 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2fa1c233b63854f6d845066ccd8e48cd, type: 3}
m_Name: Spell_PurifyingBlaze
m_EditorClassIdentifier:
spellName: Purifying Blaze
category: 2
animationClipName: FlameThrower
description:
baseFaithCost: 10
castTime: 0.5
rotationDuration: 1
type: 2
effectPrefab: {fileID: 4995691561355407686, guid: 336783fc6edf8e94186b6d9fd74effed,
type: 3}
preCastDelay: 0
duration: 1.5
postEndDelay: 0.1

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 314c562ab8abc4807882b972f2458352
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -13,6 +13,7 @@ MonoBehaviour:
m_Name: Spell_QuantumBlast
m_EditorClassIdentifier:
spellName: Quantum Blast
category: 2
animationClipName: MagicPush
description:
baseFaithCost: 10

View File

@@ -13,6 +13,7 @@ MonoBehaviour:
m_Name: Spell_QuantumShield
m_EditorClassIdentifier:
spellName: Quantum Shield
category: 1
animationClipName: Shield
description:
baseFaithCost: 10

View File

@@ -13,6 +13,7 @@ MonoBehaviour:
m_Name: Spell_SparkOfJustice
m_EditorClassIdentifier:
spellName: Spark of Justice
category: 2
animationClipName: Fireball
description:
baseFaithCost: 10
@@ -22,4 +23,5 @@ MonoBehaviour:
type: 3}
spawnDelay: 0.85
lifeTime: 10
spawnOffset: {x: 0, y: 1.19, z: 1.35}
aimHeightOffset: 0.75

View File

@@ -18,8 +18,9 @@ public class SpellActivator : MonoBehaviour
if (!magicAttacks.isPlaying)
{
magicAttacks.TryToPlaySpellAnimation();
Debug.Log("executing:" + magicAttacks.m_selectedType);
//magicAttacks.TryToPlaySpellAnimation();
//Debug.Log("executing:" + magicAttacks.m_selectedType);
Debug.LogError("!!not implemented!!!");
}
}
}

View File

@@ -794,7 +794,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 11
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: 0baead00b71554981b1681361e85af78, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -849,7 +849,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 20
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: 593b751fb37ef4e1693026f13ecfb5ff, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -1315,7 +1315,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 20
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: 0baead00b71554981b1681361e85af78, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -1742,7 +1742,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 20
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: dc808b91d3d2c4c53873cfe6fc9f0305, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -1799,7 +1799,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 20
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: e97e46ee2bee6459aad693bfa6680277, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -1969,7 +1969,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 11
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: dc808b91d3d2c4c53873cfe6fc9f0305, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -2216,7 +2216,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 11
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: 51e2575f2639b4c1980f4d657aac4507, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -2568,7 +2568,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 20
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: 51e2575f2639b4c1980f4d657aac4507, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -2973,7 +2973,7 @@ MonoBehaviour:
- {fileID: -5307908156765189853}
- {fileID: 6848363969753274938}
- {fileID: 7127115710233842467}
inEdition: 1
inEdition: 0
itemsHidden: 1
--- !u!114 &13007762861001616
MonoBehaviour:
@@ -5059,7 +5059,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 20
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: 314c562ab8abc4807882b972f2458352, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}
@@ -5732,7 +5732,7 @@ MonoBehaviour:
secondaryDescription: Not used if cant be used
type: 11
trinketColor: 0
spellDefinition: {fileID: 0}
spellDefinition: {fileID: 11400000, guid: e97e46ee2bee6459aad693bfa6680277, type: 2}
icon: {fileID: 0}
secondaryIcon: {fileID: 0}
teriaryIcon: {fileID: 0}

View File

@@ -459,7 +459,7 @@ namespace Beyond
public virtual void OnReceiveAttack(vDamage damage, vIMeleeFighter attacker)
{
if (cc == null) return;
if (magicAttacks != null && magicAttacks.shieldEffectIsActive) return;
if (magicAttacks != null && magicAttacks.IsShieldActive) return;
if (!damage.ignoreDefense && isBlocking && meleeManager != null &&
meleeManager.CanBlockAttack(damage.sender.position))

View File

@@ -12,12 +12,19 @@ namespace Beyond
// Start is called before the first frame update
private void Start()
{
baseStepController.ConditionsAreMet += () => magicAttacks.isPlaying;
// [FIX] magicAttacks.isPlaying is private.
// We check the animator directly for the "Spell" tag or state.
baseStepController.ConditionsAreMet += CheckIsPlaying;
}
// Update is called once per frame
private void Update()
private bool CheckIsPlaying()
{
var animator = magicAttacks.GetComponent<Animator>();
if (animator == null) return false;
// Assuming layer 5 is spells (from your MagicAttacks constants)
var state = animator.GetCurrentAnimatorStateInfo(5);
return state.IsTag("Spell") || state.IsTag("special");
}
}
}

View File

@@ -12,7 +12,12 @@ namespace Beyond
// Start is called before the first frame update
private void Start()
{
baseStepController.ConditionsAreMet += () => magicAttacks.selectedEffect.secondaryName == "Covert Gaze ";
// [FIX] Checked the equipped item name instead of 'selectedEffect'
baseStepController.ConditionsAreMet += () =>
{
var item = magicAttacks.GetEquippedSpellItem();
return item != null && item.name.Contains("Covert Gaze");
};
}
}
}

View File

@@ -233,7 +233,16 @@ namespace Beyond {
if (m_currentItem.type == bItemType.QuantaPower || m_currentItem.type == bItemType.PowerScroll)
{
Player.Instance.Magic.SelectPowerBasedOnArea(currentIndex);
// --- FIX STARTS HERE ---
// Old logic: Player.Instance.Magic.SelectPowerBasedOnArea(currentIndex);
// New Logic: Directly tell the equipment area to select this slot.
// MagicAttacks automatically looks at whatever is equipped in this area.
if (equipArea != null)
{
equipArea.SetEquipSlot(currentIndex);
}
// --- FIX ENDS HERE ---
if (!InputNameToPress.IsNullOrWhitespace())
{
CrossPlatformInputManager.SetButtonDown(InputNameToPress);
@@ -263,5 +272,4 @@ namespace Beyond {
isLocked = false;
}
}
}
}