Aimbot-ParallelEnv/Assets/Script/UI/TargetLevelProbabilityPanel.cs

291 lines
14 KiB
C#
Raw Normal View History

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;
}
}
}