Boilerplate Plugin
Info
The Bondage Club Plugin that does absolutely nothing! This plugin serves as a starting point for development, handling user settings and a simple settings authority system. As time goes on, it gets more and more out of date - it is not intended to be used as a reference for quality code, just as a place to start from.
Install
Install with Tampermonkey Source Code
Developer Guide
Warning
This is not a framework or library. This does not provide an interface for creating configurable plugins. This is boilerplate code, and it is expected to be changed, possibly dramatically. Don't expect to be able to fork the code, modify a few config files, and have a brand new functional plugin.
Additionally, don't expect this code or documentation to remain up to date with the base game, this plugin is simply a starting point for me to develop other plugins, and a reference for you if you find it helpful.
The Boilerplate code includes the following features:
- A
utils/
directory with helpful functions taken from littlesera's codebase, the BCX codebase, and my own additions. - Setup functions to initialise the mod.
- A basic user-settings page for the mod.
- An example hook to ChatRoomMessage, which logs all the data to the console.
Configuring DataModel
Settings & Player Stats can be added/changed fairly dynamically, with some hard-coded input.
Settings Object Structure
BC Provides a global object for storing plugin settings under Player.ExtensionSettings
. By adding a key here (Player.ExtensionSettings.ModIdentifier
) and calling ServerPlayerExtensionSettingsSync(ModIdentifier)
, BC will store our updated settings persistently. Some logic has been added using LZString and localStorage to ensure these settings are saved and loaded correctly.
At the moment, I am storing { settings, playerState }
together in Player.ExtensionSettings.ModIdentifier
. This may change, as it's unclear if this is the best place to store playerState; I don't know if it has storage limitations and and I don't know if compressing/decompressing with LZString every time we want to change a persistent variable is efficient.
To try to keep settings modular, I've bound the structure of our settings object to the structure of our settings page. The settings page renders as such:
In the above structure, there is no support for nesting.
To match this format, our DataModel object looks like this:
Example DataModel
Adding Settings
To add a new setting, you must add it to an existing key/feature, or under a new key/feature, in defaultSettings
(src/config/defaultDataModel.ts
).
Example Adding Settings
const defaultSettings: PluginSettings = {
General: {
PluginEnabled: {
type: "checkbox",
label: "Enable Plugin",
description: "Enable or disable the plugin",
defaultValue: true,
},
YourNewKey: {
type: "checkbox"
label: "New setting within general."
description: "This setting toggles whether or not a new thing happens across the plugin."
defaultValue: true
}
...
},
Feature1: {
...
},
Feature2: {
...
},
YourNewFeature: {
YourNewKey2: {
type: "text",
label: "New setting within YourNewFeature",
description: "This setting does something."
defaultValue: true
},
...
},
...
};
Adding new settings pages
If you create a new key under defaultSettings, the settings code will automatically create a default page for it at runtime, which will list the settings on that page and handle setting/changing them. Unless you want to render additional features in that page, you don't need to do anything. If you would like to hook to the Load/Unload/Run/Click/Exit functions though:
- Create a corresponding file in
src/settings/ui/pages
named{YourNewKey}.ts
. - The new file must export an object with the following:
- You must then also add the new page to the array
importedPluginPages
insrc/config/defaultDataModel.ts
Example Adding Settings
import { PluginSettingsPage } from "../../../types/plugin";
const YourNewKey: PluginSettingsPage = {
name: "Feature 1",
id: "Feature1",
// -- Optional Below //
Load: () => {},
Unload: () => {},
Run: () => {},
Click: () => {},
Exit: () => {},
// -- Optional Above -- //
};
export default YourNewKey
() => {}
, or even not include them. src/settings/ui/index.ts
will first run all the logic needed, but if you need to render additional buttons and content you can.
Warning
The id
of the key must be identical to the id of the new defaultSetting
key.
Smart Menu Button Utility
Warning
Out of date! The mod now uses the Extension Settings menu built in to BC, like the MBS Plugin does, and like future plugins will. It still uses this function to determine where to place the "remote" button though.
Various mods are inconsistent with button layout on menus:
- BCX places its Player and OtherPlayer settings button at the bottom-right of both menus, ignoring the normal account settings menu altogether.
- LSCG uses the normal account settings menu for its Player settings, and puts a button at the top left of the OtherPlayer Information Screen for its remote OtherPlayer settings.
- BC Responsive puts its Player settings button on the normal account settings menu, but overwrites the normal grid of account settings and creates its own smaller button on the side. It does this because with lots of mods enabled, the normal settings menu wraps too many settings options weirdly.
My approach prioritizes compatibility with as many mods as possible, by reading the canvas pixels to determine the smartest place to put the button.
Like BC Responsive, I've opted not to use the normal settings menu grid to avoid causing my (or other) mod settings buttons to wrap. Like LSCG, I've opted to put the remote OtherPlayer settings in the top left, but with some dynamic positioning.
As seen in the images above, our plugin will try to place itself anywhere on the vertical line where there's a white space big enough for the button. If none exists, it will place the button off-screen. In the case of Player settings and Remote settings, this leaves plenty of space for future plugins to add buttons on that line.
This logic is handled by src/utils/findCanvasSafeSpace.ts
which accepts the desired position for the element, the size of the element, the spaceNeeded
(padding to check for white pixels around the button), and the spaceBetween
which determines what the next attempted position will be.
Info
To best accomplish this, the plugin tries to be amongst the lowest priority code to run in any of the menu hooks. This encourages all other mods to complete their logic first, before it tries to determine what's on screen. But depending on other plugin code, it may not always run last - this is not something I can account for without forcibly overwriting functions.