diff --git a/src/mod-ollama-chat_events.cpp b/src/mod-ollama-chat_events.cpp index a1eb690d7..41240d848 100644 --- a/src/mod-ollama-chat_events.cpp +++ b/src/mod-ollama-chat_events.cpp @@ -10,6 +10,7 @@ #include "Guild.h" #include "PlayerbotAI.h" #include "PlayerbotMgr.h" +#include "RandomPlayerbotMgr.h" #include "ChannelMgr.h" #include "Channel.h" #include "AiFactory.h" @@ -26,22 +27,25 @@ static OllamaBotEventChatter eventChatter; static std::unordered_map botEventCooldowns; -void OllamaBotEventChatter::DispatchGameEvent(Player* source, std::string type, std::string detail) +void OllamaBotEventChatter::DispatchGameEvent(Player* source, std::string type, std::string detail, Guild* guildOverride) { if (!g_Enable || !g_EnableEventChatter) return; - + if (!source) { return; } + // Guild hooks pass the guild explicitly; everyone else derives it from the source. + Guild* eventGuild = guildOverride ? guildOverride : source->GetGuild(); + bool isSourceBot = PlayerbotsMgr::instance().GetPlayerbotAI(source) != nullptr; bool hasNearbyRealPlayer = false; bool isGuildEvent = false; // Only set isGuildEvent for specific event types - if (source->GetGuild() && g_EnableGuildEventChatter) { + if (eventGuild && g_EnableGuildEventChatter) { // List of event types that are considered guild events if ( type == g_GuildEventTypeGuildJoin || @@ -56,7 +60,7 @@ void OllamaBotEventChatter::DispatchGameEvent(Player* source, std::string type, type == g_GuildEventTypeGuildLogin ) { // Check if there are real players in the guild - Guild* guild = source->GetGuild(); + Guild* guild = eventGuild; for (auto const& pair : ObjectAccessor::GetPlayers()) { Player* player = pair.second; if (!player || !player->IsInWorld()) @@ -105,7 +109,7 @@ void OllamaBotEventChatter::DispatchGameEvent(Player* source, std::string type, if (isGuildEvent) { // For guild events, get guild bots - Guild* guild = source->GetGuild(); + Guild* guild = eventGuild; for (auto const& pair : ObjectAccessor::GetPlayers()) { Player* player = pair.second; @@ -663,53 +667,67 @@ void ChatOnGameObjectUse::OnGameObjectUse(Player* player, GameObject* go) eventChatter.DispatchGameEvent(player, g_EventTypeUsedObject, go->GetGOInfo()->name); } -ChatOnGuildMemberChange::ChatOnGuildMemberChange() : PlayerScript("ChatOnGuildMemberChange") {} +ChatOnGuildEvent::ChatOnGuildEvent() : GuildScript("ChatOnGuildEvent") {} -void ChatOnGuildMemberChange::OnGuildMemberJoin(Player* player, Guild* /*guild*/) +void ChatOnGuildEvent::OnAddMember(Guild* guild, Player* player, uint8& /*plRank*/) { - if (!player || !player->GetGuild() || !g_EnableGuildEventChatter) + if (!player || !guild || !g_EnableGuildEventChatter) return; - + if (!g_GuildEventTypeGuildJoin.empty()) - eventChatter.DispatchGameEvent(player, g_GuildEventTypeGuildJoin, player->GetGuild()->GetName()); + eventChatter.DispatchGameEvent(player, g_GuildEventTypeGuildJoin, guild->GetName(), guild); } -void ChatOnGuildMemberChange::OnGuildMemberLeave(Player* player, Guild* guild) +void ChatOnGuildEvent::OnRemoveMember(Guild* guild, Player* player, bool /*isDisbanding*/, bool /*isKicked*/) { if (!player || !guild || !g_EnableGuildEventChatter) return; - + + // player->GetGuild() may already be cleared at this point, so pass the guild explicitly. if (!g_GuildEventTypeGuildLeave.empty()) - eventChatter.DispatchGameEvent(player, g_GuildEventTypeGuildLeave, guild->GetName()); + eventChatter.DispatchGameEvent(player, g_GuildEventTypeGuildLeave, guild->GetName(), guild); } -void ChatOnGuildMemberChange::OnGuildMemberRankChange(Player* player, Guild* /*guild*/, uint8 /*oldRank*/, uint8 newRank) +void ChatOnGuildEvent::OnEvent(Guild* guild, uint8 eventType, ObjectGuid::LowType /*playerGuid1*/, + ObjectGuid::LowType playerGuid2, uint8 /*newRank*/) { - if (!player || !player->GetGuild() || !g_EnableGuildEventChatter) + if (!guild || !g_EnableGuildEventChatter) return; - - if (newRank < player->GetGuild()->GetMember(player->GetGUID())->GetRankId()) - { - // Promotion - if (!g_GuildEventTypeGuildPromotion.empty()) - eventChatter.DispatchGameEvent(player, g_GuildEventTypeGuildPromotion, std::to_string(newRank)); - } - else - { - // Demotion - if (!g_GuildEventTypeGuildDemotion.empty()) - eventChatter.DispatchGameEvent(player, g_GuildEventTypeGuildDemotion, std::to_string(newRank)); - } + + // Only promote/demote are handled here; join/leave come from OnAddMember/OnRemoveMember. + bool isPromotion = (eventType == GUILD_EVENT_LOG_PROMOTE_PLAYER); + bool isDemotion = (eventType == GUILD_EVENT_LOG_DEMOTE_PLAYER); + if (!isPromotion && !isDemotion) + return; + + // For promote/demote, playerGuid2 is the affected member. + Player* target = ObjectAccessor::FindPlayer(ObjectGuid::Create(playerGuid2)); + if (!target) + return; + + std::string const& eventName = isPromotion ? g_GuildEventTypeGuildPromotion : g_GuildEventTypeGuildDemotion; + if (eventName.empty()) + return; + + eventChatter.DispatchGameEvent(target, eventName, guild->GetName(), guild); } -// Add new event for non-bot guild member login -void ChatOnGuildMemberChange::OnGuildMemberLogin(Player* player, Guild* guild) +ChatOnLogin::ChatOnLogin() : PlayerScript("ChatOnLogin") {} + +void ChatOnLogin::OnPlayerLogin(Player* player) { - if (!player || !guild || !g_EnableGuildEventChatter) + if (!player || !g_EnableGuildEventChatter) + return; + // Only real players trigger guild-login chatter. GetPlayerbotAI() is unreliable here because + // mod-playerbots attaches the bot AI in its own OnPlayerLogin, which can run after this hook; + // sRandomPlayerbotMgr.IsRandomBot() reads a registry populated before any bot logs in, so it + // catches the mass bot login at server startup that would otherwise flood the event queue. + if (PlayerbotsMgr::instance().GetPlayerbotAI(player) || sRandomPlayerbotMgr.IsRandomBot(player)) + return; + Guild* guild = player->GetGuild(); + if (!guild) return; - if (PlayerbotsMgr::instance().GetPlayerbotAI(player)) - return; // Only real players if (!g_GuildEventTypeGuildLogin.empty()) - eventChatter.DispatchGameEvent(player, g_GuildEventTypeGuildLogin, guild->GetName()); + eventChatter.DispatchGameEvent(player, g_GuildEventTypeGuildLogin, guild->GetName(), guild); } diff --git a/src/mod-ollama-chat_events.h b/src/mod-ollama-chat_events.h index 3387db3dc..be7e43b17 100644 --- a/src/mod-ollama-chat_events.h +++ b/src/mod-ollama-chat_events.h @@ -8,7 +8,9 @@ class OllamaBotEventChatter { public: - void DispatchGameEvent(Player* source, std::string type, std::string detail); + // guildOverride lets guild hooks supply the guild explicitly, so dispatch does not depend on + // source->GetGuild() (which is unreliable while a member is being added/removed). + void DispatchGameEvent(Player* source, std::string type, std::string detail, Guild* guildOverride = nullptr); void QueueEvent(Player* bot, std::string type, std::string detail, std::string actorName, bool isGuildEvent = false); std::string BuildPrompt(Player* bot, std::string promptTemplate, std::string eventType, std::string eventDetail, std::string actorName); }; @@ -81,14 +83,25 @@ class ChatOnGameObjectUse : public PlayerScript void OnGameObjectUse(Player* player, GameObject* go); }; -class ChatOnGuildMemberChange : public PlayerScript +// Guild membership events. AzerothCore exposes these on GuildScript (NOT PlayerScript): +// OnAddMember / OnRemoveMember cover join/leave (and hand us the Guild* + Player* directly), +// while OnEvent carries promote/demote via the guild event-log type. +class ChatOnGuildEvent : public GuildScript { public: - ChatOnGuildMemberChange(); - void OnGuildMemberJoin(Player* player, Guild* guild); - void OnGuildMemberLeave(Player* player, Guild* guild); - void OnGuildMemberRankChange(Player* player, Guild* guild, uint8 oldRank, uint8 newRank); - void OnGuildMemberLogin(Player* player, Guild* guild); + ChatOnGuildEvent(); + void OnAddMember(Guild* guild, Player* player, uint8& plRank) override; + void OnRemoveMember(Guild* guild, Player* player, bool isDisbanding, bool isKicked) override; + void OnEvent(Guild* guild, uint8 eventType, ObjectGuid::LowType playerGuid1, ObjectGuid::LowType playerGuid2, + uint8 newRank) override; +}; + +// There is no guild-login hook; catch logins on PlayerScript and filter to guilded real players. +class ChatOnLogin : public PlayerScript +{ +public: + ChatOnLogin(); + void OnPlayerLogin(Player* player) override; }; diff --git a/src/mod-ollama-chat_main.cpp b/src/mod-ollama-chat_main.cpp index 9c1a14197..1c038594c 100644 --- a/src/mod-ollama-chat_main.cpp +++ b/src/mod-ollama-chat_main.cpp @@ -23,5 +23,7 @@ void Addmod_ollama_chatScripts() new ChatOnLevelUp(); new ChatOnAchievement(); new ChatOnGameObjectUse(); + new ChatOnGuildEvent(); + new ChatOnLogin(); new OllamaChatConfigCommand(); }