Skip to content

Content Class Documentation

Notest edited this page May 13, 2024 · 20 revisions

Content Provider

Content Provides don't need an empty constructor, but for all intents and purposes of this library they are needed if extra features are wanted. They will never have any of their methods called.

When a Content Provider's GetContent is called the arguments passed to it are:
A ContentEventFrame List you must add a new ContentEventFrame containing ContentEvent, seenAmount, camera and time
The seenAmount which represents the amount of coverage the ContentProvider got by the camera, 0 being the lowest and 1 the highest
The camera used to poll the ContentProvider
And the time in the video the content was polled

As far as I'm aware, only the player holding the camera can or needs to poll ContentProviders, which will be serialized, sent to other players and then deserialized.

Content Event

Every Content Event needs an empty constructor that will be called by the Content Event ID Mapper. They will never have any of their methods called when done so.

Content Event's IDs are ushorts, IDs 1000 ~ 1040 are used by the base game, IDs 0 ~ 999 are skipped (or reserved?) for an unknown reason to me, thus I suggest leaving IDs 0 ~ 1999 free for the base game
A content event's ID needs to be prefixed to the Content Event ID Mapper, otherwise once the CD is uploaded, an error modal will be shown.

Your content value is how much score towards the quota your content will give if your content has a maximum seenAmount and for how many seconds it was filmed. Make it something reasonable as it doesn't seem to be scaled depending on your quota/day.

Bear in mind that your content event will only appear and count once each video depending on the UniqueID, unless MaxSameEventType is changed. Bear in mind that when decompiled MaxSameEventType shows up as 3 but in-game it is 1 as observed with UnityExplorer.

UniqueID

This is an ID that will determine if another content event of the same type/Non-unique ID is a duplicate
This topic is made easier by explaining in-game behaviour:

Each monster in-game has an UniqueID that gets supplied to their ContentEvents, the UniqueID in question is the monster's viewID, meaning that you will be able to record two monsters of the same type and get two content events out of them, even if they're the same type of ContentEvent.

Each player in-game has an UniqueID that gets supplied to their ContentEvents, which is the player's actorNumber, meaning that if you film two separate players dying, you'll get two content events out of them. If you film the same player emoting multiple times you'll only get one content event, given that all emotes have the same non-unique ID.

Each prop in-game has the same UniqueID, meaning that if you film two sunflowers you'll only get one content event, if you film two skulls you'll only get one content event, if you film two bones and two skulls you'll only get two content events (one from the bones and one from the skulls), etc etc...

Serializer and Deserializer

This is for extra data you want to maintain in the process of serialization and deserialization by the ContentBuffer and co. If you do not specify them, the data you had stored in the ContentEvent will be lost in the process.

Content Event ID Mapper

The Content Event ID Mapper's GetContentEventGenerated is just a big hardcoded switch case statement, in which the ushort ID it is called by will return a matching Content Event of same ID. If it is unable to do so, it will throw throw an exception: "Unknown content event id: {id}"
GetContentEvent calls GetContentEventGenerated to try and get a ContentEvent, if null is returned it will check the PropContentDatabase and return the PropContentEvent if found or ArtifactContentEvent if the PropContent is an artifact, setting the former's Content variable to the PropContent found.

For the intents and purposes of this library, it prefix patches GetContentEvent and returns the event assigned at the ID, it will not have special cases for prop content or artifact content.

Content Polling

Every VideoCamera Update() it will TickPoll() the content poller to raycast 2 * 20 times, from top left to the bottom right of the VideoCamera, every time the maximum y position of 20 is reached it will reset back to 0. Aditionally, every .15 seconds it will call CompletePoll() to bring the current y position to 20, raycasting it y * 20 times and then will reset the y position, and null the camera variable, ending the polling (in VideoCamera polling is restarted right after with StartPolling).

Each time it hits a GameObject with a ContentProvider, it will add its seenAmount by one and insert it into a dictionary. The dictionary contains the ContentProvider as a key and its seenAmount as the value

Finally, once it is done polling, it will divide the seenAmount by 400 (i.e. 20*20), normalizing it on a range of 0-1.
And once CompletePoll is called it will iterate through each ContentProvider in the list and call its GetContent method, adding its respective ContentEvent to a ContentEventFrame list, which is then pushed to the ContentBuffer.

ClipTime

This is updated every .15 seconds every time polling starts (reminder that CompletePoll ends polling every .15 seconds and is restarted right after with StartPolling)

Content Buffer

Streaks and PushFrame

Once an event list is pushed to the ContentBuffer, the content event will get checked to see if it is not in currentStreak, where if it is not, it gets added, otherwise it gets pushed to the buffer's content list.

After that, it loops through the currentStreak and checks if the current streak key is not in the pushed contentEvent list, if it is not it'll end the streak for the given event and promptly erases the key from the currentStreak list. In sum, events will be in currentStreak as long as they're being filmed, the only benefit for filming after 15 seconds is getting a better screen coverage of the content.

Each content type has a streak, and once a content type's streak ends it will calculate the score of each content in the streak and pick the one with the highest score to add to the buffer.

PickBest

PickBest immediately ends whatever streak is happening, clears currentStreak, then sorts the buffer by highest score.
Then it removes/prunes every content that goes beyond MaxSameEventType in the buffer, it matches according to the content's UniqueID and ID, meaning that it only leaves the best out of each type of unique content. After which the result is finally set as the ContentBuffer's buffer.

PickBest is called every time a clip ends, then once again by EvaluateRecording where all the buffers get joined, effectively removing/pruning content that do not meet the aforementioned criteria from the entire video at once.
(Once again, bear in mind that MaxSameEventType is 3 when decompiled and 1 in-game)

Score

The quota is represented by a relatively small number called score which then gets converted to views for the end user.

Your content's final score is calculated by how much camera coverage the content got times its content value times how many seconds it was filmed for, the frames are normalized by the curve in a range of 0-1, with 13.05 seconds being the minimum to achieve close to 1 (.99) as 1 is impossible. Specifically, the calculation is: percentageToScreenToFactorCurve.Evaluate(seenAmount) * contentEventValue * streakCurve.Evaluate(contentCount)

The following are the three relevant values for streakCurve:
9.15 seconds for .9509
13.05 seconds for .99 and
15 seconds for .9948981, which is a negligible difference.

Feel free to use the following code to get all streakCurve values:

using Zorro.Core;
for (int i = 0; i < 101; i++)
{
    float num = SingletonAsset<BigNumbers>.Instance.streakCurve.Evaluate((float)i);
    Debug.Log($"{num}, {i}");
}

Views

The score to views conversion works as the following:
GetScoreToViews(score, day) is called, day is converted into run, in which each 3 days will constitute one more run
GetScoreToViewsFromRun(score, run) is called, and it multiplies the score with the run being evaluated by an AnimationCurve

Likes

Likes are simply how many views the respective content got you.

Content Event Frame

This holds a reference to a respective ContentEvent, seenAmount, time and handles one part of serializing and deserializing data in the ContentEvents and its own data (contentEvent ID, seenAmount, etc...)

The ContentEventFrame also handles getting the score from a ContentEvent, this is one part of the score calculation as it does not include contentCount
This one part being: percentageToScreenToFactorCurve.Evaluate(seenAmount * contentEventValue)

Content Evaluator

The ContentEvaluator is called by RPC_UploadFlashcard, it joins the buffers of all the clips into one buffer and calls PickBest in that buffer in the end.