added "finish" button to dialogue UI, implementation of many, many trinket's effects, autot-argetting fix on death, added script for particles/player following

This commit is contained in:
2025-12-19 11:39:39 +01:00
parent 56f7fb4aaa
commit f624eeeeb6
14 changed files with 3062 additions and 1774 deletions

View File

@@ -5,6 +5,7 @@ using System.Linq;
using Invector.vCharacterController.AI.FSMBehaviour;
using Beyond;
using System;
using Invector; // Required for vDamage
namespace Beyond
{
@@ -70,14 +71,23 @@ namespace Beyond
_playerController = Player.Instance.GetComponent<bThirdPersonController>();
if (_playerController == null)
{
Debug.LogError("AutoTargetting: Could not find bThirdPersonController on Player.Instance! Custom roll rotation may not work correctly.");
Debug.LogError("AutoTargetting: Could not find bThirdPersonController on Player.Instance!");
}
else
{
// Subscribe to Player Death
_playerController.onDead.AddListener(OnPlayerDead);
}
_gameStateManager = GameStateManager.Instance;
if (_gameStateManager != null)
{
_gameStateManager.m_OnStateChanged.AddListener(HandleGameStateChanged);
HandleGameStateChanged(_gameStateManager.CurrentState);
// Initial check, but avoid running if dead
if(_playerController != null && _playerController.currentHealth > 0)
{
HandleGameStateChanged(_gameStateManager.CurrentState);
}
}
else
{
@@ -88,11 +98,6 @@ namespace Beyond
if (targetLockSystem == null)
{
targetLockSystem = Player.Instance.GetComponentInChildren<bLockOn>(true);
if (targetLockSystem == null)
{
Debug.LogWarning("AutoTargetting: bLockOn system not found. Auto-lock will be disabled.");
autoLockSelectedTarget = false;
}
}
if (targetLockSystem != null)
@@ -106,8 +111,13 @@ namespace Beyond
{
if (_gameStateManager != null) _gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged);
StopAndClearAllFadeCoroutines();
// Unsubscribe from Player Death
if (_playerController != null)
{
_playerController.onDead.RemoveListener(OnPlayerDead);
}
StopAndClearAllFadeCoroutines();
if (_targetingLoopCoroutine != null) StopCoroutine(_targetingLoopCoroutine);
if (targetLockSystem != null)
@@ -120,6 +130,36 @@ namespace Beyond
#endregion
#region Core Logic
private void OnPlayerDead(GameObject deadObject)
{
// Immediately clear targets and stop the loop when player dies
ClearTarget(false);
if (_targetingLoopCoroutine != null)
{
StopCoroutine(_targetingLoopCoroutine);
_targetingLoopCoroutine = null;
}
if (targetLockSystem != null)
{
targetLockSystem.SetLockOn(false);
}
}
public void ResetSystem()
{
ClearTarget(false);
_manualSwitchCooldownActive = false;
_manualSwitchCooldownTimer = 0f;
// Restart the loop if we are in combat
if (_gameStateManager != null && _gameStateManager.CurrentState == GameStateManager.State.COMBAT)
{
if (_targetingLoopCoroutine == null) _targetingLoopCoroutine = StartCoroutine(TargetingLoop());
}
}
private void StopAndClearAllFadeCoroutines()
{
@@ -135,6 +175,9 @@ namespace Beyond
private void HandleGameStateChanged(GameStateManager.State newState)
{
// Don't start loops if player is dead
if (_playerController != null && _playerController.currentHealth <= 0) return;
if (newState == GameStateManager.State.COMBAT)
{
if (_targetingLoopCoroutine == null) _targetingLoopCoroutine = StartCoroutine(TargetingLoop());
@@ -155,6 +198,11 @@ namespace Beyond
{
while (true)
{
if (_playerController != null && _playerController.currentHealth <= 0)
{
yield break;
}
if (_manualSwitchCooldownActive)
{
_manualSwitchCooldownTimer -= targetingInterval;
@@ -174,7 +222,6 @@ namespace Beyond
{
if (_playerTransform == null || _gameStateManager == null || _manualSwitchCooldownActive) return;
// Step 1: Always find the absolute best candidate in range right now.
vFSMBehaviourController bestCandidate = null;
float minDistanceSqr = maxTargetingDistance * maxTargetingDistance;
HashSet<vFSMBehaviourController> combatControllers = _gameStateManager.GetActiveCombatcontrollers();
@@ -195,13 +242,11 @@ namespace Beyond
}
}
// Step 2: If the best candidate is different from our current one, switch the highlight.
if (CurrentTarget != bestCandidate)
{
SetNewTarget(bestCandidate);
}
// Step 3: Every update, evaluate and apply the correct lock-on state for the current target.
UpdateLockOnState();
}
@@ -209,7 +254,6 @@ namespace Beyond
{
if (targetLockSystem == null || _playerTransform == null) return;
// Determine if the target *should* be locked based on distance rules.
bool shouldBeLocked = false;
if (CurrentTarget != null && (autoLockSelectedTarget || alwaysLockOnInCombat))
{
@@ -217,21 +261,15 @@ namespace Beyond
if (targetLockSystem.isLockingOn)
{
// If already locked, stay locked unless we are beyond the unlock threshold.
shouldBeLocked = distanceToTarget <= unlockDistanceThreshold;
}
else
{
// If not locked, we only engage the lock if we are within the auto-lock distance.
shouldBeLocked = distanceToTarget <= autoLockOnDistance;
}
}
// Synchronize the desired state with the lock-on system.
Transform desiredLockTarget = shouldBeLocked ? CurrentTarget.transform : null;
// --- THIS IS THE FIX ---
// We now pass the 'shouldBeLocked' boolean to tell the system whether to lock or unlock.
targetLockSystem.ManuallySetLockOnTarget(desiredLockTarget, shouldBeLocked);
if (alwaysLockOnInCombat && desiredLockTarget != null && !targetLockSystem.isLockingOn)
@@ -261,11 +299,7 @@ namespace Beyond
public void ExecuteRotationTowardsCurrentTarget(float deltaTime)
{
if (_playerController != null && !_playerController.enabled)
{
return;
}
if (_playerController != null && !_playerController.enabled) return;
if (CurrentTarget == null || _playerTransform == null) return;
Vector3 directionToTarget = CurrentTarget.transform.position - _playerTransform.position;
@@ -311,6 +345,16 @@ namespace Beyond
#region Helper Methods
// --- Restored Method ---
public float GetCurrentTargetHealth()
{
if (CurrentTarget != null && CurrentTarget.aiController != null)
{
return CurrentTarget.aiController.currentHealth;
}
return -1f;
}
public bool IsTargetInAngle(Transform sourceTransform, vFSMBehaviourController targetAI, float angleThreshold)
{
if (targetAI == null || sourceTransform == null) return false;
@@ -329,15 +373,6 @@ namespace Beyond
return distSqr <= (maxTargetingDistance * maxTargetingDistance);
}
public float GetCurrentTargetHealth()
{
if (CurrentTarget != null && CurrentTarget.aiController != null)
{
return CurrentTarget.aiController.currentHealth;
}
return -1f;
}
public void ClearTarget(bool findNewOneImmediately)
{
if (targetLockSystem != null)
@@ -349,7 +384,7 @@ namespace Beyond
if (findNewOneImmediately && _gameStateManager != null && _gameStateManager.CurrentState == GameStateManager.State.COMBAT)
{
if (!_manualSwitchCooldownActive)
if (_playerController != null && _playerController.currentHealth > 0 && !_manualSwitchCooldownActive)
{
UpdateTarget();
}
@@ -359,7 +394,6 @@ namespace Beyond
#endregion
#region Visuals
private Renderer[] GetTargetRenderers(vFSMBehaviourController targetController)
{
if (targetController == null) return new Renderer[0];
@@ -399,7 +433,6 @@ namespace Beyond
}
if (material != null) material.SetColor(materialHighlightPropertyName, toValue);
}
#endregion
}
}

View File

@@ -75,6 +75,7 @@ namespace Beyond
public bMeleeCombatInput MeleeCombatInput => m_meleeCombatInput;
// --- TRINKET SYSTEM INTEGRATION START ---
// Initialize default values to 1f to prevent divide-by-zero or zero-stat issues on startup
private TrinketManager.TrinketStats m_trinketStats = new TrinketManager.TrinketStats
{
healthMult = 1f,
@@ -85,7 +86,8 @@ namespace Beyond
faithRegenMult = 1f,
attackSpeedMult = 1f,
thornDamageMult = 1f,
staminaMult = 1f
staminaMult = 1f,
soulfireDamageMult = 1f
};
public TrinketManager.TrinketStats CurrentTrinketStats => m_trinketStats;
@@ -93,29 +95,66 @@ namespace Beyond
public void UpdateTrinketStats(TrinketManager.TrinketStats newStats)
{
m_trinketStats = newStats;
// Force stats recalculation
UpdatePlayerStatistics();
// 1. Recalculate Base Stats (Health/Faith/Stamina/Natural Regen)
// This sets m_vController.healthRecovery to the base natural value (from Maturity/Brightness)
UpdatePlayerStatistics();
// Apply immediate effects
if (m_vController)
// 2. Apply Speed & Animation Speed
if (m_vController)
{
m_vController.speedMultiplier = m_trinketStats.speedMult;
// 2. Apply Attack Speed (UI Method)
if (m_vController.animator)
m_vController.animator.SetFloat("AttackSpeed", m_trinketStats.attackSpeedMult);
// --- HEALTH REGEN LOGIC START ---
// Start with the base natural rate we just calculated in UpdatePlayerStatistics
float totalRegenRate = m_vController.healthRecovery;
float totalRegenCap = 1f; // Default 100% cap
// Add Passive Trinket Regen (e.g. "Growth" effect: Regen up to 50%)
if (m_trinketStats.effectGrowth)
{
// Example: +2 HP/sec up to 50%
totalRegenRate += 2f;
totalRegenCap = 0.5f;
}
// If you cast to bThirdPersonController, you can set the Cap
if (m_vController is bThirdPersonController bController)
{
bController.healthRecoveryCap = totalRegenCap;
}
// Update the final rate on the controller
m_vController.SetHealthRecovery(totalRegenRate);
// --- HEALTH REGEN LOGIC END ---
}
// 3. Apply Damage Multiplier (NEW)
// 3. Apply Damage Multipliers
if (m_meleeManager)
{
// Calculate the base multiplier from stats
// Start with the base multiplier from items
float totalDamageMult = m_trinketStats.damageMult;
totalDamageMult += 0.10f;
// Send to Melee Manager
// "The Darkening": Bonus dmg
if (m_trinketStats.effectDarkening)
{
totalDamageMult += 0.10f;
}
// "Determination": Combo finish +5% damage
// (Global application for now)
if (m_trinketStats.effectDetermination)
{
totalDamageMult += 0.05f;
}
// Send final value to your modified vMeleeManager
// NOTE: Requires the SetGlobalDamageMultiplier method added to vMeleeManager
m_meleeManager.SetGlobalDamageMultiplier(totalDamageMult);
}
}
// --- TRINKET SYSTEM INTEGRATION END ---
@@ -172,6 +211,7 @@ namespace Beyond
private float faithBaseMaxValue = 100f;
private System.Action onMenuScrollClosed;
// Updated Action signature to pass 3 floats for UI (Health, Faith, Stamina)
public System.Action<float, float, float> onStatsUpdated;
private UnityAction<Transform> onDialogueEnded;
private bLockOn m_lockOn;
@@ -231,28 +271,26 @@ namespace Beyond
private void OnDamageHit(vHitInfo arg0)
{
// Slow motion logic (existing)
// Slow motion logic
if (slowMoOnHtScale < 1f - float.Epsilon)
{
TimeController.Instance.Reset();
}
// 1. Health Vampirism
// 1. Health Vampirism (Vitality)
if (m_trinketStats.effectHealthVampirism)
{
// Logic: Heal 2% of Player's Max Health per hit
// Mathf.Max ensures we always heal at least 1 HP
// Heal 2% of Max Health per hit, min 1 HP
int healAmount = Mathf.Max(1, (int)(MaxHealth * 0.02f));
m_vController.ChangeHealth(healAmount);
}
// 2. Faith Vampirism
// 2. Faith Vampirism (Trust)
if (m_trinketStats.effectFaithVampirism)
{
// Logic: Add 1 Faith point per hit
// Add 1 Faith point per hit
UpdateFaithCurrentValue(1);
}
// ----------------------------
}
private void OnConversationStarted(Transform transform)
@@ -311,7 +349,7 @@ namespace Beyond
controller.RemoveAnimatorTags();
}
// ... [Audio Play Methods - kept same] ...
// ... [Audio Play Methods] ...
public void PlayNoFaithClip() { PlayRandomSound(m_noFaithClips); }
private void PlayRandomSound(AudioClip[] sounds)
{
@@ -367,14 +405,32 @@ namespace Beyond
public void OnReceivedDamage(vDamage damage)
{
if (m_cutScenePlaying) return;
// --- TRINKET DEFENSE CALCULATION ---
// defenseMult of 0.9 means 90% damage taken (10% reduction)
// --- 1. DEFENSE CALCULATION ---
// Example: 0.9 defenseMult = 90% damage taken (10% reduction)
if (Mathf.Abs(m_trinketStats.defenseMult - 1f) > float.Epsilon)
{
damage.damageValue = (int)(damage.damageValue * m_trinketStats.defenseMult);
}
// -----------------------------------
// --- 2. THORN DAMAGE (Reflect Damage) ---
if (m_trinketStats.thornDamageMult > 1f && damage.sender != null)
{
// Calculate reflect amount (Base damage * (Mult - 1))
int thornVal = (int)(damage.damageValue * (m_trinketStats.thornDamageMult - 1f));
if (thornVal > 0)
{
vDamage reflectDmg = new vDamage(thornVal);
reflectDmg.sender = transform;
reflectDmg.damageType = "Thorns";
reflectDmg.reaction_id = -1; // No flinch
// Uses Invector extension method to apply damage to sender
damage.sender.gameObject.ApplyDamage(reflectDmg);
}
}
// ----------------------------------------
#if UNITY_IOS && !UNITY_EDITOR
HapticEngine.ImpactFeedbackHeavy();
@@ -396,6 +452,8 @@ namespace Beyond
if (m_Respawner) m_Respawner.SaveRespawnPoint();
}
// ... [Quest and Attribute Methods] ...
public List<Quest> GetAllGuilts()
{
if (!m_questJournal)
@@ -525,12 +583,14 @@ namespace Beyond
// --- TRINKET INTEGRATION IN STATS CALCULATION ---
// 1. Calculate specific total multipliers for UI scaling
// 1. Calculate specific total multipliers
float totalHealthMult = finalMultiplier * m_trinketStats.healthMult;
float totalFaithMult = finalMultiplier * m_trinketStats.faithMult;
float totalStaminaMult = finalMultiplier * (m_trinketStats.staminaMult > 0 ? m_trinketStats.staminaMult : 1f); // Handle 0 default
// Handle 0 default for Stamina
float validStaminaMult = m_trinketStats.staminaMult > 0 ? m_trinketStats.staminaMult : 1f;
float totalStaminaMult = finalMultiplier * validStaminaMult;
// 2. Capture Current Health Percentage BEFORE changes
// This prevents the health bar from looking empty when Max HP increases
float healthPercent = m_vController.maxHealth > 0 ? m_vController.currentHealth / m_vController.maxHealth : 1f;
// 3. Apply Stats
@@ -544,12 +604,16 @@ namespace Beyond
m_vController.maxHealth = newMaxHealth;
// Apply Proportional Current Health
// We update the current health to match the previous percentage
//m_vController.currentHealth = Mathf.RoundToInt(newMaxHealth * healthPercent);
// Uses ChangeHealth() because currentHealth has a protected setter
m_vController.ChangeHealth(Mathf.RoundToInt(newMaxHealth * healthPercent));
// Stamina
m_vController.maxStamina = Mathf.Round(totalStaminaMult * staminaBaseMaxValue);
m_vController.staminaRecovery = (finalMultiplier * staminaBaseRegenValue);
// Natural Health Recovery (From Maturity/Brightness)
// We set the BASE here. UpdateTrinketStats will later add any trinket bonuses to this value.
m_vController.healthRecovery = finalMultiplier * healthBaseRegenValue;
// ------------------------------------------------

File diff suppressed because it is too large Load Diff