Added try/catch for TypeDictionary errors in Lint code

TypeDictionary errors are very hard for modders to debug as they don't mention which actor is causing the error
This commit is contained in:
Gustas
2022-08-15 18:19:40 +03:00
committed by abcdefg30
parent cc58fe1a0f
commit d438508994
6 changed files with 135 additions and 88 deletions

View File

@@ -33,24 +33,32 @@ namespace OpenRA.Mods.Common.Lint
{
foreach (var actorInfo in rules.Actors)
{
var count = actorInfo.Value.TraitInfos<IDefaultVisibilityInfo>().Count();
if (count == 0)
emitError($"Actor type `{actorInfo.Key}` does not define a default visibility type!");
else if (count > 1)
emitError($"Actor type `{actorInfo.Key}` defines multiple default visibility types!");
else
// Catch TypeDictionary errors
try
{
var vis = actorInfo.Value.TraitInfoOrDefault<HiddenUnderShroudInfo>();
if (vis != null && vis.Type == VisibilityType.Footprint)
var count = actorInfo.Value.TraitInfos<IDefaultVisibilityInfo>().Count();
if (count == 0)
emitError($"Actor type `{actorInfo.Key}` does not define a default visibility type!");
else if (count > 1)
emitError($"Actor type `{actorInfo.Key}` defines multiple default visibility types!");
else
{
var ios = actorInfo.Value.TraitInfoOrDefault<IOccupySpaceInfo>();
if (ios == null)
emitError($"Actor type `{actorInfo.Key}` defines VisibilityType.Footprint in `{vis.GetType()}` but has no IOccupySpace traits!");
else if (ios.OccupiedCells(actorInfo.Value, CPos.Zero).Count == 0)
emitError($"Actor type `{actorInfo.Key}` defines VisibilityType.Footprint in `{vis.GetType()}` but does not have any footprint cells!");
var vis = actorInfo.Value.TraitInfoOrDefault<HiddenUnderShroudInfo>();
if (vis != null && vis.Type == VisibilityType.Footprint)
{
var ios = actorInfo.Value.TraitInfoOrDefault<IOccupySpaceInfo>();
if (ios == null)
emitError($"Actor type `{actorInfo.Key}` defines VisibilityType.Footprint in `{vis.GetType()}` but has no IOccupySpace traits!");
else if (ios.OccupiedCells(actorInfo.Value, CPos.Zero).Count == 0)
emitError($"Actor type `{actorInfo.Key}` defines VisibilityType.Footprint in `{vis.GetType()}` but does not have any footprint cells!");
}
}
}
catch (InvalidOperationException e)
{
emitError($"{e.Message} (Actor type `{actorInfo.Key}`)");
}
}
}
}

View File

@@ -33,13 +33,21 @@ namespace OpenRA.Mods.Common.Lint
{
foreach (var actorInfo in rules.Actors)
{
var health = actorInfo.Value.TraitInfoOrDefault<IHealthInfo>();
if (health == null)
continue;
// Catch TypeDictionary errors
try
{
var health = actorInfo.Value.TraitInfoOrDefault<IHealthInfo>();
if (health == null)
continue;
var hitShapes = actorInfo.Value.TraitInfos<HitShapeInfo>();
if (!hitShapes.Any())
emitError($"Actor type `{actorInfo.Key}` has a Health trait but no HitShape trait!");
var hitShapes = actorInfo.Value.TraitInfos<HitShapeInfo>();
if (!hitShapes.Any())
emitError($"Actor type `{actorInfo.Key}` has a Health trait but no HitShape trait!");
}
catch (InvalidOperationException e)
{
emitError($"{e.Message} (Actor type `{actorInfo.Key}`)");
}
}
}
}

View File

@@ -32,16 +32,24 @@ namespace OpenRA.Mods.Common.Lint
{
foreach (var actorInfo in rules.Actors)
{
var ios = actorInfo.Value.TraitInfoOrDefault<IOccupySpaceInfo>();
foreach (var rsi in actorInfo.Value.TraitInfos<RevealsShroudInfo>())
// Catch TypeDictionary errors
try
{
if (rsi.Type != VisibilityType.Footprint)
continue;
var ios = actorInfo.Value.TraitInfoOrDefault<IOccupySpaceInfo>();
foreach (var rsi in actorInfo.Value.TraitInfos<RevealsShroudInfo>())
{
if (rsi.Type != VisibilityType.Footprint)
continue;
if (ios == null)
emitError($"Actor type `{actorInfo.Key}` defines VisibilityType.Footprint in `{rsi.GetType()}` but has no IOccupySpace traits!");
else if (ios.OccupiedCells(actorInfo.Value, CPos.Zero).Count == 0)
emitError($"Actor type `{actorInfo.Key}` defines VisibilityType.Footprint in `{rsi.GetType()}` but does not have any footprint cells!");
if (ios == null)
emitError($"Actor type `{actorInfo.Key}` defines VisibilityType.Footprint in `{rsi.GetType()}` but has no IOccupySpace traits!");
else if (ios.OccupiedCells(actorInfo.Value, CPos.Zero).Count == 0)
emitError($"Actor type `{actorInfo.Key}` defines VisibilityType.Footprint in `{rsi.GetType()}` but does not have any footprint cells!");
}
}
catch (InvalidOperationException e)
{
emitError($"{e.Message} (Actor type `{actorInfo.Key}`)");
}
}
}

View File

@@ -40,68 +40,76 @@ namespace OpenRA.Mods.Common.Lint
var factions = rules.Actors[SystemActors.World].TraitInfos<FactionInfo>().Select(f => f.InternalName).ToArray();
foreach (var actorInfo in rules.Actors)
{
var images = new HashSet<string>();
// Actors may have 0 or 1 RenderSprites traits
var renderInfo = actorInfo.Value.TraitInfoOrDefault<RenderSpritesInfo>();
if (renderInfo != null)
// Catch TypeDictionary errors
try
{
images.Add(renderInfo.GetImage(actorInfo.Value, null).ToLowerInvariant());
var images = new HashSet<string>();
// Some actors define faction-specific artwork
foreach (var faction in factions)
images.Add(renderInfo.GetImage(actorInfo.Value, faction).ToLowerInvariant());
}
foreach (var traitInfo in actorInfo.Value.TraitInfos<TraitInfo>())
{
// Remove the "Info" suffix
var traitName = traitInfo.GetType().Name;
traitName = traitName.Remove(traitName.Length - 4);
var fields = traitInfo.GetType().GetFields();
foreach (var field in fields)
// Actors may have 0 or 1 RenderSprites traits
var renderInfo = actorInfo.Value.TraitInfoOrDefault<RenderSpritesInfo>();
if (renderInfo != null)
{
var sequenceReference = field.GetCustomAttributes<SequenceReferenceAttribute>(true).FirstOrDefault();
if (sequenceReference == null)
continue;
images.Add(renderInfo.GetImage(actorInfo.Value, null).ToLowerInvariant());
// Some sequences may specify their own Image override
IEnumerable<string> sequenceImages = images;
if (!string.IsNullOrEmpty(sequenceReference.ImageReference))
// Some actors define faction-specific artwork
foreach (var faction in factions)
images.Add(renderInfo.GetImage(actorInfo.Value, faction).ToLowerInvariant());
}
foreach (var traitInfo in actorInfo.Value.TraitInfos<TraitInfo>())
{
// Remove the "Info" suffix
var traitName = traitInfo.GetType().Name;
traitName = traitName.Remove(traitName.Length - 4);
var fields = traitInfo.GetType().GetFields();
foreach (var field in fields)
{
var imageField = fields.First(f => f.Name == sequenceReference.ImageReference);
var imageOverride = (string)imageField.GetValue(traitInfo);
if (string.IsNullOrEmpty(imageOverride))
{
if (!sequenceReference.AllowNullImage)
emitError($"Actor type `{actorInfo.Value.Name}` trait `{traitName}` must define a value for `{sequenceReference.ImageReference}`");
var sequenceReference = field.GetCustomAttributes<SequenceReferenceAttribute>(true).FirstOrDefault();
if (sequenceReference == null)
continue;
// Some sequences may specify their own Image override
IEnumerable<string> sequenceImages = images;
if (!string.IsNullOrEmpty(sequenceReference.ImageReference))
{
var imageField = fields.First(f => f.Name == sequenceReference.ImageReference);
var imageOverride = (string)imageField.GetValue(traitInfo);
if (string.IsNullOrEmpty(imageOverride))
{
if (!sequenceReference.AllowNullImage)
emitError($"Actor type `{actorInfo.Value.Name}` trait `{traitName}` must define a value for `{sequenceReference.ImageReference}`");
continue;
}
sequenceImages = new[] { imageOverride.ToLowerInvariant() };
}
sequenceImages = new[] { imageOverride.ToLowerInvariant() };
}
foreach (var sequence in LintExts.GetFieldValues(traitInfo, field, sequenceReference.DictionaryReference))
{
if (string.IsNullOrEmpty(sequence))
continue;
foreach (var i in sequenceImages)
foreach (var sequence in LintExts.GetFieldValues(traitInfo, field, sequenceReference.DictionaryReference))
{
if (sequenceReference.Prefix)
if (string.IsNullOrEmpty(sequence))
continue;
foreach (var i in sequenceImages)
{
// TODO: Remove prefixed sequence references and instead use explicit lists of lintable references
if (!sequences.Sequences(i).Any(s => s.StartsWith(sequence)))
emitWarning($"Actor type `{actorInfo.Value.Name}` trait `{traitName}` field `{field.Name}` defines a prefix `{sequence}` that does not match any sequences on image `{i}`.");
if (sequenceReference.Prefix)
{
// TODO: Remove prefixed sequence references and instead use explicit lists of lintable references
if (!sequences.Sequences(i).Any(s => s.StartsWith(sequence)))
emitWarning($"Actor type `{actorInfo.Value.Name}` trait `{traitName}` field `{field.Name}` defines a prefix `{sequence}` that does not match any sequences on image `{i}`.");
}
else if (!sequences.HasSequence(i, sequence))
emitError($"Actor type `{actorInfo.Value.Name}` trait `{traitName}` field `{field.Name}` references an undefined sequence `{sequence}` on image `{i}`.");
}
else if (!sequences.HasSequence(i, sequence))
emitError($"Actor type `{actorInfo.Value.Name}` trait `{traitName}` field `{field.Name}` references an undefined sequence `{sequence}` on image `{i}`.");
}
}
}
}
catch (InvalidOperationException e)
{
emitError($"{e.Message} (Actor type `{actorInfo.Key}`)");
}
}
foreach (var weaponInfo in rules.Weapons)

View File

@@ -32,13 +32,21 @@ namespace OpenRA.Mods.Common.Lint
{
foreach (var actorInfo in rules.Actors)
{
var buildable = actorInfo.Value.TraitInfoOrDefault<BuildableInfo>();
if (buildable == null)
continue;
// Catch TypeDictionary errors
try
{
var buildable = actorInfo.Value.TraitInfoOrDefault<BuildableInfo>();
if (buildable == null)
continue;
var tooltip = actorInfo.Value.TraitInfos<TooltipInfo>().FirstOrDefault(info => info.EnabledByDefault);
if (tooltip == null)
emitError("The following buildable actor has no (enabled) Tooltip: " + actorInfo.Key);
var tooltip = actorInfo.Value.TraitInfos<TooltipInfo>().FirstOrDefault(info => info.EnabledByDefault);
if (tooltip == null)
emitError("The following buildable actor has no (enabled) Tooltip: " + actorInfo.Key);
}
catch (InvalidOperationException e)
{
emitError($"{e.Message} (Actor type `{actorInfo.Key}`)");
}
}
}
}

View File

@@ -33,14 +33,21 @@ namespace OpenRA.Mods.Common.Lint
var providedPrereqs = rules.Actors.SelectMany(a => a.Value.TraitInfos<ITechTreePrerequisiteInfo>().SelectMany(p => p.Prerequisites(a.Value)));
// TODO: this check is case insensitive while the real check in-game is not
foreach (var i in rules.Actors)
foreach (var actorInfo in rules.Actors)
{
var bi = i.Value.TraitInfoOrDefault<BuildableInfo>();
if (bi != null)
foreach (var prereq in bi.Prerequisites)
if (!prereq.StartsWith("~disabled"))
if (!providedPrereqs.Contains(prereq.Replace("!", "").Replace("~", "")))
emitError($"Buildable actor {i.Key} has prereq {prereq} not provided by anything.");
// Catch TypeDictionary errors
try
{
var bi = actorInfo.Value.TraitInfoOrDefault<BuildableInfo>();
if (bi != null)
foreach (var prereq in bi.Prerequisites)
if (!prereq.StartsWith("~disabled") && !providedPrereqs.Contains(prereq.Replace("!", "").Replace("~", "")))
emitError($"Buildable actor {actorInfo.Key} has prereq {prereq} not provided by anything.");
}
catch (InvalidOperationException e)
{
emitError($"{e.Message} (Actor type `{actorInfo.Key}`)");
}
}
}
}