Failed to call internal hook 'OnPlayerChat' on plugin 'BetterChat v5.2.14' [2032160890] (Chat channel is set to Team, however the player is not in a team.)
at CancelOptions Oxide.Plugins.BetterChat.SendBetterChatMessage(BetterChatMessage chatMessage, ChatChannel chatchannel) in C:/XRustServers/Rust10xPVP/carbon/plugins/BetterChat.cs:line 257
at object Oxide.Plugins.BetterChat.OnPlayerChat(BasePlayer bplayer, string message, ChatChannel chatchannel) in C:/XRustServers/Rust10xPVP/carbon/plugins/BetterChat.cs:line 107
at object Oxide.Plugins.BetterChat.InternalCallHook(uint hook, object[] args) in BetterChat.cs/Internal:line 173
Github Copilots Response:
The exception is thrown intentionally in SendBetterChatMessage when the chat channel is Team but the player has no team. This can legitimately happen (e.g., client still has team channel selected after leaving a team, or third-party framework passes an unexpected channel). Instead of throwing, gracefully fall back to Global chat and log a warning.
#if RUST
private BetterChatMessage.CancelOptions SendBetterChatMessage(BetterChatMessage chatMessage, Chat.ChatChannel chatchannel)
#else
private BetterChatMessage.CancelOptions SendBetterChatMessage(BetterChatMessage chatMessage)
#endif
{
Dictionary<string, object> chatMessageDict = chatMessage.ToDictionary();
#if RUST
chatMessageDict.Add("ChatChannel", chatchannel);
#endif
foreach (Plugin plugin in plugins.GetAll())
{
object hookResult = plugin.CallHook("OnBetterChat", chatMessageDict);
if (hookResult is Dictionary<string, object>)
{
try
{
chatMessageDict = hookResult as Dictionary<string, object>;
}
catch (Exception e)
{
PrintError($"Failed to load modified OnBetterChat hook data from plugin '{plugin.Title} ({plugin.Version})':{Environment.NewLine}{e}");
continue;
}
}
else if (hookResult != null)
return BetterChatMessage.CancelOptions.BetterChatOnly;
}
chatMessage = BetterChatMessage.FromDictionary(chatMessageDict);
if (chatMessage.CancelOption != BetterChatMessage.CancelOptions.None)
{
return chatMessage.CancelOption;
}
var output = chatMessage.GetOutput();
#if RUST
BasePlayer basePlayer = chatMessage.Player.Object as BasePlayer;
// Track the actual channel used for output/logging
var usedChannel = chatchannel;
switch (chatchannel)
{
case Chat.ChatChannel.Team:
RelationshipManager.PlayerTeam team = basePlayer.Team;
if (team == null || team.members.Count == 0)
{
// Fallback: player attempted Team chat without a team — route to Global instead of throwing
usedChannel = Chat.ChatChannel.Global;
PrintWarning($"Received Team chat from {basePlayer.displayName} ({basePlayer.UserIDString}) while not in a team. Routing to Global.");
foreach (BasePlayer p in BasePlayer.activePlayerList.Where(p => !chatMessage.BlockedReceivers.Contains(p.UserIDString)))
p.SendConsoleCommand("chat.add", (int)usedChannel, chatMessage.Player.Id, output.Chat);
break;
}
// Valid team chat
team.BroadcastTeamChat(basePlayer.userID, chatMessage.Player.Name, chatMessage.Message, chatMessage.UsernameSettings.Color);
List<Network.Connection> onlineMemberConnections = team.GetOnlineMemberConnections();
if (onlineMemberConnections != null)
{
ConsoleNetwork.SendClientCommand(onlineMemberConnections, "chat.add", (int) chatchannel, chatMessage.Player.Id, output.Chat);
}
break;
case Chat.ChatChannel.Cards:
BaseCardGameEntity baseCardGame = basePlayer.GetMountedVehicle() as BaseCardGameEntity;
if (baseCardGame == null /* || !cardTable.GameController.PlayerIsInGame(basePlayer) */)
{
// Fallback: not at a card table — route to Global instead of throwing
usedChannel = Chat.ChatChannel.Global;
PrintWarning($"Received Cards chat from {basePlayer.displayName} ({basePlayer.UserIDString}) while not at a card table. Routing to Global.");
foreach (BasePlayer p in BasePlayer.activePlayerList.Where(p => !chatMessage.BlockedReceivers.Contains(p.UserIDString)))
p.SendConsoleCommand("chat.add", (int)usedChannel, chatMessage.Player.Id, output.Chat);
break;
}
List<Network.Connection> list = Facepunch.Pool.GetList<Network.Connection>();
foreach (CardPlayerData playerData in baseCardGame.GameController.PlayerData)
{
if (playerData.HasUser)
{
list.Add(BasePlayer.FindByID(playerData.UserID).net.connection);
}
}
if (list.Count > 0)
{
ConsoleNetwork.SendClientCommand(list, "chat.add", (int) chatchannel, chatMessage.Player.Id, output.Chat);
}
Facepunch.Pool.FreeList(ref list);
break;
case Chat.ChatChannel.Local:
float localRange = Chat.localChatRange * Chat.localChatRange;
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
if (chatMessage.BlockedReceivers.Contains(player.UserIDString))
{
continue;
}
if ((basePlayer.transform.position - player.transform.position).sqrMagnitude <= localRange)
{
player.SendConsoleCommand("chat.add", (int)chatchannel, chatMessage.Player.Id, output.Chat);
}
}
break;
default:
foreach (BasePlayer p in BasePlayer.activePlayerList.Where(p => !chatMessage.BlockedReceivers.Contains(p.UserIDString)))
p.SendConsoleCommand("chat.add", (int) usedChannel, chatMessage.Player.Id, output.Chat);
break;
}
#else
foreach (IPlayer p in players.Connected.Where(p => !chatMessage.BlockedReceivers.Contains(p.Id)))
p.Message(output.Chat);
#endif
#if RUST
Puts($"[{usedChannel}] {output.Console}");
var chatEntry = new Chat.ChatEntry
{
Channel = usedChannel,
Message = output.Console,
UserId = chatMessage.Player.Id,
Username = chatMessage.Player.Name,
Color = chatMessage.UsernameSettings.Color,
Time = Epoch.Current
};
Chat.Record(chatEntry);
#else
Puts(output.Console);
#endif
return chatMessage.CancelOption;
}