C#:
using System;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Plugins;
using UnityEngine;
using System.Linq;
using System.Globalization;
using Rust;
namespace Oxide.Plugins {
[Info("RaceTrack", "k1lly0u", "1.0.0")]
[Description("A standalone racing event for cars, boats, mincopters and horses")]
class RaceTrack : RustPlugin {
#region Fields
private Hash<string, TrackData> trackData;
private Hash<string, ModularCarData> carData;
private RestoreData restoreData;
private List<uint> spawnedEntities;
private DynamicConfigFile entitydata, trackdata, cardata, restorationData;
[PluginReference]
Plugin CarCommander, BoatCommander, HeliCommander, Economics, ServerRewards;
private static RaceTrack ins;
private static bool disableSpectate;
private RaceManager manager;
private Timer autoTimer;
private int lastEventIndex = 0;
private Hash<string, TrackData> raceTracks = new Hash<string, TrackData>();
private Hash<ulong, TrackData> trackCreator = new Hash<ulong, TrackData>();
private CuiElementContainer scoreContainer;
private LightType lightType;
private const string SEDAN_PREFAB = "assets/content/vehicles/sedan_a/sedantest.entity.prefab";
private const string ROWBOAT_PREFAB = "assets/content/vehicles/boats/rowboat/rowboat.prefab";
private const string RHIB_PREFAB = "assets/content/vehicles/boats/rhib/rhib.prefab";
private const string MINICOPTER_PREFAB = "assets/content/vehicles/minicopter/minicopter.entity.prefab";
private const string HORSE_PREFAB = "assets/rust.ai/nextai/testridablehorse.prefab";
private const string BOOGIEBOARD_PREFAB = "assets/prefabs/misc/summer_dlc/boogie_board/boogieboard.deployed.prefab";
private const string INNER_TUBE_PREFAB = "assets/prefabs/misc/summer_dlc/inner_tube/innertube.deployed.prefab";
private const string KAYAK_PREFAB = "assets/content/vehicles/boats/kayak/kayak.prefab";
private const string KAYAK_PADDLE_ITEM = "paddle";
private const string LANTERN_PREFAB = "assets/prefabs/deployable/lantern/lantern.deployed.prefab";
private const string FLASHER_PREFAB = "assets/prefabs/deployable/playerioents/lights/flasherlight/electric.flasherlight.deployed.prefab";
private const string SIREN_PREFAB = "assets/prefabs/deployable/playerioents/lights/sirenlight/electric.sirenlight.deployed.prefab";
private const string WALL_PREFAB = "assets/prefabs/building core/wall.low/wall.low.prefab";
private enum RaceMode {
Laps, Sprint
}
private enum RaceType {
Car, Boat, RHIB, Minicopter, Horse, Kayak, BoogieBoard, InnerTube
}
#endregion
#region Oxide Hooks
private void Loaded() {
permission.RegisterPermission("racetrack.admin", this);
permission.RegisterPermission("racetrack.play", this);
lang.RegisterMessages(Messages, this);
entitydata = Interface.Oxide.DataFileSystem.GetFile("RaceTrack/entity_data");
trackdata = Interface.Oxide.DataFileSystem.GetFile("RaceTrack/track_data");
cardata = Interface.Oxide.DataFileSystem.GetFile("RaceTrack/car_data");
restorationData = Interface.Oxide.DataFileSystem.GetFile("RaceTrack/restoration_data");
trackdata.Settings.Converters = new JsonConverter[] {
new UnityVector3Converter()
}
;
restorationData.Settings.Converters = new JsonConverter[] {
new UnityVector3Converter()
}
;
}
private void OnServerInitialized() {
ins = this;
disableSpectate = configData.DisableSpectate;
if (disableSpectate) {
//Unsubscribe(nameof(OnPlayerInput));
//Unsubscribe(nameof(OnPlayerTick));
Unsubscribe(nameof(CanSpectateTarget));
}
if (!configData.Racers.DisableMetabolism) Unsubscribe(nameof(OnRunPlayerMetabolism));
lightType = ParseType<LightType>(configData.Checkpoints.LightType);
LoadData();
CleanupEntities();
StartEventTimer();
}
private void OnPlayerConnected(BasePlayer player) {
if (player.IsSleeping() || player.HasPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot)) {
timer.Once(1, () => OnPlayerConnected(player));
return;
}
if (restoreData.HasRestoreData(player.userID)) restoreData.RestorePlayer(player);
}
private void OnPlayerRespawned(BasePlayer player) => OnPlayerConnected(player);
private void OnPlayerDisconnected(BasePlayer player) {
if (manager != null) manager.PlayerDisconnected(player);
else {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver != null) {
raceDriver.DismountPlayer();
NextTick(() => {
if (raceDriver != null) UnityEngine.Object.Destroy(raceDriver);
}
);
}
}
}
private void OnPlayerSleepEnded(BasePlayer player) {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver != null) {
if (!player.isMounted && raceDriver.Vehicle != null && raceDriver.Vehicle.entity != null) raceDriver.MountPlayer(raceDriver.Vehicle);
}
}
private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
if (entity == null || info == null) return;
if (entity.GetComponent<RaceDriver>() != null) NullifyDamage(ref info);
if (entity.GetComponent<RaceVehicle>() != null) NullifyDamage(ref info);
BaseVehicleModule module = entity as BaseVehicleModule;
if (module != null && module.Vehicle?.GetComponent<RaceVehicle>()) NullifyDamage(ref info);
CheckPoint.Marker marker = entity.GetComponent<CheckPoint.Marker>();
if (marker != null && manager != null && manager.Status != EventStatus.Finished) NullifyDamage(ref info);
}
private object OnRunPlayerMetabolism(PlayerMetabolism metabolism, BasePlayer owner, float delta) {
if (owner.GetComponent<RaceDriver>()) return true;
return null;
}
private object CanMountEntity(BasePlayer player, BaseMountable mountable) {
if (mountable.GetComponent<RaceCar>()) return false;
return null;
}
private object CanDismountEntity(BasePlayer player, BaseMountable mountable) {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver != null && !raceDriver.HasFinished) return false;
return null;
}
private object OnPlayerCommand(BasePlayer player, string command, string[] args) {
if (player != null && player.GetComponent<RaceDriver>()) {
if (configData.CommandBlacklist.Any(x => x.StartsWith("/") ? x.Substring(1).ToLower() == command : x.ToLower() == command)) {
SendReply(player, msg("blacklistcmd", player.UserIDString));
return false;
}
}
return null;
}
private object OnServerCommand(ConsoleSystem.Arg arg) {
BasePlayer player = arg.Player();
if (player != null && player.GetComponent<RaceDriver>()) {
if (configData.CommandBlacklist.Any(x => arg.cmd.FullName.Contains(x.ToLower())) || arg.cmd.FullName == "vehicle.swapseats") {
SendReply(player, msg("blacklistcmd", player.UserIDString));
return false;
}
}
return null;
}
private object CanSpectateTarget(BasePlayer player, string name) {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver != null && raceDriver.Player.IsSpectating()) {
raceDriver.UpdateSpectateTarget(1);
return false;
}
return null;
}
private void Unload() {
SaveRestoreData();
if (autoTimer != null) autoTimer.Destroy();
RaceDriver[] raceDrivers = UnityEngine.Object.FindObjectsOfType<RaceDriver>();
if (raceDrivers != null) {
foreach(RaceDriver raceDriver in raceDrivers) UnityEngine.Object.Destroy(raceDriver);
}
RaceVehicle[] raceVehicles = UnityEngine.Object.FindObjectsOfType<RaceVehicle>();
if (raceVehicles != null) {
foreach(RaceVehicle raceVehicle in raceVehicles) UnityEngine.Object.Destroy(raceVehicle);
}
CheckPoint[] checkPoints = UnityEngine.Object.FindObjectsOfType<CheckPoint>();
if (checkPoints != null) {
foreach(CheckPoint checkPoint in checkPoints) UnityEngine.Object.Destroy(checkPoint);
}
CheckPoint.Marker[] markers = UnityEngine.Object.FindObjectsOfType<CheckPoint.Marker>();
if (markers != null) {
foreach (CheckPoint.Marker marker in markers) UnityEngine.Object.Destroy(marker);
}
UnityEngine.Object.Destroy(manager);
ins = null;
}
#endregion
#region Functions
private void CleanupEntities() {
if (spawnedEntities.Count > 0) {
PrintWarning("Finding and destroying left over entities");
BaseNetworkable[] objects = BaseNetworkable.serverEntities.ToArray();
if (objects != null) {
foreach(BaseNetworkable obj in BaseNetworkable.serverEntities) {
if (obj != null && !obj.IsDestroyed) {
if (spawnedEntities.Contains(obj.net.ID)) obj.Kill(BaseNetworkable.DestroyMode.None);
}
}
}
spawnedEntities.Clear();
SaveEntityData();
PrintWarning("Cleanup completed");
}
}
private string FormatTime(double time) {
TimeSpan dateDifference = TimeSpan.FromSeconds((float)time);
int days = dateDifference.Days;
int hours = dateDifference.Hours;
hours += (days * 24);
int mins = dateDifference.Minutes;
int secs = dateDifference.Seconds;
if (hours > 0) return string.Format("{0:00}:{1:00}:{2:00}", hours, mins, secs);
else return string.Format("{0:00}:{1:00}", mins, secs);
}
private T ParseType<T>(string type) {
try {
return (T)Enum.Parse(typeof(T), type, true);
}
catch {
return default(T);
}
}
private static string EntityFromRaceType(RaceType raceType) {
switch (raceType) {
case RaceType.Car : return SEDAN_PREFAB;
case RaceType.Boat : return ROWBOAT_PREFAB;
case RaceType.RHIB : return RHIB_PREFAB;
case RaceType.Minicopter : return MINICOPTER_PREFAB;
case RaceType.Horse : return HORSE_PREFAB;
case RaceType.Kayak : return KAYAK_PREFAB;
case RaceType.BoogieBoard : return BOOGIEBOARD_PREFAB;
case RaceType.InnerTube : return INNER_TUBE_PREFAB;
default : return string.Empty;
}
}
private static Type VehicleComponentFromRaceType(RaceType raceType) {
switch (raceType) {
case RaceType.Car : return typeof(RaceCar);
case RaceType.Boat : case RaceType.RHIB : case RaceType.Kayak : case RaceType.BoogieBoard : case RaceType.InnerTube : return typeof(RaceBoat);
case RaceType.Minicopter : return typeof(RaceHelicopter);
case RaceType.Horse : return typeof(RaceHorse);
default : return null;
}
}
private void NullifyDamage(ref HitInfo info) {
info.damageTypes = new DamageTypeList();
info.HitEntity = null;
info.HitMaterial = 0;
info.PointStart = Vector3.zero;
}
private void StartEventTimer() {
if (configData.Automation.Enabled) {
if (trackData.Count == 0) {
PrintError("You have no race tracks set up.Unable to automate events");
return;
}
autoTimer = timer.In(configData.Timers.Interval, () => {
if (manager == null) manager = new GameObject().AddComponent<RaceManager>();
if (manager.Status != EventStatus.Finished) return;
if (configData.Automation.Random) {
var data = trackData.ElementAt(UnityEngine.Random.Range(0, trackData.Count - 1));
manager.SetTrackData(data.Key, data.Value);
manager.OpenEvent();
}
else {
if (configData.Automation.Order.Count < 1) {
PrintError("You have no race tracks set in your config.Unable to automate events by order");
return;
}
if (!trackData.ContainsKey(configData.Automation.Order[lastEventIndex])) {
PrintError($"Unable to find a race track with the name : {configData.Automation.Order[lastEventIndex]}");
return;
}
var data = trackData[configData.Automation.Order[lastEventIndex]];
manager.SetTrackData(configData.Automation.Order[lastEventIndex], data);
lastEventIndex++;
if (lastEventIndex >= configData.Automation.Order.Count) lastEventIndex = 0;
manager.OpenEvent();
}
}
);
}
}
private void BroadcastEvent() {
if (!configData.Entrance.Enabled) BroadcastToChat(string.Format(msg("eventopen1"), manager.RaceType));
else {
if (configData.Entrance.ServerRewards) BroadcastToChat(string.Format(msg("eventopenfee1"), configData.Entrance.Amount, msg("serverrewards"), manager.RaceType));
if (configData.Entrance.Economics) BroadcastToChat(string.Format(msg("eventopenfee1"), configData.Entrance.Amount, msg("economics"), manager.RaceType));
if (configData.Entrance.Scrap) if (configData.Entrance.Scrap) BroadcastToChat(string.Format(msg("eventopenfee1"), configData.Entrance.Amount, msg("scrap"), manager.RaceType));
}
if (configData.Rewards.Enabled) {
string rewards = configData.Rewards.ServerRewards ? msg("serverrewards") : configData.Rewards.Scrap ? msg("scrap") : msg("economics");
if (configData.Rewards.Podium) BroadcastToChat(string.Format(msg("eventprizepodium"), configData.Rewards.Prize1, rewards, configData.Rewards.Prize2, configData.Rewards.Prize3));
else BroadcastToChat(string.Format(msg("eventprize"), configData.Rewards.Prize1, rewards));
}
}
private void OnVehicleUnderwater(BasicCar car) {
RaceCar raceCar = car.GetComponent<RaceCar>();
if (raceCar != null) {
raceCar.ResetVehicle();
ins.CarCommander.Call("ToggleController", car, true);
}
}
private static void BroadcastToChat(string message) {
foreach (BasePlayer player in BasePlayer.activePlayerList) player.SendConsoleCommand("chat.add", new object[] {
0, 0, message
}
);
}
#endregion
#region Component
enum EventStatus {
Open, Loading, Started, Finishing, Finished
}
private class RaceManager : MonoBehaviour {
private List<BasePlayer> joiners;
private List<RaceDriver> raceDrivers;
private List<RaceVehicle> raceVehicles;
private List<CheckPoint> checkPoints;
internal List<RaceDriver> spectateTargets;
private RaceScores[] winners;
private int playersFinished;
private int totalRacers;
private string trackName;
private TrackData trackData;
private EventStatus status;
private int maxRacers;
private int countdown = 10;
private int timerTick;
private double startTime;
private double endTime;
private bool hasStarted;
private bool isEnding;
private bool isStarting;
private bool isRacing;
private bool hasLaunchedFirework;
#region Properties
public EventStatus Status {
get {
return status;
}
}
public bool HasTrackSet {
get {
return trackData != null;
}
}
public int RacerCount {
get {
return raceDrivers.Count;
}
}
public int CheckpointCount {
get {
return trackData.checkPoints.Count;
}
}
public int JoinerCount {
get {
return joiners.Count;
}
}
public string TrackName {
get {
return trackName;
}
}
public RaceMode RaceMode {
get {
return trackData.raceMode;
}
}
public RaceType RaceType {
get {
return trackData.raceType;
}
}
public TrackData TrackData {
get {
return trackData;
}
}
public int Laps {
get {
return trackData.laps;
}
}
public double StartTime {
get {
return startTime;
}
}
public double CurrentTime {
get {
return DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
}
}
public bool CanReset {
get {
return isRacing;
}
}
#endregion
private void Awake() {
joiners = Facepunch.Pool.GetList<BasePlayer>();
raceDrivers = Facepunch.Pool.GetList<RaceDriver>();
raceVehicles = Facepunch.Pool.GetList<RaceVehicle>();
checkPoints = Facepunch.Pool.GetList<CheckPoint>();
spectateTargets = Facepunch.Pool.GetList<RaceDriver>();
countdown = ins.configData.Timers.Countdown;
status = EventStatus.Finished;
enabled = false;
}
private void OnDestroy() {
DestroyEvent();
ins.StartEventTimer();
}
#region Event Management
public void SetTrackData(string trackName, TrackData trackData) {
this.trackName = trackName;
this.trackData = trackData;
maxRacers = trackData.gridPoints.Count;
}
public void OpenEvent() {
if (ins.autoTimer != null) ins.autoTimer.Destroy();
isStarting = false;
isEnding = false;
hasStarted = false;
isRacing = false;
status = EventStatus.Loading;
ins.scoreContainer = null;
ServerMgr.Instance.StartCoroutine(CreateCheckPoints());
}
public void StartEvent() {
isStarting = false;
hasStarted = true;
status = EventStatus.Started;
InvokeHandler.CancelInvoke(this, StartEvent);
InvokeHandler.CancelInvoke(this, EndEvent);
foreach (BasePlayer player in joiners) SpawnPlayer(player);
joiners.Clear();
ins.DisplayInformation(raceDrivers, trackName, trackData);
Countdown();
winners = new RaceScores[raceDrivers.Count];
playersFinished = 0;
totalRacers = raceDrivers.Count;
}
public void EndEvent() {
InvokeHandler.CancelInvoke(this, Countdown);
InvokeHandler.CancelInvoke(this, CalculateScores);
raceDrivers.ForEach((RaceDriver driver) => CuiHelper.DestroyUi(driver.Player, UITime));
if (status == EventStatus.Open) {
status = EventStatus.Finished;
ins.IssueRefunds(joiners);
joiners.Clear();
BroadcastToChat(msg("cancelled"));
Destroy(this);
}
else FinishRace();
}
public void JoinEvent(BasePlayer player) {
if (status != EventStatus.Open) {
player.ChatMessage(status == EventStatus.Finished ? msg("noevent", player.UserIDString) : msg("nojoin", player.UserIDString));
return;
}
if (joiners.Contains(player)) {
player.ChatMessage(msg("inevent", player.UserIDString));
return;
}
if (joiners.Count >= maxRacers) {
player.ChatMessage(msg("eventfull", player.UserIDString));
return;
}
joiners.Add(player);
player.ChatMessage(msg("joinevent", player.UserIDString));
BroadcastToChat(string.Format(msg("joinevent.player"), player.displayName));
if (joiners.Count >= trackData.minPlayers && !isStarting) {
isStarting = true;
InvokeHandler.CancelInvoke(this, EndEvent);
BroadcastToChat(string.Format(msg("minplayers.reached"), ins.configData.Timers.StartTime));
InvokeHandler.Invoke(this, StartEvent, ins.configData.Timers.StartTime);
}
}
public void LeaveEvent(BasePlayer player, bool hasFinished) {
if (status == EventStatus.Open) {
if (joiners.Contains(player)) {
ins.IssueRefund(player);
joiners.Remove(player);
if (joiners.Count < trackData.minPlayers) {
isStarting = false;
InvokeHandler.CancelInvoke(this, EndEvent);
InvokeHandler.CancelInvoke(this, StartEvent);
InvokeHandler.Invoke(this, EndEvent, ins.configData.Timers.CloseTime);
BroadcastToChat(string.Format(msg("leftevent.wait"), player.displayName));
}
else BroadcastToChat(string.Format(msg("leftevent"), player.displayName));
}
}
else if (status == EventStatus.Started) {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver != null) {
raceDrivers.Remove(raceDriver);
spectateTargets.Remove(raceDriver);
if (!disableSpectate) {
raceDrivers.ForEach((RaceDriver spectatorDriver) => {
if (spectatorDriver.Player.IsSpectating()) {
if (raceDrivers.Count == 0) spectatorDriver.FinishSpectating();
else {
if (raceDriver.SpectateTarget == raceDriver) raceDriver.UpdateSpectateTarget();
}
}
}
);
}
winners[raceDrivers.Count] = new RaceScores(raceDriver, CurrentTime - StartTime, raceDriver.LapNumber - 1, raceDriver.GetDistanceTravelled());
winners[raceDrivers.Count].hasFinished = false;
Destroy(raceDriver);
if (!hasFinished) BroadcastToChat(string.Format(msg("leftevent"), player.displayName));
totalRacers -= 1;
if (raceDrivers.Count == 0) EndEvent();
}
}
else player.ChatMessage(msg("noevent", player.UserIDString));
}
private void DestroyEvent() {
isStarting = false;
isEnding = false;
isRacing = false;
joiners.Clear();
for (int i = raceDrivers.Count - 1; i >= 0; i--) {
RaceDriver raceDriver = raceDrivers[i];
Destroy(raceDriver);
}
raceDrivers.Clear();
for (int i = raceVehicles.Count - 1; i >= 0; i--) {
RaceVehicle raceVehicle = raceVehicles[i];
BaseMountable baseMountable = raceVehicle.entity;
Destroy(raceVehicle);
}
raceVehicles.Clear();
for (int i = checkPoints.Count - 1; i >= 0; i--) {
CheckPoint checkPoint = checkPoints[i];
Destroy(checkPoint);
}
checkPoints.Clear();
Facepunch.Pool.FreeList(ref joiners);
Facepunch.Pool.FreeList(ref raceDrivers);
Facepunch.Pool.FreeList(ref spectateTargets);
Facepunch.Pool.FreeList(ref raceVehicles);
Facepunch.Pool.FreeList(ref checkPoints);
CancelInvoke();
InvokeHandler.CancelInvoke(this, OpenEvent);
InvokeHandler.CancelInvoke(this, StartEvent);
InvokeHandler.CancelInvoke(this, EndEvent);
InvokeHandler.CancelInvoke(this, CalculateScores);
InvokeHandler.CancelInvoke(this, Countdown);
InvokeHandler.CancelInvoke(this, DestroyCountdown);
InvokeHandler.CancelInvoke(this, FinishRace);
}
private IEnumerator DestroyCheckpoints() {
for (int i = checkPoints.Count - 1; i >= 0; i--) {
CheckPoint checkPoint = checkPoints[i];
Destroy(checkPoint);
yield return new WaitForSeconds(0.5f);
}
checkPoints.Clear();
Destroy(this, 15f);
}
#endregion
#region Player Management
private void SpawnPlayer(BasePlayer player) {
if (player == null) return;
if (player.isMounted) {
player.GetMounted().DismountPlayer(player, true);
player.DismountObject();
}
ins.restoreData.AddData(player);
player.inventory.Strip();
ins.NextTick(() => {
player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, true);
string[] clothing = ins.configData.Racers.Clothing.GetRandom();
for (int i = 0; i < clothing.Length; i++) {
Item item = ItemManager.CreateByName(clothing[i]);
if (item != null) {
if (!item.MoveToContainer(player.inventory.containerWear)) item.Remove(0f);
}
}
}
);
player.metabolism.Reset();
player.health = 100f;
RaceDriver raceDriver = player.gameObject.AddComponent<RaceDriver>();
raceDrivers.Add(raceDriver);
spectateTargets.Add(raceDriver);
int gridId = raceDrivers.Count - 1;
TrackData.PointInfo gridPoint = trackData.gridPoints.ElementAt(gridId);
BaseEntity baseEntity = CreateEntity(RaceType, gridPoint.position, gridPoint.rotation, trackData.useCommander, trackData.UseModularCars);
RaceVehicle raceVehicle = baseEntity.gameObject.AddComponent(VehicleComponentFromRaceType(RaceType)) as RaceVehicle;
ins.spawnedEntities.Add(baseEntity.net.ID);
raceVehicles.Add(raceVehicle);
if (RaceType == RaceType.BoogieBoard || RaceType == RaceType.InnerTube || RaceType == RaceType.Kayak) baseEntity.GetComponent<Rigidbody>().isKinematic = true;
if (RaceType == RaceType.Horse) (baseEntity as RidableHorse).SetBreed(trackData.horseBreed);
raceVehicle.Driver = raceDriver;
raceVehicle.Manager = this;
raceVehicle.gridId = gridId;
raceDriver.Vehicle = raceVehicle;
raceDriver.Manager = this;
if (player.isMounted) {
player.GetMounted()?.DismountPlayer(player);
ins.NextTick(() => ins.MovePosition(player, raceVehicle.transform.position + raceVehicle.transform.up, true));
return;
}
else ins.MovePosition(player, raceVehicle.transform.position + raceVehicle.transform.up, true);
}
private BaseEntity CreateEntity(RaceType raceType, Vector3 position, float rotation, bool useCommander, bool useModularCars) {
if (useModularCars) return SpawnModularCar(position, rotation);
if (raceType == RaceType.Car && ins.CarCommander && useCommander) return SpawnCarCommander(position, rotation);
if (raceType == RaceType.Boat && ins.BoatCommander && useCommander) return SpawnBoatCommander(position, rotation);
if (raceType == RaceType.Minicopter && ins.HeliCommander && useCommander) return SpawnHeliCommander(position, rotation);
string prefab = EntityFromRaceType(raceType);
BaseEntity baseEntity = GameManager.server.CreateEntity(prefab, position + (Vector3.up * 0.2f), Quaternion.Euler(0, rotation, 0));
baseEntity.enableSaving = false;
baseEntity.Spawn();
baseEntity.enabled = false;
return baseEntity;
}
private BaseEntity SpawnCarCommander(Vector3 position, float rotation) {
BaseEntity baseEntity = ins.CarCommander.Call("SpawnAtLocation", position + (Vector3.up * 0.2f), Quaternion.Euler(0, rotation, 0), false, true) as BasicCar;
ins.CarCommander.Call("ToggleController", baseEntity as BasicCar, false);
return baseEntity;
}
private BaseEntity SpawnBoatCommander(Vector3 position, float rotation) {
BaseEntity baseEntity = ins.BoatCommander.Call("SpawnAtLocation", position + (Vector3.up * 0.2f), Quaternion.Euler(0, rotation, 0), false, true, true, false, false, false, RaceType == RaceType.RHIB) as MotorRowboat;
ins.BoatCommander.Call("ToggleController", baseEntity as MotorRowboat, false);
return baseEntity;
}
private BaseEntity SpawnHeliCommander(Vector3 position, float rotation) {
BaseEntity baseEntity = ins.HeliCommander.Call("SpawnAtLocation", position + (Vector3.up * 0.2f), Quaternion.Euler(0, rotation, 0), "Mini", false, 0UL, true) as MiniCopter;
ins.HeliCommander.Call("ToggleController", baseEntity, false);
return baseEntity;
}
private BaseEntity SpawnModularCar(Vector3 position, float rotation) {
ModularCarData modularCarData = ins.carData[TrackData.modularCarProfiles.GetRandom()];
ModularCar modularCar = modularCarData.LoadVehicle(position + (Vector3.up * 0.2f), Quaternion.Euler(0, rotation, 0));
modularCar.SetFlag(BaseEntity.Flags.Reserved1, true);
modularCar.SetFlag(BaseEntity.Flags.On, false);
modularCar.rigidBody.isKinematic = true;
modularCar.enabled = false;
return modularCar;
}
public void PlayerDisconnected(BasePlayer player) {
if (joiners.Contains(player)) {
joiners.Remove(player);
if (joiners.Count < trackData.minPlayers) {
isStarting = false;
InvokeHandler.CancelInvoke(this, EndEvent);
InvokeHandler.CancelInvoke(this, StartEvent);
InvokeHandler.Invoke(this, EndEvent, ins.configData.Timers.CloseTime);
BroadcastToChat(string.Format(msg("leftevent.wait"), player.displayName));
}
else BroadcastToChat(string.Format(msg("leftevent"), player.displayName));
}
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver != null) {
raceDrivers.Remove(raceDriver);
spectateTargets.Remove(raceDriver);
if (!disableSpectate) {
raceDrivers.ForEach((RaceDriver spectatorDriver) => {
if (spectatorDriver.Player.IsSpectating()) {
if (raceDrivers.Count == 0) spectatorDriver.FinishSpectating();
else {
if (raceDriver.SpectateTarget == raceDriver) raceDriver.UpdateSpectateTarget();
}
}
}
);
}
winners[raceDrivers.Count] = new RaceScores(raceDriver, CurrentTime - StartTime, raceDriver.LapNumber - 1, raceDriver.GetDistanceTravelled());
winners[raceDrivers.Count].hasFinished = false;
raceDriver.DismountPlayer();
Destroy(raceDriver);
totalRacers -= 1;
if (raceDrivers.Count == 0) EndEvent();
}
}
public TrackData.PointInfo GetLastCheckpoint(int number) {
if (number < 0) number = 0;
if (number > trackData.checkPoints.Count - 1) number = trackData.checkPoints.Count - 1;
return trackData.checkPoints[number];
}
#endregion
#region Checkpoints
private IEnumerator CreateCheckPoints() {
print("[RaceTrack] Creating race checkpoints, please wait!");
enabled = true;
for (int i = 0; i < trackData.checkPoints.Count; i++) {
print($"[RaceTrack] Building Checkpoint {i + 1}/{trackData.checkPoints.Count}");
TrackData.PointInfo pointInfo = trackData.checkPoints[i];
CheckPoint checkPoint = new GameObject().AddComponent<CheckPoint>();
checkPoint.transform.position = pointInfo.position;
checkPoint.transform.rotation = Quaternion.Euler(0, pointInfo.rotation, 0);
checkPoint.SetCheckpointData(i, pointInfo.size);
checkPoints.Add(checkPoint);
yield return new WaitWhile(()=> checkPoint.IsBuilding);
yield return new WaitForSeconds(0.5f);
}
print("[RaceTrack] All checkpoints have been loaded.Race is now open!");
ins.SaveEntityData();
countdown = Mathf.Max(11, ins.configData.Timers.Countdown);
status = EventStatus.Open;
ins.BroadcastEvent();
InvokeHandler.Invoke(this, EndEvent, ins.configData.Timers.CloseTime);
enabled = false;
}
#endregion
#region Pre-race Setup
private void Countdown() {
--countdown;
ins.UpdateCountdown(raceDrivers, countdown > 0 ? string.Format(msg("countdown.count"), countdown) : msg("countdown.go"));
if (countdown > 0) InvokeHandler.Invoke(this, Countdown, 1f);
else {
startTime = CurrentTime;
EnableAllVehicles();
InvokeHandler.Invoke(this, DestroyCountdown, 1f);
if (trackData.maxTime > 0) {
foreach(RaceDriver raceDriver in raceDrivers) {
raceDriver.Player.ChatMessage(string.Format(msg("eventtimer", raceDriver.Player.UserIDString), ins.FormatTime(trackData.maxTime)));
}
endTime = UnityEngine.Time.realtimeSinceStartup + trackData.maxTime;
InvokeHandler.Invoke(this, FinishRace, trackData.maxTime);
}
}
}
private void EnableAllVehicles() {
foreach(RaceDriver raceDriver in raceDrivers) {
RaceVehicle raceVehicle = raceDriver.Vehicle;
if (RaceType == RaceType.Car) {
if (TrackData.UseModularCars) {
raceVehicle.entity.SetFlag(BaseEntity.Flags.Reserved1, false);
raceVehicle.entity.SetFlag(BaseEntity.Flags.On, true);
(raceVehicle.entity as ModularCar).rigidBody.isKinematic = false;
raceVehicle.entity.enabled = true;
}
else {
if (ins.CarCommander && TrackData.useCommander) ins.CarCommander.Call("ToggleController", raceVehicle.entity as BasicCar, true);
else raceVehicle.entity.GetComponent<BasicCar>().enabled = true;
}
}
else if (RaceType == RaceType.Boat || RaceType == RaceType.RHIB) {
if (ins.BoatCommander && TrackData.useCommander) ins.BoatCommander.Call("ToggleController", raceVehicle.entity as MotorRowboat, true);
else raceVehicle.entity.GetComponent<MotorRowboat>().enabled = true;
raceVehicle.entity.GetComponent<MotorRowboat>().EngineToggle(true);
}
else if (RaceType == RaceType.Kayak || RaceType == RaceType.BoogieBoard || RaceType == RaceType.InnerTube) {
raceVehicle.entity.GetComponent<Rigidbody>().isKinematic = false;
}
else if (RaceType == RaceType.Horse) {
raceVehicle.entity.GetComponent<BaseRidableAnimal>().enabled = true;
}
else {
if (ins.HeliCommander && TrackData.useCommander) ins.HeliCommander.Call("ToggleController", raceVehicle.entity as MiniCopter, true);
else raceVehicle.entity.GetComponent<MiniCopter>().enabled = true;
(raceVehicle as RaceHelicopter).OnRaceStarted();
}
raceDriver.StartLap();
}
isRacing = true;
}
private void DestroyCountdown() {
foreach (var raceDriver in raceDrivers) {
raceDriver.DestroyUI(UIInfo);
raceDriver.DestroyUI(UITime);
if (trackData.raceMode == RaceMode.Laps) ins.UpdateLapCounter(raceDriver, true, null);
else ins.UpdateLapCounter(raceDriver, false, null);
}
}
#endregion
#region Finished Racers
public void FinishedRace(RaceDriver raceDriver) {
spectateTargets.Remove(raceDriver);
if (!hasLaunchedFirework && ins.configData.Checkpoints.LaunchFirework) {
MortarFirework firework = GameManager.server.CreateEntity(ins.configData.Checkpoints.FireworkPrefab, raceDriver.transform.position + (Vector3.up * 3f)) as MortarFirework;
if (firework != null) {
firework.enableSaving = false;
firework.Spawn();
firework.Ignite();
}
hasLaunchedFirework = true;
}
winners[playersFinished] = new RaceScores(raceDriver, trackData);
playersFinished++;
if (!disableSpectate) {
raceDrivers.ForEach((RaceDriver spectatorDriver) => {
if (spectatorDriver.Player.IsSpectating()) {
if (playersFinished >= totalRacers) spectatorDriver.FinishSpectating();
else {
if (spectatorDriver.SpectateTarget == raceDriver) spectatorDriver.UpdateSpectateTarget();
}
}
}
);
}
raceDriver.DismountPlayer();
if (!isEnding) {
isEnding = true;
int finishTime = ins.configData.Timers.EndTime;
if (trackData.maxTime > 0) {
double forcedFinishTime = endTime - UnityEngine.Time.realtimeSinceStartup;
if (forcedFinishTime < finishTime) finishTime = (int)forcedFinishTime;
}
foreach(RaceDriver driver in raceDrivers) driver.Player.ChatMessage(string.Format(msg("win.end", driver.Player.UserIDString), raceDriver.Player.displayName, finishTime));
timerTick = ins.configData.Timers.EndTime;
TimerTick();
}
if (playersFinished >= totalRacers) {
InvokeHandler.CancelInvoke(this, FinishRace);
FinishRace();
}
else {
if (disableSpectate) LeaveEvent(raceDriver.Player, true);
else ins.NextTick(raceDriver.BeginSpectating);
}
ins.NextTick(() => Destroy(raceDriver.Vehicle));
}
private void TimerTick() {
timerTick -= 1;
if (timerTick == 30 || timerTick == 10) {
int finishTime = timerTick;
if (trackData.maxTime > 0) {
double forcedFinishTime = endTime - UnityEngine.Time.realtimeSinceStartup;
if (forcedFinishTime < finishTime) finishTime = (int)forcedFinishTime;
}
foreach (RaceDriver driver in raceDrivers) driver.Player.ChatMessage(string.Format(msg("win.countdown", driver.Player.UserIDString), finishTime));
}
if (timerTick > 0) InvokeHandler.Invoke(this, TimerTick, 1f);
if (timerTick == 0) FinishRace();
}
public void FinishRace() {
InvokeHandler.CancelInvoke(this, TimerTick);
status = EventStatus.Finishing;
foreach(RaceDriver raceDriver in raceDrivers) {
if (raceDriver.Player.IsSpectating()) raceDriver.FinishSpectating();
raceDriver.DestroyAllUI();
}
ins.NextTick(DismountDrivers);
}
private void DismountDrivers() {
foreach (RaceDriver raceDriver in raceDrivers) raceDriver.DismountPlayer();
ins.NextTick(CalculateScores);
}
public void UpdateLapCounter(RaceDriver raceDriver) {
ins.UpdateLapCounter(raceDriver, RaceMode == RaceMode.Laps, null);
foreach (RaceDriver spectator in raceDrivers) {
if (spectator.SpectateTarget == raceDriver) ins.UpdateLapCounter(raceDriver, RaceMode == RaceMode.Laps, spectator);
}
}
public void UpdateLapTime(RaceDriver raceDriver) {
ins.UpdateLapTime(raceDriver, raceDriver.LastLapTime, null);
foreach (RaceDriver spectator in raceDrivers) {
if (spectator.SpectateTarget == raceDriver) ins.UpdateLapTime(raceDriver, raceDriver.LastLapTime, spectator);
}
}
#endregion
#region Scores
public void CalculateScores() {
foreach (RaceDriver raceDriver in raceDrivers.Where(x => !x.HasFinished).OrderByDescending(x => x.GetDistanceTravelled())) {
winners[playersFinished] = new RaceScores(raceDriver, CurrentTime - StartTime, raceDriver.LapNumber - 1, raceDriver.GetDistanceTravelled());
playersFinished++;
}
string podiumStr = string.Empty;
for (int i = 0; i < 3; i++) {
if (winners.Length >= i + 1 && winners[i] != null) podiumStr += string.Format(msg("win.podium"), i == 0 ? "\n1st" : i == 1 ? "\n2nd" : "\n3rd", ins.StripTags(winners[i].displayName));
}
BroadcastToChat(string.Format(msg("win.winners"), podiumStr));
BroadcastToChat(msg("win.viewscores"));
ins.CreateScoreboard(TrackName, winners);
foreach (RaceDriver raceDriver in raceDrivers) raceDriver.AddUI(ins.scoreContainer, UIScoreboard);
IssueRewards(winners);
ServerMgr.Instance.StartCoroutine(DestroyCheckpoints());
}
private void IssueRewards(RaceScores[] raceScores) {
int amount = raceScores.Length < 3 ? raceScores.Length : 3;
for (int i = 0; i < amount; i++) {
if (raceScores[i] == null) continue;
if (raceScores[i].hasFinished) ins.IssueReward(raceScores[i].playerId, i + 1);
}
}
public class RaceScores {
public ulong playerId;
public string displayName;
public double raceTime;
public float laps;
public float distance;
public bool hasFinished;
// ... [Code preview truncated due to length. Full source in attached file] ...