Implement passive relics and six-family defense towers
This commit is contained in:
@@ -439,6 +439,19 @@ namespace BumpCombat.Player
|
||||
ResolveMovementGainPerSecond() * actualMovementTime));
|
||||
}
|
||||
|
||||
public bool TryRestorePassiveRelicGauge(float maxGaugeFraction)
|
||||
{
|
||||
if (CurrentArtifact == null
|
||||
|| maxGaugeFraction <= 0f
|
||||
|| CurrentGauge >= MaxGauge)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddGauge(MaxGauge * maxGaugeFraction);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddNormalHitCharge()
|
||||
{
|
||||
if (IsExecutingArtifact
|
||||
@@ -2213,14 +2226,32 @@ namespace BumpCombat.Player
|
||||
return false;
|
||||
}
|
||||
|
||||
public Vector2 GetPassiveRelicUseOrigin() => lastArtifactUsePosition;
|
||||
public Vector2 GetPassiveRelicUseDirection() => GetUseDirection();
|
||||
|
||||
public bool TryEmitPassiveRelicEcho(
|
||||
Vector2 origin,
|
||||
Vector2 direction,
|
||||
float damageMultiplier) =>
|
||||
EmitPassiveRelicAttack(CurrentArtifact, false, origin, direction, damageMultiplier);
|
||||
|
||||
public bool TryEmitPassiveRelicRepeat(
|
||||
ActiveArtifactDefinition definition,
|
||||
bool charged,
|
||||
Vector2 origin,
|
||||
Vector2 direction,
|
||||
float damageMultiplier) =>
|
||||
EmitPassiveRelicAttack(definition, charged, origin, direction, damageMultiplier);
|
||||
|
||||
private bool EmitPassiveRelicAttack(
|
||||
ActiveArtifactDefinition definition,
|
||||
bool charged,
|
||||
Vector2 origin,
|
||||
Vector2 direction,
|
||||
float damageMultiplier)
|
||||
{
|
||||
ActiveArtifactDefinition definition = CurrentArtifact;
|
||||
if (definition == null
|
||||
|| definition.NormalDamage <= 0f
|
||||
|| (charged ? definition.ChargedDamage : definition.NormalDamage) <= 0f
|
||||
|| damageMultiplier <= 0f
|
||||
|| definition.Effect == ActiveArtifactEffect.Pulse)
|
||||
{
|
||||
@@ -2236,18 +2267,18 @@ namespace BumpCombat.Player
|
||||
direction.Normalize();
|
||||
int castIdentity = ++nextArtifactCastIdentity;
|
||||
float echoIgniteTickDamage = playerStats.CalculateDamage(
|
||||
definition.NormalIgniteTickDamage * damageMultiplier,
|
||||
(charged ? definition.ChargedIgniteTickDamage : definition.NormalIgniteTickDamage) * damageMultiplier,
|
||||
definition.DamageTag);
|
||||
if (definition.Effect == ActiveArtifactEffect.ChainLightning)
|
||||
{
|
||||
EnemyController first = FindInitialChainTarget(origin, definition.NormalRange);
|
||||
EnemyController first = FindInitialChainTarget(origin, (charged ? definition.ChargedRange : definition.NormalRange));
|
||||
if (first == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool applied = false;
|
||||
foreach (ChainStrike strike in PlanArcChain(definition, first, origin))
|
||||
foreach (ChainStrike strike in (charged ? PlanBranchingChain(definition, first, origin) : PlanArcChain(definition, first, origin)))
|
||||
{
|
||||
applied |= ApplyArtifactHit(
|
||||
strike.Enemy,
|
||||
@@ -2256,7 +2287,7 @@ namespace BumpCombat.Player
|
||||
(strike.End - strike.Start).normalized,
|
||||
strike.Knockback,
|
||||
true,
|
||||
false,
|
||||
charged,
|
||||
echoIgniteTickDamage,
|
||||
castIdentity,
|
||||
true);
|
||||
@@ -2266,18 +2297,18 @@ namespace BumpCombat.Player
|
||||
}
|
||||
|
||||
Vector2 center = origin;
|
||||
Vector2 radii = new Vector2(definition.NormalRange, definition.NormalRange);
|
||||
float rayRange = definition.NormalRange;
|
||||
float rayHalfWidth = Mathf.Max(0.05f, definition.NormalWidth * 0.5f);
|
||||
Vector2 radii = new Vector2((charged ? definition.ChargedRange : definition.NormalRange), (charged ? definition.ChargedRange : definition.NormalRange));
|
||||
float rayRange = (charged ? definition.ChargedRange : definition.NormalRange);
|
||||
float rayHalfWidth = Mathf.Max(0.05f, (charged ? definition.ChargedWidth : definition.NormalWidth) * 0.5f);
|
||||
switch (definition.Effect)
|
||||
{
|
||||
case ActiveArtifactEffect.Cyclone:
|
||||
center += GetCycloneHitCenterOffset(definition.NormalRange, false);
|
||||
radii = GetCycloneHitRadii(definition.NormalRange, false);
|
||||
center += GetCycloneHitCenterOffset((charged ? definition.ChargedRange : definition.NormalRange), charged);
|
||||
radii = GetCycloneHitRadii((charged ? definition.ChargedRange : definition.NormalRange), charged);
|
||||
break;
|
||||
case ActiveArtifactEffect.ThunderCrash:
|
||||
center = origin + direction * 0.45f;
|
||||
radii = GetThunderCrashHitRadii(definition.NormalRange, direction);
|
||||
radii = GetThunderCrashHitRadii((charged ? definition.ChargedRange : definition.NormalRange), direction);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2292,7 +2323,7 @@ namespace BumpCombat.Player
|
||||
bool anyDamage = false;
|
||||
foreach (EnemyController enemy in enemies)
|
||||
{
|
||||
if (hitCount >= definition.NormalMaxTargets)
|
||||
if (hitCount >= (charged ? definition.ChargedMaxTargets : definition.NormalMaxTargets))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -2324,11 +2355,11 @@ namespace BumpCombat.Player
|
||||
bool damaged = ApplyArtifactHit(
|
||||
enemy,
|
||||
definition,
|
||||
definition.NormalDamage * damageMultiplier,
|
||||
(charged ? definition.ChargedDamage : definition.NormalDamage) * damageMultiplier,
|
||||
hitDirection,
|
||||
definition.NormalKnockback,
|
||||
(charged ? definition.ChargedKnockback : definition.NormalKnockback),
|
||||
true,
|
||||
false,
|
||||
charged,
|
||||
echoIgniteTickDamage,
|
||||
castIdentity,
|
||||
true);
|
||||
@@ -2444,7 +2475,10 @@ namespace BumpCombat.Player
|
||||
false,
|
||||
true,
|
||||
definition.Effect,
|
||||
charged));
|
||||
charged,
|
||||
playerStats.CalculateDamage(
|
||||
GameplayConstants.Current.Combat.BaseDamage,
|
||||
DamageTag.Collision)));
|
||||
return damageWasApplied;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Progression;
|
||||
|
||||
namespace BumpCombat.Player
|
||||
{
|
||||
@@ -33,12 +34,17 @@ namespace BumpCombat.Player
|
||||
private DashController dashController;
|
||||
private ActiveArtifactController activeArtifactController;
|
||||
private PlayerHealth playerHealth;
|
||||
private PassiveRelicController passiveRelics;
|
||||
private Vector2 moveInput;
|
||||
private Vector2 pendingImpactRecoil;
|
||||
private Vector2 damageKnockbackDirection;
|
||||
private float damageKnockbackDistance;
|
||||
private float damageKnockbackDuration;
|
||||
private float damageKnockbackElapsed;
|
||||
private Vector2 voluntaryMovementStart;
|
||||
private bool voluntaryMovementPending;
|
||||
|
||||
public void SetPassiveRelicController(PassiveRelicController relics) => passiveRelics = relics;
|
||||
|
||||
public Vector2 MoveDirection { get; private set; }
|
||||
public Vector2 FacingDirection { get; private set; } = Vector2.right;
|
||||
@@ -72,6 +78,7 @@ namespace BumpCombat.Player
|
||||
dashController = GetComponent<DashController>();
|
||||
activeArtifactController = GetComponent<ActiveArtifactController>();
|
||||
playerHealth = GetComponent<PlayerHealth>();
|
||||
passiveRelics = GetComponent<PassiveRelicController>();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
@@ -191,6 +198,16 @@ namespace BumpCombat.Player
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (voluntaryMovementPending
|
||||
&& body != null
|
||||
&& !IsTransitionLocked
|
||||
&& !IsDamageKnockbackActive
|
||||
&& (dashController == null || !dashController.IsDashing))
|
||||
{
|
||||
passiveRelics?.AddVoluntaryMovement(
|
||||
Vector2.Distance(voluntaryMovementStart, body.position));
|
||||
}
|
||||
voluntaryMovementPending = false;
|
||||
AttackIntentVelocity = Vector2.zero;
|
||||
if (IsTransitionLocked)
|
||||
{
|
||||
@@ -244,6 +261,8 @@ namespace BumpCombat.Player
|
||||
startPosition + AttackIntentVelocity * Time.fixedDeltaTime);
|
||||
|
||||
body.MovePosition(targetPosition);
|
||||
voluntaryMovementStart = startPosition;
|
||||
voluntaryMovementPending = targetPosition != startPosition;
|
||||
CurrentVelocity = (targetPosition - startPosition) / Time.fixedDeltaTime;
|
||||
if (CurrentVelocity.sqrMagnitude > 0.0001f)
|
||||
{
|
||||
@@ -332,6 +351,7 @@ namespace BumpCombat.Player
|
||||
pendingImpactRecoil = Vector2.zero;
|
||||
IsDamageKnockbackActive = false;
|
||||
body.linearVelocity = Vector2.zero;
|
||||
voluntaryMovementPending = false;
|
||||
body.position = ClampToArena(position);
|
||||
transform.position = body.position;
|
||||
return true;
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace BumpCombat.Player
|
||||
private float hurtMovementLockedUntil;
|
||||
private float guardUntil;
|
||||
private float guardCooldownUntil;
|
||||
private float shieldAmount;
|
||||
private float shieldExpiresAt;
|
||||
private int nextGuardActivationIdentity;
|
||||
private int activeGuardActivationIdentity;
|
||||
private bool activeGuardHasBlockedAttack;
|
||||
@@ -41,12 +43,14 @@ namespace BumpCombat.Player
|
||||
public event Action<Vector2, float> OnDamaged;
|
||||
/// <summary>Raised only for positive health actually lost after mitigation and lethal clamping.</summary>
|
||||
public event Action<float> OnDamageTaken;
|
||||
public event Action<float> OnEnemyDamageTaken;
|
||||
public event Action<int> OnGuardAttackBlocked;
|
||||
public event Action<Vector2> OnGuardImpact;
|
||||
public event Action OnDied;
|
||||
|
||||
public float MaxHealth => playerStats.MaxHealth;
|
||||
public float CurrentHealth { get; private set; }
|
||||
public float CurrentShield => Time.time < shieldExpiresAt ? shieldAmount : 0f;
|
||||
public static float DefaultGuardCooldown =>
|
||||
GameplayConstants.Current.Player.GuardCooldownDuration;
|
||||
public static float DefaultGuardDuration =>
|
||||
@@ -134,6 +138,10 @@ namespace BumpCombat.Player
|
||||
private void Update()
|
||||
{
|
||||
RefreshRunManagerSubscription();
|
||||
if (shieldAmount > 0f && Time.time >= shieldExpiresAt)
|
||||
{
|
||||
shieldAmount = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleInputSystemUpdate()
|
||||
@@ -173,7 +181,7 @@ namespace BumpCombat.Player
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTakeDamage(float damage, Vector2 knockbackDirection, float knockbackDistance)
|
||||
public bool TryTakeDamage(float damage, Vector2 knockbackDirection, float knockbackDistance, bool fromEnemyAttack = true)
|
||||
{
|
||||
if (CurrentHealth <= 0f)
|
||||
{
|
||||
@@ -212,6 +220,18 @@ namespace BumpCombat.Player
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CurrentShield > 0f)
|
||||
{
|
||||
float absorbed = Mathf.Min(shieldAmount, damageTaken);
|
||||
shieldAmount -= absorbed;
|
||||
damageTaken -= absorbed;
|
||||
if (damageTaken <= 0f)
|
||||
{
|
||||
invulnerableUntil = Time.time + InvulnerabilityDuration;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
float healthBefore = CurrentHealth;
|
||||
|
||||
CurrentHealth = Mathf.Max(0f, CurrentHealth - damageTaken);
|
||||
@@ -232,6 +252,7 @@ namespace BumpCombat.Player
|
||||
if (actualDamageTaken > 0f)
|
||||
{
|
||||
OnDamageTaken?.Invoke(actualDamageTaken);
|
||||
if (fromEnemyAttack) OnEnemyDamageTaken?.Invoke(actualDamageTaken);
|
||||
}
|
||||
|
||||
if (CurrentHealth <= 0f)
|
||||
@@ -277,6 +298,8 @@ namespace BumpCombat.Player
|
||||
}
|
||||
|
||||
CurrentHealth = MaxHealth;
|
||||
shieldAmount = 0f;
|
||||
shieldExpiresAt = 0f;
|
||||
invulnerableUntil = Time.time + Mathf.Max(0f, invulnerabilitySeconds);
|
||||
hurtMovementLockedUntil = 0f;
|
||||
guardUntil = 0f;
|
||||
@@ -289,6 +312,18 @@ namespace BumpCombat.Player
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GrantPassiveRelicShield(float amount, float duration)
|
||||
{
|
||||
if (amount <= 0f || duration <= 0f || CurrentHealth <= 0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
shieldAmount = amount;
|
||||
shieldExpiresAt = Time.time + duration;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Heal(float amount)
|
||||
{
|
||||
if (amount <= 0f || CurrentHealth <= 0f)
|
||||
@@ -325,6 +360,8 @@ namespace BumpCombat.Player
|
||||
|
||||
private void HandleRunStarted()
|
||||
{
|
||||
shieldAmount = 0f;
|
||||
shieldExpiresAt = 0f;
|
||||
guardUntil = 0f;
|
||||
guardCooldownUntil = 0f;
|
||||
nextGuardActivationIdentity = 0;
|
||||
|
||||
Reference in New Issue
Block a user