Browse Source

Added Discord chat support.

Devn00b 2 months ago
parent
commit
78f4a6e63b

+ 4 - 0
DB/updates/DiscordBridgeOptions-2-5-24.sql

@@ -0,0 +1,4 @@
+INSERT INTO `ruleset_details` (`ruleset_id`, `rule_category`, `rule_type`, `rule_value`, `description`) VALUES (1, 'R_Discord', 'DiscordEnabled', '1', 'Enable (1) or Disable(0) the Discord Bridge System.');
+INSERT INTO `ruleset_details` (`ruleset_id`, `rule_category`, `rule_type`, `rule_value`, `description`) VALUES (1, 'R_Discord', 'DiscordWebhookURL', 'https://example.com', 'Webhook url for EQ2 -> Discord coms.');
+INSERT INTO `ruleset_details` (`ruleset_id`, `rule_category`, `rule_type`, `rule_value`, `description`) VALUES (1, 'R_Discord', 'DiscordBotToken', '0', 'This is the token for the bot, given in the discord developer site.');
+INSERT INTO `ruleset_details` (`ruleset_id`, `rule_category`, `rule_type`, `rule_value`, `description`) VALUES (1, 'R_Discord', 'DiscordListenChan', '0', 'Channe ID you want to listen to chat from. this is for Discord -> EQ2 coms.');

+ 60 - 0
EQ2/source/WorldServer/Chat/Chat.cpp

@@ -22,9 +22,24 @@
 #include "../../common/Log.h"
 #include "../../common/ConfigReader.h"
 #include "../../common/PacketStruct.h"
+		#include "../Rules/Rules.h"
+		extern RuleManager rule_manager;
+
+//devn00b
+#ifdef DISCORD
+	#ifndef WIN32
+		#include <dpp/dpp.h>
+		#include "ChatChannel.h"
+
+		extern ChatChannel channel;
+	#endif
+#endif
+
 
 extern ConfigReader configReader;
 
+
+
 Chat::Chat() {
 	m_channels.SetName("Chat::Channels");
 }
@@ -262,6 +277,8 @@ bool Chat::LeaveAllChannels(Client *client) {
 bool Chat::TellChannel(Client *client, const char *channel_name, const char *message, const char* name) {
 	vector<ChatChannel *>::iterator itr;
 	bool ret = false;
+	bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool();
+	const char* discordchan = rule_manager.GetGlobalRule(R_Discord, DiscordChannel)->GetString();
 
 	m_channels.readlock(__FUNCTION__, __LINE__);
 	for (itr = channels.begin(); itr != channels.end(); itr++) {
@@ -271,6 +288,21 @@ bool Chat::TellChannel(Client *client, const char *channel_name, const char *mes
 			else
 				ret = (*itr)->TellChannel(client, message, name);
 
+			if(enablediscord == true && client){
+
+				if (strcmp(channel_name, discordchan) != 0){	
+					m_channels.releasereadlock(__FUNCTION__, __LINE__);
+					return ret;
+				}
+#ifdef DISCORD
+				if (client) {
+                	std::string whofrom = client->GetPlayer()->GetName();
+                	std::string msg = string(message);
+                	ret = PushDiscordMsg(msg.c_str(), whofrom.c_str());
+				}
+#endif				
+			}
+
 			break;
 		}
 	}
@@ -310,3 +342,31 @@ ChatChannel* Chat::GetChannel(const char *channel_name) {
 
 	return ret;
 }
+
+#ifdef DISCORD
+//this sends chat from EQ2EMu to Discord. Currently using webhooks. Makes things simpler code wise.
+int Chat::PushDiscordMsg(const char* msg, const char* from) {
+	bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool();
+	
+	if(enablediscord == false) {
+		LogWrite(INIT__INFO, 0,"Discord","Bot Disabled By Rule...");
+		return 0;
+	}
+
+ 	m_channels.readlock(__FUNCTION__, __LINE__);
+	const char* hook = rule_manager.GetGlobalRule(R_Discord, DiscordWebhookURL)->GetString();
+	std::string servername = net.GetWorldName();
+	char ourmsg[4096];
+
+	//form our message
+	sprintf(ourmsg,"[%s] [%s] Says: %s",from, servername.c_str(), msg);
+
+	/* send a message with this webhook */
+	dpp::cluster bot("");
+	dpp::webhook wh(hook);
+	bot.execute_webhook(wh, dpp::message(ourmsg));
+    m_channels.releasereadlock(__FUNCTION__, __LINE__);
+	
+	return 1;
+}
+#endif

+ 9 - 0
EQ2/source/WorldServer/Chat/Chat.h

@@ -27,6 +27,13 @@
 #include "../client.h"
 #include "ChatChannel.h"
 
+#ifdef DISCORD
+	#ifndef WIN32
+		#pragma once
+		#include <dpp/dpp.h>
+	#endif
+#endif
+
 using namespace std;
 /*
 
@@ -100,6 +107,8 @@ public:
 	bool LeaveAllChannels(Client *client);
 	bool TellChannel(Client *client, const char *channel_name, const char *message, const char* name = 0);
 	bool SendChannelUserList(Client *client, const char *channel_name);
+	//devn00b
+	int PushDiscordMsg(const char*, const char*);
 	ChatChannel* GetChannel(const char* channel_name);
 
 private:

+ 22 - 8
EQ2/source/WorldServer/Chat/ChatChannel.cpp

@@ -135,23 +135,36 @@ bool ChatChannel::TellChannel(Client *client, const char *message, const char* n
 		packet_struct->setDataByName("from_spawn_id", 0xFFFFFFFF);
 		packet_struct->setDataByName("to_spawn_id", 0xFFFFFFFF);
 
-		if (client)
+		if (client != NULL){
 			packet_struct->setDataByName("from", client->GetPlayer()->GetName());
-		else
-			packet_struct->setDataByName("from", name2);
+		} else {
+			char name3[128];
+			sprintf(name3,"[%s] from discord",name2);
+			packet_struct->setDataByName("from", name3);
+		}
 
 		packet_struct->setDataByName("to", to_client->GetPlayer()->GetName());
 		packet_struct->setDataByName("channel", to_client->GetMessageChannelColor(CHANNEL_CUSTOM_CHANNEL));
-		packet_struct->setDataByName("language", client->GetPlayer()->GetCurrentLanguage());
+
+		if(client != NULL){
+			packet_struct->setDataByName("language", client->GetPlayer()->GetCurrentLanguage());
+		}else{
+			packet_struct->setDataByName("language", 0);
+		}
 		packet_struct->setDataByName("message", message);
 		packet_struct->setDataByName("channel_name", name);
 		packet_struct->setDataByName("show_bubble", 1);
-		
-		if (client->GetPlayer()->GetCurrentLanguage() == 0 || to_client->GetPlayer()->HasLanguage(client->GetPlayer()->GetCurrentLanguage())) {
+
+		if(client != NULL){
+			if (client->GetPlayer()->GetCurrentLanguage() == 0 || to_client->GetPlayer()->HasLanguage(client->GetPlayer()->GetCurrentLanguage())) {
+				packet_struct->setDataByName("understood", 1);
+			}
+		} else {
 			packet_struct->setDataByName("understood", 1);
 		}
+
 		packet_struct->setDataByName("unknown4", 0);
-		
+	
 		to_client->QueuePacket(packet_struct->serialize());
 		safe_delete(packet_struct);
 	}
@@ -210,4 +223,5 @@ bool ChatChannel::SendChannelUserList(Client *client) {
 	safe_delete(packet_struct);
 
 	return true;
-}
+} 
+

+ 6 - 0
EQ2/source/WorldServer/Rules/Rules.cpp

@@ -376,6 +376,12 @@ void RuleManager::Init()
 
 	RULE_INIT(R_World, DatabaseVersion, "0");
 
+	//devn00b
+	RULE_INIT(R_Discord, DiscordEnabled, "0"); //Enable/Disable built in discord bot.
+	RULE_INIT(R_Discord, DiscordWebhookURL, "None"); //Webhook url used for server -> discord messages.
+	RULE_INIT(R_Discord, DiscordBotToken, "None"); //Bot token used to connect to discord and provides discord -> server messages.
+	RULE_INIT(R_Discord, DiscordChannel, "Discord"); // in-game channel used for server -> discord messages.
+	RULE_INIT(R_Discord, DiscordListenChan, "0"); // Discord ChannelID used for discord->server messages.
 #undef RULE_INIT
 }
 

+ 8 - 2
EQ2/source/WorldServer/Rules/Rules.h

@@ -40,7 +40,8 @@ enum RuleCategory {
 	R_Zone,
 	R_Loot,
 	R_Spells,
-	R_Expansion
+	R_Expansion,
+	R_Discord
 };
 
 enum RuleType {
@@ -231,7 +232,12 @@ enum RuleType {
 	DatabaseVersion,
 
 	SkipLootGrayMob,
-	LootDistributionTime	
+	LootDistributionTime,
+	DiscordEnabled,
+	DiscordWebhookURL,
+	DiscordBotToken,
+	DiscordChannel,
+	DiscordListenChan
 };
 
 class Rule {

+ 97 - 0
EQ2/source/WorldServer/makefile.discord

@@ -0,0 +1,97 @@
+# Programs
+CC			= gcc
+CXX			= g++
+LINKER			= g++
+
+
+# Configuration
+Build_Dir	= build
+Source_Dir	= ..
+APP		= eq2world
+
+
+# LUA flags
+Lua_C_Flags	= -DLUA_COMPAT_ALL -DLUA_USE_LINUX
+Lua_W_Flags	= -Wall
+
+
+# World flags
+C_Flags		= -I/usr/include/mariadb -I../depends/fmt/include -I../depends/recastnavigation/Detour/Include -I/usr/local/include/boost -I../depends/glm/ -march=native -pipe -pthread -std=c++17
+LD_Flags	= -L/usr/lib/x86_64-linux-gnu -lz -lpthread -lmariadbclient -L../depends/recastnavigation/RecastDemo/Build/gmake/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex
+W_Flags		= -Wall -Wno-reorder
+D_Flags		= -DEQ2 -DWORLD -D_GNU_SOURCE
+
+
+# Setup Debug or Release build
+ifeq ($(BUILD),debug)
+	# "Debug" build - minimum optimization, and debugging symbols
+	C_Flags += -O -ggdb
+	D_Flags += -DDEBUG -DDISCORD
+	LD_Flags += -ldpp
+	Current_Build_Dir := $(Build_Dir)/debug
+	App_Filename = $(APP)_debug
+else
+	# "Release" build - optimization, and no debug symbols
+	C_Flags += -O2 -s -DNDEBUG
+	Current_Build_Dir := $(Build_Dir)/release
+	App_Filename = $(APP)
+endif
+
+
+# File lists
+World_Source		= $(wildcard $(Source_Dir)/WorldServer/*.cpp) $(wildcard $(Source_Dir)/WorldServer/*/*.cpp)
+World_Objects		= $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(World_Source)))
+Common_Source		= $(wildcard $(Source_Dir)/common/*.cpp)
+Common_Objects		= $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Common_Source)))
+Lua_Source			= $(wildcard $(Source_Dir)/LUA/*.c)
+Lua_Objects			= $(patsubst %.c,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Lua_Source)))
+
+
+# Receipes
+all: $(APP)
+
+$(APP): $(Common_Objects) $(World_Objects) $(Lua_Objects)
+	@echo Linking...
+	@$(LINKER) $(W_Flags) $^ $(LD_Flags) -o $(App_Filename)
+	@test -e $(APP) || /bin/true
+	#@ln -s $(App_Filename) $(APP) || /bin/true
+	@echo Finished building world.
+	
+$(Current_Build_Dir)/LUA/%.o: $(Source_Dir)/LUA/%.c
+	@mkdir -p $(dir $@)
+	$(CC) -c $(Lua_C_Flags) $(Lua_W_Flags) $< -o $@
+
+$(Current_Build_Dir)/%.o: $(Source_Dir)/%.cpp
+	@mkdir -p $(dir $@)
+	$(CXX) -c $(C_Flags) $(D_Flags) $(W_Flags) $< -o $@
+
+#setup:
+#	@test ! -e volumes.phys && ln -s $(Conf_Dir)/volumes.phys . || /bin/true
+#	@test ! -e vgemu-structs.xml && ln -s $(Conf_Dir)/vgemu-structs.xml . || /bin/true
+#	@$(foreach folder,$(wildcard $(Content_Dir)/scripts/*),test -d $(Content_Dir)/scripts && test ! -e $(notdir $(folder)) && ln -s $(folder) . || /bin/true)
+#	@echo "Symlinks have been created."
+#	@cp -n $(Conf_Dir)/vgemu-world.xml .
+#	@echo "You need to edit your config file: vgemu-world.xml"
+
+release:
+	@$(MAKE) "BUILD=release"
+	
+debug:
+	@$(MAKE) "BUILD=debug"
+
+clean:
+	rm -rf $(filter-out %Lua,$(foreach folder,$(wildcard $(Current_Build_Dir)/*),$(folder))) $(App_Filename) $(APP)
+
+cleanlua:
+	rm -rf $(Current_Build_Dir)/Lua
+
+cleanall:
+	rm -rf $(Build_Dir) $(App_Filename) $(APP)
+
+#cleansetup:
+#	rm volumes.phys vgemu-structs.xml $(foreach folder,$(wildcard $(Content_Dir)/scripts/*),$(notdir $(folder)))
+
+#docs: docs-world
+
+#docs-world:
+#	@cd ../../doc; doxygen Doxyfile-World

+ 83 - 3
EQ2/source/WorldServer/net.cpp

@@ -57,6 +57,16 @@ using namespace std;
 #include "Transmute.h"
 #include "Zone/ChestTrap.h"
 
+//devn00b
+#ifdef DISCORD
+	//linux only for the moment.
+	#ifndef WIN32
+		#include <dpp/dpp.h>
+		#include "Chat/Chat.h"
+		extern Chat chat;
+	#endif
+#endif
+
 double frame_time = 0.0;
 
 #ifdef WIN32
@@ -113,6 +123,12 @@ extern map<int16, int16> EQOpcodeVersions;
 ThreadReturnType ItemLoad (void* tmp);
 ThreadReturnType AchievmentLoad (void* tmp);
 ThreadReturnType SpellLoad (void* tmp);
+//devn00b
+#ifdef DISCORD
+	#ifndef WIN32
+		ThreadReturnType StartDiscord (void* tmp);
+	#endif
+#endif
 
 int main(int argc, char** argv) {
 #ifdef PROFILER
@@ -238,9 +254,12 @@ int main(int argc, char** argv) {
 		pthread_t thread2;
 		pthread_create(&thread2, NULL, SpellLoad, &world);
 		pthread_detach(thread2);
-		//pthread_t thread3;
-		//pthread_create(&thread3, NULL, AchievmentLoad, &world);
-		//pthread_detach(thread3);
+		//devn00b
+		#ifdef DISCORD
+		pthread_t thread3;
+		pthread_create(&thread3, NULL, StartDiscord, &world);
+		pthread_detach(thread3);
+		#endif
 #endif
 	}
 
@@ -930,3 +949,64 @@ void NetConnection::WelcomeHeader()
 
 	fflush(stdout);
 }
+
+#ifdef DISCORD
+ThreadReturnType StartDiscord(void* tmp)
+{
+#ifndef DISCORD
+	THREAD_RETURN(NULL);
+#endif
+	if (tmp == 0) {
+		ThrowError("StartDiscord: tmp = 0!");
+		THREAD_RETURN(NULL);
+	}
+
+#ifdef WIN32
+	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
+#endif
+
+	bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool();
+	
+	if(enablediscord == false) {
+		LogWrite(INIT__INFO, 0,"Discord","Bot Disabled By Rule...");
+		THREAD_RETURN(NULL);
+	}
+
+	LogWrite(INIT__INFO, 0, "Discord", "Starting Discord Bridge...");
+	const char* bottoken = rule_manager.GetGlobalRule(R_Discord, DiscordBotToken)->GetString();
+
+	if(strlen(bottoken)== 0) {
+		LogWrite(INIT__INFO, 0,"Discord","Bot Token Was Empty...");
+		THREAD_RETURN(NULL);
+	}
+
+	dpp::cluster bot(bottoken, dpp::i_default_intents | dpp::i_message_content);
+	
+	//if we have debug on, go ahead and show DPP logs.
+	#ifdef DEBUG
+		bot.on_log([&bot](const dpp::log_t & event) {
+		std::cout << "[" << dpp::utility::loglevel(event.severity) << "] " << event.message << "\n";
+		});
+	#endif
+	
+	bot.on_message_create([&bot](const dpp::message_create_t& event) {
+		if (event.msg.author.is_bot() == false) {
+			std::string chanid  = event.msg.channel_id.str();
+			std::string listenchan = rule_manager.GetGlobalRule(R_Discord, DiscordListenChan)->GetString();
+
+			if(chanid.compare(listenchan) != 0 || !chanid.size() || !listenchan.size()) {
+				return;
+			}
+			 chat.TellChannel(NULL, listenchan.c_str(), event.msg.content.c_str(), event.msg.author.username.c_str());
+		}
+	});
+
+	while(true) {
+		bot.start(dpp::st_wait);
+		//wait 30s for reconnect. prevents hammering discord and a potential ban.
+		std::this_thread::sleep_for(std::chrono::milliseconds(30000));
+	}
+	
+	THREAD_RETURN(NULL);
+}
+#endif