convert game playing..
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Progression
|
||||
{
|
||||
public enum PassiveRelicId
|
||||
{
|
||||
TwinHornTalisman,
|
||||
ResonanceCore,
|
||||
RuptureSeed,
|
||||
}
|
||||
|
||||
public static class PassiveRelicRules
|
||||
{
|
||||
private static readonly PassiveRelicId[] Catalog =
|
||||
{
|
||||
PassiveRelicId.TwinHornTalisman,
|
||||
PassiveRelicId.ResonanceCore,
|
||||
PassiveRelicId.RuptureSeed,
|
||||
};
|
||||
|
||||
public static bool RollsChance(float chance, float roll) =>
|
||||
chance > 0f && roll >= 0f && roll < Mathf.Clamp01(chance);
|
||||
|
||||
public static PassiveRelicId[] GetAvailableRelics(
|
||||
IReadOnlyCollection<PassiveRelicId> owned)
|
||||
{
|
||||
List<PassiveRelicId> available = new(Catalog.Length);
|
||||
foreach (PassiveRelicId id in Catalog)
|
||||
{
|
||||
if (owned == null || !owned.Contains(id))
|
||||
{
|
||||
available.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
return available.ToArray();
|
||||
}
|
||||
|
||||
public static float ApplyTwinHornDamage(
|
||||
float damage,
|
||||
bool ownsRelic,
|
||||
bool isOrdinaryBump,
|
||||
bool hasArtifactColor,
|
||||
float chance,
|
||||
float roll)
|
||||
{
|
||||
return ownsRelic
|
||||
&& isOrdinaryBump
|
||||
&& !hasArtifactColor
|
||||
&& RollsChance(chance, roll)
|
||||
? damage * 2f
|
||||
: damage;
|
||||
}
|
||||
public static float GetExplosionDamage(float maximumHealth, float fraction) =>
|
||||
Mathf.Max(0f, maximumHealth) * Mathf.Clamp01(fraction);
|
||||
|
||||
public static float GetExplosionRadius(float bodyDiameter, float diameterMultiplier) =>
|
||||
Mathf.Max(0f, bodyDiameter) * Mathf.Max(0f, diameterMultiplier);
|
||||
|
||||
public static bool IsPlayerCreditedKill(EnemyDamageSource source) =>
|
||||
source == EnemyDamageSource.Player;
|
||||
}
|
||||
|
||||
public sealed class PassiveRelicController : MonoBehaviour
|
||||
{
|
||||
public const string EchoSourceId = "passive_relic_echo";
|
||||
|
||||
private readonly HashSet<PassiveRelicId> owned = new();
|
||||
private readonly HashSet<int> processedDeaths = new();
|
||||
private ActiveArtifactController artifacts;
|
||||
private PlayerController player;
|
||||
private Collider2D playerCollider;
|
||||
private PlayerHealth playerHealth;
|
||||
private bool active;
|
||||
private float nextEchoAllowedAt;
|
||||
|
||||
public static PassiveRelicController Instance { get; private set; }
|
||||
public int OwnedCount => owned.Count;
|
||||
public bool Owns(PassiveRelicId id) => owned.Contains(id);
|
||||
public int AvailableCount => GetAvailableRelics().Length;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static System.Func<float> RandomRollForTests { get; set; }
|
||||
#endif
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
CombatEvents.OnValidHit += HandleValidHit;
|
||||
CombatEvents.OnEnemyDied += HandleEnemyDied;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
active = false;
|
||||
CombatEvents.OnValidHit -= HandleValidHit;
|
||||
CombatEvents.OnEnemyDied -= HandleEnemyDied;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Instance == this)
|
||||
{
|
||||
Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginDefenseRun()
|
||||
{
|
||||
owned.Clear();
|
||||
processedDeaths.Clear();
|
||||
nextEchoAllowedAt = 0f;
|
||||
player = GetComponent<PlayerController>();
|
||||
playerCollider = GetComponent<Collider2D>();
|
||||
playerHealth = GetComponent<PlayerHealth>();
|
||||
artifacts = GetComponent<ActiveArtifactController>();
|
||||
active = true;
|
||||
}
|
||||
|
||||
public void EndDefenseRun()
|
||||
{
|
||||
active = false;
|
||||
}
|
||||
|
||||
public PassiveRelicId[] GetAvailableRelics() =>
|
||||
PassiveRelicRules.GetAvailableRelics(owned);
|
||||
|
||||
public bool TryAcquire(PassiveRelicId id)
|
||||
{
|
||||
if (!active || !System.Array.Exists(GetAvailableRelics(), candidate => candidate == id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return owned.Add(id);
|
||||
}
|
||||
|
||||
public float ApplyOrdinaryBumpDamageMultiplier(
|
||||
float damage,
|
||||
bool isOrdinaryBump,
|
||||
bool hasArtifactColor)
|
||||
{
|
||||
if (!active || !owned.Contains(PassiveRelicId.TwinHornTalisman))
|
||||
{
|
||||
return damage;
|
||||
}
|
||||
|
||||
return PassiveRelicRules.ApplyTwinHornDamage(
|
||||
damage,
|
||||
true,
|
||||
isOrdinaryBump,
|
||||
hasArtifactColor,
|
||||
GameplayConstants.Current.PassiveRelics.TwinHornDoubleDamageChance,
|
||||
GetRandomRoll());
|
||||
}
|
||||
private void HandleValidHit(CombatHitResult hit)
|
||||
{
|
||||
if (!active
|
||||
|| !owned.Contains(PassiveRelicId.ResonanceCore)
|
||||
|| hit.Attacker != gameObject
|
||||
|| hit.Damage <= 0f
|
||||
|| playerHealth == null
|
||||
|| playerHealth.CurrentHealth <= 0f
|
||||
|| hit.SourceId == EchoSourceId
|
||||
|| Time.time < nextEchoAllowedAt
|
||||
|| !RollsChance(GameplayConstants.Current.PassiveRelics.ResonanceEchoChance)
|
||||
|| artifacts == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (artifacts.TryEmitPassiveRelicEcho(
|
||||
hit.HitPosition,
|
||||
hit.KnockbackDirection,
|
||||
GameplayConstants.Current.PassiveRelics.ResonanceEchoDamageMultiplier))
|
||||
{
|
||||
nextEchoAllowedAt = Time.time
|
||||
+ GameplayConstants.Current.PassiveRelics.ResonanceEchoCooldownSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEnemyDied(GameObject target)
|
||||
{
|
||||
if (!active || !owned.Contains(PassiveRelicId.RuptureSeed) || target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnemyController victim = target.GetComponent<EnemyController>();
|
||||
if (victim == null
|
||||
|| !PassiveRelicRules.IsPlayerCreditedKill(victim.LastDamageSource)
|
||||
|| !processedDeaths.Add(victim.SpawnLifetimeIdentity)
|
||||
|| !RollsChance(GameplayConstants.Current.PassiveRelics.RuptureSeedChance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float damage = PassiveRelicRules.GetExplosionDamage(
|
||||
victim.MaximumHealthAtDeath,
|
||||
GameplayConstants.Current.PassiveRelics.RuptureExplosionMaxHealthFraction);
|
||||
float diameter = playerCollider != null
|
||||
? Mathf.Max(playerCollider.bounds.size.x, playerCollider.bounds.size.y)
|
||||
: 0f;
|
||||
float radius = PassiveRelicRules.GetExplosionRadius(
|
||||
diameter,
|
||||
GameplayConstants.Current.PassiveRelics.RuptureExplosionPlayerBodyDiameters);
|
||||
if (damage <= 0f || radius <= 0f || playerHealth == null || playerHealth.CurrentHealth <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Collider2D[] hits = Physics2D.OverlapCircleAll(victim.transform.position, radius);
|
||||
HashSet<EnemyController> damagedEnemies = new();
|
||||
foreach (Collider2D hit in hits)
|
||||
{
|
||||
EnemyController enemy = hit != null ? hit.GetComponentInParent<EnemyController>() : null;
|
||||
if (enemy == null
|
||||
|| enemy == victim
|
||||
|| enemy.IsDead
|
||||
|| !damagedEnemies.Add(enemy))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 direction = ((Vector2)enemy.transform.position - (Vector2)victim.transform.position).normalized;
|
||||
enemy.TryTakeDamage(
|
||||
damage,
|
||||
direction,
|
||||
0f,
|
||||
true,
|
||||
false,
|
||||
EnemyDamageSource.PassiveRelicExplosion);
|
||||
}
|
||||
}
|
||||
|
||||
private static float GetRandomRoll()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (RandomRollForTests != null)
|
||||
{
|
||||
return RandomRollForTests();
|
||||
}
|
||||
#endif
|
||||
return Random.value;
|
||||
}
|
||||
|
||||
private static bool RollsChance(float chance)
|
||||
{
|
||||
return PassiveRelicRules.RollsChance(chance, GetRandomRoll());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user