OnEngineStatsRefreshed
OnEngineStatsRefreshed is called at the end of VehicleModuleEngine.RefreshPerformanceStats, after the module has recalculated its acceleration, top speed, fuel economy and overall performance fractions from the installed engine parts.
void OnEngineStatsRefreshed(VehicleModuleEngine vehicleModuleEngine, Rust.Modular.EngineStorage engineStorage)Parameters
| Parameter | Type | Description |
|---|---|---|
vehicleModuleEngine | VehicleModuleEngine | The VehicleModuleEngine instance whose performance fractions were just recomputed. Use it to read OverallPerformanceFraction and the per-attribute fractions, or to find the parent vehicle via GetParentEntity(). |
engineStorage | Rust.Modular.EngineStorage | The EngineStorage container holding the installed engine parts. Its accelerationBoostPercent, topSpeedBoostPercent and fuelEconomyBoostPercent fields are the inputs the refreshed stats were derived from. |
Return value
The return value is ignored; the game finishes RefreshPerformanceStats with the stats it already computed. Returning a value changes nothing, and the only way to influence engine stats is through the boost percentages in engineStorage before this hook fires.
How it works
The hook fires inside VehicleModuleEngine.RefreshPerformanceStats after the module has already written PerformanceFractionAcceleration, PerformanceFractionTopSpeed, PerformanceFractionFuelEconomy and OverallPerformanceFraction. RefreshPerformanceStats runs whenever the engine's part loadout changes - parts inserted, removed or swapped in the EngineStorage container - so this is the point where you can observe the final numbers rather than raw part data.
vehicleModuleEngine gives you the module itself: read the four performance fraction fields (0-1 floats) to see how good the current build is, and call GetParentEntity() to walk up to the ModularCar if you want to act on the whole vehicle. engineStorage is the source of the boost percentages used in the calculation; reading them alongside the fractions shows whether a change came from compression/boost parts rather than base tier.
The hook is fire-and-forget; there is no return value to influence the game. Because it can fire for every engine module on the server whenever a player edits a car engine, avoid heavy work such as full physics scans or chat spam. Cache results, debounce with a short timer.Once, or store per-vehicle state in a dictionary keyed by net.ID.Value if you need to compare against the previous build.
Example plugin: Reward players who reach a high overall engine performance
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Engine Performance Reward", "RustPluginGenerator", "1.0.0")]
[Description("Gives a reward when a modular car engine reaches a configured overall performance fraction")]
public class EnginePerformanceReward : RustPlugin
{
private const string PermReward = "engineperformancereward.reward";
private class PluginConfig
{
[JsonProperty("Minimum overall performance (0-1)")]
public float MinimumPerformance = 0.8f;
[JsonProperty("Reward item shortname")]
public string RewardItem = "scrap";
[JsonProperty("Reward amount")]
public int RewardAmount = 25;
[JsonProperty("Cooldown seconds per engine")]
public float CooldownSeconds = 300f;
}
private PluginConfig config;
private readonly Dictionary<ulong, float> lastRewardAt = new Dictionary<ulong, float>();
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
["Reward"] = "Your car engine reached {0:P0} overall performance. Reward: {1} x {2}",
["Cooldown"] = "This engine was already rewarded recently."
}, this);
}
private string Msg(string key, string userId = null, params object[] args) =>
string.Format(lang.GetMessage(key, this, userId), args);
private void Init()
{
permission.RegisterPermission(PermReward, this);
}
private void OnEngineStatsRefreshed(VehicleModuleEngine vehicleModuleEngine, Rust.Modular.EngineStorage engineStorage)
{
if (vehicleModuleEngine == null || engineStorage == null) return;
if (vehicleModuleEngine.OverallPerformanceFraction < config.MinimumPerformance) return;
var car = vehicleModuleEngine.GetParentEntity() as ModularCar;
if (car == null) return;
BasePlayer driver = null;
// Find a mounted player who is driving the car
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected) continue;
var mounted = player.GetMounted();
if (mounted == car && player.userID.IsSteamId())
{
driver = player;
break;
}
}
if (driver == null) return;
if (!permission.UserHasPermission(driver.UserIDString, PermReward)) return;
// Cooldown per engine module, not per player
ulong moduleId = vehicleModuleEngine.net.ID.Value;
float now = Time.realtimeSinceStartup;
if (lastRewardAt.TryGetValue(moduleId, out float last) && now - last < config.CooldownSeconds)
{
driver.ChatMessage(Msg("Cooldown", driver.UserIDString));
return;
}
lastRewardAt[moduleId] = now;
var item = ItemManager.CreateByName(config.RewardItem, config.RewardAmount, 0UL);
if (item == null) return;
driver.GiveItem(item);
driver.ChatMessage(Msg("Reward", driver.UserIDString, vehicleModuleEngine.OverallPerformanceFraction,
config.RewardAmount, item.info.displayName.english));
}
private void Unload()
{
lastRewardAt.Clear();
}
}
}The plugin listens for OnEngineStatsRefreshed, then gets the parent entity as a ModularCar and finds a connected player whose GetMounted() returns that car. It checks OverallPerformanceFraction against a configurable threshold and uses a cooldown dictionary keyed by the engine module's network ID so repeated refreshes do not spam rewards. On success it gives the configured item through ItemManager.CreateByName and GiveItem, sends a localized chat message through lang, and records the module ID with a timestamp. Cooldown entries are cleared on unload, and all player-facing text is loaded from the messages dictionary.
Common use cases
Player notifications for engine builds
Show the driver their new overall performance percentage when they finish installing parts, so they know whether the build is worth keeping.
Reward high performance builds
Grant a configurable item or permission when a car engine crosses a performance threshold, encouraging players to tune their modular cars.
Track engine tuning statistics
Store the last seen performance fraction per module and compute how much players improve their cars over time for leaderboards or admin reports.
Pitfalls
Fires while the player is still sorting parts
RefreshPerformanceStats runs on every loadout change, so a player dropping three parts into the storage produces several calls. Debounce or compare against the previously stored value before acting.
No return value control
You cannot alter the computed stats from this hook. If you need to change performance, adjust the engine parts or boost percentages in front of the calculation, not here.
Parent entity may not be a player vehicle
GetParentEntity() can return null or a vehicle with no mounted player while the module is being moved. Null-check the parent and the rider list before sending chat or granting items.
Build a plugin with OnEngineStatsRefreshed
Describe what should happen and get a compiled plugin that uses this hook.
Edit the prompt on the next screen. 2 free plugins a month, no card.
Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.
Build a plugin with OnEngineStatsRefreshed
Generate