Skip to content

Commit

Permalink
Add chibi replacement (#300)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonko0493 authored Jul 1, 2023
1 parent 2b31079 commit fb0b7d1
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 28 deletions.
25 changes: 24 additions & 1 deletion src/SerialLoops.Lib/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private static bool DoBuild(string directory, Project project, Config config, IL
{
if (string.IsNullOrEmpty(config.DevkitArmPath))
{
log.LogError("DevkitARM must be supplied in order to build");
log.LogError("DevkitARM must be supplied in order to build!");
return false;
}
if (file.Contains("events"))
Expand All @@ -141,6 +141,10 @@ private static bool DoBuild(string directory, Project project, Config config, IL
log.LogWarning($"Source file found at '{file}', outside of data and events directory; skipping...");
}
}
else if (Path.GetExtension(file).Equals(".bna", StringComparison.OrdinalIgnoreCase))
{
ReplaceSingleAnimationFile(grp, file, index, log);
}
else
{
log.LogError($"Unsure what to do with file '{Path.GetFileName(file)}'");
Expand Down Expand Up @@ -373,5 +377,24 @@ private static void ReplaceSingleFile(ArchiveFile<DataFile> archive, string file
log.LogException($"Failed replacing source file {index} in dat.bin with file '{filePath}'", ex);
}
}
private static void ReplaceSingleAnimationFile(ArchiveFile<GraphicsFile> archive, string filePath, int index, ILogger log)
{
try
{
GraphicsFile file = archive.Files.FirstOrDefault(f => f.Index == index);
GraphicsFile newFile = new()
{
Name = file.Name,
Index = file.Index,
};
newFile.Initialize(File.ReadAllBytes(filePath), file.Offset, log);
newFile.Edited = true;
archive.Files[archive.Files.IndexOf(file)] = newFile;
}
catch (Exception ex)
{
log.LogException($"Failed replacing file {index} in grp.bin with file '{filePath}'", ex);
}
}
}
}
55 changes: 45 additions & 10 deletions src/SerialLoops.Lib/Items/ChibiItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using HaruhiChokuretsuLib.Util;
using SkiaSharp;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace SerialLoops.Lib.Items
Expand All @@ -13,8 +14,9 @@ public class ChibiItem : Item, IPreviewableGraphic
{
public Chibi Chibi { get; set; }
public int ChibiIndex { get; set; }
public List<(string Name, ChibiEntry Chibi)> ChibiEntries { get; set; } = new();
public Dictionary<string, IEnumerable<(SKBitmap Frame, int Timing)>> ChibiAnimations { get; set; } = new();
public List<(string Name, ChibiGraphics Chibi)> ChibiEntries { get; set; } = new();
public Dictionary<string, bool> ChibiEntryModifications { get; set; } = new();
public Dictionary<string, List<(SKBitmap Frame, short Timing)>> ChibiAnimations { get; set; } = new();
public (string ScriptName, ScriptCommandInvocation command)[] ScriptUses { get; set; }

public ChibiItem(Chibi chibi, Project project) : base($"CHIBI{chibi.ChibiEntries[0].Animation}", ItemType.Chibi)
Expand All @@ -27,25 +29,36 @@ public ChibiItem(Chibi chibi, Project project) : base($"CHIBI{chibi.ChibiEntries
DisplayName = $"CHIBI_{firstAnimationName[0..firstAnimationName.IndexOf('_')]}";
ChibiIndex = chibiIndices.IndexOf(firstAnimationName[0..3]);
ChibiEntries.AddRange(Chibi.ChibiEntries.Where(c => c.Animation > 0)
.Select(c => (project.Grp.Files.First(f => f.Index == c.Animation).Name[0..^3], c)));
.Select(c => (project.Grp.Files.First(f => f.Index == c.Animation).Name[0..^3], new ChibiGraphics(c, project))));
ChibiEntries.ForEach(e => ChibiEntryModifications.Add(e.Name, false));
ChibiEntries.ForEach(e => ChibiAnimations.Add(e.Name, GetChibiAnimation(e.Name, project.Grp)));
PopulateScriptUses(project.Evt);
}

private IEnumerable<(SKBitmap Frame, int Timing)> GetChibiAnimation(string entryName, ArchiveFile<GraphicsFile> grp)
public void SetChibiAnimation(string entryName, List<(SKBitmap, short)> framesAndTimings)
{
ChibiEntry entry = ChibiEntries.First(c => c.Name == entryName).Chibi;
GraphicsFile animation = grp.Files.First(f => f.Index == entry.Animation);
ChibiGraphics chibiGraphics = ChibiEntries.First(c => c.Name == entryName).Chibi;
ChibiEntryModifications[entryName] = true;
GraphicsFile texture = chibiGraphics.Animation.SetFrameAnimationAndGetTexture(framesAndTimings, chibiGraphics.Texture.Palette);
texture.Index = chibiGraphics.Texture.Index;
chibiGraphics.Texture = texture;
}

public List<(SKBitmap Frame, short Timing)> GetChibiAnimation(string entryName, ArchiveFile<GraphicsFile> grp)
{
ChibiGraphics chibiGraphics = ChibiEntries.First(c => c.Name == entryName).Chibi;
GraphicsFile animation = chibiGraphics.Animation;

IEnumerable<SKBitmap> frames = animation.GetAnimationFrames(grp.Files.First(f => f.Index == entry.Texture)).Select(f => f.GetImage());
IEnumerable<int> timings = animation.AnimationEntries.Select(a => (int)((FrameAnimationEntry)a).Time);
IEnumerable<SKBitmap> frames = animation.GetAnimationFrames(chibiGraphics.Texture).Select(f => f.GetImage());
IEnumerable<short> timings = animation.AnimationEntries.Select(a => ((FrameAnimationEntry)a).Time);

return frames.Zip(timings);
return frames.Zip(timings).ToList();
}

public override void Refresh(Project project, ILogger log)
{
GetChibiAnimation(Name, project.Grp);
ChibiAnimations.Clear();
ChibiEntries.ForEach(e => ChibiAnimations.Add(e.Name, GetChibiAnimation(e.Name, project.Grp)));
PopulateScriptUses(project.Evt);
}

Expand Down Expand Up @@ -84,5 +97,27 @@ public static Direction CodeToDirection(string code)
_ => Direction.DOWN_LEFT,
};
}

public class ChibiGraphics
{
public GraphicsFile Texture { get; set; }
public GraphicsFile Animation { get; set; }

public ChibiGraphics(ChibiEntry entry, Project project)
{
Texture = project.Grp.Files.First(g => g.Index == entry.Texture);
Animation = project.Grp.Files.First(g => g.Index == entry.Animation);
}

public void Write(Project project, ILogger log)
{
using MemoryStream textureStream = new();
Texture.GetImage().Encode(textureStream, SKEncodedImageFormat.Png, 1);
IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{Texture.Index:X3}.png"), textureStream.ToArray(), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{Texture.Index:X3}_pal.csv"), string.Join(',', Texture.Palette.Select(c => c.ToString())), project, log);

IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{Animation.Index:X3}.bna"), Animation.GetBytes(), project, log);
}
}
}
}
2 changes: 1 addition & 1 deletion src/SerialLoops.Lib/SerialLoops.Lib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageReference Include="BunLabs.NAudio.Flac" Version="2.0.1" />
<PackageReference Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.3" />
<PackageReference Include="HarfBuzzSharp.NativeAssets.macOS" Version="2.8.2.3" />
<PackageReference Include="HaruhiChokuretsuLib" Version="0.33.0" />
<PackageReference Include="HaruhiChokuretsuLib" Version="0.34.7" />
<PackageReference Include="NAudio.Vorbis" Version="1.5.0" />
<PackageReference Include="NitroPacker.Core" Version="2.2.5" />
<PackageReference Include="NLayer" Version="1.14.0" />
Expand Down
2 changes: 1 addition & 1 deletion src/SerialLoops/Controls/ChibiDirectionSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private void InitializeComponent()
public void UpdateAvailableDirections(ChibiItem chibi, string prefix)
{
_availableDirections.Clear();
foreach ((string name, ChibiEntry entry) in chibi.ChibiEntries.Where(c => c.Name.StartsWith(prefix)))
foreach ((string name, ChibiItem.ChibiGraphics entry) in chibi.ChibiEntries.Where(c => c.Name.StartsWith(prefix)))
{
_availableDirections.Add(ChibiItem.CodeToDirection(name[^2..]));
}
Expand Down
2 changes: 1 addition & 1 deletion src/SerialLoops/Controls/EditorTabsPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private DocumentPage CreateTab(ItemDescription item, Project project, ILogger lo
return new CharacterSpriteEditor(
(CharacterSpriteItem)project.Items.First(i => i.Name == item.Name), project, log);
case ItemDescription.ItemType.Chibi:
return new ChibiEditor((ChibiItem)project.Items.First(i => i.Name == item.Name), log);
return new ChibiEditor((ChibiItem)project.Items.First(i => i.Name == item.Name), project, log);
case ItemDescription.ItemType.Group_Selection:
return new GroupSelectionEditor((GroupSelectionItem)project.Items.First(i => i.Name == item.Name), log, project, this);
case ItemDescription.ItemType.Map:
Expand Down
109 changes: 102 additions & 7 deletions src/SerialLoops/Editors/ChibiEditor.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
using Eto.Forms;
using HaruhiChokuretsuLib.Archive.Graphics;
using HaruhiChokuretsuLib.Util;
using SerialLoops.Controls;
using SerialLoops.Dialogs;
using SerialLoops.Lib;
using SerialLoops.Lib.Items;
using SerialLoops.Utility;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Timers;

namespace SerialLoops.Editors
{
Expand All @@ -17,11 +20,12 @@ public class ChibiEditor : Editor
private ChibiItem _chibi;
private DropDown _animationSelection;
private ChibiDirectionSelector _directionSelector;
private string _currentChibiEntry;

private AnimatedImage _animatedImage;
private StackLayout _framesStack;

public ChibiEditor(ChibiItem chibi, ILogger log) : base(chibi, log)
public ChibiEditor(ChibiItem chibi, Project project, ILogger log) : base(chibi, log, project)
{
}

Expand All @@ -34,6 +38,7 @@ public override Container GetEditorPanel()
private StackLayout GetAnimatedChibi()
{
_animatedImage = new(_chibi.ChibiAnimations.FirstOrDefault().Value);
_currentChibiEntry = _chibi.ChibiAnimations.FirstOrDefault().Key;
_animatedImage.Play();
return new StackLayout
{
Expand Down Expand Up @@ -107,6 +112,45 @@ private Container GetBottomPanel()
MessageBox.Show("Chibi frames exported!!", "Success!", MessageBoxType.Information);
}
};
Button replaceFramesButton = new() { Text = "Replace Frames" };
replaceFramesButton.Click += (sender, args) =>
{
OpenFileDialog openFileDialog = new()
{
Title = "Select frames",
CheckFileExists = true,
MultiSelect = true,
Filters = { new("Image Files", ".png", ".jpg", ".jpeg", ".bmp", ".gif") },
};
if (openFileDialog.ShowAndReportIfFileSelected(this))
{
List<SKBitmap> frames = openFileDialog.Filenames.Select(f => SKBitmap.Decode(f)).ToList();
short[] timings = new short[frames.Count];
Array.Fill<short>(timings, 32);
List<(SKBitmap Frame, short Timing)> framesAndTimings = frames.Zip(timings).ToList();
UpdateChibiFrames(framesAndTimings);
}
};
Button addFramesButton = new() { Text = "Add Frames" };
addFramesButton.Click += (sender, args) =>
{
OpenFileDialog openFileDialog = new()
{
Title = "Select frames",
CheckFileExists = true,
MultiSelect = true,
Filters = { new("Image Files", ".png", ".jpg", ".jpeg", ".bmp", ".gif") },
};
if (openFileDialog.ShowAndReportIfFileSelected(this))
{
List<SKBitmap> frames = openFileDialog.Filenames.Select(f => SKBitmap.Decode(f)).ToList();
short[] timings = new short[frames.Count];
Array.Fill<short>(timings, 32);
List<(SKBitmap Frame, short Timing)> newFramesAndTimings = frames.Zip(timings).ToList();
_chibi.ChibiAnimations[_currentChibiEntry].AddRange(newFramesAndTimings);
UpdateChibiFrames(_chibi.ChibiAnimations[_currentChibiEntry]);
}
};

return new TableLayout
{
Expand Down Expand Up @@ -145,13 +189,31 @@ private Container GetBottomPanel()
{
exportSpritesButton,
exportGifButton,
replaceFramesButton,
}
}
),
}
};
}

private void UpdateChibiFrames(List<(SKBitmap Frame, short Timing)> framesAndTimings)
{
Application.Instance.Invoke(() =>
{
GraphicsFile animation = _chibi.ChibiEntries.First(c => c.Name == _currentChibiEntry).Chibi.Animation;
_chibi.SetChibiAnimation(_currentChibiEntry, framesAndTimings);
_chibi.ChibiAnimations[_currentChibiEntry].Clear();
_chibi.ChibiAnimations[_currentChibiEntry].AddRange(_chibi.GetChibiAnimation(_currentChibiEntry, _project.Grp));
AnimatedImage newImage = new(_chibi.ChibiAnimations[_currentChibiEntry]);
_animatedImage.FramesWithTimings = newImage.FramesWithTimings;
_animatedImage.CurrentFrame = 0;
_animatedImage.UpdateImage();
UpdateFramesStack();
UpdateTabTitle(false);
});
}

private StackLayout GetFramesStack()
{
_framesStack = new()
Expand All @@ -166,8 +228,10 @@ private StackLayout GetFramesStack()
private void UpdateFramesStack()
{
_framesStack.Items.Clear();
int i = 0;
foreach ((SKGuiImage image, int timing) in _animatedImage.FramesWithTimings)
{
int currentFrame = i;
StackLayout frameLayout = new()
{
Orientation = Orientation.Vertical,
Expand All @@ -180,22 +244,53 @@ private void UpdateFramesStack()
};
if (timing >= 0)
{
frameLayout.Items.Add($"{timing} frames");
Timer frameStackTimer = new(1000) { AutoReset = false };
NumericStepper timingStepper = new()
{
Value = timing,
MinValue = 0,
MaxValue = short.MaxValue,
Width = 50,
DecimalPlaces = 0,
MaximumDecimalPlaces = 0,
};
timingStepper.ValueChanged += (sender, args) =>
{
frameStackTimer.Stop();
frameStackTimer.Start();
};
frameStackTimer.Elapsed += (sender, args) =>
{
Application.Instance.Invoke(() =>
{
GraphicsFile animation = _chibi.ChibiEntries.First(c => c.Name == _currentChibiEntry).Chibi.Animation;
_chibi.ChibiAnimations[_currentChibiEntry][currentFrame] = (image.SkBitmap, (short)timingStepper.Value);
_chibi.SetChibiAnimation(_currentChibiEntry, _chibi.ChibiAnimations[_currentChibiEntry]);
AnimatedImage newImage = new(_chibi.ChibiAnimations[_currentChibiEntry]);
_animatedImage.FramesWithTimings = newImage.FramesWithTimings;
_animatedImage.CurrentFrame = 0;
_animatedImage.UpdateImage();
UpdateFramesStack();
UpdateTabTitle(false);
});
};
frameLayout.Items.Add(ControlGenerator.GetControlWithLabel("Frames", timingStepper));
}
_framesStack.Items.Add(frameLayout);
i++;
}
}

private void ChibiSelection_SelectedKeyChanged(object sender, EventArgs e)
{
_directionSelector.UpdateAvailableDirections(_chibi, _animationSelection.SelectedKey.Trim());
string selectedAnimationKey = GetSelectedAnimationKey();
if (!_chibi.ChibiAnimations.ContainsKey(selectedAnimationKey))
_currentChibiEntry = GetSelectedAnimationKey();
if (!_chibi.ChibiAnimations.ContainsKey(_currentChibiEntry))
{
selectedAnimationKey = _chibi.ChibiAnimations.Keys.First();
_directionSelector.Direction = ChibiItem.CodeToDirection(selectedAnimationKey[^2..]);
_currentChibiEntry = _chibi.ChibiAnimations.Keys.First();
_directionSelector.Direction = ChibiItem.CodeToDirection(_currentChibiEntry[^2..]);
}
AnimatedImage newImage = new(_chibi.ChibiAnimations[selectedAnimationKey]);
AnimatedImage newImage = new(_chibi.ChibiAnimations[_currentChibiEntry]);
_animatedImage.FramesWithTimings = newImage.FramesWithTimings;
_animatedImage.CurrentFrame = 0;
_animatedImage.UpdateImage();
Expand Down
Loading

0 comments on commit fb0b7d1

Please sign in to comment.