using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using Cinemachine;
using BoatAttack.UI;
using Object = UnityEngine.Object;
namespace BoatAttack
{
///
/// This is an overall controller for a boat
///
public class Boat : MonoBehaviour
{
// Boat stats
public Renderer boatRenderer; // The renderer for the boat mesh
public Renderer engineRenderer; // The renderer for the boat mesh
public Engine engine;
private Matrix4x4 _spawnPosition;
// RaceStats
[NonSerialized] public int Place = 0;
[NonSerialized] public float LapPercentage;
[NonSerialized] public int LapCount;
[NonSerialized] public bool MatchComplete;
private int _wpCount = -1;
private WaypointGroup.Waypoint _lastCheckpoint;
private WaypointGroup.Waypoint _nextCheckpoint;
[NonSerialized] public readonly List SplitTimes = new List();
public CinemachineVirtualCamera cam;
private float _camFovVel;
[NonSerialized] public RaceUI RaceUi;
private Object _controller;
private int _playerIndex;
// Shader Props
private static readonly int LiveryPrimary = Shader.PropertyToID("_Color1");
private static readonly int LiveryTrim = Shader.PropertyToID("_Color2");
private void Awake()
{
_spawnPosition = transform.localToWorldMatrix;
TryGetComponent(out engine.RB);
}
public void Setup(int player = 1, bool isHuman = true, BoatLivery livery = new BoatLivery())
{
_playerIndex = player - 1;
cam.gameObject.layer = LayerMask.NameToLayer("Player" + player); // assign player layer
SetupController(isHuman); // create or change controller
Colorize(livery);
}
void SetupController(bool isHuman)
{
var controllerType = isHuman ? typeof(HumanController) : typeof(AiController);
// If controller exists then make sure it's teh right one, if not add it
if (_controller)
{
if (_controller.GetType() == controllerType) return;
Destroy(_controller);
_controller = gameObject.AddComponent(controllerType);
}
else
{
_controller = gameObject.AddComponent(controllerType);
}
}
private void Update()
{
UpdateLaps();
if (RaceUi)
{
RaceUi.UpdatePlaceCounter(Place);
RaceUi.UpdateSpeed(engine.VelocityMag);
}
}
private void LateUpdate()
{
if (cam)
{
var fov = Mathf.SmoothStep(80f, 100f, engine.VelocityMag * 0.005f);
cam.m_Lens.FieldOfView = Mathf.SmoothDamp(cam.m_Lens.FieldOfView, fov, ref _camFovVel, 0.5f);
}
}
private void FixedUpdate()
{
if (WaypointGroup.Instance == null)
return;
if (!RaceManager.RaceStarted)
{
// race not started, make sure to keep boat fairly aligned.
var target = WaypointGroup.Instance.StartingPositions[_playerIndex];
Vector3 targetPosition = target.GetColumn(3);
Vector3 targetForward = target.GetColumn(2);
var t = transform;
var currentPosition = t.position;
var currentForward = t.forward;
targetPosition.y = currentPosition.y;
engine.RB.AddForce((currentPosition - targetPosition) * 0.25f);
engine.RB.MoveRotation(Quaternion.LookRotation(Vector3.Slerp(currentForward, targetForward, 0.1f * Time.fixedDeltaTime)));
}
}
private void UpdateLaps()
{
if (WaypointGroup.Instance == null)
return;
LapPercentage = WaypointGroup.Instance.GetPercentageAroundTrack(transform.position);
var lowPercentage = _lastCheckpoint?.normalizedDistance ?? 0f;
var highPercentage = _nextCheckpoint?.normalizedDistance ?? 1f;
LapPercentage = Mathf.Clamp(LapPercentage, lowPercentage, highPercentage <= 0.001f ? 1f : highPercentage);
if (RaceUi)
{
RaceUi.UpdateLapCounter(LapCount);
}
}
private void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("waypoint") || MatchComplete) return;
var wp = WaypointGroup.Instance.GetTriggersWaypoint(other as BoxCollider);
var wpIndex = WaypointGroup.Instance.GetWaypointIndex(wp);
if (wp.isCheckpoint || wpIndex == 0)
{
_lastCheckpoint = wp;
_nextCheckpoint = WaypointGroup.Instance.GetNextCheckpoint(wpIndex);
}
EnteredWaypoint(wpIndex, wp.isCheckpoint);
}
private void EnteredWaypoint(int index, bool checkpoint)
{
var count = WaypointGroup.Instance.WPs.Count;
var nextWp = (int) Mathf.Repeat(_wpCount + 1, count);
if (nextWp != index) return;
_wpCount = nextWp;
if (index != 0) return;
LapCount++;
SplitTimes.Add(RaceManager.RaceTime);
if (LapCount > RaceManager.GetLapCount())
{
Debug.Log($"Boat {name} finished {RaceUI.OrdinalNumber(Place)} with time:{RaceUI.FormatRaceTime(SplitTimes.Last())}");
RaceManager.BoatFinished(_playerIndex);
MatchComplete = true;
}
}
[ContextMenu("Randomize")]
private void ColorizeInvoke()
{
Colorize(Color.black, Color.black, true);
}
private void Colorize(Color primaryColor, Color trimColor, bool random = false)
{
var livery = new BoatLivery
{
primaryColor = random ? ConstantData.GetRandomPaletteColor : primaryColor,
trimColor = random ? ConstantData.GetRandomPaletteColor : trimColor
};
Colorize(livery);
}
///
/// This sets both the primary and secondary colour and assigns via a MPB
///
private void Colorize(BoatLivery livery)
{
boatRenderer?.material?.SetColor(LiveryPrimary, livery.primaryColor);
engineRenderer?.material?.SetColor(LiveryPrimary, livery.primaryColor);
boatRenderer?.material?.SetColor(LiveryTrim, livery.trimColor);
engineRenderer?.material?.SetColor(LiveryTrim, livery.trimColor);
}
public void ResetPosition()
{
if (WaypointGroup.Instance)
{
var resetMatrix = WaypointGroup.Instance.GetClosestPointOnWaypoint(transform.position);
var resetPoint = resetMatrix.GetColumn(3);
resetPoint.y = _spawnPosition.GetColumn(3).y;
engine.RB.velocity = Vector3.zero;
engine.RB.angularVelocity = Vector3.zero;
engine.RB.position = resetPoint;
engine.RB.rotation = resetMatrix.rotation;
}
}
}
[Serializable]
public class BoatData
{
public string boatName;
public AssetReference boatPrefab;
public BoatLivery livery;
public bool human;
[NonSerialized] public Boat Boat;
[NonSerialized] public GameObject BoatObject;
public void SetController(GameObject boat, Boat controller)
{
BoatObject = boat;
this.Boat = controller;
}
}
[Serializable]
public struct BoatLivery
{
[ColorUsage(false)] public Color primaryColor;
[ColorUsage(false)] public Color trimColor;
}
}