Skip to content

Commit

Permalink
Add character sprite editor, remove SLG maps as options from ADV mode (
Browse files Browse the repository at this point in the history
  • Loading branch information
jonko0493 authored Jul 18, 2024
1 parent ced0ce9 commit a36dbcb
Show file tree
Hide file tree
Showing 21 changed files with 497 additions and 136 deletions.
14 changes: 6 additions & 8 deletions src/SerialLoops.Lib/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace SerialLoops.Lib
Expand Down Expand Up @@ -117,9 +118,9 @@ private static bool DoBuild(string directory, Project project, Config config, IL
{
ReplaceSingleGraphicsFile(grp, file, index, project.Localize, log);
}
else if (file.EndsWith("_pal.csv", StringComparison.OrdinalIgnoreCase))
else if (file.EndsWith(".gi", StringComparison.OrdinalIgnoreCase))
{
// ignore palette files as they will be handled by the PNGs above
// ignore graphics info files as they will be handled by the PNGs above
}
else if (Path.GetExtension(file).Equals(".s", StringComparison.OrdinalIgnoreCase))
{
Expand Down Expand Up @@ -232,13 +233,10 @@ private static void ReplaceSingleGraphicsFile(ArchiveFile<GraphicsFile> grp, str
grpFile.InitializeFontFile();
}

string paletteFile = Path.Combine(Path.GetDirectoryName(filePath), $"{Path.GetFileNameWithoutExtension(filePath)}_pal.csv");
if (File.Exists(paletteFile))
{
grpFile.SetPalette(File.ReadAllText(paletteFile).Split(',').Select(c => SKColor.Parse(c)).ToList());
}
GraphicInfo graphicInfo = JsonSerializer.Deserialize<GraphicInfo>(File.ReadAllText(Path.Combine(Path.GetDirectoryName(filePath), $"{Path.GetFileNameWithoutExtension(filePath)}.gi")));

grpFile.SetImage(filePath);
graphicInfo.Set(grpFile);
grpFile.SetImage(filePath, newSize: true);

grp.Files[grp.Files.IndexOf(grpFile)] = grpFile;
}
Expand Down
15 changes: 4 additions & 11 deletions src/SerialLoops.Lib/IO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,11 @@ namespace SerialLoops.Lib
{
public static class IO
{
private class IODirectory
private class IODirectory(string name, IODirectory[] subdirectories, IOFile[] files)
{
public string Name { get; set; }
public IODirectory[] Subdirectories { get; set; }
public IOFile[] Files { get; set; }

public IODirectory(string name, IODirectory[] subdirectories, IOFile[] files)
{
Name = name;
Subdirectories = subdirectories;
Files = files;
}
public string Name { get; set; } = name;
public IODirectory[] Subdirectories { get; set; } = subdirectories;
public IOFile[] Files { get; set; } = files;

public void Create(string basePath, ILogger log)
{
Expand Down
10 changes: 3 additions & 7 deletions src/SerialLoops.Lib/Items/BackgroundItem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using HaruhiChokuretsuLib.Archive;
using HaruhiChokuretsuLib.Archive.Data;
using HaruhiChokuretsuLib.Archive.Event;
using HaruhiChokuretsuLib.Archive.Data;
using HaruhiChokuretsuLib.Archive.Graphics;
using HaruhiChokuretsuLib.Util;
using SerialLoops.Lib.Util;
Expand Down Expand Up @@ -206,16 +204,14 @@ public void Write(Project project, ILogger log)
using MemoryStream grp1Stream = new();
Graphic1.GetImage().Encode(grp1Stream, SKEncodedImageFormat.Png, 1);
IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{Graphic1.Index:X3}.png"), grp1Stream.ToArray(), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{Graphic1.Index:X3}_pal.csv"),
string.Join(',', Graphic1.Palette.Select(c => c.ToString())), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{Graphic1.Index:X3}.gi"), Graphic1.GetGraphicInfoFile(), project, log);

if (BackgroundType != BgType.KINETIC_SCREEN && BackgroundType != BgType.TEX_CG_SINGLE)
{
using MemoryStream grp2Stream = new();
Graphic2.GetImage().Encode(grp2Stream, SKEncodedImageFormat.Png, 1);
IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{Graphic2.Index:X3}.png"), grp2Stream.ToArray(), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{Graphic2.Index:X3}_pal.csv"),
string.Join(',', Graphic1.Palette.Select(c => c.ToString())), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{Graphic2.Index:X3}.gi"), Graphic1.GetGraphicInfoFile(), project, log);
}
else if (BackgroundType == BgType.KINETIC_SCREEN)
{
Expand Down
3 changes: 1 addition & 2 deletions src/SerialLoops.Lib/Items/BackgroundMusicItem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using HaruhiChokuretsuLib.Archive.Event;
using HaruhiChokuretsuLib.Audio.ADX;
using HaruhiChokuretsuLib.Audio.ADX;
using HaruhiChokuretsuLib.Util;
using NAudio.Flac;
using NAudio.Vorbis;
Expand Down
197 changes: 171 additions & 26 deletions src/SerialLoops.Lib/Items/CharacterSpriteItem.cs
Original file line number Diff line number Diff line change
@@ -1,67 +1,212 @@
using HaruhiChokuretsuLib.Archive;
using HaruhiChokuretsuLib.Archive.Data;
using HaruhiChokuretsuLib.Archive.Event;
using HaruhiChokuretsuLib.Archive.Graphics;
using HaruhiChokuretsuLib.Util;
using SerialLoops.Lib.Util;
using SkiaSharp;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace SerialLoops.Lib.Items
{
public class CharacterSpriteItem : Item, IPreviewableGraphic
public class CharacterSpriteItem(CharacterSprite sprite, CharacterDataFile chrdata, Project project, ILogger log) : Item($"SPR_{project.Characters[(int)sprite.Character].Name}_{chrdata.Sprites.IndexOf(sprite):D3}{(sprite.IsLarge ? "_L" : "")}", ItemType.Character_Sprite), IPreviewableGraphic
{
public CharacterSprite Sprite { get; set; }
public int Index { get; set; }
private readonly ILogger _log = log;

public CharacterSpriteItem(CharacterSprite sprite, CharacterDataFile chrdata, Project project) : base($"SPR_{sprite.Character}_{chrdata.Sprites.IndexOf(sprite):D3}", ItemType.Character_Sprite)
{
Sprite = sprite;
Index = chrdata.Sprites.IndexOf(sprite);
}
public CharacterSprite Sprite { get; set; } = sprite;
public CharacterSpriteGraphics Graphics { get; set; } = new(sprite, project.Grp);
public int Index { get; set; } = chrdata.Sprites.IndexOf(sprite);

public override void Refresh(Project project, ILogger log)
{
}

public List<(SKBitmap Frame, int Timing)> GetClosedMouthAnimation(Project project)
{
return Sprite.GetClosedMouthAnimation(project.Grp, project.MessInfo);
return Sprite.GetClosedMouthAnimation(project.MessInfo, Graphics.BodyLayout, Graphics.BodyTextures, Graphics.EyeAnimation, Graphics.EyeTexture, Graphics.MouthAnimation, Graphics.MouthTexture);
}

public List<(SKBitmap Frame, int Timing)> GetLipFlapAnimation(Project project)
{
return Sprite.GetLipFlapAnimation(project.Grp, project.MessInfo);
return Sprite.GetLipFlapAnimation(project.MessInfo, Graphics.BodyLayout, Graphics.BodyTextures, Graphics.EyeAnimation, Graphics.EyeTexture, Graphics.MouthAnimation, Graphics.MouthTexture);
}

public SKBitmap GetBaseLayout(Project project)
public SKBitmap GetBaseLayout()
{
List<GraphicsFile> textures = new() { project.Grp.GetFileByIndex(Sprite.TextureIndex1), project.Grp.GetFileByIndex(Sprite.TextureIndex2), project.Grp.GetFileByIndex(Sprite.TextureIndex3) };
GraphicsFile layout = project.Grp.GetFileByIndex(Sprite.LayoutIndex);
(SKBitmap spriteBitmap, _) = layout.GetLayout(textures, 0, layout.LayoutEntries.Count, darkMode: false, preprocessedList: true);

return spriteBitmap;
return Graphics.BodyLayout.GetLayout(Graphics.BodyTextures, 0, Graphics.BodyLayout.LayoutEntries.Count, darkMode: false, preprocessedList: true).bitmap;
}

public List<(SKBitmap Frame, short Timing)> GetEyeFrames(Project project)
public List<(SKBitmap Frame, short Timing)> GetEyeFrames()
{
GraphicsFile eyeTexture = project.Grp.GetFileByIndex(Sprite.EyeTextureIndex);
GraphicsFile eyeAnimation = project.Grp.GetFileByIndex(Sprite.EyeAnimationIndex);

return eyeAnimation.GetAnimationFrames(eyeTexture).Select(g => g.GetImage()).Zip(eyeAnimation.AnimationEntries.Select(a => ((FrameAnimationEntry)a).Time)).ToList();
return Graphics.EyeAnimation.GetAnimationFrames(Graphics.EyeTexture).Select(g => g.GetImage()).Zip(Graphics.EyeAnimation.AnimationEntries.Select(a => ((FrameAnimationEntry)a).Time)).ToList();
}

public List<(SKBitmap Frame, short Timing)> GetMouthFrames(Project project)
public List<(SKBitmap Frame, short Timing)> GetMouthFrames()
{
GraphicsFile mouthTexture = project.Grp.GetFileByIndex(Sprite.MouthTextureIndex);
GraphicsFile mouthAnimation = project.Grp.GetFileByIndex(Sprite.MouthAnimationIndex);
return Graphics.MouthAnimation.GetAnimationFrames(Graphics.MouthTexture).Select(g => g.GetImage()).ToList().Zip(Graphics.MouthAnimation.AnimationEntries.Select(a => ((FrameAnimationEntry)a).Time)).ToList();
}

return mouthAnimation.GetAnimationFrames(mouthTexture).Select(g => g.GetImage()).ToList().Zip(mouthAnimation.AnimationEntries.Select(a => ((FrameAnimationEntry)a).Time)).ToList();
public void SetSprite(SKBitmap layoutBitmap, List<(SKBitmap Frame, short Time)> eyeFramesAndTimings, List<(SKBitmap Frame, short Time)> mouthFramesAndTimings, short eyeX, short eyeY, short mouthX, short mouthY)
{
List<SKColor> palette = SetBaseLayoutAndReturnPalette(layoutBitmap, _log);
SetEyeAnimation(eyeFramesAndTimings, palette);
SetMouthAnimation(mouthFramesAndTimings, palette);
Graphics.EyeAnimation.AnimationX = eyeX;
Graphics.EyeAnimation.AnimationY = eyeY;
Graphics.MouthAnimation.AnimationX = mouthX;
Graphics.MouthAnimation.AnimationY = mouthY;
}

public SKBitmap GetPreview(Project project)
{
return GetClosedMouthAnimation(project).First().Frame;
}

private List<SKColor> SetBaseLayoutAndReturnPalette(SKBitmap layoutBitmap, ILogger log)
{
List<LayoutEntry> layoutEntries = [];
List<SKColor> palette = Helpers.GetPaletteFromImage(layoutBitmap, 255, log);
palette.Insert(0, SKColors.Transparent);
short srcX = 0, srcY = 0;

List<(int W, int H)> sizes = [(256, 128), (256, 32)];
if (Sprite.IsLarge)
{
sizes.Add((256, 64));
}
else
{
sizes.Add((128, 32));
}

for (short i = 0; i < 3; i++)
{
SKBitmap texture = new(sizes[i].W, sizes[i].H);
if (srcY >= layoutBitmap.Height)
{
Graphics.BodyTextures[i].Palette = palette;
Graphics.BodyTextures[i].SetImage(texture, newSize: true);
continue;
}

SKCanvas textureCanvas = new(texture);

for (short y = 0; y < texture.Height; y += 32)
{
for (short x = 0; x < texture.Width; x += 32)
{
if (x == 0 && y == 0)
{
continue;
}

SKRect src = new(srcX, srcY, srcX + 32, srcY + 32);
srcX += 32;
if (srcX >= layoutBitmap.Width)
{
srcX = 0;
srcY += 32;
}

// If it's all transparent pixels, we can skip it but we should maintain our current position
bool allTransparent = true;
for (int yy = (int)src.Top; yy < (int)src.Bottom; yy++)
{
for (int xx = (int)src.Left; xx < (int)src.Right; xx++)
{
if (layoutBitmap.GetPixel(xx, yy).Alpha != 0)
{
allTransparent = false;
break;
}
}
if (!allTransparent)
{
break;
}
}
if (allTransparent)
{
x -= 32;
continue;
}

SKRect dst = new(x, y, x + 32, y + 32);
layoutEntries.Add(new(i, x, y, 32, 32, (short)src.Left, (short)src.Top, 32, 32, SKColors.White));

textureCanvas.DrawBitmap(layoutBitmap, src, dst);

if (srcY >= layoutBitmap.Height)
{
break;
}
}
if (srcY >= layoutBitmap.Height)
{
break;
}
}
textureCanvas.Flush();
Graphics.BodyTextures[i].Palette = palette;
Graphics.BodyTextures[i].SetImage(texture, newSize: true);
}

Graphics.BodyLayout.LayoutEntries = layoutEntries;
return palette;
}

private void SetEyeAnimation(List<(SKBitmap Frame, short Time)> framesAndTimings, List<SKColor> palette)
{
GraphicsFile newEyeTexture = Graphics.EyeAnimation.SetFrameAnimationAndGetTexture(framesAndTimings, palette);
GraphicInfo eyeTexInfo = new(Graphics.EyeTexture);
eyeTexInfo.SetWithoutPalette(newEyeTexture);
newEyeTexture.Index = Graphics.EyeTexture.Index;
Graphics.EyeTexture = newEyeTexture;
}

private void SetMouthAnimation(List<(SKBitmap Frame, short Time)> framesAndTimings, List<SKColor> palette)
{
GraphicsFile newMouthTexture = Graphics.MouthAnimation.SetFrameAnimationAndGetTexture(framesAndTimings, palette);
GraphicInfo mouthTexInfo = new(Graphics.MouthTexture);
mouthTexInfo.SetWithoutPalette(newMouthTexture);
newMouthTexture.Index = Graphics.MouthTexture.Index;
Graphics.MouthTexture = newMouthTexture;
}
}

public class CharacterSpriteGraphics(CharacterSprite sprite, ArchiveFile<GraphicsFile> grp)
{
public GraphicsFile BodyLayout { get; set; } = grp.GetFileByIndex(sprite.LayoutIndex);
public List<GraphicsFile> BodyTextures { get; set; } = [grp.GetFileByIndex(sprite.TextureIndex1), grp.GetFileByIndex(sprite.TextureIndex2), grp.GetFileByIndex(sprite.TextureIndex3)];
public GraphicsFile EyeAnimation { get; set; } = grp.GetFileByIndex(sprite.EyeAnimationIndex);
public GraphicsFile EyeTexture { get; set; } = grp.GetFileByIndex(sprite.EyeTextureIndex);
public GraphicsFile MouthAnimation { get; set; } = grp.GetFileByIndex(sprite.MouthAnimationIndex);
public GraphicsFile MouthTexture { get; set; } = grp.GetFileByIndex(sprite.MouthTextureIndex);

public void Write(Project project, ILogger log)
{
IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{BodyLayout.Index:X3}.lay"), BodyLayout.GetBytes(), project, log);

foreach (GraphicsFile bodyTexture in BodyTextures)
{
using MemoryStream bodyTextureStream = new();
bodyTexture.GetImage().Encode(bodyTextureStream, SKEncodedImageFormat.Png, GraphicsFile.PNG_QUALITY);
IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{bodyTexture.Index:X3}.png"), bodyTextureStream.ToArray(), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{bodyTexture.Index:X3}.gi"), bodyTexture.GetGraphicInfoFile(), project, log);
}

IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{EyeAnimation.Index:X3}.bna"), EyeAnimation.GetBytes(), project, log);
using MemoryStream eyeTextureStream = new();
EyeTexture.GetImage().Encode(eyeTextureStream, SKEncodedImageFormat.Png, GraphicsFile.PNG_QUALITY);
IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{EyeTexture.Index:X3}.png"), eyeTextureStream.ToArray(), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{EyeTexture.Index:X3}.gi"), EyeTexture.GetGraphicInfoFile(), project, log);

IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{MouthAnimation.Index:X3}.bna"), MouthAnimation.GetBytes(), project, log);
using MemoryStream mouthTextureStream = new();
MouthTexture.GetImage().Encode(mouthTextureStream, SKEncodedImageFormat.Png, GraphicsFile.PNG_QUALITY);
IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{MouthTexture.Index:X3}.png"), mouthTextureStream.ToArray(), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{MouthTexture.Index:X3}.gi"), MouthTexture.GetGraphicInfoFile(), project, log);
}
}
}
15 changes: 5 additions & 10 deletions src/SerialLoops.Lib/Items/ChibiItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using HaruhiChokuretsuLib.Archive.Data;
using HaruhiChokuretsuLib.Archive.Graphics;
using HaruhiChokuretsuLib.Util;
using SerialLoops.Lib.Util;
using SkiaSharp;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -84,23 +85,17 @@ public static Direction CodeToDirection(string code)
};
}

public class ChibiGraphics
public class ChibiGraphics(ChibiEntry entry, Project project)
{
public GraphicsFile Texture { get; set; }
public GraphicsFile Animation { get; set; }

public ChibiGraphics(ChibiEntry entry, Project project)
{
Texture = project.Grp.GetFileByIndex(entry.Texture);
Animation = project.Grp.GetFileByIndex(entry.Animation);
}
public GraphicsFile Texture { get; set; } = project.Grp.GetFileByIndex(entry.Texture);
public GraphicsFile Animation { get; set; } = project.Grp.GetFileByIndex(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.WriteStringFile(Path.Combine("assets", "graphics", $"{Texture.Index:X3}.gi"), Texture.GetGraphicInfoFile(), project, log);

IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{Animation.Index:X3}.bna"), Animation.GetBytes(), project, log);
}
Expand Down
3 changes: 1 addition & 2 deletions src/SerialLoops.Lib/Items/ItemItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ public void Write(Project project, ILogger log)
using MemoryStream grp1Stream = new();
ItemGraphic.GetImage().Encode(grp1Stream, SKEncodedImageFormat.Png, 1);
IO.WriteBinaryFile(Path.Combine("assets", "graphics", $"{ItemGraphic.Index:X3}.png"), grp1Stream.ToArray(), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{ItemGraphic.Index:X3}_pal.csv"),
string.Join(',', ItemGraphic.Palette.Select(c => c.ToString())), project, log);
IO.WriteStringFile(Path.Combine("assets", "graphics", $"{ItemGraphic.Index:X3}.gi"), ItemGraphic.GetGraphicInfoFile(), project, log);
}

SKBitmap IPreviewableGraphic.GetPreview(Project project)
Expand Down
Loading

0 comments on commit a36dbcb

Please sign in to comment.