201 lines
6.7 KiB
C#
201 lines
6.7 KiB
C#
using Northbound.Core;
|
|
using Northbound.Gameplay.Buildings;
|
|
using Northbound.Gameplay.Player;
|
|
using UnityEngine;
|
|
|
|
namespace Northbound.Gameplay.World
|
|
{
|
|
public enum PortalState
|
|
{
|
|
Enemy,
|
|
Ruined,
|
|
Allied
|
|
}
|
|
|
|
public sealed class PortalSite : MonoBehaviour, IDamageable, IInteractable, IEnemyStructureTarget
|
|
{
|
|
[SerializeField] private int networkIndex;
|
|
[SerializeField] private PortalState state = PortalState.Enemy;
|
|
[SerializeField] private BuildingDefinition buildingDefinition;
|
|
[SerializeField] private float rebuildManaCost = 40f;
|
|
[SerializeField] private float rebuildActionDuration = 0.5f;
|
|
[SerializeField] private bool allowsMovementWhileRebuilding;
|
|
[SerializeField] private bool canCancelRebuilding = true;
|
|
[SerializeField] private float travelActionDuration = 0.25f;
|
|
[SerializeField] private bool allowsMovementWhileTraveling;
|
|
[SerializeField] private bool canCancelTraveling;
|
|
[SerializeField] private Renderer stateRenderer;
|
|
[SerializeField] private Material enemyMaterial;
|
|
[SerializeField] private Material ruinedMaterial;
|
|
[SerializeField] private Material alliedMaterial;
|
|
|
|
public int NetworkIndex => networkIndex;
|
|
public PortalState State => state;
|
|
public Team Team => state == PortalState.Enemy ? Team.Enemy : Team.Player;
|
|
public bool IsAlive => state != PortalState.Ruined;
|
|
public bool CanInteract => state != PortalState.Enemy;
|
|
public float CurrentHealth { get; private set; }
|
|
public float MaximumHealth => buildingDefinition == null ? 1f : buildingDefinition.MaximumHealth;
|
|
public bool CanBeTargetedByEnemies => state == PortalState.Allied && IsAlive;
|
|
public IDamageable DamageReceiver => this;
|
|
|
|
private void Awake()
|
|
{
|
|
CurrentHealth = MaximumHealth;
|
|
RefreshVisual();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
SharedGameState.Instance?.Register(this);
|
|
}
|
|
|
|
public void Configure(
|
|
int index,
|
|
PortalState initialState,
|
|
BuildingDefinition definition,
|
|
Renderer renderer,
|
|
Material enemy,
|
|
Material ruined,
|
|
Material allied)
|
|
{
|
|
networkIndex = index;
|
|
state = initialState;
|
|
buildingDefinition = definition;
|
|
stateRenderer = renderer;
|
|
enemyMaterial = enemy;
|
|
ruinedMaterial = ruined;
|
|
alliedMaterial = allied;
|
|
CurrentHealth = MaximumHealth;
|
|
RefreshVisual();
|
|
}
|
|
|
|
public void TakeDamage(float amount, Team sourceTeam)
|
|
{
|
|
var playerAttackingEnemyPortal = state == PortalState.Enemy && sourceTeam == Team.Player;
|
|
var enemyAttackingAlliedPortal = state == PortalState.Allied && sourceTeam == Team.Enemy;
|
|
if (!playerAttackingEnemyPortal && !enemyAttackingAlliedPortal)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CurrentHealth = Mathf.Max(0f, CurrentHealth - Mathf.Max(0f, amount));
|
|
if (CurrentHealth > 0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
state = PortalState.Ruined;
|
|
RefreshVisual();
|
|
|
|
var gameState = SharedGameState.Instance;
|
|
if (playerAttackingEnemyPortal && gameState != null && gameState.CountEnemyPortals() == 0)
|
|
{
|
|
gameState.MarkVictory();
|
|
}
|
|
}
|
|
|
|
public string GetInteractionPrompt()
|
|
{
|
|
if (state == PortalState.Ruined)
|
|
{
|
|
return $"Rebuild portal ({rebuildManaCost:0} mana)";
|
|
}
|
|
|
|
return "Travel through adjacent portal";
|
|
}
|
|
|
|
public PlayerActionDefinition GetActionDefinition()
|
|
{
|
|
return state == PortalState.Ruined
|
|
? new PlayerActionDefinition("Rebuild Portal", rebuildActionDuration, allowsMovementWhileRebuilding, canCancelRebuilding)
|
|
: new PlayerActionDefinition("Travel Portal", travelActionDuration, allowsMovementWhileTraveling, canCancelTraveling);
|
|
}
|
|
|
|
public bool CanStartInteraction(GameObject actor)
|
|
{
|
|
var gameState = SharedGameState.Instance;
|
|
if (state == PortalState.Ruined)
|
|
{
|
|
return gameState != null && gameState.Mana + 0.001f >= rebuildManaCost;
|
|
}
|
|
|
|
if (state != PortalState.Allied || !actor.TryGetComponent<PlayerController>(out var player))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var direction = player.LastMoveDirection.z >= 0f ? 1 : -1;
|
|
var destination = gameState?.GetPortal(networkIndex + direction);
|
|
return destination != null && destination.State == PortalState.Allied;
|
|
}
|
|
|
|
public bool Interact(GameObject actor)
|
|
{
|
|
if (state == PortalState.Ruined)
|
|
{
|
|
return Rebuild();
|
|
}
|
|
|
|
if (state != PortalState.Allied || !actor.TryGetComponent<PlayerController>(out var player))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var direction = player.LastMoveDirection.z >= 0f ? 1 : -1;
|
|
var destination = SharedGameState.Instance?.GetPortal(networkIndex + direction);
|
|
if (destination == null || destination.State != PortalState.Allied)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var controller = actor.GetComponent<CharacterController>();
|
|
if (controller != null)
|
|
{
|
|
controller.enabled = false;
|
|
}
|
|
|
|
actor.transform.position = destination.transform.position + Vector3.forward * (direction * 2.5f) + Vector3.up;
|
|
if (controller != null)
|
|
{
|
|
controller.enabled = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool Rebuild()
|
|
{
|
|
var gameState = SharedGameState.Instance;
|
|
if (gameState == null || !gameState.TrySpendMana(rebuildManaCost))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
state = PortalState.Allied;
|
|
CurrentHealth = MaximumHealth;
|
|
RefreshVisual();
|
|
return true;
|
|
}
|
|
|
|
private void RefreshVisual()
|
|
{
|
|
if (stateRenderer == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
stateRenderer.sharedMaterial = state switch
|
|
{
|
|
PortalState.Enemy => enemyMaterial,
|
|
PortalState.Allied => alliedMaterial,
|
|
_ => ruinedMaterial
|
|
};
|
|
|
|
transform.localScale = state == PortalState.Ruined
|
|
? new Vector3(1f, 0.28f, 1f)
|
|
: Vector3.one;
|
|
}
|
|
}
|
|
}
|