Skip to content

Commit

Permalink
Add Configurable Company API to wiki (#87)
Browse files Browse the repository at this point in the history
* Added configurable company API

* Fixed PR requested changes

* Updated overview

* updated dependency header
  • Loading branch information
TheAnsuz authored Feb 9, 2024
1 parent 6ccfae1 commit 3e9f1ef
Show file tree
Hide file tree
Showing 7 changed files with 448 additions and 1 deletion.
23 changes: 23 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,29 @@ export default defineConfig({

]
},
{
text: 'Configurable Company',
link: '/dev/apis/configurable-company',
collapsed: true,
items: [
{
text: 'Developing Pages',
link: '/dev/apis/configurable-company/developing-pages'
},
{
text: 'Developing Categories',
link: '/dev/apis/configurable-company/developing-categories'
},
{
text: 'Developing Configurations',
link: '/dev/apis/configurable-company/developing-configs'
},
{
text: 'Event Listening',
link: '/dev/apis/configurable-company/event-listening'
}
]
}
]
}
]
Expand Down
141 changes: 141 additions & 0 deletions docs/dev/apis/configurable-company.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
prev: true
next: true
description: Guide on how to add and use Configurable company for your plugin.
---

# Configurable company for your plugin

You will learn how you can add the API to your project and create simple configurations.

For an in-depth guide on the configurations click one of the following buttons:
[Developing pages](/dev/apis/configurable-company/developing-pages)
[Developing categories](/dev/apis/configurable-company/developing-categories)
[Developing configurations](/dev/apis/configurable-company/developing-configs)
[Developing events](/dev/apis/configurable-company/event-listening)

## Requirements

To develop with Configurable company, you should have the following mods:

- [BepinExPack by BepInEx](https://thunderstore.io/c/lethal-company/p/BepInEx/BepInExPack/)

- [ConfigurableCompany by Ansuz/AMRV](https://thunderstore.io/c/lethal-company/p/AMRV/ConfigurableCompany/)

I encourage to use a [mod manager](https://thunderstore.io/c/lethal-company/p/ebkr/r2modman/) to install them and avoid any problems.

## Setting up environment

:::warning
This guide assumes you are using [Visual studio](https://visualstudio.microsoft.com) as IDE.
:::

Before adding the dependency to your project you should have your base plugin created, if you don't know how see [Initial setup](/dev/initial-setup) and [Starting a mod](/dev/starting-a-mod).

You can add the project via [NuGet packages](#using-nuget-package-manager) or by [referencing it manually](#adding-file-manually).

### Using NuGet package manager

If you wish to learn how to use **NuGet**, [here](https://learn.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio) is a link to microsoft's guide.

You need to add the package [**Amrv.ConfigurableCompany**](https://www.nuget.org/packages/Amrv.ConfigurableCompany). You can add it to your project by using the _manage nuget packages_ option from your project.
Otherwise you can add the reference by manually editing the `.csproj` file and adding the following tags:

```xml
<ItemGroup>
<PackageReference Include="Amrv.ConfigurableCompany" Version="2.5.0" PrivateAssets="all"/>
</ItemGroup>
```

### Adding dependency manually

First of all you need a copy of the `.dll` file (wich you can [download manually](https://thunderstore.io/c/lethal-company/p/AMRV/ConfigurableCompany/versions/) or copy an existing one if you have the mod installed).

Add the `Amrv.ConfigurableCompany.dll` to the library folder of your project. _Make sure you also include it as a reference in the project file `project.csproj`._

:::info
You can reference the full path to the dll that you are using for the game, however I don't recommend using absolute paths.
:::

```xml
<ItemGroup>
<!--You can name the reference however you want as long as unique-->
<Reference Include="ConfigurableCompany">
<!--This is an example path, use the one you have with the dll-->
<HintPath>libs\ConfigurableCompany.dll</HintPath>
</Reference>
</ItemGroup>
```

You might as well mark the mod as a hard dependency of your project.

::: info
You can get the dependency string and the mod version from the `LethalConfiguration` class.

```cs
LethalConfiguration.PLUGIN_GUID
LethalConfiguration.PLUGIN_VERSION
```

:::

```cs
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
[BepInDependency(LethalConfiguration.PLUGIN_GUID, BepInDependency.DependencyFlags.HardDependency)] // [!code ++]
public class YourPlugin : BaseUnityPlugin {
// The rest of the class goes here ...
}
```

## How does it work

Configurations and categories need an `ID` that will identify those configurations even if your plugin is not enabled for the player, also allowing other plugins to interact with your configurations if they need to.

As a convention I recommend that you define your ID's as follows:
`owner_your-plugin-name_configuration-name`
::: warning
ID's must be composed of letters, numbers, underscores or hypens and should be in lowercase.
Also they are meant to be unique.
:::

All configurations and categories will be loaded once the player decides to host a game and then the player will be able to modify any configuration to their liking.
Once the player starts the game or saves all the changes will be loaded into their respective configuration. If you want do instantly detect when your configuration has changed you should [listen to the corresponding event](/dev/apis/configurable-company/event-listening).

When another player joins, all the configurations will request a synchronization to the client.

::: warning
Keep in mind configurations depend on the current save file so (for now) until the player selects a file, configurations will hold their default value.
:::

## Creating a simple configuration

ConfigurableCompany contains all the resources you need in a class called `LethalConfiguration` located at the namespace `Amrv.ConfigurableCompany`. There you can reference and declare everything you might need.

Configurable company uses [builder pattern](https://en.wikipedia.org/wiki/Builder_pattern) to create pages, categories and configurations, making it very easy to add and update configurations.

Here is an example that adds a `ranged float` that allows any value from **10** to **100** configuration:

```cs
Configuration simpleConfig = LethalConfiguration.CreateConfig("me_my-mod_id")
.SetName("My first configuration")
.SetType(ConfigurationTypes.RangeFloat(10, 100))
.SetTooltip("My cool description")
.SetSynchronized(true)
.Build(); // Note you can omit this Build call if you are assigning the config to a Configuration variable
```

::: info
Note there are more options to build your configurations, a full guide can be found in [Developing configs](/dev/apis/configurable-company/developing-configs.md).
:::

Now that you have your simple configuration you might need to get the value. You can get it in multiple ways but the most common ones are:

```cs
float value = simpleConfig.Get<float>();
float valueOrDefault = simpleConfig.Get<float>(10f);
int valueCasted = simpleConfig.Get<int>();
```

::: tip
You don't know when the user changes the value so as a recomendation you should use the values once the game starts or [listen to configuration changes](/dev/apis/configurable-company/event-listening.md).
:::
71 changes: 71 additions & 0 deletions docs/dev/apis/configurable-company/developing-categories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
prev: true
next: true
description: How to create Configurable company categories for your plugin and how to use them.
---

# Developing Categories with Configurable Company

## What is a Category

Categories are another way to organize your configurations, like [configuration pages](/dev/apis/configurable-company/developing-pages.md) they don't modify how configurations work however they will allow players to focus on what configurations they want to change, specially when there are many configurations.

## Creating a Category

Categories are used to organize your configuration in a folder-like structure so that the final user can easily navigate the menu. These are symbolic and do not change the behavior of the configurations at all.

To create a category is as simple as calling `LethalConfiguration.CreateCategory`. You can choose to provide an ID right from the start or later, however all categories must contain an unique ID.

**IDs** must be lowercase, contain only letters, numbers, hypens `-` and underscores `_`. I'd recommend to make your IDs look something like `owner_your-plugin-name_category-name`.

Here is an example on how you can create a category:

:::info
If you don't know what a parameter does, check [parameters](#parameters) section.
:::
```csharp
ConfigurationCategory sampleCategory = LethalConfiguration.CreateCategory()
.SetID("developer_some-mod_sample-category")
.SetName("Sample category")
.SetPage(ConfigurationPage) // Optional
.SetColorRGB(255, 0, 0) // Optional
.HideIfEmpty(false) // Optional
.Build();
```

:::tip
It's not necesary to call `Build()` if you are assigning the builder to a `ConfigurationCategory` as it will implicitly call the build method to create the category.
:::

## Parameters

- `SetID(string)`: The unique ID of the category.
- `SetName(string)`: The name that will be displayed on the in-game menu.
- `SetColor(Color[white])`: (There are multiples methods to set the color. choose the prefered one based on your preferences) Sets the color of the category panel in the in-game menu.
- `HideIfEmpty(bool[true])`: Marks the category to not be displayed at all if there are no configurations inside this category.
- `SetPage(ConfigurationPage)`: Makes this category go listed on the provided page. One is generated by default but I encourage you to create a page for your mod.

`Build()` will create the category. It will not be created until the method is called (However as mentioned above, the method will called automatically if you assign the creation to a category).

::: warning
Once the `Build()` is called, you will <u>**not**</u> be able to modify the category any further.
:::

## Using a Category

To use a defined category, you must store the variable with the `ConfigurationCategory` and assign your configurations to it.

You also have the option to get a category from it's ID, allowing you to even get categories declared in other plugins.

```csharp
string categoryId = "some-mod_category";
if (LethalConfiguration.TryGetCategory(categoryId, out ConfigurationCategory category)) {
// If the category exists you can use it
} else {
// If the category does not exists you might need to create it
}
```

:::tip
You can declare categories as `internal static readonly` variables to access them anywhere in your project.
:::
112 changes: 112 additions & 0 deletions docs/dev/apis/configurable-company/developing-configs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
prev: true
next: true
description: How to create Configurable Company configurations for your plugin and how to use them.
---

# Developing Configurations with Configurable Company

Configurations are the core of the API, they allow the developer to define options that the final user can modify to whatever value they want.

Additionally configurations will be synchronized between the host and clients when they join their game.

You can add a lot of information to configurations and even modify configurations from other plugins.

::: warning
As the developer you should avoid setting the value of a configuration manually as that might interfere with the player's choice.
:::

## Creating a configuration

To create a configuration is as simple as calling `LethalConfiguration.CreateConfig`. You can choose to provide an ID right from the start or later, however all configurations must contain an unique ID.

**IDs** must be lowercase, contain only letters, numbers, hypens `-` and underscores `_`. I'd recommend to make your IDs look something like `owner_your-plugin-configuration-name`.

Here is an example on how you can create a configuration:
:::info
If you don't know what a parameter does, check [parameters](#parameters) section.
:::

```csharp
LethalConfiguration.CreateConfig()
.SetID("developer_some-mod_sample-configuration")
.SetName("Sample configuration")
.SetTooltip( // Optional but recommended
"This is a custom configuration that does nothing",
"This is a second line for the tooltip",
"",
"This is another line")
.SetCategory(category) // Optional
.SetType(ConfigurationTypes.String)
.SetValue("Random value")
.SetExperimental(false) // Optional
.SetSynchronized(false) // Optional
.Build(); // Optional
```

:::tip
It's not necesary to call `Build()` if you are assigning the builder to a `Configuration` as it will implicitly call the build method to create the configuration.
:::

## Parameters

- `SetID(string)`: The unique ID of the configuration.
- `SetName(string)`: The name that will be displayed on the in-game menu.
- `SetTooltip(string array)`: Each line of the configuration tooltip. Keep it short and informative.
- `SetCategory(string/ConfigurationCategory)`: Wich category will hold this configuration. There must be always a category, however a default one will be used as failsafe.
- `SetType(ConfigurationType)`: The type of values that this configuration accepts. You can choose one from the alredy existing types `ConfigurationTypes.` or create your own.
- `SetValue(object)`: The value that will contain upon creation. Might be changed instantly if read from file.
- `SetExperimental(bool)`: If this configuration is not guaranteed to work. This is only visual notification for the users.
- `SetSynchronized(bool)`: Marks the configuration to be synchronized with other clients when they join the game. Useful if a configuration might only change client-side.
- `SetNeedsRestart(bool)`: Marks the configuration that the client must restart the game for it to work properly.

::: warning
Once the `Build()` is called, you will <u>**not**</u> be able to modify the category any further.
:::

## Configuration Types

You can choose to create a configuration of your own type however it will take you less time to use one of the existing ones (or request a type to be implemented):

- `String`: Allows any text (up to 32 characters).
- `SmallString`: Allows a short text (up to 10 characters).
- `Boolean`: Allows true or false.
- `Percent`: Allows a float value that goes from **0** to **100**.
- `Float`: Allows any float or whole number value.
- `Integer`: Allows any whole number value.
- `RangeInteger(min, max)`: A integer that only accepts value within the specified range.
- `RangeFloat(min, max)`: A float that only accepts values within the specified range.
- `Slider(min, max)`: A slider that allows any non-rounded value within the specified range.
- `StringOfLength(length)`: A string that allows you to set a maximum amount of characters that can go from 1 to 48.
- `Options(Enumeration/object collection or array)`: A choosable option that allows for a specific value in a collection _The provided collection must be of just one type, you **can't** use an heterogeneous array_.

## Using a Configuration

There are multiple ways you can get the value of a configuration. Each one might be used according to the situation.

- `configuration.Get<T>()`: This will retrieve the value as an instance of T, no failsafes are used if the conversion of the value to T fails.
- `configuration.Get<T>(T failsafe)`: This will retrieve the value as an instance of T but if the conversion fails it will return the `failsafe` value instead.
- `configuration.TryGet<T>(out T value)`: This is a standard TryGet that will return true if the Get succeded and false if it failed. The resulting value will be stored in `T value`.
- `configuration.Value`: Will return the raw object for the configuration without any cast or check.

You also have the option to get a configuration from it's ID, allowing you to even get configurations declared in other plugins.

```csharp
string configurationId = "some-mod_configuration";
if (LethalConfiguration.TryGetConfig(configurationId, out Configuration category)) {
// If the configuration exists you can use it
} else {
// If the configuration does not exists you might need to create it
}
```

_Some configuration types will be automatically converted to other types if you request it. For example the **Options** type will allow you to get the index of the item if you use `Get<int>()` or the value itself if you request it as `Get<T>()` (T being the type of the collection)._

To set a configuration you need to use `configuration.TrySet(newValue, ChangeReason)` and will return true if the set was succesful.

You can also reset a configuration to it's default value using `configuration.Reset()`.

:::warning
Keep in mind that setting or resetting configurations is not recommended as may interfere with the needs of the final user.
I encourage you to **not** set configurations by yourself and instead let the player choose their values.
:::
Loading

0 comments on commit 3e9f1ef

Please sign in to comment.