C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
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 System.Reflection;
using Rust;
namespace Oxide.Plugins {
using Extensions;
[Info("RaceTrack", "k1lly0u", "0.2.24")]
[Description("A standalone racing event for cars, boats, mincopters and horses")]
class RaceTrack : RustPlugin {
#region Fields
[PluginReference]
Plugin CarCommander, BoatCommander, HeliCommander, Economics, ServerRewards;
private Hash<string, TrackData> trackData;
private Hash<string, ModularCarData> carData;
private RestoreData restoreData;
private List<ulong> spawnedEntities;
private DynamicConfigFile entitydata, trackdata, cardata, restorationData;
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 static RaceTrack Instance;
private static RaceManager Current;
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 SUBMARINE_PREFAB = "assets/content/vehicles/submarine/submarinesolo.entity.prefab";
private const string SNOWMOBILE_PREFAB = "assets/content/vehicles/snowmobiles/snowmobile.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, Submarine, Snowmobile, Foot
}
private enum EventStatus {
Open, Loading, Prestart, Started, Finishing, Finished
}
private enum LightType {
Lantern, Flasher, Siren
}
#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()
}
;
LoadData();
}
private void OnServerInitialized() {
Instance = this;
if (Configuration.DisableSpectate) Unsubscribe(nameof(CanSpectateTarget));
if (!Configuration.Racers.DisableMetabolism) Unsubscribe(nameof(OnRunPlayerMetabolism));
Instance.Unsubscribe(nameof(OnEngineStart));
lightType = ParseType<LightType>(Configuration.Checkpoints.LightType);
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 (Current) Current.PlayerDisconnected(player);
else {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver) {
raceDriver.DismountPlayer();
NextTick(() => {
if (raceDriver) UnityEngine.Object.Destroy(raceDriver);
}
);
}
}
}
private void OnPlayerSleepEnded(BasePlayer player) {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver) {
if (!player.isMounted) {
if (raceDriver.Vehicle && raceDriver.Vehicle.GetEntity()) raceDriver.MountPlayer(raceDriver.Vehicle);
UpdatePosition(raceDriver);
}
}
}
private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
if (!entity || info == null) return;
if (entity.GetComponent<RaceDriver>()) {
NullifyDamage(ref info);
return;
}
if (entity.GetComponent<RaceVehicle>()) {
if (Current.AllowDamage && entity is ModularCar) {
HandleModularVehicleDamage(entity, info);
return;
}
NullifyDamage(ref info);
return;
}
BaseVehicleModule module = entity as BaseVehicleModule;
if (module && module.Vehicle?.GetComponent<RaceVehicle>()) {
if (Current.AllowDamage) {
HandleModularVehicleDamage(entity, info);
return;
}
NullifyDamage(ref info);
return;
}
CheckPoint.Marker marker = entity.GetComponent<CheckPoint.Marker>();
if (marker && Current && Current.Status != EventStatus.Finished) NullifyDamage(ref info);
}
private void HandleModularVehicleDamage(BaseCombatEntity entity, HitInfo info) {
BaseModularVehicle baseModularVehicle = entity as BaseModularVehicle ?? (entity as BaseVehicleModule)?.Vehicle;
if (baseModularVehicle) {
RaceDriver raceDriver = entity.GetComponent<RaceVehicle>()?.Driver;
if (raceDriver) {
if (info.damageTypes.GetMajorityDamageType() != DamageType.Collision) {
NullifyDamage(ref info);
return;
}
info.damageTypes.Scale(DamageType.Collision, Current.TrackData.modularDamageMulti);
if (info.damageTypes.Total() >= baseModularVehicle.health) {
Current.FailedRace(raceDriver);
}
}
}
}
private object OnRunPlayerMetabolism(PlayerMetabolism metabolism, BasePlayer owner, float delta) => owner.GetComponent<RaceDriver>() ? (object)true : null;
private object CanMountEntity(BasePlayer player, BaseMountable mountable) => mountable.GetComponent<RaceModularCar>() ? (object)true : null;
private object CanDismountEntity(BasePlayer player, BaseMountable mountable) {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver && !raceDriver.HasFinished) return false;
return null;
}
private object OnEngineStart(MiniCopter miniCopter, BasePlayer player) {
if (!Current || Current.Status != EventStatus.Prestart) return null;
RaceHelicopter raceHelicopter = miniCopter.GetComponent<RaceHelicopter>();
if (raceHelicopter) return false;
return null;
}
private object OnPlayerCommand(BasePlayer player, string command, string[] args) {
if (player && player.GetComponent<RaceDriver>()) {
if (Configuration.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 && player.GetComponent<RaceDriver>()) {
if (Configuration.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 && raceDriver.Player.IsSpectating()) {
raceDriver.UpdateSpectateTarget(1);
return false;
}
return null;
}
private object OnSamSiteTarget(SamSite samSite, BaseCombatEntity baseCombatEntity) => baseCombatEntity.GetComponent<RaceVehicle>() ? (object)true : null;
private void Unload() {
SaveRestoreData();
if (autoTimer != null) autoTimer.Destroy();
RaceDriver[] raceDrivers = UnityEngine.Object.FindObjectsOfType<RaceDriver>();
if (raceDrivers != null) {
for (int i = 0; i < raceDrivers.Length; i++) {
UnityEngine.Object.Destroy(raceDrivers[i]);
}
}
RaceVehicle[] raceVehicles = UnityEngine.Object.FindObjectsOfType<RaceVehicle>();
if (raceVehicles != null) {
for (int i = 0; i < raceVehicles.Length; i++) {
UnityEngine.Object.Destroy(raceVehicles[i]);
}
}
CheckPoint[] checkPoints = UnityEngine.Object.FindObjectsOfType<CheckPoint>();
if (checkPoints != null) {
for (int i = 0; i < checkPoints.Length; i++) {
UnityEngine.Object.Destroy(checkPoints[i]);
}
}
CheckPoint.Marker[] markers = UnityEngine.Object.FindObjectsOfType<CheckPoint.Marker>();
if (markers != null) {
for (int i = 0; i < markers.Length; i++) {
UnityEngine.Object.Destroy(markers[i]);
}
}
UnityEngine.Object.Destroy(Current);
Current = null;
Instance = null;
Configuration = null;
}
#endregion
#region Functions
private void CleanupEntities() {
if (spawnedEntities.Count > 0) {
PrintWarning("Finding and destroying left over entities");
foreach (BaseNetworkable obj in BaseNetworkable.serverEntities) {
if (obj && !obj.IsDestroyed) {
if (spawnedEntities.Contains(obj.net.ID.Value)) obj.Kill(BaseNetworkable.DestroyMode.None);
}
}
spawnedEntities.Clear();
SaveEntityData();
PrintWarning("Cleanup completed");
}
}
private T ParseType<T>(string type) {
try {
return (T)Enum.Parse(typeof(T), type, true);
}
catch {
return default(T);
}
}
private static string FormatTime(double time) {
TimeSpan dateDifference = TimeSpan.FromSeconds((float)time);
int hours = dateDifference.Hours + (dateDifference.Days * 24);
if (hours > 0) return string.Format("{0:00}:{1:00}:{2:00}", hours, dateDifference.Minutes, dateDifference.Seconds);
else return string.Format("{0:00}:{1:00}", dateDifference.Minutes, dateDifference.Seconds);
}
private static bool IsWaterSurfaceBasedRace(RaceType raceType) => raceType == RaceType.Boat || raceType == RaceType.BoogieBoard || raceType == RaceType.InnerTube || raceType == RaceType.Kayak || raceType == RaceType.RHIB;
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;
case RaceType.Submarine : return SUBMARINE_PREFAB;
case RaceType.Snowmobile : return SNOWMOBILE_PREFAB;
default : return string.Empty;
}
}
private static Type VehicleComponentFromRaceType(RaceType raceType) {
switch (raceType) {
case RaceType.Car : return typeof(RaceModularCar);
case RaceType.Boat : case RaceType.RHIB : return typeof(RaceBoat);
case RaceType.Kayak : return typeof(RaceKayak);
case RaceType.BoogieBoard : case RaceType.InnerTube : return typeof(RaceInflatable);
case RaceType.Minicopter : return typeof(RaceHelicopter);
case RaceType.Horse : return typeof(RaceHorse);
case RaceType.Submarine : return typeof(RaceSubmarine);
case RaceType.Snowmobile : return typeof(RaceSnowmobile);
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 (Configuration.Automation.Enabled) {
if (trackData.Count == 0) {
PrintError("You have no race tracks set up.Unable to automate events");
return;
}
autoTimer = timer.In(Configuration.Timers.Interval, () => {
if (Configuration.Automation.MinimumPlayers > 0 && BasePlayer.activePlayerList.Count < Configuration.Automation.MinimumPlayers) {
StartEventTimer();
return;
}
if (Configuration.Automation.Time.Enabled) {
float currentTime = TOD_Sky.Instance.Cycle.Hour;
if (Configuration.Automation.Time.Start > Configuration.Automation.Time.End) {
if (currentTime > Configuration.Automation.Time.Start || currentTime < Configuration.Automation.Time.End) {
StartEventTimer();
return;
}
}
else {
if (currentTime > Configuration.Automation.Time.Start && currentTime < Configuration.Automation.Time.End) {
StartEventTimer();
return;
}
}
}
if (!Current) Current = new GameObject().AddComponent<RaceManager>();
if (Current.Status != EventStatus.Finished) return;
if (Configuration.Automation.Random) {
KeyValuePair<string, TrackData> data = trackData.ElementAt(UnityEngine.Random.Range(0, trackData.Count));
Current.SetTrackData(data.Key, data.Value);
Current.OpenEvent();
}
else {
if (Configuration.Automation.Order.Count < 1) {
PrintError("You have no race tracks set in your config.Unable to automate events by order");
return;
}
if (!trackData.ContainsKey(Configuration.Automation.Order[lastEventIndex])) {
PrintError($"Unable to find a race track with the name : {Configuration.Automation.Order[lastEventIndex]}");
return;
}
TrackData data = trackData[Configuration.Automation.Order[lastEventIndex]];
Current.SetTrackData(Configuration.Automation.Order[lastEventIndex], data);
lastEventIndex++;
if (lastEventIndex >= Configuration.Automation.Order.Count) lastEventIndex = 0;
Current.OpenEvent();
}
}
);
}
}
private void BroadcastEvent() {
if (!Configuration.Entrance.Enabled) BroadcastToChat(string.Format(msg("eventopen1"), Current.RaceType));
else {
if (Configuration.Entrance.ServerRewards) BroadcastToChat(string.Format(msg("eventopenfee1"), Configuration.Entrance.Amount, msg("serverrewards"), Current.RaceType));
if (Configuration.Entrance.Economics) BroadcastToChat(string.Format(msg("eventopenfee1"), Configuration.Entrance.Amount, msg("economics"), Current.RaceType));
if (Configuration.Entrance.Scrap) BroadcastToChat(string.Format(msg("eventopenfee1"), Configuration.Entrance.Amount, msg("scrap"), Current.RaceType));
}
if (Configuration.Rewards.Enabled) {
string rewards = Configuration.Rewards.ServerRewards ? msg("serverrewards") : Configuration.Rewards.Scrap ? msg("scrap") : msg("economics");
if (Configuration.Rewards.Podium) BroadcastToChat(string.Format(msg("eventprizepodium"), Configuration.Rewards.Prize1, rewards, Configuration.Rewards.Prize2, Configuration.Rewards.Prize3));
else BroadcastToChat(string.Format(msg("eventprize"), Configuration.Rewards.Prize1, rewards));
}
}
private void OnVehicleUnderwater(BasicCar car) {
RaceModularCar raceCar = car.GetComponent<RaceModularCar>();
if (raceCar) {
raceCar.ResetVehicle();
Instance.CarCommander.Call("ToggleController", car, true);
}
}
private static void DisableTerrainAntiHack() {
int protection = ConVar.AntiHack.terrain_protection;
ConVar.AntiHack.terrain_protection = 0;
Instance.timer.In(3f, () => ConVar.AntiHack.terrain_protection = protection);
}
private static void BroadcastToChat(string message) {
foreach (BasePlayer player in BasePlayer.activePlayerList) player.SendConsoleCommand("chat.add", new object[] {
0, 0, message
}
);
}
private static string ToOrdinal(int i) => (i + "th").Replace("1th", "1st").Replace("2th", "2nd").Replace("3th", "3rd");
private object IsEventPlayer(BasePlayer player) => player.GetComponent<RaceDriver>() ? (object)true : null;
private object isEventPlayer(BasePlayer player) => player.GetComponent<RaceDriver>() ? (object)true : null;
#endregion
#region Component
private class RaceManager : MonoBehaviour {
private List<BasePlayer> joiners;
private List<RaceDriver> raceDrivers;
private List<RaceVehicle> raceVehicles;
private List<CheckPoint> checkPoints;
private List<RaceDriver> positions;
internal List<RaceDriver> spectateTargets;
private RaceScores[] winners;
private int playersFinished;
private int playersFailed;
private int totalRacers;
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 hasLaunchedFirework;
#region Properties
public EventStatus Status {
get;
private set;
}
public bool HasTrackSet => TrackData != null;
public int RacerCount => raceDrivers.Count;
public int CheckpointCount => TrackData.checkPoints.Count;
public int JoinerCount => joiners.Count;
public string TrackName {
get;
private set;
}
public RaceMode RaceMode => TrackData.raceMode;
public RaceType RaceType => TrackData.raceType;
public bool AllowDamage => TrackData.allowVehicleDamage && TrackData.UseModularCars;
public TrackData TrackData {
get;
private set;
}
public List<CheckPoint> Checkpoints => checkPoints;
public int Laps => TrackData.laps;
public double StartTime => startTime;
public double CurrentTime => DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
public bool CanReset {
get;
private set;
}
#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>();
positions = Facepunch.Pool.GetList<RaceDriver>();
countdown = Configuration.Timers.Countdown;
Status = EventStatus.Finished;
enabled = false;
}
private void OnDestroy() {
DestroyEvent();
Instance?.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 (Instance.autoTimer != null) Instance.autoTimer.Destroy();
isStarting = false;
isEnding = false;
hasStarted = false;
CanReset = false;
Status = EventStatus.Loading;
Instance.scoreContainer = null;
ServerMgr.Instance.StartCoroutine(CreateCheckPoints());
}
public void StartEvent() {
if (TrackData.raceType == RaceType.Minicopter) Instance.Subscribe("OnEngineStart");
isStarting = false;
hasStarted = true;
Status = EventStatus.Prestart;
InvokeHandler.CancelInvoke(this, StartEvent);
InvokeHandler.CancelInvoke(this, EndEvent);
for (int i = 0; i < joiners.Count; i++) {
BasePlayer joiner = joiners[i];
if (!joiner || joiner.IsDead()) continue;
SpawnPlayer(joiner);
}
joiners.Clear();
DisplayInformation(raceDrivers, TrackName, TrackData);
Countdown();
winners = new RaceScores[raceDrivers.Count];
playersFinished = 0;
totalRacers = raceDrivers.Count;
}
public void EndEvent() {
Instance.Unsubscribe("OnEngineStart");
InvokeHandler.CancelInvoke(this, Countdown);
InvokeHandler.CancelInvoke(this, CalculateScores);
raceDrivers.ForEach((RaceDriver driver) => {
driver.UpdateTravelDistance();
CuiHelper.DestroyUi(driver.Player, UITime);
}
);
if (Status == EventStatus.Open) {
Status = EventStatus.Finished;
Instance.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"), Configuration.Timers.StartTime));
InvokeHandler.Invoke(this, StartEvent, Configuration.Timers.StartTime);
}
}
public void LeaveEvent(BasePlayer player, bool hasFinished) {
if (Status == EventStatus.Open) {
if (joiners.Contains(player)) {
Instance.IssueRefund(player);
joiners.Remove(player);
if (joiners.Count < TrackData.minPlayers) {
isStarting = false;
InvokeHandler.CancelInvoke(this, EndEvent);
InvokeHandler.CancelInvoke(this, StartEvent);
InvokeHandler.Invoke(this, EndEvent, Configuration.Timers.CloseTime);
BroadcastToChat(string.Format(msg("leftevent.wait"), player.displayName));
}
else BroadcastToChat(string.Format(msg("leftevent"), player.displayName));
}
}
else if (Status == EventStatus.Prestart || Status == EventStatus.Started) {
RaceDriver raceDriver = player.GetComponent<RaceDriver>();
if (raceDriver) {
raceDrivers.Remove(raceDriver);
spectateTargets.Remove(raceDriver);
positions.Remove(raceDriver);
raceDriver.UpdateTravelDistance();
if (!Configuration.DisableSpectate) {
DisableTerrainAntiHack();
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.TotalDistanceTravelled);
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;
CanReset = false;
joiners.Clear();
for (int i = raceDrivers.Count - 1; i >= 0; i--) Destroy(raceDrivers[i]);
raceDrivers.Clear();
for (int i = raceVehicles.Count - 1; i >= 0; i--) Destroy(raceVehicles[i]);
raceVehicles.Clear();
for (int i = checkPoints.Count - 1; i >= 0; i--) Destroy(checkPoints[i]);
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);
Facepunch.Pool.FreeList(ref positions);
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--) {
Destroy(checkPoints[i]);
yield return new WaitForSeconds(0.5f);
}
checkPoints.Clear();
Destroy(this, 15f);
}
#endregion
#region Player Management
private void SpawnPlayer(BasePlayer player) {
if (!player) return;
if (player.isMounted) {
player.GetMounted().DismountPlayer(player, true);
player.DismountObject();
}
if (player.IsSleeping()) player.EndSleeping();
player.inventory.crafting.CancelAll(true);
Instance.restoreData.AddData(player);
player.inventory.Strip();
Instance.NextTick(() => {
player.inventory.containerWear.SetFlag(ItemContainer.Flag.IsLocked, true);
string[] clothing = Configuration.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;
int gridId = raceDrivers.Count;
RaceDriver raceDriver = player.gameObject.AddComponent<RaceDriver>();
raceDrivers.Add(raceDriver);
spectateTargets.Add(raceDriver);
positions.Add(raceDriver);
TrackData.PointInfo gridPoint = TrackData.gridPoints[gridId];
if (TrackData.raceType == RaceType.Foot) {
raceDriver.gridId = gridId;
raceDriver.Manager = this;
raceDriver.Position = gridId + 1;
if (player.isMounted) {
player.GetMounted()?.DismountPlayer(player);
Instance.NextTick(() => {
MovePosition(player, gridPoint.position, true);
player.OverrideViewAngles(Quaternion.Euler(0, gridPoint.rotation, 0) * Vector3.forward);
}
);
return;
}
MovePosition(player, gridPoint.position, true);
player.OverrideViewAngles(Quaternion.Euler(0, gridPoint.rotation, 0) * Vector3.forward);
raceDriver.freezePosition = gridPoint.position;
}
else {
BaseEntity baseEntity = CreateEntity(RaceType, gridPoint.position, gridPoint.rotation, TrackData.useCommander, TrackData.UseModularCars);
RaceVehicle raceVehicle = RaceType == RaceType.Car && !TrackData.UseModularCars ? baseEntity.gameObject.AddComponent<RaceSedan>() : baseEntity.gameObject.AddComponent(VehicleComponentFromRaceType(RaceType)) as RaceVehicle;
Instance.spawnedEntities.Add(baseEntity.net.ID.Value);
raceVehicles.Add(raceVehicle);
raceVehicle.ToggleKinematic(true);
if (RaceType == RaceType.Horse) (baseEntity as RidableHorse).SetBreed(TrackData.horseBreed);
raceVehicle.Driver = raceDriver;
raceVehicle.Manager = this;
raceDriver.gridId = gridId;
raceDriver.Vehicle = raceVehicle;
raceDriver.Manager = this;
raceDriver.Position = gridId + 1;
raceDriver.enabled = false;
if (player.isMounted) {
player.GetMounted()?.DismountPlayer(player);
Instance.NextTick(() => MovePosition(player, raceVehicle.transform.position + raceVehicle.transform.up, true));
return;
}
MovePosition(player, raceVehicle.transform.position + raceVehicle.transform.up, true);
}
}
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, Configuration.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) {
raceDrivers.Remove(raceDriver);
spectateTargets.Remove(raceDriver);
positions.Remove(raceDriver);
if (!Configuration.DisableSpectate) {
DisableTerrainAntiHack();
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.TotalDistanceTravelled);
winners[raceDrivers.Count].hasFinished = false;
raceDriver.DismountPlayer();
Destroy(raceDriver);
totalRacers -= 1;
if (raceDrivers.Count == 0) EndEvent();
}
}
private BaseEntity CreateEntity(RaceType raceType, Vector3 position, float rotation, bool useCommander, bool useModularCars) {
if (IsWaterSurfaceBasedRace(raceType)) position.y = TerrainMeta.WaterMap.GetHeight(position);
if (useModularCars) return SpawnModularCar(position, rotation);
if (raceType == RaceType.Car && Instance.CarCommander && useCommander) return SpawnCarCommander(position, rotation);
if (raceType == RaceType.Boat && Instance.BoatCommander && useCommander) return SpawnBoatCommander(position, rotation);
if (raceType == RaceType.Minicopter && Instance.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 = Instance.CarCommander.Call("SpawnAtLocation", position + (Vector3.up * 0.2f), Quaternion.Euler(0, rotation, 0), false, true) as BasicCar;
Instance.CarCommander.Call("ToggleController", baseEntity as BasicCar, false);
return baseEntity;
}
private BaseEntity SpawnBoatCommander(Vector3 position, float rotation) {
BaseEntity baseEntity = Instance.BoatCommander.Call("SpawnAtLocation", position + (Vector3.up * 0.2f), Quaternion.Euler(0, rotation, 0), false, true, true, false, false, false, RaceType == RaceType.RHIB) as MotorRowboat;
Instance.BoatCommander.Call("ToggleController", baseEntity as MotorRowboat, false);
return baseEntity;
}
private BaseEntity SpawnHeliCommander(Vector3 position, float rotation) {
BaseEntity baseEntity = Instance.HeliCommander.Call("SpawnAtLocation", position + (Vector3.up * 0.2f), Quaternion.Euler(0, rotation, 0), "Mini", false, 0UL, true) as MiniCopter;
Instance.HeliCommander.Call("ToggleController", baseEntity, false);
return baseEntity;
}
private BaseEntity SpawnModularCar(Vector3 position, float rotation) {
ModularCarData modularCarData = Instance.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.SetHealth(200);
modularCar.SetMaxHealth(200);
return modularCar;
}
#endregion
#region Checkpoints
private IEnumerator CreateCheckPoints() {
print("[RaceTrack] Creating race checkpoints, please wait!");
enabled = true;
for (int i = 0; i < CheckpointCount; i++) {
print($"[RaceTrack] Building Checkpoint {i + 1}/{CheckpointCount}");
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);
if (pointInfo.SegmentDistance < 0f) {
if (i == CheckpointCount - 1) {
if (TrackData.raceMode == RaceMode.Laps) pointInfo.SegmentDistance = Vector3.Distance(pointInfo.position, TrackData.checkPoints[0].position);
else pointInfo.SegmentDistance = 0f;
}
else pointInfo.SegmentDistance = Vector3.Distance(pointInfo.position, TrackData.checkPoints[i + 1].position);
}
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!");
Instance.SaveEntityData();
countdown = Mathf.Max(11, Configuration.Timers.Countdown);
Status = EventStatus.Open;
Instance.BroadcastEvent();
InvokeHandler.Invoke(this, EndEvent, Configuration.Timers.CloseTime);
enabled = false;
}
public TrackData.PointInfo GetCheckpoint(int index) {
if (index < 0) index += CheckpointCount;
if (index >= CheckpointCount) index -= CheckpointCount;
return TrackData.checkPoints[Mathf.Clamp(index, 0, CheckpointCount - 1)];
}
#endregion
#region Pre-race Setup
private void Countdown() {
--countdown;
UpdateCountdown(raceDrivers, countdown > 0 ? string.Format(msg("countdown.count"), countdown) : msg("countdown.go"));
if (countdown > 0) InvokeHandler.Invoke(this, Countdown, 1f);
else {
if (TrackData.raceType == RaceType.Minicopter) Instance.Unsubscribe("OnEngineStart");
Status = EventStatus.Started;
startTime = CurrentTime;
EnableAllVehicles();
InvokeHandler.Invoke(this, DestroyCountdown, 1f);
if (TrackData.maxTime > 0) {
for (int i = 0; i < raceDrivers.Count; i++) {
RaceDriver raceDriver = raceDrivers[i];
raceDriver.Player.ChatMessage(string.Format(msg("eventtimer", raceDriver.Player.UserIDString), FormatTime(TrackData.maxTime)));
}
endTime = UnityEngine.Time.realtimeSinceStartup + TrackData.maxTime;
InvokeHandler.Invoke(this, FinishRace, TrackData.maxTime);
}
InvokeHandler.InvokeRepeating(this, UpdateDriverPositions, 1f, 1f);
}
}
private void EnableAllVehicles() {
if (TrackData.raceType == RaceType.Foot) {
for (int i = 0; i < raceDrivers.Count; i++) {
RaceDriver raceDriver = raceDrivers[i];
if (raceDriver) raceDriver.enabled = false;
}
return;
}
for (int i = 0; i < raceDrivers.Count; i++) {
RaceDriver raceDriver = raceDrivers[i];
RaceVehicle raceVehicle = raceDriver.Vehicle;
BaseMountable vehicle = raceVehicle.GetEntity();
if (RaceType == RaceType.Car) {
if (TrackData.UseModularCars) {
vehicle.SetFlag(BaseEntity.Flags.Reserved1, false);
vehicle.SetFlag(BaseEntity.Flags.On, true);
}
else {
if (Instance.CarCommander && TrackData.useCommander) Instance.CarCommander.Call("ToggleController", vehicle as BasicCar, true);
}
}
else if (RaceType == RaceType.Boat || RaceType == RaceType.RHIB) {
if (Instance.BoatCommander && TrackData.useCommander) Instance.BoatCommander.Call("ToggleController", vehicle as MotorRowboat, true);
vehicle.GetComponent<MotorRowboat>().EngineToggle(true);
}
else if (RaceType == RaceType.Minicopter) {
MiniCopter miniCopter = vehicle as MiniCopter;
if (Instance.HeliCommander && TrackData.useCommander) Instance.HeliCommander.Call("ToggleController", miniCopter, true);
(raceVehicle as RaceHelicopter).OnRaceStarted();
miniCopter.SetFlag(miniCopter.engineController.engineStartingFlag, true, false, true);
miniCopter.SetFlag(BaseEntity.Flags.On, false, false, true);
miniCopter.Invoke(miniCopter.engineController.FinishStartingEngine, miniCopter.engineController.engineStartupTime);
}
vehicle.enabled = true;
raceVehicle.ToggleKinematic(false);
raceVehicle.WakeUp();
raceDriver.StartRace();
}
CanReset = true;
}
private void DestroyCountdown() {
for (int i = 0; i < raceDrivers.Count; i++) {
RaceDriver raceDriver = raceDrivers[i];
raceDriver.DestroyUI(UIInfo);
raceDriver.DestroyUI(UITime);
if (TrackData.raceMode == RaceMode.Laps) RaceTrack.UpdateLapCounter(raceDriver, true, null);
el
// ... [Code preview truncated due to length. Full source in attached file] ...