modernuo/Projects/Server/Client/ArtData.cs
Kamron Batman c75514cc04
feat: Updates to .NET 9 (#1984)
### Summary

* Bumps to .NET 9 with updated dependencies
* Comparing a value type against null is no longer allowed
* CI/CD now uses the version specified in global.json
* Serialization generator updated to .NET 9 with bug fixes, fixes to turkish language, and parallelization
2024-12-08 10:16:34 -08:00

185 lines
5.4 KiB
C#

/*************************************************************************
* ModernUO *
* Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ArtData.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace Server;
public class ArtData : IDisposable
{
private readonly FileStream _dataStream;
private readonly Dictionary<int, UOPEntry> _dataRanges;
public bool IsInitialized => _dataRanges.Count > 0 && _dataStream != null;
public ArtData()
{
var artPath = Core.FindDataFile("art.mul", false);
if (artPath != null)
{
var idxPath = Core.FindDataFile("artidx.mul", false);
if (idxPath != null)
{
_dataRanges = LoadMulRanges(idxPath);
_dataStream = new FileStream(artPath, FileMode.Open, FileAccess.Read, FileShare.Read);
return;
}
}
artPath = Core.FindDataFile("artLegacyMUL.uop", false);
if (artPath != null)
{
_dataStream = new FileStream(artPath, FileMode.Open, FileAccess.Read, FileShare.Read);
_dataRanges = UOPFiles.ReadUOPIndexes(_dataStream, ".tga", 0x14000, 5);
}
}
public Rectangle2D GetStaticBounds(int index)
{
if (index is < 0 or > 0x10000)
{
return Rectangle2D.Empty;
}
index += 16384;
if (!_dataRanges.TryGetValue(index, out var entry))
{
return Rectangle2D.Empty;
}
Span<ushort> buffer = stackalloc ushort[entry.Size / 2];
_dataStream.Seek(entry.Offset, SeekOrigin.Begin);
_ = _dataStream.Read(MemoryMarshal.AsBytes(buffer));
var width = buffer[2];
var height = buffer[3];
if (width == 0 || height == 0)
{
return Rectangle2D.Empty;
}
return GetBoundsFromRGBA1555Bitmap(width, height, buffer[4..]);
}
private static Dictionary<int, UOPEntry> LoadMulRanges(string idxPath)
{
using var fs = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read);
using var br = new BinaryReader(fs);
var count = (int)(fs.Length / 12);
var ranges = new Dictionary<int, UOPEntry>(count);
for (var i = 0; i < count; i++)
{
var offset = br.ReadInt32();
var length = br.ReadInt32();
br.ReadInt32(); // Skip the Data field since we don't use it
if (length > 0)
{
ranges[i] = new UOPEntry(offset, length);
}
}
ranges.TrimExcess();
return ranges;
}
public static Rectangle2D GetBoundsFromRGBA1555Bitmap(int width, int height, ReadOnlySpan<ushort> data)
{
ReadOnlySpan<ushort> lookups = data[..height];
data = data[height..];
int xMin = width;
int yMin = height;
int xMax = -1;
int yMax = -1;
for (int y = 0; y < height; y++)
{
var i = lookups[y];
var x = 0;
while (true)
{
int startX = data[i++];
var pixelCount = data[i++];
if (startX + pixelCount == 0 || startX > width)
{
break;
}
x += startX;
for (var end = x + pixelCount; x < end; x++)
{
// Get the pixel value from the bitmap data
ushort pixel = data[i++];
// Check if the pixel is non-black (0 in 555 RGB is completely black)
if (pixel == 0)
{
continue;
}
if (x < xMin)
{
xMin = x;
}
if (x > xMax)
{
xMax = x;
}
if (y < yMin)
{
yMin = y;
}
if (y > yMax)
{
yMax = y;
}
}
}
}
return xMax switch
{
// If no non-black pixels were found, return an empty rectangle
-1 => Rectangle2D.Empty,
_ => new Rectangle2D(xMin, yMin, xMax - xMin, yMax - yMin)
};
}
public void Dispose()
{
_dataStream?.Dispose();
GC.SuppressFinalize(this);
}
~ArtData()
{
_dataStream?.Dispose();
}
}