JSON quick guide (v1.9)

This advanced feature requires familiarity with:
- Unity concepts like game objects, components, and object path
- JSON data format and data type like boolean, integer, float, etc.
- Using an in-game editor to find the values you want (Unity Explorer, Runtime Editor, etc.)


1. Introduction

Monkey allows users to create their own custom UI elements and mods via JSON.  

When starting the game, monkey.json will be loaded first.  It contains the most requested
features and serves as a documented reference on how to use the system.

Any valid JSON file located in BepInEx\config\Monkey\Mods will then be loaded in 
alphabetical order and added to the custom section of the UI.

While you can modify monkey.json directly, this will be overwritten each time you 
upgrade.  It is recommended to create your own JSON mod, and drop it in the mods 
folder.


2. Example

Each custom UI element is described by a JSON object, with properties that looks like this:
{
    "Name": "Throat Wear Speed",
    "Type": "Slider",
    "Level": 2,
    "Min": 0,
    "Max": 100,
    "Value": 50,
    "Group": "my_group_name",
    "Refresh": "Female",
    "Path": [
        "/Female Avatar Root Haired V0.826/Controladores/HolesControllers/BocaSexController",
        "BocaSexController",
        "holeDesgasteCalcule/generalMod"
    ],
    "Note": "Controls the amount of wear before deep throating"
}

This will create a UI 'slider' displayed at indentation level 2, with a value range of [0-100] and
set initially to 50.  Refresh will overwrite the game value when a new female model enters the 
scene. The slider is linked to the game variable described by the path.  

A JSON object contains a series of properties defined as key:value pairs.  Properties can be in 
any order.  Only Name, Type and Path are required.  Optional fields will default to values
in parenthesis.

- Name      Text displayed in the Monkey UI
- Type      Button, Enum, Gene, Label, Menu, Slider, or Toggle
- Level     The element visual indentation level                (0)
- Min       The slider minima                                   (0)
- Max [1]   The slider maxima                                   (1)
- Value     Initial value for the slider                        (none)
- Group     Allow multiple elements to be grouped together      (none)
- Refresh   How the value updates when an event occurs          (female*)
- Path      The location of the game variable linked to the UI element
- Guid      A global unique identifier used for setting/getting properties via API (null)

(*) Monkey uses special rules to insure compatibility. See the Refresh section for more information.


3. Path

A JSON object is linked to a game variable via the path array.  This is the most complicated part 
of creating your own UI element.  It requires you to locate the variable using an editor, then
properly transcribe the game object path, component name and member path.

A path contains three elements:
3.1 The unity game object path and name
    The path is in the form of element_1/.../element_N, where each entry mimics the parent/child 
    unity object hierarchy.
    
    > If the game object has a unique name, you can use it directly like "Indoor Spot Light"

    > If the game object has a unique tag, you can use it between brackets like:
        "<MainFemaleCharacter>"

        Note: only one tag per path and needs to be the first element

    > You can perform searches using '*', like:
        "<MainFemaleCharacter>/FemaleAnimator/*/CC_Base_Body".

        Note: Only one search (*) per path, and requires at least one element following the star

    > You can use a leading '/' to specify names from the unity root

    Searches are CPU intensive.  The game contains tens of thousands of game objects, so be as 
    specific as possible.  For example:
        "<MainFemaleCharacter>/*/CC_Base_Body" will work as above, but cost 4x more.
        "*/CC_Base_Body" will retrieve the wrong object (non-unique name) and cost 100x more.

    If more than one game object matches, the first one is returned, with a console
    warning where possible as Unity doesn't guarantee object ordering between runs.
    
3.2 The unity component name if any
    If the data member you need is inside the game object, you can leave this blank ("").

3.3 The path to the data member 
    The path is in the form of element_1/.../element_N
    Monkey should handle all common data type except methods invocation with parameters


4. Group

Groups allow you to set multiple values using only one visible UI control.  

All elements sharing the same group name are part of the group.  The first JSON element with 
a given name will be the head control.   It will be the only one displayed in the UI and its
properties will be used as default for the entire group.

Example:
[
    {
        "Name": "Red",
        "Type": "Slider",
        "Level": 1,
        "Min": 0,
        "Max": 1,
        "Group": "IndoorGrayscale",
        "Refresh": "Female",
        "Value": 0.75,
        "Path": [
            "Indoor Spot Light",
            "Light",
            "color/r"
        ]
    },
    {
        "Name": "Green",
        "Type": "Slider",
        "Group": "IndoorGrayscale",
        "Path": [
            "Indoor Spot Light",
            "Light",
            "color/g"
        ]
    },
    {
        "Name": "Blue",
        "Type": "Slider",
        "Group": "IndoorGrayscale",
        "Path": [
            "Indoor Spot Light",
            "Light",
            "color/b"
        ]
    }
]

This links the red, green and blue sliders together in the 'IndoorGrayscale' group.  

Only the red slider will be displayed as it's the first in the JSON using this group name.  All the
other sliders will inherit the properties from the head control.

When loading, all three sliders will be initialized to 0.75, and they will reapply their value
when a new model arrives.  When you adjust the red slider, the values are propagated to all group
sliders.

You can override the value and refresh property on each element as needed.  If you don't specify
anything, the head control values will be used.

Groups can only have elements of the same type, with two exceptions: buttons and genes with 
a value property can be added to any group.  

When the head control value changes, buttons will perform their action as if the user pressed 
it.  Genes will be set to the value specified in the JSON.  

This mechanism allows you to chain sequence of actions when a control changes, or in response
to a refresh event.


5. Delay

Delays allow you to postpone the processing of a JSON element.  By default, when you press
on a UI element, or when a refresh event occurs, the JSON objects associated with the element
will get processed on the same frame.

There are several situations in game where you may want to delay processing.  For example, when 
a new model enters the scene the game will perform operations over several frames.  If you try 
to read or write values during that time, the value will be incorrect or overwritten.

This feature is used to delay refreshing all interpretations sliders, as they are computed within
the first 20 frames of a model arriving.

If an object has a delay, the corresponding action will occur on frame + delay.  This works during
refresh, or when an element is part of a group activated by the user.

6. Enum

Internally the game uses many C# enumeration, which are sets of named constants each with their 
own integral values.  Before using this, I'd recommend you read the C# documentation and 
understand how they work.

This element type will allow you to create a slider that will display and set enums properly
using their name or a name of your choosing.

For example, this is one of the commonly used game enumerations:
	public enum HumanTraitScore
	{
		normal,
		alto,
		muyAlto,
		bajo,
		muyBajo
	}

The integer values are implicitly defined here, with normal = 0, alto = 1, muyAlto = 2, bajo = 3, muyBajo = 4.

Monkey provides two additional properties to deal with enums.  Only one of the following can be used per type.


6.1 Sort

You can specify how the enums element will be presented to the user. 

By default, the order of the element in the game will be used.  You can however sort the element by their value
or by their name.

In the example above:
Game order: normal, alto, muyAlto, bajo, muyBajo
Sort by Name: alto, bajo, muyAlto, muyBajo, normal
Sort by Value: same as game order here.  

Note that enums may not be consecutive, sequential or may contain negative value.  Sort may be needed
with negative values as C# uses unsigned magnitude by default.


6.2 Map

One issue with the enum example above is that the game order isn't logical, and sorting won't fix that.
For a slider, we would want to go from muyBajo, bajo, normal, alto, muyAlto; from very low to very high.

You can solve this by using a 'map', telling Monkey both the order and display name that should be used.
    "Map": {
        "muyBajo": "Very Low",
        "bajo":    "Low",
        "normal":  "Normal",
        "alto":    "High",
        "muyAlto": "Very High"
    }  

This will create a slider from "Very Low" to "Very High" and set the internal enum properly.

Important Notes: 
- The spelling and case of enums must match exactly 
- All game enum entries per type must be defined
- Only one map per enum type is needed.  Duplicate maps will be ignored.


6.3 Flags

If a game enum has a [flags] attribute, it means it is considered a bitfield.  
Monkey supports setting flags by value or via C# string representation "field_1, ..., field_n".



7. Gene

The gene type allows you to easily create a slider to control any of the 1200+ genes 
supported by the game.

To obtain the list of genes, open a saved model in a hex editor, and extract 
the JSON at the end, located after the word "TValle".

For example, this gene controls the eyelash color:
    {
        "m_id": "Coloreador_EyeLashes_",
        "m_data": {
            "keys": [
                "Index",
                "Symetria",
                "Volatil",
                "Sensible",
                "IndexCount",
                "IndexMaxCount",
                "Mods"
            ],
            "values": [
                "255",
                "0.666",
                "False",
                "False",
                "0",
                "0",
                "{\"c\":[0.345012366771698,0.6478950381278992,0.36114975810050967,0.1843927800655365]}"
            ]
        },
        "m_children": []
    },

The gene name is stored in the m_id field.  In this case it's called "Coloreador_EyeLashes_".   The
gene values are stored in the array {\"c\": [ values ]}.  In this case the gene has 4 values, 
corresponding to the color vector components Red, Green, Blue and Alpha.

All gene in a save are float ranging from 0 to 1.  But what a value represents and how it affects 
the game can get complicated.  In the case of color, the game uses a simple lerp from 
0..1 to 0..255 per channel.  

The game currently doesn't implement eyelashes colors and is excluded Monkey.  If this changes,
you can now easily add support for it.  The following JSON object will control the gene blue component:

    {
        "Name": "Eyelashes Color Blue",
        "Type": "Gene",
        "Level": 1,
        "Path": [
            "",
            "",
            "Coloreador_EyeLashes_/[2]"
        ]
    }

You only need to specify the gene name, and the array index of the value.  Indexes
are zero based, so 0 is red, 1 is green, 2 is blue and 3 is alpha.  In no index is 
specified, 0 is used by default.

While adding new slider to control genes is easy, understanding what each gene and corresponding
value may require research.  Model behavior is usually driven by combinations of several 
genes, so isolating gene effects can get tricky.
 

8. Refresh

Refresh allows control over how JSON object values update when specific events occur.

Monkey caches all values in memory as keeping many JSON objects and game values
synchronized each frame is very CPU intensive.  This means however that the game and 
object values can diverge.  

To synchronize those value, Monkey perform a Refresh of all the object value when certain game 
event occurs - load/unload of an asset, a new model entering the scene, JSON reload (F9), etc.

Each object will either be updated with the game value or write their own value to the game.  
Buttons that perform actions will get triggered as well.

Refresh can support any combination of the following values:
    None    No refresh will occur regardless of events
    Read    Use the game value unless changed by the user then overwrite it
    Female  The object will refresh when a new model enters the scene
    Load    The object will refresh when its JSON script is loaded
    Unload  The object will refresh when its JSON script is unloaded
    Frame   The object value will be read each frame (this is CPU intensive)

If no refresh property is specified, Monkey use the following default:
 - Button default to None
 - Genes default to Read
 - All others: Female

 Please note:
- Elements in a group inherit the refresh property of the head element
- Pressing F9 will trigger both a 'load' and 'female' event
- Values will only overwrite if they have been changed by the user, or if a value property
  is set.
- User interactions will trigger all group elements regardless of refresh value


9. External API access

Monkey exposes some functionality via an API component attached to the Monkey game object.
This provides external mods with a consistent and supported interface.  While the underlying
code can change, the exposed Api will remain the same.

You can view the methods via an editor or by using dnspy and looking at the Api.cs module.

Monkey.json female position is an example of how you can use this directly from a json mod.


10. Additional notes:

- You can reload the JSON file using F9 without exiting the game during an interview.
- For performance reasons Monkey caches all game values.  If you have multiple controls 
  referring to the same element, or change values with an external editor, the UI will 
  not reflect this.
- Spelling and capitalization are important. "R" and "r" is not the same and Monkey may not
  be able to locate elements with incorrect spelling.  Some names also have 
  leading/trailing white space which must be included to work.
  
While I tried my best to handle common mistakes, if you break the JSON enough it may crash 
Monkey and or the game.  So... keep a backup around!

Happy modding!
-T-
