C#:
using System;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using Facepunch;
using Network;
// ____ _ _ _
// / ___(_) __ _(_) | ___
// \___ \| |/ _` | | |/ _ \
// ___) | | (_| | | | (_) |
// |____/|_|\__, |_|_|\___/
// |___/
// ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾
// ✦ RUST PLUGINS ✦
// ✦ https://sigilo.dev ✦
// ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾
namespace Oxide.Plugins
{
[Info("MotorcycleCombat", "Sigilo", "1.1.0")]
[Description("Adds boost, jump, and minigun to motorcycles with crash explosion.")]
public class MotorcycleCombat : RustPlugin
{
private class PluginConfig
{
public bool RequirePermission = true;
public float BoostThrustMultiplier = 0.5f;
public float BoostFuelDuration = 1.0f;
public float BoostRechargeTime = 10.0f;
public string BoostActivateSound = "assets/prefabs/npc/patrol helicopter/effects/rocket_fire.prefab";
public string BoostLoopEffect = "assets/prefabs/ammo/rocket/rocket_basic.prefab";
public float JumpForce = 15.0f;
public float JumpCooldown = 3.0f;
public string JumpSound = "assets/prefabs/locks/keypad/effects/lock.code.lock.prefab";
public float MinigunDamage = 15.0f;
public float MinigunRange = 300.0f;
public float MinigunBulletVelocity = 400.0f;
public float MinigunBulletGravity = 0.6f;
public float MinigunAimconeDegrees = 0.6f;
public float MinigunRoundsPerSecond = 8.0f;
public int MinigunMagazineSize = 80;
public float MinigunReloadTime = 25.0f;
public bool MinigunEnableMuzzleFlash = true;
public bool MinigunEnableSound = true;
public string MinigunSoundEffect = "assets/prefabs/weapons/ak47u/effects/attack.prefab";
public string MinigunMuzzleFlashEffect = "assets/prefabs/weapons/ak47u/effects/attack.prefab";
public string MinigunImpactEffect = "assets/bundled/prefabs/fx/impacts/additive/explosion.prefab";
public float CrashSpeedThreshold = 20.0f;
public string CrashExplosionEffect = "assets/bundled/prefabs/fx/explosions/explosion_01.prefab";
public string CrashFireEffect = "assets/bundled/prefabs/fx/fire/fire_v3_2x2.prefab";
public bool EnableHUD = true;
public bool UnlimitedFuel = true;
}
private PluginConfig config;
private const string HUD_PANEL = "MotorcycleCombat_HUD";
private const string PermissionUse = "motorcyclecombat.use";
private static readonly int BulletLayerMask = ~((1 << 2) | (1 << 29));
private readonly Dictionary<ulong, BoostState> boostByVehicle = new Dictionary<ulong, BoostState>();
private readonly Dictionary<ulong, float> lastJumpTime = new Dictionary<ulong, float>();
private readonly Dictionary<ulong, Timer> minigunTimersByPlayer = new Dictionary<ulong, Timer>();
private readonly Dictionary<ulong, int> minigunAmmoByVehicle = new Dictionary<ulong, int>();
private readonly Dictionary<ulong, bool> isMinigunReloading = new Dictionary<ulong, bool>();
private readonly Dictionary<ulong, Timer> minigunReloadTimers = new Dictionary<ulong, Timer>();
private readonly Dictionary<ulong, Timer> hudUpdateTimers = new Dictionary<ulong, Timer>();
private readonly List<PendingBullet> pendingBullets = new List<PendingBullet>();
private readonly HashSet<BasePlayer> activeRiders = new HashSet<BasePlayer>();
private readonly HashSet<ulong> crashVulnerableVehicles = new HashSet<ulong>();
private readonly Dictionary<ulong, BaseVehicle> spawnedBikesByPlayer = new Dictionary<ulong, BaseVehicle>();
private class BoostState
{
public BaseVehicle Vehicle;
public bool IsActive;
public float FuelRemaining;
public float RechargeTimer;
public float LastEffectTime;
}
private class PendingBullet
{
public BasePlayer Owner;
public BaseVehicle Vehicle;
public Vector3 Origin;
public Vector3 Velocity;
public Vector3 CurrentPosition;
public float SpawnTime;
public float BaseDamage;
public float MaxLifetime;
public Vector3 LastPosition;
}
private void Init()
{
permission.RegisterPermission(PermissionUse, this);
}
private readonly Dictionary<ulong, float> lastMotoSpawn = new Dictionary<ulong, float>();
private const float MOTO_SPAWN_COOLDOWN = 5f;
[ChatCommand("moto")]
private void CmdSpawnMoto(BasePlayer player, string command, string[] args)
{
if (!HasPermission(player))
{
player.ChatMessage("<color=#ff0000>You don't have permission to use this command.</color>");
return;
}
if (player.IsDead())
{
player.ChatMessage("<color=#ff0000>You cannot spawn a motorcycle while dead.</color>");
return;
}
if (player.isMounted)
{
player.ChatMessage("<color=#ff0000>You cannot spawn a motorcycle while mounted.</color>");
return;
}
if (player.IsSwimming())
{
player.ChatMessage("<color=#ff0000>You cannot spawn a motorcycle in water.</color>");
return;
}
if (player.IsBuildingBlocked())
{
player.ChatMessage("<color=#ff0000>You cannot spawn a motorcycle in a building blocked area.</color>");
return;
}
float now = Time.realtimeSinceStartup;
if (lastMotoSpawn.TryGetValue(player.userID, out float lastSpawn))
{
float remaining = MOTO_SPAWN_COOLDOWN - (now - lastSpawn);
if (remaining > 0)
{
player.ChatMessage($"<color=#ff0000>You must wait {remaining:F0} seconds before spawning another motorcycle.</color>");
return;
}
}
if (spawnedBikesByPlayer.TryGetValue(player.userID, out BaseVehicle existingBike) && existingBike != null && !existingBike.IsDestroyed)
{
bool hasMountedPlayers = false;
foreach (var mount in existingBike.allMountPoints)
{
if (mount?.mountable != null && mount.mountable.GetMounted() != null)
{
hasMountedPlayers = true;
break;
}
}
if (hasMountedPlayers)
{
player.ChatMessage("<color=#ff0000>Your previous motorcycle has players mounted. Dismount first.</color>");
return;
}
existingBike.Kill(BaseNetworkable.DestroyMode.None);
}
Vector3 spawnPos = player.transform.position + player.eyes.HeadForward() * 3f;
spawnPos.y = player.transform.position.y;
if (!Physics.Raycast(spawnPos + Vector3.up * 2f, Vector3.down, out RaycastHit hit, 5f, BulletLayerMask))
{
player.ChatMessage("<color=#ff0000>Invalid spawn location.</color>");
return;
}
spawnPos = hit.point + Vector3.up * 0.5f;
var moto = GameManager.server.CreateEntity("assets/content/vehicles/bikes/motorbike.prefab", spawnPos, Quaternion.LookRotation(player.eyes.HeadForward())) as BaseVehicle;
if (moto == null)
{
player.ChatMessage("<color=#ff0000>Failed to spawn motorcycle.</color>");
return;
}
moto.Spawn();
spawnedBikesByPlayer[player.userID] = moto;
lastMotoSpawn[player.userID] = now;
player.ChatMessage("<color=#00ff00>Motorcycle spawned!</color>");
}
protected override void LoadDefaultConfig()
{
config = new PluginConfig();
SaveConfig();
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception("Config is null");
}
catch
{
PrintWarning("Invalid config, creating new...");
LoadDefaultConfig();
}
}
protected override void SaveConfig() => Config.WriteObject(config, true);
private void OnServerInitialized()
{
config = Config.ReadObject<PluginConfig>();
SaveConfig();
Puts($"MotorcycleCombat v1.1.0 loaded");
}
private void OnTick()
{
ProcessPendingBullets();
if (boostByVehicle.Count > 0) ProcessBoostPhysics();
}
private void Unload()
{
foreach (var player in BasePlayer.activePlayerList)
DestroyHUD(player);
foreach (var t in minigunTimersByPlayer.Values) t?.Destroy();
minigunTimersByPlayer.Clear();
foreach (var t in minigunReloadTimers.Values) t?.Destroy();
minigunReloadTimers.Clear();
foreach (var t in hudUpdateTimers.Values) t?.Destroy();
hudUpdateTimers.Clear();
pendingBullets.Clear();
}
private void OnPlayerInput(BasePlayer player, InputState input)
{
if (player == null || input == null || !player.IsConnected) return;
var mounted = player.GetMountedVehicle();
if (mounted == null) return;
bool isMotorbike = mounted.ShortPrefabName.Contains("motorbike");
if (!isMotorbike) return;
if (!HasPermission(player)) return;
bool isSidecar = mounted.ShortPrefabName.Contains("sidecar");
bool isDriver = !isSidecar || IsDriver(player, mounted);
bool isPassenger = isSidecar && !IsDriver(player, mounted);
ulong vehicleId = mounted.net.ID.Value;
if (config.EnableHUD)
StartHUDUpdates(player, mounted, isDriver, isPassenger);
if (isDriver)
{
if (input.IsDown(BUTTON.SPRINT))
HandleBoostInput(player, mounted, true);
else
HandleBoostInput(player, mounted, false);
if (input.WasJustPressed(BUTTON.RELOAD))
TryJump(player, mounted);
var heldItem = player.GetActiveItem();
bool hasWeapon = heldItem != null && (heldItem.info.category == ItemCategory.Weapon || heldItem.info.category == ItemCategory.Tool);
if (!hasWeapon && input.IsDown(BUTTON.FIRE_PRIMARY))
{
if (!IsMinigunReloading(vehicleId))
StartMinigunTimer(player, mounted);
}
else
{
StopMinigunTimer(player.userID);
}
}
}
private void HandleBoostInput(BasePlayer player, BaseVehicle vehicle, bool isPressed)
{
ulong vehicleId = vehicle.net.ID.Value;
if (!boostByVehicle.TryGetValue(vehicleId, out BoostState state))
{
state = new BoostState
{
Vehicle = vehicle,
IsActive = false,
FuelRemaining = config.BoostFuelDuration,
RechargeTimer = 0f,
LastEffectTime = 0f
};
boostByVehicle[vehicleId] = state;
}
if (isPressed && state.FuelRemaining > 0f && state.RechargeTimer <= 0f && IsGrounded(vehicle))
{
if (!state.IsActive)
{
state.IsActive = true;
crashVulnerableVehicles.Add(vehicleId);
Effect.server.Run(config.BoostActivateSound, vehicle.transform.position);
}
}
else
{
if (state.IsActive)
{
state.IsActive = false;
if (state.FuelRemaining <= 0f)
{
state.RechargeTimer = config.BoostRechargeTime;
}
}
}
}
private void ProcessBoostPhysics()
{
float dt = Time.deltaTime;
float currentTime = Time.realtimeSinceStartup;
List<ulong> toRemove = null;
foreach (var kvp in boostByVehicle)
{
ulong vehicleId = kvp.Key;
BoostState state = kvp.Value;
var vehicle = state.Vehicle;
if (vehicle == null || vehicle.IsDestroyed)
{
if (toRemove == null) toRemove = new List<ulong>();
toRemove.Add(vehicleId);
continue;
}
if (state.RechargeTimer > 0f)
{
state.RechargeTimer -= dt;
if (state.RechargeTimer <= 0f)
{
state.FuelRemaining = config.BoostFuelDuration;
state.RechargeTimer = 0f;
var driver = GetDriver(vehicle);
if (driver != null)
Effect.server.Run("assets/bundled/prefabs/fx/invite_notice.prefab", vehicle.transform.position);
}
}
if (state.IsActive && state.FuelRemaining > 0f)
{
if (!IsGrounded(vehicle))
{
state.IsActive = false;
continue;
}
state.FuelRemaining -= dt;
var rb = vehicle.GetComponent<Rigidbody>();
if (rb != null)
{
Vector3 boostForce = vehicle.transform.forward * config.BoostThrustMultiplier * 50f;
rb.AddForce(boostForce, ForceMode.Acceleration);
}
if (currentTime - state.LastEffectTime > 0.2f)
{
Vector3 exhaustPos = vehicle.transform.position - vehicle.transform.forward * 1.0f;
Effect.server.Run(config.BoostLoopEffect, exhaustPos, -vehicle.transform.forward);
state.LastEffectTime = currentTime;
}
if (state.FuelRemaining <= 0f)
{
state.IsActive = false;
state.FuelRemaining = 0f;
state.RechargeTimer = config.BoostRechargeTime;
}
}
else if (!state.IsActive && state.RechargeTimer <= 0f && state.FuelRemaining >= config.BoostFuelDuration)
{
crashVulnerableVehicles.Remove(vehicleId);
}
}
if (toRemove != null)
foreach (var id in toRemove)
{
boostByVehicle.Remove(id);
crashVulnerableVehicles.Remove(id);
}
}
private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info == null) return;
var vehicle = entity as BaseVehicle;
if (vehicle == null || !vehicle.ShortPrefabName.Contains("motorbike")) return;
ulong vehicleId = vehicle.net.ID.Value;
if (!crashVulnerableVehicles.Contains(vehicleId)) return;
if (!boostByVehicle.TryGetValue(vehicleId, out BoostState state)) return;
if (!state.IsActive) return;
var rb = vehicle.GetComponent<Rigidbody>();
float speed = rb != null ? rb.velocity.magnitude : 0f;
if (speed < config.CrashSpeedThreshold) return;
bool isCollision = info.damageTypes.Has(Rust.DamageType.Collision) ||
info.damageTypes.Has(Rust.DamageType.Blunt) ||
(info.Initiator == null && info.damageTypes.Total() > 0);
if (!isCollision) return;
TriggerCrashExplosion(vehicle, info.HitPositionWorld);
}
private void TriggerCrashExplosion(BaseVehicle vehicle, Vector3 crashPoint)
{
if (vehicle == null || vehicle.IsDestroyed) return;
ulong vehicleId = vehicle.net.ID.Value;
boostByVehicle.Remove(vehicleId);
crashVulnerableVehicles.Remove(vehicleId);
Vector3 pos = crashPoint != Vector3.zero ? crashPoint : vehicle.transform.position;
for (int i = 0; i < 3; i++)
{
Vector3 offset = UnityEngine.Random.insideUnitSphere * 2f;
Effect.server.Run(config.CrashExplosionEffect, pos + offset, Vector3.up);
}
Effect.server.Run(config.CrashFireEffect, pos, Vector3.up);
Effect.server.Run(config.CrashFireEffect, pos + Vector3.up * 1f, Vector3.up);
Effect.server.Run("assets/bundled/prefabs/fx/explosions/explosion_03.prefab", pos, Vector3.up);
var driver = GetDriver(vehicle);
if (driver != null)
{
activeRiders.Remove(driver);
StopHUDUpdates(driver);
DestroyHUD(driver);
}
timer.Once(0.1f, () =>
{
if (vehicle != null && !vehicle.IsDestroyed)
{
vehicle.Kill(BaseNetworkable.DestroyMode.Gib);
}
});
}
private void TryJump(BasePlayer player, BaseVehicle vehicle)
{
ulong vehicleId = vehicle.net.ID.Value;
float now = Time.realtimeSinceStartup;
if (lastJumpTime.TryGetValue(vehicleId, out float lastTime))
{
float remaining = config.JumpCooldown - (now - lastTime);
if (remaining > 0) return;
}
var rb = vehicle.GetComponent<Rigidbody>();
if (rb == null) return;
if (!IsGrounded(vehicle)) return;
lastJumpTime[vehicleId] = now;
rb.AddForce(Vector3.up * config.JumpForce, ForceMode.VelocityChange);
Effect.server.Run(config.JumpSound, vehicle.transform.position);
}
private bool IsGrounded(BaseVehicle vehicle)
{
Vector3 pos = vehicle.transform.position;
if (!Physics.Raycast(pos + Vector3.up * 0.3f, Vector3.down, 1.5f, BulletLayerMask))
return false;
var rb = vehicle.GetComponent<Rigidbody>();
if (rb != null && Mathf.Abs(rb.velocity.y) > 5.0f)
return false;
return true;
}
private void StartMinigunTimer(BasePlayer player, BaseVehicle vehicle)
{
if (minigunTimersByPlayer.ContainsKey(player.userID)) return;
float fireInterval = 1f / config.MinigunRoundsPerSecond;
FireMinigun(player, vehicle);
minigunTimersByPlayer[player.userID] = timer.Every(fireInterval, () =>
{
if (player == null || !player.IsConnected || player.IsDead())
{
StopMinigunTimer(player.userID);
return;
}
var mounted = player.GetMountedVehicle();
if (mounted == null || mounted != vehicle)
{
StopMinigunTimer(player.userID);
return;
}
FireMinigun(player, vehicle);
});
}
private void StopMinigunTimer(ulong playerId)
{
if (minigunTimersByPlayer.TryGetValue(playerId, out var t))
{
t?.Destroy();
minigunTimersByPlayer.Remove(playerId);
}
}
private void FireMinigun(BasePlayer player, BaseVehicle vehicle)
{
ulong vehicleId = vehicle.net.ID.Value;
int ammo = GetMinigunAmmo(vehicleId);
if (ammo <= 0)
{
StartMinigunReload(player, vehicle, vehicleId);
StopMinigunTimer(player.userID);
return;
}
minigunAmmoByVehicle[vehicleId] = ammo - 1;
Vector3 muzzlePos = player.eyes.position + player.eyes.HeadForward() * 1.0f;
Vector3 direction = player.eyes.HeadForward();
float aimcone = config.MinigunAimconeDegrees * Mathf.Deg2Rad;
direction = ApplyAimcone(direction, aimcone);
Vector3 velocity = direction * config.MinigunBulletVelocity;
if (config.MinigunEnableSound)
Effect.server.Run(config.MinigunSoundEffect, muzzlePos);
if (config.MinigunEnableMuzzleFlash)
Effect.server.Run(config.MinigunMuzzleFlashEffect, muzzlePos);
float maxLifetime = config.MinigunRange / config.MinigunBulletVelocity;
pendingBullets.Add(new PendingBullet
{
Owner = player,
Vehicle = vehicle,
Origin = muzzlePos,
Velocity = velocity,
CurrentPosition = muzzlePos,
SpawnTime = Time.realtimeSinceStartup,
BaseDamage = config.MinigunDamage,
MaxLifetime = maxLifetime,
LastPosition = muzzlePos
});
}
private Vector3 ApplyAimcone(Vector3 direction, float aimcone)
{
float x = UnityEngine.Random.Range(-aimcone, aimcone);
float y = UnityEngine.Random.Range(-aimcone, aimcone);
return Quaternion.Euler(x * Mathf.Rad2Deg, y * Mathf.Rad2Deg, 0) * direction;
}
private void ProcessPendingBullets()
{
if (pendingBullets.Count == 0) return;
float dt = Time.deltaTime;
float currentTime = Time.realtimeSinceStartup;
for (int i = pendingBullets.Count - 1; i >= 0; i--)
{
var bullet = pendingBullets[i];
float age = currentTime - bullet.SpawnTime;
if (age > bullet.MaxLifetime)
{
pendingBullets.RemoveAt(i);
continue;
}
bullet.Velocity.y -= config.MinigunBulletGravity * dt * 9.81f;
bullet.LastPosition = bullet.CurrentPosition;
bullet.CurrentPosition += bullet.Velocity * dt;
Vector3 direction = bullet.CurrentPosition - bullet.LastPosition;
float distance = direction.magnitude;
if (distance > 0.01f && Physics.Raycast(bullet.LastPosition, direction.normalized, out RaycastHit hit, distance, BulletLayerMask))
{
var entity = hit.GetEntity();
if (entity != null && entity != bullet.Vehicle)
{
var combatEntity = entity as BaseCombatEntity;
if (combatEntity != null)
{
var hitInfo = new HitInfo(bullet.Owner, combatEntity, Rust.DamageType.Bullet, bullet.BaseDamage, hit.point);
combatEntity.OnAttacked(hitInfo);
}
Effect.server.Run(config.MinigunImpactEffect, hit.point, hit.normal);
}
else if (entity == null)
{
Effect.server.Run(config.MinigunImpactEffect, hit.point, hit.normal);
}
pendingBullets.RemoveAt(i);
}
}
}
private int GetMinigunAmmo(ulong vehicleId)
{
if (!minigunAmmoByVehicle.TryGetValue(vehicleId, out int ammo))
{
ammo = config.MinigunMagazineSize;
minigunAmmoByVehicle[vehicleId] = ammo;
}
return ammo;
}
private bool IsMinigunReloading(ulong vehicleId)
{
return isMinigunReloading.TryGetValue(vehicleId, out bool reloading) && reloading;
}
private void StartMinigunReload(BasePlayer player, BaseVehicle vehicle, ulong vehicleId)
{
if (IsMinigunReloading(vehicleId)) return;
isMinigunReloading[vehicleId] = true;
player.ChatMessage($"<color=#ffaa00>Reloading... {config.MinigunReloadTime}s</color>");
if (minigunReloadTimers.TryGetValue(vehicleId, out var existingTimer))
existingTimer?.Destroy();
minigunReloadTimers[vehicleId] = timer.Once(config.MinigunReloadTime, () =>
{
minigunAmmoByVehicle[vehicleId] = config.MinigunMagazineSize;
isMinigunReloading[vehicleId] = false;
var v = BaseNetworkable.serverEntities.Find(new NetworkableId(vehicleId)) as BaseVehicle;
if (v != null)
{
Effect.server.Run("assets/bundled/prefabs/fx/invite_notice.prefab", v.transform.position);
var driver = GetDriver(v);
if (driver != null)
driver.ChatMessage("<color=#00ff00>Minigun reloaded!</color>");
}
minigunReloadTimers.Remove(vehicleId);
});
}
private void StartHUDUpdates(BasePlayer player, BaseVehicle vehicle, bool isDriver, bool isPassenger)
{
if (hudUpdateTimers.ContainsKey(player.userID)) return;
UpdateHUD(player, vehicle, isDriver, isPassenger);
hudUpdateTimers[player.userID] = timer.Every(0.5f, () =>
{
if (player == null || !player.IsConnected)
{
StopHUDUpdates(player);
return;
}
var mounted = player.GetMountedVehicle();
if (mounted == null || !mounted.ShortPrefabName.Contains("motorbike"))
{
StopHUDUpdates(player);
DestroyHUD(player);
return;
}
UpdateHUD(player, mounted, IsDriver(player, mounted), !IsDriver(player, mounted) && mounted.ShortPrefabName.Contains("sidecar"));
});
}
private void StopHUDUpdates(BasePlayer player)
{
if (player == null) return;
if (hudUpdateTimers.TryGetValue(player.userID, out var t))
{
t?.Destroy();
hudUpdateTimers.Remove(player.userID);
}
}
private void UpdateHUD(BasePlayer player, BaseVehicle vehicle, bool isDriver, bool isPassenger)
{
if (player == null || vehicle == null) return;
if (!isDriver) return;
ulong vehicleId = vehicle.net.ID.Value;
CuiHelper.DestroyUi(player, HUD_PANEL);
var elements = new CuiElementContainer();
elements.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0.30 0.095", AnchorMax = "0.70 0.125" },
CursorEnabled = false
}, "Hud", HUD_PANEL);
float boostFuel = config.BoostFuelDuration;
bool boostRecharging = false;
if (boostByVehicle.TryGetValue(vehicleId, out BoostState boostState))
{
boostFuel = boostState.FuelRemaining;
boostRecharging = boostState.RechargeTimer > 0f;
}
float jumpCooldown = 0f;
if (lastJumpTime.TryGetValue(vehicleId, out float lastTime))
jumpCooldown = Mathf.Max(0, config.JumpCooldown - (Time.realtimeSinceStartup - lastTime));
int ammo = GetMinigunAmmo(vehicleId);
bool reloading = IsMinigunReloading(vehicleId);
elements.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0.7" },
RectTransform = { AnchorMin = "0.02 0.1", AnchorMax = "0.34 0.9" }
}, HUD_PANEL, HUD_PANEL + "_BoostBG");
string boostValue;
if (boostRecharging)
{
boostValue = "<color=#ffaa00>RECHARGING</color>";
}
else
{
float boostPercent = (boostFuel / config.BoostFuelDuration) * 100f;
string boostColor = boostPercent > 50 ? "#44aaff" : (boostPercent > 25 ? "#ffcc00" : "#ff4444");
boostValue = $"<color={boostColor}>{boostPercent:F0}%</color>";
}
elements.Add(new CuiLabel
{
Text = {
Text = $"<color=#cccccc>Boost:</color> {boostValue}",
FontSize = 11,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1",
Font = "robotocondensed-bold.ttf"
},
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, HUD_PANEL + "_BoostBG");
elements.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0.7" },
RectTransform = { AnchorMin = "0.35 0.1", AnchorMax = "0.65 0.9" }
}, HUD_PANEL, HUD_PANEL + "_JumpBG");
string jumpValue;
if (jumpCooldown > 0)
{
jumpValue = $"<color=#ffaa00>{jumpCooldown:F1}s</color>";
}
else
{
jumpValue = "<color=#88ff88>READY</color>";
}
elements.Add(new CuiLabel
{
Text = {
Text = $"<color=#cccccc>Jump:</color> {jumpValue}",
FontSize = 11,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1",
Font = "robotocondensed-bold.ttf"
},
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, HUD_PANEL + "_JumpBG");
elements.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0.7" },
RectTransform = { AnchorMin = "0.66 0.1", AnchorMax = "0.98 0.9" }
}, HUD_PANEL, HUD_PANEL + "_AmmoBG");
string ammoValue;
if (reloading)
{
ammoValue = "<color=#ffaa00>RELOADING</color>";
}
else
{
string ammoColor = ammo > 30 ? "#88ff88" : (ammo > 10 ? "#ffcc00" : "#ff4444");
ammoValue = $"<color={ammoColor}>{ammo}</color>";
}
elements.Add(new CuiLabel
{
Text = {
Text = $"<color=#cccccc>Ammo:</color> {ammoValue}",
FontSize = 11,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1",
Font = "robotocondensed-bold.ttf"
},
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
}, HUD_PANEL + "_AmmoBG");
CuiHelper.AddUi(player, elements);
}
private void DestroyHUD(BasePlayer player)
{
if (player == null) return;
CuiHelper.DestroyUi(player, HUD_PANEL);
}
private void OnEntityDismounted(BaseMountable mountable, BasePlayer player)
{
if (player == null) return;
activeRiders.Remove(player);
StopMinigunTimer(player.userID);
StopHUDUpdates(player);
DestroyHUD(player);
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null) return;
activeRiders.Remove(player);
StopMinigunTimer(player.userID);
StopHUDUpdates(player);
DestroyHUD(player);
}
private void OnEntityKill(BaseNetworkable entity)
{
if (entity is BaseVehicle vehicle)
{
ulong vehicleId = vehicle.net.ID.Value;
boostByVehicle.Remove(vehicleId);
crashVulnerableVehicles.Remove(vehicleId);
lastJumpTime.Remove(vehicleId);
minigunAmmoByVehicle.Remove(vehicleId);
isMinigunReloading.Remove(vehicleId);
if (minigunReloadTimers.TryGetValue(vehicleId, out var t))
{
t?.Destroy();
minigunReloadTimers.Remove(vehicleId);
}
ulong ownerToRemove = 0;
foreach (var kvp in spawnedBikesByPlayer)
{
if (kvp.Value == vehicle)
{
ownerToRemove = kvp.Key;
break;
}
}
if (ownerToRemove != 0)
spawnedBikesByPlayer.Remove(ownerToRemove);
}
}
private object OnFuelCheck(EntityFuelSystem fuelSystem)
{
if (!config.UnlimitedFuel) return null;
var vehicle = fuelSystem.GetFuelContainer()?.GetParentEntity() as BaseVehicle;
if (vehicle != null && vehicle.ShortPrefabName.Contains("motorbike"))
return true;
return null;
}
private bool HasPermission(BasePlayer player)
{
if (!config.RequirePermission) return true;
return permission.UserHasPermission(player.UserIDString, PermissionUse);
}
private bool IsDriver(BasePlayer player, BaseVehicle vehicle)
{
if (vehicle == null) return false;
var driver = GetDriver(vehicle);
return driver != null && driver == player;
}
private BasePlayer GetDriver(BaseVehicle vehicle)
{
if (vehicle == null) return null;
foreach (var mount in vehicle.allMountPoints)
{
if (mount?.mountable != null && mount.isDriver)
{
return mount.mountable.GetMounted() as BasePlayer;
}
}
return null;
}
}
}