using System; using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; public class TargetLevelProbabilityPanel : MonoBehaviour { [SerializeField] private GameObject singleLevelProbabilityPanel; [SerializeField] private GameObject targetTitleText; private GameObject titleText; public List singleLevelPanelsObjs = new List(); public List singleLevelPanels = new List(); private int panelNum = 0; private Targets thisTarget; /// /// Initializes panels for a specific target, including the title and level probabilities. /// /// The specified target, used to obtain associated level numbers and probability data. /// The title name for the panel. /// /// This method first calculates the panel dimensions, then creates and sets the title text. Subsequently, it creates individual probability panels for each level and adds event listeners for sliders and input fields. /// It relies on the CommonParameterContainer instance to access scene prefab sets and level probabilities. /// public void IntializePanels(Targets thisTarget, string titleName) { this.thisTarget = thisTarget; int levelNum = CommonParameterContainer.Instance.scenePrefabSet.GetLevelNumber(thisTarget); // initialize target level probability panel size float defaultWidth = singleLevelProbabilityPanel.GetComponent().sizeDelta.x; float defaultLevelHeight = singleLevelProbabilityPanel.GetComponent().sizeDelta.y; float titleHeight = targetTitleText.GetComponent().sizeDelta.y; // Debug.Log("averageProbability: " + averageProbability); transform.GetComponent().sizeDelta = new Vector2(defaultWidth, (defaultLevelHeight * levelNum) + titleHeight); // create title text titleText = Instantiate(targetTitleText, transform); titleText.GetComponent().text = titleName; // create and initialize single level probability panels for (int i = 0; i < levelNum; i++) { int tempIndex = i; singleLevelPanelsObjs.Add(Instantiate(singleLevelProbabilityPanel, transform)); singleLevelPanels.Add(singleLevelPanelsObjs[i].GetComponent()); Debug.Log(CommonParameterContainer.Instance.levelProbs.Count); singleLevelPanels[i].InitializeLevelProbabilityPanel(i, CommonParameterContainer.Instance.levelProbs[thisTarget][i]); //add onValueChanged event to slider and input field singleLevelPanels[i].probabilitySlider.onValueChanged.AddListener((value) => OnProbabilityValueChange(value, tempIndex)); singleLevelPanels[i].inputField.onEndEdit.AddListener((value) => OnProbabilityValueChange(value, tempIndex)); } panelNum = levelNum; } /// /// Retrieves the probability values for all levels of the current target. /// /// A list of floating-point numbers containing the probability values for each level. /// /// This method iterates through the panels of all levels, collecting and returning the probability value for each level. /// It relies on the singleLevelPanels list, which should have been properly initialized and populated before calling this method. /// public List GetTargetProb() { List targetProb = new List(); for (int i = 0; i < panelNum; i++) { targetProb.Add(singleLevelPanels[i].ProbabilityValue); } return targetProb; } /// /// On Probability Slider Value Change.Adjust other sliders' value to make sure the total value is 1. /// /// change to this value /// changed panel index private void OnProbabilityValueChange(T value, int exceptedIndex) { float changedValue = 0f; switch (value) { case float floatValue: changedValue = floatValue; break; case string stringValue: changedValue = float.Parse(stringValue); // limit the value between 0 and 1 if (changedValue > 1 && changedValue <= 100) { changedValue /= 100; } else if (changedValue > 100) { changedValue = 1; } break; default: Debug.LogError("Invalid value type!"); throw new ArgumentException("Unsupported value type"); } float newTotalValue = 0; // new total probability value without locked panel int unlockedPanelNum = 0; int remainCorrectionNum = panelNum; float extraValue = 0; // the extra value after probability value changed float maxLimitValue = 1; // the max value of unlocked panel permitted float[] correctionValues = new float[panelNum]; // calculate total probability value for (int i = 0; i < panelNum; i++) { // disable slider listener singleLevelPanels[i].probabilitySlider.onValueChanged.RemoveAllListeners(); if (singleLevelPanels[i].UnLocked) { unlockedPanelNum++; newTotalValue += (i == exceptedIndex ? changedValue : singleLevelPanels[i].ProbabilityValue); } else { // delete locked probability value from maxLimitValue maxLimitValue -= singleLevelPanels[i].ProbabilityValue; } } // only have one panel if (panelNum == 1) { singleLevelPanels[exceptedIndex].SetProbability(1); enableSliderListener(); return; } //only one panel is unlocked if (unlockedPanelNum == 1) { // limit this panel value at maxLimitValue singleLevelPanels[exceptedIndex].SetProbability(maxLimitValue); enableSliderListener(); return; } // Calculate the average correction value extraValue = newTotalValue - maxLimitValue; // initialize correction value to each panel correctionValues = Enumerable.Repeat(extraValue / (unlockedPanelNum - 1), panelNum).ToArray(); // make sure all probability value is not less than 0 and equal to 1 int iterationCount = 0; while (remainCorrectionNum > 0) { iterationCount++; (correctionValues, remainCorrectionNum) = reCalculateCorrectionValues(correctionValues, exceptedIndex, changedValue, extraValue, maxLimitValue); // protect the infinite loop if (iterationCount >= 100) { Debug.LogError("Infinite loop detected!"); break; } } // applicate the value to all unlocked panels excepted the changed one applyCorrectionValue(correctionValues); enableSliderListener(); } /// /// enable all sliders' onValueChanged action listener /// private void enableSliderListener() { for (int i = 0; i < panelNum; i++) { int tempIndex = i; singleLevelPanels[i].probabilitySlider.onValueChanged.AddListener((value) => OnProbabilityValueChange(value, tempIndex)); } } /// /// Recalculates correction values. /// /// The current array of correction values. /// The index of the panel that is expected not to change. /// The expected probability value. /// Additional correction value. /// The maximum limit for the probability value. /// Returns a tuple containing a float array and an integer. /// The float array is the new correction values, and the integer is the number of panels that need to be corrected in the next iteration. /// /// This method calculates new correction values based on the provided parameters. /// During the iteration, some panels might exceed set limits, and their values will need to be corrected in the next iteration. /// private (float[], int) reCalculateCorrectionValues(float[] correctionValues, int exceptedIndex, float value, float extraValue, float maxLimitValue) { // the number of panels which need to be corrected in next iteration int remainCorrectionNum = 0; List reCorrectionIndex = new List(); // the index of the last panel which need to be corrected in this iteration int lastReCorrectionIndex = 0; // another extra value after correctionValue applicate to probability value which value also less than 0 // this value will be added to the panel which value is bigger. float underZeroExtraValueAfterCorrected = 0; for (int i = 0; i < panelNum; ++i) { // if the panel is the changed one if (i == exceptedIndex) { // limit the value under maxLimitValue correctionValues[i] = value > maxLimitValue ? (singleLevelPanels[i].ProbabilityValue - maxLimitValue) : (singleLevelPanels[i].ProbabilityValue - value); continue; } // if the panel is locked if (singleLevelPanels[i].IsLocked) { correctionValues[i] = 0; continue; } // if the panel is unlocked // and the probability value is less than 0 or bigger than maxLimitValue if (singleLevelPanels[i].ProbabilityValue < 0 || singleLevelPanels[i].ProbabilityValue > maxLimitValue) { // correct this panel value to 0 or maxLimitValue correctionValues[i] = singleLevelPanels[i].ProbabilityValue; } // probability value will be correct to 0 else if (singleLevelPanels[i].ProbabilityValue == correctionValues[i]) { // that's good keep this correction value, and add it to the extraValue underZeroExtraValueAfterCorrected += correctionValues[i]; } // probability value will be correct over the limit (bigger than maxLimitValue or less than 0) else if (singleLevelPanels[i].ProbabilityValue - correctionValues[i] < 0 || singleLevelPanels[i].ProbabilityValue - correctionValues[i] > maxLimitValue) { // should be corrected to 0 or maxLimitValue,and add it to the extraValue underZeroExtraValueAfterCorrected += singleLevelPanels[i].ProbabilityValue; correctionValues[i] = singleLevelPanels[i].ProbabilityValue; } // left panels correction value which need to be re-corrected else { lastReCorrectionIndex = i; reCorrectionIndex.Add(i); } } foreach (int index in reCorrectionIndex) { // calculate the average correction value float newAverageCorrectionValue = (extraValue - underZeroExtraValueAfterCorrected) / reCorrectionIndex.Count; // if the panel is the last one which need to be corrected. // Avoidance of floating-point rounding error, the last panel correction value will be the extraValue minus the sum of the other panels correction value if (index == lastReCorrectionIndex) { correctionValues[index] = extraValue - (newAverageCorrectionValue * (reCorrectionIndex.Count - 1)); // still out of limit after correction, correct it to 0 and activate next iteration if (singleLevelPanels[index].ProbabilityValue - correctionValues[index] < 0 || singleLevelPanels[index].ProbabilityValue - correctionValues[index] > maxLimitValue) { correctionValues[index] = singleLevelPanels[index].ProbabilityValue; remainCorrectionNum++; } } else { correctionValues[index] = newAverageCorrectionValue; // still out of limit after correction, correct it to 0 and activate next iteration if (singleLevelPanels[index].ProbabilityValue - correctionValues[index] < 0 || singleLevelPanels[index].ProbabilityValue - correctionValues[index] > maxLimitValue) { correctionValues[index] = singleLevelPanels[index].ProbabilityValue; remainCorrectionNum++; } } } return (correctionValues, remainCorrectionNum); } /// /// Applies correction values to adjust the probability values for each level of the current target. /// /// An array of correction values to be applied to the probability value of each level. /// /// This method iterates through all the level panels, updating the probability by subtracting the corresponding correction value from the probability value of each level. /// It also synchronously updates the level probabilities for this target stored in the CommonParameterContainer instance. /// This method assumes that the length of the correctionValues array matches the number of level panels. /// private void applyCorrectionValue(float[] correctionValues) { for (int i = 0; i < panelNum; i++) { singleLevelPanels[i].SetProbability(singleLevelPanels[i].ProbabilityValue - correctionValues[i]); CommonParameterContainer.Instance.levelProbs[thisTarget][i] = singleLevelPanels[i].ProbabilityValue; } } }