Files
Northbound/Assets/Scripts/Building.cs

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
}
}