Skip to content

Commit

Permalink
Merge pull request OpenSAGE#845 from charliefoxtwo/bugfix/unit-modelc…
Browse files Browse the repository at this point in the history
…onditionstate

Fix some units not showing correct ModelConditionState
  • Loading branch information
Tarcontar authored Feb 6, 2024
2 parents 0b10a23 + c2e99ef commit 8f99ccc
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 10 deletions.
48 changes: 43 additions & 5 deletions src/OpenSage.Game.Tests/Logic/Draw/W3dModelDrawTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ public class W3dModelDrawTests
[InlineData(new[] { ModelConditionFlag.Sold, ModelConditionFlag.Night, ModelConditionFlag.Snow, ModelConditionFlag.ReallyDamaged })]
[InlineData(new[] { ModelConditionFlag.Damaged, ModelConditionFlag.Snow })]
[InlineData(new[] { ModelConditionFlag.AwaitingConstruction, ModelConditionFlag.PartiallyConstructed, ModelConditionFlag.ActivelyBeingConstructed, ModelConditionFlag.Night, ModelConditionFlag.Snow, ModelConditionFlag.ReallyDamaged })]
public void FindBestFittingConditionState_TestExactMatch(ModelConditionFlag[] providedFlags)
public void FindBestFittingConditionState_AmericaBarracks_TestExactMatch(ModelConditionFlag[] providedFlags)
{
var flags = new BitArray<ModelConditionFlag>(providedFlags);
var bestConditionState = W3dModelDraw.FindBestFittingConditionState(ModelConditionStates(), flags);
var bestConditionState = W3dModelDraw.FindBestFittingConditionState(AmericaBarracksModelConditionStates(), flags);
Assert.Contains(bestConditionState.ConditionFlags, f => f.CountIntersectionBits(flags) == flags.NumBitsSet); // .equals wasn't working?
}

Expand All @@ -24,19 +24,40 @@ public void FindBestFittingConditionState_TestExactMatch(ModelConditionFlag[] pr
[InlineData(new[] { ModelConditionFlag.AwaitingConstruction, ModelConditionFlag.PartiallyConstructed, ModelConditionFlag.ActivelyBeingConstructed }, new[] { ModelConditionFlag.PartiallyConstructed, ModelConditionFlag.ActivelyBeingConstructed })]
[InlineData(new[] { ModelConditionFlag.AwaitingConstruction, ModelConditionFlag.PartiallyConstructed, ModelConditionFlag.ActivelyBeingConstructed, ModelConditionFlag.Night, ModelConditionFlag.Snow, ModelConditionFlag.Damaged }, new[] { ModelConditionFlag.PartiallyConstructed, ModelConditionFlag.ActivelyBeingConstructed, ModelConditionFlag.Night, ModelConditionFlag.Snow, ModelConditionFlag.Damaged })]
[InlineData(new[] { ModelConditionFlag.AwaitingConstruction, ModelConditionFlag.PartiallyConstructed, ModelConditionFlag.ActivelyBeingConstructed, ModelConditionFlag.Night }, new[] { ModelConditionFlag.PartiallyConstructed, ModelConditionFlag.ActivelyBeingConstructed, ModelConditionFlag.Night })]
public void FindBestFittingConditionState_TestPartialMatch(ModelConditionFlag[] expectedFlags, ModelConditionFlag[] providedFlags)
public void FindBestFittingConditionState_AmericaBarracks_TestPartialMatch(ModelConditionFlag[] expectedFlags, ModelConditionFlag[] providedFlags)
{
var flags = new BitArray<ModelConditionFlag>(providedFlags);
var expected = new BitArray<ModelConditionFlag>(expectedFlags);
var bestConditionState = W3dModelDraw.FindBestFittingConditionState(ModelConditionStates(), flags);
var bestConditionState = W3dModelDraw.FindBestFittingConditionState(AmericaBarracksModelConditionStates(), flags);
Assert.Contains(bestConditionState.ConditionFlags, f => f.CountIntersectionBits(expected) == expected.NumBitsSet); // .equals wasn't working?
}

[Theory]
[InlineData(new[] { ModelConditionFlag.Moving })]
[InlineData(new[] { ModelConditionFlag.ActivelyConstructing })]
[InlineData(new[] { ModelConditionFlag.Moving, ModelConditionFlag.Carrying })]
public void FindBestFittingConditionState_GlaWorker_TestExactMatch(ModelConditionFlag[] providedFlags)
{
var flags = new BitArray<ModelConditionFlag>(providedFlags);
var bestConditionState = W3dModelDraw.FindBestFittingConditionState(GlaWorkerConditionStates(), flags);
Assert.Contains(bestConditionState.ConditionFlags, f => f.CountIntersectionBits(flags) == flags.NumBitsSet); // .equals wasn't working?
}

[Theory]
[InlineData(new[] { ModelConditionFlag.Carrying }, new[] { ModelConditionFlag.Carrying, ModelConditionFlag.ReallyDamaged })]
public void FindBestFittingConditionState_GlaWorker_TestPartialMatch(ModelConditionFlag[] expectedFlags, ModelConditionFlag[] providedFlags)
{
var flags = new BitArray<ModelConditionFlag>(providedFlags);
var expected = new BitArray<ModelConditionFlag>(expectedFlags);
var bestConditionState = W3dModelDraw.FindBestFittingConditionState(GlaWorkerConditionStates(), flags);
Assert.Contains(bestConditionState.ConditionFlags, f => f.CountIntersectionBits(expected) == expected.NumBitsSet); // .equals wasn't working?
}

/// <summary>
/// Example model condition states take from usa AmericaBarracks ModuleTag_01
/// </summary>
/// <returns></returns>
private static List<ModelConditionState> ModelConditionStates()
private static List<ModelConditionState> AmericaBarracksModelConditionStates()
{
return [
CreateModelConditionState(ModelConditionFlag.None),
Expand Down Expand Up @@ -92,6 +113,23 @@ private static List<ModelConditionState> ModelConditionStates()
];
}

private static List<ModelConditionState> GlaWorkerConditionStates()
{
return
[
CreateModelConditionState([ModelConditionFlag.Moving], [ModelConditionFlag.ActivelyConstructing, ModelConditionFlag.Moving]),
CreateModelConditionState(ModelConditionFlag.Attacking),
CreateModelConditionState(ModelConditionFlag.Moving, ModelConditionFlag.Attacking),
CreateModelConditionState([ModelConditionFlag.Moving, ModelConditionFlag.Carrying], [ModelConditionFlag.ActivelyConstructing, ModelConditionFlag.Moving, ModelConditionFlag.Carrying]),
CreateModelConditionState(ModelConditionFlag.Carrying),
CreateModelConditionState([ModelConditionFlag.Dying], [ModelConditionFlag.Dying, ModelConditionFlag.Carrying]),
CreateModelConditionState(ModelConditionFlag.Dying, ModelConditionFlag.ExplodedFlailing),
CreateModelConditionState(ModelConditionFlag.Dying, ModelConditionFlag.ExplodedBouncing),
CreateModelConditionState(ModelConditionFlag.SpecialCheering),
CreateModelConditionState([ModelConditionFlag.ActivelyConstructing], [ModelConditionFlag.ActivelyConstructing, ModelConditionFlag.Carrying]),
];
}

private static ModelConditionState CreateModelConditionState(params ModelConditionFlag[] flags)
{
var model = new ModelConditionState();
Expand Down
28 changes: 23 additions & 5 deletions src/OpenSage.Game/Logic/Object/Draw/W3dModelDraw.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,12 @@ internal static T FindBestFittingConditionState<T>(List<T> conditionStates, BitA
{
// prefer exact match
// if not, find the first one with the most number of matching bits
// this may be convoluted, but the unit tests pass!
T bestConditionState = default;
var bestIntersections = 0;
T bestContainedConditionState = default;
var leastMissing = int.MaxValue;
var leastMissingBestIntersections = 0;
T defaultConditionState = default;

foreach (var conditionState in conditionStates)
Expand All @@ -189,12 +193,26 @@ internal static T FindBestFittingConditionState<T>(List<T> conditionStates, BitA
}

var intersections = conditionStateFlags.CountIntersectionBits(flags);
// we've found a state that matches everything we have, but it may have extra things we don't have that we don't want
// the order of the condition states in the ini files is not guaranteed to be ideal
if (intersections == flags.NumBitsSet)
{
// if we have an exact match, great! doesn't matter what else we've found
return conditionState;
}
if (intersections > bestIntersections)
var missing = conditionStateFlags.NumBitsSet - intersections;
if (missing == 0)
{
// if we have an exact match, great! doesn't matter what else we've found
return conditionState;
}

// otherwise, this state may have extra things we don't have. Cool, but less than ideal... let's keep searching?
// this ensures we don't store the first least missing as better, even if a subsequent one has better best intersections (which should be a better "score")
if (missing < leastMissing || missing == leastMissing && intersections > leastMissingBestIntersections)
{
bestContainedConditionState = conditionState;
leastMissing = missing;
leastMissingBestIntersections = intersections;
}
} else if (intersections > bestIntersections)
{
// not an exact match, but save this if we can't find an exact match
bestIntersections = conditionStateFlags.NumBitsSet;
Expand All @@ -203,7 +221,7 @@ internal static T FindBestFittingConditionState<T>(List<T> conditionStates, BitA
}
}

return bestConditionState ?? defaultConditionState;
return bestContainedConditionState ?? bestConditionState ?? defaultConditionState;
}

public override void UpdateConditionState(BitArray<ModelConditionFlag> flags, Random random)
Expand Down

0 comments on commit 8f99ccc

Please sign in to comment.