Skip to main content

πŸŒƒ Summary

Hello!

I've gone through a lot of projects lately, and here's a summary of what has been happening.

My development time is divided between the maintenance of Free tools, Patreon exclusive tools, and the continuation of research.

(This page is a clone of the post published on Patreon)

Free tool updates πŸŒŠβ€‹

β˜€οΈ Animator As Code V1 has been released. This update brings Sub-State Machines, Blend Trees, support for non-VRChat projects, better integration with non-destructive workflows, integration with Modular Avatar, support for VRCAnimatorPlayAudio, and prettier support for VRCParameterDriver. The V1 API is now considered stable, and there will be no more breaking changes. (Read more)

β˜€οΈ Last month, I've released LetMeSee, a new tool that lets you see your content in VR, with the Unity Editor in Edit Mode. I had initially created this tool so that I could test VR toon shaders on a non-VRChat Unity project that uses the Universal Render Pipeline, but it works just as well for more traditional projects. (Read more)

βš™οΈ In Prefabulous Universal, Generate Twist Bones now creates VRC Constraints if the VRChat SDK is installed. In addition, Delete Polygons, Assign UV Tile, and Replace Textures should now run before VRCFury.

β˜€οΈ It is almost guaranteed that in the next few months, some complex clothing (i.e. kimonos and yukatas) will be sold with VRC Constraints directly in them. For this reason I've added a new component Convert VRC Constraints to Unity Constraints in order to provide options for users who wish to use avatars for other Unity applications, VTubing apps, or social VR platforms.

✨ Animation Viewer now supports CTRL-K, the Unity Search window. (Read more)

Patreon exclusive updates ⭐​

✨ Double Hip Tracker is receiving an update in V1.3.0 that changes the behaviour of the double trackers. It's an option, default ON.

In short, we will measure the distance that separates the two trackers. If that distance changes too much, one of the trackers probably flew off. We will now try to detect that, in addition to the usual method. (Read more)

Also, when both trackers are lost, the double tracker will now freeze in place, instead of flying off.

There are other changes described in the full update post.

✨ Vixen is receiving a small update in V1.3.0 so that you can change the boundary values of PhysBones, Contacts, and OSC. This is especially useful to change the range of activation for a PhysBone angle, the range of a PhysBone squish, or permit using two different ranges using one proximity Contact. That, along with a few fixes. (Read more)

✨ In IconGen V1.1.0, you can now export all of your decorated icons to PNG, so that you can redistribute decorated icons to other users. (Learn how)

If you're a Patreon supporter, you should download them now!

More updates are currently being worked on for FaceTra Shape Creator, Starmesh, and Vixen.

Research πŸ§ͺ​

πŸ§ͺ Project H-View: I have shared on GitHub a personal project of an ImGui.NET application capable of displaying the entire Expressions Menu into a compact layout, and makes extensive use of OSCQuery. (Read more) (GitHub)

It also has an early implementation of that ImGui application being rendered directly into a SteamVR Overlay! (Video)

I've always wanted to try controlling tools like VRCLens and VirtualLens2 as an OSC application, and also learn how to build an ImGui overlay using the OpenVR API, so I'm happy having finally taken the time to do this.

If you are a C# developer, you may be able to make use of that project (it's under the MIT License).

πŸ§ͺ Project Nochat: UdonSharp has been mainstream for so long, we may have forgotten that C# files (.cs) were never intended to be executed directly inside VRChat worlds. The ability to write C#, and have it work on VRChat worlds, is a feature that is entirely community-driven.

Therefore, VRChat prefabs that use UdonSharp do not depend on any of VRChat's intellectual property at all.

I've taken the opportunity to try running UdonSharp prefabs in a plain Unity project with VR controls, without VRChat. It works! Zero lines of executable code from VRChat needed, as Udon is completely unnecessary. (Video)

The fact that UdonSharp prefabs do not require VRChat to run is relevant as I want to create and enable experiences outside the limitations of the VRChat platform, so porting content could make it possible to experience content we're already familiar with, but with different virtual environment capabilities. I hope I'll be able to share more with you on this subject. (Read more)

πŸ§ͺ Project Myrddin: This project is very similar to Project Nochat, however, this one is an attempt to run the VRChat SDK without Udon, with the option to run ClientSim with VR controls. UdonSharp components would run as C#, without the Udon VM. This way, one may be able to use traditional IDE debugging features directly on UdonSharp content (breakpoints, instruction stepping, hot code reload without leaving Play mode, etc.).

VR controls in-editor would let you grab world pickups with actual VR controllers and interact with them, opening the possibility of iterating faster on VR content just like a normal Unity game developer would. (Video)

Thank you ⭐​

Your support makes it sustainable to continue the development of all these projects as a full-time occupation. Thank you so much! ⭐

✨ Vixen V1.3.0

Custom bounds​

You can now customize the bounds in OSC Floats, Contacts, and PhysBones.

  • By default, it is always between 0.0 and 1.0
  • This can be used to change the range of activation for a PhysBone angle, the range of a PhysBone squish, or permit using two different ranges using one proximity Contact.
  • To use it, enable Use Custom Bounds in the control settings.

Fixes​

  • Prevents the editor from hanging up when the user mistakenly pastes a page long of unrelated content in the search field.
    • The search query is now limited to 100 characters max.
  • The Vixen icon will no longer show up in the Scene view.
  • User-created Modular Avatar Menu Installer components can now be properly added directly to a Vixen Control.
  • Existing Modular Avatar Menu Installer components can now be properly detected even if it is inactive in the hierarchy.
  • Vixen Control should no longer cause Modular Avatar Menu Item to print harmless error messages when it is deleted during a build.
  • Vixen used to add a Vixen BSG phase to NDMF. This has been removed as it was never used.
    • Vixen BSG (BlendShapeGenerator) was what is now known as Starmesh.
  • Changed the way Vixen detects how VRChat SDK is installed.

✨ IconGen V1.1.0

  • Added a button to export all icons to PNG.
    • In the IconGen asset file, go to Show Advanced > Export all to PNG...

✨ Double Hip Tracker V1.3.0

This update introduces two changes in the behaviour of the double trackers:

Changes:

  • Add "Freeze when both trackers are lost" checkbox: If both trackers of any given double tracker are lost, then the tracker will freeze in place.
    • This new behaviour may be more in line with VRChat's current handling of single trackers being lost. The previous behaviour used to be that the tracker would fall back to the degraded data of the last known working tracker.
    • On previous versions, this checkbox would be effectively OFF. The default behavior is now for this checkbox to be ON.
  • Add a new method to detect when trackers are flying off, even when SteamVR hardware trackers are reporting themselves as healthy.
    • In short, a new option called Detect Separation (default ON), will measure the distance that separates the two trackers. If that distance changes too much, one of the trackers probably flew off. We will now try to detect that, in addition to the usual method.
    • There is a dedicated page that explains this new option, and when to turn this off.
  • Add Auto-detect hip trackers checkbox: You can now disable automatic detection of hip trackers.
    • When you disable automatic detection, it works identically to the Double Chest Tracker.
    • You can use this if you want to always use the same two hip tracker serial numbers without needing to shake your hips, but beware, you may have to press the "Realign Trackers" button if you happen to wear your double hip tracker belt after starting the application.
  • The UI is now organized across tabs.
    • Align trackers tab: For normal use. It has the buttons to realign trackers.
    • Edit trackers tab: Configure which Double Trackers are enabled, and their configuration.
    • Other tab: Provides rarely used options.
  • Add Send HMD position to VRChat (default OFF).
  • Add vertical offset options: Some users have been having issues with the OSC tracker having some sort of vertical offset.
    • No solution had been found for this, so the option to offset the tracker vertically stays.
    • This option will be used during support requests to troubleshoot mismatching tracking space issues.

β˜€οΈ Convert VRC Constraints to Unity Constraints (Prefabulous for Platform Conversions V2.0)

β˜€οΈ New component: Convert VRC Constraints to Unity Constraints​

A new component, Convert VRC Constraints to Unity Constraints, has been added to Prefabulous for Platform Conversions.

It converts VRC Constraint components back to native Unity Constraints, to the extent applicable.

  • VRC-specific features (local axis, freeze to world) are not supported.
  • The VRChat SDK assemblies and DLLs do not need to be installed in the project, as long as the types exist, so they could be mere stubs.

βš™οΈ Prefabulous Universal V2.1

  • Modify the behaviour of Generate Twist Bones on VRChat projects:
    • If VRChat 3.7.0 or above is installed in the project, we will generate a VRC Aim Constraint component instead of a Unity Aim Constraint.
  • To try resolving an execution ordering issue with VRCFury, Delete Polygons, Assign UV Tile, and Replace Textures no longer run in the Optimizing phase.

βš™οΈ Prefabulous for VRChat V2.1.0

  • Allow installation with VRChat 3.7.x.
  • Allow installation with Animator As Code 1.1.x.
  • Allow installation with Animator As Code - Modular Avatar functions 1.1.x.
  • Allow installation with Animator As Code - VRChat 1.1.x.
  • An additional component Accurate Eye Tracking Transforms is made available for users who wish to try, but it is not documented as it is not ready.

β˜€οΈ Animator As Code V1

🌊 Any Platform

I am releasing Animator As Code V1.

Starting from V1.1.0, all Animator As Code V1 packages are leaving Alpha/Beta.

Animator As Code V1 can now be safely used in public projects that wish to do so.

Compatibility with projects that contain Animator As Code V0​

Animator As Code V1 is designed to be installed even in projects that already have Animator As Code V0.

Animator As Code V0 will not be overwritten by Animator As Code V1. Both installations will act as separate, non-conflicting installs. Projects like FaceEmo will continue to function properly.

In fact Animator As Code V1 has already been extensively used in my own tools (Prefabulous, ComboGestureExpressions, Vixen).

There is no real need to migrate between V0 and V1, if V0 already provides all the functionality you need in your project.

If you choose to migrate, V0 and V1 are almost identical.

Features in V1 compared to V0​

Sub-State Machines​

  • Pull major contributions from @galister which:
    • Adds support for sub-state machines, which is important because it enables the creation of states that evaluate multiple transitions within one frame, which is not possible to do without sub-state machines (with one exception).
      • This trait is already extensively used in ComboGestureExpressions V2 and above.
    • Share functionality of state and sub-state machines.
    • Share functionality of Int and Float parameters together.
// When using Sub-State Machines, the Sub-State Machine will evaluate all transitions until
// it resolves a destination state within one single frame.
// This means it can traverse multiple transition conditions at once, no matter how nested
// the Sub-State Machine is.
//
// This is not doable with just states, so Sub-State Machines have a functional value beyond mere organization.
var a = layer.IntParameter("IntA");
var b = layer.IntParameter("IntB");
var rootSsm = layer.NewSubStateMachine("UsingNestedSubStateMachines");
for (var i = 0; i < 16; i++)
{
// A Sub-State Machine can have other Sub-State Machines created inside them.
// TransitionsFromEntry creates a transition between `subSsm` and the Entry node of the Sub-State Machine it belongs in.
// Exits creates a transition between `subSsm` and the Exit node.

var subSsm = rootSsm.NewSubStateMachine($"A = {i}");
subSsm.TransitionsFromEntry().When(a.IsEqualTo(i));
subSsm.Exits();

for (var j = 0; j < 16; j++)
{
var state = subSsm.NewState($"A = {i} and B = {j}");
state.TransitionsFromEntry().When(b.IsEqualTo(j));
state.Exits()
.When(a.IsNotEqualTo(i))
.Or()
.When(b.IsNotEqualTo(j));
}
}

// This creates a transition between the Sub-State Machine and itself.
// When that Sub-State Machine exits, it will re-enter itself.
rootSsm.Restarts();

Packaging, Non-VRChat projects, and non-destructive​

  • Make it usable in non-VRChat avatar projects.
    • VRChat-related functionality is now exposed as extension functions in a separate package.
    • Also, separate destructive functions and non-destructive functions.
    • Since this no longer requires a VRChat project, this also means it may now be usable in VRChat world projects.
// Animator As Code V1 no longer requires VRChat (compared to V0).
// VRChat-specific functions have been moved to extension methods.
// If you want to use VRChat Avatars functionality, add the `Animator As Code V1 - VRChat` package, and do the following:
//
// Add the following import, which contains extension methods:
using AnimatorAsCode.V1.VRC;

// To access VRChat parameters, use the following extension method:
var vrcAv3 = layer.Av3();
// To access VRChat assets, use the following extension method:
var vrcAssets = aac.VrcAssets();

layer.NewState("UsingVRChat")
.WithAnimation(vrcAssets.ProxyForGesture(AacAv3.Av3Gesture.HandOpen, false))
// VRChat State Behaviours are created through extension methods located in namespace `AnimatorAsCode.V1.VRC`
.TrackingAnimates(AacAv3.Av3TrackingElement.RightHand)
.Driving(driver => driver.Sets(layer.BoolParameter("A"), true))
.TransitionsFromEntry()
.When(vrcAv3.GestureRight.IsEqualTo(AacAv3.Av3Gesture.HandOpen));
  • Make it more usable in non-destructive components.
    • It is already in use in Prefabulous and Vixen.
  • Move to packages, for distribution using VCC (and now, ALCOM).

BlendTrees​

layer.NewState("BlendTrees").WithAnimation(aac.NewBlendTree()
.FreeformDirectional2D(layer.FloatParameter("X"), layer.FloatParameter("Y"))
.WithAnimation(aac.NewClip("Center"), 0, 0)
.WithAnimation(aac.NewClip("Up"), Vector2.up)
.WithAnimation(aac.NewClip("Right"), 1, 0)
.WithAnimation(aac.NewClip("Down"), 0, -1)
.WithAnimation(aac.NewClip("Left"), -1, 0)
);
var one = layer.FloatParameter("One");
layer.OverrideValue(one, 1f);
layer.NewState("Direct BlendTree").WithAnimation(aac.NewBlendTree()
.Direct()
.WithAnimation(aac.NewClip("DrivenByA"), layer.FloatParameter("A"))
.WithAnimation(aac.NewClip("AlwaysOn"), one)
.WithAnimation(aac.NewBlendTree().Simple1D(layer.FloatParameter("B"))
// In Animator As Code, it is safe to declare points in a Simple1D blend tree in a different order (i.e. 0, 1, -1).
// (In native blend trees, it would not have been safe to do so)
.WithAnimation(aac.NewClip("Zero"), 0)
.WithAnimation(aac.NewClip("Positive"), 1)
.WithAnimation(aac.NewClip("Negative"), -1)
, one)
);

Create Animator Controllers​

  • Create new Animator Controllers. This is made specifically for non-destructive workflows, where additional features are built separately, and then merged by a framework (i.e. Modular Avatar Merge Animator, or VRCFury Full Controller).
  • Animator As Code V1 has a separate package specifically to create Modular Avatar components, see the section later on this page.
// When creating a new controller, the ContainerMode of the configuration usually needs to be
// set to either `Container.OnlyWhenPersistenceRequired` or `Container.Everything`.
//
// This is because it is not possible to add state behaviours to states unless the Animator Controller
// is already persisted in the asset database.
var controller = aac.NewAnimatorController();
var fx = controller.NewLayer();

VRChat-specific features​

AnimatorPlayAudio​

AudioSource source = MyAudioSource(); // This can be a string instead.
AudioClip[] clips = MyAudioClips();
layer.NewState("UsingAudio")
.Audio(source, audio =>
{
// Get the PlayAudio object if there's a need to edit it directly.
VRCAnimatorPlayAudio vrcAnimatorPlayAudio = audio.PlayAudio;

// By default, a PlayAudio created through AAC does nothing (unlike a manually created behaviour)
// so you need to invoke anything that is relevant.
audio
.SelectsClip(VRC_AnimatorPlayAudio.Order.Random, clips)
.SetsLooping()
.RandomizesPitch(0.8f, 1.2f)
.RandomizesVolume(0.5f, 1f)
// "Replays" means Stop and Play.
// "StartsPlaying" means just Play.
// "StopsPlaying" means just Stop.
// To do neither Stop nor Play, don't invoke anything.
.StartsPlayingOnEnter()
.StopsPlayingOnExit();
});

Prettier Parameter Drivers​

  • As VRCAvatarParameterDriver has evolved over the years, there is now a dedicated AacFlState.Driving(...) function, which takes a lambda expression as a parameter.
  • If you invoke AacFlState.Driving(...) multiple times, it will create multiple VRCAvatarParameterDriver behaviours on the same state. One may be local, the other not.
layer.NewState("UsingDrivers").Driving(driver => driver
.Copies(layer.FloatParameter("CopySource"), layer.FloatParameter("CopyDestination"))
.Sets(layer.FloatParameter("Set"), 2.3f)
.Increases(layer.FloatParameter("IncreaseBy2"), 2f)
.Decreases(layer.FloatParameter("DecreaseBy3"), 3f)
.Randomizes(layer.FloatParameter("Randomizes"), 0f, 100f)
.Randomizes(layer.BoolParameter("RandomizesBool"), 0.25f) // 25% chance of being true.
.Remaps(layer.FloatParameter("RemapsSource"), 0f, 2f, layer.FloatParameter("RemapsDestination"), 2f, 4f)
// This creates a second VRCAvatarParameterDriver in the same behaviour.
).Driving(driver => driver
.Sets(layer.FloatParameter("SecondDriver"), 100f)
.Locally() // Only this second VRCAvatarParameterDriver is local.
);

Integration with Modular Avatar (MaAc)​

Using the Animator As Code V1 - Modular Avatar functions, it can create Modular Avatar components.

  • Newly created Animator Controllers can be assigned into a new Modular Avatar Merge Animator.
  • Synced Bool parameters (1 bit) can be created using the same Float parameters objects used in Animator As Code, through Modular Avatar Parameters.
var ctrl = aac.NewAnimatorController();
var fx = ctrl.NewLayer();

var toggleFloatParameter = fx.FloatParameter("MyToggle");

fx.NewState("BlendTree")
.WithAnimation(aac.NewBlendTree().Simple1D(toggleFloatParameter)
.WithAnimation(aac.NewClip().Toggling(myObject, false), 0)
.WithAnimation(aac.NewClip().Toggling(myObject, true), 1)
)
.WithWriteDefaultsSetTo(true);

// Create a new object in the scene. We will add Modular Avatar components inside it.
var modularAvatar = MaAc.Create(holder);

// By creating a Modular Avatar Merge Animator component,
// our animator controller will be added to the avatar's FX layer.
modularAvatar.NewMergeAnimator(ctrl, VRCAvatarDescriptor.AnimLayerType.FX);

// We use a float in the animator blend tree, but we declare it as a bool
// so that it takes 1 bit in the expression parameters.
// By default, it is saved and synced.
modularAvatar.NewBoolToFloatParameter(toggleFloatParameter).WithDefaultValue(true);

Examples updated with NDMF integration​

The documentation includes an example on how to integrate with Non-Destructive Modular Framework using a plugin.

Changes in V1.1.0 compared to V1.0.99xx​

Animator As Code V1.1.0 contains breaking changes compared to Animator As Code (Alpha) V1.0.99xx.

This list does not contain the breaking changes between V0 and V1, please see the migration guide for this.

Compared to 1.0.99xx:

  • Commit breaking changes to fix inconsistencies in the API:
    • ⚑ (BREAKING) AacFlController.AnimatorController is no longer settable.
    • ⚑ (BREAKING) Replace public readonly fields with get-only properties.
    • ⚑ (BREAKING) Rename AacFlSettingObjectReferenceKeyframes to AacFlSettingKeyframesObjectReference.
    • ⚑ (BREAKING) Make constructors non-public.
      • WARNING: The AacFlSettingKeyframes constructor will be made private/internal in V1.2.0.
      • For compatibility reasons it remains public for the duration of V1.1.x.
  • Fix inconsistencies in the API:
    • Rename AacFlState.WithMotionTime to AacFlState.MotionTime.
    • Rename *Percent to *Normalized.
    • Add additional single-valued and array overloads.
    • Make Component[] methods null-element safe.
    • Add AacFlSettingCurveObjectReference.WithUnit to be on-par with AacFlSettingCurve.
    • Add AacFlSettingCurveColor.WithUnit to be on-par with AacFlSettingCurve.
  • Inline documentation pass.
  • Update LICENSE: Add galister for major contributions.
  • Accomodate new VRCAnimatorPlayAudio requirements:
    • Nodes need to know the Animator Root, so that relative paths can be resolved during the creation of State behaviours (i.e. Relative path of an AudioSource).
    • Nodes need to have the ability to create a New Behaviour, even if one already exists.
  • Functional fixes:
    • Fix AacFlState.WithCycleOffset(AacFlFloatParameter floatParam) now correctly enables the parameter.
    • Fix Any state transitions will be created from SSMs:
      • Due to an implementation error, creating Any state transitions previously did not have any effect in the graph.
      • This now creates Any state from the root machine.
      • Sub-state machines "cannot" have Any state transitions created directly from them.
      • Internally, Any always comes from the root state machine, but visually in the graph, it will come from the sub-state machine.
    • Make sure State and SSM names don't contain a period '.':
      • If the name of a state contains a period ".", it can cause the animator to misbehave, so sanitize it.
      • Transitions would not work properly during the runtime execution of the animator.
      • Apparently this is because sub state machines internally use the dot as a separator.
      • Sanitize the name so that menu state names such as "J. Inner" don't mess things up.
    • Fix calling Or() after TransitionsFromEntry().When(...) no longer fails:
      • Calling Or() after TransitionsFromEntry().When(...) used to fail due to an unexpected internal state.
      • This was due to the implicit conversion operator, which converted a null AnimationState to a AacTransitionEndpoint containing null in it.
      • Fix this by returning null in the implicit conversion operators.
  • Add AacAccessorForExtensions:
    • Add static class AacAccessorForExtensions:
      • Provides methods for use by extension functions, exposing methods departing from normal fluent interface usage.
    • Prepare to make methods marked "Not for public use" private starting from V1.2.0.
    • Due to their active use in other packages, it is not immediately private.

Other notes:

  • Generated assets will now be created with the following prefix: "zAutogenerated/", which may group the animations together in some editor views.
    • This change is a derivative of a suggestion by nullstalgia in the CGE repository.

Future breaking changes in V1.2.0​

  • The AacFlSettingKeyframes constructor will be made private/internal in V1.2.0.
    • For compatibility reasons it remains public for the duration of V1.1.x.
    • It is already marked as obsolete in V1.1.x.
  • The methods AacFlBase.InternalConfiguration and AacFlBase.InternalDoCreateLayer will be made private/internal in V1.2.0.
    • For compatibility reasons it remains public for the duration of V1.1.x.
    • It is already marked as obsolete in V1.1.x.
    • The class AacAccessorForExtensions replaces it.

These are likely going to be the last breaking change in V1's lifetime.

βš™οΈ Auto-reset OSC config V1.1.5

  • Allow installation with VRChat 3.7.x
  • If there are no bugs reported with VRChat OSC config capabilities for a few months following the August 2024 update, this package will be updated with no code and obsoleted.

βš™οΈ ComboGestureExpressions V3.2.1

  • Allow installation with VRChat Avatars SDK 3.7.x
  • Allow installation with Animator As Code 1.1.x
  • Allow installation with Visual Expressions Editor 2.x
  • Allow installation with Animation Viewer 2.x

βš™οΈ Prefabulous for VRM V2.0.1

  • Compilation should no longer fail in non-VRChat projects that still have the VRC_SDK_VRCSDK3 scripting define around.
    • Components now extend VRChat's IEditorOnly class only if the VRChat Avatars SDK is installed.

βš™οΈ Prefabulous Universal V2.0.3

  • Compilation should no longer fail in non-VRChat projects that still have the VRC_SDK_VRCSDK3 scripting define around.
    • Components now extend VRChat's IEditorOnly class only if the VRChat Avatars SDK is installed.
  • Delete Polygons should now run before AAO.

βš™οΈ Blendshape Viewer V2.1.2

  • Fix layout errors should no longer be produced when the search returns 0 results.
  • Prevents the editor from hanging up when the user mistakenly pastes a page long of unrelated content in the search field.
    • The search query is now limited to 100 characters max.

βš™οΈ Property Finder V2.0.4

  • Prevents the editor from hanging up when the user mistakenly pastes a page long of unrelated content in the search field.
    • The search query is now limited to 100 characters max.

✨ Animation Viewer V2.1

  • Add support for CTRL-K Unity Search window.
  • The "Animation Viewer" search provider must be enabled in that CTRL-K search window (three dots at the top right).
  • Animation clips will show up in double in that Search window, as this version does not modify the stock Project search provider.
  • The prefix anim:<your search query> may be used in your search query to only show Animation Viewer results.

βš™οΈ LetMeSee V1.1.3

  • Continue rendering when Play Mode is paused:
    • When the Editor is paused, continue rendering in the HMD as if it was not paused.
    • This allows the user to continue viewing the scene in VR while objects are frozen in place (i.e. physically simulated hair).

βš™οΈ LetMeSee V1.1.2

  • Add language selector.
  • Add machine-translated Japanese localization file, translated using ChatGPT.
  • Add machine-translated Korean localization file, translated using ChatGPT.

Versions 1.1.0 and 1.1.1 contain errors.