示例#1
0
        internal void UpdateCurrentCardTranslationMode()
        {
            var origMode = CurrentCardLoadTranslationMode;

            if (StudioAPI.InsideStudio)
            {
                CurrentCardLoadTranslationMode =
                    StudioTranslateCardNameOnLoad?.Value ?? CardLoadTranslationMode.Disabled;
                CurrentGameMode = GameMode.Studio;
            }

            else if (MakerAPI.InsideMaker)
            {
                CurrentCardLoadTranslationMode =
                    MakerTranslateCardNameOnLoad?.Value ?? CardLoadTranslationMode.Disabled;
                CurrentGameMode = GameMode.Maker;
            }
            else
            {
                CurrentGameMode = KoikatuAPI.GetCurrentGameMode();

                if (CurrentGameMode == GameMode.MainGame)
                {
                    CurrentCardLoadTranslationMode =
                        GameTranslateCardNameOnLoad?.Value ?? CardLoadTranslationMode.Disabled;
                }
                else
                {
                    CurrentCardLoadTranslationMode = CardLoadTranslationMode.Disabled;
                }
            }


            Logger.LogDebug($"UpdateCurrentCardTranslationMode: {origMode} => {CurrentCardLoadTranslationMode}");
        }
        private ChaFile GetExtDataTargetChaFile(bool setDirty)
        {
#if KK || KKS
            // In main game store ext data for the character inside of the main chaFile object (the one that gets saved to game saves) instead of its copies.
            // This allows saving ext data inside talk scenes and H scenes without losing it after exiting to main map.
            if (KoikatuAPI.GetCurrentGameMode() == GameMode.MainGame)
            {
                var heroine = ChaControl.GetHeroine();
                if (heroine != null)
                {
                    if (setDirty)
                    {
                        CharacterApi.Hooks.SetDirty(heroine, true);
                    }
                    return(heroine.charFile);
                }

                var player = ChaControl.GetPlayer();
                if (player != null)
                {
                    if (setDirty)
                    {
                        CharacterApi.Hooks.SetDirty(player, true);
                    }
                    return(player.charFile);
                }
            }
#endif
            return(ChaFileControl);
        }
示例#3
0
        public static bool GetFileNamePatch(ref string __result, string path)
        {
            // Should only work during main game
            var gmode = KoikatuAPI.GetCurrentGameMode();

            if (gmode != GameMode.MainGame && gmode != GameMode.Maker)
            {
                return(true);
            }

            try
            {
                if (File.Exists(UserData.Path + "chara/female/" + path))
                {
                    __result = path;
                    return(false);
                }

                var lookedAtPath     = new Uri(path);
                var basePathForChara = new Uri(UserData.Path + "chara/female/");
                if (basePathForChara.IsBaseOf(lookedAtPath))
                {
                    __result = MainGameFolders.GetRelativePath(path, UserData.Path + "chara/female/");
                    return(false);
                }

                return(true);
            }
            catch
            {
                return(true);
            }
        }
示例#4
0
 public void UpdateHover()
 {
     if (KoikatuAPI.GetCurrentGameMode() != GameMode.Studio)
     {
         if (!isShoeActive || currentConfig == null || !GroundAnim)
         {
             DisableHover();
         }
         else
         {
             EnableHover();
         }
     }
     else
     {
         if (!isShoeActive || currentConfig == null)
         {
             DisableHover();
         }
         else
         {
             EnableHover();
         }
     }
 }
示例#5
0
        public static bool ConvertCharaFilePath(ref ChaFileControl __instance, ref string __result, string path)
        {
            // Should only work during main game
            var gmode = KoikatuAPI.GetCurrentGameMode();

            if (gmode != GameMode.MainGame && gmode != GameMode.Maker)
            {
                return(true);
            }
            if (path == null)
            {
                return(true);
            }

            if (!path.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
            {
                path += ".png";
            }

            if (File.Exists(path))
            {
                __result = path;
                return(false);
            }

            if (File.Exists(UserData.Path + "chara/female/" + path))
            {
                __result = UserData.Path + "chara/female/" + path;
                return(false);
            }

            return(true);
        }
        /// <summary>
        /// Save your custom data to the character card under the ID you specified when registering this controller.
        /// This should be used inside the <see cref="OnCardBeingSaved"/> event.
        /// Consider using one of the other "Get___ExtData" and "Set___ExtData" methods instead since they are more reliable and handle copying and transferring outfits and they conform to built in maker load toggles.
        /// </summary>
        /// <param name="data">Your custom data to be written to the character card. Can be null to remove the data.</param>
        public void SetExtendedData(PluginData data)
        {
            if (ExtendedDataId == null)
            {
                throw new ArgumentException(nameof(ExtendedDataId));
            }
            ExtendedSave.SetExtendedDataById(ChaFileControl, ExtendedDataId, data);

#if KK //todo || KKS
            // Needed for propagating changes back to the original charFile since they don't get copied back.
            var heroine = ChaControl.GetHeroine();
            if (heroine != null)
            {
                ExtendedSave.SetExtendedDataById(heroine.charFile, ExtendedDataId, data);

                if (ChaControl != heroine.chaCtrl)
                {
                    ExtendedSave.SetExtendedDataById(heroine.chaCtrl.chaFile, ExtendedDataId, data);

                    // Update other instance to reflect the new ext data
                    var other = heroine.chaCtrl.GetComponent(GetType()) as CharaCustomFunctionController;
                    if (other != null)
                    {
                        other.OnReloadInternal(KoikatuAPI.GetCurrentGameMode());
                    }
                }

                var npc = heroine.GetNPC();
                if (npc != null && npc.chaCtrl != null && npc.chaCtrl != ChaControl)
                {
                    ExtendedSave.SetExtendedDataById(npc.chaCtrl.chaFile, ExtendedDataId, data);

                    // Update other instance to reflect the new ext data
                    var other = npc.chaCtrl.GetComponent(GetType()) as CharaCustomFunctionController;
                    if (other != null)
                    {
                        other.OnReloadInternal(KoikatuAPI.GetCurrentGameMode());
                    }
                }
            }

            var player = ChaControl.GetPlayer();
            if (player != null)
            {
                ExtendedSave.SetExtendedDataById(player.charFile, ExtendedDataId, data);

                if (ChaControl != player.chaCtrl)
                {
                    ExtendedSave.SetExtendedDataById(player.chaCtrl.chaFile, ExtendedDataId, data);

                    // Update other instance to reflect the new ext data
                    var other = player.chaCtrl.GetComponent(GetType()) as CharaCustomFunctionController;
                    if (other != null)
                    {
                        other.OnReloadInternal(KoikatuAPI.GetCurrentGameMode());
                    }
                }
            }
#endif
        }
示例#7
0
        private void LateUpdate()
        {
            if (NeedsFullRefresh)
            {
                OnReload(KoikatuAPI.GetCurrentGameMode(), true);
                NeedsFullRefresh = false;
                return;
            }

            if (_baselineKnown == true)
            {
                if (NeedsBaselineUpdate)
                {
                    UpdateBaseline();
                }

                foreach (var modifier in Modifiers)
                {
                    var additionalModifiers = _additionalBoneEffects
                                              .Select(x => x.GetEffect(modifier.BoneName, this, CurrentCoordinate.Value))
                                              .Where(x => x != null)
                                              .ToList();

                    modifier.Apply(CurrentCoordinate.Value, additionalModifiers, _isDuringHScene);
                }
            }
            else if (_baselineKnown == false)
            {
                _baselineKnown = null;
                CollectBaseline();
            }

            NeedsBaselineUpdate = false;
        }
示例#8
0
        private void LateUpdate()
        {
            if (NeedsFullRefresh)
            {
                OnReload(KoikatuAPI.GetCurrentGameMode(), true);
                NeedsFullRefresh = false;
                return;
            }

            if (_baselineKnown == true)
            {
                if (NeedsBaselineUpdate)
                {
                    UpdateBaseline();
                }

                ApplyEffects();
            }
            else if (_baselineKnown == false)
            {
                _baselineKnown = null;
                CollectBaseline();
            }

            NeedsBaselineUpdate = false;
        }
        private void Start()
        {
            EnableBld         = new ConfigWrapper <bool>(nameof(EnableBld), this, true);
            EnableBldAlways   = new ConfigWrapper <bool>(nameof(EnableBldAlways), this, false);
            EnableCum         = new ConfigWrapper <bool>(nameof(EnableCum), this, true);
            EnableSwt         = new ConfigWrapper <bool>(nameof(EnableSwt), this, true);
            EnableTear        = new ConfigWrapper <bool>(nameof(EnableTear), this, true);
            EnableDrl         = new ConfigWrapper <bool>(nameof(EnableDrl), this, true);
            EnablePersistance = new ConfigWrapper <bool>(nameof(EnablePersistance), this, true);

            Hooks.InstallHook();

            CharacterApi.RegisterExtraBehaviour <SkinEffectsController>(GUID);
            GameAPI.RegisterExtraBehaviour <SkinEffectGameController>(GUID);

            SkinEffectsGui.Init(this);

            if (KoikatuAPI.GetCurrentGameMode() != GameMode.Studio)
            {
                SceneManager.sceneLoaded += (arg0, mode) =>
                {
                    // Preload effects for H scene in case they didn't get loaded yet to prevent freeze on first effect appearing
                    if (arg0.name == "H")
                    {
                        TextureLoader.InitializeTextures();
                    }
                };
            }
        }
示例#10
0
        private void LateUpdate()
        {
            if (NeedsFullRefresh)
            {
                OnReload(KoikatuAPI.GetCurrentGameMode(), true);
                NeedsFullRefresh = false;
                return;
            }

            if (_baselineKnown == true)
            {
                if (NeedsBaselineUpdate)
                {
                    UpdateBaseline();
                }

                foreach (var modifier in Modifiers)
                {
                    modifier.Apply(CurrentCoordinate.Value);
                }
            }
            else if (_baselineKnown == false)
            {
                _baselineKnown = null;
                StartCoroutine(CollectBaselineCo());
            }

            NeedsBaselineUpdate = false;
        }
 /// <summary>
 /// Warning: When overriding make sure to call the base method at the end of your logic!
 /// </summary>
 protected virtual void OnEnable()
 {
     // Order is Awake - OnEnable - Start, so need to check if we started yet
     if (Started)
     {
         OnReloadInternal(KoikatuAPI.GetCurrentGameMode());
     }
 }
示例#12
0
        private static void ChaControl_ChangeSettingEyelineUp(ChaControl __instance)
        {
            //Only care about maker and studio, other modes have problems that aren't worth the effort
            if (KoikatuAPI.GetCurrentGameMode() == GameMode.MainGame)
            {
                return;
            }

            EyebrowFix.SetEyeliners(__instance, __instance.chaFile.custom.face.foregroundEyes);
        }
示例#13
0
 internal static void ChaFile_SaveFile_Postfix(ChaFile __instance)
 {
     // ReSharper disable once UseNullPropagation
     if (__instance == null)
     {
         return;
     }
     __instance.GetTranslationHelperController()
     .SafeProcObject(c => c.OnCardSaveComplete(KoikatuAPI.GetCurrentGameMode()));
 }
示例#14
0
        internal static void TriggerHelperCoroutine()
        {
            //Only trigger if not already running, and in main game
            if (coroutineActive || KoikatuAPI.GetCurrentGameMode() != GameMode.MainGame)
            {
                return;
            }
            coroutineActive = true;

            pluginInstance.StartCoroutine(LoopEveryXSeconds());
        }
示例#15
0
 private static void NotBraShortsOverride(ChaControl __instance, ref bool value)
 {
     if (__instance.GetCurrentCrest() == CrestType.liberated)
     {
         if (KoikatuAPI.GetCurrentGameMode() == GameMode.MainGame || GameAPI.InsideHScene)
         {
             // Force underwear to be off
             value = true;
         }
     }
 }
示例#16
0
        private static void OnCardBeingSaved(ChaFile chaFile)
        {
            KoikatuAPI.Log(LogLevel.Debug, "[KKAPI] Character save: " + chaFile.parameter.fullname);

            var gamemode = KoikatuAPI.GetCurrentGameMode();

            foreach (var behaviour in GetBehaviours(MakerAPI.GetCharacterControl()))
            {
                behaviour.OnCardBeingSavedInternal(gamemode);
            }
        }
 private static int GetCurrentMapNo()
 {
     if (KoikatuAPI.GetCurrentGameMode() != GameMode.MainGame)
     {
         return(-1);
     }
     if (!Game.IsInstance() || Game.Instance.actScene == null || Game.Instance.actScene.Map == null)
     {
         return(-1);
     }
     return(Game.Instance.actScene.Map.no);
 }
示例#18
0
        private IEnumerator Start()
        {
            yield return(new WaitUntil(() =>
            {
                switch (KKAPI.KoikatuAPI.GetCurrentGameMode())
                {
                case KKAPI.GameMode.Maker:
                    return KKAPI.Maker.MakerAPI.InsideAndLoaded;

                case KKAPI.GameMode.Studio:
                    return KKAPI.Studio.StudioAPI.StudioLoaded;

                case KKAPI.GameMode.MainGame:
                    return null != GameObject.Find("MapScene") && SceneManager.GetActiveScene().isLoaded&& null != Camera.main;      //KKAPI doesn't provide an api for in game check

                default:
                    return false;
                }
            }));

            Settings               = new GlobalSettings();
            CameraSettings         = new CameraSettings();
            LightingSettings       = new LightingSettings();
            PostProcessingSettings = new PostProcessingSettings(CameraSettings.MainCamera);

            _skyboxManager           = Instance.GetOrAddComponent <SkyboxManager>();
            _skyboxManager.Parent    = this;
            _skyboxManager.AssetPath = ConfigCubeMapPath.Value;
            _skyboxManager.Logger    = Logger;
            DontDestroyOnLoad(_skyboxManager);

            _postProcessingManager        = Instance.GetOrAddComponent <PostProcessingManager>();
            _postProcessingManager.Parent = this;
            _postProcessingManager.LensDirtTexturesPath = ConfigLensDirtPath.Value;
            DontDestroyOnLoad(_postProcessingManager);

            _lightManager = new LightManager(this);

            _focusPuller = Instance.GetOrAddComponent <FocusPuller>();
            _focusPuller.init(this);
            DontDestroyOnLoad(_focusPuller);
            _presetManager = new PresetManager(ConfigPresetPath.Value, this);

            _inspector = new Inspector.Inspector(this);

            // It takes some time to fully loaded in studio to save/load stuffs.
            yield return(new WaitUntil(() =>
            {
                return (KoikatuAPI.GetCurrentGameMode() == GameMode.Studio) ? KKAPI.Studio.StudioAPI.InsideStudio && _skyboxManager != null : true;
            }));

            _isLoaded = true;
        }
示例#19
0
        private static void ChaFile_SaveFile_Postfix(ChaFile __instance)
        {
            try
            {
                __instance.SafeProc(i => i.GetTranslationHelperController()
                                    .SafeProc(c => c.OnCardSaveComplete(KoikatuAPI.GetCurrentGameMode())));
            }

            catch (Exception err)
            {
                Logger.LogException(err, nameof(ChaFile_SaveFile_Postfix));
            }
        }
        private static void ReloadChara(ChaControl chaControl = null)
        {
            if (IsCurrentlyReloading(chaControl))
            {
                return;
            }

            if (chaControl == null)
            {
                _currentlyReloading.UnionWith(ChaControls);
            }
            else
            {
                _currentlyReloading.Add(chaControl);
            }

            KoikatuAPI.Logger.LogDebug("Character load/reload: " + GetLogName(chaControl));

            // Always send events to controllers before subscribers of CharacterReloaded
            var gamemode = KoikatuAPI.GetCurrentGameMode();

            foreach (var behaviour in GetBehaviours(chaControl))
            {
                behaviour.OnReloadInternal(gamemode);
            }

            var args = new CharaReloadEventArgs(chaControl);

            try
            {
                CharacterReloaded?.Invoke(null, args);
            }
            catch (Exception e)
            {
                KoikatuAPI.Logger.LogError(e);
            }

            if (MakerAPI.InsideAndLoaded)
            {
                MakerAPI.OnReloadInterface(args);
            }

            if (chaControl == null)
            {
                _currentlyReloading.Clear();
            }
            else
            {
                _currentlyReloading.Remove(chaControl);
            }
        }
        /// <summary>
        /// Save your custom data to the character card under the ID you specified when registering this controller.
        /// This should be used inside the <see cref="OnCardBeingSaved"/> event.
        /// Consider using one of the other "Get___ExtData" and "Set___ExtData" methods instead since they are more reliable and handle copying and transferring outfits and they conform to built in maker load toggles.
        /// </summary>
        /// <param name="data">Your custom data to be written to the character card. Can be null to remove the data.</param>
        public void SetExtendedData(PluginData data)
        {
            if (ExtendedDataId == null)
            {
                throw new ArgumentException(nameof(ExtendedDataId));
            }
            ExtendedSave.SetExtendedDataById(ChaFileControl, ExtendedDataId, data);

#if KK || KKS
            if (KoikatuAPI.GetCurrentGameMode() == GameMode.MainGame)
            {
                // In main game store ext data for the character inside of the main chaFile object (the one that gets saved to game saves).
                // This allows saving ext data inside talk scenes and H scenes without losing it after exiting to main map.
                var heroine = ChaControl.GetHeroine();
                if (heroine != null)
                {
                    ExtendedSave.SetExtendedDataById(heroine.charFile, ExtendedDataId, data);

                    if (ChaControl != heroine.chaCtrl)
                    {
                        ExtendedSave.SetExtendedDataById(heroine.chaCtrl.chaFile, ExtendedDataId, data);
                        // Update other instance to reflect the new ext data
                        CharacterApi.Hooks.SetDirty(heroine, true);
                    }

                    var npc = heroine.GetNPC();
                    if (npc != null && npc.chaCtrl != null && npc.chaCtrl != ChaControl && npc.chaCtrl != heroine.chaCtrl)
                    {
                        ExtendedSave.SetExtendedDataById(npc.chaCtrl.chaFile, ExtendedDataId, data);
                        // Update other instance to reflect the new ext data
                        CharacterApi.Hooks.SetDirty(heroine, true);
                    }
                }
                else
                {
                    var player = ChaControl.GetPlayer();
                    if (player != null)
                    {
                        ExtendedSave.SetExtendedDataById(player.charFile, ExtendedDataId, data);

                        if (ChaControl != player.chaCtrl)
                        {
                            ExtendedSave.SetExtendedDataById(player.chaCtrl.chaFile, ExtendedDataId, data);
                            // Update other instance to reflect the new ext data
                            CharacterApi.Hooks.SetDirty(player, true);
                        }
                    }
                }
            }
#endif
        }
示例#22
0
            protected override void Start()
            {
                if (KoikatuAPI.GetCurrentGameMode() == GameMode.MainGame)
                {
                    return;
                }
                HairAccessoryCustomizer = new HairAccessoryCustomizerSupport.UrineBag(ChaControl);
                MaterialEditor          = new MaterialEditorSupport.UrineBag(ChaControl);
                MaterialRouter          = new MaterialRouterSupport.UrineBag(ChaControl);
                AccStateSync            = new AccStateSyncSupport.UrineBag(ChaControl);
                DynamicBoneEditor       = new DynamicBoneEditorSupport.UrineBag(ChaControl);

                CurrentCoordinate.Subscribe(value => { OnCoordinateChanged(); });
                base.Start();
            }
示例#23
0
        public void RefreshTexture(string texType)
        {
            if (texType != null && KoikatuAPI.GetCurrentGameMode() != GameMode.Studio)
            {
                var i = Array.FindIndex(ChaControl.objClothes, x => x != null && x.name == texType);
                if (i >= 0)
                {
                    ChaControl.ChangeCustomClothes(i, true, false, false, false);
                    return;
                }
            }

            // Fall back if the specific tex couldn't be refreshed
            RefreshAllTextures();
        }
        private static void OnCardBeingSaved(ChaFile chaFile)
        {
            KoikatuAPI.Logger.LogDebug("Character save: " + chaFile.parameter.fullname);

            var gamemode = KoikatuAPI.GetCurrentGameMode();

            var chaControl = gamemode == GameMode.Maker ?
                             MakerAPI.GetCharacterControl() :
                             ChaControls.FirstOrDefault(control => control.chaFile == chaFile);

            foreach (var behaviour in GetBehaviours(chaControl))
            {
                behaviour.OnCardBeingSavedInternal(gamemode);
            }
        }
示例#25
0
        public CoroutineLimiter(long limit, string limiterName, bool resetOnSceneTransition = false)
        {
            Limit                = limit;
            _limiterName         = limiterName;
            _waitUntilBelowLimit = new WaitUntil(IsBelowLimit);
            if (resetOnSceneTransition)
            {
                SceneManager.activeSceneChanged += ActiveSceneChanged;
            }
            if (KoikatuAPI.GetCurrentGameMode() == GameMode.Studio)
            {
                StudioSaveLoadApi.SceneLoad += StudioSaveLoadApi_SceneLoad;
            }

            Reset();
        }
示例#26
0
        private bool IsLoaded()
        {
            switch (KoikatuAPI.GetCurrentGameMode())
            {
            case GameMode.Maker:
                return(KKAPI.Maker.MakerAPI.InsideAndLoaded);

            case GameMode.Studio:
                return(KKAPI.Studio.StudioAPI.StudioLoaded);

            case GameMode.MainGame:
                return("MyRoom" == SceneManager.GetActiveScene().name&& SceneManager.GetActiveScene().isLoaded&& null != Camera.main);      //HS2API doesn't provide an api for in game check

            default:
                return(false);
            }
        }
示例#27
0
        //Got tired of searching for the correct hooks, just check for new dynamic bones on a loop.  Genious!
        internal static IEnumerator LoopEveryXSeconds()
        {
            while (coroutineActive)
            {
                //If not in the main game, continue
                if (KoikatuAPI.GetCurrentGameMode() != GameMode.MainGame)
                {
                    yield return(new WaitForSeconds(3));
                }

                VRControllerCollider.SetVRControllerColliderToDynamicBones();

                // VRPlugin.Logger.Log(LogLevel.Info, $"Camera distance {distance}");

                yield return(new WaitForSeconds(3));
            }
        }
        private static void ReloadChara(Human chaControl = null)
        {
            if (IsCurrentlyReloading(chaControl))
            {
                return;
            }

            if (chaControl == null)
            {
                _currentlyReloading.UnionWith(ChaControls);
            }
            else
            {
                _currentlyReloading.Add(chaControl);
            }

            KoikatuAPI.Logger.LogDebug("Character load/reload");

            // Always send events to controllers before subscribers of CharacterReloaded
            var gamemode = KoikatuAPI.GetCurrentGameMode();

            foreach (var behaviour in GetBehaviours(chaControl))
            {
                behaviour.OnReloadInternal(gamemode);
            }

            OnCharacterReload(chaControl);

            if (MakerAPI.InsideAndLoaded)
            {
                MakerAPI.OnReloadInterface(new CharaReloadEventArgs(chaControl));
            }

            if (chaControl == null)
            {
                _currentlyReloading.Clear();
                Hooks.LastLoadedCardPaths.Clear();
            }
            else
            {
                _currentlyReloading.Remove(chaControl);
                Hooks.LastLoadedCardPaths[chaControl] = null;
            }
        }
示例#29
0
        private void Start()
        {
            Instance = this;
            Logger   = base.Logger;

            if (KoikatuAPI.GetCurrentGameMode() != GameMode.Studio)
            {
                XyzMode                 = Config.Wrap("Maker", Metadata.XyzModeName, Metadata.XyzModeDesc, false);
                RaiseLimits             = Config.Wrap("Maker", Metadata.RaiseLimitsName, Metadata.RaiseLimitsDesc, false);
                XyzMode.SettingChanged += KKABMX_GUI.OnIsAdvancedModeChanged;

                var showAdv = Config.Wrap("Maker", "Show Advanced Bonemod Window", "", false);
                showAdv.SettingChanged += (sender, args) => gameObject.GetComponent <KKABMX_AdvancedGUI>().enabled = showAdv.Value;

                gameObject.AddComponent <KKABMX_GUI>();
            }

            CharacterApi.RegisterExtraBehaviour <BoneController>(ExtDataGUID);

            Hooks.Init();
        }
        private IEnumerator Start()
        {
            yield return(new WaitUntil(() =>
            {
                switch (KoikatuAPI.GetCurrentGameMode())
                {
                case GameMode.Studio:
                    return KKAPI.Studio.StudioAPI.StudioLoaded;

                case GameMode.Unknown:
                case GameMode.Maker:
                case GameMode.MainGame:
                    return false;

                default:
                    return false;
                }
            }));

            ConfigKey1 = Config.Bind("1. Roll", "Roll (clockwise)", new KeyboardShortcut(F4),
                                     new ConfigDescription("Keyboard shortcut to roll clockwise (right)"));
            ConfigKey2 = Config.Bind("2. Front/Back", "Front/Back", new KeyboardShortcut(F6),
                                     new ConfigDescription("Keyboard shortcut to rotate into front/back"));
            ConfigKey3 = Config.Bind("3. Side", "Side (clockwise)", new KeyboardShortcut(F7),
                                     new ConfigDescription("Keyboard shortcut to rotate to side in clockwise manner"));

            ConfigKeyC1 = Config.Bind("1. Roll", "Roll (counter-clockwise)", new KeyboardShortcut(F4, Ctrl),
                                      new ConfigDescription("Keyboard shortcut to roll counter-clockwise (left)"));
            ConfigKeyC3 = Config.Bind("3. Side", "Side (counter-clockwise)", new KeyboardShortcut(F7, Ctrl),
                                      new ConfigDescription("Keyboard shortcut to rotate to side in counter-clockwise manner"));

            RollAngle = Config.Bind("1. Roll", "Roll Angle", Key1Default,
                                    new ConfigDescription("Roll angle", new AcceptableValueRange <float>(Key1Min, Key1Max)));
            FrontBackAngle = Config.Bind("2. Front/Back", "Front/Back Angle (leave this be)", Key2Default,
                                         new ConfigDescription("No point adjusting this unless you don't want front/back",
                                                               new AcceptableValueRange <float>(Key2Min, Key2Max)));
            SideAngle = Config.Bind("3. Side", "Side Angle", Key3Default,
                                    new ConfigDescription("Angle to rotate to side", new AcceptableValueRange <float>(Key3Min, Key3Max)));
        }