418 lines
15 KiB
C#
418 lines
15 KiB
C#
using System.Collections.Generic;
|
||
using ChocDino.UIFX;
|
||
using Cielonos.MainGame.Buffs.Character;
|
||
using Cielonos.MainGame.Characters;
|
||
using Cielonos.MainGame.Effects.Feedback;
|
||
using Cielonos.MainGame.UI;
|
||
using SLSUtilities.Feedback;
|
||
using SLSUtilities.General;
|
||
using SLSUtilities.FunctionalAnimation;
|
||
using SLSUtilities.WwiseAssistance;
|
||
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame.Inventory.Collections
|
||
{
|
||
public partial class Polychrome : MainWeaponBase
|
||
{
|
||
private PassionSystem _passionSystem;
|
||
|
||
private bool _canAirLightAttack;
|
||
private bool _canAirHeavyAttack;
|
||
|
||
protected override void Update()
|
||
{
|
||
if (player.inventorySc.equipmentSm.currentMainWeapon == this)
|
||
{
|
||
functionSm?.Update(player.selfTimeSm.DeltaTime);
|
||
}
|
||
}
|
||
|
||
public override void OnEquipped()
|
||
{
|
||
base.OnEquipped();
|
||
|
||
_currentKatanaParticle = string.Empty;
|
||
|
||
player.eventSm.onFirstJump.TryAdd(nameof(Polychrome), new PrioritizedAction(() =>
|
||
{
|
||
_canAirLightAttack = true;
|
||
_canAirHeavyAttack = true;
|
||
comboSm["AirLight"].Reset();
|
||
}));
|
||
|
||
_passionSystem = CombatManager.GetCombatSystem<PassionSystem>();
|
||
_passionSystem.OnLevelChanged += OnPassionLevelChanged;
|
||
|
||
RegisterFunctionsToAnimSc(StayBlocking);
|
||
viewObjects["Katana"].SetFadeAnim(0.2f);
|
||
viewObjects["Saya"].SetFadeAnim(0.2f);
|
||
|
||
UpdateViewObjectVisuals();
|
||
}
|
||
|
||
public override void OnUnequipped()
|
||
{
|
||
base.OnUnequipped();
|
||
_passionSystem.OnLevelChanged -= OnPassionLevelChanged;
|
||
player.eventSm.onFirstJump.Remove(nameof(Polychrome));
|
||
}
|
||
|
||
private void OnPassionLevelChanged(int oldLevel, int newLevel)
|
||
{
|
||
UpdateViewObjectVisuals();
|
||
}
|
||
|
||
public override void OnSwitchIn()
|
||
{
|
||
if (_passionSystem.LevelIndex >= 3)
|
||
{
|
||
List<Enemy> availableEnemies = CombatManager.EnemySm.GetEnemiesInRadius(player.transform.position, 4);
|
||
List<Enemy> disruptable = CombatManager.EnemySm.GetDisruptableEnemies(availableEnemies);
|
||
Enemy target = CombatManager.EnemySm.GetScoredEnemies(availableEnemies)
|
||
.ApplyScoreModifier(disruptable, 0f, 1f).BestEnemy();
|
||
|
||
if (PlayTargetedAnimation("DisruptionAttackB", target))
|
||
{
|
||
FeedbackClip timeScaleModifierClip = player.feedbackSc.GetFeedbackData("DisruptionStartup")
|
||
.Clip<TimeScaleModifierAction>("Time");
|
||
float duration = fullBodyFuncAnimSm.collection["DisruptionAttackB"]
|
||
.Interval(IntervalType.Startup).Duration * 2;
|
||
|
||
timeScaleModifierClip.duration = duration;
|
||
player.feedbackSc.PlayFeedback("DisruptionStartup");
|
||
comboSm.main.Reset();
|
||
_passionSystem.DecreasePassion(100, false);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
PlayTargetedAnimation("EquipBlock");
|
||
SetBlock(equipBlockData);
|
||
player.selfTimeSm.AddLocalTimer(0.4f, () =>
|
||
{
|
||
RemoveBlock(equipBlockData);
|
||
fullBodyFuncAnimSm.Stop("EquipBlock");
|
||
});
|
||
}
|
||
}
|
||
|
||
public override void OnPrimaryPress()
|
||
{
|
||
if (player.statusSm.HasStatus(StatusType.Stun))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (player.reactionSc.blockSm.HaveBlockSource(blockData.blockName))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (player.landMovementSc.isJumping)
|
||
{
|
||
if (!_canAirLightAttack || !functionSm["LightAttack"].IsAvailable())
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (PlayTargetedAnimation("AirAttack" + comboSm["AirLight"].GetNextNodeName("L")))
|
||
{
|
||
comboSm["AirLight"].NextCombo("L");
|
||
functionSm["LightAttack"].Execute();
|
||
|
||
if (comboSm["AirLight"].GetCurrentNodeName() == "L1")
|
||
{
|
||
_canAirLightAttack = false;
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
CharacterBase target = CombatManager.EnemySm.GetBestEnemy(5);
|
||
|
||
if (player.landMovementSc.isSprinting && functionSm["LightAttack"].IsAvailable())
|
||
{
|
||
comboSm.main.Reset();
|
||
if (PlayTargetedAnimation("RunAttack", target))
|
||
{
|
||
comboSm.main.NextCombo("L");
|
||
}
|
||
|
||
functionSm["LightAttack"].Execute();
|
||
return;
|
||
}
|
||
|
||
if (functionSm["LightAttack"].IsAvailable())
|
||
{
|
||
string nextNodeName = comboSm.main.GetNextNodeName("L");
|
||
bool keepAdsorption = nextNodeName is "L4" or "L5";
|
||
if (PlayTargetedAnimation("Attack" + comboSm.main.GetNextNodeName("L"), target, 1f, keepAdsorption))
|
||
{
|
||
float totalTime = 0f;
|
||
CombatManager.EnemySm.activeEnemiesList.ForEach(enemy =>
|
||
{
|
||
(enemy as Enemy).behaviorSc.DispatchContextEvent("PlayerLightAttack", totalTime);
|
||
});
|
||
|
||
comboSm.main.NextCombo("L");
|
||
functionSm["LightAttack"].Execute();
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool TryPlayParryAttack(Enemy parryTarget, List<Enemy> availableEnemies)
|
||
{
|
||
if (parryTarget != null && !availableEnemies.Contains(parryTarget))
|
||
{
|
||
float distance = Vector3.Distance(player.transform.position.Flatten(), parryTarget.transform.position.Flatten());
|
||
return PlayTargetedAnimation(distance > 2f ? "DodgeParryAttack" : "BlockParryAttack", parryTarget);
|
||
}
|
||
return PlayTargetedAnimation("BlockParryAttack");
|
||
}
|
||
|
||
public override void OnSecondaryPress()
|
||
{
|
||
if (player.statusSm.HasStatus(StatusType.Stun))
|
||
{
|
||
return;
|
||
}
|
||
|
||
List<Enemy> availableEnemies = CombatManager.EnemySm.GetEnemiesInRadius(player.transform.position, 4);
|
||
BlockSubmodule blockSm = player.reactionSc.blockSm;
|
||
if (!blockSm.afterPerfectBlockTimer.isCompleted)
|
||
{
|
||
Enemy blockedTarget = blockSm.perfectBlockedTarget as Enemy;
|
||
if (TryPlayParryAttack(blockedTarget, availableEnemies))
|
||
{
|
||
blockSm.afterPerfectBlockTimer.Complete();
|
||
RemoveBlock();
|
||
}
|
||
}
|
||
|
||
DodgeSubmodule dodgeSm = player.reactionSc.dodgeSm;
|
||
if (!dodgeSm.afterPerfectDodgeTimer.isCompleted)
|
||
{
|
||
Enemy dodgedTarget = dodgeSm.perfectDodgedTarget as Enemy;
|
||
if (TryPlayParryAttack(dodgedTarget, availableEnemies))
|
||
{
|
||
dodgeSm.afterPerfectDodgeTimer.Complete();
|
||
}
|
||
}
|
||
|
||
if (player.reactionSc.blockSm.HaveBlockSource(blockData.blockName))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (player.landMovementSc.isJumping)
|
||
{
|
||
if (!_canAirHeavyAttack || !functionSm["HeavyAttack"].IsAvailable())
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (PlayTargetedAnimation("AirAttackRStart"))
|
||
{
|
||
player.landMovementSc.ExtraJump(10f);
|
||
comboSm.main.Reset();
|
||
functionSm["HeavyAttack"].Execute();
|
||
_canAirLightAttack = false;
|
||
_canAirHeavyAttack = false;
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (player.inputSc.IsHoldingSpecialA)
|
||
{
|
||
if (functionSm["DisruptionAttack"].IsAvailable())
|
||
{
|
||
bool isEnhanced = _passionSystem.LevelIndex >= 3;
|
||
string suffix = isEnhanced ? "B" : "A";
|
||
|
||
List<Enemy> disruptable = CombatManager.EnemySm.GetDisruptableEnemies(availableEnemies);
|
||
Enemy target = CombatManager.EnemySm.GetScoredEnemies(availableEnemies)
|
||
.ApplyScoreModifier(disruptable, 0f, 1f).BestEnemy();
|
||
|
||
if (PlayTargetedAnimation("DisruptionAttack" + suffix, target))
|
||
{
|
||
if (disruptable.Count > 0)
|
||
{
|
||
FeedbackClip timeScaleModifierClip = player.feedbackSc.GetFeedbackData("DisruptionStartup")
|
||
.Clip<TimeScaleModifierAction>("Time");
|
||
float duration = fullBodyFuncAnimSm.collection["DisruptionAttack" + suffix]
|
||
.Interval(IntervalType.Startup).Duration * 2;
|
||
|
||
timeScaleModifierClip.duration = duration;
|
||
player.feedbackSc.PlayFeedback("DisruptionStartup");
|
||
}
|
||
|
||
if (isEnhanced)
|
||
{
|
||
_passionSystem.DecreasePassion(100f, false);
|
||
}
|
||
|
||
comboSm.main.Reset();
|
||
functionSm["DisruptionAttack"].Execute();
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if (functionSm["HeavyAttack"].IsAvailable())
|
||
{
|
||
Enemy target = CombatManager.EnemySm.GetBestEnemy(availableEnemies);
|
||
string nextNodeName = comboSm.main.GetNextNodeName("R");
|
||
bool keepAdsorption = nextNodeName is "RC";
|
||
if (PlayTargetedAnimation("Attack" + nextNodeName, target, 1f, keepAdsorption))
|
||
{
|
||
float totalTime = fullBodyFuncAnimSm.GetIntervalScaledDuration(IntervalType.Startup) - 0.2f;
|
||
CombatManager.EnemySm.activeEnemiesList.ForEach(enemy =>
|
||
{
|
||
(enemy as Enemy)!.behaviorSc.DispatchContextEvent("PlayerHeavyAttack", totalTime);
|
||
});
|
||
comboSm.main.NextCombo("R");
|
||
functionSm["HeavyAttack"].Execute();
|
||
}
|
||
}
|
||
}
|
||
|
||
public override void OnSpecialCPress()
|
||
{
|
||
comboSm.main.Reset();
|
||
SetBlock();
|
||
|
||
if (!player.statusSm.HasStatus(StatusType.Stun))
|
||
{
|
||
CharacterBase target = CombatManager.EnemySm.GetBestEnemy(5);
|
||
if (fullBodyFuncAnimSm.Stop(DisruptionType.ForcedAction))
|
||
{
|
||
PlayTargetedAnimation("Block", target, 2f, false, true, upperBodyFuncAnimSm, 1f, 0.1f, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
public override void OnSpecialCRelease()
|
||
{
|
||
if (upperBodyFuncAnimSm.currentRuntimeFuncAnim is { animationName: "Block" })
|
||
{
|
||
upperBodyFuncAnimSm.Stop(DisruptionType.ForcedAction);
|
||
}
|
||
|
||
player.selfTimeSm.AddLocalTimer(0.04f, () => RemoveBlock());
|
||
}
|
||
|
||
public override void OnSpecialBPress()
|
||
{
|
||
player.operationSc.Dodge();
|
||
DodgeSource defaultDodge = DodgeSource.Default(player);
|
||
player.reactionSc.dodgeSm.ApplyDodge(defaultDodge);
|
||
player.reactionSc.dodgeSm.GetCurrentDodgeSource().PerfectDodge(null);
|
||
player.reactionSc.dodgeSm.RemoveDodge("DefaultDodge");
|
||
}
|
||
}
|
||
|
||
public partial class Polychrome
|
||
{
|
||
private string _currentKatanaParticle;
|
||
|
||
private void UpdateViewObjectVisuals()
|
||
{
|
||
if (_passionSystem == null) return;
|
||
|
||
int level = _passionSystem.LevelIndex;
|
||
|
||
// 开关粒子特效
|
||
if (level < 3) // C, B,A级
|
||
{
|
||
if (_currentKatanaParticle != string.Empty)
|
||
{
|
||
GetParticle().Stop();
|
||
_currentKatanaParticle = string.Empty;
|
||
}
|
||
}
|
||
else if (level <= 4) // S,SS级 (2)
|
||
{
|
||
if (_currentKatanaParticle != "ParticleStage0")
|
||
{
|
||
if (_currentKatanaParticle != string.Empty)
|
||
{
|
||
GetParticle().Stop();
|
||
}
|
||
_currentKatanaParticle = "ParticleStage0";
|
||
GetParticle().Play();
|
||
}
|
||
}
|
||
else // SSS级及以上
|
||
{
|
||
if (_currentKatanaParticle != "ParticleStage1")
|
||
{
|
||
if (_currentKatanaParticle != string.Empty)
|
||
{
|
||
GetParticle().Stop();
|
||
}
|
||
_currentKatanaParticle = "ParticleStage1";
|
||
GetParticle().Play();
|
||
}
|
||
}
|
||
|
||
return;
|
||
|
||
ParticleSystem GetParticle()
|
||
{
|
||
return viewObjects["Katana"].functionalParts.TryGetValue(_currentKatanaParticle, out GameObject particleObj) ?
|
||
particleObj.GetComponent<ParticleSystem>() : null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public partial class Polychrome
|
||
{
|
||
public override Dictionary<string, string> GetDescriptionArgs()
|
||
{
|
||
var args = new Dictionary<string, string>();
|
||
if (attackData == null) return args;
|
||
|
||
// TODO: 将下列 AttackUnit key 替换为 AttackData ScriptableObject 中的实际 key 名
|
||
TryPopulateAttack(args, "light", "ProbingAttack");
|
||
// TryPopulateAttack(args, "heavy", "HeavySlash");
|
||
// TryPopulateAttack(args, "disruption_a", "DisruptionA");
|
||
// TryPopulateAttack(args, "disruption_b", "DisruptionB");
|
||
// TryPopulateAttack(args, "disruption_c", "DisruptionC");
|
||
|
||
return args;
|
||
}
|
||
|
||
private void TryPopulateAttack(Dictionary<string, string> args, string prefix, string unitKey)
|
||
{
|
||
if (attackData.attackUnits.TryGetValue(unitKey, out AttackUnit unit))
|
||
{
|
||
DisplayTextResolver.PopulateAttackArgs(args, prefix, unit, player);
|
||
}
|
||
}
|
||
}
|
||
|
||
// =================================================================
|
||
// 以下为已废弃的 ExtraUIContainer 相关代码 / Deprecated ExtraUIContainer Codes
|
||
// =================================================================
|
||
/*
|
||
public partial class Polychrome
|
||
{
|
||
private PolychromeExtraUIContainer ExtraUIContainer => extraUIContainer as PolychromeExtraUIContainer;
|
||
|
||
// OnEquipped:
|
||
// extraUIContainer = Instantiate(extraUIContainerPrefab, PlayerCanvas.MainWeaponUIArea.transform).GetComponent<MainWeaponExtraUIContainer>();
|
||
// extraUIContainer.mainWeapon = this;
|
||
|
||
// OnUnequipped:
|
||
// Destroy(extraUIContainer.gameObject);
|
||
|
||
// UpdateVisuals:
|
||
// if (ExtraUIContainer != null)
|
||
// {
|
||
// ExtraUIContainer.SetStars(level);
|
||
// }
|
||
}
|
||
*/
|
||
} |