using HarmonyLib; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; using UnityEngine.SceneManagement; namespace Oxide.Plugins { [Info("GhostRider", "0xF", "1.2.0")] partial class GhostRider : RustPlugin { private const string MOTORBIKE_DRIVER_SEAT_PREFAB = "assets/prefabs/vehicle/seats/motorbikedriverseat.prefab"; private const string BOOST_EFFECT_PREFAB = "assets/prefabs/npc/patrol helicopter/effects/gun_fire_small.prefab"; private const string FLAME_PREFAB = "assets/prefabs/weapons/flamethrower/flamethrower_fireball.prefab"; private readonly string[] HORSE_BONE_NAMES = new string[] { "head", "L_Fore_Foot_END", "R_Fore_Foot_END", "L_Rear_Foot_END", "R_Rear_Foot_END" }; private const uint FIREBALL_PREFABID = 3369311876; private static uint HEADBONE = StringPool.Get("head"); private static GhostRider Instance; private Dictionary> subEntities = new Dictionary>(); void Init() { Instance = this; } void OnServerInitialized() { Bike.doPlayerDamage = config.CanCrashDamage; foreach (BasePlayer player in BasePlayer.allPlayerList) { DisposeRiderStaff(player); } foreach (BaseNetworkable baseNetworkable in BaseNetworkable.serverEntities) { if (baseNetworkable is BaseVehicle) { BaseVehicle vehicle = baseNetworkable as BaseVehicle; if (vehicle.HasDriver()) { if (baseNetworkable is Bike) SetupRiderStaff(vehicle.GetDriver(), vehicle as Bike); else if (baseNetworkable is RidableHorse) SetupRiderStaff(vehicle.GetDriver(), vehicle as RidableHorse); } } } } void OnEntityKill(BaseVehicle vehicle) { if (vehicle is Bike || vehicle is RidableHorse) DisposeRiderStaff(vehicle); } void Unload() { foreach (BasePlayer player in BasePlayer.allPlayerList) DisposeRiderStaff(player); foreach (BaseNetworkable baseNetworkable in BaseNetworkable.serverEntities) { if (baseNetworkable is Bike || baseNetworkable is RidableHorse) DisposeRiderStaff(baseNetworkable as BaseVehicle); } } private BaseEntity CreateFireball() { GameObject gameObject = new GameObject("ghostrider-fireball"); gameObject.layer = (int)Rust.Layer.Reserved1; SceneManager.MoveGameObjectToScene(gameObject, Rust.Server.EntityScene); BaseEntity fireball = gameObject.AddComponent(); fireball.prefabID = 3369311876; fireball.enableSaving = false; gameObject.SetActive(true); return fireball; } private static bool IsValidVehicle(BaseVehicle vehicle) { if (!vehicle) return false; if (!vehicle.PrefabName.Contains("motor") && !vehicle.PrefabName.Contains("horse")) return false; if (vehicle is Bike) { if (config.Only2Wheels && (vehicle as Bike).GetWheels().Length != 2) return false; } return true; } private static bool IsMotorbikeSeat(BikeDriverSeat seat) { return seat.PrefabName == MOTORBIKE_DRIVER_SEAT_PREFAB; } private void BaseSetupRiderStaff(BasePlayer player, BaseVehicle baseVehicle) { if (!IsValidVehicle(baseVehicle)) return; if (subEntities.TryGetValue(baseVehicle, out List ents)) { foreach (var ent in ents) if (!ent.IsDestroyed) ent.Kill(); ents.Clear(); } else { ents = subEntities[baseVehicle] = new List(); } BaseEntity headFireball = CreateFireball(); headFireball.SetParent(player, HEADBONE); headFireball.Spawn(); ents.Add(headFireball); baseVehicle.gameObject.AddComponent(); } private void SetupRiderStaff(BasePlayer player, Bike bike) { if (!IsValidVehicle(bike)) return; BaseSetupRiderStaff(player, bike); foreach (CarWheel wheel in bike.GetWheels()) { BaseEntity fireball = CreateFireball(); fireball.SetParent(bike); fireball.transform.localPosition = wheel.wheelCollider.transform.localPosition; fireball.Spawn(); subEntities[bike].Add(fireball); } } private void SetupRiderStaff(BasePlayer player, RidableHorse horse) { if (!IsValidVehicle(horse)) return; BaseSetupRiderStaff(player, horse); foreach (string boneName in HORSE_BONE_NAMES) { BaseEntity fireball = CreateFireball(); fireball.SetParent(horse, boneName); fireball.transform.localPosition = Vector3.zero; fireball.Spawn(); subEntities[horse].Add(fireball); } } private void DisposeRiderStaff(BaseVehicle vehicle) { if (subEntities.TryGetValue(vehicle, out List ents)) { foreach (var ent in ents) if (!ent.IsDestroyed) ent.Kill(); ents.Clear(); UnityEngine.Object.Destroy(vehicle.gameObject.GetComponent()); } } private class GroundFlame : FacepunchBehaviour { private BaseVehicle vehicle; private float tickRate = 0.15f; private float nextFlameTime; private float range = 2f; void Start() { vehicle = GetComponent(); InvokeRepeating(new Action(this.FlameTick), tickRate, tickRate); } public void FlameTick() { if (vehicle.GetSpeed() > 5 && UnityEngine.Time.realtimeSinceStartup >= this.nextFlameTime) { this.nextFlameTime = UnityEngine.Time.realtimeSinceStartup + 0.4f; FireBall fireball = global::GameManager.server.CreateEntity(FLAME_PREFAB, vehicle.transform.position - vehicle.transform.rotation * -Vector3.forward * range, default(Quaternion), true) as FireBall; if (fireball) { fireball.creatorEntity = vehicle.GetDriver(); fireball.lifeTimeMin = 3; fireball.lifeTimeMax = 3; fireball.Spawn(); fireball.CancelInvoke(new Action(fireball.Think)); } } } } private void DisposeRiderStaff(BasePlayer player) { foreach (BaseEntity fireball in player.children.Where(e => e.prefabID == FIREBALL_PREFABID && e.parentBone == HEADBONE).ToArray()) { if (!fireball.IsDestroyed) fireball.Kill(); } } void Loaded() { foreach (Type type in this.GetType().GetNestedTypes(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) { object[] attribute = type.GetCustomAttributes(typeof(HarmonyPatch), false); if (attribute.Length >= 1) { PatchClassProcessor patchClassProcessor = this.HarmonyInstance.CreateClassProcessor(type); patchClassProcessor.Patch(); } } } [HarmonyPatch(typeof(BaseVehicle), "PlayerMounted")] private static class BaseVehicle_PlayerMounted_Patch { private static bool Prefix(BaseVehicle __instance, BasePlayer __0, BaseMountable __1) { if (__instance is Bike && __1 is BikeDriverSeat && IsMotorbikeSeat(__1 as BikeDriverSeat)) { Instance.SetupRiderStaff(__0, __instance as Bike); return false; } else if (__instance is RidableHorse) { Instance.SetupRiderStaff(__0, __instance as RidableHorse); return false; } return true; } } [HarmonyPatch(typeof(BaseVehicle), "PrePlayerDismount")] private static class BaseVehicle_PrePlayerDismount_Patch { private static bool Prefix(BaseVehicle __instance, BaseMountable __1) { if (__instance is Bike && __1 is BikeDriverSeat && IsMotorbikeSeat(__1 as BikeDriverSeat) || __instance is RidableHorse) { Instance.DisposeRiderStaff(__instance); return false; } return true; } } [HarmonyPatch(typeof(BasePlayer), "DismountObject")] private static class BasePlayer_DismountObject_Patch { private static void Postfix(BasePlayer __instance) { Instance.DisposeRiderStaff(__instance); } } [HarmonyPatch(typeof(Bike), "PlayerServerInput")] private static class Bike_PlayerServerInput_Patch { private static void Postfix(Bike __instance, InputState inputState, BasePlayer player) { if (!__instance.IsDriver(player) || !IsValidVehicle(__instance)) return; if (inputState.WasJustPressed(BUTTON.SPRINT)) { Effect.server.Run(BOOST_EFFECT_PREFAB, __instance.transform.position + __instance.transform.rotation * new Vector3(0.2f, 1f, -0.7f)); } } } [HarmonyPatch(typeof(Bike), "GetMaxDriveForce")] private static class Bike_GetMaxDriveForce_Patch { private static void Postfix(Bike __instance, ref float __result) { if (Instance.subEntities.ContainsKey(__instance) && __instance.SprintInput && IsValidVehicle(__instance)) __result = __result * config.EngineMul; } } [HarmonyPatch(typeof(RidableHorse), "GetRunSpeed")] private static class RidableHorse_GetRunSpeed_Patch { private static void Postfix(RidableHorse __instance, ref float __result) { if (Instance.subEntities.ContainsKey(__instance) && __instance.currentRunState == BaseRidableAnimal.RunState.sprint && IsValidVehicle(__instance)) __result = __result * config.HorseMul; } } #region Config static Configuration config; public class Configuration { [JsonProperty(PropertyName = "Can bike crashes cause damage or death to the rider?")] public bool CanCrashDamage { get; set; } = false; [JsonProperty(PropertyName = "Only 2-wheels motorbike?")] public bool Only2Wheels { get; set; } = false; [JsonProperty(PropertyName = "Multiplier Engine Power")] public float EngineMul { get; set; } = 2.5f; [JsonProperty(PropertyName = "Multiplier Horse Power")] public float HorseMul { get; set; } = 2f; public static Configuration DefaultConfig() { return new Configuration(); } } protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) LoadDefaultConfig(); SaveConfig(); } catch (Exception ex) { Debug.LogException(ex); PrintWarning("Creating new configuration file."); LoadDefaultConfig(); } } protected override void LoadDefaultConfig() => config = Configuration.DefaultConfig(); protected override void SaveConfig() => Config.WriteObject(config); #endregion } }