Skip to content

For Developers

Caeden117 edited this page Aug 28, 2020 · 34 revisions

Here is where you'll find tutorials and information about various development-related things for Counters+. Whether you just want to get the project set up, or you want to expand upon the project, this page should have the information you need.

Index

Because this page might be long, here are some quick links to the various ways you can contribute to Counters+.

Getting Set Up

After forking or cloning the Counters+ source, there are a few extra steps you need to accomplish to get the plugin building.

Counters+ 2.0.0 and Above

To set the project up, simply right click the Counters+ project in the Solution Explorer, and navigate to Beat Saber Modding Tools >>> Set Beat Saber Directory.... Click that, and the references should automatically be set up for you.

If that does not work for whatever reason, right click the References dropdown underneath the Counters+ project in the Solution Explorer, then click Beat Saber Reference Manager. Simply uncheck a reference, then hit Apply. After that, re-check that reference, then click Apply again.

Counters+ 1.9.1 and Below

In order to build this project, please add your Beat Saber directory path to the Counters+.csproj.user file located in the project directory. This file is in .gitignore, so it will not count as a changed file.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <!-- Set YOUR OWN Beat Saber folder here to resolve most of the dependency paths! -->
    <BeatSaberDir>E:\Program Files (x86)\Steam\steamapps\common\Beat Saber</BeatSaberDir>
  </PropertyGroup>
</Project>

This BeatSaberDir property is also used in a Post Build event, where Counters+ will copy the built file from the source folder to your Beat Saber Plugins folder using the property.

Dependencies

If you plan on adding any new dependencies which are located in the Beat Saber directory, it would be nice if you edited the paths to use $(BeatSaberDir) in Counters+.csproj, to make it easier for myself and other contributors to build the project with the new dependencies.

...
<Reference Include="BS_Utils">
  <HintPath>$(BeatSaberDir)\Plugins\BS_Utils.dll</HintPath>
</Reference>
<Reference Include="IPA.Loader">
  <HintPath>$(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll</HintPath>
</Reference>
...

Custom Counter System

Have you ever wanted to add a counter of your own to Counters+, but you want it to also be its own standalone mod? Counters+ has a solution that allows you to easily implement any Counter of choice into the Counters+ system.

The Custom Counter system is a method of extending Counters+'s large array of counting technology by exposing easy to use methods for creating your own Counters from scratch within Counters+'s system, or re-using an existing Canvas.

Developers who create UI Enhancement mods that take up part of the in-game UI space should probably look into Counters+ integration, as users may have setups that will collide with your mod.

Dependency or No Dependency?

The original Counters+ Custom Counter system was designed with the intention that a hard dependency on Counters+ was never necessary. While the new Custom Counter system keeps that dependency optional, it will be much easier on the side of the developer to create Custom Counters with the intent for a hard Counters+ dependency. With a hard dependency in mind, you do not have to worry about maintaining two separate parts of code; one for if Counters+ is installed, and one where it's not.

Getting Started

To get started, go to your plugin's manifest.json file.

Counters+ 2.0.0's Custom Counter system uses the new BSIPA Feature's system, introduced in BSIPA versions after 4.0.5. If needed, you can grab the latest BSIPA builds here.

Here is an example Custom Counter defined in manifest.json:

"features": {
  "CountersPlus.CustomCounter": {
    "Name": "Example Custom Counter",
    "CounterLocation": "ExampleCustomCounterMod.CustomCounter",
    "ConfigDefaults": {
      "Enabled": true,
      "Position": "AboveCombo",
      "Distance": 0
    },
    "BSML": {
      "Resource": "ExampleCustomCounterMod.TestCustomUI.bsml",
      "Host": "ExampleCustomCounterMod.TestCustomUIHost",
      "Icon": "ExampleCustomCounterMod.william_gay.png"
    }
  }
}

If not defined already, add a features object to the manifest. Unlike previously, this new Features system uses an object, not an array. After this, you want to create a new child object under this, called CountersPlus.CustomCounter.

Name (Required)

The name of your Custom Counter. This displays in the Counters+ Settings Menu, as well as being used as an identifier for various internal components. Be warned! You should come up with a good name, because I do not recommend changing it after it's first public release!

Description

A description for your Custom Counter. Currently unused.

CounterLocation (Required)

The namespace location to the Custom Counter you want to implement. This can be a MonoBehaviour, a class that inherits a Counters+ Custom Counter type, or neither of those.

DO NOT WORRY ABOUT CREATING AN INSTANCE! Counters+ uses Zenject to new up an instance of your Custom Counter when entering a song. This also means you can take full advantage of Zenject in your Custom Counters!

ConfigDefaults

If you wish to provide your own defaults on where the counter will go, override this object. This is a ConfigModel, and is comprised of 3 parts:

  • Enabled which controls, well, whether or not it's enabled in the first place.
  • Position is an Enum of string values that act as a "relative" point to go off of. Valid options are:
    • AboveCombo and BelowCombo
    • AboveMultiplier and BelowMultiplier
    • BelowEnergy
    • AboveHighway
  • Distance is steps taken away from these relative points to determine the final position.

BSML

This is an optional object which describes behavior in the Counters+ Settings Menu. You can skip this object if you do not plan on offering config options, or a custom icon.

Resource

This is a namespace location to a .bsml file, which will be appended underneath the ConfigModel options.

For this BSML file, it is recommended to base your UI off of this template to keep consistent with Counters+:

<vertical xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='https://monkeymanboy.github.io/BSML-Docs/ https://raw.githubusercontent.com/monkeymanboy/BSML-Docs/gh-pages/BSMLSchema.xsd' spacing='1' horizontal-fit='PreferredSize' vertical-fit='PreferredSize'>
     <!-- BSML goes here -->
</vertical>

You may safely leave this blank; if so, no additional content (or host objects) will be appended.

Host

This is a namespace location to a Type, which will handle all UIValues, UIActions, and UIEvents for your provided BSML file.

Like with CounterLocation, Counters+ uses Zenject magic to automatically create this object when it is needed. You can also take full advantage of Zenject inside this object.

Icon

This is a namespace location to an Image, which will override the default Custom Counter icon in the list of all Counters.

I'm unsure of exactly all the supported file formats, but I'm 100% certain .png works, and about 85% certain .jpg works. I wouldn't test your luck with anything else.

Creating a Counter

If you already have a MonoBehaviour that you wish to reuse, then sure, you can plug that into the CounterLocation and Counters+ will gladly pick it up and load it in game. However, I really recommend that you create the Custom Counter from scratch to fully take advantage of all that Counters+ 2.0.0 has to offer.

Counters+ provides a few public classes, in the CountersPlus.Counters.Custom namespace, which you can inherit to speed up your custom counting.

BasicCustomCounter

This is a barebones Custom Counter class that only provides you with a few utility objects (Injected with Zenject), as well as some methods to help get you started.

CanvasUtility

CanvasUtility is a field inside this class that provides you with quick access to functions related to Counters+ canvases, as well as quick and easy text generation.

Settings

This is the CustomConfigModel that is used for your Custom Counter. You can use this, combined with CanvasUtility, to easily create custom text in just one line of code.

CounterInit

CounterInit is an abstract method (Meaning you have to override it) which is called when Counters+ loads the object. This is where you create your counter text, and subscribe to events.

CounterDestroy

CounterDestroy is an abstract method (Meaning you have to override it) which is called when the object is destroyed. This is where you clean up after yourself; unsubscribe from events, and destroy anything that might not automatically be picked up by Unity.

CanvasCustomCounter

If you really want to reuse an existing Canvas, then you can let Counters+ reposition it for you with a few additional lines of code, and a Custom Counter.

This CanvasCustomCounter class inherits BasicCustomCounter, so helpers like the Settings object and CanvasUtility will also make its way over to CanvasCustomCounter.

The CanvasCustomCounter class is designed to look for a defined Canvas object, whether by name or by direct reference, and re-parent and reposition it into the Counters+ system.

CanvasReference

This is a virtual property (Meaning overriding it is optional) which points to a direct Canvas object. You should try using this first. Remember: The Custom Counter you will be using can take full advantage of Zenject. If your mod happens to use Zenject for its own components, you can easily inject your main Canvas into the Custom Counter type.

CanvasObjectName

This is a virtual property (Meaning overriding it is optional) which gives the exact, case-sensitive name to a GameObject with an attached Canvas component. This should be your Plan B. Counters+ will give your Custom Counter 10 shots to find this Canvas object before giving up.

PreReparent

This is a virtual method (Meaning overriding it is optional) that is triggered before Counters+ attempts to search for your Canvas. This is essentially the same as CounterInit, in case you still need to do event subscribing.

PostReparent

This is a virtual method (Meaning overriding it is optional) that is triggered after Counters+ has successfully re-parented your Canvas into the Counters+ system. If there is any repositioning or manual work that still needs to be done, you can do so in the PostReparent function.

Helper Interfaces

But wait, there's more!

If you need to subscribe to basic game events, such as note cutting/missing and score updating, Counters+ has interfaces that expose these events for you, and will automatically trigger them without you needing to go out, find an object reference, and subscribing to these events yourself. One more thing, these interfaces are designed so that, in the case of a breaking game update, Counters+ will only break in one spot. This means that I would only have to fix code in one place, and these interfaces will be fully operational.

These helper interfaces are located in the CountersPlus.Counters.Interfaces namespace.

INoteEventHandler

This interface exposes basic note cut and missing events.

IScoreEventHandler

This interface exposes basic score updating, and max score updating events. Both methods only give out modified scores (As in, score when taking into account the user's modifier bonus).

Adding New Options to Existing Counters

If you feel that an existing Counters+ counter needs a new feature or option, then it might be worth forking Counters+ for yourself and looking into adding this yourself. This short section will cover the 3 main components you need to cover in order to add a new option to Counters+, complete with UI support.

We'll assume that you've already forked Counters+, got the project set up, and fixed any reference issues.

Adding to a ConfigModel

The first thing you should do when adding a new Counters+ option is to add it to an existing ConfigModel.

A ConfigModel is an abstract class located in CountersPlus.Utils.Config.cs which holds configuration data for every counter in Counters+. Each Counter in Counters+ have an inherited ConfigModel class of their own, also located in the bottom of Config.cs. Find the ConfigModel class of the Counter you wish to add an option to, and add it, along with the default value you wish to assign. Do not add to the ConfigModel class itself, as you'll find that option for every single Counter in Counters+. Unless you want to.

Modifying Counter code

With your new Option in hand, go into the Counters folder of the Counters+ solution and modify the counter class you want with that new option. All of the Counters have a settings variable, which you can easily use to access your new setting. If you wish to add an option for all counters, add it to the abstract Counter<T> class.

Settings UI

Now it's time to add your setting to the Counters+ Settings UI. For every counter, their advanced settings are located in two separate places: CountersPlus.UI.BSML.Config for the UI "blueprints", and CountersPlus.UI.ViewControllers.ConfigModelControllers for the code that handles retrieving and setting ConfigModel variables according to the BSML UI elements.

If need be, advanced counter settings (Like lists of various options) are stored in CountersPlus.UI.AdvancedCounterSettings.cs

You can simply copy and paste code from the other BSML files and ConfigModelController classes to add your option to the Counters+ menu.

(Optional) Contributor

If you've made it this far, congrats! A new option has been successfully added to an existing Counters+ counter. Good job! Go ahead and add your name and what you did to the ContributorsAndDonators.cs class, and you will show up in the Contributors list. Please keep your contribution message nice and short to make room for a second row of contributors (and/or to not get cut off by the edge of the screen)

Pull Request

Once you're done, shoot a pull request to the master branch, and I'll look over it and see if it's good enough to merge into master.

Clone this wiki locally