Skip to content
RustPluginGenerator

OnNpcConversationResponded

OnNpcConversationResponded is called after a player presses a response button in an NPC conversation and the NPC has already been told to speak or move to the next node.

Signature
void OnNpcConversationResponded(NPCTalking npcTalking, BasePlayer player, ConversationData conversationFor, ConversationData.ResponseNode responseNode)

Parameters

Hook parameters
ParameterTypeDescription
npcTalkingNPCTalkingThe NPCTalking component (usually a Scientist NPC or one of the NPC vendors) whose conversation is being advanced. Use it when you need the entity, its position or its network id.
playerBasePlayerThe BasePlayer who pressed a response button in the dialogue UI. This is the player to reward, message or move.
conversationForConversationDataThe ConversationData that describes the conversation tree this response belongs to. Useful for telling one NPC conversation apart from another via its name or starting node.
responseNodeConversationData.ResponseNodeThe ConversationData.ResponseNode the player chose. Its shortname, responseText and next node index let you react to a specific dialogue option.

Return value

The return value is ignored. The hook is called with continue semantics, so whatever you return has no effect on the original method; the NPC has already been advanced to the next node before the hook runs.

How it works

The hook fires at the end of NPCTalking.Server_ResponsePressed, the server RPC that runs when a player clicks one of the dialogue options shown by a talking NPC. By then the NPC has already been forced onto the next speech node, so the conversation itself has progressed; this hook is a notification, not a gate.

Use player to identify who answered and apply per-player logic such as rewards, quest progress or chat feedback. responseNode is the most precise handle: compare responseNode.shortname (or responseText) against the options you care about to know which branch the player picked. conversationFor identifies the whole dialogue tree, which helps when several NPCs share option names. npcTalking gives you the entity (npcTalking.GetEntity() in most uses is the NPC) if you need to check distance, position or the NPC type.

There are no narrowing tricks that change which calls arrive, because the parameters are all distinct types already; declaring BasePlayer for the second argument is exact. Because this only runs on a human click, the hook is low frequency and safe for chat messages, UI updates and data writes. Do not expect it to block a response; that is what OnNpcConversationRespond is for.

Example plugin: Reward players once for picking the vendor's gift dialogue

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

namespace Oxide.Plugins
{
    [Info("Npc Dialogue Rewards", "RustPluginGenerator", "1.0.0")]
    [Description("Grants items once per player when they pick configured NPC conversation responses")]
    public class NpcDialogueRewards : RustPlugin
    {
        private const string PermUse = "npcdialoguerewards.use";

        private PluginConfig config;
        private StoredData data;

        private class PluginConfig
        {
            [JsonProperty("Response shortnames that pay out")]
            public List<string> RewardShortnames = new List<string> { "acknowledge_reward" };

            [JsonProperty("Item shortname to grant")]
            public string ItemShortname = "scrap";

            [JsonProperty("Item amount")]
            public int ItemAmount = 25;

            [JsonProperty("Player must have permission")]
            public bool RequirePermission = true;
        }

        private class StoredData
        {
            [JsonProperty("Claimed options")]
            public Dictionary<string, List<string>> Claimed = new Dictionary<string, List<string>>();
        }

        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"] = "You received {0}x {1} for your answer.",
                ["AlreadyClaimed"] = "You already received the reward for this option."
            }, this);
        }

        private string Msg(string key, string userId = null, params object[] args)
        {
            return string.Format(lang.GetMessage(key, this, userId), args);
        }

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
            data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
        }

        private void Unload()
        {
            if (data != null) Interface.Oxide.DataFileSystem.WriteObject(Name, data);
        }

        private void OnNpcConversationResponded(NPCTalking npcTalking, BasePlayer player, ConversationData conversationFor, ConversationData.ResponseNode responseNode)
        {
            if (player == null || conversationFor == null || responseNode == null || !player.IsConnected) return;

            // ResponseNode has no shortname field; use the parent conversation shortname as the option id.
            string responseId = conversationFor.shortname;
            if (string.IsNullOrEmpty(responseId)) return;
            if (!config.RewardShortnames.Contains(responseId)) return;
            if (config.RequirePermission && !permission.UserHasPermission(player.UserIDString, PermUse)) return;

            List<string> claimed;
            if (!data.Claimed.TryGetValue(player.UserIDString, out claimed))
            {
                claimed = new List<string>();
                data.Claimed[player.UserIDString] = claimed;
            }

            if (claimed.Contains(responseId))
            {
                player.ChatMessage(Msg("AlreadyClaimed", player.UserIDString));
                return;
            }

            var item = ItemManager.CreateByName(config.ItemShortname, config.ItemAmount);
            if (item == null)
            {
                PrintWarning("Unknown item shortname in config: " + config.ItemShortname);
                return;
            }

            player.GiveItem(item);
            claimed.Add(responseId);
            Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            player.ChatMessage(Msg("Reward", player.UserIDString, item.amount, item.info.displayName.english));
        }
    }
}

The plugin listens for conversation responses and compares the parent conversation's shortname (conversationFor.shortname) against a configurable list of rewarded names. When a match is found it records the player in an Oxide data file keyed by their Steam id, so each option can only pay out once per player. Rewards are delivered with ItemManager.CreateByName plus a chat confirmation sent through the language file. The data is written on change and again in Unload, and the player, conversation, response, and connection state are checked before use. Because the hook only fires on a real button press, the per-click data write is cheap.

Common use cases

  • Quest and reputation progress

    Track which dialogue branches a player picked so a custom quest system can advance a stage or add reputation points when the right option is clicked.

  • One-time giveaways from NPC vendors

    Detect a specific reward option and hand out scrap, an item or a currency, keeping a per-player record so the same conversation cannot be farmed.

  • Audit and moderation logging

    Write a timestamped entry when players answer NPC dialogue, useful for tracing scamming attempts around player-built NPC shops or scripted events.

Pitfalls

  • It cannot block the response

    The NPC is already moved to the next node before the hook runs, so you cannot cancel the choice here. Use OnNpcConversationRespond if you need to prevent or rewrite an answer.

  • shortname is not a stable contract

    Response node shortnames belong to the game's conversation data and can change between Rust patches. Treat configured option names as something admins may need to re-check after an update.

  • Same player, multiple clicks

    A player can reopen the conversation and pick the same option again, so any reward logic must keep its own dedupe record rather than relying on the game.

Build a plugin with OnNpcConversationResponded

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

Prompt: Quest and reputation progress using the OnNpcConversationResponded 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 OnNpcConversationResponded

Generate