Skip to content
RustPluginGenerator

OnClientProjectileEffectCreate

OnClientProjectileEffectCreate is called on the server right before BaseProjectile sends a clientside projectile effect to a connection. Returning a non-null value suppresses that effect for the shot.

Signature
object OnClientProjectileEffectCreate(Network.Connection sourceConnection, BaseProjectile projectile, string prefabName)

Parameters

Hook parameters
ParameterTypeDescription
sourceConnectionNetwork.ConnectionNetwork.Connection of the player whose client receives the projectile effect. May be null when the effect is broadcast or originates from a non-player source.
projectileBaseProjectileThe BaseProjectile firing the shot, e.g. the held weapon entity. Use it to read the owner, item, or prefab name.
prefabNamestringAsset path of the client-side projectile effect prefab that would be sent to clients, such as the tracer or bullet visual.

Return value

Return null (or nothing) to let the effect be created and sent normally. Return any non-null value to skip the effect for that connection entirely, so no clientside projectile visual is produced from this call.

How it works

The hook fires inside BaseProjectile.CreateProjectileEffectClientside, the method that builds the reusable projectile Effect and ships it to one or more client connections. It runs for each shot that produces a clientside visual, so automatic weapons call it many times per second. sourceConnection is the target connection (null for broadcasts or non-player sources), projectile is the weapon entity doing the firing, and prefabName is the effect prefab about to be used. Returning anything non-null stops the original method before the effect is initialized, so the clients never see that projectile visual while damage and server-side ballistics are unaffected. Because it is a high-frequency hook, do not print or log from it. Keep the check cheap: a permission or flag lookup on the weapon owner. You can narrow the parameter to a subclass such as BaseProjectile already covers most guns; if you only care about one weapon type, check projectile.ShortPrefabName instead of narrowing further, since Oxide dispatch only matches when the argument is an instance of the declared type.

Example plugin: Hide bullet tracers for players with a permission

SilentTracers.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("SilentTracers", "RustPluginGenerator", "1.0.0")]
    [Description("Suppresses clientside bullet tracer effects for players holding a permission.")]
    public class SilentTracers : RustPlugin
    {
        private const string PermHide = "silenttracers.hide";

        private PluginConfig config;

        private class PluginConfig
        {
            [JsonProperty("Broadcast message when toggled")]
            public bool NotifyToggle = true;
        }

        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>
            {
                ["Enabled"] = "Tracer effects are now hidden for you.",
                ["Disabled"] = "Tracer effects are visible again.",
                ["NoPermission"] = "You do not have permission to use this command."
            }, this);
        }

        private string Msg(string key, string userId = null) => lang.GetMessage(key, this, userId);

        private void Init()
        {
            permission.RegisterPermission(PermHide, this);
        }

        [ChatCommand("tracers")]
        private void CmdTracers(BasePlayer player, string command, string[] args)
        {
            if (player == null) return;

            if (!permission.UserHasPermission(player.UserIDString, PermHide))
            {
                if (!player.IsAdmin) return;
                player.ChatMessage(Msg("NoPermission", player.UserIDString));
                return;
            }
        }

        [ConsoleCommand("silenttracers.toggle")]
        private void CcmdToggle(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            if (permission.UserHasPermission(player.UserIDString, PermHide))
            {
                permission.RevokeUserPermission(player.UserIDString, PermHide);
                if (config.NotifyToggle) player.ChatMessage(Msg("Disabled", player.UserIDString));
            }
            else
            {
                permission.GrantUserPermission(player.UserIDString, PermHide, this);
                if (config.NotifyToggle) player.ChatMessage(Msg("Enabled", player.UserIDString));
            }
        }

        private object OnClientProjectileEffectCreate(Network.Connection sourceConnection, BaseProjectile projectile, string prefabName)
        {
            if (projectile == null) return null;

            var owner = projectile.GetOwnerPlayer();
            if (owner == null) return null;

            if (permission.UserHasPermission(owner.UserIDString, PermHide))
            {
                return true;
            }

            return null;
        }
    }
}

The plugin registers a permission and checks it against the weapon owner found through projectile.GetOwnerPlayer(). When the owner has silenttracers.hide, the hook returns a non-null value so CreateProjectileEffectClientside never builds the effect for that connection, hiding the bullet tracer from the shooter and nearby clients. Damage is untouched because the check happens after the shot is resolved server-side. A chat command toggles the permission so admins and testers can switch visuals without editing files. Null checks guard the connection and projectile so console or NPC-driven effects pass through unharmed.

Common use cases

  • Staff-only silent weapons

    Give admins a permission that suppresses tracers so they can shoot test targets without drawing attention from players watching the tracer line.

  • Reduce clientside visual load

    Suppress projectile effects for high-rate weapons in crowded events to keep client frame rates stable while keeping damage identical.

  • Cinematic or PvE servers

    Disable tracers on custom game modes where the visual does not fit the theme, using a prefab whitelist on prefabName.

Pitfalls

  • It fires for every shot

    Automatic weapons can call this hook many times a second per player. Avoid allocations, permission checks with string concatenation, or any printing inside the hook body.

  • The connection can be null

    Broadcast or non-player effects pass a null sourceConnection. Check the projectile owner instead of assuming the connection exists.

  • Cosmetic only

    Returning non-null removes the visual effect, not the bullet. Damage, hit registration and server-side ballistics still apply.

Build a plugin with OnClientProjectileEffectCreate

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Staff-only silent weapons using the OnClientProjectileEffectCreate hook, with a config file and a permission
Generate this plugin

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 OnClientProjectileEffectCreate

Generate