playerbot chat ai system

This commit is contained in:
luis
2026-05-08 14:06:08 -03:00
parent ecbdc55de7
commit 46f704784b
7 changed files with 958 additions and 0 deletions
+4
View File
@@ -83,6 +83,10 @@ set(PLAYERBOT_CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Chat/BotChatCommandHandler.h
${CMAKE_CURRENT_SOURCE_DIR}/Chat/ChatTemplateManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Chat/ChatTemplateManager.h
${CMAKE_CURRENT_SOURCE_DIR}/Chat/AIChatService.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Chat/AIChatService.h
${CMAKE_CURRENT_SOURCE_DIR}/Chat/BotChatManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Chat/BotChatManager.h
# Core hooks and helpers
${CMAKE_CURRENT_SOURCE_DIR}/Core/PlayerBotHooks.cpp
@@ -0,0 +1,352 @@
/*
* Copyright (C) 2024 TrinityCore <https://www.trinitycore.org/>
*
* AI CHAT SERVICE - IMPLEMENTATION
*/
#include "AIChatService.h"
#include "Player.h"
#include "Config/PlayerbotConfig.h"
#include "Log.h"
#include <sstream>
#include <random>
#include <chrono>
#include <iomanip>
#include <fstream>
namespace Playerbot
{
AIChatService* AIChatService::instance()
{
static AIChatService instance;
return &instance;
}
AIChatService::AIChatService()
{
}
void AIChatService::Initialize()
{
::std::lock_guard lock(_initMutex);
if (_initialized.load(::std::memory_order_acquire))
{
TC_LOG_WARN("playerbot.chat", "AIChatService: Already initialized");
return;
}
LoadConfig();
if (!_config.enabled)
{
TC_LOG_INFO("playerbot.chat", "AIChatService: Disabled via config");
_initialized.store(true, ::std::memory_order_release);
return;
}
TC_LOG_INFO("playerbot.chat", "AIChatService: Initialized with API URL: {}, Model: {}",
_config.apiUrl, _config.model);
_initialized.store(true, ::std::memory_order_release);
}
void AIChatService::LoadConfig()
{
_config.apiUrl = sPlayerbotConfig->GetString("Playerbot.AIChat.ApiUrl", "http://localhost:11434/api/chat"); // Default to Ollama
_config.apiKey = sPlayerbotConfig->GetString("Playerbot.AIChat.ApiKey", "");
_config.model = sPlayerbotConfig->GetString("Playerbot.AIChat.Model", "llama3-8b-8192");
_config.maxTokens = sPlayerbotConfig->GetInt("Playerbot.AIChat.MaxTokens", 150);
_config.temperature = sPlayerbotConfig->GetFloat("Playerbot.AIChat.Temperature", 0.7f);
_config.enabled = sPlayerbotConfig->GetBool("Playerbot.AIChat.Enable", false);
_config.contextLimit = sPlayerbotConfig->GetInt("Playerbot.AIChat.ContextLimit", 10);
_config.conversationTimeout = sPlayerbotConfig->GetInt("Playerbot.AIChat.ConversationTimeout", 300);
TC_LOG_DEBUG("playerbot.chat", "AIChatService: Config loaded - Enable={}, Url={}, Model={}, MaxTokens={}, Temperature={:.2f}",
_config.enabled, _config.apiUrl, _config.model, _config.maxTokens, _config.temperature);
}
void AIChatService::ReloadConfig()
{
LoadConfig();
TC_LOG_INFO("playerbot.chat", "AIChatService: Configuration reloaded");
}
std::string AIChatService::GenerateResponse(ObjectGuid botGuid, ObjectGuid playerGuid,
std::string const& playerMessage, bool isWhisper)
{
if (!_initialized.load(::std::memory_order_acquire))
{
TC_LOG_ERROR("playerbot.chat", "AIChatService: Not initialized");
return "";
}
if (!_config.enabled)
{
TC_LOG_DEBUG("playerbot.chat", "AIChatService: Disabled, skipping AI response");
return "";
}
if (playerMessage.empty())
{
TC_LOG_DEBUG("playerbot.chat", "AIChatService: Empty player message");
return "";
}
// Get or create conversation context
ConversationContext* context = GetConversation(botGuid, playerGuid);
if (!context)
{
TC_LOG_ERROR("playerbot.chat", "AIChatService: Failed to get/create conversation context");
return "";
}
// Add player message to context
ChatMessage playerMsg;
playerMsg.content = playerMessage;
playerMsg.isFromPlayer = true;
playerMsg.timestamp = ::std::chrono::system_clock::to_time_t(::std::chrono::system_clock::now());
context->messages.push_back(playerMsg);
context->lastActivityTime = playerMsg.timestamp;
// Trim conversation history if too long
TrimConversationHistory(*context);
// Generate AI response
std::string response = CallAIAPI(*context, playerMessage);
if (!response.empty())
{
// Add bot response to context
ChatMessage botMsg;
botMsg.content = response;
botMsg.isFromPlayer = false;
botMsg.timestamp = ::std::chrono::system_clock::to_time_t(::std::chrono::system_clock::now());
context->messages.push_back(botMsg);
context->lastActivityTime = botMsg.timestamp;
TC_LOG_DEBUG("playerbot.chat", "AIChatService: Bot {} generated response for player {}: '{}'",
context->botName, context->playerName, response);
}
else
{
TC_LOG_WARN("playerbot.chat", "AIChatService: Failed to generate response for player {} from bot {}",
context->playerName, context->botName);
}
return response;
}
ConversationContext* AIChatService::GetConversation(ObjectGuid botGuid, ObjectGuid playerGuid)
{
::std::lock_guard lock(_conversationMutex);
auto& botConversations = _conversations[botGuid];
auto it = botConversations.find(playerGuid);
if (it == botConversations.end())
{
// Create new conversation context
ConversationContext context;
context.playerGuid = playerGuid;
context.botGuid = botGuid;
context.lastActivityTime = ::std::chrono::system_clock::to_time_t(::std::chrono::system_clock::now());
// Try to get player and bot names (this will be filled in by the caller)
context.playerName = "Player";
context.botName = "Bot";
context.botClass = 0;
context.botLevel = 0;
botConversations[playerGuid] = context;
return &botConversations[playerGuid];
}
return &it->second;
}
void AIChatService::CleanupOldConversations()
{
::std::lock_guard lock(_conversationMutex);
uint32 currentTime = ::std::chrono::system_clock::to_time_t(::std::chrono::system_clock::now());
uint32 cleaned = 0;
for (auto& botPair : _conversations)
{
auto it = botPair.second.begin();
while (it != botPair.second.end())
{
if (currentTime - it->second.lastActivityTime > _config.conversationTimeout)
{
it = botPair.second.erase(it);
++cleaned;
}
else
{
++it;
}
}
}
if (cleaned > 0)
{
TC_LOG_DEBUG("playerbot.chat", "AIChatService: Cleaned up {} old conversations", cleaned);
}
}
void AIChatService::TrimConversationHistory(ConversationContext& context)
{
if (context.messages.size() > _config.contextLimit)
{
// Remove oldest messages, keeping the contextLimit most recent
size_t toRemove = context.messages.size() - _config.contextLimit;
context.messages.erase(context.messages.begin(), context.messages.begin() + toRemove);
}
}
std::string AIChatService::BuildSystemPrompt(ConversationContext const& context)
{
::std::ostringstream prompt;
prompt << "You are a World of Warcraft bot character named " << context.botName << ".\n";
prompt << "You are a level " << context.botLevel << " ";
// Add class-specific personality
switch (context.botClass)
{
case CLASS_WARRIOR:
prompt << "Warrior. You are brave and honorable, often speaking about battle, honor, and strength.";
break;
case CLASS_PALADIN:
prompt << "Paladin. You are righteous and devout, often speaking about the Light, justice, and protecting the innocent.";
break;
case CLASS_HUNTER:
prompt << "Hunter. You love nature and animals, often speaking about your pet, tracking, and the wild.";
break;
case CLASS_ROGUE:
prompt << "Rogue. You are stealthy and cunning, often speaking about shadows, secrets, and quick strikes.";
break;
case CLASS_PRIEST:
prompt << "Priest. You are spiritual and wise, often speaking about the Light, healing, and faith.";
break;
case CLASS_DEATH_KNIGHT:
prompt << "Death Knight. You are dark and brooding, often speaking about death, the Scourge, and your dark powers.";
break;
case CLASS_SHAMAN:
prompt << "Shaman. You are connected to the elements, often speaking about spirits, nature, and elemental balance.";
break;
case CLASS_MAGE:
prompt << "Mage. You are intelligent and studious, often speaking about magic, arcane knowledge, and research.";
break;
case CLASS_WARLOCK:
prompt << "Warlock. You are mysterious and powerful, often speaking about demons, dark magic, and forbidden knowledge.";
break;
case CLASS_MONK:
prompt << "Monk. You are disciplined and peaceful, often speaking about balance, meditation, and martial arts.";
break;
case CLASS_DRUID:
prompt << "Druid. You are one with nature, often speaking about the Emerald Dream, shapeshifting, and protecting nature.";
break;
case CLASS_DEMON_HUNTER:
prompt << "Demon Hunter. You are fierce and determined, often speaking about hunting demons and sacrificing for power.";
break;
case CLASS_EVOKER:
prompt << "Evoker. You are draconic and powerful, often speaking about dragons, magic, and the Dragon Isles.";
break;
default:
prompt << "adventurer. You are exploring the world of Azeroth.";
break;
}
prompt << "\n\nKeep your responses brief (1-2 sentences), in character, and relevant to the conversation context.";
prompt << " Do not break character or mention that you are an AI.";
prompt << " Respond as if you are actually this character in the game world.";
return prompt.str();
}
std::string AIChatService::BuildConversationHistory(ConversationContext const& context)
{
::std::ostringstream history;
for (auto const& msg : context.messages)
{
if (msg.isFromPlayer)
history << context.playerName << ": " << msg.content << "\n";
else
history << context.botName << ": " << msg.content << "\n";
}
return history.str();
}
std::string AIChatService::CallAIAPI(ConversationContext const& context, std::string const& playerMessage)
{
// Build JSON payload for API call
::std::ostringstream jsonPayload;
jsonPayload << "{\n";
jsonPayload << " \"model\": \"" << _config.model << "\",\n";
jsonPayload << " \"messages\": [\n";
jsonPayload << " {\"role\": \"system\", \"content\": \"" << BuildSystemPrompt(context) << "\"},\n";
// Add conversation history
for (auto const& msg : context.messages)
{
if (msg.isFromPlayer)
jsonPayload << " {\"role\": \"user\", \"content\": \"" << msg.content << "\"},\n";
else
jsonPayload << " {\"role\": \"assistant\", \"content\": \"" << msg.content << "\"},\n";
}
jsonPayload << " {\"role\": \"user\", \"content\": \"" << playerMessage << "\"}\n";
jsonPayload << " ],\n";
jsonPayload << " \"max_tokens\": " << _config.maxTokens << ",\n";
jsonPayload << " \"temperature\": " << _config.temperature << "\n";
jsonPayload << "}";
// Send HTTP request
std::string response = SendHTTPRequest(_config.apiUrl, jsonPayload.str());
if (response.empty())
{
TC_LOG_ERROR("playerbot.chat", "AIChatService: Empty response from API");
return "";
}
// Parse JSON response to extract the AI's message
// For now, simple parsing - in production, use a proper JSON library
size_t contentPos = response.find("\"content\": \"");
if (contentPos != ::std::string::npos)
{
contentPos += 11; // Skip "content": "
size_t endPos = response.find("\"", contentPos);
if (endPos != ::std::string::npos)
{
std::string content = response.substr(contentPos, endPos - contentPos);
// Clean up escaped characters
size_t pos = 0;
while ((pos = content.find("\\n", pos)) != ::std::string::npos)
{
content.replace(pos, 2, "\n");
pos += 1;
}
return content;
}
}
TC_LOG_ERROR("playerbot.chat", "AIChatService: Failed to parse API response: {}", response);
return "";
}
std::string AIChatService::SendHTTPRequest(std::string const& url, std::string const& jsonPayload)
{
// TODO: Implement HTTP request using TrinityCore's HTTP infrastructure
// For now, return empty string to indicate failure
TC_LOG_ERROR("playerbot.chat", "AIChatService: HTTP requests not yet implemented - please configure TrinityCore HTTP client");
return "";
}
} // namespace Playerbot
+160
View File
@@ -0,0 +1,160 @@
/*
* Copyright (C) 2024 TrinityCore <https://www.trinitycore.org/>
*
* AI CHAT SERVICE - Free AI Integration for Bot Conversations
*
* Purpose: Provides AI-powered chat responses for bots using free AI APIs
* Supports: Groq, Ollama (local), and other OpenAI-compatible APIs
*/
#pragma once
#include "Define.h"
#include "ObjectGuid.h"
#include "SharedDefines.h"
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <mutex>
namespace Playerbot
{
/**
* @brief Single message in a conversation
*/
struct ChatMessage
{
std::string content;
bool isFromPlayer; // true = player, false = bot
uint32 timestamp;
};
/**
* @brief Conversation context for a bot with a player
*/
struct ConversationContext
{
ObjectGuid playerGuid;
ObjectGuid botGuid;
std::vector<ChatMessage> messages;
uint32 lastActivityTime;
std::string playerName;
std::string botName;
uint8 botClass;
uint32 botLevel;
};
/**
* @brief AI service configuration
*/
struct AIChatConfig
{
std::string apiUrl; // API endpoint (e.g., "https://api.groq.com/openai/v1/chat/completions")
std::string apiKey; // API key (empty for local Ollama)
std::string model; // Model name (e.g., "llama3-8b-8192", "mixtral-8x7b-32768")
uint32 maxTokens; // Maximum tokens in response
float temperature; // Response randomness (0.0-1.0)
bool enabled; // Master enable/disable
uint32 contextLimit; // Max messages to include in context
uint32 conversationTimeout; // Seconds before conversation expires
};
/**
* @brief AI-powered chat service for bots
*
* Integrates with free AI APIs to generate contextual responses
* Supports conversation history and context awareness
*/
class TC_GAME_API AIChatService
{
public:
static AIChatService* instance();
/**
* Initialize the AI chat service
*/
void Initialize();
/**
* Generate a response to a player message
*
* @param botGuid Bot's GUID
* @param playerGuid Player's GUID
* @param playerMessage The message from the player
* @param isWhisper Whether this is a whisper (vs public chat)
* @return Generated response, or empty string on failure
*/
std::string GenerateResponse(ObjectGuid botGuid, ObjectGuid playerGuid,
std::string const& playerMessage, bool isWhisper);
/**
* Get conversation context for a bot-player pair
*/
ConversationContext* GetConversation(ObjectGuid botGuid, ObjectGuid playerGuid);
/**
* Clear old conversations (cleanup)
*/
void CleanupOldConversations();
/**
* Check if AI chat is enabled
*/
bool IsEnabled() const { return _config.enabled; }
/**
* Reload configuration
*/
void ReloadConfig();
private:
AIChatService();
~AIChatService() = default;
AIChatService(AIChatService const&) = delete;
AIChatService& operator=(AIChatService const&) = delete;
/**
* Load configuration from config file
*/
void LoadConfig();
/**
* Call AI API to generate response
*/
std::string CallAIAPI(ConversationContext const& context, std::string const& playerMessage);
/**
* Build system prompt for the bot
*/
std::string BuildSystemPrompt(ConversationContext const& context);
/**
* Build conversation history for API call
*/
std::string BuildConversationHistory(ConversationContext const& context);
/**
* Clean old messages from conversation context
*/
void TrimConversationHistory(ConversationContext& context);
/**
* HTTP request helper
*/
std::string SendHTTPRequest(std::string const& url, std::string const& jsonPayload);
// Configuration
AIChatConfig _config;
// Conversation storage (botGuid -> playerGuid -> context)
::std::unordered_map<ObjectGuid, ::std::unordered_map<ObjectGuid, ConversationContext>> _conversations;
::std::mutex _conversationMutex;
// Initialization state
::std::atomic<bool> _initialized{false};
::std::mutex _initMutex;
};
} // namespace Playerbot
@@ -0,0 +1,235 @@
/*
* Copyright (C) 2024 TrinityCore <https://www.trinitycore.org/>
*
* BOT CHAT MANAGER - IMPLEMENTATION
*/
#include "BotChatManager.h"
#include "AIChatService.h"
#include "Player.h"
#include "World.h"
#include "Log.h"
#include "ObjectAccessor.h"
#include <random>
namespace Playerbot
{
BotChatManager* BotChatManager::instance()
{
static BotChatManager instance;
return &instance;
}
BotChatManager::BotChatManager()
: _aiService(nullptr)
{
}
void BotChatManager::Initialize()
{
::std::lock_guard lock(_initMutex);
if (_initialized.load(::std::memory_order_acquire))
{
TC_LOG_WARN("playerbot.chat", "BotChatManager: Already initialized");
return;
}
// Initialize AI chat service
_aiService = AIChatService::instance();
_aiService->Initialize();
if (!_aiService->IsEnabled())
{
TC_LOG_INFO("playerbot.chat", "BotChatManager: AI chat disabled, using fallback responses");
}
else
{
TC_LOG_INFO("playerbot.chat", "BotChatManager: Initialized with AI chat enabled");
}
_initialized.store(true, ::std::memory_order_release);
}
void BotChatManager::HandleIncomingMessage(ObjectGuid playerGuid, ObjectGuid botGuid,
std::string const& message, bool isWhisper, uint8 chatType)
{
if (!_initialized.load(::std::memory_order_acquire))
{
TC_LOG_ERROR("playerbot.chat", "BotChatManager: Not initialized");
return;
}
// Validate inputs
if (playerGuid.IsEmpty() || botGuid.IsEmpty())
{
TC_LOG_DEBUG("playerbot.chat", "BotChatManager: Invalid GUID(s) - player: {}, bot: {}",
playerGuid.ToString(), botGuid.ToString());
return;
}
if (message.empty())
{
TC_LOG_DEBUG("playerbot.chat", "BotChatManager: Empty message");
return;
}
// Get player and bot
Player* player = GetPlayer(playerGuid);
Player* bot = GetBot(botGuid);
if (!player || !bot)
{
TC_LOG_DEBUG("playerbot.chat", "BotChatManager: Player or bot not found - player: {}, bot: {}",
player ? "found" : "not found", bot ? "found" : "not found");
return;
}
// Update conversation context with player/bot info
UpdateConversationContext(botGuid, playerGuid);
// Generate and send AI response
SendAIResponse(botGuid, playerGuid, message, isWhisper);
}
bool BotChatManager::SendAIResponse(ObjectGuid botGuid, ObjectGuid playerGuid,
std::string const& playerMessage, bool isWhisper)
{
if (!_aiService || !_aiService->IsEnabled())
{
// Fallback: simple pre-defined responses if AI is disabled
return SendFallbackResponse(botGuid, playerGuid, isWhisper);
}
// Generate AI response
std::string response = _aiService->GenerateResponse(botGuid, playerGuid, playerMessage, isWhisper);
if (response.empty())
{
TC_LOG_WARN("playerbot.chat", "BotChatManager: AI returned empty response, using fallback");
return SendFallbackResponse(botGuid, playerGuid, isWhisper);
}
// Send the response
Player* bot = GetBot(botGuid);
Player* player = GetPlayer(playerGuid);
if (!bot || !player)
{
TC_LOG_ERROR("playerbot.chat", "BotChatManager: Failed to get player or bot for response");
return false;
}
SendBotMessage(bot, player, response, isWhisper);
return true;
}
bool BotChatManager::SendFallbackResponse(ObjectGuid botGuid, ObjectGuid playerGuid, bool isWhisper)
{
Player* bot = GetBot(botGuid);
Player* player = GetPlayer(playerGuid);
if (!bot || !player)
{
return false;
}
// Simple fallback responses
static const char* fallbackResponses[] = {
"Greetings, adventurer!",
"Well met, friend.",
"Light be with you!",
"For the Alliance!",
"Lok'tar Ogar!",
"May the shadows guide you.",
"Nature's blessing upon you.",
"By the Light!",
"For the Horde!",
"Strength and honor!"
};
// Select random response
static ::std::random_device rd;
static ::std::mt19937 gen(rd());
::std::uniform_int_distribution<> dis(0, sizeof(fallbackResponses) / sizeof(fallbackResponses[0]) - 1);
std::string response = fallbackResponses[dis(gen)];
SendBotMessage(bot, player, response, isWhisper);
return true;
}
bool BotChatManager::IsEnabled() const
{
return _aiService && _aiService->IsEnabled();
}
void BotChatManager::Cleanup()
{
if (_aiService)
{
_aiService->CleanupOldConversations();
}
}
::Player* BotChatManager::GetPlayer(ObjectGuid guid)
{
return ObjectAccessor::FindConnectedPlayer(guid);
}
::Player* BotChatManager::GetBot(ObjectGuid guid)
{
return ObjectAccessor::FindConnectedPlayer(guid);
}
void BotChatManager::SendBotMessage(::Player* bot, ::Player* target, std::string const& message, bool isWhisper)
{
if (!bot || !target)
{
return;
}
if (isWhisper)
{
bot->Whisper(message.c_str(), LANG_UNIVERSAL, target);
TC_LOG_DEBUG("playerbot.chat", "Bot {} whispered to {}: '{}'",
bot->GetName(), target->GetName(), message);
}
else
{
bot->Say(message.c_str(), LANG_UNIVERSAL);
TC_LOG_DEBUG("playerbot.chat", "Bot {} said: '{}'", bot->GetName(), message);
}
}
void BotChatManager::UpdateConversationContext(ObjectGuid botGuid, ObjectGuid playerGuid)
{
if (!_aiService)
{
return;
}
ConversationContext* context = _aiService->GetConversation(botGuid, playerGuid);
if (!context)
{
return;
}
// Update player and bot info
Player* bot = GetBot(botGuid);
Player* player = GetPlayer(playerGuid);
if (bot)
{
context->botName = bot->GetName();
context->botClass = bot->GetClass();
context->botLevel = bot->GetLevel();
}
if (player)
{
context->playerName = player->GetName();
}
}
} // namespace Playerbot
+128
View File
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2024 TrinityCore <https://www.trinitycore.org/>
*
* BOT CHAT MANAGER - Manages Bot Conversations and AI Responses
*
* Purpose: Handles incoming player messages and generates AI-powered responses
* Integrates with the existing action system for seamless chat/whisper functionality
*/
#pragma once
#include "Define.h"
#include "ObjectGuid.h"
#include "SharedDefines.h"
#include <string>
#include <unordered_map>
#include <memory>
#include <mutex>
class Player;
namespace Playerbot
{
class AIChatService;
/**
* @brief Chat message from a player to a bot
*/
struct IncomingChatMessage
{
ObjectGuid playerGuid;
ObjectGuid botGuid;
std::string message;
bool isWhisper;
uint8 chatType; // CHAT_MSG_SAY, CHAT_MSG_WHISPER, etc.
};
/**
* @brief Manages bot conversations and AI responses
*
* This class acts as the bridge between the game's chat system
* and the AI chat service, handling message routing and response delivery.
*/
class TC_GAME_API BotChatManager
{
public:
static BotChatManager* instance();
/**
* Initialize the chat manager
*/
void Initialize();
/**
* Handle incoming chat message from player to bot
*
* @param playerGuid GUID of the player who sent the message
* @param botGuid GUID of the bot receiving the message
* @param message The message content
* @param isWhisper Whether this is a whisper
* @param chatType The chat type (CHAT_MSG_SAY, CHAT_MSG_WHISPER, etc.)
*/
void HandleIncomingMessage(ObjectGuid playerGuid, ObjectGuid botGuid,
std::string const& message, bool isWhisper, uint8 chatType);
/**
* Generate and send AI response to player
*
* @param botGuid GUID of the bot
* @param playerGuid GUID of the player
* @param playerMessage The message from the player
* @param isWhisper Whether to whisper the response
* @return true if response was sent successfully
*/
bool SendAIResponse(ObjectGuid botGuid, ObjectGuid playerGuid,
std::string const& playerMessage, bool isWhisper);
/**
* Check if AI chat is enabled
*/
bool IsEnabled() const;
/**
* Cleanup old conversations
*/
void Cleanup();
private:
BotChatManager();
~BotChatManager() = default;
BotChatManager(BotChatManager const&) = delete;
BotChatManager& operator=(BotChatManager const&) = delete;
/**
* Get player by GUID
*/
::Player* GetPlayer(ObjectGuid guid);
/**
* Get bot by GUID
*/
::Player* GetBot(ObjectGuid guid);
/**
* Send chat message from bot
*/
void SendBotMessage(::Player* bot, ::Player* target, std::string const& message, bool isWhisper);
/**
* Send fallback response when AI is unavailable
*/
bool SendFallbackResponse(ObjectGuid botGuid, ObjectGuid playerGuid, bool isWhisper);
/**
* Update conversation context with player/bot info
*/
void UpdateConversationContext(ObjectGuid botGuid, ObjectGuid playerGuid);
// AI chat service
AIChatService* _aiService;
// Initialization state
::std::atomic<bool> _initialized{false};
::std::mutex _initMutex;
};
} // namespace Playerbot
@@ -47,6 +47,7 @@
#include "Session/BotWorldSessionMgr.h"
#include "Session/BotPacketRelay.h"
#include "Chat/BotChatCommandHandler.h"
#include "Chat/BotChatManager.h"
#include "Professions/ProfessionManager.h"
#include "Professions/ProfessionDatabase.h"
#include "Professions/ProfessionAuctionBridge.h"
@@ -180,6 +181,11 @@ bool PlayerbotModule::Initialize()
// Initialize BG Scripts (forces linker to include script object files)
Playerbot::Coordination::Battleground::InitializeBGScripts();
// Initialize AI Chat System
TC_LOG_INFO("module.playerbot", "Initializing AI Chat System...");
Playerbot::BotChatManager::instance()->Initialize();
TC_LOG_INFO("module.playerbot", "AI Chat System initialized");
// ==========================================================================
// NOTE: Do NOT register with ModuleUpdateManager here!
@@ -1796,6 +1796,79 @@ Playerbot.AI.MaxTargetDistance = 50.0
#
Playerbot.AI.PullDistance = 30.0
###############################################################################
# A11. AI CHAT SYSTEM
#
# AI-powered bot conversations using free AI APIs
# Supports: Ollama (local), Groq, and other OpenAI-compatible APIs
#
###############################################################################
#
# Playerbot.AIChat.Enable
# Description: Enable AI-powered chat responses for bots
# Default: 0 (disabled - requires API setup)
# Values: 0 = Disabled (uses fallback responses), 1 = Enabled
#
Playerbot.AIChat.Enable = 0
#
# Playerbot.AIChat.ApiUrl
# Description: API endpoint for AI service
# Default: "http://localhost:11434/api/chat" (Ollama local)
# Examples: "https://api.groq.com/openai/v1/chat/completions" (Groq)
# "http://localhost:11434/api/chat" (Ollama)
#
Playerbot.AIChat.ApiUrl = http://localhost:11434/api/chat
#
# Playerbot.AIChat.ApiKey
# Description: API key for cloud services (empty for local Ollama)
# Default: "" (no key)
# Note: Required for Groq, OpenAI, etc. Not needed for local Ollama
#
Playerbot.AIChat.ApiKey =
#
# Playerbot.AIChat.Model
# Description: AI model to use for chat generation
# Default: "llama3-8b-8192" (Llama 3 8B via Ollama)
# Examples: "llama3-8b-8192", "mixtral-8x7b-32768", "gemma-7b-it"
#
Playerbot.AIChat.Model = llama3-8b-8192
#
# Playerbot.AIChat.MaxTokens
# Description: Maximum tokens in AI response (controls response length)
# Default: 150
# Range: 50-500
#
Playerbot.AIChat.MaxTokens = 150
#
# Playerbot.AIChat.Temperature
# Description: Response randomness (0.0 = deterministic, 1.0 = creative)
# Default: 0.7
# Range: 0.0-1.0
#
Playerbot.AIChat.Temperature = 0.7
#
# Playerbot.AIChat.ContextLimit
# Description: Maximum messages to include in conversation context
# Default: 10
# Range: 5-20
#
Playerbot.AIChat.ContextLimit = 10
#
# Playerbot.AIChat.ConversationTimeout
# Description: Seconds before conversation context is expired
# Default: 300 (5 minutes)
# Range: 60-3600
#
Playerbot.AIChat.ConversationTimeout = 300
###############################################################################
# B19. AI DECISION FUSION [WIRED]
#