Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Dat/FileParsing/LocoBinaryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,15 @@ public IEnumerable<BogieSprite> ReadBogieSprites(int count)
{
yield return new BogieSprite
{
RollStates = ReadByte(),
NumAnimationFrames = ReadByte(),
Flags = (BogieSpriteFlags)ReadByte(),
Width = ReadByte(),
HeightNegative = ReadByte(),
HeightPositive = ReadByte(),
NumRollSprites = ReadByte(),
};

SkipImageId(3);
SkipByte(1); // NumFramesPerRotation is not part of object definition, its calculated on load
SkipImageId(3); // image ids not part of object definition
}
}

Expand Down
4 changes: 2 additions & 2 deletions Dat/FileParsing/LocoBinaryWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ public void Write(BogieSprite[] bogies)
{
foreach (var bogie in bogies)
{
Write(bogie.RollStates);
Write(bogie.NumAnimationFrames);
Write((uint8_t)bogie.Flags);
Write(bogie.Width);
Write(bogie.HeightNegative);
Write(bogie.HeightPositive);
Write(bogie.NumRollSprites);
WriteEmptyBytes(1); // NumFramesPerRotation is not part of object definition, its calculated on load
WriteEmptyImageId(3); // image ids not part of object definition
}
}
Expand Down
2 changes: 2 additions & 0 deletions Dat/Loaders/Vehicle/VehicleObjectLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public static LocoObject Load(Stream stream)
// define groups
var imageTable = ImageTableGrouper.CreateImageTable(model, ObjectType, imageList);

// here, loco does additional image table setup based on the object type - we skip that for now

return new LocoObject(ObjectType, model, stringTable, imageTable);
}
}
Expand Down
202 changes: 198 additions & 4 deletions Definitions/ObjectModels/Graphics/ImageTableGrouper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Definitions.ObjectModels.Objects.Competitor;
using Definitions.ObjectModels.Objects.Vehicle;
using Definitions.ObjectModels.Types;
using System.Diagnostics;

Expand Down Expand Up @@ -78,7 +79,7 @@ private static IEnumerable<ImageTableGroup> CreateGroups(ILocoStruct obj, Object
case ObjectType.Dock:
return CreateDockGroups(imageList);
case ObjectType.Vehicle:
return [new("<uncategorised>", [.. imageList])];
return CreateVehicleGroups((VehicleObject)obj, imageList);
case ObjectType.Tree:
return CreateTreeGroups(imageList);
case ObjectType.Snow:
Expand Down Expand Up @@ -147,14 +148,207 @@ private static IEnumerable<ImageTableGroup> CreateCliffEdgeGroups(List<GraphicsE

private static IEnumerable<ImageTableGroup> CreateCompetitorGroups(CompetitorObject model, List<GraphicsElement> imageList)
{
var index = 0;
var offset = 0;
foreach (var emotion in Enum.GetValues<EmotionFlags>())
{
if (model.Emotions.HasFlag(emotion))
{
yield return new(emotion.ToString().ToLower(), imageList[index..(index + 2)]);
index += 2;
yield return new(emotion.ToString().ToLower(), imageList[offset..(offset + 2)]);
offset += 2;
}
}
}

static uint8_t GetYawAccuracyFlat(uint8_t numFrames)
=> numFrames switch
{
8 => 1,
16 => 2,
32 => 3,
_ => 4,
};

static uint8_t GetYawAccuracySloped(uint8_t numFrames)
=> numFrames switch
{
4 => 0,
8 => 1,
16 => 2,
_ => 3,
};

private static IEnumerable<ImageTableGroup> CreateVehicleGroups(VehicleObject model, List<GraphicsElement> imageList)
{
var offset = 0;

var counter = 0;
foreach (var bodySprite in model.BodySprites)
{
if (!bodySprite.Flags.HasFlag(BodySpriteFlags.HasSprites))
{
continue;
}

var symmetryMultiplier = bodySprite.Flags.HasFlag(BodySpriteFlags.RotationalSymmetry) ? 2 : 1;

// flat
{
var flatImageIdStart = offset;
bodySprite._FlatYawAccuracy = GetYawAccuracyFlat(bodySprite.NumFlatRotationFrames);
bodySprite._NumFramesPerRotation = (uint8_t)(bodySprite.NumAnimationFrames * bodySprite.NumCargoFrames * bodySprite.NumRollFrames + (bodySprite.Flags.HasFlag(BodySpriteFlags.HasBrakingLights) ? 1 : 0));

var numFlatFrames = bodySprite._NumFramesPerRotation * bodySprite.NumFlatRotationFrames;
offset += numFlatFrames / symmetryMultiplier;

yield return new($"[bodySprite {counter}] flat", imageList[flatImageIdStart..offset]);
}

if (bodySprite.Flags.HasFlag(BodySpriteFlags.HasGentleSprites))
{
{
var numGentleTransitionFrames = bodySprite._NumFramesPerRotation * 4; // transition frames up/down deg6

// gentle transition up
{
var gentleTransitionUpImageIdStart = offset;
offset += numGentleTransitionFrames / symmetryMultiplier;
yield return new($"[bodySprite {counter}] gentle transition up", imageList[gentleTransitionUpImageIdStart..offset]);
}

// gentle transition down
{
var gentleTransitionDownImageIdStart = offset;
offset += numGentleTransitionFrames / symmetryMultiplier;
yield return new($"[bodySprite {counter}] gentle transition down", imageList[gentleTransitionDownImageIdStart..offset]);
}
}

{
bodySprite._SlopedYawAccuracy = GetYawAccuracySloped(bodySprite.NumSlopedRotationFrames);
var numGentleFrames = bodySprite._NumFramesPerRotation * bodySprite.NumSlopedRotationFrames; // up/down deg12

// gentle up
{
var gentleUpImageIdStart = offset;
offset += numGentleFrames / symmetryMultiplier;
yield return new($"[bodySprite {counter}] gentle up", imageList[gentleUpImageIdStart..offset]);
}

// gentle down
{
var gentleDownImageIdStart = offset;
offset += numGentleFrames / symmetryMultiplier;
yield return new($"[bodySprite {counter}] gentle down", imageList[gentleDownImageIdStart..offset]);
}
}

if (bodySprite.Flags.HasFlag(BodySpriteFlags.HasSteepSprites))
{
{
var numSteepTransitionFrames = bodySprite._NumFramesPerRotation * 4; // transition frames up/down deg18

// steep transition up
{
var steepTransitionUpImageIdStart = offset;
offset += numSteepTransitionFrames / symmetryMultiplier;
yield return new($"[bodySprite {counter}] steep transition up", imageList[steepTransitionUpImageIdStart..offset]);
}

// steep transition down
{
var steepTransitionDownImageIdStart = offset;
offset += numSteepTransitionFrames / symmetryMultiplier;
yield return new($"[bodySprite {counter}] steep transition down", imageList[steepTransitionDownImageIdStart..offset]);
}
}

{
var numSteepFrames = bodySprite.NumSlopedRotationFrames * bodySprite._NumFramesPerRotation; // up/down deg25

// steep up
{
var steepUpImageIdStart = offset;
offset += numSteepFrames / symmetryMultiplier;
yield return new($"[bodySprite {counter}] steep up", imageList[steepUpImageIdStart..offset]);
}

// steep down
{
var steepDownImageIdStart = offset;
offset += numSteepFrames / symmetryMultiplier;
yield return new($"[bodySprite {counter}] steep down", imageList[steepDownImageIdStart..offset]);
}
}
}
}

counter++;
}

counter = 0;
foreach (var bogieSprite in model.BogieSprites)
{
if (!bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasSprites))
{
continue;
}

var symmetryMultiplier = bogieSprite.Flags.HasFlag(BogieSpriteFlags.RotationalSymmetry) ? 2 : 1;

// flat
{
var flatImageIdStart = offset;
var numFlatFrames = bogieSprite.NumAnimationFrames * 32;
offset += numFlatFrames / symmetryMultiplier;

yield return new($"[bogieSprite {counter}] flat", imageList[flatImageIdStart..offset]);
}

if (bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasGentleSprites))
{
var numGentleFrames = bogieSprite.NumAnimationFrames * 32; // up/down 12 deg

// gentle up
{
var gentleUpImageIdStart = offset;
offset += numGentleFrames / symmetryMultiplier;
yield return new($"[bogieSprite {counter}] gentle up", imageList[gentleUpImageIdStart..offset]);
}

// gentle down
{
var gentleDownImageIdStart = offset;
offset += numGentleFrames / symmetryMultiplier;
yield return new($"[bogieSprite {counter}] gentle down", imageList[gentleDownImageIdStart..offset]);
}

if (bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasSteepSprites))
{
var numSteepFrames = bogieSprite.NumAnimationFrames * 32; // up/down 25 deg

// steep up
{
var steepUpImageIdStart = offset;
offset += numSteepFrames / symmetryMultiplier;
yield return new($"[bogieSprite {counter}] steep up", imageList[steepUpImageIdStart..offset]);
}

// steep down
{
var steepDownImageIdStart = offset;
offset += numSteepFrames / symmetryMultiplier;
yield return new($"[bogieSprite {counter}] steep down", imageList[steepDownImageIdStart..offset]);
}
}
}

counter++;
}

var remainder = imageList[offset..];
if (remainder.Count > 0)
{
yield return new("<uncategorised>", remainder);
}
}

Expand Down
3 changes: 3 additions & 0 deletions Definitions/ObjectModels/Objects/Vehicle/BodySprite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public class BodySprite : ILocoStruct
public uint8_t NumRollFrames { get; set; }
public uint8_t HalfLength { get; set; }
public BodySpriteFlags Flags { get; set; }

// the following properties are not written into the body sprite data structure,
// they are computed on loading the image table
[Browsable(false)] public uint8_t _Width { get; set; }
[Browsable(false)] public uint8_t _HeightNegative { get; set; }
[Browsable(false)] public uint8_t _HeightPositive { get; set; }
Expand Down
8 changes: 4 additions & 4 deletions Definitions/ObjectModels/Objects/Vehicle/BodySpriteFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ public enum BodySpriteFlags : uint8_t
{
None = 0,
HasSprites = 1 << 0, // If not set then no body will be loaded
RotationalSymmetry = 1 << 1, // requires 32 rather than 64 sprites
HasUnkSprites = 1 << 2,
HasGentleSprites = 1 << 3, // for gentle slopes
HasSteepSprites = 1 << 4, // for steep slopes
RotationalSymmetry = 1 << 1, // Requires 32 rather than 64 sprites
Flag02_Deprecated = 1 << 2, // Incomplete feature of vanilla. Do not repurpose until new object format
HasGentleSprites = 1 << 3, // For gentle slopes
HasSteepSprites = 1 << 4, // For steep slopes
HasBrakingLights = 1 << 5,
HasSpeedAnimation = 1 << 6, // Speed based animation (such as hydrofoil)
}
10 changes: 6 additions & 4 deletions Definitions/ObjectModels/Objects/Vehicle/BogieSprite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@ namespace Definitions.ObjectModels.Objects.Vehicle;
[TypeConverter(typeof(ExpandableObjectConverter))]
public class BogieSprite : ILocoStruct
{
public uint8_t RollStates { get; set; }
public uint8_t NumAnimationFrames { get; set; }
public BogieSpriteFlags Flags { get; set; }
public uint8_t Width { get; set; }
public uint8_t HeightNegative { get; set; }
public uint8_t HeightPositive { get; set; }
public uint8_t NumRollSprites { get; set; }

[Browsable(false)]
public Dictionary<BogieSpriteSlopeType, List<int>> ImageIds { get; set; } = new();

[Browsable(false)]
public int NumImages { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Flags.HasFlag(BogieSpriteFlags.HasSprites))
{
if (RollStates is not (1 or 2 or 4))
if (NumAnimationFrames is not (1 or 2 or 4))
{
yield return new ValidationResult($"{nameof(RollStates)} must be either 1, 2, or 4 when {nameof(Flags)} includes {nameof(BogieSpriteFlags.HasSprites)}.", [nameof(RollStates), nameof(Flags)]);
yield return new ValidationResult($"{nameof(NumAnimationFrames)} must be either 1, 2, or 4 when {nameof(Flags)} includes {nameof(BogieSpriteFlags.HasSprites)}.", [nameof(NumAnimationFrames), nameof(Flags)]);
}
}
}
Expand Down
10 changes: 4 additions & 6 deletions Definitions/ObjectModels/Objects/Vehicle/BogieSpriteFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ public enum BogieSpriteFlags : uint8_t
{
None = 0,
HasSprites = 1 << 0, // If not set then no bogie will be loaded
RotationalSymmetry = 1 << 1, // requires 16 rather than 32 sprites
unk_02 = 1 << 2,
HasGentleSprites = 1 << 3, // for gentle slopes
HasSteepSprites = 1 << 4, // for steep slopes
HasBrakingLights = 1 << 5,
HasSpeedAnimation = 1 << 6, // Speed based animation (such as hydrofoil)
RotationalSymmetry = 1 << 1, // Requires 16 rather than 32 sprites
HasGentleSprites = 1 << 2, // For gentle slopes
HasSteepSprites = 1 << 3, // For steep slopes
LargerBoundingBox = 1 << 4, // Increases bounding box size
}
12 changes: 5 additions & 7 deletions Gui/ViewModels/LocoTypes/ObjectEditorViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,9 @@ void ValidateForOG()
}
}

public static IObjectViewModel? GetViewModelFromStruct(ILocoStruct locoStruct)
public static IObjectViewModel? GetViewModelFromStruct(LocoObject locoObject)
{
var locoStruct = locoObject.Object;
var asm = Assembly
.GetExecutingAssembly()
.GetTypes()
Expand Down Expand Up @@ -259,10 +260,12 @@ public override void Load()
if (Model.TryLoadObject(CurrentFile, out var newObj))
{
CurrentObject = newObj;
StringTableViewModel = null;
ExtraContentViewModel = null;

if (CurrentObject?.LocoObject != null)
{
CurrentObjectViewModel = GetViewModelFromStruct(CurrentObject.LocoObject.Object);
CurrentObjectViewModel = GetViewModelFromStruct(CurrentObject.LocoObject);
StringTableViewModel = new(CurrentObject.LocoObject.StringTable);

if (CurrentObject.LocoObject.Object is SoundObject soundObject)
Expand All @@ -283,11 +286,6 @@ public override void Load()
}
}
}
else
{
StringTableViewModel = null;
ExtraContentViewModel = null;
}

if (CurrentObject != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public BuildingViewModel(BuildingObject model) : base(model)
// Subscribe to BuildingAnimations changes
BuildingAnimations.ListChanged += OnBuildingComponentChanged;
}

public override void CopyBackToModel()
{
Model.BuildingComponents.BuildingVariations = [.. BuildingVariations.Select(x => x.ToList())];
Expand Down
Loading