Implement passive relics and six-family defense towers

This commit is contained in:
2026-09-23 18:11:38 +09:00
parent 5d11288a6f
commit c94cbda1e6
21 changed files with 2882 additions and 152 deletions
@@ -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;
+38 -1
View File
@@ -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;