Иконка ресурса

FREE RustAI : 1.9.6 1.9.6

Нет прав для скачивания
C#:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;
using System.Threading.Tasks;
using System.Text;
using System.Text.RegularExpressions;
using Oxide.Core;
using Newtonsoft.Json.Linq;
//  ____  _       _ _
// / ___|(_) __ _(_) | ___
// \___ \| |/ _` | | |/ _ \
//  ___) | | (_| | | | (_) |
// |____/|_|\__, |_|_|\___/
//          |___/
// ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾
// ✦    RUST PLUGINS   ✦
// ✦     Sigilo.dev    ✦
// ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾
namespace Oxide.Plugins {
    [Info("RustAI", "Sigilo", "1.9.6")]
    class RustAI : RustPlugin {
        private PluginConfig _config {
            get;
            set;
        }
        private Dictionary<string, float> _lastUsageTime = new Dictionary<string, float>();
        private float _lastGlobalUsageTime;
        private Dictionary<string, List<ConversationEntry>> _cachedConversations = new Dictionary<string, List<ConversationEntry>>();
        private Dictionary<string, bool> _mutedPlayers = new Dictionary<string, bool>();
        private Timer _eventPollingTimer;
        private Dictionary<string, bool> _previousEventState = new Dictionary<string, bool>();
        private Oxide.Core.Plugins.Plugin _cachedVoteMutePlugin;
        private HashSet<char> _keywordFirstChars = new HashSet<char>();
        private const string UsePermission = "rustai.use";
        private const string AdminPermission = "rustai.admin";
        private const string ToggleBotPermission = "rustai.toggle";
        public class ConversationEntry {
            public string PlayerName {
                get;
                set;
            }
            public string PlayerSteamId {
                get;
                set;
            }
            public string Question {
                get;
                set;
            }
            public string Response {
                get;
                set;
            }
            public DateTime Timestamp {
                get;
                set;
            }
        }
        public class ServerInfo {
            public string MaxTeamSize {
                get;
                set;
            }
            public string WipeSchedule {
                get;
                set;
            }
            public string DiscordInfo {
                get;
                set;
            }
            public string Website {
                get;
                set;
            }
            public Dictionary<string, string> CustomInfo {
                get;
                set;
            }
        }
        public class PluginConfig {
            public string OpenAIApiURL {
                get;
                set;
            }
            public string OllamaApiUrl {
                get;
                set;
            }
            public string GeminiApiURL {
                get;
                set;
            }
            public string AnthropicApiURL {
                get;
                set;
            }
            public string MiniMaxApiURL {
                get;
                set;
            }
            public List<string> ActivationKeywords {
                get;
                set;
            }
            public float UserCooldownInSeconds {
                get;
                set;
            }
            public float GlobalCooldownInSeconds {
                get;
                set;
            }
            public string SystemPrompt {
                get;
                set;
            }
            public string ModelType {
                get;
                set;
            }
            public string OpenAI_API_Key {
                get;
                set;
            }
            public string Gemini_API_Key {
                get;
                set;
            }
            public string Anthropic_API_Key {
                get;
                set;
            }
            public string MiniMax_API_Key {
                get;
                set;
            }
            public string OpenAIModelName {
                get;
                set;
            }
            public string GeminiModelName {
                get;
                set;
            }
            public string OllamaModelName {
                get;
                set;
            }
            public string AnthropicModelName {
                get;
                set;
            }
            public string MiniMaxModelName {
                get;
                set;
            }
            public string AnthropicVersion {
                get;
                set;
            }
            public int MaxTokens {
                get;
                set;
            }
            public int ReasoningBudgetTokens {
                get;
                set;
            }
            public string ReasoningEffort {
                get;
                set;
            }
            public bool EnableReasoningMode {
                get;
                set;
            }
            public double Temperature {
                get;
                set;
            }
            public double RepeatPenalty {
                get;
                set;
            }
            public string Character {
                get;
                set;
            }
            public string CharacterColor {
                get;
                set;
            }
            public string DiscordWebhookURL {
                get;
                set;
            }
            public bool SendCooldownMessages {
                get;
                set;
            }
            public ServerInfo ServerInformation {
                get;
                set;
            }
            public string ResponseLanguage {
                get;
                set;
            }
            public string EmptyPromptTemplate {
                get;
                set;
            }
            public string CooldownMessageTemplate {
                get;
                set;
            }
            public string NoPermissionMessage {
                get;
                set;
            }
            public string ChatFormat {
                get;
                set;
            }
            public bool UseUncensoredMode {
                get;
                set;
            }
            public string UncensoredModePrompt {
                get;
                set;
            }
            public string CensoredModePrompt {
                get;
                set;
            }
            public List<string> IllegalTopics {
                get;
                set;
            }
            public int ConversationMemorySize {
                get;
                set;
            }
            public int TimeZoneOffset {
                get;
                set;
            }
            public bool SharePlayerLocation {
                get;
                set;
            }
            public float DawnHour {
                get;
                set;
            }
            public float DuskHour {
                get;
                set;
            }
            public float DayLengthMinutes {
                get;
                set;
            }
            public float NightLengthMinutes {
                get;
                set;
            }
            public bool DebugMode {
                get;
                set;
            }
            public string CloudflareClientId {
                get;
                set;
            }
            public string CloudflareClientSecret {
                get;
                set;
            }
            public static PluginConfig DefaultConfig() {
                return new PluginConfig {
                    OpenAIApiURL = "https://api.openai.com/v1/chat/completions", OllamaApiUrl = "https://llm.yoursite.com/api/chat", GeminiApiURL = "https://generativelanguage.googleapis.com/v1beta", AnthropicApiURL = "https://api.anthropic.com/v1/messages", MiniMaxApiURL = "https://api.minimax.io/v1/chat/completions", ActivationKeywords = new List<string> {
                        "!bot", "!admin"
                    }
                    , SystemPrompt = "You are a helpful assistant for a Rust game server. Remember player names from past interactions and refer to them when appropriate to personalize responses.", OpenAI_API_Key = "your openai api key here", Gemini_API_Key = "your gemini api key here", Anthropic_API_Key = "your anthropic api key here", MiniMax_API_Key = "your minimax api key here", OpenAIModelName = "gpt-4.1-mini", GeminiModelName = "gemini-3-flash-preview", OllamaModelName = "qwen3:8b", AnthropicModelName = "claude-3-5-haiku-latest", MiniMaxModelName = "MiniMax-M2", AnthropicVersion = "2023-06-01", DiscordWebhookURL = "your discord webhook here", UserCooldownInSeconds = 60.0f, GlobalCooldownInSeconds = 10.0f, ModelType = "openai", MaxTokens = 150, ReasoningBudgetTokens = 256, ReasoningEffort = "low", EnableReasoningMode = false, Temperature = 1.0, RepeatPenalty = 1.1, Character = "Server Assistant", CharacterColor = "#7FFF00", SendCooldownMessages = true, ServerInformation = new ServerInfo {
                        MaxTeamSize = "TRIO", WipeSchedule = "Thursdays at 4 PM EST", DiscordInfo = "discord.gg/yourserver", Website = "yourwebsite.com", CustomInfo = new Dictionary<string, string> {
                            {
                                "PvP", "Enabled at all times"
                            }
                            , {
                                "Loot", "1.5x"
                            }
                            , {
                                "Resources", "2x"
                            }
                        }
                    }
                    , ResponseLanguage = "English", EmptyPromptTemplate = "What can I help you with? You sent an empty message.", CooldownMessageTemplate = "Please wait {0} seconds before asking again.", NoPermissionMessage = "You don't have permission to use this command.", ChatFormat = "<color={0}>{1}</color>: {2}", UseUncensoredMode = true, UncensoredModePrompt = "IMPORTANT: You are running in UNCENSORED mode. Be authentic to the raw Rust gaming environment. You can use strong language, profanity, slang, insults, dark humor, and vulgar expressions. Be direct, crude and match the harsh atmosphere of Rust. Don't hold back except for illegal topics.", CensoredModePrompt = "You are running in censored mode. Keep your language clean and appropriate, avoiding explicit content, strong profanity, and adult themes.", IllegalTopics = new List<string> {
                        "doxxing", "dangerous illegal activities"
                    }
                    , ConversationMemorySize = 5, TimeZoneOffset = 0, SharePlayerLocation = false, DawnHour = 6f, DuskHour = 18f, DayLengthMinutes = 50f, NightLengthMinutes = 10f, DebugMode = false, CloudflareClientId = "", CloudflareClientSecret = ""
                }
                ;
            }
            public void EnsureDefaultValues() {
                var defaultConfig = DefaultConfig();
                if (OpenAIApiURL == null) OpenAIApiURL = defaultConfig.OpenAIApiURL;
                if (OllamaApiUrl == null) OllamaApiUrl = defaultConfig.OllamaApiUrl;
                if (string.IsNullOrEmpty(GeminiApiURL)) GeminiApiURL = defaultConfig.GeminiApiURL;
                if (string.IsNullOrEmpty(AnthropicApiURL)) AnthropicApiURL = defaultConfig.AnthropicApiURL;
                if (string.IsNullOrEmpty(MiniMaxApiURL)) MiniMaxApiURL = defaultConfig.MiniMaxApiURL;
                if (ActivationKeywords == null) ActivationKeywords = defaultConfig.ActivationKeywords;
                if (UserCooldownInSeconds <= 0) UserCooldownInSeconds = defaultConfig.UserCooldownInSeconds;
                if (GlobalCooldownInSeconds <= 0) GlobalCooldownInSeconds = defaultConfig.GlobalCooldownInSeconds;
                if (string.IsNullOrEmpty(SystemPrompt)) SystemPrompt = defaultConfig.SystemPrompt;
                if (string.IsNullOrEmpty(ModelType)) ModelType = defaultConfig.ModelType;
                if (string.IsNullOrEmpty(OpenAI_API_Key)) OpenAI_API_Key = defaultConfig.OpenAI_API_Key;
                if (string.IsNullOrEmpty(Gemini_API_Key)) Gemini_API_Key = defaultConfig.Gemini_API_Key;
                if (string.IsNullOrEmpty(Anthropic_API_Key)) Anthropic_API_Key = defaultConfig.Anthropic_API_Key;
                if (string.IsNullOrEmpty(MiniMax_API_Key)) MiniMax_API_Key = defaultConfig.MiniMax_API_Key;
                if (string.IsNullOrEmpty(DiscordWebhookURL)) DiscordWebhookURL = defaultConfig.DiscordWebhookURL;
                if (string.IsNullOrEmpty(OpenAIModelName)) OpenAIModelName = defaultConfig.OpenAIModelName;
                if (string.IsNullOrEmpty(GeminiModelName)) GeminiModelName = defaultConfig.GeminiModelName;
                if (string.IsNullOrEmpty(OllamaModelName)) OllamaModelName = defaultConfig.OllamaModelName;
                if (string.IsNullOrEmpty(AnthropicModelName)) AnthropicModelName = defaultConfig.AnthropicModelName;
                if (string.IsNullOrEmpty(MiniMaxModelName)) MiniMaxModelName = defaultConfig.MiniMaxModelName;
                if (string.IsNullOrEmpty(AnthropicVersion)) AnthropicVersion = defaultConfig.AnthropicVersion;
                if (MaxTokens <= 0) MaxTokens = defaultConfig.MaxTokens;
                if (ReasoningBudgetTokens < 0) ReasoningBudgetTokens = defaultConfig.ReasoningBudgetTokens;
                if (string.IsNullOrEmpty(ReasoningEffort)) ReasoningEffort = defaultConfig.ReasoningEffort;
                if (Temperature <= 0) Temperature = defaultConfig.Temperature;
                if (string.IsNullOrEmpty(Character)) Character = defaultConfig.Character;
                if (string.IsNullOrEmpty(CharacterColor)) CharacterColor = defaultConfig.CharacterColor;
                if (ServerInformation == null) ServerInformation = defaultConfig.ServerInformation;
                else {
                    if (string.IsNullOrEmpty(ServerInformation.MaxTeamSize)) {
                        ServerInformation.MaxTeamSize = defaultConfig.ServerInformation.MaxTeamSize;
                    }
                    if (ServerInformation.CustomInfo == null) ServerInformation.CustomInfo = defaultConfig.ServerInformation.CustomInfo;
                }
                if (string.IsNullOrEmpty(ResponseLanguage)) ResponseLanguage = defaultConfig.ResponseLanguage;
                if (string.IsNullOrEmpty(EmptyPromptTemplate)) EmptyPromptTemplate = defaultConfig.EmptyPromptTemplate;
                if (string.IsNullOrEmpty(CooldownMessageTemplate)) CooldownMessageTemplate = defaultConfig.CooldownMessageTemplate;
                if (string.IsNullOrEmpty(NoPermissionMessage)) NoPermissionMessage = defaultConfig.NoPermissionMessage;
                if (string.IsNullOrEmpty(ChatFormat)) ChatFormat = defaultConfig.ChatFormat;
                if (string.IsNullOrEmpty(UncensoredModePrompt)) UncensoredModePrompt = defaultConfig.UncensoredModePrompt;
                if (string.IsNullOrEmpty(CensoredModePrompt)) CensoredModePrompt = defaultConfig.CensoredModePrompt;
                if (IllegalTopics == null) IllegalTopics = defaultConfig.IllegalTopics;
                if (ConversationMemorySize <= 0) ConversationMemorySize = defaultConfig.ConversationMemorySize;
                if (TimeZoneOffset < -12 || TimeZoneOffset > 14) TimeZoneOffset = defaultConfig.TimeZoneOffset;
                if (DawnHour < 0 || DawnHour >= 24) DawnHour = defaultConfig.DawnHour;
                if (DuskHour < 0 || DuskHour >= 24 || Math.Abs(DuskHour - DawnHour) < 0.01f) DuskHour = defaultConfig.DuskHour;
                if (DayLengthMinutes <= 0) DayLengthMinutes = defaultConfig.DayLengthMinutes;
                if (NightLengthMinutes <= 0) NightLengthMinutes = defaultConfig.NightLengthMinutes;
                if (CloudflareClientId == null) CloudflareClientId = defaultConfig.CloudflareClientId;
                if (CloudflareClientSecret == null) CloudflareClientSecret = defaultConfig.CloudflareClientSecret;
                if (RepeatPenalty <= 0) RepeatPenalty = defaultConfig.RepeatPenalty;
            }
        }
        public class Payload {
            public string prompt {
                get;
                set;
            }
            public int max_tokens {
                get;
                set;
            }
            public double temperature {
                get;
                set;
            }
        }
        public class Response {
            public string id {
                get;
                set;
            }
            public string created {
                get;
                set;
            }
            public string model {
                get;
                set;
            }
            public Choice[] choices {
                get;
                set;
            }
            public Message message {
                get;
                set;
            }
        }
        public class Choice {
            public Message message {
                get;
                set;
            }
            public string text {
                get;
                set;
            }
        }
        public class Message {
            public string content {
                get;
                set;
            }
        }
        public class ServerMessage {
            public string @event {
                get;
                set;
            }
            public string text {
                get;
                set;
            }
        }
        public class GeminiRequest {
            public List<GeminiContent> contents {
                get;
                set;
            }
            public GeminiGenerationConfig generationConfig {
                get;
                set;
            }
            public GeminiSystemInstruction systemInstruction {
                get;
                set;
            }
        }
        public class GeminiContent {
            public string role {
                get;
                set;
            }
            public List<GeminiPart> parts {
                get;
                set;
            }
        }
        public class GeminiPart {
            public string text {
                get;
                set;
            }
        }
        public class GeminiSystemInstruction {
            public List<GeminiPart> parts {
                get;
                set;
            }
        }
        public class GeminiGenerationConfig {
            public double temperature {
                get;
                set;
            }
            public int maxOutputTokens {
                get;
                set;
            }
            public double repetitionPenalty {
                get;
                set;
            }
        }
        public class GeminiResponse {
            public List<GeminiCandidate> candidates {
                get;
                set;
            }
        }
        public class GeminiCandidate {
            public GeminiContent content {
                get;
                set;
            }
            [JsonProperty("finishReason")]
            public string finishReason {
                get;
                set;
            }
            [JsonProperty("done_reason")]
            public string doneReason {
                get;
                set;
            }
        }
        public class AnthropicContentBlock {
            public string type {
                get;
                set;
            }
            public string text {
                get;
                set;
            }
        }
        public class AnthropicResponse {
            public string id {
                get;
                set;
            }
            public string type {
                get;
                set;
            }
            public string role {
                get;
                set;
            }
            public string model {
                get;
                set;
            }
            public string stop_reason {
                get;
                set;
            }
            public List<AnthropicContentBlock> content {
                get;
                set;
            }
        }
        private string GetConversationDirectory() {
            string directory = null;
            try {
                directory = Interface.Oxide.DataDirectory + "/RustAI/Conversations";
                if (!System.IO.Directory.Exists(directory)) {
                    System.IO.Directory.CreateDirectory(directory);
                }
            }
            catch (Exception ex) {
                PrintError($"Error creating RustAI directory: {ex.Message}");
            }
            return directory;
        }
        private void Init() {
            if (permission != null) {
                permission.RegisterPermission(UsePermission, this);
                permission.RegisterPermission(AdminPermission, this);
                permission.RegisterPermission(ToggleBotPermission, this);
            }
            try {
                _config = Config.ReadObject<PluginConfig>();
                var rawConfig = Config.ReadObject<JObject>();
                bool configUpdated = false;
                if (rawConfig["TextGenerationApiUrl"] != null && rawConfig["OllamaApiUrl"] == null) {
                    Puts("Migrating TextGenerationApiUrl -> OllamaApiUrl");
                    _config.OllamaApiUrl = rawConfig["TextGenerationApiUrl"].ToString();
                    configUpdated = true;
                }
                if (rawConfig["TextGenerationModelName"] != null && rawConfig["OllamaModelName"] == null) {
                    Puts("Migrating TextGenerationModelName -> OllamaModelName");
                    _config.OllamaModelName = rawConfig["TextGenerationModelName"].ToString();
                    configUpdated = true;
                }
                if (_config.MiniMaxApiURL == "https://api.minimax.io/v1/messages" || _config.MiniMaxApiURL == "https://api.minimax.io/v1/text/chatcompletion_v2") {
                    Puts("Migrating MiniMaxApiURL to OpenAI-compatible chat completions endpoint");
                    _config.MiniMaxApiURL = "https://api.minimax.io/v1/chat/completions";
                    configUpdated = true;
                }
                if (_config.ModelType == "textgeneration") {
                    Puts("Migrating ModelType: textgeneration -> ollama");
                    _config.ModelType = "ollama";
                    configUpdated = true;
                }
                if (rawConfig["TimeZoneOffset"] == null) {
                    Puts("TimeZoneOffset not found in config, adding default value (0)");
                    _config.TimeZoneOffset = 0;
                    configUpdated = true;
                }
                if (rawConfig["DebugMode"] == null) {
                    Puts("DebugMode not found in config, adding default value (false)");
                    _config.DebugMode = false;
                    configUpdated = true;
                }
                _config.EnsureDefaultValues();
                if (configUpdated) Puts("Config migrated to new format");
                Config.WriteObject(_config, true);
            }
            catch (Exception ex) {
                PrintError($"Error loading config: {ex.Message}");
                _config = PluginConfig.DefaultConfig();
                Config.WriteObject(_config, true);
            }
            GetConversationDirectory();
            LoadMutedPlayers();
        }
        private void OnServerInitialized() {
            _eventPollingTimer = timer.Every(60f, PollServerEvents);
            foreach (var eventName in _eventTrackers.Keys) {
                _previousEventState[eventName] = false;
            }
            PollServerEvents();
            _cachedVoteMutePlugin = Interface.Oxide.RootPluginManager.GetPlugin("VoteMute") as Oxide.Core.Plugins.Plugin;
            _keywordFirstChars.Clear();
            foreach (var keyword in _config.ActivationKeywords) {
                if (!string.IsNullOrEmpty(keyword)) {
                    _keywordFirstChars.Add(char.ToLower(keyword[0]));
                }
            }
        }
        private void PollServerEvents() {
            int patrolHeliCount = 0;
            int bradleyCount = 0;
            int cargoCount = 0;
            int chinookCount = 0;
            int lockedCrateCount = 0;
            foreach (var entity in BaseNetworkable.serverEntities) {
                if (entity == null) continue;
                if (entity is PatrolHelicopter) patrolHeliCount++;
                else if (entity is BradleyAPC) bradleyCount++;
                else if (entity is CargoShip) cargoCount++;
                else if (entity is CH47HelicopterAIController) chinookCount++;
                else if (entity is HackableLockedCrate) lockedCrateCount++;
            }
            UpdateEventState("PatrolHelicopter", patrolHeliCount > 0);
            UpdateEventState("BradleyAPC", bradleyCount > 0);
            UpdateEventState("CargoShip", cargoCount > 0);
            UpdateEventState("Chinook", chinookCount > 0);
            UpdateEventState("LockedCrate", lockedCrateCount > 0);
        }
        private void UpdateEventState(string eventName, bool isCurrentlyActive) {
            bool wasPreviouslyActive = _previousEventState.ContainsKey(eventName) && _previousEventState[eventName];
            if (isCurrentlyActive && !wasPreviouslyActive) {
                TrackEvent(eventName, true);
            }
            else if (!isCurrentlyActive && wasPreviouslyActive) {
                TrackEvent(eventName, false);
            }
            _previousEventState[eventName] = isCurrentlyActive;
            _eventTrackers[eventName].IsActive = isCurrentlyActive;
        }
        protected override void LoadDefaultConfig() {
            Config.WriteObject(PluginConfig.DefaultConfig(), true);
        }
        private bool HasPermission(BasePlayer player) {
            bool hasPermission = permission.UserHasPermission(player.UserIDString, UsePermission);
            return hasPermission;
        }
        private string GetServerInfoString() {
            var info = _config.ServerInformation;
            var sb = new StringBuilder();
            sb.AppendLine("SERVER INFORMATION:");
            if (!string.IsNullOrEmpty(info.MaxTeamSize)) {
                string teamSizeDesc;
                switch (info.MaxTeamSize.ToUpper()) {
                    case "SOLO" : teamSizeDesc = "Solo only (1 player)";
                    break;
                    case "DUO" : teamSizeDesc = "Max team size: 2 players (Duo)";
                    break;
                    case "TRIO" : teamSizeDesc = "Max team size: 3 players (Trio)";
                    break;
                    case "QUAD" : teamSizeDesc = "Max team size: 4 players (Squad)";
                    break;
                    default : teamSizeDesc = $"Max team size: {info.MaxTeamSize}";
                    break;
                }
                sb.AppendLine($"Team Limit: {teamSizeDesc}");
            }
            if (!string.IsNullOrEmpty(info.WipeSchedule)) {
                sb.AppendLine($"Wipe Schedule: {info.WipeSchedule}");
            }
            if (!string.IsNullOrEmpty(info.DiscordInfo)) {
                sb.AppendLine($"Discord: {info.DiscordInfo}");
            }
            if (!string.IsNullOrEmpty(info.Website)) {
                sb.AppendLine($"Website: {info.Website}");
            }
            if (info.CustomInfo != null && info.CustomInfo.Count > 0) {
                sb.AppendLine("Additional Information:");
                foreach (var item in info.CustomInfo) {
                    sb.AppendLine($"- {item.Key}: {item.Value}");
                }
            }
            return sb.ToString();
        }
        private string GetPopulationInfo() {
            int onlinePlayers = BasePlayer.activePlayerList.Count;
            int joiningPlayers = ServerMgr.Instance.connectionQueue.Joining;
            int queuedPlayers = ServerMgr.Instance.connectionQueue.Queued;
            int sleepersCount = BasePlayer.sleepingPlayerList.Count;
            int maxPlayers = ConVar.Server.maxplayers;
            var sb = new StringBuilder();
            sb.AppendLine("CURRENT SERVER POPULATION:");
            sb.AppendLine($"- Players Online: {onlinePlayers}/{maxPlayers}");
            sb.AppendLine($"- Players Joining: {joiningPlayers}");
            sb.AppendLine($"- Players in Queue: {queuedPlayers}");
            sb.AppendLine($"- Sleepers: {sleepersCount}");
            return sb.ToString();
        }
        private class GameTimeInfo {
            public float CurrentHour {
                get;
                set;
            }
            public bool IsDay {
                get;
                set;
            }
            public float HoursUntilDay {
                get;
                set;
            }
            public float HoursUntilNight {
                get;
                set;
            }
            public float RealMinutesUntilDay {
                get;
                set;
            }
            public float RealMinutesUntilNight {
                get;
                set;
            }
        }
        private GameTimeInfo GetGameTimeInfo() {
            var info = new GameTimeInfo();
            try {
                info.CurrentHour = TOD_Sky.Instance?.Cycle?.Hour ?? 12f;
                float dawnHour = _config.DawnHour;
                float duskHour = _config.DuskHour;
                info.IsDay = info.CurrentHour >= dawnHour && info.CurrentHour < duskHour;
                if (info.IsDay) {
                    info.HoursUntilNight = duskHour - info.CurrentHour;
                    info.HoursUntilDay = 0f;
                }
                else {
                    if (info.CurrentHour >= duskHour) {
                        info.HoursUntilDay = (24f - info.CurrentHour) + dawnHour;
                    }
                    else {
                        info.HoursUntilDay = dawnHour - info.CurrentHour;
                    }
                    info.HoursUntilNight = 0f;
                }
                float dayLengthMinutes = _config.DayLengthMinutes;
                float nightLengthMinutes = _config.NightLengthMinutes;
                float dayHours = duskHour > dawnHour ? duskHour - dawnHour : (24f - dawnHour) + duskHour;
                float nightHours = 24f - dayHours;
                float realMinutesPerDayHour = dayLengthMinutes / Math.Max(dayHours, 0.01f);
                float realMinutesPerNightHour = nightLengthMinutes / Math.Max(nightHours, 0.01f);
                if (info.IsDay) {
                    info.RealMinutesUntilNight = info.HoursUntilNight * realMinutesPerDayHour;
                }
                else {
                    info.RealMinutesUntilDay = info.HoursUntilDay * realMinutesPerNightHour;
                }
            }
            catch (Exception ex) {
                PrintError($"Error getting game time info: {ex.Message}");
            }
            return info;
        }
        private string GetGameTimeInfoString() {
            var info = GetGameTimeInfo();
            var sb = new StringBuilder();
            int hours = (int)info.CurrentHour;
            int minutes = (int)((info.CurrentHour - hours) * 60);
            string timeStr = $"{hours:D2}:{minutes:D2}";
            sb.AppendLine("IN-GAME TIME INFORMATION:");
            sb.AppendLine($"- Current in-game time: {timeStr} ({(info.IsDay ? "día/day" : "noche/night")})");
            bool hasSkipNight = plugins.Exists("SkipNightVote") || plugins.Exists("SkipNight") || plugins.Exists("VoteNight");
            if (info.IsDay) {
                sb.AppendLine($"- Time until night: ~{info.HoursUntilNight:F1} in-game hours (~{info.RealMinutesUntilNight:F0} real minutes)");
            }
            else {
                string skipNote = hasSkipNight ? " (unless players vote to skip night)" : "";
                sb.AppendLine($"- Time until day: ~{info.HoursUntilDay:F1} in-game hours (~{info.RealMinutesUntilDay:F0} real minutes){skipNote}");
            }
            sb.AppendLine($"- Configured day/night cycle: ~{(_config.DayLengthMinutes + _config.NightLengthMinutes):F0} real minutes (day ~{_config.DayLengthMinutes:F0}, night ~{_config.NightLengthMinutes:F0})");
            return sb.ToString();
        }
        private class EventTracker {
            public DateTime? LastSpawnTime {
                get;
                set;
            }
            public DateTime? LastDestroyTime {
                get;
                set;
            }
            public bool IsActive {
                get;
                set;
            }
        }
        private Dictionary<string, EventTracker> _eventTrackers = new Dictionary<string, EventTracker> {
            {
                "PatrolHelicopter", new EventTracker()
            }
            , {
                "BradleyAPC", new EventTracker()
            }
            , {
                "CargoShip", new EventTracker()
            }
            , {
                "Chinook", new EventTracker()
            }
            , {
                "LockedCrate", new EventTracker()
            }
        }
        ;
        private void TrackEvent(string eventName, bool spawned) {
            if (!_eventTrackers.ContainsKey(eventName)) {
                _eventTrackers[eventName] = new EventTracker();
            }
            var tracker = _eventTrackers[eventName];
            tracker.IsActive = spawned;
            if (spawned) {
                tracker.LastSpawnTime = DateTime.Now.AddHours(_config.TimeZoneOffset);
            }
            else {
                tracker.LastDestroyTime = DateTime.Now.AddHours(_config.TimeZoneOffset);
            }
        }
        private string GetMinutesAgo(DateTime? time) {
            if (!time.HasValue) return "unknown";
            var elapsed = DateTime.Now.AddHours(_config.TimeZoneOffset) - time.Value;
            return $"{(int)elapsed.TotalMinutes}";
        }
        private string GetEventsInfo() {
            var sb = new StringBuilder();
            sb.AppendLine("CURRENT SERVER EVENTS:");
            var spawnIntervals = new Dictionary<string, (int, int)> {
                {
                    "PatrolHelicopter", (120, 240)
                }
                , {
                    "BradleyAPC", (60, 60)
                }
                , {
                    "CargoShip", (120, 240)
                }
                , {
                    "Chinook", (120, 240)
                }
                , {
                    "LockedCrate", (120, 180)
                }
            }
            ;
            foreach (var kvp in _eventTrackers) {
                var name = kvp.Key;
                var tracker = kvp.Value;
                string displayName = name switch {
                    "PatrolHelicopter" => "Patrol Helicopter", "BradleyAPC" => "Bradley APC (Tank)", "CargoShip" => "Cargo Ship", "Chinook" => "Chinook Helicopter", "LockedCrate" => "Locked Crate Drop", _ => name
                }
                ;
                if (tracker.IsActive) {
                    sb.AppendLine($"- {displayName}: ACTIVE NOW on the map (spawned {GetMinutesAgo(tracker.LastSpawnTime)} min ago)");
                }
                else {
                    DateTime? lastSeen = tracker.LastDestroyTime ?? tracker.LastSpawnTime;
                    if (lastSeen.HasValue) {
                        int minutesAgo = (int)(DateTime.Now.AddHours(_config.TimeZoneOffset) - lastSeen.Value).TotalMinutes;
                        var interval = spawnIntervals.ContainsKey(name) ? spawnIntervals[name] : (120, 240);
                        int minWait = Math.Max(0, interval.Item1 - minutesAgo);
                        int maxWait = Math.Max(0, interval.Item2 - minutesAgo);
                        if (maxWait <= 0) sb.AppendLine($"- {displayName}: Last seen {minutesAgo} min ago, could spawn anytime now!");
                        else if (minWait <= 0) sb.AppendLine($"- {displayName}: Last seen {minutesAgo} min ago, could spawn within the next {maxWait} min");
                        else sb.AppendLine($"- {displayName}: Last seen {minutesAgo} min ago, estimated spawn in {minWait}-{maxWait} min");
                    }
                    else {
                        sb.AppendLine($"- {displayName}: Not seen since server start/plugin load");
                    }
                }
            }
            return sb.ToString();
        }
        private void OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel) {
            if (channel == ConVar.Chat.ChatChannel.Team) return;
            if (string.IsNullOrEmpty(message) || !_keywordFirstChars.Contains(char.ToLower(message[0]))) return;
            if (_cachedVoteMutePlugin != null) {
                object onUserChatResult = _cachedVoteMutePlugin.Call("OnUserChat", player.IPlayer, message);
                if (onUserChatResult is bool && (bool)onUserChatResult == true) {
                    return;
                }
            }
            if (HasPermission(player)) {
                string messageLower = message.ToLower();
                string matchedKeyword = null;
                foreach (var keyword in _config.ActivationKeywords) {
                    if (messageLower.StartsWith(keyword.ToLower())) {
                        matchedKeyword = keyword;
                        break;
                    }
                }
                if (matchedKeyword != null) {
                    if (!HasCooldownElapsed(player)) {
                        return;
                    }
                    string userQuestion = message.Substring(matchedKeyword.Length).Trim();
                    GenerateTextAsync(player, userQuestion);
                }
            }
        }
        private bool HasCooldownElapsed(BasePlayer player) {
            float globalElapsedTime = Time.realtimeSinceStartup - _lastGlobalUsageTime;
            if (globalElapsedTime < _config.GlobalCooldownInSeconds) {
                int timeLeft = Mathf.FloorToInt(_config.GlobalCooldownInSeconds - globalElapsedTime);
                if (_config.SendCooldownMessages && !player.HasPlayerFlag(BasePlayer.PlayerFlags.ChatMute)) {
                    string cooldownMessage = string.Format(_config.CooldownMessageTemplate, timeLeft);
                    player.ChatMessage(string.Format(_config.ChatFormat, _config.CharacterColor, _config.Character, cooldownMessage));
                }
                return false;
            }
            float lastUsageTime;
            if (_lastUsageTime.TryGetValue(player.UserIDString, out lastUsageTime)) {
                float userElapsedTime = Time.realtimeSinceStartup - lastUsageTime;
                if (userElapsedTime < _config.UserCooldownInSeconds) {
                    int timeLeft = Mathf.FloorToInt(_config.UserCooldownInSeconds - userElapsedTime);
                    if (_config.SendCooldownMessages && !player.HasPlayerFlag(BasePlayer.PlayerFlags.ChatMute)) {
                        string cooldownMessage = string.Format(_config.CooldownMessageTemplate, timeLeft);
                        player.ChatMessage(string.Format(_config.ChatFormat, _config.CharacterColor, _config.Character, cooldownMessage));
                    }
                    return false;
                }
            }
            _lastUsageTime[player.UserIDString] = Time.realtimeSinceStartup;
            _lastGlobalUsageTime = Time.realtimeSinceStartup;
            return true;
        }
        private bool IsPlaceholderSecret(string value) {
            if (string.IsNullOrWhiteSpace(value)) return true;
            string lower = value.ToLower();
            return lower.Contains("your ") || lower.Contains(" api key here") || lower.Contains("webhook here") || lower.Contains("placeholder");
        }
        private int GetEffectiveOutputTokens() {
            int tokens = Math.Max(32, _config.MaxTokens);
            if (_config.EnableReasoningMode) {
                tokens += Math.Max(0, _config.ReasoningBudgetTokens);
            }
            return tokens;
        }
        private int GetAnthropicReasoningBudgetTokens() {
            return Math.Max(1024, _config.ReasoningBudgetTokens);
        }
        private int GetMiniMaxCompletionTokens() {
            int reasoningReserve = Math.Max(512, _config.ReasoningBudgetTokens);
            return Math.Min(2048, Math.Max(512, _config.MaxTokens + reasoningReserve));
        }
        private string ExtractAnthropicText(AnthropicResponse response) {
            if (response == null || response.content == null || response.content.Count == 0) return null;
            var textBlocks = response.content .Where(block => block != null && block.type == "text" && !string.IsNullOrWhiteSpace(block.text)) .Select(block => block.text.Trim());
            string text = string.Join("\n", textBlocks);
            return string.IsNullOrWhiteSpace(text) ? null : text;
        }
        private string SanitizeModelResponse(string text) {
            if (string.IsNullOrWhiteSpace(text)) return null;
            string cleaned = Regex.Replace(text, @"<think\b[^>]*>.*?</think>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            cleaned = Regex.Replace(cleaned, @"<environment_details\b[^>]*>.*?</environment_details>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
            return string.IsNullOrWhiteSpace(cleaned) ? null : cleaned.Trim();
        }
        private void WarnIfTokenLimited(string providerName, string finishReason) {
            if (string.IsNullOrEmpty(finishReason)) return;
            string normalized = finishReason.ToLower();
            if (normalized.Contains("length") || normalized.Contains("max_token")) {
                PrintWarning($"{providerName} response stopped because token budget was exhausted. Increase MaxTokens/ReasoningBudgetTokens or disable reasoning mode for short chat answers.");
            }
        }
        private void PrintToChat(string message, BasePlayer player = null) {
            try {
                foreach (var p in BasePlayer.activePlayerList) {
                    bool playerMuted = false;
                    _mutedPlayers.TryGetValue(p.UserIDString, out playerMuted);
                    if (!playerMuted && !p.HasPlayerFlag(BasePlayer.PlayerFlags.ChatMute)) {
                        p.ChatMessage(message);
                    }
                }
                Puts($"Message broadcast to all non-muted players: {message}");
            }
            catch (Exception ex) {
                PrintError($"Error sending chat message: {ex.Message}");
            }
        }
        private List<ConversationEntry> GetPlayerConversationHistory(BasePlayer player) {
            var conversations = LoadConversations(player.UserIDString);
            return conversations .OrderByDescending(e => e.Timestamp) .Take(_config.ConversationMemorySize) .OrderBy(e => e.Timestamp) .ToList();
        }
        private string FormatConversationHistoryForPrompt(List<ConversationEntry> history, string playerName) {
            if (history == null || history.Count == 0) return "";
            var sb = new StringBuilder();
            sb.AppendLine($"RECENT CONVERSATION HISTORY WITH {playerName}:");
            foreach (var entry in history) {
                sb.AppendLine($"{entry.PlayerName}: {entry.Question}");
                sb.AppendLine($"{_config.Character}: {entry.Response}");
                sb.AppendLine();
            }
            return sb.ToString();
        }
        private string GetPlayerContextInfo(BasePlayer player) {
            var sb = new StringBuilder();
            sb.AppendLine("ASKING PLAYER'S CURRENT STATE:");
            try {
                float healthPercent = (player.health / player.MaxHealth()) * 100f;
                string healthStatus = healthPercent > 75 ? "healthy" : healthPercent > 40 ? "injured" : healthPercent > 15 ? "critical" : "almost dead";
                sb.AppendLine($"- Health: {player.health:F0}/{player.MaxHealth():F0} HP ({healthStatus})");
                if (player.IsWounded()) sb.AppendLine("- Status: WOUNDED/DOWNED (on the ground)");
                else if (player.IsSleeping()) sb.AppendLine("- Status: Sleeping");
                if (player.currentTeam != 0) {
                    var team = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
                    if (team != null) sb.AppendLine($"- Team: In a team of {team.members.Count} players");
                }
                else {
                    sb.AppendLine("- Team: Solo (no team)");
                }
                var activeItem = player.GetActiveItem();
                if (activeItem != null) sb.AppendLine($"- Holding: {activeItem.info.displayName.english}");
                else sb.AppendLine("- Holding: Nothing (bare hands)");
                if (_config.SharePlayerLocation) {
                    var pos = player.transform.position;
                    string location = GetApproximateLocation(pos);
                    sb.AppendLine($"- Location: {location}");
                }
                if (player.IsBuildingBlocked()) sb.AppendLine("- Zone: Outside another player's base (building blocked - could be raiding, door camping, or scouting)");
                float calories = player.metabolism?.calories?.value ?? 0;
                float hydration = player.metabolism?.hydration?.value ?? 0;
                string hungerStatus = null;
                string thirstStatus = null;
                if (calories < 50) hungerStatus = "starving";
                else if (calories < 100) hungerStatus = "hungry";
                if (hydration < 25) thirstStatus = "dehydrated";
                else if (hydration < 50) thirstStatus = "thirsty";
                if (hungerStatus != null || thirstStatus != null) {
                    var statusParts = new List<string>();
                    if (hungerStatus != null) statusParts.Add(hungerStatus);
                    if (thirstStatus != null) statusParts.Add(thirstStatus);
                    sb.AppendLine($"- Needs: {string.Join(", ", statusParts)}");
                }
                float comfort = player.currentComfort;
                float temp = player.currentTemperature;
                if (comfort > 0.5f) sb.AppendLine("- Comfort: Cozy and warm");
                else if (temp < 5) sb.AppendLine("- Temperature: Freezing cold");
                else if (temp > 40) sb.AppendLine("- Temperature: Overheating");
                if (player.lifeStory != null && player.lifeStory.secondsAlive > 60) {
                    int minutesAlive = (i
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху