diff --git a/.vscode/settings.json b/.vscode/settings.json index 2981997..ef3ca7a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,18 @@ { - "cSpell.words": ["Leaderboard", "leaderboards"] + "cSpell.words": [ + "Digitalroot", + "discordconnector", + "ipify", + "Leaderboard", + "leaderboards", + "PLAYERID", + "PUBLICIP", + "r2modman", + "Roadmap", + "Spidey", + "STEAMID", + "thedefside", + "Valheim", + "Xithyr" + ] } diff --git a/Metadata/CHANGELOG.md b/Metadata/CHANGELOG.md index 971f734..e197654 100644 --- a/Metadata/CHANGELOG.md +++ b/Metadata/CHANGELOG.md @@ -4,6 +4,18 @@ A full changelog for reference. ## History +### Version 2.0.5 + +Features: + +- Adds a config option to format how position data is formatted +- Adds a config option to format how the automatically-appended position data is formatted +- Adds a new variable which can be used in any messages: `%WORLD_NAME%` turns into the name of the world. + +Changes: + +- `%POS%` now renders without the enclosing parentheses. + ### Version 2.0.4 Features: @@ -14,7 +26,7 @@ Features: Other Changes: -- Set BepInEx depencency to eactly 5.4.19 instead of 5.* (this stops a warning from showing up) +- Set BepInEx dependency to exactly 5.4.19 instead of 5.* (this stops a warning from showing up) ### Version 2.0.2 @@ -30,7 +42,7 @@ With this update, we bring back Steam_ID variable inclusion and leaderboard mess Fixes: -- Periodic leaderboard messages sending will now respect your config value intead of never sending +- Periodic leaderboard messages sending will now respect your config value instead of never sending - The STEAMID variable works again. An alias is the PLAYERID variable, which does the same thing -- they both provide the full player id, so `Steam_` or `XBox_` Breaking changes: @@ -45,7 +57,7 @@ Breaking changes: - Removed steamid variable (internally) and tracking stats by steamid. This broke with the PlayFab changes to Valheim. It will be a bit involved to figure out how to deliver the same thing again, so if you have an idea or seen it done in another mod, please reach out with a Github Issue or ping on Discord. - Leaderboard records will reset and a new database with suffix '-records2.db' will be saved anew. This is because what is being tracked is changed (used to be steamid, now it is using the character id). -- Perodic leaderboard messages will not send, ignoring the setting in the config (for now). This is until a more reliable method of determining players apart. +- Periodic leaderboard messages will not send, ignoring the setting in the config (for now). This is until a more reliable method of determining players apart. ### Version 1.8.0 @@ -160,7 +172,7 @@ not migrate the data. For the migration steps, it will be outputting log information (at INFO level) with how many records were migrated and which steps completed. -- Ranked Lowest Player Leaderbaord +- Ranked Lowest Player Leaderboard Added an inverse of the Top Player leaderboard. @@ -230,7 +242,7 @@ Breaking Changes: Features: -- Additional leaderboard options. The existing leaerboard option will now default to sending top 3 players for what is enabled. +- Additional leaderboard options. The existing leaderboard option will now default to sending top 3 players for what is enabled. You can enable a highest and lowest leaderboard for each tracked stat now. All leaderboards get sent on the same interval. ### Version 1.2.2 @@ -328,7 +340,7 @@ This is fixed and you can have join messages disabled and death messages enabled Features: - %PLAYER_NAME% is replaced in messages with the player name, allowing you to change - where in the message the playe is mentioned (Thanks @Digitalroot) + where in the message the player is mentioned (Thanks @Digitalroot) - Configurable Ping and Shout messages Fixes: @@ -418,7 +430,7 @@ when you log in, it adds a little context: "John joined the game for the 1st tim "Stuart arrives. Previous logins: 15". The context additions are not yet created but record-keeping is ready and makes sense to get it started as soon as possible. -If you want to disable the record keeping in its entirity, set the Collect Player Stats +If you want to disable the record keeping in its entirety, set the Collect Player Stats config value to false. This will prevent any records from being saved or written to disk. ### Version 0.5.0 @@ -472,7 +484,7 @@ are not broadcast and instead it is only network messages. ### Version 0.2.0 - Use config values to set what messages get sent for what actions -- More granualarity with Enable/Disable for existing messages +- More granularity with Enable/Disable for existing messages ### Version 0.1.2 diff --git a/Metadata/README.md b/Metadata/README.md index 292b887..2a165dc 100644 --- a/Metadata/README.md +++ b/Metadata/README.md @@ -12,7 +12,7 @@ Connect your Valheim server (dedicated or served from the game itself) to a Disc - Record number of logins/deaths/pings and flavor the Discord messages - Works with non-dedicated server (games opened to lan from the client) -### Supported Message Notificaitons +### Supported Message Notifications - Server startup (server starting, loading the world) - Server started (world loaded, ready to join) @@ -35,6 +35,18 @@ See the [current roadmap](https://github.com/nwesterhausen/valheim-discordconnec ## Changelog +### Version 2.0.5 + +Features: + +- Adds a config option to format how position data is formatted +- Adds a config option to format how the automatically-appended position data is formatted +- Adds a new variable which can be used in any messages: `%WORLD_NAME%` turns into the name of the world. + +Changes: + +- `%POS%` now renders without the enclosing parentheses. + ### Version 2.0.4 Features: @@ -45,7 +57,7 @@ Features: Other Changes: -- Set BepInEx depencency to eactly 5.4.19 instead of 5.* (this stops a warning from showing up) +- Set BepInEx dependency to exactly 5.4.19 instead of 5.* (this stops a warning from showing up) ### Version 2.0.2 @@ -61,7 +73,7 @@ With this update, we bring back Steam_ID variable inclusion and leaderboard mess Fixes: -- Periodic leaderboard messages sending will now respect your config value intead of never sending +- Periodic leaderboard messages sending will now respect your config value instead of never sending - The STEAMID variable works again. An alias is the PLAYERID variable, which does the same thing -- they both provide the full player id, so `Steam_` or `XBox_` Breaking changes: @@ -76,18 +88,4 @@ Breaking changes: - Removed steamid variable (internally) and tracking stats by steamid. This broke with the PlayFab changes to Valheim. It will be a bit involved to figure out how to deliver the same thing again, so if you have an idea or seen it done in another mod, please reach out with a Github Issue or ping on Discord. - Leaderboard records will reset and a new database with suffix '-records2.db' will be saved anew. This is because what is being tracked is changed (used to be steamid, now it is using the character id). -- Perodic leaderboard messages will not send, ignoring the setting in the config (for now). This is until a more reliable method of determining players apart. - -### Version 1.8.0 - -Features: - -- Web requests to Discord API are async instead of blocking the main thread - -Fixes: - -- Handles the edge case when a toggle was enabled but the text in 'messages' for that toggle was blank, the plugin would crash. (e.g. if 'send shout' toggle was `true` but the 'shout message' was blank, in prior versions this would crash the plugin) - -Full changelog history available on the -[Github repository](https://github.com/nwesterhausen/valheim-discordconnector/blob/main/Metadata/CHANGELOG.md) -or [discordconnector.valheim.nwest.games](https://discordconnector.valheim.nwest.games/changelog). +- Periodic leaderboard messages will not send, ignoring the setting in the config (for now). This is until a more reliable method of determining players apart. diff --git a/Metadata/icon-hdpi.png b/Metadata/icon-hdpi.png new file mode 100644 index 0000000..9df75a3 Binary files /dev/null and b/Metadata/icon-hdpi.png differ diff --git a/Metadata/icon.pdn b/Metadata/icon.pdn deleted file mode 100644 index 3e47645..0000000 Binary files a/Metadata/icon.pdn and /dev/null differ diff --git a/Metadata/icon.png b/Metadata/icon.png index 9cee143..ffd5ac6 100644 Binary files a/Metadata/icon.png and b/Metadata/icon.png differ diff --git a/Metadata/icon.svg b/Metadata/icon.svg new file mode 100644 index 0000000..1d3e56e --- /dev/null +++ b/Metadata/icon.svg @@ -0,0 +1,101 @@ + + + +DiscordConnectorValheim diff --git a/Metadata/manifest.json b/Metadata/manifest.json index eec4808..5a3a24a 100644 --- a/Metadata/manifest.json +++ b/Metadata/manifest.json @@ -1,6 +1,6 @@ { "name": "DiscordConnector", - "version_number": "2.0.4", + "version_number": "2.0.5", "website_url": "https://discordconnector.valheim.nwest.games/", "description": "Connects your Valheim server to a Discord webhook. Works for both dedicated and client-hosted servers.", "dependencies": ["denikson-BepInExPack_Valheim-5.4.1901"] diff --git a/Metadata/thunderstore.toml b/Metadata/thunderstore.toml index 5081b54..5bdea17 100644 --- a/Metadata/thunderstore.toml +++ b/Metadata/thunderstore.toml @@ -4,7 +4,7 @@ schemaVersion = "0.0.1" [package] namespace = "nwesterhausen" name = "DiscordConnector" -versionNumber = "2.0.4" +versionNumber = "2.0.5" description = "Connects your Valheim server to a Discord webhook. Works for both dedicated and client-hosted servers." websiteUrl = "https://discordconnector.valheim.nwest.games/" containsNsfwContent = false diff --git a/README.md b/README.md index 08ad53a..a0f8dad 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![Build](https://github.com/nwesterhausen/valheim-discordconnector/actions/workflows/dotnet.yml/badge.svg)](https://github.com/nwesterhausen/valheim-discordconnector/actions/workflows/dotnet.yml) [![Publish](https://github.com/nwesterhausen/valheim-discordconnector/actions/workflows/publish.yml/badge.svg)](https://github.com/nwesterhausen/valheim-discordconnector/actions/workflows/publish.yml) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/nwesterhausen/valheim-discordconnector?label=Github%20Release&style=flat&labelColor=%2332393F)](https://github.com/nwesterhausen/valheim-discordconnector/releases/latest) -[![Thunderstore.io](https://img.shields.io/badge/Thunderstore.io-2.0.4-%23375a7f?style=flat&labelColor=%2332393F)](https://valheim.thunderstore.io/package/nwesterhausen/DiscordConnector/) -[![NexusMods](https://img.shields.io/badge/NexusMods-2.0.4-%23D98F40?style=flat&labelColor=%2332393F)](https://www.nexusmods.com/valheim/mods/1551/) +[![Thunderstore.io](https://img.shields.io/badge/Thunderstore.io-2.0.5-%23375a7f?style=flat&labelColor=%2332393F)](https://valheim.thunderstore.io/package/nwesterhausen/DiscordConnector/) +[![NexusMods](https://img.shields.io/badge/NexusMods-2.0.5-%23D98F40?style=flat&labelColor=%2332393F)](https://www.nexusmods.com/valheim/mods/1551/) Connect your Valheim server to Discord. ([See website for installation or configuration instructions](https://discordconnector.valheim.nwest.games/)). This plugin is largely based on [valheim-discord-notifier](https://github.com/aequasi/valheim-discord-notifier), but this plugin supports randomized messages, muting players, and Discord message embeds. diff --git a/docs/changelog.md b/docs/changelog.md index 5f45015..261847a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,18 @@ A full changelog ## History +### Version 2.0.5 + +Features: + +- Adds a config option to format how position data is formatted +- Adds a config option to format how the automatically-appended position data is formatted +- Adds a new variable which can be used in any messages: `%WORLD_NAME%` turns into the name of the world. + +Changes: + +- `%POS%` now renders without the enclosing parentheses. + ### Version 2.0.4 Features: @@ -14,7 +26,7 @@ Features: Other Changes: -- Set BepInEx depencency to eactly 5.4.19 instead of 5.* (this stops a warning from showing up) +- Set BepInEx dependency to exactly 5.4.19 instead of 5.* (this stops a warning from showing up) ### Version 2.0.2 @@ -30,7 +42,7 @@ With this update, we bring back Steam_ID variable inclusion and leaderboard mess Fixes: -- Periodic leaderboard messages sending will now respect your config value intead of never sending +- Periodic leaderboard messages sending will now respect your config value instead of never sending - The STEAMID variable works again. An alias is the PLAYERID variable, which does the same thing -- they both provide the full player id, so `Steam_` or `XBox_` Breaking changes: @@ -45,7 +57,7 @@ Breaking changes: - Removed steamid variable (internally) and tracking stats by steamid. This broke with the PlayFab changes to Valheim. It will be a bit involved to figure out how to deliver the same thing again, so if you have an idea or seen it done in another mod, please reach out with a Github Issue or ping on Discord. - Leaderboard records will reset and a new database with suffix '-records2.db' will be saved anew. This is because what is being tracked is changed (used to be steamid, now it is using the character id). -- Perodic leaderboard messages will not send, ignoring the setting in the config (for now). This is until a more reliable method of determining players apart. +- Periodic leaderboard messages will not send, ignoring the setting in the config (for now). This is until a more reliable method of determining players apart. ### Version 1.8.0 @@ -160,7 +172,7 @@ not migrate the data. For the migration steps, it will be outputting log information (at INFO level) with how many records were migrated and which steps completed. -- Ranked Lowest Player Leaderbaord +- Ranked Lowest Player Leaderboard Added an inverse of the Top Player leaderboard. @@ -230,7 +242,7 @@ Breaking Changes: Features: -- Additional leaderboard options. The existing leaerboard option will now default to sending top 3 players for what is enabled. +- Additional leaderboard options. The existing leaderboard option will now default to sending top 3 players for what is enabled. You can enable a highest and lowest leaderboard for each tracked stat now. All leaderboards get sent on the same interval. ### Version 1.2.2 @@ -328,7 +340,7 @@ This is fixed and you can have join messages disabled and death messages enabled Features: - %PLAYER_NAME% is replaced in messages with the player name, allowing you to change - where in the message the playe is mentioned (Thanks @Digitalroot) + where in the message the player is mentioned (Thanks @Digitalroot) - Configurable Ping and Shout messages Fixes: @@ -418,7 +430,7 @@ when you log in, it adds a little context: "John joined the game for the 1st tim "Stuart arrives. Previous logins: 15". The context additions are not yet created but record-keeping is ready and makes sense to get it started as soon as possible. -If you want to disable the record keeping in its entirity, set the Collect Player Stats +If you want to disable the record keeping in its entirety, set the Collect Player Stats config value to false. This will prevent any records from being saved or written to disk. ### Version 0.5.0 @@ -472,7 +484,7 @@ are not broadcast and instead it is only network messages. ### Version 0.2.0 - Use config values to set what messages get sent for what actions -- More granualarity with Enable/Disable for existing messages +- More granularity with Enable/Disable for existing messages ### Version 0.1.2 diff --git a/docs/configuration-details.md b/docs/configuration-details.md index 3292bba..5aa7242 100644 --- a/docs/configuration-details.md +++ b/docs/configuration-details.md @@ -20,8 +20,8 @@ Filename `games.nwest.valheim.discordconnector.cfg` | Webhook URL | (none) | The main Discord webhook URL to send notifications/messages to. | | Use fancier discord messages | false | Set to true to enable using embeds in the Discord messages. If left false, all messages will remain plain strings (except for the leaderboard). | | Allow positions to be sent | true | Set to false to prevent any positions/coordinates from being sent. If this is true, it can be overridden per message in the toggles config file. | -| Ignored players | (none) | List of playernames to never send a discord message for (they also won't be tracked in stats). This list should be semicolon (`;`) separated. | -| Ignored players (Regex) | (none) | Regex which playernames are matched against to determine to not send a discord message for (they also won't be tracked in stats) | +| Ignored players | (none) | List of player names to never send a discord message for (they also won't be tracked in stats). This list should be semicolon (`;`) separated. | +| Ignored players (Regex) | (none) | Regex which player names are matched against to determine to not send a discord message for (they also won't be tracked in stats) | | Collect stats | true | When this setting is enabled, DiscordConnector will record basic stats (leave, join, ping, shout, death) about players. | | Send leaderboard updates | false | If you set this to true, that will enable DiscordConnector to send a leaderboard for stats to Discord on the set interval | | Leaderboard update interval | 600 | Time in minutes between each leaderboard update sent to Discord. | @@ -56,6 +56,21 @@ You may assign strings to these variables to reference them in any messages. | Defined Variable 9 | (none) | This variable can be reference in any of the message content settings with %VAR9% | | Defined Variable 10 | (none) | This variable can be reference in any of the message content settings with %VAR10% | +### Variable Configurations + +Some variables can be configured. Mainly the positional information. + +| Option | Default | Description | +| -- | -- | -- | +| POS Variable Formatting | `%X%, %Y%, %Z%` | Change how the %POS% variable is formatted. +| Auto-Appended POS Format | `Coords: (%POS%)` | Change this to modify how Discord Connector automatically appends the POS data. + +!!! info + + POS data gets auto-appended if you enable the `Send Position with ...` toggles. + + If you manually include `%POS%` in your messages then those will not be affected by the "auto-append" format setting. + ## Messages Filename `games.nwest.valheim.discordconnector-messages.cfg` @@ -64,7 +79,7 @@ All of the message options support having multiple messages defined in a semicol If you wanted to have a couple different messages for when a player dies (always chosen at random), you could simply set the config value like this: -``` +```toml Player Death Message = %PLAYER_NAME% has died a beautiful death!;%PLAYER_NAME% went to their end with honor!;%PLAYER_NAME% died. ``` @@ -202,7 +217,7 @@ The toggle configuration is a collection of on/off switches for all the message !!! Info All leaderboard toggles are restricted by the `Send leaderboard updates` toggle in the [Main config](#main-config). -For the ranked leadboards, you choose how many ranks to calculate and display with the `How many places to list in the top ranking leaderboards` setting in the [Main Config](#main-config). +For the ranked leaderboards, you choose how many ranks to calculate and display with the `How many places to list in the top ranking leaderboards` setting in the [Main Config](#main-config). | Option | Default | Description | | --------------------------------------------- | ------- | ------------------------------------------------------------------------------- | @@ -233,8 +248,8 @@ For the ranked leadboards, you choose how many ranks to calculate and display wi | Option | Default | Description | | -------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- | -| Send Player Join Messages | true | If enabled (and player-first anouncements are enabled), will send an extra message on a player's first leave from the server. | -| Send Player Leave Messages | false | If enabled (and player-first anouncements are enabled), will send an extra message on a player's first join to the server. | -| Send Player Death Messages | true | If enabled (and player-first anouncements are enabled), will send an extra message on a player's first death." | -| Send Player Shout Messages | false | If enabled (and player-first anouncements are enabled), will send an extra message on a player's first ping. | -| Send Player Ping Messages | false | If enabled (and player-first anouncements are enabled), will send an extra message on a player's first shout. | +| Send Player Join Messages | true | If enabled (and player-first announcements are enabled), will send an extra message on a player's first leave from the server. | +| Send Player Leave Messages | false | If enabled (and player-first announcements are enabled), will send an extra message on a player's first join to the server. | +| Send Player Death Messages | true | If enabled (and player-first announcements are enabled), will send an extra message on a player's first death." | +| Send Player Shout Messages | false | If enabled (and player-first announcements are enabled), will send an extra message on a player's first ping. | +| Send Player Ping Messages | false | If enabled (and player-first announcements are enabled), will send an extra message on a player's first shout. | diff --git a/docs/editing-configuration.md b/docs/editing-configuration.md index d4f9126..a5198bc 100644 --- a/docs/editing-configuration.md +++ b/docs/editing-configuration.md @@ -1,6 +1,8 @@ +# How to Edit the Configuration + ## Editing the config using R2Modman -With R2Modman open to the profile you are using for the server, +With R2Modman open to the profile you are using for the server, 1. Go to Config Editor 2. Click on the DiscordConnector config @@ -18,4 +20,4 @@ With R2Modman open to the profile you are using for the server, ![webhook url setting](/img/howto-5.png) -4. Save the file \ No newline at end of file +4. Save the file diff --git a/docs/index.md b/docs/index.md index 5be244f..6ea6999 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,7 +11,7 @@ A plugin to connect your Valheim server to Discord. - Record number of logins/deaths/pings and flavor the Discord messages - Works with non-dedicated server (games opened to lan from the client) -### Supported Message Notificaitons +### Supported Message Notifications - Server startup (server starting, loading the world) - Server started (world loaded, ready to join) @@ -30,4 +30,4 @@ See the [current roadmap](https://github.com/nwesterhausen/valheim-discordconnec - Fancier Discord messages - Discord bot integration - Multiple webhook support -- More statistics able to be sent \ No newline at end of file +- More statistics able to be sent diff --git a/docs/randomized-messages.md b/docs/randomized-messages.md index 6782934..961e26f 100644 --- a/docs/randomized-messages.md +++ b/docs/randomized-messages.md @@ -1,8 +1,8 @@ -## Randomized Messages +# Randomized Messages To use the random message feature, when editing the config file, separate each message with a semicolon. This does mean you cannot use a semicolon as part of your message. Raise an issue on the github if you have a better resolution. -### Using r2modman to edit +## Using r2modman to edit Use r2modman to modify the config value @@ -13,7 +13,7 @@ Use r2modman to modify the config value ![example modification](/img/howto-6.png) -### Manually Editing the Config File +## Manually Editing the Config File In the `games.nwest.valheim.discordconnector.cfg` file, a multi-message string would look like this: @@ -26,4 +26,4 @@ In the `games.nwest.valheim.discordconnector.cfg` file, a multi-message string w # Setting type: String # Default value: Server is starting up. Server Launch Message = Server is starting up.;Server Launching!;Server taking off. -``` \ No newline at end of file +``` diff --git a/docs/webhook-instructions.md b/docs/webhook-instructions.md index d762e2f..23a137e 100644 --- a/docs/webhook-instructions.md +++ b/docs/webhook-instructions.md @@ -1,3 +1,5 @@ +# Webhook Instructions + ## How to get a Discord webhook 1. Make sure you have admin permission on the Discord server @@ -18,4 +20,4 @@ 7. After modifying the webhook name and avatar, make sure to click save to save your changes. 8. Click on "Copy Webhook URL" to copy the webhook url which needs to go into the config file for DiscordConnector. - ![copy webhook after saving changes](/img/howto-3.png) \ No newline at end of file + ![copy webhook after saving changes](/img/howto-3.png) diff --git a/src/Config/PluginConfig.cs b/src/Config/PluginConfig.cs index acc0567..eda2cfc 100644 --- a/src/Config/PluginConfig.cs +++ b/src/Config/PluginConfig.cs @@ -195,6 +195,10 @@ public void ReloadConfig(string configPath) public string UserVariable8 => variableConfig.UserVariable8; public string UserVariable9 => variableConfig.UserVariable9; + // Configured Dynamic Variables + public string PosVarFormat => variableConfig.PosVarFormat; + public string AppendedPosFormat => variableConfig.AppendedPosFormat; + // Debug Toggles public bool DebugEveryPlayerPosCheck => togglesConfig.DebugEveryPlayerPosCheck; public bool DebugEveryEventCheck => togglesConfig.DebugEveryEventCheck; diff --git a/src/Config/VariableConfig.cs b/src/Config/VariableConfig.cs index 0128824..5cc1201 100644 --- a/src/Config/VariableConfig.cs +++ b/src/Config/VariableConfig.cs @@ -1,4 +1,5 @@ -using BepInEx.Configuration; +using System; +using BepInEx.Configuration; namespace DiscordConnector.Config { @@ -10,6 +11,7 @@ internal class VariableConfig // config header strings private const string VARIABLE_SETTINGS = "Variable Definition"; + private const string DYNAMIC_VARIABLE_CONFIG = "Variables.DynamicConfig"; // Variable Definition private ConfigEntry userVar; @@ -22,6 +24,8 @@ internal class VariableConfig private ConfigEntry userVar7; private ConfigEntry userVar8; private ConfigEntry userVar9; + private ConfigEntry posVarFormat; + private ConfigEntry appendedPosFormat; public VariableConfig(ConfigFile configFile) { @@ -79,12 +83,26 @@ private void LoadConfig() "", "This variable can be reference in any of the message content settings with %VAR10%"); + posVarFormat = config.Bind(DYNAMIC_VARIABLE_CONFIG, + "POS Variable Formatting", + "%X%, %Y%, %Z%", + "Modify this to change how the %POS% variable gets displayed." + Environment.NewLine + + "You can use %X%, %Y%, and %Z% in this value to customize how the %POS% gets sent."); + appendedPosFormat = config.Bind(DYNAMIC_VARIABLE_CONFIG, + "Auto-Appended POS Format", + "Coords: (%POS%)", + "This defines how the automatic inclusion of the position data is included. This gets appended to the messages sent." + Environment.NewLine + + "If you prefer to embed the POS inside the message instead of embedding it, you can modify the messages in the message config " + Environment.NewLine + + "to include the %POS% variable. This POS message only gets appended on the message if no %POS% is in the message getting sent " + Environment.NewLine + + "but you have sent position data enabled for that message."); + config.Save(); } public string ConfigAsJson() { string jsonString = "{"; + jsonString += "\"User-Defined\":{"; jsonString += $"\"userVar\":\"{UserVariable}\","; jsonString += $"\"userVar1\":\"{UserVariable1}\","; jsonString += $"\"userVar2\":\"{UserVariable2}\","; @@ -95,6 +113,11 @@ public string ConfigAsJson() jsonString += $"\"userVar7\":\"{UserVariable7}\","; jsonString += $"\"userVar8\":\"{UserVariable8}\","; jsonString += $"\"userVar9\":\"{UserVariable9}\""; + jsonString += "},"; + jsonString += "\"Dynamic-Configured\":{"; + jsonString += $"\"posVarFormat\":\"{PosVarFormat}\","; + jsonString += $"\"appendedPosFormat\":\"{AppendedPosFormat}\""; + jsonString += "}"; jsonString += "}"; return jsonString; } @@ -109,5 +132,7 @@ public string ConfigAsJson() public string UserVariable7 => userVar7.Value; public string UserVariable8 => userVar8.Value; public string UserVariable9 => userVar9.Value; + public string PosVarFormat => posVarFormat.Value; + public string AppendedPosFormat => appendedPosFormat.Value; } } diff --git a/src/DiscordApi.cs b/src/DiscordApi.cs index f4c31ab..54a586a 100644 --- a/src/DiscordApi.cs +++ b/src/DiscordApi.cs @@ -21,12 +21,12 @@ public static void SendMessage(string message, Vector3 pos) if (Plugin.StaticConfig.DiscordEmbedsEnabled) { SendMessageWithFields(message, new List> { - Tuple.Create("Coordinates",$"{pos}") + Tuple.Create("Coordinates",MessageTransformer.FormatVector3AsPos(pos)) }); } else { - SendMessage($"{message} Coords: {pos}"); + SendMessage($"{message} {MessageTransformer.FormatAppendedPos(pos)}"); } } /// diff --git a/src/Handlers.cs b/src/Handlers.cs new file mode 100644 index 0000000..1ec9a49 --- /dev/null +++ b/src/Handlers.cs @@ -0,0 +1,421 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace DiscordConnector +{ + internal static class Handlers + { + /// + /// Track players on the server using their host names, e.g. Steam_109248510103 or XBox_2091010148 + /// + public static HashSet joinedPlayers = new HashSet(); + + /// + /// Perform the necessary steps for a player joining the server. + /// + public static void Join(ZNetPeer peer) + { + // - If it's their first time: + // a. If first join announcements are enabled: + // i. Send a first join announcement to Discord + // b. (else) If player join messages are enabled: + // i. Send a message to discord + // - If recording player join stats is enabled + // a. Save a record of the player joining + // - Add player to the player list + + // Get the player's hostname to use for record keeping and logging + string playerHostName = $"{peer.m_socket.GetHostName()}"; + + // Try adding the player to the joinedPlayers list. If we are not able to add them, check if it's a dead player before doing nothing. + if (!joinedPlayers.Add(playerHostName)) + { + Plugin.StaticLogger.LogDebug($"{playerHostName} already exists in list of joined players."); + + // Seems that player is dead if character ZDOID id is 0 + // m_characterID id=0 means dead, user_id always matches peer.m_uid + if (peer.m_characterID.id != 0) + { + Handlers.Death(peer); + } + + return; + } + + Plugin.StaticLogger.LogDebug($"Added player {playerHostName} peer_id:{peer.m_uid} ({peer.m_playerName}) to joined player list."); + + // Create basic message pre-formatting + string preFormattedMessage = ""; + // If first-time joining announcements are enabled and we have no record of the player joining, set the message content to the first time join announcement + if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Join, peer.m_playerName) == 0) + { + preFormattedMessage = Plugin.StaticConfig.PlayerFirstJoinMessage; + } + // If sending messages for players joining is enabled + else if (Plugin.StaticConfig.PlayerJoinMessageEnabled) + { + preFormattedMessage = Plugin.StaticConfig.JoinMessage; + } + + + // If recording player join statistics is enabled, save a record of player joining + if (Plugin.StaticConfig.StatsJoinEnabled) + { + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Join, peer.m_playerName, playerHostName, peer.m_refPos); + } + + + // If sending messages for players joining is completely disabled + if (string.IsNullOrEmpty(preFormattedMessage)) + { + return; + } + + FinalizeFormattingAndSend(peer, playerHostName, preFormattedMessage, Plugin.StaticConfig.PlayerJoinPosEnabled); + } + + /// + /// Perform the necessary steps for a player leaving the server. + /// + public static void Leave(ZNetPeer peer) + { + // - If it's their first time: + // a. If first leave announcements are enabled: + // i. Send a first leave announcement to Discord + // b. (else) If player leave messages are enabled: + // i. Send a message to discord + // - If recording player leave stats is enabled + // a. Save a record of the player leaving + // - Remove player from the player list + + // Get the player's hostname to use for record keeping and logging + string playerHostName = $"{peer.m_socket.GetHostName()}"; + + // Try removing the player to the joinedPlayers list. If we couldn't remove them, then do nothing. + if (!joinedPlayers.Remove(playerHostName)) + { + Plugin.StaticLogger.LogDebug($"{playerHostName} did not exist in the list of joined players!"); + return; + } + + Plugin.StaticLogger.LogDebug($"Removed player {playerHostName} peer_id:{peer.m_uid} ({peer.m_playerName}) from joined player list."); + + // Create basic message pre-formatting + string preFormattedMessage = ""; + // If first-time leaving announcements are enabled and we have no record of the player leaving, set the message content to the first time leave announcement + if (Plugin.StaticConfig.AnnouncePlayerFirstLeaveEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Leave, peer.m_playerName) == 0) + { + preFormattedMessage = Plugin.StaticConfig.PlayerFirstLeaveMessage; + } + // If sending messages for players leaving is enabled + else if (Plugin.StaticConfig.PlayerLeaveMessageEnabled) + { + preFormattedMessage = Plugin.StaticConfig.LeaveMessage; + } + + + // If recording player leave statistics is enabled, save a record of player leaving + if (Plugin.StaticConfig.StatsLeaveEnabled) + { + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Join, peer.m_playerName, playerHostName, peer.m_refPos); + } + + + // If sending messages for players leaving is completely disabled + if (string.IsNullOrEmpty(preFormattedMessage)) + { + return; + } + + FinalizeFormattingAndSend(peer, playerHostName, preFormattedMessage, Plugin.StaticConfig.PlayerLeavePosEnabled); + } + + /// + /// Perform the necessary steps for a player dying on the server. + /// + public static void Death(ZNetPeer peer) + { + // - If it's their first time: + // a. If first death announcements are enabled: + // i. Send a first death announcement to Discord + // b. (else) If player death messages are enabled: + // i. Send a message to discord + // - If recording player death stats is enabled + // a. Save a record of the player dying + // - Remove player from the player list + + // Get the player's hostname to use for record keeping and logging + string playerHostName = $"{peer.m_socket.GetHostName()}"; + + // Create basic message pre-formatting + string preFormattedMessage = ""; + // If first-time dying announcements are enabled and we have no record of the player dying, set the message content to the first time death announcement + if (Plugin.StaticConfig.AnnouncePlayerFirstDeathEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Death, peer.m_playerName) == 0) + { + preFormattedMessage = Plugin.StaticConfig.PlayerFirstDeathMessage; + } + // If sending messages for players dying is enabled + else if (Plugin.StaticConfig.PlayerDeathMessageEnabled) + { + preFormattedMessage = Plugin.StaticConfig.DeathMessage; + } + + if (Plugin.StaticConfig.StatsDeathEnabled) + { + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Death, peer.m_playerName, playerHostName, peer.m_refPos); + } + + + + // If sending messages for players dying is completely disabled + if (string.IsNullOrEmpty(preFormattedMessage)) + { + return; + } + + FinalizeFormattingAndSend(peer, playerHostName, preFormattedMessage, Plugin.StaticConfig.PlayerLeavePosEnabled); + } + + /// + /// Perform the necessary steps for a player pinging on the server. + /// + public static void Ping(ZNetPeer peer, Vector3 pos) + { + // - If it's their first time: + // a. If first ping announcements are enabled: + // i. Send a first ping announcement to Discord + // b. (else) If player ping messages are enabled: + // i. Send a message to discord + // - If recording player ping stats is enabled + // a. Save a record of the player pinging + // - Remove player from the player list + + // Get the player's hostname to use for record keeping and logging + string playerHostName = $"{peer.m_socket.GetHostName()}"; + + // Create basic message pre-formatting + string preFormattedMessage = ""; + // If first-time pinging announcements are enabled and we have no record of the player pinging, set the message content to the first time ping announcement + if (Plugin.StaticConfig.AnnouncePlayerFirstPingEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Ping, peer.m_playerName) == 0) + { + preFormattedMessage = Plugin.StaticConfig.PlayerFirstPingMessage; + } + // If sending messages for players pinging is enabled + else if (Plugin.StaticConfig.ChatPingEnabled) + { + preFormattedMessage = Plugin.StaticConfig.PingMessage; + } + + if (Plugin.StaticConfig.StatsPingEnabled) + { + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Ping, peer.m_playerName, playerHostName, pos); + } + + + + // If sending messages for players pinging is completely disabled + if (string.IsNullOrEmpty(preFormattedMessage)) + { + return; + } + + FinalizeFormattingAndSend(peer, playerHostName, preFormattedMessage, Plugin.StaticConfig.ChatPingPosEnabled, pos); + } + + + /// + /// Perform the necessary steps for a player shouting on the server. + /// + public static void Shout(ZNetPeer peer, Vector3 pos, string text) + { + // - If it's their first time: + // a. If first shout announcements are enabled: + // i. Send a first shout announcement to Discord + // b. (else) If player shout messages are enabled: + // i. Send a message to discord + // - If recording player shout stats is enabled + // a. Save a record of the player shouting + + // Get the player's hostname to use for record keeping and logging + string playerHostName = $"{peer.m_socket.GetHostName()}"; + + // Create basic message pre-formatting + string preFormattedMessage = ""; + // If first-time shouting announcements are enabled and we have no record of the player shouting, set the message content to the first time ping announcement + if (Plugin.StaticConfig.AnnouncePlayerFirstShoutEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Shout, peer.m_playerName) == 0) + { + preFormattedMessage = Plugin.StaticConfig.PlayerFirstShoutMessage; + } + // If sending messages for players shouting is enabled + else if (Plugin.StaticConfig.ChatShoutEnabled) + { + preFormattedMessage = Plugin.StaticConfig.ShoutMessage; + } + + if (Plugin.StaticConfig.StatsShoutEnabled) + { + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Shout, peer.m_playerName, playerHostName, pos); + } + + + + // If sending messages for players shouting is completely disabled + if (string.IsNullOrEmpty(preFormattedMessage)) + { + return; + } + + FinalizeFormattingAndSend(peer, playerHostName, preFormattedMessage, Plugin.StaticConfig.ChatPingPosEnabled, pos, text); + } + + /// + /// Finish formatting the message based on the positional data (if allowed) and dispatch it to the Discord webhook. + /// + /// Player peer reference + /// Player host name + /// Raw message to format for sending to discord + /// If we are allowed to include the position data + private static void FinalizeFormattingAndSend(ZNetPeer peer, string playerHostName, string preFormattedMessage, bool posEnabled) + { + FinalizeFormattingAndSend(peer, playerHostName, preFormattedMessage, posEnabled, peer.m_refPos); + } + + /// + /// Finish formatting the message based on the positional data (if allowed) and dispatch it to the Discord webhook. + /// + /// Player peer reference + /// Player host name + /// Raw message to format for sending to discord + /// If we are allowed to include the position data + /// Positional data to use in formatting + private static void FinalizeFormattingAndSend(ZNetPeer peer, string playerHostName, string preFormattedMessage, bool posEnabled, Vector3 pos) + { + // Format the message accordingly, depending if it has the %POS% variable or not + string finalMessage; + if (preFormattedMessage.Contains("%POS%")) + { + if (!posEnabled) + { + preFormattedMessage.Replace("%POS%", ""); + } + + finalMessage = MessageTransformer.FormatPlayerMessage(preFormattedMessage, peer.m_playerName, playerHostName, pos); + + } + else + { + finalMessage = MessageTransformer.FormatPlayerMessage(preFormattedMessage, peer.m_playerName, playerHostName); + } + + // If sending the position with the player join message is enabled + if (posEnabled) + { + // If "fancier" discord messages are enabled OR if the message we intend to send DOES NOT contain the %POS% variable + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !finalMessage.Contains("%POS%")) + { + // Send the message to discord with an auto-appended POS (or as a POS embed if "fancier" discord messages are enabled) + DiscordApi.SendMessage(finalMessage, pos); + return; + } + } + + // Sending position data is not allowed OR the message doesn't contain the %POS% variable + DiscordApi.SendMessage(finalMessage); + } + + + + /// + /// Finish formatting the message based on the positional data (if allowed) and dispatch it to the Discord webhook. + /// + /// This method handles the text from shouts and other chat-adjacent things + /// + /// Player peer reference + /// Player host name + /// Raw message to format for sending to discord + /// If we are allowed to include the position data + /// Positional data to use in formatting + /// Text that was sent + private static void FinalizeFormattingAndSend(ZNetPeer peer, string playerHostName, string preFormattedMessage, bool posEnabled, Vector3 pos, string text) + { + // Format the message accordingly, depending if it has the %POS% variable or not + string finalMessage; + if (preFormattedMessage.Contains("%POS%")) + { + if (!posEnabled) + { + preFormattedMessage.Replace("%POS%", ""); + } + + finalMessage = MessageTransformer.FormatPlayerMessage(preFormattedMessage, peer.m_playerName, playerHostName, text, pos); + + } + else + { + finalMessage = MessageTransformer.FormatPlayerMessage(preFormattedMessage, peer.m_playerName, playerHostName, text); + } + + // If sending the position with the player join message is enabled + if (posEnabled) + { + // If "fancier" discord messages are enabled OR if the message we intend to send DOES NOT contain the %POS% variable + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !finalMessage.Contains("%POS%")) + { + // Send the message to discord with an auto-appended POS (or as a POS embed if "fancier" discord messages are enabled) + DiscordApi.SendMessage(finalMessage, pos); + return; + } + } + + // Sending position data is not allowed OR the message doesn't contain the %POS% variable + DiscordApi.SendMessage(finalMessage); + } + + /// + /// Handle a non-player chat message. Currently only works for shouts. + /// + /// If allowed in the configuration, this will send a message to discord as if a player shouted. + /// + /// Type of chat message + /// Listed username of sender + /// Text sent to chat + internal static void NonPlayerChat(Talker.Type type, string user, string text) + { + // Check if we allow non-player shouts + if (Plugin.StaticConfig.AllowNonPlayerShoutLogging) + { + // Guard against chats that aren't shouts by non-players + if (type != Talker.Type.Shout) + { + Plugin.StaticLogger.LogDebug($"Ignored ping/join/leave from non-player {user}"); + return; + } + + string nonPlayerHostName = ""; + Plugin.StaticLogger.LogDebug($"Sending shout from '{user}' to discord: '{text}'"); + + // Only if we are sending shouts per the config should we send the shout + if (Plugin.StaticConfig.ChatShoutEnabled) + { + // Clean any "formatting" done to the username. This includes coloring via tags. + string userCleaned = MessageTransformer.CleanCaretFormatting(user); + // Format the message into the shout format as defined in the config files + string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.ShoutMessage, userCleaned, nonPlayerHostName, text); + + // Non-players shouldn't have a position, so disregard any position in the message formatting + if (message.Contains("%POS%")) + { + message.Replace("%POS%", ""); + } + + DiscordApi.SendMessage(message); + } + // Exit the function since we sent the message + return; + } + + Plugin.StaticLogger.LogInfo($"Ignored shout from {user} because they aren't a real player"); + } + } +} diff --git a/src/MessageTransformer.cs b/src/MessageTransformer.cs index c018023..812d122 100644 --- a/src/MessageTransformer.cs +++ b/src/MessageTransformer.cs @@ -26,12 +26,16 @@ internal static class MessageTransformer private const string EVENT_MSG = "%EVENT_MSG%"; private const string EVENT_PLAYERS = "%PLAYERS%"; private const string N = "%N%"; + private const string WORLD_NAME = "%WORLD_NAME%"; + private const string WORLD_SEED_NAME = "%WORLD_SEED_NAME%"; + private const string WORLD_SEED = "%WORLD_SEED%"; private static Regex OpenCaretRegex = new Regex(@"<[\w=]+>"); private static Regex CloseCaretRegex = new Regex(@""); + private static string ReplaceVariables(string rawMessage) { - return rawMessage + return ReplaceDynamicVariables(rawMessage) .Replace(VAR, Plugin.StaticConfig.UserVariable) .Replace(VAR_1, Plugin.StaticConfig.UserVariable1) .Replace(VAR_2, Plugin.StaticConfig.UserVariable2) @@ -44,6 +48,15 @@ private static string ReplaceVariables(string rawMessage) .Replace(VAR_9, Plugin.StaticConfig.UserVariable9) .Replace(PUBLIC_IP, Plugin.PublicIpAddress); } + private static string ReplaceDynamicVariables(string rawMessage) + { + string world_name = ""; + Plugin.StaticServerInfo.TryGetValue(Plugin.ServerInfo.WorldName, out world_name); + + return rawMessage + .Replace(WORLD_NAME, world_name); + } + public static string FormatServerMessage(string rawMessage) { return MessageTransformer.ReplaceVariables(rawMessage); @@ -115,6 +128,17 @@ public static string FormatLeaderboardHeader(string rawMessage, int n) .Replace(N, n.ToString()); } + /// + /// Remove caret formatting from a string. This is used to strip special color codes away from user names. + /// + /// For example, some mods can send messages as shouts in the game. They may try to color the name of the user: + /// `[Admin] vadmin` + /// This function strips away any caret formatting, making the string "plain text" + /// `[Admin] vadmin` + /// + /// + /// String to strip caret formatting from + /// Same string but without the caret formatting public static string CleanCaretFormatting(string str) { // regex.Replace(input, sub, 1); @@ -123,5 +147,30 @@ public static string CleanCaretFormatting(string str) return result; } + + /// + /// Format a vector3 position into the formatted version used by discord connector + /// + /// Position vector to turn into string + /// String following the formatting laid out in the variable config file. + public static string FormatVector3AsPos(Vector3 vec3) + { + return Plugin.StaticConfig.PosVarFormat + .Replace("%X%", vec3.x.ToString("F1")) + .Replace("%Y%", vec3.y.ToString("F1")) + .Replace("%Z%", vec3.z.ToString("F1")); + } + + /// + /// Format the appended position data using the config values. + /// + /// Position vector to include + /// String to append with the position information + public static string FormatAppendedPos(Vector3 vec3) + { + string posStr = FormatVector3AsPos(vec3); + return Plugin.StaticConfig.AppendedPosFormat + .Replace(POS, posStr); + } } } diff --git a/src/Patches/ChatPatches.cs b/src/Patches/ChatPatches.cs index 089d219..72631bd 100644 --- a/src/Patches/ChatPatches.cs +++ b/src/Patches/ChatPatches.cs @@ -5,15 +5,29 @@ namespace DiscordConnector.Patches { internal class ChatPatches { + private const string ArrivalShout = "I have arrived!"; [HarmonyPatch(typeof(Chat), nameof(Chat.OnNewChatMessage))] internal class OnNewChatMessage { + /// + /// Look into the chat message and perform some actions depending on what it is. No modifications are made to the messages being sent in game. + /// + /// + /// + /// DiscordConnector is concerned with Shouts and Pings, as the other types of chat messages are not broadcast to the server where DiscordConnector operates. + /// In the special case of shouting in a server hosted from the client version of Valheim, some player has joined the game. + /// + /// + /// The implementation here passes details of the chat message to one of the functions. + /// + /// private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos, ref Talker.Type type, ref string user, ref string text, ref string senderNetworkUserId) { if (string.IsNullOrEmpty(user)) { Plugin.StaticLogger.LogInfo("Ignored shout from invalid user (null reference)"); + return; } if (Plugin.StaticConfig.MutedPlayers.IndexOf(user) >= 0 || Plugin.StaticConfig.MutedPlayersRegex.IsMatch(user)) { @@ -21,153 +35,37 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos return; } - ZNetPeer peerInstance = ZNet.instance.GetPeerByPlayerName(user); + ZNetPeer peer = ZNet.instance.GetPeerByPlayerName(user); - if (peerInstance == null || peerInstance.m_socket == null) + // If peer or the peer socket is null, the message wasn't sent from a player + if (peer == null || peer.m_socket == null) { - // Check if we allow non-player shouts - if (Plugin.StaticConfig.AllowNonPlayerShoutLogging) - { - // Guard against chats that aren't shouts by non-players - if (type != Talker.Type.Shout) - { - Plugin.StaticLogger.LogDebug($"Ignored ping/join/leave from non-player {user}"); - return; - } - - string nonplayerHostName = ""; - Plugin.StaticLogger.LogDebug($"Sending shout from '{user}' to discord: '{text}'"); - - // Only if we are sending shouts per the config should we send the shout - if (Plugin.StaticConfig.ChatShoutEnabled) - { - string userCleaned = MessageTransformer.CleanCaretFormatting(user); - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.ShoutMessage, userCleaned, nonplayerHostName, text); - - if (message.Contains("%POS%")) - { - message.Replace("%POS%", ""); - } - - DiscordApi.SendMessage(message); - } - // Exit the function since we sent the message - return; - } - - Plugin.StaticLogger.LogInfo($"Ignored shout from {user} because they aren't a real player"); + Handlers.NonPlayerChat(type, user, text); return; } // Get the player's hostname to use for record keeping and logging - string playerHostName = peerInstance.m_socket.GetHostName(); + string playerHostName = peer.m_socket.GetHostName(); switch (type) { case Talker.Type.Ping: - if (Plugin.StaticConfig.AnnouncePlayerFirstPingEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Ping, user) == 0) - { - DiscordApi.SendMessage( - MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstPingMessage, user, playerHostName) - ); - } - if (Plugin.StaticConfig.StatsPingEnabled) - { - Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Ping, user, playerHostName, pos); - } - if (Plugin.StaticConfig.ChatPingEnabled) - { - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PingMessage, user, playerHostName); - if (Plugin.StaticConfig.ChatPingPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) - { - DiscordApi.SendMessage( - message, - pos - ); - break; - } - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PingMessage, user, playerHostName, pos); - } - if (message.Contains("%POS%")) - { - message.Replace("%POS%", ""); - } - DiscordApi.SendMessage(message); - } + Handlers.Ping(peer, pos); break; case Talker.Type.Shout: - if (text.Equals("I have arrived!")) + if (text.Equals(ArrivalShout)) { - if (!Plugin.IsHeadless()) + if (Plugin.IsHeadless()) { - if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Join, user) == 0) - { - DiscordApi.SendMessage( - MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, user, playerHostName) - ); - } - if (Plugin.StaticConfig.StatsJoinEnabled) - { - Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Join, user, playerHostName, pos); - } - if (Plugin.StaticConfig.PlayerJoinMessageEnabled) - { - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, user, playerHostName); - if (Plugin.StaticConfig.PlayerJoinPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) - { - DiscordApi.SendMessage( - message, - pos - ); - break; - } - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, user, playerHostName, pos); - } - if (message.Contains("%POS%")) - { - message.Replace("%POS%", ""); - } - DiscordApi.SendMessage(message); - } + return; } + + // On servers hosted from the client version, the host player shouts instead of joining + Handlers.Join(peer); } else { - if (Plugin.StaticConfig.AnnouncePlayerFirstShoutEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Shout, user) == 0) - { - DiscordApi.SendMessage( - MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstShoutMessage, user, playerHostName, text) - ); - } - if (Plugin.StaticConfig.StatsShoutEnabled) - { - Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Shout, user, playerHostName, pos); - } - if (Plugin.StaticConfig.ChatShoutEnabled) - { - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.ShoutMessage, user, playerHostName, text); - if (Plugin.StaticConfig.ChatShoutPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) - { - DiscordApi.SendMessage( - message, - pos - ); - break; - } - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.ShoutMessage, user, playerHostName, text, pos); - } - if (message.Contains("%POS%")) - { - message.Replace("%POS%", ""); - } - DiscordApi.SendMessage(message); - } + Handlers.Shout(peer, pos, text); } break; default: diff --git a/src/Patches/ZNetPatches.cs b/src/Patches/ZNetPatches.cs index 2e2e1cc..740e239 100644 --- a/src/Patches/ZNetPatches.cs +++ b/src/Patches/ZNetPatches.cs @@ -25,6 +25,21 @@ private static void Postfix() } } + [HarmonyPatch(typeof(ZNet), nameof(ZNet.SetServer))] + internal class SetServer + { + private static void Postfix(ref bool server, ref bool openServer, ref bool publicServer, ref string serverName, ref string password, ref World world) + { + DiscordApi.SendMessage("Loaded server info"); + Plugin.StaticServerSetup.Add(Plugin.ServerSetup.IsServer, server); + Plugin.StaticServerSetup.Add(Plugin.ServerSetup.IsOpenServer, openServer); + Plugin.StaticServerSetup.Add(Plugin.ServerSetup.IsPublicServer, publicServer); + Plugin.StaticServerInfo.Add(Plugin.ServerInfo.WorldName, world.m_name); + Plugin.StaticServerInfo.Add(Plugin.ServerInfo.WorldSeed, $"{world.m_seed}"); + Plugin.StaticServerInfo.Add(Plugin.ServerInfo.WorldSeedName, world.m_seedName); + } + } + [HarmonyPatch(typeof(ZNet), nameof(ZNet.SaveWorld))] internal class SaveWorld { @@ -51,177 +66,19 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) return; } - // Get the player's hostname to use for record keeping and logging - string playerHostName = $"{peer.m_socket.GetHostName()}"; + Handlers.Join(peer); - if (joinedPlayers.IndexOf(peer.m_uid) >= 0) - { - // Seems that player is dead if character ZDOID id is 0 - // m_characterID id=0 means dead, user_id always matches peer.m_uid - if (peer.m_characterID.id != 0) - { - Plugin.StaticLogger.LogDebug($"Player \"join\" from someone already on the server. {playerHostName} peer_id:{peer.m_uid} ({peer.m_playerName})"); - return; - } - if (Plugin.StaticConfig.PlayerDeathMessageEnabled) - { - if (Plugin.StaticConfig.AnnouncePlayerFirstDeathEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Death, peer.m_playerName) == 0) - { - string firstDeathMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName, playerHostName); - if (Plugin.StaticConfig.PlayerDeathPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstDeathMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstDeathMessage, peer.m_refPos); - } - else - { - DiscordApi.SendMessage(MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName, playerHostName, peer.m_refPos)); - } - } - else - { - DiscordApi.SendMessage(firstDeathMessage); - } - } - else - { - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName, playerHostName); - if (Plugin.StaticConfig.PlayerDeathPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) - { - DiscordApi.SendMessage(message, peer.m_refPos); - } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName, playerHostName, peer.m_refPos); - DiscordApi.SendMessage(message); - } - } - else - { - DiscordApi.SendMessage(message); - } - } - } - - if (Plugin.StaticConfig.StatsDeathEnabled) - { - Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Death, peer.m_playerName, playerHostName, peer.m_refPos); - } - } - else - { - // PLAYER JOINED - joinedPlayers.Add(peer.m_uid); - Plugin.StaticLogger.LogDebug($"Added player {playerHostName} peer_id:{peer.m_uid} ({peer.m_playerName}) to joined player list."); - if (Plugin.StaticConfig.PlayerJoinMessageEnabled) - { - if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Join, peer.m_playerName) == 0) - { - string firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName, playerHostName); - if (Plugin.StaticConfig.PlayerJoinPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstJoinMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstJoinMessage, peer.m_refPos); - } - else - { - firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, playerHostName, peer.m_refPos); - DiscordApi.SendMessage(firstJoinMessage); - } - } - else - { - DiscordApi.SendMessage( - MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName, playerHostName)); - } - } - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, playerHostName); - if (Plugin.StaticConfig.PlayerJoinPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) - { - DiscordApi.SendMessage(message, peer.m_refPos); - } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, playerHostName, peer.m_refPos); - DiscordApi.SendMessage(message); - } - } - else - { - DiscordApi.SendMessage(message); - } - } - - if (Plugin.StaticConfig.StatsJoinEnabled) - { - Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Join, peer.m_playerName, playerHostName, peer.m_refPos); - } - } } - } - [HarmonyPatch(typeof(ZNet), nameof(ZNet.RPC_Disconnect))] - internal class RPC_Disconnect - { - private static void Prefix(ZRpc rpc) + [HarmonyPatch(typeof(ZNet), nameof(ZNet.RPC_Disconnect))] + internal class RPC_Disconnect { - ZNetPeer peer = ZNet.instance.GetPeer(rpc); - if (peer != null && peer.m_uid != 0) + private static void Prefix(ZRpc rpc) { - // Get the player's hostname to use for record keeping and logging - string playerHostName = $"{peer.m_socket.GetHostName()}"; - if (Plugin.StaticConfig.PlayerLeaveMessageEnabled) - { - if (Plugin.StaticConfig.AnnouncePlayerFirstLeaveEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Leave, peer.m_playerName) == 0) - { - string firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName, playerHostName); - if (Plugin.StaticConfig.PlayerLeavePosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstLeaveMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstLeaveMessage, peer.m_refPos); - } - else - { - firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName, playerHostName, peer.m_refPos); - DiscordApi.SendMessage(firstLeaveMessage); - } - } - else - { - DiscordApi.SendMessage(firstLeaveMessage); - } - } - - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.LeaveMessage, peer.m_playerName, playerHostName); - if (Plugin.StaticConfig.PlayerLeavePosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) - { - DiscordApi.SendMessage(message, peer.m_refPos); - } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.LeaveMessage, peer.m_playerName, playerHostName, peer.m_refPos); - DiscordApi.SendMessage(message); - } - - } - else - { - DiscordApi.SendMessage(message); - } - } - - if (Plugin.StaticConfig.StatsLeaveEnabled) + ZNetPeer peer = ZNet.instance.GetPeer(rpc); + if (peer != null && peer.m_uid != 0) { - Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Leave, peer.m_playerName, playerHostName, peer.m_refPos); + Handlers.Leave(peer); } } } diff --git a/src/Plugin.cs b/src/Plugin.cs index 748e84c..761b79b 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -1,4 +1,5 @@ -using BepInEx; +using System.Collections.Generic; +using BepInEx; using BepInEx.Logging; using DiscordConnector.Records; using HarmonyLib; @@ -16,6 +17,20 @@ public class Plugin : BaseUnityPlugin internal static Leaderboard StaticLeaderboards; internal static EventWatcher StaticEventWatcher; internal static ConfigWatcher StaticConfigWatcher; + internal enum ServerInfo + { + WorldName, + WorldSeed, + WorldSeedName + } + internal static Dictionary StaticServerInfo; + internal enum ServerSetup + { + IsServer, + IsOpenServer, + IsPublicServer, + } + internal static Dictionary StaticServerSetup; internal static string PublicIpAddress; private Harmony _harmony; @@ -25,6 +40,8 @@ public Plugin() StaticConfig = new PluginConfig(Config); StaticDatabase = new Records.Database(Paths.GameRootPath); StaticLeaderboards = new Leaderboard(); + StaticServerInfo = new Dictionary(); + StaticServerSetup = new Dictionary(); StaticConfigWatcher = new ConfigWatcher(); } diff --git a/src/PluginInfo.cs b/src/PluginInfo.cs index e93f651..ac9c35e 100644 --- a/src/PluginInfo.cs +++ b/src/PluginInfo.cs @@ -17,7 +17,7 @@ internal static class PluginInfo { public const string PLUGIN_ID = "games.nwest.valheim.discordconnector"; public const string PLUGIN_NAME = "Valheim Discord Connector"; - public const string PLUGIN_VERSION = "2.0.4"; + public const string PLUGIN_VERSION = "2.0.5"; public const string PLUGIN_REPO_SHORT = "github: nwesterhausen/valheim-discordconnector"; public const string PLUGIN_AUTHOR = "Nicholas Westerhausen"; }