Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ DerivedDataCache/*
# Built Visual Studio Code Extensions
*.vsix

# DPP DLLs and library folders
!Source/ThirdParty/Binaries/**/*.dll
!Source/ThirdParty/Binaries/**/*.lib
# DPP library files (developers must extract locally)
# Download from https://dpp.dev/install-windows-vs-zip.html
Source/ThirdParty/DPPLibrary
!Source/ThirdParty/DPPLibrary/DPPLibrary.Build.cs

Binaries
*.pdb
*.modules
*.lib
Binary file removed Binaries/Win64/dpp.dll
Binary file not shown.
Binary file removed Binaries/Win64/libcrypto-1_1-x64.dll
Binary file not shown.
Binary file removed Binaries/Win64/libsodium.dll
Binary file not shown.
Binary file removed Binaries/Win64/libssl-1_1-x64.dll
Binary file not shown.
Binary file removed Binaries/Win64/opus.dll
Binary file not shown.
Binary file removed Binaries/Win64/zlib1.dll
Binary file not shown.
Binary file modified Content/FicsitChat_Config.uasset
Binary file not shown.
Binary file modified Content/FicsitChat_ConfigStruct.uasset
Binary file not shown.
4 changes: 2 additions & 2 deletions FicsitChat.uplugin
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0.0",
"SemVersion": "1.0.0",
"VersionName": "1.2.0",
"SemVersion": "1.2.0",
"AcceptsAnyRemoteVersion": true,
"FriendlyName": "FICSIT.chat",
"Description": "Satisfactory to Discord chat bridge.",
Expand Down
70 changes: 61 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ Satisfactory to Discord chat bridge mod with lots of configurability.

## Usage

1. Create a Discord bot on Discord's [developer portal](discord.com/developers) (make sure to copy the bot token as you will need it in the next few steps)
1. Create a Discord bot on Discord's [developer portal](https://discord.com/developers/applications) (make sure to copy the bot token as you will need it in the next few steps - it only appears once and you have to revoke the old one to generate a new one)
- Example application name: `FicsitChat`
- Example description: `Satisfactory to Discord chat bridge.`
- Example bot username: `FICSIT.chat`
- Example icon: [`assets\ficsit_chat_icon_512x512.png`](assets\ficsit_chat_icon_512x512.png)
2. Enable message content intent
3. Invite the bot to your server
4. Enter the bot token into FICSIT.chat's [configuration](#configuration)
5. Enable [developer mode in your Discord client](https://discord.com/developers/docs/game-sdk/store#application-test-mode)
5. Enable [developer mode in your Discord client](https://discord.com/developers/docs/activities/building-an-activity#step-0-enable-developer-mode)
6. Copy the ID of the channel (`Hover over channel->Right click->Copy Channel ID`) you want the bot to use to post Satisfactory messages and send Discord messages back to Satisfactory.
7. Enter the channel ID into FICSIT.chat's [configuration](#configuration)
8. Modify the other options in FICSIT.chat's [configuration](#configuration) to your heart's content
7. Enter the channel ID into FICSIT.chat's configuration (see next section)
8. Modify the other options in FICSIT.chat's configuration to your heart's content

Have fun!

Expand All @@ -38,18 +38,70 @@ See the in-game configuration screen (`Main Menu->Mods->FICSIT.chat`) for modify

#### Dedicated servers

Todo.
Change the options in the `FicsitChat.cfg` file located in the `FactoryGame/Configs` folder of the game. Check the [Game Install Folder Documentation](https://docs.ficsit.app/satisfactory-modding/latest/faq.html#Files_GameInstall) to find where it is. Modifying this file also works for the regular game.

You need to restart the game / dedicated server for the file's content to be reloaded.

The configuration file's content looks like this:

```
{
"BotToken": "BOT_TOKEN_GOES_HERE",
"HasJoinedMessage": true,
"HasLeftMessage": true,
"ChannelId": "CHANNEL_ID_GOES_HERE",
"ChatMessageColor":
{
"Red": 0.34999999403953552,
"Green": 0.40000000596046448,
"Blue": 0.94999998807907104
},
"EnableFooter": true,
"FooterText": "FICSIT.chat",
"EnableDebugLogging": false,
"SML_ModVersion_DoNotChange": "1.2.0"
}
```

## Contributing

To report bugs/crashes, or give suggestions, head over to the repository's [issues tab](https://github.com/Steveplays28/FicsitChat/issues).

### Know bugs

If you add your discord bot to multiple discord servers, it's gonna send messages to channels with the same id if they exist.

### TODO

- [ ] Add support for multiple discord servers (adding server id and channel id pair to configuration)
- [ ] Build target and DPP for Linux

## Development

- Satisfactory version: `Update 8`
- Satisfactory Mod Loader (SML) version: `3.5.0`
- Satisfactory version: `1.1`
- Satisfactory Mod Loader (SML) version: `3.11.3`

### Prerequisites

This mod requires the **D++ Discord library** to be set up before building. The current version used is **10.1.4**.

1. Download the Visual Studio 2022 version for Windows from the [website](https://dl.dpp.dev/latest/win64-release-vs2022)
2. Extract the contents to: `Source/ThirdParty/DPPLibrary/`
- The final structure should look like:
```
Source/ThirdParty/DPPLibrary/
├── DPPLibrary.Build.cs
└── libdpp-10.1.4-win64/ (or your version)
├── bin/
├── include/
└── lib/
```

> **Note:** The DPP library files are not included in this repository due to their size. Each developer must download and extract them locally.

### Building

Visit the [Satisfactory modding documentation](https://docs.ficsit.app/satisfactory-modding/latest/Development/index.html) for information on how to set up the project for your IDE.
Visit the [Satisfactory modding documentation](https://docs.ficsit.app/satisfactory-modding/latest/Development/index.html) for information on how to set up the project for your IDE.

## License

Expand All @@ -68,7 +120,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.

## Attribution

Icon created by Drew (xXdrewbaccaXx).
Icon created by Drew (xXdrewbaccaXx).

## Contact info

Expand Down
1 change: 1 addition & 0 deletions Source/FicsitChat/FicsitChat.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class FicsitChat : ModuleRules
{
public FicsitChat(ReadOnlyTargetRules Target) : base(Target)
{
CppStandard = CppStandardVersion.Cpp20;
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

// FactoryGame transitive dependencies
Expand Down
122 changes: 113 additions & 9 deletions Source/FicsitChat/Private/FicsitChatModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,130 @@ void FFicsitChatModule::ShutdownModule() {
void FFicsitChatModule::RegisterHooks() {
#if !WITH_EDITOR
AFGChatManager *afgChatManager = GetMutableDefault<AFGChatManager>();
SUBSCRIBE_METHOD_VIRTUAL_AFTER(AFGChatManager::Multicast_BroadcastChatMessage, afgChatManager, [](AFGChatManager *self, const FChatMessageStruct &newMessage) {
UE_LOG(LogFicsitChat, Verbose, TEXT("Chat message by %s sent to all clients: %s"), *newMessage.Sender->GetUserName(), *newMessage.MessageString);

// Define the lambda separately to avoid macro preprocessor issues with commas
auto chatMessageHandler = [](AFGChatManager *self, FChatMessageStruct newMessage) {
std::string joinPrefix = "<PlayerName/>";
std::string discordPrefix = "(from discord)";
std::string userName = TCHAR_TO_UTF8(*newMessage.MessageSender.ToString());
std::string message = TCHAR_TO_UTF8(*newMessage.MessageText.ToString());

UE_LOG(LogFicsitChat, Display, TEXT("Chat message by %s sent to all clients: %s"), *newMessage.MessageSender.ToString(), *newMessage.MessageText.ToString());

FFicsitChat_ConfigStruct config = FFicsitChat_ConfigStruct::GetActiveConfig((UFicsitChatWorldModule *)self->GetWorld());
UFicsitChatWorldModule *worldModule = (UFicsitChatWorldModule *)self->GetWorld()->GetSubsystem<UWorldModuleManager>()->FindModule(TEXT("FicsitChat"));

std::string userName = TCHAR_TO_UTF8(*newMessage.Sender->GetUserName());
std::string message = TCHAR_TO_UTF8(*newMessage.MessageString);
if (message == std::string("has joined the game!") && !config.HasJoinedMessage) {
// Check if worldModule exists and bot is initialized
if (!worldModule || !worldModule->bot.IsValid()) {
UE_LOG(LogFicsitChat, Warning, TEXT("Discord bot not initialized, cannot send message"));
return;
}
if (message == std::string("has left the game!") && !config.HasLeftMessage) {

// Message is from Discord
if (message.compare(0, discordPrefix.length(), discordPrefix) == 0) {
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("Ignoring message from discord"));
}
return;
}

dpp::embed embed =
dpp::embed().set_color(dpp::colors::orange).set_title(userName).set_description(message).set_footer(dpp::embed_footer().set_text("If you're tired, just remember you can buy a FICSIT™ Coffee Cup at the AWESOME Shop!"));
// Message is join / leave game message
if (message.compare(0, joinPrefix.length(), joinPrefix) == 0) {
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("Message starts with join/leave prefix: %s"), *FString(joinPrefix.c_str()));
UE_LOG(LogFicsitChat, Verbose, TEXT("Full message: %s"), *FString(message.c_str()));
}

// FIXME: Figure out a proper way to localize this
std::string joinMessages[] = {
"has joined the game!", // en_US
"entrou no jogo!" // pt_BR
};
std::string leaveMessages[] = {
"has left the game!", // en_US
"saiu do jogo!" // pt_BR
};
int joinMsgCount = sizeof(joinMessages) / sizeof(joinMessages[0]);
int leaveMsgCount = sizeof(leaveMessages) / sizeof(leaveMessages[0]);

// Extract the part after the prefix for comparison
std::string messageSuffix = message.substr(joinPrefix.length() + 1);
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("Message suffix (after prefix): %s"), *FString(messageSuffix.c_str()));
}

bool foundJoinMatch = false;
for (int i = 0; i < joinMsgCount; i++) {
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("Comparing with join message [%d]: %s"), i, *FString(joinMessages[i].c_str()));
}

if (messageSuffix.compare(0, joinMessages[i].length(), joinMessages[i]) == 0) {
foundJoinMatch = true;
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("MATCH FOUND: Join message detected (index %d)"), i);
}

if (!config.HasJoinedMessage) {
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("HasJoinedMessage is false, suppressing join message"));
}
return;
}
message = joinMessages[0] + " :white_check_mark:";
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("Normalized join message to: %s"), *FString(message.c_str()));
}
break;
}
}

if (!foundJoinMatch) {
bool foundLeaveMatch = false;
for (int i = 0; i < leaveMsgCount; i++) {
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("Comparing with leave message [%d]: %s"), i, *FString(leaveMessages[i].c_str()));
}

if (messageSuffix.compare(0, leaveMessages[i].length(), leaveMessages[i]) == 0) {
foundLeaveMatch = true;
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("MATCH FOUND: Leave message detected (index %d)"), i);
}

if (!config.HasLeftMessage) {
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("HasLeftMessage is false, suppressing leave message"));
}
return;
}
message = leaveMessages[0] + " :no_entry:";
if (config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("Normalized leave message to: %s"), *FString(message.c_str()));
}
break;
}
}

if (!foundLeaveMatch && config.EnableDebugLogging) {
UE_LOG(LogFicsitChat, Verbose, TEXT("NO MATCH: Message has join/leave prefix but doesn't match any known patterns"));
}
}
}

dpp::embed embed = dpp::embed().set_color(dpp::colors::orange).set_title(userName).set_description(message);
if (config.ShowFooter) {
std::string footerText = TCHAR_TO_UTF8(*config.FooterText);
if (config.FooterText.IsEmpty()) {
footerText = "If you're tired, just remember you can buy a FICSIT™ Coffee Cup at the AWESOME Shop!";
}
embed.set_footer(dpp::embed_footer().set_text(footerText));
}

worldModule->bot->message_create(dpp::message(dpp::snowflake::snowflake(TCHAR_TO_UTF8(*config.ChannelId)), embed));
});
};

SUBSCRIBE_METHOD_VIRTUAL_AFTER(AFGChatManager::AddChatMessageToReceived, afgChatManager, chatMessageHandler);
#endif
}

Expand Down
Loading