240 lines
14 KiB
Markdown
240 lines
14 KiB
Markdown
# PLib
|
|
|
|
PLib is Peter's library for making mods. All mods in this repository depend on PLib via [ILMerge](https://github.com/dotnet/ILMerge).
|
|
|
|
PLib 4.0 is now modular, allowing mods to only include and merge the components that they use, along with the much reduced PLib Core library.
|
|
PLib 4.0 is not backwards compatible with PLib 2.0 and 3.0, but both can be run side by side (if that was possible).
|
|
PLib 4.0 and up supports [Harmony 2.0 for the new game versions merging the vanilla game and Spaced Out! DLC](https://forums.kleientertainment.com/forums/topic/130712-oni-is-upgrading-to-harmony-20/), but PLib 3.0 and lower do not.
|
|
|
|
## Obtaining
|
|
|
|
### Source
|
|
|
|
PLib can be checked out and compiled from source (tested on Visual Studio Community 2019).
|
|
If Oxygen Not Included is installed in a different location, make a copy of `Directory.Build.Props.default` named `Directory.Build.Props.user` and update the game folder paths appropriately.
|
|
|
|
### Binary
|
|
|
|
DLL releases for major versions are available in the [releases](https://github.com/peterhaneve/ONIMods/releases) page.
|
|
**Only the complete library is available from NuGet, which is sufficient for most users.**
|
|
The lightweight PLib Options library (which includes only the options portion of PLib, and does not perform any forwarding) is also available in the releases section.
|
|
|
|
### NuGet
|
|
|
|
PLib is available as a [NuGet package](https://www.nuget.org/packages/PLib/).
|
|
|
|
## Usage
|
|
|
|
PLib must be included with mods that depend on it.
|
|
The best method to do this is to use ILMerge or ILRepack and add the PLib project or DLL as a reference in the mod project.
|
|
ILMerge is available as a [NuGet package](https://www.nuget.org/packages/ilmerge) and is best used as a post-build command.
|
|
Suggested command:
|
|
```powershell
|
|
"$(ILMergeConsolePath)" /ndebug /out:$(TargetName)Merged.dll $(TargetName).dll PLib.dll /targetplatform:v4,C:\Windows\Microsoft.NET\Framework64\v4.0.30319
|
|
```
|
|
|
|
This helps ensure that each mod uses the version of PLib that it was built against, reducing the risk of breakage due to PLib changes.
|
|
Note that if using ILMerge, all dependencies of your mod *and PLib* must be added as references to the project.
|
|
Avoid merging Unity assemblies with the compiled DLL, and turn off *Copy Local* on all other library DLLs such as `0Harmony` and `Assembly-CSharp`.
|
|
PLib can also be packaged separately as a DLL with an individual mod, but on Mac and Linux this may lead to version issues.
|
|
|
|
Some parts of PLib need to be patched only once, or rely on having the latest version.
|
|
To handle this problem, PLib uses *forwarded components*, which only loads the components and patches that are in use, using the latest version of each component available across all installed mods.
|
|
Only one instance of each registered forwarded component is instantiated, even if multiple mods have the same version, although which one is used in case of a tie is unspecified.
|
|
Custom forwarded components in mod code can be implemented by subclassing `PeterHan.PLib.Core.PForwardedComponent`.
|
|
|
|
### Initialization
|
|
|
|
Initialize PLib by calling `PUtil.InitLibrary(bool)` in `OnLoad`.
|
|
PLib *must* be initialized before using most of PLib functionality, but instantiating most PLib components will now also initialize PLib if necessary.
|
|
|
|
It will emit the mod's `AssemblyFileVersion` to the log if the `bool` parameter is true, which can aids with debugging.
|
|
Using the `AssemblyVersion` instead is discouraged, because changing `AssemblyVersion` breaks any explicit references to the assembly by name.
|
|
(Ever wonder why .NET 3.5 still uses the .NET 2.0 version string?)
|
|
|
|
## Core
|
|
|
|
The `PLib.Core` component is required by all other PLib components.
|
|
It contains only a minimal patch manager and general utilities that do not require any patches at runtime, such as Detours, reflection utilities, and basic game helpers.
|
|
|
|
## User Interface
|
|
|
|
The `PLib.UI` component is used to create custom user interfaces, in the same style as the base game.
|
|
It requires `PLib.Core`.
|
|
For more details on the classes in this component, see the XML documentation.
|
|
|
|
### Side Screens
|
|
|
|
Add a side screen class in a postfix patch on `DetailsScreen.OnPrefabInit` using `PUIUtils.AddSideScreenContent<T>()`.
|
|
The type parameter should be a custom class which extends `SideScreenContent`.
|
|
The optional argument can be used to set an existing UI `GameObject` as the UI to be displayed, either created using PLib UI or custom creation.
|
|
|
|
If the argument is `null`, create the UI in the `OnPrefabInit` of the side screen content class, and use `AddTo(gameObject, 0)` on the root PPanel to add it to the side screen content.
|
|
A reference to the UI `GameObject` created by `AddTo` should also be stored in the `ContentContainer` property of the side screen content class.
|
|
Note that `SetTarget` is called on the very first object selected *before* `OnPrefabInit` runs for the first time.
|
|
Make sure that this case is handled in code, and that `OnPrefabInit` refreshes the UI after it is built to match the current target object.
|
|
|
|
## Options
|
|
|
|
The `PLib.Options` component provides utilities for reading and writing config files, as well as editing configs in-game via the mod menu.
|
|
It requires `PLib.UI` and `PLib.Core`.
|
|
|
|
#### Reading/writing config files
|
|
|
|
To read, use `PLib.Options.POptions.ReadSettings<T>()` where T is the type that the config file will be deserialized to.
|
|
In PLib 4.0, the type will be associated with the assembly that defines that type, not the calling assembly like PLib 2.0 and 3.0.
|
|
By default, PLib will place the config file in the mod assembly directory, named `config.json`, and will give each archived version its own configuration.
|
|
|
|
The `ConfigFile` attribute can be used to modify the name of the configuration file and enable auto-indenting to improve human readability.
|
|
If the Use
|
|
|
|
To write, use `PLib.Options.POptions.WriteSettings<T>(T settings)`, where again T is the settings type.
|
|
|
|
#### Registering for the config screen
|
|
|
|
PLib.Options adds configuration menus to the Mods screen for mods that are registered.
|
|
Register a mod by using `POptions.RegisterOptions(UserMod2, Type settingsType)` in `OnLoad`.
|
|
Creating a new `POptions` instance is required to use this method, but only one `POptions` instance should be created per mod.
|
|
|
|
The argument should be the type of the class the mod uses for its options, and must be JSON serializable.
|
|
`Newtonsoft.Json` is bundled with the game and can be referenced.
|
|
|
|
The class used for mod options can also contain a `ModInfo([string url=""], [string image=""])` annotation to display additional mod information.
|
|
**Note that the title from PLib 2.0 and 3.0 is no longer part of this attribute**, as this functionality has been moved to the Klei `mod.yaml` file.
|
|
The URL can be used to specify a custom website for the mod's home page; if left empty, it defaults to the Steam Workshop page for the mod.
|
|
The image, if specified, will attempt to load a preview image (best size is 192x192) with that name from the mod's data folder and display it in the settings dialog.
|
|
|
|
Each option must be a property, not a member, and should be annotated with `Option(string displaytext, [string tooltip=""])` to be visible in the mod config menu.
|
|
Currently supported types are: `int`, `int?`, `float`, `float?`, `string`, `bool`, `Color`, `Color32`, and `Enum`.
|
|
If a property is a read-only `System.Action`, a button will be created that will execute the returned action if clicked.
|
|
If a property is of type `LocText`, no matter what it returns, the text in `displaytext` will be displayed as a full-width label with no input field.
|
|
If a property is of a user-defined type, PLib will check the public properties of that type -- if any of them have `Option` attributes, the property will be rendered as its own category with each of the inner options grouped inside.
|
|
If a valid localization string key name is used for `displaytext` (such as `STRINGS.YOURMOD.OPTIONS.YOUROPTION`), the localized value of that string from the strings database is used as the display text.
|
|
|
|
To support types not in the predefined list, the `[DynamicOption(Type)]` attribute can be added to specify the type of an `IOptionsEntry` handler class that can display the specified type.
|
|
|
|
#### Categories
|
|
|
|
The optional third parameter of `Option` allows setting a custom category for the option to group related options together.
|
|
The category name is displayed as the title for the section.
|
|
If a valid localization string key name is used for the category (such as `STRINGS.YOURMOD.OPTIONS.YOURCATEGORY`), the localized value of that string from the strings database is used as the title.
|
|
All options inside a nested custom options class are placed under a category matching the title of the declaring `Option` property.
|
|
|
|
#### Range limits
|
|
|
|
`int`, `int?`, `float` and `float?` options can have validation in the form of a range limit.
|
|
Annotate the property with `PLib.Options.Limit(double min, double max)`.
|
|
If `PLib.Options.Limit` is used on a `string` field, the `max` will be used as the maximum string length for the option value.
|
|
Note that users can still enter values outside of the range manually in the configuration file.
|
|
|
|
#### Example
|
|
|
|
```cs
|
|
using Newtonsoft.Json;
|
|
using PeterHan.PLib.Options;
|
|
|
|
// ...
|
|
|
|
[JsonObject(MemberSerialization.OptIn)]
|
|
[ModInfo("https://www.github.com/peterhaneve/ONIMods")]
|
|
public class TestModSettings
|
|
{
|
|
[Option("Wattage", "How many watts you can use before exploding.")]
|
|
[Limit(1, 50000)]
|
|
[JsonProperty]
|
|
public float Watts { get; set; }
|
|
|
|
public TestModSettings()
|
|
{
|
|
Watts = 10000f; // defaults to 10000, e.g. if the config doesn't exist
|
|
}
|
|
}
|
|
```
|
|
|
|
```cs
|
|
using PeterHan.PLib.Core;
|
|
using PeterHan.PLib.Options;
|
|
|
|
// ...
|
|
|
|
public sealed class ModLoad : KMod.UserMod2
|
|
{
|
|
public static void OnLoad()
|
|
{
|
|
PUtil.InitLibrary(false);
|
|
new POptions().RegisterOptions(typeof(TestModSettings));
|
|
}
|
|
}
|
|
```
|
|
|
|
This is how it looks in the mod menu:
|
|
|
|

|
|
|
|
## Actions
|
|
|
|
The `PLib.Actions` component is used to register actions to be executed on user input.
|
|
It requires `PLib.Core`.
|
|
Actions created by this component can be rebound in the game options.
|
|
|
|
Register actions by using `PActionManager.CreateAction(string, LocString, PKeyBinding)` in `OnLoad`.
|
|
Creating a new `PActionManager` instance is required to use this method, but only one `PActionManager` instance should be created per mod.
|
|
|
|
The identifier should be unique to the action used and should include the mod name to avoid conflicts with other mods.
|
|
If multiple mods register the same action identifier, only the first will receive a valid `PAction`.
|
|
The returned `PAction` object has a `GetKAction` method which can be used to retrieve an `Action` that works in standard Klei functions.
|
|
The `PKeyBinding` is used to specify the default key binding.
|
|
|
|
Note that the game can change the values in the `Action` enum. Instead of using the built-in `Action.NumActions` to denote "no action", consider using `PAction.MaxAction` instead which will use the correct value at runtime if necessary.
|
|
|
|
## Lighting
|
|
|
|
The `PLib.Lighting` component allows `Light2D` objects to emit light in a custom shape and intensity falloff.
|
|
It requires `PLib.Core`.
|
|
|
|
Register lighting types by using `PLightManager.Register(string, CastLight)` in `OnLoad`.
|
|
Creating a new `PLightManager` instance is required to use this method, but only one `PLightManager` instance should be created per mod.
|
|
|
|
The identifier should be unique to the light pattern that will be registered.
|
|
If multiple mods register the same lighting type identifier, all will receive a valid `ILightShape` object but only the first mod to register that shape will be used to render it.
|
|
The returned `ILightShape` object has a `GetKLightShape` method which can be used to retrieve a `LightShape` that works in standard `Light2D` functions.
|
|
The `CastLight` specifies a callback in the mod that can handle drawing the light. It needs the signature
|
|
```c
|
|
void CastLight(LightingArgs args);
|
|
```
|
|
The mod will receive an object encapsulating the lighting arguments, including the source of the light with the `Light2D` component and the starting cell.
|
|
See the `LightingArgs` class for more details.
|
|
|
|
## Buildings
|
|
|
|
The `PLib.Buildings` component abstracts several details of adding new buildings.
|
|
It requires `PLib.Core`.
|
|
|
|
Register a new building by using `PBuildingManager.Register(PBuilding)` in `OnLoad`.
|
|
Creating a new `PBuildingManager` instance is required to use this method, but only one `PBuildingManager` instance should be created per mod.
|
|
|
|
The `PBuilding` instance should be created only once, in `OnLoad`.
|
|
The building name, description, and effect can all be specified directly, or left empty to use the default localization string keys for each string.
|
|
The tech tree location and build menu location can also be specified without needing other patches.
|
|
|
|
## Database
|
|
|
|
The `PLib.Database` component deals with non-building operations involving the game database, translation, and codex files.
|
|
It requires `PLib.Core`.
|
|
|
|
### Translations
|
|
|
|
Register a mod for translation by using `PLocalization.Register()` in `OnLoad`.
|
|
Creating a new `PLocalization` instance is required to use this method, but only one `PLocalization` instance should be created per mod.
|
|
|
|
All classes in the mod assembly with `public static` `LocString` fields will be eligible for translation.
|
|
Translation files need to be placed in the `translations` folder in the mod directory, named as the target language code (*zh-CN* for example) and ending with the `.po` extension.
|
|
Note that the translation only occurs after all mods load, so avoid referencing the `LocString` fields during class initialization or `OnLoad` as they may not yet be localized at that time.
|
|
|
|
### Codex Entries
|
|
|
|
Register a mod for codex loading by using `PCodexManager.RegisterCreatures()` and/or `PCodexManager.RegisterPlants()`.
|
|
Creating a new `PCodexManager` instance is required to use this method, but only one `PCodexManager` instance should be created per mod.
|
|
|
|
The codex files will be loaded using the same structure as the base game: a `codex` folder must exist in the mod directory, with `Creatures` and `Plants` subfolders containing the codex data.
|