using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenVIII
{
///
/// Images of parts of most of the menus and ui.
///
public sealed partial class Icons : SP2
{
#region Fields
private static readonly ID[] NumberStarts = { ID.Num_8x8_0_0, ID.Num_8x8_1_0, ID.Num_8x8_2_0, ID.Num_8x16_0_0, ID.Num_8x16_1_0, ID.Num_16x16_0_0 };
private static ConcurrentDictionary _numbersIDs;
private Rectangle _dataSize;
// ReSharper disable once InconsistentNaming
private new Dictionary Entries;
#endregion Fields
#region Enums
public enum NumType
{
// ReSharper disable once UnusedMember.Global
Num8X8,
// ReSharper disable once UnusedMember.Global
Num8X8A,
Num8X8B,
Num8X16,
Num8X16A,
// ReSharper disable once UnusedMember.Global
Num16X16,
SysFntBig,
SysFnt,
MenuFont
}
#endregion Enums
#region Properties
public new uint Count => (uint)Entries.Count;
public Rectangle DataSize { get => _dataSize; private set => _dataSize = value; }
// ReSharper disable once UnusedMember.Global
public new uint EntriesPerTexture { get; } = (uint)Enum.GetValues(typeof(ID)).Cast().Max();
public new uint PaletteCount => (uint)Textures.Count;
// ReSharper disable once UnusedMember.Local
private new uint TextureStartOffset { get; } = 0;
#endregion Properties
#region Indexers
public new EntryGroup this[Enum id] => GetEntryGroup(id);
#endregion Indexers
#region Methods
public static Icons Load()
{
var r = Load();
Memory.MainThreadOnlyActions.Enqueue(r.Trim);
return r;
}
public Rectangle Draw(int number, NumType type, int palette, string format, Vector2 location, Vector2 scale, float fade = 1f, Font.ColorID color = Font.ColorID.White, bool blink = false, bool skipDraw = false)
{
switch (type)
{
case NumType.SysFnt:
DataSize = Memory.Font.RenderBasicText(number.ToString(), location.ToPoint(), scale, Font.Type.sysfnt, Fade: fade, color: color, blink: blink, skipdraw: skipDraw);
return DataSize;
case NumType.SysFntBig:
DataSize = Memory.Font.RenderBasicText(number.ToString(), location.ToPoint(), scale, Font.Type.sysFntBig, Fade: fade, color: color, blink: blink, skipdraw: skipDraw);
return DataSize;
case NumType.MenuFont:
DataSize = Memory.Font.RenderBasicText(number.ToString(), location.ToPoint(), scale, Font.Type.menuFont, Fade: fade, color: color, blink: blink, skipdraw: skipDraw);
return DataSize;
case NumType.Num8X8:
case NumType.Num8X8A:
case NumType.Num8X8B:
case NumType.Num8X16:
case NumType.Num8X16A:
case NumType.Num16X16:
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
if (_numbersIDs == null)
{
_numbersIDs = new ConcurrentDictionary();
var j = 0;
foreach (var id in NumberStarts)
_numbersIDs.TryAdd(j++, Enumerable.Range((int)id, 10).Select(x => checked((ID)x)).ToArray());
}
var intList = number.ToString(format).Select(digit => int.Parse(digit.ToString()));
var dst = new Rectangle { Location = location.ToPoint() };
DataSize = dst;
if (Entries == null) return DataSize;
foreach (var i in intList)
{
if (_numbersIDs == null) continue;
if (!skipDraw)
Draw(_numbersIDs[(int)type][i], palette, dst, scale, fade,
blink
? Color.Lerp(Font.ColorID2Color[color], Font.ColorID2Blink[color], Menu.Blink_Amount)
: Font.ColorID2Color[color]);
var width = Entries[_numbersIDs[(int)type][i]].GetRectangle.Width * scale.X;
var height = Entries[_numbersIDs[(int)type][i]].GetRectangle.Height * scale.Y;
dst.Offset(width, 0);
_dataSize.Width += (int)width;
if (_dataSize.Height < (int)height)
_dataSize.Height = (int)height;
}
return DataSize;
}
public void Draw(Enum id, int palette, Rectangle dst, Vector2 scale, float fade = 1f, Color? color = null)
{
if ((ID)id != ID.None && Textures.Count > 0)
Entries?[(ID)id].Draw(Textures, palette, dst, scale, fade, color);
}
public override void Draw(Enum id, Rectangle dst, float fade = 1) => Draw((ID)id, 2, dst, Vector2.One, fade);
public Entry GetEntry(Enum id, int index) => Entries?[(ID)id]?[index];
public override Entry GetEntry(Enum id) => Entries[(ID)id][0];
public EntryGroup GetEntryGroup(Enum id) => (ID)id != ID.None ? Entries?[(ID)id] : null;
public Color MostSaturated(Enum ic, byte pal)
{
if (Textures.Count <= pal) return default;
var eg = this[(ID)ic];
if (eg == null) return default;
var tex = Textures[pal];
return eg.MostSaturated(tex, pal);
}
public override void Trim(Enum ic, byte pal)
{
var eg = this[(ID)ic];
if (Textures != null && Textures.Count > pal)
eg.Trim(Textures[pal]);
}
protected override void DefaultValues()
{
base.DefaultValues();
var red = new Color[16];
var yellow = new Color[16];
red[15] = new Color(255, 30, 30, 255); //red
red[14] = new Color(140, 30, 30, 255); //dark red
red[13] = new Color(37, 37, 37, 255); //gray
yellow[15] = new Color(222, 222, 8, 255); //yellow
yellow[14] = new Color(131, 131, 24, 255); //dark yellow
yellow[13] = new Color(41, 41, 41, 255); //gray
//FORCE_ORIGINAL = true;
Props = new List
{
// ReSharper disable StringLiteralTypo
new TexProps{Filename = "icon.tex",Count = 1,Big = new List{ new BigTexProps{Filename = "iconfl{0:00}.TEX",Split = 4} } }, //0-15 palette
new TexProps{Filename = "icon.tex",Count = 1,Colors = red,Big = new List{ new BigTexProps{Filename = "iconfl{0:00}.TEX",Split = 4,Colors = red } } },//16 palette
new TexProps{Filename = "icon.tex",Count = 1,Colors = yellow,Big = new List{ new BigTexProps { Filename = "iconfl{0:00}.TEX", Split = 4, Colors = yellow } } }//17 palette
// ReSharper restore StringLiteralTypo
};
IndexFilename = "icon.sp1";
}
protected override void InitEntries(ArchiveBase aw = null)
{
if (Entries != null) return;
//read from icon.sp1
MemoryStream ms;
var buffer = aw.GetBinaryFile(IndexFilename);
if (buffer == null) return;
using (var br = new BinaryReader(ms = new MemoryStream(buffer)))
{
var locations = new Loc[br.ReadUInt32()];
for (var i = 0; i < locations.Length; i++)
{
locations[i] = (br.ReadUInt16(), br.ReadUInt16());
}
Entries = new Dictionary(locations.Length + 10);
for (var i = 0; i < locations.Length; i++)
{
ms.Seek(locations[i].Seek, SeekOrigin.Begin);
var c = (byte)locations[i].Length;
Entries[(ID)i] = new EntryGroup(c);
for (var e = 0; e < c; e++)
{
var tmp = new Entry();
tmp.LoadfromStreamSP1(br);
tmp.Part = (byte)e;
tmp.SetLoc(locations[i]);
Entries[(ID)i].Add(tmp);
}
}
}
}
protected override void InitTextures(ArchiveBase aw = null)
{
Textures = new List();
foreach (var texProps in Props)
{
var buffer = aw.GetBinaryFile(texProps.Filename);
if (buffer == null) continue;
var tex = new T();
tex.Load(buffer);
void add(ushort cult, Color[] colors = null)
{
void oneImage() => Textures.Add(TextureHandler.Create(texProps.Filename, tex, 1, 1, cult, colors));
if (ForceOriginal == false && texProps.Big != null && texProps.Big.Count > 0)
{
var textureHandler = TextureHandler.Create(texProps.Big[0].Filename, tex, 2,
texProps.Big[0].Split / 2,
cult, colors);
if (textureHandler.AllTexture2Ds.FirstOrDefault() == default ||
textureHandler.AllTexture2Ds.Any(x => x == null))
{
textureHandler.Dispose();
oneImage();
}
else
Textures.Add(textureHandler);
}
else oneImage();
}
if (texProps.Colors == null || texProps.Colors.Length == 0)
for (ushort cult = 0; cult < tex.GetClutCount; cult++)
add(cult);
else
add((ushort)Textures.Count, texProps.Big?[0]?.Colors ?? texProps.Colors); // Unsure if the big check here is worth doing... or if it'll cause issues.
}
}
protected override VertexPositionTexture_Texture2D Quad(Enum ic, byte pal, float scale = 0.25F, Box_Options options = Box_Options.Center | Box_Options.Middle, float z = 0f)
{
Trim(ic, pal);
var eg = this[(ID)ic];
var r = Quad(eg[0], Textures[pal], scale, eg.Count == 1 ? options : options | Box_Options.UseOffset);
if (eg.Count <= 1) return r;
var tmp = new List(r.VPT.Length * eg.Count);
tmp.AddRange(r.VPT);
for (var i = 1; i < eg.Count; i++)
tmp.AddRange(Quad(eg[0], Textures[pal], scale, options | Box_Options.UseOffset, i * 0.001f).VPT);
return new VertexPositionTexture_Texture2D(tmp.ToArray(), r.Texture);
}
private void Trim()
{
Trim(ID.Bar_Fill, 5);
//trim checks to see if it's ran once before.
//so no need to check if it's already ran.
//will throw exception if not in main thread.
for (byte i = 0; i <= 7; i++)
Trim(ID._0_Hit_ + i, 2);
Trim(ID.Trigger_, 2);
Trim(ID.Perfect__, 2);
Trim(ID.Renzokuken_Seperator, 6);
}
#endregion Methods
}
}