C#:
#nullable disable
using System.Collections.Generic;
using HarmonyLib;
using Oxide.Core.Plugins;
using UnityEngine;
namespace Oxide.Plugins;
[Info("Bike Bunnyhop", "Magic Services // crash", "2.0.0")]
internal class BikeBunnyhop : RustPlugin
{
private const string PermUse = "bikebunnyhop.use";
private static BikeBunnyhop _instance;
private ConfigData _config;
private readonly Dictionary<ulong, float> _lastBunnyhopTime = new();
private readonly Dictionary<ulong, float> _customEnergy = new();
private readonly Dictionary<ulong, Rigidbody> _bikeRigidbodies = new();
private readonly HashSet<ulong> _boostedBikes = new();
private sealed class ConfigData
{
public float CooldownSeconds { get; set; } = 3f;
public float EnergyCostPercent { get; set; } = 0.1f;
public float MinimumEnergyRequired { get; set; } = 0.1f;
public float BaseForce { get; set; } = 2.5f;
public float SpeedMultiplier { get; set; } = 0.3f;
public float MaxAdditionalForce { get; set; } = 10f;
public bool UseCustomEnergy { get; set; } = true;
public float TopSpeedMultiplier { get; set; } = 1.0f;
public float AirTurnSpeed { get; set; } = 0.3f;
}
protected override void LoadDefaultConfig()
{
_config = new ConfigData();
SaveConfig();
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<ConfigData>();
if (_config == null) LoadDefaultConfig();
SaveConfig();
}
catch
{
LoadDefaultConfig();
}
}
protected override void SaveConfig() => Config.WriteObject(_config);
private void Init()
{
_instance = this;
permission.RegisterPermission(PermUse, this);
}
private void Unload()
{
_instance = null;
}
private void OnServerInitialized()
{
foreach (var entity in BaseNetworkable.serverEntities)
{
if (entity is Bike bike)
OnEntitySpawned(bike);
}
}
private void OnEntitySpawned(Bike bike)
{
if (bike is null) return;
var id = bike.net.ID.Value;
_customEnergy.TryAdd(id, 1f);
var rb = bike.GetComponent<Rigidbody>();
if (rb is not null)
_bikeRigidbodies[id] = rb;
}
private void OnEntityKill(Bike bike)
{
if (bike is null) return;
var id = bike.net.ID.Value;
_lastBunnyhopTime.Remove(id);
_customEnergy.Remove(id);
_bikeRigidbodies.Remove(id);
_boostedBikes.Remove(id);
}
private void OnEntityMounted(BaseMountable mountable, BasePlayer player)
{
if (player is null || mountable is null) return;
if (!permission.UserHasPermission(player.UserIDString, PermUse)) return;
var bike = mountable.VehicleParent() as Bike;
if (bike is null) return;
if (!bike.IsDriver(player)) return;
_boostedBikes.Add(bike.net.ID.Value);
}
private void OnEntityDismounted(BaseMountable mountable, BasePlayer player)
{
if (mountable is null || player is null) return;
var bike = mountable.VehicleParent() as Bike;
if (bike is null) return;
var bikeId = bike.net.ID.Value;
if (!_boostedBikes.Contains(bikeId)) return;
if (!bike.HasDriver())
_boostedBikes.Remove(bikeId);
}
private object OnPlayerInput(BasePlayer player, InputState input)
{
if (player is null || input == null) return null;
if (!input.WasJustPressed(BUTTON.FIRE_SECONDARY)) return null;
if (!permission.UserHasPermission(player.UserIDString, PermUse)) return null;
if (player.GetMountedVehicle() is not Bike bike || !bike.HasDriver()) return null;
var bikeId = bike.net.ID.Value;
if (!CanBunnyhop(bike, bikeId)) return null;
ApplyCustomBunnyhop(bike, bikeId);
return null;
}
private bool CanBunnyhop(Bike bike, ulong bikeId)
{
if (_lastBunnyhopTime.TryGetValue(bikeId, out float lastTime))
{
if (Time.time - lastTime < _config.CooldownSeconds)
return false;
}
if (!IsGrounded(bike)) return false;
if (bike.engineController?.IsWaterlogged() == true) return false;
if (!_config.UseCustomEnergy) return true;
if (_customEnergy.TryGetValue(bikeId, out var energy)) return !(energy < _config.MinimumEnergyRequired);
_customEnergy[bikeId] = 1f;
energy = 1f;
return !(energy < _config.MinimumEnergyRequired);
}
private void ApplyCustomBunnyhop(Bike bike, ulong bikeId)
{
if (!_bikeRigidbodies.TryGetValue(bikeId, out var rigidBody) || rigidBody is null)
return;
var force = _config.BaseForce;
if (_config.SpeedMultiplier > 0f)
{
var speed = bike.GetSpeed();
var energyMult = 1f;
if (_config.UseCustomEnergy && _customEnergy.TryGetValue(bikeId, out var energy))
energyMult = energy;
var speedBonus = Mathf.Min(Mathf.Abs(speed * _config.SpeedMultiplier) * energyMult, _config.MaxAdditionalForce);
force += speedBonus;
}
rigidBody.AddRelativeForce(Vector3.up * force, ForceMode.VelocityChange);
_lastBunnyhopTime[bikeId] = Time.time;
if (!_config.UseCustomEnergy || !_customEnergy.ContainsKey(bikeId)) return;
_customEnergy[bikeId] = Mathf.Clamp01(_customEnergy[bikeId] - _config.EnergyCostPercent);
timer.Once(_config.CooldownSeconds * 2f, () =>
{
if (_customEnergy.ContainsKey(bikeId))
_customEnergy[bikeId] = Mathf.Clamp01(_customEnergy[bikeId] + 0.05f);
});
}
private static bool IsGrounded(Bike bike)
{
var transform = bike.transform;
return Physics.Raycast(transform.position, Vector3.down, 0.5f, LayerMask.GetMask("Terrain", "World", "Construction"));
}
#region Harmony Patches
[AutoPatch]
[HarmonyPatch(typeof(Bike), nameof(Bike.GetMaxDriveForce))]
internal static class Bike_GetMaxDriveForce_Patch
{
[HarmonyPostfix]
private static void Postfix(Bike __instance, ref float __result)
{
if (_instance is null) return;
if (_instance._config.TopSpeedMultiplier <= 1f) return;
if (!_instance._boostedBikes.Contains(__instance.net.ID.Value)) return;
__result *= _instance._config.TopSpeedMultiplier;
}
}
[AutoPatch]
[HarmonyPatch(typeof(Bike), "PDDirectionStab")]
internal static class Bike_PDDirectionStab_Patch
{
[HarmonyPrefix]
private static bool Prefix(Bike __instance)
{
if (_instance is null) return true;
if (_instance._config.AirTurnSpeed <= 0f) return true;
if (__instance.carPhysics.IsGrounded()) return true;
var bikeId = __instance.net.ID.Value;
if (!_instance._boostedBikes.Contains(bikeId)) return true;
if (!_instance._bikeRigidbodies.TryGetValue(bikeId, out var rb) || rb is null) return true;
var angularVelocity = rb.angularVelocity;
var max = _instance._config.AirTurnSpeed * 10f;
angularVelocity.y = Mathf.Clamp(angularVelocity.y, -max, max);
rb.angularVelocity = angularVelocity;
return false;
}
}
[AutoPatch]
[HarmonyPatch(typeof(Bike), "AwakeBikePhysicsTick")]
internal static class Bike_AwakeBikePhysicsTick_Patch
{
[HarmonyPostfix]
private static void Postfix(Bike __instance)
{
if (_instance is null) return;
if (_instance._config.AirTurnSpeed <= 0f) return;
if (__instance.carPhysics.IsGrounded()) return;
var bikeId = __instance.net.ID.Value;
if (!_instance._boostedBikes.Contains(bikeId)) return;
if (!_instance._bikeRigidbodies.TryGetValue(bikeId, out var rb) || rb is null) return;
if (rb.isKinematic) return;
var steerInput = __instance.GetSteerInput();
if (steerInput == 0f) return;
rb.AddRelativeTorque(0f, steerInput * _instance._config.AirTurnSpeed, 0f, ForceMode.VelocityChange);
}
}
#endregion
}