291 lines
14 KiB
C#
291 lines
14 KiB
C#
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<GameObject> singleLevelPanelsObjs = new List<GameObject>();
|
|
public List<SingleLevelProbabilityPanel> singleLevelPanels = new List<SingleLevelProbabilityPanel>();
|
|
|
|
private int panelNum = 0;
|
|
private Targets thisTarget;
|
|
|
|
/// <summary>
|
|
/// Initializes panels for a specific target, including the title and level probabilities.
|
|
/// </summary>
|
|
/// <param name="thisTarget">The specified target, used to obtain associated level numbers and probability data.</param>
|
|
/// <param name="titleName">The title name for the panel.</param>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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<RectTransform>().sizeDelta.x;
|
|
float defaultLevelHeight = singleLevelProbabilityPanel.GetComponent<RectTransform>().sizeDelta.y;
|
|
float titleHeight = targetTitleText.GetComponent<RectTransform>().sizeDelta.y;
|
|
// Debug.Log("averageProbability: " + averageProbability);
|
|
transform.GetComponent<RectTransform>().sizeDelta = new Vector2(defaultWidth, (defaultLevelHeight * levelNum) + titleHeight);
|
|
// create title text
|
|
titleText = Instantiate(targetTitleText, transform);
|
|
titleText.GetComponent<TextMeshProUGUI>().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<SingleLevelProbabilityPanel>());
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the probability values for all levels of the current target.
|
|
/// </summary>
|
|
/// <returns>A list of floating-point numbers containing the probability values for each level.</returns>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public List<float> GetTargetProb()
|
|
{
|
|
List<float> targetProb = new List<float>();
|
|
for (int i = 0; i < panelNum; i++)
|
|
{
|
|
targetProb.Add(singleLevelPanels[i].ProbabilityValue);
|
|
}
|
|
return targetProb;
|
|
}
|
|
|
|
/// <summary>
|
|
/// On Probability Slider Value Change.Adjust other sliders' value to make sure the total value is 1.
|
|
/// </summary>
|
|
/// <param name="value">change to this value</param>
|
|
/// <param name="exceptedIndex">changed panel index</param>
|
|
private void OnProbabilityValueChange<T>(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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// enable all sliders' onValueChanged action listener
|
|
/// </summary>
|
|
private void enableSliderListener()
|
|
{
|
|
for (int i = 0; i < panelNum; i++)
|
|
{
|
|
int tempIndex = i;
|
|
singleLevelPanels[i].probabilitySlider.onValueChanged.AddListener((value) => OnProbabilityValueChange(value, tempIndex));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculates correction values.
|
|
/// </summary>
|
|
/// <param name="correctionValues">The current array of correction values.</param>
|
|
/// <param name="exceptedIndex">The index of the panel that is expected not to change.</param>
|
|
/// <param name="value">The expected probability value.</param>
|
|
/// <param name="extraValue">Additional correction value.</param>
|
|
/// <param name="maxLimitValue">The maximum limit for the probability value.</param>
|
|
/// <returns>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.</returns>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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<int> reCorrectionIndex = new List<int>();
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies correction values to adjust the probability values for each level of the current target.
|
|
/// </summary>
|
|
/// <param name="correctionValues">An array of correction values to be applied to the probability value of each level.</param>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
}
|
|
} |