396 lines
12 KiB
C#
396 lines
12 KiB
C#
using System;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
namespace Northbound
|
|
{
|
|
public class Building : DamageableNetworkBehaviour, IVisionProvider, ITeamMember
|
|
{
|
|
[Header("Building Data")]
|
|
public BuildingData buildingData;
|
|
|
|
[Header("Runtime Info")]
|
|
public Vector3Int gridPosition;
|
|
public int rotation; // 0-3 (0=0°, 1=90°, 2=180°, 3=270°)
|
|
|
|
[Header("Team")]
|
|
[Tooltip("Building team (Player/Enemy/Monster/Neutral)")]
|
|
public TeamType initialTeam = TeamType.Player;
|
|
|
|
[Header("Ownership (for pre-placed buildings)")]
|
|
[Tooltip("For pre-placed buildings, set owner here (0 = neutral, 1+ = player ID)")]
|
|
public ulong initialOwnerId = 0;
|
|
[Tooltip("Is this a pre-placed building? If checked, uses initialOwnerId")]
|
|
public bool useInitialOwner = false;
|
|
|
|
[Header("Health Bar")]
|
|
public GameObject healthBarPrefab;
|
|
|
|
[Header("Debug")]
|
|
public bool showGridBounds = true;
|
|
public Color gridBoundsColor = Color.cyan;
|
|
|
|
private NetworkVariable<ulong> _ownerId = new NetworkVariable<ulong>(
|
|
0,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server
|
|
);
|
|
|
|
private NetworkVariable<TeamType> _team = new NetworkVariable<TeamType>(
|
|
TeamType.Neutral,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server
|
|
);
|
|
|
|
public event Action<TeamType> OnTeamChanged;
|
|
|
|
private BuildingHealthBar _healthBar;
|
|
private float _lastRegenTime;
|
|
private bool _isInitialized = false;
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
base.OnNetworkSpawn();
|
|
|
|
if (IsOwner)
|
|
{
|
|
if (maxHealth == 0)
|
|
{
|
|
maxHealth = buildingData != null ? buildingData.maxHealth : 100;
|
|
_currentHealth.Value = maxHealth;
|
|
}
|
|
|
|
if (_team.Value == TeamType.Neutral)
|
|
{
|
|
_team.Value = initialTeam;
|
|
}
|
|
|
|
if (useInitialOwner && _ownerId.Value == 0)
|
|
{
|
|
_ownerId.Value = initialOwnerId;
|
|
}
|
|
else if (!useInitialOwner && _ownerId.Value == 0)
|
|
{
|
|
_ownerId.Value = OwnerClientId;
|
|
}
|
|
|
|
_lastRegenTime = Time.time;
|
|
|
|
if (buildingData != null && buildingData.providesVision)
|
|
{
|
|
FogOfWarSystem.Instance?.RegisterVisionProvider(this);
|
|
}
|
|
}
|
|
|
|
_team.OnValueChanged += OnTeamValueChanged;
|
|
|
|
if (showHealthBar && healthBarPrefab != null)
|
|
{
|
|
base.InitializeHealthBar();
|
|
}
|
|
|
|
UpdateHealthUI();
|
|
UpdateTeamVisuals();
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
base.OnNetworkDespawn();
|
|
|
|
_team.OnValueChanged -= OnTeamValueChanged;
|
|
|
|
if (IsOwner && buildingData != null && buildingData.providesVision)
|
|
{
|
|
FogOfWarSystem.Instance?.UnregisterVisionProvider(this);
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!IsOwner || buildingData == null)
|
|
return;
|
|
|
|
if (buildingData.autoRegenerate && _currentHealth.Value < maxHealth)
|
|
{
|
|
if (Time.time - _lastRegenTime >= 1f)
|
|
{
|
|
int regenAmount = Mathf.Min(buildingData.regenPerSecond, maxHealth - _currentHealth.Value);
|
|
_currentHealth.Value += regenAmount;
|
|
_lastRegenTime = Time.time;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Initialize(BuildingData data, Vector3Int gridPos, int rot, ulong ownerId, TeamType team = TeamType.Player)
|
|
{
|
|
buildingData = data;
|
|
gridPosition = gridPos;
|
|
rotation = rot;
|
|
|
|
if (IsOwner && IsSpawned)
|
|
{
|
|
maxHealth = data.maxHealth;
|
|
_currentHealth.Value = maxHealth;
|
|
_ownerId.Value = ownerId;
|
|
_team.Value = team;
|
|
|
|
if (data.providesVision)
|
|
{
|
|
FogOfWarSystem.Instance?.RegisterVisionProvider(this);
|
|
}
|
|
}
|
|
|
|
_isInitialized = true;
|
|
}
|
|
|
|
public void SetOwner(ulong newOwnerId, TeamType newTeam)
|
|
{
|
|
if (!IsOwner)
|
|
{
|
|
SetOwnerServerRpc(newOwnerId, newTeam);
|
|
return;
|
|
}
|
|
|
|
SetOwnerServerRpc(newOwnerId, newTeam);
|
|
}
|
|
|
|
[ServerRpc]
|
|
private void SetOwnerServerRpc(ulong newOwnerId, TeamType newTeam)
|
|
{
|
|
ulong previousOwner = _ownerId.Value;
|
|
TeamType previousTeam = _team.Value;
|
|
|
|
_ownerId.Value = newOwnerId;
|
|
_team.Value = newTeam;
|
|
|
|
Debug.Log($"<color=yellow>[Building] {buildingData?.buildingName ?? "Building"} ownership changed: {previousOwner} → {newOwnerId}, team: {TeamManager.GetTeamName(previousTeam)} → {TeamManager.GetTeamName(newTeam)}</color>");
|
|
|
|
if (buildingData != null && buildingData.providesVision)
|
|
{
|
|
FogOfWarSystem.Instance?.UnregisterVisionProvider(this);
|
|
FogOfWarSystem.Instance?.RegisterVisionProvider(this);
|
|
}
|
|
}
|
|
|
|
#region ITeamMember Implementation
|
|
|
|
public TeamType GetTeam() => _team.Value;
|
|
|
|
public void SetTeam(TeamType team)
|
|
{
|
|
if (!IsOwner)
|
|
{
|
|
SetTeamServerRpc(team);
|
|
return;
|
|
}
|
|
|
|
SetTeamServerRpc(team);
|
|
}
|
|
|
|
[ServerRpc]
|
|
private void SetTeamServerRpc(TeamType team)
|
|
{
|
|
_team.Value = team;
|
|
}
|
|
|
|
private void OnTeamValueChanged(TeamType previousValue, TeamType newValue)
|
|
{
|
|
OnTeamChanged?.Invoke(newValue);
|
|
UpdateTeamVisuals();
|
|
Debug.Log($"<color=cyan>[Building] {buildingData?.buildingName ?? "Building"} team changed: {TeamManager.GetTeamName(previousValue)} → {TeamManager.GetTeamName(newValue)}</color>");
|
|
}
|
|
|
|
private void UpdateTeamVisuals()
|
|
{
|
|
Color teamColor = TeamManager.GetTeamColor(_team.Value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IVisionProvider Implementation
|
|
|
|
public ulong GetOwnerId() => _ownerId.Value;
|
|
|
|
public float GetVisionRange()
|
|
{
|
|
return buildingData != null ? buildingData.visionRange : 0f;
|
|
}
|
|
|
|
public Transform GetTransform() => transform;
|
|
|
|
public bool IsActive()
|
|
{
|
|
return IsSpawned && !IsDead() && buildingData != null && buildingData.providesVision;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDamageable Overrides
|
|
|
|
protected override void Die(ulong killerId)
|
|
{
|
|
base.Die(killerId);
|
|
|
|
if (!IsOwner) return;
|
|
|
|
Debug.Log($"<color=red>[Building] {buildingData?.buildingName ?? "Building"} ({TeamManager.GetTeamName(_team.Value)}) destroyed! Attacker: {killerId}</color>");
|
|
|
|
InvokeOnDestroyed();
|
|
NotifyDestroyedClientRpc();
|
|
|
|
if (buildingData != null && buildingData.providesVision)
|
|
{
|
|
FogOfWarSystem.Instance?.UnregisterVisionProvider(this);
|
|
}
|
|
|
|
if (BuildingManager.Instance != null)
|
|
{
|
|
BuildingManager.Instance.RemoveBuilding(this);
|
|
}
|
|
|
|
Invoke(nameof(DespawnBuilding), 0.5f);
|
|
}
|
|
|
|
private void DespawnBuilding()
|
|
{
|
|
if (IsOwner && NetworkObject != null)
|
|
{
|
|
NetworkObject.Despawn(true);
|
|
}
|
|
}
|
|
|
|
[ClientRpc]
|
|
private void NotifyDestroyedClientRpc()
|
|
{
|
|
if (!IsOwner)
|
|
{
|
|
InvokeOnDestroyed();
|
|
}
|
|
}
|
|
|
|
public override void TakeDamage(int damage, ulong attackerId)
|
|
{
|
|
if (!IsOwner)
|
|
{
|
|
TakeDamageOwnerRpc(damage, attackerId);
|
|
return;
|
|
}
|
|
|
|
if (buildingData != null && buildingData.isIndestructible)
|
|
{
|
|
Debug.Log($"<color=yellow>[Building] {buildingData.buildingName} is indestructible.</color>");
|
|
return;
|
|
}
|
|
|
|
var attackerObj = NetworkManager.Singleton.SpawnManager.SpawnedObjects[attackerId];
|
|
var attackerTeamMember = attackerObj?.GetComponent<ITeamMember>();
|
|
|
|
if (attackerTeamMember != null)
|
|
{
|
|
if (!TeamManager.CanAttack(attackerTeamMember, this))
|
|
{
|
|
Debug.Log($"<color=yellow>[Building] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} team cannot attack {TeamManager.GetTeamName(_team.Value)} team.</color>");
|
|
return;
|
|
}
|
|
}
|
|
|
|
base.TakeDamage(damage, attackerId);
|
|
}
|
|
|
|
[Rpc(SendTo.Owner)]
|
|
private void TakeDamageOwnerRpc(int damage, ulong attackerId)
|
|
{
|
|
TakeDamage(damage, attackerId);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Health Management
|
|
|
|
public new int GetMaxHealth()
|
|
{
|
|
return buildingData != null ? buildingData.maxHealth : 100;
|
|
}
|
|
|
|
public new float GetHealthPercentage()
|
|
{
|
|
int maxHp = GetMaxHealth();
|
|
return maxHp > 0 ? (float)_currentHealth.Value / maxHp : 0f;
|
|
}
|
|
|
|
public bool IsDestroyed() => _currentHealth.Value <= 0;
|
|
|
|
#endregion
|
|
|
|
#region Health UI
|
|
|
|
protected override void InitializeHealthBar()
|
|
{
|
|
if (_healthBar != null)
|
|
return;
|
|
|
|
if (healthBarPrefab != null)
|
|
{
|
|
GameObject healthBarObj = Instantiate(healthBarPrefab, transform);
|
|
_healthBar = healthBarObj.GetComponent<BuildingHealthBar>();
|
|
|
|
if (_healthBar != null)
|
|
{
|
|
_healthBar.Initialize(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void UpdateHealthUI()
|
|
{
|
|
if (_healthBar != null)
|
|
{
|
|
_healthBar.UpdateHealth(_currentHealth.Value, GetMaxHealth());
|
|
}
|
|
|
|
InvokeOnHealthChanged(_currentHealth.Value, GetMaxHealth());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Grid Bounds
|
|
|
|
public Bounds GetGridBounds()
|
|
{
|
|
if (buildingData == null) return new Bounds(transform.position, Vector3.one);
|
|
|
|
Vector3 gridSize = buildingData.GetSize(rotation);
|
|
Vector3 shrunkSize = gridSize - Vector3.one * 0.01f;
|
|
return new Bounds(transform.position + Vector3.up * gridSize.y * 0.5f, shrunkSize);
|
|
}
|
|
|
|
public Bounds GetBounds()
|
|
{
|
|
return GetGridBounds();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Gizmos
|
|
|
|
private void OnDrawGizmos()
|
|
{
|
|
if (!showGridBounds || buildingData == null) return;
|
|
|
|
Bounds bounds = GetGridBounds();
|
|
|
|
Color teamColor = Application.isPlaying ? TeamManager.GetTeamColor(_team.Value) : TeamManager.GetTeamColor(initialTeam);
|
|
Gizmos.color = new Color(teamColor.r, teamColor.g, teamColor.b, 0.3f);
|
|
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
|
|
|
Gizmos.color = teamColor;
|
|
Gizmos.DrawWireSphere(transform.position + Vector3.up * 2f, 0.5f);
|
|
|
|
UnityEditor.Handles.Label(transform.position + Vector3.up * 3f,
|
|
$"{buildingData.buildingName}\nHP: {_currentHealth.Value}/{maxHealth}");
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|