FText UEnvQueryTest::DescribeBoolTestParams(const FString& ConditionDesc) const { FText ParamDesc; if (Condition != EEnvTestCondition::NoCondition) { FFormatNamedArguments Args; Args.Add(TEXT("ConditionDesc"), FText::FromString(ConditionDesc)); if(BoolFilter.IsNamedParam()) { Args.Add(TEXT("Filter"), FText::FromString(UEnvQueryTypes::DescribeBoolParam(BoolFilter))); } else { Args.Add(TEXT("Filter"), BoolFilter.Value ? FText::GetEmpty() : LOCTEXT("NotWithSpace", "not ")); } FText InvalidConditionDesc = LOCTEXT("InvalidCondition", "invalid condition"); ParamDesc = (Condition == EEnvTestCondition::Match) ? (BoolFilter.IsNamedParam() ? FText::Format(LOCTEXT("RequiresCondition", "require {ConditionDesc}: {Filter}"), Args) : FText::Format(LOCTEXT("RequiresFilter", "require {Filter}{ConditionDesc}%s"), Args) ) : InvalidConditionDesc; } FFormatNamedArguments Args; Args.Add(TEXT("ParmDesc"), ParamDesc); if (!IsScoring()) { ParamDesc = FText::Format(LOCTEXT("ParmDescWithDontScore", "{ParmDesc}, don't score"), Args); } else if (ScoringEquation == EEnvTestScoreEquation::Constant) { FText WeightDesc = Weight.IsNamedParam() ? FText::FromName(Weight.ParamName) : FText::Format(LOCTEXT("ConstantWeightDescPattern", "x{0}"), FText::AsNumber(FMath::Abs(Weight.Value))); Args.Add(TEXT("WeightDesc"), WeightDesc); ParamDesc = FText::Format(LOCTEXT("ParmDescWithConstantWeight", "{ParmDesc}, constant weight [{WeightDesc}]"), Args); } else if (Weight.IsNamedParam()) { Args.Add(TEXT("WeightName"), FText::FromName(Weight.ParamName)); ParamDesc = FText::Format(LOCTEXT("ParmDescWithWeight", "{ParmDesc}, weight: {WeightName}"), Args); } else { FNumberFormattingOptions NumberFormattingOptions; NumberFormattingOptions.MaximumFractionalDigits = 2; Args.Add(TEXT("ConditionDesc"), FText::FromString(ConditionDesc)); Args.Add(TEXT("WeightCondition"), (Weight.Value > 0) ? FText::GetEmpty() : LOCTEXT("NotWithSpace", "not ")); Args.Add(TEXT("WeightValue"), FText::AsNumber(FMath::Abs(Weight.Value), &NumberFormattingOptions)); ParamDesc = FText::Format(LOCTEXT("ParmDescWithPreference", "{ParmDesc}, prefer {WeightCondition}{ConditionDesc} [x{WeightValue}]"), Args); } return ParamDesc; }
EVisibility FEnvQueryTestDetails::GetScoreVisibility() const { if (IsScoring()) { return EVisibility::Visible; } return EVisibility::Collapsed; }
FText UEnvQueryTest::DescribeFloatTestParams() const { FText FilterDesc; if (IsFiltering()) { switch (FilterType) { case EEnvTestFilterType::Minimum: FilterDesc = FText::Format(LOCTEXT("FilterAtLeast", "at least {0}"), FText::FromString(FloatValueMin.ToString())); break; case EEnvTestFilterType::Maximum: FilterDesc = FText::Format(LOCTEXT("FilterUpTo", "up to {0}"), FText::FromString(FloatValueMax.ToString())); break; case EEnvTestFilterType::Range: FilterDesc = FText::Format(LOCTEXT("FilterBetween", "between {0} and {1}"), FText::FromString(FloatValueMin.ToString()), FText::FromString(FloatValueMax.ToString())); break; default: break; } } FNumberFormattingOptions NumberFormattingOptions; NumberFormattingOptions.MaximumFractionalDigits = 2; FText ScoreDesc; if (!IsScoring()) { ScoreDesc = LOCTEXT("DontScore", "don't score"); } else if (ScoringEquation == EEnvTestScoreEquation::Constant) { FText FactorDesc = ScoringFactor.IsDynamic() ? FText::FromString(ScoringFactor.ToString()) : FText::Format(FText::FromString("x{0}"), FText::AsNumber(FMath::Abs(ScoringFactor.DefaultValue), &NumberFormattingOptions)); ScoreDesc = FText::Format(FText::FromString("{0} [{1}]"), LOCTEXT("ScoreConstant", "constant score"), FactorDesc); } else if (ScoringFactor.IsDynamic()) { ScoreDesc = FText::Format(FText::FromString("{0}: {1}"), LOCTEXT("ScoreFactor", "score factor"), FText::FromString(ScoringFactor.ToString())); } else { FText ScoreSignDesc = (ScoringFactor.DefaultValue > 0) ? LOCTEXT("Greater", "greater") : LOCTEXT("Lesser", "lesser"); FText ScoreValueDesc = FText::AsNumber(FMath::Abs(ScoringFactor.DefaultValue), &NumberFormattingOptions); ScoreDesc = FText::Format(FText::FromString("{0} {1} [x{2}]"), LOCTEXT("ScorePrefer", "prefer"), ScoreSignDesc, ScoreValueDesc); } return FilterDesc.IsEmpty() ? ScoreDesc : FText::Format(FText::FromString("{0}, {1}"), FilterDesc, ScoreDesc); }
EVisibility FEnvQueryTestDetails::GetFloatScoreVisibility() const { if (IsScoring()) { const UEnvQueryTest* MyTestOb = Cast<const UEnvQueryTest>(MyTest.Get()); if (MyTestOb && MyTestOb->GetWorkOnFloatValues()) { return EVisibility::Visible; } } return EVisibility::Collapsed; }
FText UEnvQueryTest::DescribeFloatTestParams() const { FText ParamDesc; if (Condition != EEnvTestCondition::NoCondition) { FFormatNamedArguments Args; Args.Add(TEXT("Filter"), FText::FromString(UEnvQueryTypes::DescribeFloatParam(FloatFilter))); FText InvalidConditionDesc = LOCTEXT("InvalidCondition", "invalid condition"); ParamDesc = (Condition == EEnvTestCondition::AtLeast) ? FText::Format(LOCTEXT("AtLeastFiltered", "at least {Filter}"), Args) : (Condition == EEnvTestCondition::UpTo) ? FText::Format(LOCTEXT("UpToFiltered", "up to {Filter}"), Args) : InvalidConditionDesc; } FFormatNamedArguments Args; Args.Add(TEXT("ParmDesc"), ParamDesc); if (!IsScoring()) { ParamDesc = FText::Format(LOCTEXT("ParmDescWithDontScore", "{ParmDesc}, don't score"), Args); } else if (ScoringEquation == EEnvTestScoreEquation::Constant) { FText WeightDesc = Weight.IsNamedParam() ? FText::FromName(Weight.ParamName) : FText::Format( LOCTEXT("ConstantWeightDescPattern", "x{0}"), FText::AsNumber(FMath::Abs(Weight.Value)) ); Args.Add(TEXT("WeightDesc"), WeightDesc); ParamDesc = FText::Format(LOCTEXT("ParmDescWithConstantWeight", "{ParmDesc}, constant weight [{WeightDesc}]"), Args); } else if (Weight.IsNamedParam()) { Args.Add(TEXT("WeightName"), FText::FromName(Weight.ParamName)); ParamDesc = FText::Format(LOCTEXT("ParmDescWithWeight", "{ParmDesc}, weight: {WeightName}"), Args); } else { FNumberFormattingOptions NumberFormattingOptions; NumberFormattingOptions.MaximumFractionalDigits = 2; Args.Add(TEXT("WeightCondition"), (Weight.Value > 0) ? LOCTEXT("Greater", "greater") : LOCTEXT("Lesser", "lesser")); Args.Add(TEXT("WeightValue"), FText::AsNumber(FMath::Abs(Weight.Value), &NumberFormattingOptions)); ParamDesc = FText::Format(LOCTEXT("DescriptionPreferWeightValue", "{ParmDesc}, prefer {WeightCondition} [x{WeightValue}]"), Args); } return ParamDesc; }
FText UEnvQueryTest::DescribeBoolTestParams(const FString& ConditionDesc) const { FText FilterDesc; if (IsFiltering() && FilterType == EEnvTestFilterType::Match) { FilterDesc = BoolValue.IsDynamic() ? FText::Format(FText::FromString("{0} {1}: {2}"), LOCTEXT("FilterRequire", "require"), FText::FromString(ConditionDesc), FText::FromString(BoolValue.ToString())) : FText::Format(FText::FromString("{0} {1}{2}"), LOCTEXT("FilterRequire", "require"), BoolValue.DefaultValue ? FText::GetEmpty() : LOCTEXT("NotWithSpace", "not "), FText::FromString(ConditionDesc)); } FNumberFormattingOptions NumberFormattingOptions; NumberFormattingOptions.MaximumFractionalDigits = 2; FText ScoreDesc; if (!IsScoring()) { ScoreDesc = LOCTEXT("DontScore", "don't score"); } else if (ScoringEquation == EEnvTestScoreEquation::Constant) { FText FactorDesc = ScoringFactor.IsDynamic() ? FText::FromString(ScoringFactor.ToString()) : FText::Format(FText::FromString("x{0}"), FText::AsNumber(FMath::Abs(ScoringFactor.DefaultValue), &NumberFormattingOptions)); ScoreDesc = FText::Format(FText::FromString("{0} [{1}]"), LOCTEXT("ScoreConstant", "constant score"), FactorDesc); } else if (ScoringFactor.IsDynamic()) { ScoreDesc = FText::Format(FText::FromString("{0}: {1}"), LOCTEXT("ScoreFactor", "score factor"), FText::FromString(ScoringFactor.ToString())); } else { FText ScoreSignDesc = (ScoringFactor.DefaultValue > 0) ? FText::GetEmpty() : LOCTEXT("NotWithSpace", "not "); FText ScoreValueDesc = FText::AsNumber(FMath::Abs(ScoringFactor.DefaultValue), &NumberFormattingOptions); ScoreDesc = FText::Format(FText::FromString("{0} {1}{2} [x{3}]"), LOCTEXT("ScorePrefer", "prefer"), ScoreSignDesc, FText::FromString(ConditionDesc), ScoreValueDesc); } return FilterDesc.IsEmpty() ? ScoreDesc : FText::Format(FText::FromString("{0}, {1}"), FilterDesc, ScoreDesc); }
void UEnvQueryTest::NormalizeItemScores(FEnvQueryInstance& QueryInstance) { if (!IsScoring()) { return; } ScoringFactor.BindData(QueryInstance.Owner.Get(), QueryInstance.QueryID); float ScoringFactorValue = ScoringFactor.GetValue(); float MinScore = 0; float MaxScore = -BIG_NUMBER; if (ClampMinType == EEnvQueryTestClamping::FilterThreshold) { FloatValueMin.BindData(QueryInstance.Owner.Get(), QueryInstance.QueryID); MinScore = FloatValueMin.GetValue(); } else if (ClampMinType == EEnvQueryTestClamping::SpecifiedValue) { ScoreClampMin.BindData(QueryInstance.Owner.Get(), QueryInstance.QueryID); MinScore = ScoreClampMin.GetValue(); } if (ClampMaxType == EEnvQueryTestClamping::FilterThreshold) { FloatValueMax.BindData(QueryInstance.Owner.Get(), QueryInstance.QueryID); MaxScore = FloatValueMax.GetValue(); } else if (ClampMaxType == EEnvQueryTestClamping::SpecifiedValue) { ScoreClampMax.BindData(QueryInstance.Owner.Get(), QueryInstance.QueryID); MaxScore = ScoreClampMax.GetValue(); } FEnvQueryItemDetails* DetailInfo = QueryInstance.ItemDetails.GetData(); if ((ClampMinType == EEnvQueryTestClamping::None) || (ClampMaxType == EEnvQueryTestClamping::None) ) { for (int32 ItemIndex = 0; ItemIndex < QueryInstance.Items.Num(); ItemIndex++, DetailInfo++) { if (!QueryInstance.Items[ItemIndex].IsValid()) { continue; } float TestValue = DetailInfo->TestResults[QueryInstance.CurrentTest]; if (TestValue != UEnvQueryTypes::SkippedItemValue) { if (ClampMinType == EEnvQueryTestClamping::None) { MinScore = FMath::Min(MinScore, TestValue); } if (ClampMaxType == EEnvQueryTestClamping::None) { MaxScore = FMath::Max(MaxScore, TestValue); } } } } DetailInfo = QueryInstance.ItemDetails.GetData(); if (MinScore != MaxScore) { for (int32 ItemIndex = 0; ItemIndex < QueryInstance.ItemDetails.Num(); ItemIndex++, DetailInfo++) { if (QueryInstance.Items[ItemIndex].IsValid() == false) { continue; } float WeightedScore = 0.0f; float& TestValue = DetailInfo->TestResults[QueryInstance.CurrentTest]; if (TestValue != UEnvQueryTypes::SkippedItemValue) { const float ClampedScore = FMath::Clamp(TestValue, MinScore, MaxScore); const float NormalizedScore = (ClampedScore - MinScore) / (MaxScore - MinScore); // TODO? Add an option to invert the normalized score before applying an equation. const float NormalizedScoreForEquation = /*bMirrorNormalizedScore ? (1.0f - NormalizedScore) :*/ NormalizedScore; switch (ScoringEquation) { case EEnvTestScoreEquation::Linear: WeightedScore = ScoringFactorValue * NormalizedScoreForEquation; break; case EEnvTestScoreEquation::InverseLinear: { // For now, we're avoiding having a separate flag for flipping the direction of the curve // because we don't have usage cases yet and want to avoid too complex UI. If we decide // to add that flag later, we'll need to remove this option, since it should just be "mirror // curve" plus "Linear". float InverseNormalizedScore = (1.0f - NormalizedScoreForEquation); WeightedScore = ScoringFactorValue * InverseNormalizedScore; break; } case EEnvTestScoreEquation::Square: WeightedScore = ScoringFactorValue * (NormalizedScoreForEquation * NormalizedScoreForEquation); break; case EEnvTestScoreEquation::Constant: // I know, it's not "constant". It's "Constant, or zero". The tooltip should explain that. WeightedScore = (NormalizedScoreForEquation > 0) ? ScoringFactorValue : 0.0f; break; default: break; } } else { // Do NOT clear TestValue to 0, because the SkippedItemValue is used to display "SKIP" when debugging. // TestValue = 0.0f; WeightedScore = 0.0f; } #if USE_EQS_DEBUGGER DetailInfo->TestWeightedScores[QueryInstance.CurrentTest] = WeightedScore; #endif QueryInstance.Items[ItemIndex].Score += WeightedScore; } } }
void UEnvQueryTest::NormalizeItemScores(FEnvQueryInstance& QueryInstance) { if (!IsScoring()) { return; } float WeightValue = 0.0f; if (!QueryInstance.GetParamValue(Weight, WeightValue, TEXT("Weight"))) { return; } float MinScore = 0; float MaxScore = -BIG_NUMBER; if (ClampMinType == EEnvQueryTestClamping::FilterThreshold) { bool bSuccess = QueryInstance.GetParamValue(FloatFilterMin, MinScore, TEXT("FloatFilterMin")); if (!bSuccess) { UE_LOG(LogEQS, Warning, TEXT("Unable to get FloatFilterMin parameter value from EnvQueryInstance %s"), FloatFilterMin.IsNamedParam() ? *FloatFilterMin.ParamName.ToString() : TEXT("<No name specified>")); } } else if (ClampMinType == EEnvQueryTestClamping::SpecifiedValue) { bool bSuccess = QueryInstance.GetParamValue(ScoreClampingMin, MinScore, TEXT("ScoreClampingMin")); if (!bSuccess) { UE_LOG(LogEQS, Warning, TEXT("Unable to get ClampMinType parameter value from EnvQueryInstance %s"), ScoreClampingMin.IsNamedParam() ? *ScoreClampingMin.ParamName.ToString() : TEXT("<No name specified>")); } } if (ClampMaxType == EEnvQueryTestClamping::FilterThreshold) { bool bSuccess = QueryInstance.GetParamValue(FloatFilterMax, MaxScore, TEXT("FloatFilterMax")); if (!bSuccess) { UE_LOG(LogEQS, Warning, TEXT("Unable to get FloatFilterMax parameter value from EnvQueryInstance %s"), FloatFilterMax.IsNamedParam() ? *FloatFilterMax.ParamName.ToString() : TEXT("<No name specified>")); } } else if (ClampMaxType == EEnvQueryTestClamping::SpecifiedValue) { bool bSuccess = QueryInstance.GetParamValue(ScoreClampingMax, MaxScore, TEXT("ScoreClampingMax")); if (!bSuccess) { UE_LOG(LogEQS, Warning, TEXT("Unable to get ScoreClampingMax parameter value from EnvQueryInstance %s"), ScoreClampingMax.IsNamedParam() ? *ScoreClampingMax.ParamName.ToString() : TEXT("<No name specified>")); } } FEnvQueryItemDetails* DetailInfo = QueryInstance.ItemDetails.GetData(); if ((ClampMinType == EEnvQueryTestClamping::None) || (ClampMaxType == EEnvQueryTestClamping::None) ) { for (int32 ItemIndex = 0; ItemIndex < QueryInstance.Items.Num(); ItemIndex++, DetailInfo++) { if (!QueryInstance.Items[ItemIndex].IsValid()) { continue; } float TestValue = DetailInfo->TestResults[QueryInstance.CurrentTest]; if (TestValue != UEnvQueryTypes::SkippedItemValue) { if (ClampMinType == EEnvQueryTestClamping::None) { MinScore = FMath::Min(MinScore, TestValue); } if (ClampMaxType == EEnvQueryTestClamping::None) { MaxScore = FMath::Max(MaxScore, TestValue); } } } } DetailInfo = QueryInstance.ItemDetails.GetData(); if (bNormalizeFromZero && MinScore > 0.0f) { MinScore = 0.0f; } if (MinScore != MaxScore) { for (int32 ItemIndex = 0; ItemIndex < QueryInstance.ItemDetails.Num(); ItemIndex++, DetailInfo++) { if (QueryInstance.Items[ItemIndex].IsValid() == false) { continue; } float WeightedScore = 0.0f; float& TestValue = DetailInfo->TestResults[QueryInstance.CurrentTest]; if (TestValue != UEnvQueryTypes::SkippedItemValue) { const float ClampedScore = FMath::Clamp(TestValue, MinScore, MaxScore); const float NormalizedScore = (ClampedScore - MinScore) / (MaxScore - MinScore); // TODO? Add an option to invert the normalized score before applying an equation. const float NormalizedScoreForEquation = /*bMirrorNormalizedScore ? (1.0f - NormalizedScore) :*/ NormalizedScore; switch (ScoringEquation) { case EEnvTestScoreEquation::Linear: WeightedScore = WeightValue * NormalizedScoreForEquation; break; case EEnvTestScoreEquation::InverseLinear: { // For now, we're avoiding having a separate flag for flipping the direction of the curve // because we don't have usage cases yet and want to avoid too complex UI. If we decide // to add that flag later, we'll need to remove this option, since it should just be "mirror // curve" plus "Linear". float InverseNormalizedScore = (1.0f - NormalizedScoreForEquation); WeightedScore = WeightValue * InverseNormalizedScore; break; } case EEnvTestScoreEquation::Square: WeightedScore = WeightValue * (NormalizedScoreForEquation * NormalizedScoreForEquation); break; case EEnvTestScoreEquation::Constant: // I know, it's not "constant". It's "Constant, or zero". The tooltip should explain that. WeightedScore = (NormalizedScoreForEquation > 0) ? WeightValue : 0.0f; break; default: break; } } else { TestValue = 0.0f; WeightedScore = 0.0f; } #if USE_EQS_DEBUGGER DetailInfo->TestWeightedScores[QueryInstance.CurrentTest] = WeightedScore; #endif QueryInstance.Items[ItemIndex].Score += WeightedScore; } } }