This commit is contained in:
2026-08-21 17:59:13 +03:00
parent b98901c366
commit f6a7547e84
951 changed files with 909007 additions and 70938 deletions
+125 -39
View File
@@ -14,11 +14,11 @@ public sealed class MapController : NetworkBehaviour
[SerializeField] private MapPlayer mapPlayer;
[SerializeField] private RoundGameLoopController roundGameLoop;
[SerializeField] private NetworkBaseHealth baseHealth;
[SerializeField] private MapEnemyBalanceConfig enemyBalance;
[Header("Target System")]
[SerializeField] private MapTargetController targetController;
[SerializeField] private Transform targetParent;
[SerializeField, Min(0.02f)] private float simulationStep = 0.1f;
[Header("Radar System")]
[SerializeField] private MapRadarController radarController;
@@ -30,11 +30,12 @@ public sealed class MapController : NetworkBehaviour
[SerializeField] private Transform relayY;
[SerializeField] private Transform crosshairVisual;
[Header("Pseudo Shot")]
[Header("Shot Rules")]
[SerializeField, Min(0.01f)] private float hitRadius = 0.65f;
[SerializeField, Min(1)] private int shotDamage = 1;
[SerializeField] private MissileType pseudoMissileType = MissileType.Standard;
[SerializeField] private bool ignoreMissileTypeForPrototype = true;
[FormerlySerializedAs("ignoreMissileTypeForPrototype")]
[SerializeField] private bool ignoreMissileTypeRequirements = true;
[Header("Network Events")]
[SerializeField] private UnityEvent<int> waveStarted;
@@ -42,6 +43,9 @@ public sealed class MapController : NetworkBehaviour
[SerializeField] private UnityEvent allTargetsResolved;
[SerializeField] private UnityEvent allTargetsDestroyed;
[Header("Shot Presentation")]
[SerializeField] private MapImpactMarkerView impactMarkerView;
private readonly NetworkVariable<Vector2> crosshairCoordinates = new(
new Vector2(5f, 5f), NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);
@@ -77,6 +81,10 @@ public sealed class MapController : NetworkBehaviour
public int CurrentWave => currentWave.Value;
public int ActiveTargetCount => activeTargets.Count;
public bool WaveIsActive => waveIsActive;
public MapEnemyBalanceConfig EnemyBalance => enemyBalance;
public float HitRadius => hitRadius;
public int ShotDamage => shotDamage;
public bool IgnoreMissileTypeRequirements => ignoreMissileTypeRequirements;
public double RadarCooldownRemaining => IsSpawned && NetworkManager != null
? Math.Max(0d, radarReadyAt.Value - NetworkManager.ServerTime.Time)
: 0d;
@@ -90,7 +98,6 @@ public sealed class MapController : NetworkBehaviour
private void OnValidate()
{
simulationStep = Mathf.Max(0.02f, simulationStep);
hitRadius = Mathf.Max(0.01f, hitRadius);
shotDamage = Mathf.Max(1, shotDamage);
}
@@ -143,11 +150,12 @@ public sealed class MapController : NetworkBehaviour
if (!IsServer || !IsSpawned || !waveIsActive)
return;
float step = enemyBalance != null ? enemyBalance.SimulationStep : 0.1f;
simulationAccumulator += Time.deltaTime;
while (simulationAccumulator >= simulationStep)
while (simulationAccumulator >= step)
{
simulationAccumulator -= simulationStep;
SimulateTargets(simulationStep);
simulationAccumulator -= step;
SimulateTargets(step);
}
}
@@ -176,11 +184,20 @@ public sealed class MapController : NetworkBehaviour
if (!IsSpawned)
return;
if (IsServer)
ResolvePseudoShotOnServer();
ResolveShotOnServer(pseudoMissileType, ignoreMissileTypeRequirements);
else
RequestPseudoFireRpc();
}
public bool RequestShot(MissileType missileType)
{
if (!IsSpawned || !IsServer || missileType == MissileType.None)
return false;
ResolveShotOnServer(missileType, ignoreMissileTypeRequirements);
return true;
}
public void RequestRadarScan()
{
if (!IsSpawned)
@@ -224,7 +241,8 @@ public sealed class MapController : NetworkBehaviour
private void SetCrosshairCoordinatesRpc(Vector2 coordinates) =>
SetCrosshairCoordinatesOnServer(coordinates);
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void RequestPseudoFireRpc() => ResolvePseudoShotOnServer();
private void RequestPseudoFireRpc() =>
ResolveShotOnServer(pseudoMissileType, ignoreMissileTypeRequirements);
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void RequestRadarScanRpc() => ScanRadarOnServer();
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
@@ -253,10 +271,12 @@ public sealed class MapController : NetworkBehaviour
private bool StartNextWaveOnServer()
{
if (!IsServer || targetController == null ||
if (!IsServer || targetController == null || enemyBalance == null ||
!targetController.HasValidConfiguration())
{
Debug.LogWarning("[Map] Cannot start target wave: map or target configs are incomplete.", this);
Debug.LogWarning(
"[Map] Cannot start target wave: map, enemy balance or target configs are incomplete.",
this);
return false;
}
@@ -268,19 +288,18 @@ public sealed class MapController : NetworkBehaviour
waveIsActive = true;
simulationAccumulator = 0f;
for (int index = 0; index < targetController.TargetsPerWave; index++)
for (int index = 0; index < enemyBalance.TargetsPerWave; index++)
{
int configIndex = targetController.GetRandomValidConfigIndex();
TargetConfig config = targetController.GetConfig(configIndex);
if (config == null)
continue;
Vector2 coordinates = new(
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum),
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum));
Vector2 coordinates = CreateSpawnCoordinates();
activeTargets.Add(new MapTargetNetworkState(
nextTargetId++, configIndex, coordinates,
config.Health, config.TimeBeforeAttack));
enemyBalance.GetHealth(config),
enemyBalance.GetAttackDelay(config)));
initialTargetCount++;
}
@@ -303,11 +322,12 @@ public sealed class MapController : NetworkBehaviour
}
bool isAirTarget = config.TargetPrefab is AirTarget;
if (isAirTarget && mapPlayer != null && config.MovementSpeed > 0f)
float movementSpeed = enemyBalance.GetMovementSpeed(config);
if (isAirTarget && mapPlayer != null && movementSpeed > 0f)
{
Vector2 destination = mapPlayer.MapCoordinates;
state.Coordinates = Vector2.MoveTowards(
state.Coordinates, destination, config.MovementSpeed * deltaTime);
state.Coordinates, destination, movementSpeed * deltaTime);
if (Vector2.Distance(state.Coordinates, destination) <= config.StoppingDistance)
{
ResolveEscapedTarget(index, state, config);
@@ -333,7 +353,9 @@ public sealed class MapController : NetworkBehaviour
private void ResolveEscapedTarget(int index, MapTargetNetworkState target, TargetConfig config)
{
int damage = config != null ? config.BaseDamage : 1;
int damage = enemyBalance != null
? enemyBalance.GetBaseDamage(config)
: config != null ? config.BaseDamage : 1;
escapedTargetCount++;
activeTargets.RemoveAt(index);
if (baseHealth != null)
@@ -341,11 +363,15 @@ public sealed class MapController : NetworkBehaviour
Debug.Log($"[Map] Target {target.TargetId} reached the base. Base damage: {damage}.", this);
}
private void ResolvePseudoShotOnServer()
private void ResolveShotOnServer(MissileType missileType, bool ignoreMissileType)
{
if (!IsServer || !waveIsActive)
{
PublishShotResult(false, -1, -1f);
PublishShotResult(
false, -1, crosshairCoordinates.Value, -1f, missileType);
Debug.Log(
$"[Map] MISS with {missileType}: there is no active target wave.",
this);
return;
}
@@ -365,36 +391,67 @@ public sealed class MapController : NetworkBehaviour
{
MapTargetNetworkState target = activeTargets[nearestIndex];
TargetConfig config = targetController.GetConfig(target.ConfigIndex);
bool missileMatches = ignoreMissileTypeForPrototype || config == null ||
bool missileMatches = ignoreMissileType || config == null ||
config.RequiredMissileType == MissileType.None ||
config.RequiredMissileType == pseudoMissileType;
if (missileMatches)
config.RequiredMissileType == missileType;
if (!missileMatches)
{
target.Health -= shotDamage;
if (target.Health <= 0)
activeTargets.RemoveAt(nearestIndex);
else
activeTargets[nearestIndex] = target;
PublishShotResult(true, target.TargetId, nearestDistance);
Debug.Log($"[Map] HIT target {target.TargetId} at {nearestDistance:0.00} map units.", this);
TryCompleteWaveOnServer();
PublishShotResult(
false, target.TargetId, crosshairCoordinates.Value,
nearestDistance, missileType);
Debug.Log(
$"[Map] MISS target {target.TargetId}: {missileType} does not " +
$"match required {config.RequiredMissileType}. Distance " +
$"{nearestDistance:0.00} is inside hit radius {hitRadius:0.00}.",
this);
return;
}
target.Health -= shotDamage;
if (target.Health <= 0)
activeTargets.RemoveAt(nearestIndex);
else
activeTargets[nearestIndex] = target;
PublishShotResult(
true, target.TargetId, target.Coordinates,
nearestDistance, missileType);
Debug.Log(
$"[Map] HIT target {target.TargetId} with {missileType} " +
$"at {nearestDistance:0.00} map units. Damage: {shotDamage}.",
this);
TryCompleteWaveOnServer();
return;
}
int missedTargetId = nearestIndex >= 0 ? activeTargets[nearestIndex].TargetId : -1;
PublishShotResult(false, missedTargetId, nearestDistance);
float printedDistance = float.IsPositiveInfinity(nearestDistance) ? -1f : nearestDistance;
Debug.Log($"[Map] MISS at {crosshairCoordinates.Value}. Nearest distance: {printedDistance:0.00}.", this);
PublishShotResult(
false, missedTargetId, crosshairCoordinates.Value,
nearestDistance, missileType);
if (nearestIndex < 0)
{
Debug.Log($"[Map] MISS with {missileType}: no active targets.", this);
return;
}
Debug.Log(
$"[Map] MISS target {activeTargets[nearestIndex].TargetId}: distance " +
$"{nearestDistance:0.00} exceeds hit radius {hitRadius:0.00}.",
this);
}
private void PublishShotResult(bool hit, int targetId, float distance)
private void PublishShotResult(
bool hit,
int targetId,
Vector2 coordinates,
float distance,
MissileType missileType)
{
lastShotResult.Value = new MapShotResultState(
lastShotResult.Value.Sequence + 1, hit, targetId,
crosshairCoordinates.Value,
float.IsPositiveInfinity(distance) ? -1f : distance);
coordinates,
float.IsPositiveInfinity(distance) ? -1f : distance,
missileType);
}
private void ScanRadarOnServer()
@@ -428,11 +485,39 @@ public sealed class MapController : NetworkBehaviour
$"escaped: {escapedTargetCount}.", this);
}
private Vector2 CreateSpawnCoordinates()
{
Vector2 baseCoordinates = mapPlayer != null
? mapPlayer.MapCoordinates
: new Vector2(5f, 5f);
Vector2 farthestCandidate = Vector2.zero;
float farthestDistance = -1f;
for (int attempt = 0; attempt < enemyBalance.SpawnPositionAttempts; attempt++)
{
Vector2 candidate = new(
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum),
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum));
float distance = Vector2.Distance(candidate, baseCoordinates);
if (distance >= enemyBalance.MinimumSpawnDistanceFromBase)
return candidate;
if (distance > farthestDistance)
{
farthestDistance = distance;
farthestCandidate = candidate;
}
}
return farthestCandidate;
}
private void ConfigurePresentation()
{
mapPlayer?.Configure(coordinateSystem);
targetController?.Configure(coordinateSystem, targetParent, mapPlayer);
radarController?.Configure(coordinateSystem, radarMarkPrefab);
impactMarkerView?.Configure(coordinateSystem);
crosshair?.Configure(
coordinateSystem, relayX, relayY, crosshairVisual, targetController);
}
@@ -498,6 +583,7 @@ public sealed class MapController : NetworkBehaviour
return;
ShotResolved?.Invoke(current);
shotResolved?.Invoke(current.Hit);
impactMarkerView?.Show(current);
}
private void HandleResolvedSequenceChanged(uint previous, uint current)