diff --git a/stardew-access/HarmonyPatches.cs b/stardew-access/HarmonyPatches.cs index fbd1d92..dcdc7bd 100644 --- a/stardew-access/HarmonyPatches.cs +++ b/stardew-access/HarmonyPatches.cs @@ -48,7 +48,7 @@ namespace stardew_access harmony.Patch( original: AccessTools.Method(typeof(CharacterCustomization), nameof(CharacterCustomization.draw), new Type[] { typeof(SpriteBatch) }), - postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.CharacterCustomizationMenuPatch)) + postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(CharacterCustomizationPatches.CharacterCustomizationMenuPatch)) ); harmony.Patch( diff --git a/stardew-access/ModConfig.cs b/stardew-access/ModConfig.cs index b35437d..ca29cc3 100644 --- a/stardew-access/ModConfig.cs +++ b/stardew-access/ModConfig.cs @@ -49,9 +49,14 @@ namespace stardew_access #region Menu Keys public KeybindList PrimaryInfoKey { get; set; } = KeybindList.Parse("C"); - // Charachter Creatinon menu (new game menu) keys + // Character Creation menu (new game menu) keys public KeybindList CharacterCreationMenuNextKey { get; set; } = KeybindList.Parse("Right"); public KeybindList CharacterCreationMenuPreviousKey { get; set; } = KeybindList.Parse("Left"); + public KeybindList CharacterCreationMenuSliderIncreaseKey { get; set; } = KeybindList.Parse("Up"); + public KeybindList CharacterCreationMenuSliderLargeIncreaseKey { get; set; } = KeybindList.Parse("PageUp"); + public KeybindList CharacterCreationMenuSliderDecreaseKey { get; set; } = KeybindList.Parse("Down"); + public KeybindList CharacterCreationMenuSliderLargeDecreaseKey { get; set; } = KeybindList.Parse("PageDown"); + public KeybindList CharacterCreationMenuDesignToggleKey { get; set; } = KeybindList.Parse("LeftControl + Space"); // Bundle menu keys public KeybindList BundleMenuIngredientsKey { get; set; } = KeybindList.Parse("I"); diff --git a/stardew-access/ModEntry.cs b/stardew-access/ModEntry.cs index a7009f9..7049d76 100644 --- a/stardew-access/ModEntry.cs +++ b/stardew-access/ModEntry.cs @@ -212,7 +212,7 @@ namespace stardew_access #region Simulate left and right clicks if (Game1.activeClickableMenu != null) { - bool isCustomizingChrachter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization); + bool isCustomizingCharacter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization); #region Mouse Click Simulation // Main Keybinds @@ -226,11 +226,11 @@ namespace stardew_access } // Alternate Keybinds - if (!isCustomizingChrachter && Game1.activeClickableMenu is not AnimalQueryMenu && Config.LeftClickAlternateKey.JustPressed()) // Excluding the character creation menu + if (!isCustomizingCharacter && Game1.activeClickableMenu is not AnimalQueryMenu && Config.LeftClickAlternateKey.JustPressed()) // Excluding the character creation menu { Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true)); } - if (!isCustomizingChrachter && Game1.activeClickableMenu is not AnimalQueryMenu && Config.RightClickAlternateKey.JustPressed()) // Excluding the character creation menu + if (!isCustomizingCharacter && Game1.activeClickableMenu is not AnimalQueryMenu && Config.RightClickAlternateKey.JustPressed()) // Excluding the character creation menu { Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true)); } @@ -239,7 +239,7 @@ namespace stardew_access if (Game1.currentMinigame != null) { - bool isCustomizingChrachter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization); + bool isCustomizingCharacter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization); #region Mouse Click Simulation // Main Keybinds diff --git a/stardew-access/Patches/CharacterCustomizationMenuPatches.cs b/stardew-access/Patches/CharacterCustomizationMenuPatches.cs new file mode 100644 index 0000000..52c4323 --- /dev/null +++ b/stardew-access/Patches/CharacterCustomizationMenuPatches.cs @@ -0,0 +1,829 @@ +using StardewValley; +using StardewValley.Menus; + +namespace stardew_access.Patches { + internal class CharacterCustomizationPatches { + private static bool isRunning = false; + private static int saveGameIndex = -1; + public static string characterCreationMenuQueryKey = " "; + public static string prevPants = " "; + public static string prevShirt = " "; + public static string prevHair = " "; + public static string prevAccessory = " "; + public static string prevSkin = " "; + public static string prevEyeColor = " "; + public static string prevEyeColorHue = " "; + public static string prevEyeColorSaturation = " "; + public static string prevEyeColorValue = " "; + public static string prevHairColor = " "; + public static string prevHairColorHue = " "; + public static string prevHairColorSaturation = " "; + public static string prevHairColorValue = " "; + public static string prevPantsColor = " "; + public static string prevPantsColorHue = " "; + public static string prevPantsColorSaturation = " "; + public static string prevPantsColorValue = " "; + public static string prevPetName = " "; + public static bool characterDesignToggle = false; + public static bool characterDesignToggleShouldSpeak = true; + public static ClickableComponent? currentComponent = null; + + internal static void CharacterCustomizationMenuPatch(CharacterCustomization __instance, bool ___skipIntro, + ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox, + TextBox ___farmnameBox, TextBox ___favThingBox) + { + try + { + bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box + string toSpeak = ""; + if (characterDesignToggleShouldSpeak) + { + toSpeak = "Press left control + space to toggle character appearance controls"; + characterDesignToggleShouldSpeak = false; + } + string itemsToSpeak = ""; + string changesToSpeak = ""; + + if (___nameBox.Selected) + { + toSpeak = ___nameBox.Text; + + if (isEscPressed) + { + ___nameBox.Selected = false; + } + } + else if (___farmnameBox.Selected) + { + toSpeak = ___farmnameBox.Text; + + if (isEscPressed) + { + ___farmnameBox.Selected = false; + } + } + else if (___favThingBox.Selected) + { + toSpeak = ___favThingBox.Text; + + if (isEscPressed) + { + ___favThingBox.Selected = false; + } + } + else if (MainClass.Config.CharacterCreationMenuNextKey.JustPressed() && !isRunning) + { + isRunning = true; + itemsToSpeak =CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); + if (itemsToSpeak != "") + toSpeak = $"{itemsToSpeak} \n {toSpeak}"; + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + else if (MainClass.Config.CharacterCreationMenuPreviousKey.JustPressed() && !isRunning) + { + isRunning = true; + toSpeak = CycleThroughItems(false, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + + else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderIncreaseKey.JustPressed() && !isRunning) + { + isRunning = true; + AdjustCurrentSlider(true, __instance); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + + else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderLargeIncreaseKey.JustPressed() && !isRunning) + { + isRunning = true; + AdjustCurrentSlider(true, __instance, 10); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + + else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderDecreaseKey.JustPressed() && !isRunning) + { + isRunning = true; + AdjustCurrentSlider(false, __instance); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + + else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderLargeDecreaseKey.JustPressed() && !isRunning) + { + isRunning = true; + AdjustCurrentSlider(false, __instance, 10); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + + else if (Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl) && MainClass.Config.CharacterCreationMenuDesignToggleKey.JustPressed() && !isRunning) + { + string displayState = ""; + characterDesignToggle = !characterDesignToggle; + saveGameIndex = Math.Min(saveGameIndex, 5); // move to random skin button if focus was beyond that point + if (characterDesignToggle) + { + displayState = "shown"; + } else { + displayState = "hidden"; + } + toSpeak = $"Character design controls {displayState}. \n {toSpeak}"; + } + + changesToSpeak = getChangesToSpeak(__instance); + if (changesToSpeak != "") + toSpeak = $"{toSpeak} \n {changesToSpeak}"; + + if (characterCreationMenuQueryKey != toSpeak && toSpeak.Trim() != "") + { + characterCreationMenuQueryKey = toSpeak; + MainClass.ScreenReader.Say(toSpeak, true); + } + } + catch (Exception e) + { + MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); + } + } + + private static string getChangesToSpeak(CharacterCustomization __instance) + { + string toSpeak = ""; + string currentPetName = getCurrentPetName(); + string currentSkin = getCurrentSkin(); + string currentHair = getCurrentHair(); + string currentShirt = getCurrentShirt(); + string currentPants = getCurrentPants(); + string currentAccessory = getCurrentAccessory(); + string currentEyeColor = getCurrentEyeColor(); + string currentEyeColorHue = getCurrentEyeColorHue(__instance); + string currentEyeColorSaturation = getCurrentEyeColorSaturation(__instance); + string currentEyeColorValue = getCurrentEyeColorValue(__instance); + string currentHairColor = getCurrentHairColor(); + string currentHairColorHue = getCurrentHairColorHue(__instance); + string currentHairColorSaturation = getCurrentHairColorSaturation(__instance); + string currentHairColorValue = getCurrentHairColorValue(__instance); + string currentPantsColor = getCurrentPantsColor(); + string currentPantsColorHue = getCurrentPantsColorHue(__instance); + string currentPantsColorSaturation = getCurrentPantsColorSaturation(__instance); + string currentPantsColorValue = getCurrentPantsColorValue(__instance); + + if (characterDesignToggle) + { + if (prevSkin != currentSkin) + { + prevSkin = currentSkin; + if (currentSkin != "") + toSpeak = $"{toSpeak} \n {currentSkin}"; + } + + if (prevHair != currentHair) + { + prevHair = currentHair; + if (currentHair != "") + toSpeak = $"{toSpeak} \n {currentHair}"; + } + + if (prevShirt != currentShirt) + { + prevShirt = currentShirt; + if (currentShirt != "") + toSpeak = $"{toSpeak} \n {currentShirt}"; + } + + if (prevPants != currentPants) + { + prevPants = currentPants; + if (currentPants != "") + toSpeak = $"{toSpeak} \n {currentPants}"; + } + + if (prevAccessory != currentAccessory) + { + prevAccessory = currentAccessory; + if (currentAccessory != "") + toSpeak = $"{toSpeak} \n {currentAccessory}"; + } + + if (prevEyeColorHue != currentEyeColorHue) + { + if (currentComponent != null && currentComponent.myID == 522) + { + prevEyeColorHue = currentEyeColorHue; + if (currentEyeColorHue != "") + toSpeak = $"{toSpeak} \n Hue: {currentEyeColorHue}"; + } else { + prevEyeColorHue = ""; + } + } + + if (prevEyeColorSaturation != currentEyeColorSaturation) + { + if (currentComponent != null && currentComponent.myID == 523) + { + prevEyeColorSaturation = currentEyeColorSaturation; + if (currentEyeColorSaturation != "") + toSpeak = $"{toSpeak} \n Saturation: {currentEyeColorSaturation}"; + } else { + prevEyeColorSaturation = ""; + } + } + + if (prevEyeColorValue != currentEyeColorValue) + { + if (currentComponent != null && currentComponent.myID == 524) + { + prevEyeColorValue = currentEyeColorValue; + if (currentEyeColorValue != "") + toSpeak = $"{toSpeak} \n Value: {currentEyeColorValue}"; + } else { + prevEyeColorValue = ""; + } + } + + if (prevEyeColor != currentEyeColor) + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 || currentComponent.myID <= 524))) + { + prevEyeColor = currentEyeColor; + if (currentEyeColor != "") + toSpeak = $"{toSpeak} \n {currentEyeColor}"; + } + } + + if (prevHairColorHue != currentHairColorHue) + { + if (currentComponent != null && currentComponent.myID == 525) + { + prevHairColorHue = currentHairColorHue; + if (currentHairColorHue != "") + toSpeak = $"{toSpeak} \n Hue: {currentHairColorHue}"; + } else { + prevHairColorHue = ""; + } + } + + if (prevHairColorSaturation != currentHairColorSaturation) + { + if (currentComponent != null && currentComponent.myID == 526) + { + prevHairColorSaturation = currentHairColorSaturation; + if (currentHairColorSaturation != "") + toSpeak = $"{toSpeak} \n Saturation: {currentHairColorSaturation}"; + } else { + prevHairColorSaturation = ""; + } + } + + if (prevHairColorValue != currentHairColorValue) + { + if (currentComponent != null && currentComponent.myID == 527) + { + prevHairColorValue = currentHairColorValue; + if (currentHairColorValue != "") + toSpeak = $"{toSpeak} \n Value: {currentHairColorValue}"; + } else { + prevHairColorValue = ""; + } + } + + if (prevHairColor != currentHairColor) + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 || currentComponent.myID <= 527))) + { + prevHairColor = currentHairColor; + if (currentHairColor != "") + toSpeak = $"{toSpeak} \n {currentHairColor}"; + } + } + + if (prevPantsColorHue != currentPantsColorHue) + { + if (currentComponent != null && currentComponent.myID == 528) + { + prevPantsColorHue = currentPantsColorHue; + if (currentPantsColorHue != "") + toSpeak = $"{toSpeak} \n Hue: {currentPantsColorHue}"; + } else { + prevPantsColorHue = ""; + } + } + + if (prevPantsColorSaturation != currentPantsColorSaturation) + { + if (currentComponent != null && currentComponent.myID == 529) + { + prevPantsColorSaturation = currentPantsColorSaturation; + if (currentPantsColorSaturation != "") + toSpeak = $"{toSpeak} \n Saturation: {currentPantsColorSaturation}"; + } else { + prevPantsColorSaturation = ""; + } + } + + if (prevPantsColorValue != currentPantsColorValue) + { + if (currentComponent != null && currentComponent.myID == 530) + { + prevPantsColorValue = currentPantsColorValue; + if (currentPantsColorValue != "") + toSpeak = $"{toSpeak} \n Value: {currentPantsColorValue}"; + } else { + prevPantsColorValue = ""; + } + } + + if (prevPantsColor != currentPantsColor) + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 || currentComponent.myID <= 530))) + { + prevPantsColor = currentPantsColor; + if (currentPantsColor != "") + toSpeak = $"{toSpeak} \n {currentPantsColor}"; + } + } + } + + if (prevPetName != currentPetName) + { + prevPetName = currentPetName; + if (currentPetName != "") + toSpeak = $"{toSpeak} \n Current Pet: {currentPetName}"; + } + return toSpeak.Trim(); + } + + + private static string CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro, + ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox, + TextBox ___farmnameBox, TextBox ___favThingBox) + { + string toSpeak = " "; + int DesignControlsIndex = 0; + Dictionary buttons = new(); + + #region Add buttons with their names IF they are available + + #region Character related + string postText = ""; + if (__instance.nameBoxCC != null && __instance.nameBoxCC.visible) + { + if (___nameBox.Text != "") + { + postText = $": {___nameBox.Text}"; + } else { + postText = " Text Box"; + } + buttons.Add(__instance.nameBoxCC, $"Farmer's Name{postText}"); + } + + if (__instance.farmnameBoxCC != null && __instance.farmnameBoxCC.visible) + { + if (___farmnameBox.Text != "") + { + postText = $": {___farmnameBox.Text}"; + } else { + postText = " Text Box"; + } + buttons.Add(__instance.farmnameBoxCC, $"Farm's Name{postText}"); + } + + if (__instance.favThingBoxCC != null && __instance.favThingBoxCC.visible) + { + if (___favThingBox.Text != "") + { + postText = $": {___favThingBox.Text}"; + } else { + postText = " Text Box"; + } + buttons.Add(__instance.favThingBoxCC, $"Favourite Thing{postText}"); + } + + if (__instance.petPortraitBox.HasValue) // Cannot get petButtons like with others + { + ClickableComponent petPrev = __instance.getComponentWithID(511); + buttons.Add(petPrev, "Previous pet button"); + + ClickableComponent petNext = __instance.getComponentWithID(510); + buttons.Add(petNext, "Next pet button"); + } + + if (__instance.randomButton != null && __instance.randomButton.visible) + buttons.Add(__instance.randomButton, "Random Skin Button"); + + // Controls to rotate the farmer (Potentially useful for low vision players) are first if they're available. + // They also appear above the gender buttons, so we handle them separately here. + if (characterDesignToggle && new[] {__instance.leftSelectionButtons.Count, __instance.rightSelectionButtons.Count }.All(c => c >= 0)) // both have Count > 0 + { + if (new[] {__instance.leftSelectionButtons[DesignControlsIndex].visible, __instance.rightSelectionButtons[DesignControlsIndex].visible }.All(v => v == true) // both visible + && new[] {__instance.leftSelectionButtons[DesignControlsIndex].name, __instance.rightSelectionButtons[DesignControlsIndex].name }.All(n => n == "Direction")) // both named "Direction" + { + buttons.Add(__instance.leftSelectionButtons[DesignControlsIndex], "Rotate Left Button"); + buttons.Add(__instance.rightSelectionButtons[DesignControlsIndex], "Rotate Right Button"); + ++DesignControlsIndex; + } + } + + if (__instance.genderButtons.Count > 0) + { + buttons.Add(__instance.genderButtons[0], ((Game1.player.IsMale) ? "Selected " : "") + "Gender: Male Button"); + buttons.Add(__instance.genderButtons[1], ((!Game1.player.IsMale) ? "Selected " : "") + "Gender: Female Button"); + } + + if (characterDesignToggle&& new[] {__instance.leftSelectionButtons.Count, __instance.rightSelectionButtons.Count }.All(c => c >= DesignControlsIndex) && new[] {__instance.leftSelectionButtons[DesignControlsIndex].visible, __instance.rightSelectionButtons[DesignControlsIndex].visible }.All(v => v == true)) + { + while(DesignControlsIndex < __instance.leftSelectionButtons.Count) + { + ClickableComponent left = __instance.leftSelectionButtons[DesignControlsIndex]; + ClickableComponent right = __instance.rightSelectionButtons[DesignControlsIndex]; + string name = left.name; + // minor cleanup on names to be slightly more descriptive + switch (name) + { + case "Skin": + name += " Tone"; + break; + case "Hair": + name += " Style"; + break; + case "Acc": + name = "Accessory"; + break; + default: + break; + } + if (!buttons.ContainsKey(left) || !buttons.ContainsKey(right)) + { + buttons.Add(left, $"Previous {name} button"); + buttons.Add(right, $"Next {name} button"); + } + //MainClass.ScreenReader.Say($"Left {DesignControlsIndex}: {__instance.leftSelectionButtons[DesignControlsIndex]} {__instance.leftSelectionButtons[DesignControlsIndex].name}\n", true); + //MainClass.ScreenReader.Say($"Right {DesignControlsIndex}: {__instance.rightSelectionButtons[DesignControlsIndex]} {__instance.rightSelectionButtons[DesignControlsIndex].name}\n", true); + ++DesignControlsIndex; + } + + ClickableComponent eyeColorHue = __instance.getComponentWithID(522); + if (eyeColorHue != null && eyeColorHue.visible) + buttons.Add(eyeColorHue, "eye color hue slider"); + + ClickableComponent eyeColorSaturation = __instance.getComponentWithID(523); + if (eyeColorSaturation != null && eyeColorSaturation.visible) + buttons.Add(eyeColorSaturation, "eye color saturation slider"); + + ClickableComponent eyeColorValue = __instance.getComponentWithID(524); + if (eyeColorValue != null && eyeColorValue.visible) + buttons.Add(eyeColorValue, "eye color Value slider"); + + ClickableComponent hairColorHue = __instance.getComponentWithID(525); + if (hairColorHue != null && hairColorHue.visible) + buttons.Add(hairColorHue, "hair color hue slider"); + + ClickableComponent hairColorSaturation = __instance.getComponentWithID(526); + if (hairColorSaturation != null && hairColorSaturation.visible) + buttons.Add(hairColorSaturation, "hair color saturation slider"); + + ClickableComponent hairColorValue = __instance.getComponentWithID(527); + if (hairColorValue != null && hairColorValue.visible) + buttons.Add(hairColorValue, "hair color Value slider"); + + ClickableComponent pantsColorHue = __instance.getComponentWithID(528); + if (pantsColorHue != null && pantsColorHue.visible) + buttons.Add(pantsColorHue, "pants color hue slider"); + + ClickableComponent pantsColorSaturation = __instance.getComponentWithID(529); + if (pantsColorSaturation != null && pantsColorSaturation.visible) + buttons.Add(pantsColorSaturation, "pants color saturation slider"); + + ClickableComponent pantsColorValue = __instance.getComponentWithID(530); + if (pantsColorValue != null && pantsColorValue.visible) + buttons.Add(pantsColorValue, "pants color Value slider"); + } + + #endregion + + #region Farm layout related + if (__instance.farmTypeButtons.Count > 0) + { + for (int i = 0; i < __instance.farmTypeButtons.Count; i++) + { + buttons.Add(__instance.farmTypeButtons[i], ((i == Game1.whichFarm) ? "Selected " : "") + getFarmHoverText(__instance.farmTypeButtons[i])); + } + } + + if (__instance.farmTypeNextPageButton != null && __instance.farmTypeNextPageButton.visible) + buttons.Add(__instance.farmTypeNextPageButton, "Next Farm Type Page Button"); + + if (__instance.farmTypePreviousPageButton != null && __instance.farmTypePreviousPageButton.visible) + buttons.Add(__instance.farmTypePreviousPageButton, "Previous Farm Type Page Button"); + #endregion + + #region Co-op related + if (__instance.source == CharacterCustomization.Source.HostNewFarm) + { + ClickableComponent cabinLeft = __instance.getComponentWithID(621); + if (Game1.startingCabins > 0) + buttons.Add(cabinLeft, "Decrease starting cabins button"); + + buttons.Add(___startingCabinsLabel, $"Starting cabins: {Game1.startingCabins}"); + + ClickableComponent cabinRight = __instance.getComponentWithID(622); + if (Game1.startingCabins < 3) + buttons.Add(cabinRight, "Increase starting cabins button"); + + if (Game1.startingCabins > 0) + { + buttons.Add(__instance.cabinLayoutButtons[0], "Cabin layout to nearby Button"); + buttons.Add(__instance.cabinLayoutButtons[1], "Cabin layout to separate Button"); + } + + ClickableComponent difficultyLeft = __instance.getComponentWithID(627); + buttons.Add(difficultyLeft, "Increase profit margin button"); + buttons.Add(___difficultyModifierLabel, "Profit Margin: " + (((Game1.player.difficultyModifier * 100) == 100f) ? "normal" : Game1.player.difficultyModifier.ToString())); + ClickableComponent difficultyRight = __instance.getComponentWithID(628); + buttons.Add(difficultyRight, "Decrease profit margin button"); + + ClickableComponent walletLeft = __instance.getComponentWithID(631); + buttons.Add(walletLeft, "Money style to " + ((!Game1.player.team.useSeparateWallets.Value) ? "separate wallets" : "shared wallets") + " button"); + } + #endregion + + if (__instance.skipIntroButton != null && __instance.skipIntroButton.visible) + buttons.Add(__instance.skipIntroButton, (___skipIntro ? "Enabled" : "Disabled") + " Skip Intro Button"); + + if (__instance.advancedOptionsButton != null && __instance.advancedOptionsButton.visible) + buttons.Add(__instance.advancedOptionsButton, "Advanced Options Button"); + + if (__instance.okButton != null && __instance.okButton.visible) + buttons.Add(__instance.okButton, "OK Button"); + + if (__instance.backButton != null && __instance.backButton.visible) + buttons.Add(__instance.backButton, "Back Button"); + #endregion + + int size = buttons.Count - 1; + + if (increase) + { + saveGameIndex++; + if (saveGameIndex > size) + saveGameIndex = 0; + } + else + { + saveGameIndex--; + if (saveGameIndex < 0) + saveGameIndex = size; + } + + currentComponent = buttons.ElementAt(saveGameIndex).Key; + currentComponent!.snapMouseCursor(); + __instance.setCurrentlySnappedComponentTo(currentComponent!.myID); + + toSpeak = buttons.ElementAt(saveGameIndex).Value; + + return toSpeak.Trim(); + } + + private static string getFarmHoverText(ClickableTextureComponent farm) + { + string hoverTitle = " ", hoverText = " "; + if (!farm.name.Contains("Gray")) + { + if (farm.hoverText.Contains('_')) + { + hoverTitle = farm.hoverText.Split('_')[0]; + hoverText = farm.hoverText.Split('_')[1]; + } + else + { + hoverTitle = " "; + hoverText = farm.hoverText; + } + } + else + { + if (farm.name.Contains("Gray")) + { + hoverText = "Reach level 10 " + Game1.content.LoadString("Strings\\UI:Character_" + farm.name.Split('_')[1]) + " to unlock."; + } + } + + return $"{hoverTitle}: {hoverText}"; + } + + private static SliderBar? getCurrentSliderBar(int id, CharacterCustomization __instance) + { + if (id >= 522 && id <= 530) + { + // Three ColorPickers with 3 SliderBars each. + // First group ids by ColorPicker. + // Maps 522-524 -> 0, 525-527 -> 1, 528-530 -> 2 + int whichColorPicker = (int)Math.Floor(((float)id - 522f) / 3f); + // Next group ids by slider type. + // Maps [522,525,528] -> 0, [523,526,529] -> 1, [524,527,530] -> 2 + int whichSliderBar = (int)Math.Floor((float)id % 3f); + ColorPicker cp; + switch (whichColorPicker) + { + default: + case 0: + // 522-524 == eye color + cp = __instance.eyeColorPicker; + break; + case 1: + // 525-527 == hair color + cp = __instance.hairColorPicker; + break; + case 2: + // 528-530 == pants color + cp = __instance.pantsColorPicker; + break; + } + SliderBar sb; + switch (whichSliderBar) + { + default: + case 0: + // 522, 525, 528 == hue slider + sb = cp.hueBar; + break; + case 1: + // 523, 526, 529 == saturation slider + sb = cp.saturationBar; + break; + case 2: + // 524, 527, 530 == value slider + sb = cp.valueBar; + break; + } + return sb; + } else { + return null; + } + } + + private static void AdjustCurrentSlider(bool increase, CharacterCustomization __instance, int amount=1) + { + if (currentComponent != null && currentComponent.myID >= 522 && currentComponent.myID <= 530) + { + SliderBar sb = getCurrentSliderBar(currentComponent.myID, __instance) !; + if (sb != null) + { + double step = ((double)sb.bounds.Width / 100d); // size of 1% change in slider value + double value = (double)sb.value; + double x = 0d; + int y = currentComponent.bounds.Center.Y; + if (increase) + { + value = Math.Min(value + amount, 99d); + x = Math.Min(Math.Ceiling((value * step)), (double)sb.bounds.Width); + } else { + value = Math.Max(value - amount, 0d); + x = Math.Max(Math.Ceiling((value * step)), 0d); + } + x += (double)currentComponent.bounds.Left; + Game1.setMousePosition((int)x, y); + Game1.activeClickableMenu.receiveLeftClick((int)x, y); + } + } + } + + // Most values (exception noted below) are 0 indexed internally but visually start from 1. Thus we increment before returning. + private static string getCurrentSkin() + { + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Skin")) + return $"Skin tone: {Game1.player.skin.Value + 1}"; + return ""; + } + + private static string getCurrentHair() + { + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Hair")) + return $"hair style: {Game1.player.hair.Value + 1}"; + return ""; + } + + private static string getCurrentShirt() + { + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Shirt")) + return $"Shirt: {Game1.player.shirt.Value + 1}"; + return ""; + } + + private static string getCurrentPants() + { + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Pants Style")) + return $"Pants: {Game1.player.pants.Value + 1}"; + return ""; + } + + private static string getCurrentAccessory() + { + // Internally accessory starts from -1 while displaying +1 on screen. + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Acc")) + return $"accessory: {Game1.player.accessory.Value + 2}"; + return ""; + } + + private static string getCurrentEyeColor() + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) + return $"Eye color: {Game1.player.newEyeColor.R}, {Game1.player.newEyeColor.G}, {Game1.player.newEyeColor.B}"; + return ""; + } + + private static string getCurrentEyeColorHue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(522, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentEyeColorSaturation(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(523, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentEyeColorValue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(524, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentHairColor() + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) + return $"Hair color: {Game1.player.hairstyleColor.R}, {Game1.player.hairstyleColor.G}, {Game1.player.hairstyleColor.B}"; + return ""; + } + + private static string getCurrentHairColorHue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(525, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentHairColorSaturation(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(526, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentHairColorValue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(527, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentPantsColor() + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) + return $"Pants color: {Game1.player.pantsColor.R}, {Game1.player.pantsColor.G}, {Game1.player.pantsColor.B}"; + return ""; + } + + private static string getCurrentPantsColorHue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(528, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentPantsColorSaturation(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(529, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentPantsColorValue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(530, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentPetName() + { + if (currentComponent != null && currentComponent.name == "Pet") + { + return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed; + } else { + return ""; + } + } + } +} diff --git a/stardew-access/Patches/TitleMenuPatches.cs b/stardew-access/Patches/TitleMenuPatches.cs index ae17c55..c93e2b6 100644 --- a/stardew-access/Patches/TitleMenuPatches.cs +++ b/stardew-access/Patches/TitleMenuPatches.cs @@ -1,5 +1,4 @@ using StardewValley; -using StardewValley.Characters; using StardewValley.Menus; using static StardewValley.Menus.CharacterCustomization; using static StardewValley.Menus.LoadGameMenu; @@ -12,7 +11,27 @@ namespace stardew_access.Patches private static bool isRunning = false; public static string characterCreationMenuQueryKey = " "; public static string advancedGameOptionsQueryKey = " "; + public static string prevPants = " "; + public static string prevShirt = " "; + public static string prevHair = " "; + public static string prevAccessory = " "; + public static string prevSkin = " "; + public static string prevEyeColor = " "; + public static string prevEyeColorHue = " "; + public static string prevEyeColorSaturation = " "; + public static string prevEyeColorValue = " "; + public static string prevHairColor = " "; + public static string prevHairColorHue = " "; + public static string prevHairColorSaturation = " "; + public static string prevHairColorValue = " "; + public static string prevPantsColor = " "; + public static string prevPantsColorHue = " "; + public static string prevPantsColorSaturation = " "; + public static string prevPantsColorValue = " "; public static string prevPetName = " "; + public static bool characterDesignToggle = false; + public static bool characterDesignToggleShouldSpeak = true; + public static ClickableComponent? currentComponent = null; internal static void AdvancedGameOptionsPatch(AdvancedGameOptions __instance) { @@ -248,8 +267,14 @@ namespace stardew_access.Patches try { bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box - string toSpeak = " "; - string currentPetName = getCurrentPetName(); + string toSpeak = ""; + if (characterDesignToggleShouldSpeak) + { + toSpeak = "Press left control + space to toggle character appearance controls"; + characterDesignToggleShouldSpeak = false; + } + string itemsToSpeak = ""; + string changesToSpeak = ""; if (___nameBox.Selected) { @@ -281,23 +306,65 @@ namespace stardew_access.Patches else if (MainClass.Config.CharacterCreationMenuNextKey.JustPressed() && !isRunning) { isRunning = true; - CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel); + itemsToSpeak =CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); + if (itemsToSpeak != "") + toSpeak = $"{itemsToSpeak} \n {toSpeak}"; Task.Delay(200).ContinueWith(_ => { isRunning = false; }); } else if (MainClass.Config.CharacterCreationMenuPreviousKey.JustPressed() && !isRunning) { isRunning = true; - CycleThroughItems(false, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel); + toSpeak = CycleThroughItems(false, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); Task.Delay(200).ContinueWith(_ => { isRunning = false; }); } - if (prevPetName != currentPetName) + else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderIncreaseKey.JustPressed() && !isRunning) { - prevPetName = currentPetName; - toSpeak = $"Current Pet: {currentPetName} \n {toSpeak}"; + isRunning = true; + AdjustCurrentSlider(true, __instance); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); } - if (characterCreationMenuQueryKey != toSpeak && toSpeak != " ") + else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderLargeIncreaseKey.JustPressed() && !isRunning) + { + isRunning = true; + AdjustCurrentSlider(true, __instance, 10); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + + else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderDecreaseKey.JustPressed() && !isRunning) + { + isRunning = true; + AdjustCurrentSlider(false, __instance); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + + else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderLargeDecreaseKey.JustPressed() && !isRunning) + { + isRunning = true; + AdjustCurrentSlider(false, __instance, 10); + Task.Delay(200).ContinueWith(_ => { isRunning = false; }); + } + + else if (Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl) && MainClass.Config.CharacterCreationMenuDesignToggleKey.JustPressed() && !isRunning) + { + string displayState = ""; + characterDesignToggle = !characterDesignToggle; + saveGameIndex = Math.Min(saveGameIndex, 5); // move to random skin button if focus was beyond that point + if (characterDesignToggle) + { + displayState = "shown"; + } else { + displayState = "hidden"; + } + toSpeak = $"Character design controls {displayState}. \n {toSpeak}"; + } + + changesToSpeak = getChangesToSpeak(__instance); + if (changesToSpeak != "") + toSpeak = $"{toSpeak} \n {changesToSpeak}"; + + if (characterCreationMenuQueryKey != toSpeak && toSpeak.Trim() != "") { characterCreationMenuQueryKey = toSpeak; MainClass.ScreenReader.Say(toSpeak, true); @@ -309,23 +376,258 @@ namespace stardew_access.Patches } } - private static void CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro, - ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel) + private static string getChangesToSpeak(CharacterCustomization __instance) + { + string toSpeak = ""; + string currentPetName = getCurrentPetName(); + string currentSkin = getCurrentSkin(); + string currentHair = getCurrentHair(); + string currentShirt = getCurrentShirt(); + string currentPants = getCurrentPants(); + string currentAccessory = getCurrentAccessory(); + string currentEyeColor = getCurrentEyeColor(); + string currentEyeColorHue = getCurrentEyeColorHue(__instance); + string currentEyeColorSaturation = getCurrentEyeColorSaturation(__instance); + string currentEyeColorValue = getCurrentEyeColorValue(__instance); + string currentHairColor = getCurrentHairColor(); + string currentHairColorHue = getCurrentHairColorHue(__instance); + string currentHairColorSaturation = getCurrentHairColorSaturation(__instance); + string currentHairColorValue = getCurrentHairColorValue(__instance); + string currentPantsColor = getCurrentPantsColor(); + string currentPantsColorHue = getCurrentPantsColorHue(__instance); + string currentPantsColorSaturation = getCurrentPantsColorSaturation(__instance); + string currentPantsColorValue = getCurrentPantsColorValue(__instance); + + if (characterDesignToggle) + { + if (prevSkin != currentSkin) + { + prevSkin = currentSkin; + if (currentSkin != "") + toSpeak = $"{toSpeak} \n {currentSkin}"; + } + + if (prevHair != currentHair) + { + prevHair = currentHair; + if (currentHair != "") + toSpeak = $"{toSpeak} \n {currentHair}"; + } + + if (prevShirt != currentShirt) + { + prevShirt = currentShirt; + if (currentShirt != "") + toSpeak = $"{toSpeak} \n {currentShirt}"; + } + + if (prevPants != currentPants) + { + prevPants = currentPants; + if (currentPants != "") + toSpeak = $"{toSpeak} \n {currentPants}"; + } + + if (prevAccessory != currentAccessory) + { + prevAccessory = currentAccessory; + if (currentAccessory != "") + toSpeak = $"{toSpeak} \n {currentAccessory}"; + } + + if (prevEyeColorHue != currentEyeColorHue) + { + if (currentComponent != null && currentComponent.myID == 522) + { + prevEyeColorHue = currentEyeColorHue; + if (currentEyeColorHue != "") + toSpeak = $"{toSpeak} \n Hue: {currentEyeColorHue}"; + } else { + prevEyeColorHue = ""; + } + } + + if (prevEyeColorSaturation != currentEyeColorSaturation) + { + if (currentComponent != null && currentComponent.myID == 523) + { + prevEyeColorSaturation = currentEyeColorSaturation; + if (currentEyeColorSaturation != "") + toSpeak = $"{toSpeak} \n Saturation: {currentEyeColorSaturation}"; + } else { + prevEyeColorSaturation = ""; + } + } + + if (prevEyeColorValue != currentEyeColorValue) + { + if (currentComponent != null && currentComponent.myID == 524) + { + prevEyeColorValue = currentEyeColorValue; + if (currentEyeColorValue != "") + toSpeak = $"{toSpeak} \n Value: {currentEyeColorValue}"; + } else { + prevEyeColorValue = ""; + } + } + + if (prevEyeColor != currentEyeColor) + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 || currentComponent.myID <= 524))) + { + prevEyeColor = currentEyeColor; + if (currentEyeColor != "") + toSpeak = $"{toSpeak} \n {currentEyeColor}"; + } + } + + if (prevHairColorHue != currentHairColorHue) + { + if (currentComponent != null && currentComponent.myID == 525) + { + prevHairColorHue = currentHairColorHue; + if (currentHairColorHue != "") + toSpeak = $"{toSpeak} \n Hue: {currentHairColorHue}"; + } else { + prevHairColorHue = ""; + } + } + + if (prevHairColorSaturation != currentHairColorSaturation) + { + if (currentComponent != null && currentComponent.myID == 526) + { + prevHairColorSaturation = currentHairColorSaturation; + if (currentHairColorSaturation != "") + toSpeak = $"{toSpeak} \n Saturation: {currentHairColorSaturation}"; + } else { + prevHairColorSaturation = ""; + } + } + + if (prevHairColorValue != currentHairColorValue) + { + if (currentComponent != null && currentComponent.myID == 527) + { + prevHairColorValue = currentHairColorValue; + if (currentHairColorValue != "") + toSpeak = $"{toSpeak} \n Value: {currentHairColorValue}"; + } else { + prevHairColorValue = ""; + } + } + + if (prevHairColor != currentHairColor) + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 || currentComponent.myID <= 527))) + { + prevHairColor = currentHairColor; + if (currentHairColor != "") + toSpeak = $"{toSpeak} \n {currentHairColor}"; + } + } + + if (prevPantsColorHue != currentPantsColorHue) + { + if (currentComponent != null && currentComponent.myID == 528) + { + prevPantsColorHue = currentPantsColorHue; + if (currentPantsColorHue != "") + toSpeak = $"{toSpeak} \n Hue: {currentPantsColorHue}"; + } else { + prevPantsColorHue = ""; + } + } + + if (prevPantsColorSaturation != currentPantsColorSaturation) + { + if (currentComponent != null && currentComponent.myID == 529) + { + prevPantsColorSaturation = currentPantsColorSaturation; + if (currentPantsColorSaturation != "") + toSpeak = $"{toSpeak} \n Saturation: {currentPantsColorSaturation}"; + } else { + prevPantsColorSaturation = ""; + } + } + + if (prevPantsColorValue != currentPantsColorValue) + { + if (currentComponent != null && currentComponent.myID == 530) + { + prevPantsColorValue = currentPantsColorValue; + if (currentPantsColorValue != "") + toSpeak = $"{toSpeak} \n Value: {currentPantsColorValue}"; + } else { + prevPantsColorValue = ""; + } + } + + if (prevPantsColor != currentPantsColor) + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 || currentComponent.myID <= 530))) + { + prevPantsColor = currentPantsColor; + if (currentPantsColor != "") + toSpeak = $"{toSpeak} \n {currentPantsColor}"; + } + } + } + + if (prevPetName != currentPetName) + { + prevPetName = currentPetName; + if (currentPetName != "") + toSpeak = $"{toSpeak} \n Current Pet: {currentPetName}"; + } + return toSpeak.Trim(); + } + + + private static string CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro, + ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox, + TextBox ___farmnameBox, TextBox ___favThingBox) { string toSpeak = " "; + int DesignControlsIndex = 0; Dictionary buttons = new(); #region Add buttons with their names IF they are available #region Character related + string postText = ""; if (__instance.nameBoxCC != null && __instance.nameBoxCC.visible) - buttons.Add(__instance.nameBoxCC, "Farmer's Name Text box"); + { + if (___nameBox.Text != "") + { + postText = $": {___nameBox.Text}"; + } else { + postText = " Text Box"; + } + buttons.Add(__instance.nameBoxCC, $"Farmer's Name{postText}"); + } if (__instance.farmnameBoxCC != null && __instance.farmnameBoxCC.visible) - buttons.Add(__instance.farmnameBoxCC, "Farm's Name Text box"); + { + if (___farmnameBox.Text != "") + { + postText = $": {___farmnameBox.Text}"; + } else { + postText = " Text Box"; + } + buttons.Add(__instance.farmnameBoxCC, $"Farm's Name{postText}"); + } if (__instance.favThingBoxCC != null && __instance.favThingBoxCC.visible) - buttons.Add(__instance.favThingBoxCC, "Favourite Thing Text box"); + { + if (___favThingBox.Text != "") + { + postText = $": {___favThingBox.Text}"; + } else { + postText = " Text Box"; + } + buttons.Add(__instance.favThingBoxCC, $"Favourite Thing{postText}"); + } if (__instance.petPortraitBox.HasValue) // Cannot get petButtons like with others { @@ -339,11 +641,94 @@ namespace stardew_access.Patches if (__instance.randomButton != null && __instance.randomButton.visible) buttons.Add(__instance.randomButton, "Random Skin Button"); + // Controls to rotate the farmer (Potentially useful for low vision players) are first if they're available. + // They also appear above the gender buttons, so we handle them separately here. + if (characterDesignToggle && new[] {__instance.leftSelectionButtons.Count, __instance.rightSelectionButtons.Count }.All(c => c >= 0)) // both have Count > 0 + { + if (new[] {__instance.leftSelectionButtons[DesignControlsIndex].visible, __instance.rightSelectionButtons[DesignControlsIndex].visible }.All(v => v == true) // both visible + && new[] {__instance.leftSelectionButtons[DesignControlsIndex].name, __instance.rightSelectionButtons[DesignControlsIndex].name }.All(n => n == "Direction")) // both named "Direction" + { + buttons.Add(__instance.leftSelectionButtons[DesignControlsIndex], "Rotate Left Button"); + buttons.Add(__instance.rightSelectionButtons[DesignControlsIndex], "Rotate Right Button"); + ++DesignControlsIndex; + } + } + if (__instance.genderButtons.Count > 0) { buttons.Add(__instance.genderButtons[0], ((Game1.player.IsMale) ? "Selected " : "") + "Gender: Male Button"); buttons.Add(__instance.genderButtons[1], ((!Game1.player.IsMale) ? "Selected " : "") + "Gender: Female Button"); } + + if (characterDesignToggle&& new[] {__instance.leftSelectionButtons.Count, __instance.rightSelectionButtons.Count }.All(c => c >= DesignControlsIndex) && new[] {__instance.leftSelectionButtons[DesignControlsIndex].visible, __instance.rightSelectionButtons[DesignControlsIndex].visible }.All(v => v == true)) + { + while(DesignControlsIndex < __instance.leftSelectionButtons.Count) + { + ClickableComponent left = __instance.leftSelectionButtons[DesignControlsIndex]; + ClickableComponent right = __instance.rightSelectionButtons[DesignControlsIndex]; + string name = left.name; + // minor cleanup on names to be slightly more descriptive + switch (name) + { + case "Skin": + name += " Tone"; + break; + case "Hair": + name += " Style"; + break; + case "Acc": + name = "Accessory"; + break; + default: + break; + } + if (!buttons.ContainsKey(left) || !buttons.ContainsKey(right)) + { + buttons.Add(left, $"Previous {name} button"); + buttons.Add(right, $"Next {name} button"); + } + //MainClass.ScreenReader.Say($"Left {DesignControlsIndex}: {__instance.leftSelectionButtons[DesignControlsIndex]} {__instance.leftSelectionButtons[DesignControlsIndex].name}\n", true); + //MainClass.ScreenReader.Say($"Right {DesignControlsIndex}: {__instance.rightSelectionButtons[DesignControlsIndex]} {__instance.rightSelectionButtons[DesignControlsIndex].name}\n", true); + ++DesignControlsIndex; + } + + ClickableComponent eyeColorHue = __instance.getComponentWithID(522); + if (eyeColorHue != null && eyeColorHue.visible) + buttons.Add(eyeColorHue, "eye color hue slider"); + + ClickableComponent eyeColorSaturation = __instance.getComponentWithID(523); + if (eyeColorSaturation != null && eyeColorSaturation.visible) + buttons.Add(eyeColorSaturation, "eye color saturation slider"); + + ClickableComponent eyeColorValue = __instance.getComponentWithID(524); + if (eyeColorValue != null && eyeColorValue.visible) + buttons.Add(eyeColorValue, "eye color Value slider"); + + ClickableComponent hairColorHue = __instance.getComponentWithID(525); + if (hairColorHue != null && hairColorHue.visible) + buttons.Add(hairColorHue, "hair color hue slider"); + + ClickableComponent hairColorSaturation = __instance.getComponentWithID(526); + if (hairColorSaturation != null && hairColorSaturation.visible) + buttons.Add(hairColorSaturation, "hair color saturation slider"); + + ClickableComponent hairColorValue = __instance.getComponentWithID(527); + if (hairColorValue != null && hairColorValue.visible) + buttons.Add(hairColorValue, "hair color Value slider"); + + ClickableComponent pantsColorHue = __instance.getComponentWithID(528); + if (pantsColorHue != null && pantsColorHue.visible) + buttons.Add(pantsColorHue, "pants color hue slider"); + + ClickableComponent pantsColorSaturation = __instance.getComponentWithID(529); + if (pantsColorSaturation != null && pantsColorSaturation.visible) + buttons.Add(pantsColorSaturation, "pants color saturation slider"); + + ClickableComponent pantsColorValue = __instance.getComponentWithID(530); + if (pantsColorValue != null && pantsColorValue.visible) + buttons.Add(pantsColorValue, "pants color Value slider"); + } + #endregion #region Farm layout related @@ -420,18 +805,230 @@ namespace stardew_access.Patches saveGameIndex = size; } - buttons.ElementAt(saveGameIndex).Key.snapMouseCursor(); + currentComponent = buttons.ElementAt(saveGameIndex).Key; + currentComponent!.snapMouseCursor(); + __instance.setCurrentlySnappedComponentTo(currentComponent!.myID); + toSpeak = buttons.ElementAt(saveGameIndex).Value; - if (toSpeak != " ") + return toSpeak.Trim(); + } + + private static SliderBar? getCurrentSliderBar(int id, CharacterCustomization __instance) + { + if (id >= 522 && id <= 530) { - MainClass.ScreenReader.Say(toSpeak, true); + // Three ColorPickers with 3 SliderBars each. + // First group ids by ColorPicker. + // Maps 522-524 -> 0, 525-527 -> 1, 528-530 -> 2 + int whichColorPicker = (int)Math.Floor(((float)id - 522f) / 3f); + // Next group ids by slider type. + // Maps [522,525,528] -> 0, [523,526,529] -> 1, [524,527,530] -> 2 + int whichSliderBar = (int)Math.Floor((float)id % 3f); + ColorPicker cp; + switch (whichColorPicker) + { + default: + case 0: + // 522-524 == eye color + cp = __instance.eyeColorPicker; + break; + case 1: + // 525-527 == hair color + cp = __instance.hairColorPicker; + break; + case 2: + // 528-530 == pants color + cp = __instance.pantsColorPicker; + break; + } + SliderBar sb; + switch (whichSliderBar) + { + default: + case 0: + // 522, 525, 528 == hue slider + sb = cp.hueBar; + break; + case 1: + // 523, 526, 529 == saturation slider + sb = cp.saturationBar; + break; + case 2: + // 524, 527, 530 == value slider + sb = cp.valueBar; + break; + } + return sb; + } else { + return null; } } + private static void AdjustCurrentSlider(bool increase, CharacterCustomization __instance, int amount=1) + { + if (currentComponent != null && currentComponent.myID >= 522 && currentComponent.myID <= 530) + { + SliderBar sb = getCurrentSliderBar(currentComponent.myID, __instance) !; + if (sb != null) + { + double step = ((double)sb.bounds.Width / 100d); // size of 1% change in slider value + double value = (double)sb.value; + double x = 0d; + int y = currentComponent.bounds.Center.Y; + if (increase) + { + value = Math.Min(value + amount, 99d); + x = Math.Min(Math.Ceiling((value * step)), (double)sb.bounds.Width); + } else { + value = Math.Max(value - amount, 0d); + x = Math.Max(Math.Ceiling((value * step)), 0d); + } + x += (double)currentComponent.bounds.Left; + Game1.setMousePosition((int)x, y); + Game1.activeClickableMenu.receiveLeftClick((int)x, y); + } + } + } + + // Most values (exception noted below) are 0 indexed internally but visually start from 1. Thus we increment before returning. + private static string getCurrentSkin() + { + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Skin")) + return $"Skin tone: {Game1.player.skin.Value + 1}"; + return ""; + } + + private static string getCurrentHair() + { + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Hair")) + return $"hair style: {Game1.player.hair.Value + 1}"; + return ""; + } + + private static string getCurrentShirt() + { + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Shirt")) + return $"Shirt: {Game1.player.shirt.Value + 1}"; + return ""; + } + + private static string getCurrentPants() + { + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Pants Style")) + return $"Pants: {Game1.player.pants.Value + 1}"; + return ""; + } + + private static string getCurrentAccessory() + { + // Internally accessory starts from -1 while displaying +1 on screen. + if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Acc")) + return $"accessory: {Game1.player.accessory.Value + 2}"; + return ""; + } + + private static string getCurrentEyeColor() + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) + return $"Eye color: {Game1.player.newEyeColor.R}, {Game1.player.newEyeColor.G}, {Game1.player.newEyeColor.B}"; + return ""; + } + + private static string getCurrentEyeColorHue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(522, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentEyeColorSaturation(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(523, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentEyeColorValue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(524, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentHairColor() + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) + return $"Hair color: {Game1.player.hairstyleColor.R}, {Game1.player.hairstyleColor.G}, {Game1.player.hairstyleColor.B}"; + return ""; + } + + private static string getCurrentHairColorHue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(525, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentHairColorSaturation(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(526, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentHairColorValue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(527, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentPantsColor() + { + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) + return $"Pants color: {Game1.player.pantsColor.R}, {Game1.player.pantsColor.G}, {Game1.player.pantsColor.B}"; + return ""; + } + + private static string getCurrentPantsColorHue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(528, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentPantsColorSaturation(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(529, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) + return sb.value!.ToString(); + return ""; + } + + private static string getCurrentPantsColorValue(CharacterCustomization __instance) + { + SliderBar sb = getCurrentSliderBar(530, __instance)!; + if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) + return sb.value!.ToString(); + return ""; + } + private static string getCurrentPetName() { - return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed; + if (currentComponent != null && currentComponent.name == "Pet") + { + return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed; + } else { + return ""; + } } private static string getFarmHoverText(ClickableTextureComponent farm) diff --git a/stardew-access/stardew-access.csproj b/stardew-access/stardew-access.csproj index 719f9a1..ab03cfc 100644 --- a/stardew-access/stardew-access.csproj +++ b/stardew-access/stardew-access.csproj @@ -12,8 +12,9 @@ - - + + +