diff --git a/PckStudio.Core/Animation.cs b/PckStudio.Core/Animation.cs index ec297c8c..145ea5ef 100644 --- a/PckStudio.Core/Animation.cs +++ b/PckStudio.Core/Animation.cs @@ -114,11 +114,11 @@ namespace PckStudio.Core return new ReadOnlyCollection(_frames); } - public IReadOnlyCollection GetInterpolatedFrames() + public IEnumerable GetInterpolatedFrames() { if (Interpolate) { - return new ReadOnlyCollection(InternalGetInterpolatedFrames().ToList()); + return InternalGetInterpolatedFrames(); } return GetFrames(); } @@ -133,7 +133,7 @@ namespace PckStudio.Core nextFrame = _frames[i + 1]; for (int tick = 0; tick < currentFrame.Ticks; tick++) { - double delta = 1.0f - tick / (double)currentFrame.Ticks; + float delta = 1.0f - tick / (float)currentFrame.Ticks; yield return new Frame(currentFrame.Texture.Interpolate(nextFrame.Texture, delta)); } } diff --git a/PckStudio.Core/DLC/DLCManager.cs b/PckStudio.Core/DLC/DLCManager.cs index e9ec2913..17df9f70 100644 --- a/PckStudio.Core/DLC/DLCManager.cs +++ b/PckStudio.Core/DLC/DLCManager.cs @@ -9,17 +9,21 @@ using OMI; using OMI.Formats.Color; using OMI.Formats.GameRule; using OMI.Formats.Languages; +using OMI.Formats.Model; using OMI.Formats.Pck; using OMI.Workers.Color; using OMI.Workers.GameRule; using OMI.Workers.Language; using OMI.Workers.Material; +using OMI.Workers.Model; using OMI.Workers.Pck; using PckStudio.Core.App; using PckStudio.Core.Deserializer; using PckStudio.Core.Extensions; +using PckStudio.Core.FileFormats; using PckStudio.Core.Interfaces; using PckStudio.Core.IO.PckAudio; +using PckStudio.Core.Model; using PckStudio.Interfaces; namespace PckStudio.Core.DLC @@ -39,6 +43,8 @@ namespace PckStudio.Core.DLC public ConsolePlatform Platform => _platform; + public DLCPackageContentSerilasationType ContentSerilasationType { get; set; } = DLCPackageContentSerilasationType.Local; + /// /// See for details. /// @@ -48,6 +54,7 @@ namespace PckStudio.Core.DLC private readonly Random _rng = new Random(); private ByteOrder _byteOrder; private ConsolePlatform _platform; + private PckFileCompiler _pckFileCompiler; /// @@ -57,6 +64,7 @@ namespace PckStudio.Core.DLC { _platform = platform; _byteOrder = GetByteOrderForPlatform(Platform); + _pckFileCompiler = new PckFileCompiler(_byteOrder, GetPlatformCompressionType(), GameRuleFile.CompressionLevel.None); SetPreferredLanguage(preferredLanguage); } @@ -90,7 +98,7 @@ namespace PckStudio.Core.DLC localisation.AddLanguage(PreferredLanguage); localisation.AddLocKey(PACKAGE_DISPLAYNAME_ID, name); _packageRegistry.RegisterPackage(identifier, package, localisation); - + return package; } @@ -138,9 +146,11 @@ namespace PckStudio.Core.DLC { _packageRegistry.RegisterPackage(identifier, package, localisation); } - return package; + return new RawAssetDLCPackage(fileInfo.Name, pckFile, ByteOrder); } + public bool CloseDLCPackage(int identifier) => _packageRegistry.UnregisterPackage(identifier); + internal LOCFile GetLocalisation(int identifier) { return _packageRegistry.ContainsPackage(identifier) ? _packageRegistry.GetLocalisation(identifier) : default; @@ -173,47 +183,61 @@ namespace PckStudio.Core.DLC bool couldBeMiniGamePack = fileInfo.Name == DEFAULT_MINIGAME_PACK_FILENAME; bool hasSkins = TryGetDLCSkinPackage(name, identifier, pckFile, fileReader, out IDLCPackage skinPackage); - DLCPackageType dlcPackageType = hasSkins ? DLCPackageType.SkinPack : DLCPackageType.RawAssets; DirectoryInfo dataDirectoryInfo = fileInfo.Directory.EnumerateDirectories().Where(dirInfo => dirInfo.Name == DATA_DIRECTORY_NAME).FirstOrDefault(); if (dataDirectoryInfo is null) - return hasSkins ? skinPackage : InvalidDLCPackage.Instance; + { + Trace.TraceInformation("No data directory found."); + return skinPackage ?? new RawAssetDLCPackage(name, pckFile, ByteOrder); + } bool hasTexturePack = TryGetTexturePack(name, description, identifier, dataDirectoryInfo, pckFile, fileReader, out IDLCPackage texturePackage); - if (hasTexturePack) + if (!hasTexturePack) { - dlcPackageType = DLCPackageType.TexturePack; - } + Trace.TraceWarning("Couldn't parse texturepack."); + return skinPackage ?? new RawAssetDLCPackage(name, pckFile, ByteOrder); + } - Dictionary> mapData = GetMapData(pckFile, dataDirectoryInfo); + PckAudioFile pckAudioFile = pckFile.GetAssetsByType(PckAssetType.AudioFile).FirstOrDefault()?.GetData(new PckAudioFileReader(ByteOrder)); + IDictionary audios = default; + if (pckAudioFile != null) + { + var songs = pckAudioFile.Categories.SelectMany(audioCategory => audioCategory.SongNames).ToList(); + audios = dataDirectoryInfo.EnumerateFiles("*.binka") + .Where(audioFile => songs.Contains(Path.GetFileNameWithoutExtension(audioFile.Name))) + .ToDictionary(audioFile => audioFile.Name, audioFile => File.ReadAllBytes(audioFile.FullName)); + } + + GameRuleFile.CompressionType compressionType = GetPlatformCompressionType(); + var reader = new GameRuleFileReader(compressionType); + IEnumerable gameRules = pckFile.GetAssetsByType(PckAssetType.GameRulesFile) + .Concat(pckFile.GetAssetsByType(PckAssetType.GameRulesHeader)) + .Select(asset => asset.GetData(reader)); + + Dictionary mapData = GetMapData(gameRules, dataDirectoryInfo); + if (mapData.Count == 0) + return texturePackage ?? skinPackage ?? new RawAssetDLCPackage(name, pckFile, ByteOrder); if (mapData.Count == 1) - { - dlcPackageType = DLCPackageType.MashUpPack; - } - - Debug.WriteLine(dlcPackageType); + return new DLCMashUpPackage(name, description, identifier, null, null, pckAudioFile, audios, parentPackage: null, skinPackage: skinPackage, texturePackage: texturePackage); + return new RawAssetDLCPackage(name, pckFile, ByteOrder); } - private Dictionary> GetMapData(PckFile pck, DirectoryInfo dataDirectory) - { - GameRuleFile.CompressionType compressionType = GetPlatformCompressionType(); - var reader = new GameRuleFileReader(compressionType); - IEnumerable values = pck.GetAssetsByType(PckAssetType.GameRulesFile) - .Concat(pck.GetAssetsByType(PckAssetType.GameRulesHeader)) - .Select(asset => asset.GetData(reader)) + private Dictionary GetMapData(IEnumerable gameRuleFiles, DirectoryInfo dataDirectory) + { + IEnumerable values = gameRuleFiles .SelectMany(grf => grf.Root.GetRules().Where(rule => rule.Name == GRF_MAP_OPTIONS_NAME && rule.ContainsParameter(BASE_SAVE_NAME_GRF_PARAMETER_KEY))) .Select(rule => rule.GetParameterValue(BASE_SAVE_NAME_GRF_PARAMETER_KEY)); return dataDirectory.EnumerateFiles("*.mcs") .Where(file => values.Contains(file.Name)) - .ToDictionary(worldFile => worldFile.Name, worldFile => MapReader.OpenSave(worldFile.OpenRead())); - } + .ToDictionary(worldFile => worldFile.Name, worldFile => MapReader.OpenSaveData(worldFile.OpenRead())); + } private bool TryGetTexturePack(string name, string description, int identifier, DirectoryInfo dataDirectoryInfo, PckFile pckFile, PckFileReader pckFormatReader, out IDLCPackage texturePackage) - { + { if (dataDirectoryInfo is null) { texturePackage = default; @@ -233,13 +257,13 @@ namespace PckStudio.Core.DLC { texturePackage = default; return false; - } + } PckFile infoPck = texturePackInfo.GetData(pckFormatReader); FileInfo texturePackFileInfo = dataDirectoryInfo.EnumerateFiles().Where(fileInfo => fileInfo.Name == dataPath).FirstOrDefault(); if (!IsValidPckFile(texturePackFileInfo)) - { + { texturePackage = null; return false; } @@ -248,11 +272,8 @@ namespace PckStudio.Core.DLC PckFile texturePackPck = pckFormatReader.FromStream(texturePackFileStream); texturePackage = GetTexturePackageFromPckFile(name, description, identifier, infoPck, texturePackPck, resolution); - IDictionary audios = dataDirectoryInfo.EnumerateFiles("*.binka") - .ToDictionary(audioFile => audioFile.Name, audioFile => File.ReadAllBytes(audioFile.FullName)); - return texturePackage is not null; - } + } private IDLCPackage GetTexturePackageFromPckFile(string name, string description, int identifier, PckFile infoPck, PckFile dataPck, DLCTexturePackage.TextureResolution resolution) { @@ -260,35 +281,38 @@ namespace PckStudio.Core.DLC return null; if (!infoPck.TryGetAsset("comparison.png", PckAssetType.TextureFile, out PckAsset comparisonAsset)) - Trace.TraceError($"Could not find 'comparison.png'."); + Trace.TraceWarning($"Could not find 'comparison.png'."); if (!infoPck.TryGetAsset("icon.png", PckAssetType.TextureFile, out PckAsset iconnAsset)) - Trace.TraceError($"Could not find 'icon.png'."); + Trace.TraceWarning($"Could not find 'icon.png'."); - Image comparisonImg = comparisonAsset?.GetTexture(); - Image iconImg = iconnAsset?.GetTexture(); - DLCTexturePackage.MetaData metaData = new DLCTexturePackage.MetaData(comparisonImg, iconImg); + DLCTexturePackage.MetaData metaData = new DLCTexturePackage.MetaData(comparisonAsset?.GetTexture(), iconnAsset?.GetTexture()); - bool hasTerrainAtlas = TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.BlockAtlas, out Atlas terrainAtlas); - bool hasItemAtlas = TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.ItemAtlas, out Atlas itemAtlas); - bool hasParticleAtlas = TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.ParticleAtlas, out Atlas particleAtlas); - bool hasPaintingAtlas = TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.PaintingAtlas, out Atlas paintingAtlas); - bool hasMoonPhaseAtlas = TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.MoonPhaseAtlas, out Atlas moonPhaseAtlas); + TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.BlockAtlas, out Atlas terrainAtlas); + TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.ItemAtlas, out Atlas itemAtlas); + TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.ParticleAtlas, out Atlas particleAtlas); + TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.PaintingAtlas, out Atlas paintingAtlas); + TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.MoonPhaseAtlas, out Atlas moonPhaseAtlas); + TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.MapIconAtlas, out Atlas mapIconsAtlas); + TryGetAtlasFromResourceCategory(dataPck, AtlasResource.AtlasType.AdditionalMapIconsAtlas, out Atlas additionalMapIconsAtlas); string itemAnimationAssetPath = ResourceLocation.GetPathFromCategory(ResourceCategory.ItemAnimation); + string blockAnimationAssetPath = ResourceLocation.GetPathFromCategory(ResourceCategory.BlockAnimation); IPckAssetDeserializer deserializer = AnimationDeserializer.DefaultDeserializer; - IDictionary animations = dataPck.GetDirectoryContent(itemAnimationAssetPath, PckAssetType.TextureFile) + IDictionary itemAnimations = dataPck.GetDirectoryContent(itemAnimationAssetPath, PckAssetType.TextureFile) + .Where(asset => !asset.IsMipmappedFile()) .ToDictionary(asset => Path.GetFileNameWithoutExtension(asset.Filename), deserializer.Deserialize); - bool hasCompassAnimation = animations.ContainsKey("compass"); - bool hasClockAnimation = animations.ContainsKey("clock"); + IDictionary blockAnimations = dataPck.GetDirectoryContent(blockAnimationAssetPath, PckAssetType.TextureFile) + .Where(asset => !asset.IsMipmappedFile()) + .ToDictionary(asset => Path.GetFileNameWithoutExtension(asset.Filename), deserializer.Deserialize); - if (!hasCompassAnimation) + if (!itemAnimations.ContainsKey("compass")) Trace.TraceError("No compass animation found!"); - if (!hasClockAnimation) + if (!itemAnimations.ContainsKey("clock")) Trace.TraceError("No clock animation found!"); ITryGet tryGetTexture = TryGet.FromDelegate((string path, out Image image) => @@ -297,9 +321,10 @@ namespace PckStudio.Core.DLC image = asset?.GetTexture(); return success; }); - + + ImageDeserializer defaultDeserializer = ImageDeserializer.DefaultDeserializer; IEnumerable blockEntityBreakingFrames = dataPck.GetDirectoryContent("res/textures/", PckAssetType.TextureFile) - .Select(ImageDeserializer.DefaultDeserializer.Deserialize); + .Select(defaultDeserializer.Deserialize); Animation blockEntityBreakAnimation = new Animation(blockEntityBreakingFrames); ColorContainer colorContainer = dataPck.GetAssetsByType(PckAssetType.ColourTableFile).FirstOrDefault()?.GetData(new COLFileReader()) ?? new ColorContainer(); @@ -314,17 +339,31 @@ namespace PckStudio.Core.DLC .ToDictionary(c => c.Name, c => (c.SurfaceColor, c.UnderwaterColor, c.FogColor)); IDictionary environmentTextures = dataPck.GetDirectoryContent("res/environment/", PckAssetType.TextureFile) - .ToDictionary(a => Path.GetFileNameWithoutExtension(a.Filename), ImageDeserializer.DefaultDeserializer.Deserialize); + .ToDictionary(a => Path.GetFileNameWithoutExtension(a.Filename), defaultDeserializer.Deserialize); environmentTextures.TryGetValue("clouds", out Image clouds); environmentTextures.TryGetValue("rain", out Image rain); environmentTextures.TryGetValue("snow", out Image snow); + DLCTexturePackage.EnvironmentData environmentData = new DLCTexturePackage.EnvironmentData(clouds, rain, snow); - IList> materials = dataPck.GetAssetsByType(PckAssetType.MaterialFile).FirstOrDefault()?.GetData(new MaterialFileReader()) - .Select(mat => new KeyValuePair(mat.Name, mat.Type)).ToList(); + IDictionary terrainTextures = dataPck.GetDirectoryContent("res/terrain/", PckAssetType.TextureFile) + .ToDictionary(a => Path.GetFileNameWithoutExtension(a.Filename), defaultDeserializer.Deserialize); + + terrainTextures.TryGetValue("sun", out Image sun); + terrainTextures.TryGetValue("moon", out Image moon); + + AbstractModelContainer customModels = null; + if (dataPck.TryGetAsset("models.bin", PckAssetType.ModelsFile, out PckAsset modelsAsset)) + { + ModelContainer models = modelsAsset.GetData(new ModelFileReader()); + customModels = AbstractModelContainer.FromModelContainer(models, null); + } + + IDictionary materials = dataPck.GetAssetsByType(PckAssetType.MaterialFile).FirstOrDefault()?.GetData(new MaterialFileReader()) + .ToDictionary(mat => mat.Name, mat => mat.Type); return new DLCTexturePackage(name, description, identifier, metaData, resolution, - terrainAtlas, itemAtlas, particleAtlas, paintingAtlas, moonPhaseAtlas, + terrainAtlas, itemAtlas, particleAtlas, paintingAtlas, moonPhaseAtlas, mapIconsAtlas, additionalMapIconsAtlas, ArmorSetDescription.Leather.GetArmorSet(tryGetTexture), ArmorSetDescription.Chain.GetArmorSet(tryGetTexture), ArmorSetDescription.Iron.GetArmorSet(tryGetTexture), @@ -333,19 +372,20 @@ namespace PckStudio.Core.DLC ArmorSetDescription.Turtle.GetArmorSet(tryGetTexture), environmentData, colors, - waterColors: null, - customModels: null, + waterColors, + customModels, materials, blockEntityBreakAnimation, - itemAnimations: null, - blockAnimations: null, + itemAnimations, + blockAnimations, + sun, moon, parentPackage: null); } private bool TryGetAtlasFromResourceCategory(PckFile pck, AtlasResource.AtlasType atlasType, out Atlas atlas) { ResourceLocation resourceLocation = ResourceLocation.GetFromCategory(AtlasResource.GetId(atlasType)); - if (!pck.TryGetAsset(resourceLocation.ToString(), PckAssetType.TextureFile, out PckAsset asset)) + if (!pck.TryGetAsset(resourceLocation.FullPath, PckAssetType.TextureFile, out PckAsset asset)) { Trace.TraceWarning($"Could not find '{resourceLocation.FullPath}'."); atlas = null; @@ -468,6 +508,25 @@ namespace PckStudio.Core.DLC _byteOrder = GetByteOrderForPlatform(platform); } - public bool CloseDLCPackage(int identifier) => _packageRegistry.UnregisterPackage(identifier); + public DLCPackageContent CompilePackage(IDLCPackage package) + { + LOCFile localisation = GetLocalisation(package.Identifier); + switch (package.GetDLCPackageType()) + { + case DLCPackageType.Invalid: + break; + case DLCPackageType.RawAssets: return _pckFileCompiler.CompileRawAssets(package); + case DLCPackageType.SkinPack: return _pckFileCompiler.CompileSkinPackage(package, localisation); + case DLCPackageType.TexturePack: return _pckFileCompiler.CompileTexturePackage(package, localisation); + case DLCPackageType.MashUpPack: return _pckFileCompiler.CompileMashUpPackage(package, localisation); + case DLCPackageType.MG01: + break; + case DLCPackageType.MG02: + break; + case DLCPackageType.MG03: + break; + } + return DLCPackageContent.Empty; + } } } \ No newline at end of file diff --git a/PckStudio.Core/DLC/DLCMashUpPackage.cs b/PckStudio.Core/DLC/DLCMashUpPackage.cs index 5f8f022a..134b1081 100644 --- a/PckStudio.Core/DLC/DLCMashUpPackage.cs +++ b/PckStudio.Core/DLC/DLCMashUpPackage.cs @@ -15,7 +15,7 @@ namespace PckStudio.Core.DLC public sealed class DLCMashUpPackage : DLCPackage { public override string Description { get; } - private AbstractGameRule _gameRule { get; } + private AbstractGameRule _gameRule; public bool HasAudioData => _pckAudio is not null && _audioData.Count > 0; private IDLCPackage _skinPackage; @@ -24,30 +24,45 @@ namespace PckStudio.Core.DLC private IDictionary _audioData; private PckAudioFile _pckAudio; - internal DLCMashUpPackage(string name, string description, int identifier, AbstractGameRule gameRule, IDLCPackage parentPackage, IDLCPackage skinPackage = null, IDLCPackage texturePackage = null) + internal DLCMashUpPackage(string name, string description, int identifier, AbstractGameRule gameRule, MapData mapData, PckAudioFile pckAudio, IDictionary audioData, IDLCPackage parentPackage, IDLCPackage skinPackage = null, IDLCPackage texturePackage = null) : base(name, identifier, parentPackage) { Description = description; _gameRule = gameRule; _skinPackage = skinPackage; _texturePackage = texturePackage; + _mapData = mapData; + _audioData = new Dictionary(); + _pckAudio = pckAudio; + _audioData = audioData; } internal DLCMashUpPackage(string name, string description, int identifier) - : this(name, description, identifier, new RootGameRule(), null) + : this(name, description, identifier, new RootGameRule(), null, new PckAudioFile(), null, null) { _skinPackage = DLCSkinPackage.CreateEmpty(this); _texturePackage = DLCTexturePackage.CreateDefaultPackage(this); - _audioData = new Dictionary(); - _pckAudio = new PckAudioFile(); } + public override DLCPackageType GetDLCPackageType() => DLCPackageType.MashUpPack; + public IDLCPackage GetSkinPackage() => _skinPackage; public IDLCPackage GetTexturePackage() => _texturePackage; - public AbstractGameRule GetGameRule() => _gameRule; - public PckAudioFile GetAudioPack() => _pckAudio; - public override DLCPackageType GetDLCPackageType() => DLCPackageType.MashUpPack; + public bool AddAudio(string name, byte[] audioData, PckAudioFile.Category category) + { + if (_audioData.ContainsKey(name) || !_pckAudio.HasCategory(category)) + return false; + if (_pckAudio.TryGetCategory(category, out PckAudioFile.AudioCategory audioCategory)) + { + audioCategory.SongNames.Add(name); + return true; + } + return false; + } + + public AbstractGameRule GetGameRule() => _gameRule; + internal PckAudioFile GetAudioPack() => _pckAudio; internal NamedData[] GetAudioFiles() => _audioData.Select(kv => new NamedData(kv.Key, kv.Value)).ToArray(); } diff --git a/PckStudio.Core/DLC/DLCPackageContent.cs b/PckStudio.Core/DLC/DLCPackageContent.cs new file mode 100644 index 00000000..a3b4965c --- /dev/null +++ b/PckStudio.Core/DLC/DLCPackageContent.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using OMI.Formats.Pck; + +namespace PckStudio.Core.DLC +{ + public class DLCPackageContent + { + public static DLCPackageContent Empty => new DLCPackageContent(default); + + internal bool IsEmpty { get; } + + internal PckFile MainPck { get; } + + internal DLCDataFolderContent DataFolder { get; } + + public record DLCDataFolderContent + { + public NamedData TexturePck { get; } + public List> Files { get; } + + public DLCDataFolderContent(NamedData texturePck, NamedData[] files) + { + TexturePck = texturePck; + Files = new List>(files); + } + + public void AddFile(NamedData namedData) => Files.Add(namedData); + public void AddFiles(NamedData[] namedData) => Files.AddRange(namedData); + public void AddFile(string name, byte[] data) => AddFile(new NamedData(name, data)); + } + + public DLCPackageContent(PckFile mainPck, NamedData texturePck, NamedData[] dataFiles) + : this(mainPck, new(texturePck, dataFiles ?? Array.Empty>())) + { + } + + public DLCPackageContent(PckFile mainPck, DLCDataFolderContent dataFolderContent) + { + MainPck = mainPck; + DataFolder = dataFolderContent; + IsEmpty = mainPck is null; + } + + public DLCPackageContent(PckFile mainPck) : this(mainPck, default) { } + } +} \ No newline at end of file diff --git a/PckStudio.Core/DLC/DLCPackageContentSerilasationType.cs b/PckStudio.Core/DLC/DLCPackageContentSerilasationType.cs new file mode 100644 index 00000000..8e227afb --- /dev/null +++ b/PckStudio.Core/DLC/DLCPackageContentSerilasationType.cs @@ -0,0 +1,8 @@ +namespace PckStudio.Core.DLC +{ + public enum DLCPackageContentSerilasationType : int + { + Local, //! create local folder with the value of: ('IDS_DISPLAY_NAME') and write all content into it. + Share //! zip file if texture or mashup pack, else just the pck file. + } +} \ No newline at end of file diff --git a/PckStudio.Core/DLC/DLCSkinPackage.cs b/PckStudio.Core/DLC/DLCSkinPackage.cs index 9adb9397..7e9a2c0f 100644 --- a/PckStudio.Core/DLC/DLCSkinPackage.cs +++ b/PckStudio.Core/DLC/DLCSkinPackage.cs @@ -11,13 +11,12 @@ namespace PckStudio.Core.DLC public enum DLCSkinPackageOrder { ById, - CapesFirst, - SkinsFirst + ByName, } public sealed class DLCSkinPackage : DLCPackage { - public DLCSkinPackageOrder SkinPackageOrder { get; set; } = DLCSkinPackageOrder.CapesFirst; + public DLCSkinPackageOrder SkinPackageOrder { get; set; } = DLCSkinPackageOrder.ById; private readonly IDictionary _capes; private readonly IDictionary _skins; @@ -38,6 +37,7 @@ namespace PckStudio.Core.DLC internal static IDLCPackage CreateEmpty(IDLCPackage parentPackage) => CreateEmpty(parentPackage.Name, parentPackage.Identifier, parentPackage); public bool TryGetSkin(SkinIdentifier skinIdentifier, out Skin.Skin skin) => _skins.TryGetValue(skinIdentifier, out skin); + public bool TryGetCape(int capeId, out Image cape) => _capes.TryGetValue(capeId, out cape); public bool ContainsSkin(SkinIdentifier skinIdentifier) => _skins.ContainsKey(skinIdentifier); diff --git a/PckStudio.Core/DLC/DLCTexturePackage.cs b/PckStudio.Core/DLC/DLCTexturePackage.cs index b77c8ae8..779de771 100644 --- a/PckStudio.Core/DLC/DLCTexturePackage.cs +++ b/PckStudio.Core/DLC/DLCTexturePackage.cs @@ -6,13 +6,9 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using OMI.Formats.Color; -using OMI.Formats.Material; -using OMI.Formats.Model; -using OMI.Formats.Pck; -using OMI.Workers.Color; using PckStudio.Core.Extensions; using PckStudio.Core.Interfaces; +using PckStudio.Core.Model; using PckStudio.Core.Properties; namespace PckStudio.Core.DLC @@ -43,11 +39,11 @@ namespace PckStudio.Core.DLC public Image IconImg { get; } = iconImg; } - public sealed class EnvironmentData + public sealed class EnvironmentData(Image clouds, Image rain, Image snow) { - public Image Clouds; - public Image Rain; - public Image Snow; + public Image Clouds = clouds; + public Image Rain = rain; + public Image Snow = snow; } public MetaData Info { get; } @@ -56,20 +52,32 @@ namespace PckStudio.Core.DLC //! => colours.col private IDictionary _colors; private IDictionary _waterColors; - private ModelContainer _customModels; //! can be null.. => models.bin - private MaterialContainer _materials; //! can be null.. + private AbstractModelContainer _customModels; //! can be null.. => models.bin + private IDictionary _materials; //! can be null.. //! terrain mipmaps will be generated automatically. Add mipmap option to settings menu ? -null private Atlas _terrainAtlas; private Atlas _itemsAtlas; private Atlas _particlesAtlas; private Atlas _paintingAtlas; - private ArmorSet[] _armorSets = new ArmorSet[6]; + private Atlas _moonPhaseAtlas; + private ArmorSet _leatherArmorSet; + private ArmorSet _chainArmorSet; + private ArmorSet _ironArmorSet; + private ArmorSet _goldArmorSet; + private ArmorSet _diamondArmorSet; + private ArmorSet _turtleArmorSet; + private Atlas _mapIconsAtlas; + private Atlas _additionalMapIconsAtlas; private EnvironmentData _environmentData; private Animation _blockEntityBreakAnimation; private IDictionary _itemAnimations; private IDictionary _blockAnimations; + private Image _sun; + private Image _moon; + + //! TODO: add resources from "res/misc/" internal DLCTexturePackage( string name, @@ -81,14 +89,25 @@ namespace PckStudio.Core.DLC Atlas itemsAtlas, Atlas particlesAtlas, Atlas paintingAtlas, - ArmorSet[] armorSets, + Atlas moonPhaseAtlas, + Atlas mapIconsAtlas, + Atlas additionalMapIconsAtlas, + ArmorSet leatherArmorSet, + ArmorSet chainArmorSet, + ArmorSet ironArmorSet, + ArmorSet goldArmorSet, + ArmorSet diamondArmorSet, + ArmorSet turtleArmorSet, + EnvironmentData environmentData, IDictionary colors, IDictionary waterColors, - ModelContainer customModels, - MaterialContainer materials, + AbstractModelContainer customModels, + IDictionary materials, Animation blockEntityBreakAnimation, IDictionary itemAnimations, IDictionary blockAnimations, + Image sun, + Image moon, IDLCPackage parentPackage ) : base(name, identifier, parentPackage) @@ -100,14 +119,25 @@ namespace PckStudio.Core.DLC _itemsAtlas = itemsAtlas; _particlesAtlas = particlesAtlas; _paintingAtlas = paintingAtlas; - _armorSets = armorSets; - _colors = colors; - _waterColors = waterColors; + _moonPhaseAtlas = moonPhaseAtlas; + _mapIconsAtlas = mapIconsAtlas; + _additionalMapIconsAtlas = additionalMapIconsAtlas; + _leatherArmorSet = leatherArmorSet; + _chainArmorSet = chainArmorSet; + _ironArmorSet = ironArmorSet; + _goldArmorSet = goldArmorSet; + _diamondArmorSet = diamondArmorSet; + _turtleArmorSet = turtleArmorSet; + _environmentData = environmentData; + _colors = colors ?? new Dictionary(); + _waterColors = waterColors ?? new Dictionary(); _customModels = customModels; _materials = materials; _blockEntityBreakAnimation = blockEntityBreakAnimation; - _itemAnimations = itemAnimations; - _blockAnimations = blockAnimations; + _itemAnimations = itemAnimations ?? new Dictionary(); + _blockAnimations = blockAnimations ?? new Dictionary(); + _sun = sun; + _moon = moon; } public TextureResolution GetResolution() => _resolution; @@ -166,28 +196,36 @@ namespace PckStudio.Core.DLC Atlas items = Atlas.FromResourceLocation(Resources.items_atlas, ResourceLocation.GetFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.ItemAtlas))); Atlas particles = Atlas.FromResourceLocation(Resources.particles_atlas, ResourceLocation.GetFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.ParticleAtlas))); Atlas painting = Atlas.FromResourceLocation(Resources.paintings_atlas, ResourceLocation.GetFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.PaintingAtlas))); + Atlas moonPhases = Atlas.FromResourceLocation(Resources.moon_phases_atlas, ResourceLocation.GetFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.MoonPhaseAtlas))); + Atlas mapIconsAtlas = Atlas.FromResourceLocation(Resources.map_icons_atlas, ResourceLocation.GetFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.MapIconAtlas))); + Atlas additionalMapIconsAtlas = Atlas.FromResourceLocation(Resources.additional_map_icons_atlas, ResourceLocation.GetFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.AdditionalMapIconsAtlas))); //ColorContainer colors = new COLFileReader().FromStream(new MemoryStream()); IDictionary colors = null; IDictionary waterColors = null; - + Animation blockEntityBreakAnimation = new Animation(terrain.GetRange(0, 15, 10, ImageLayoutDirection.Horizontal).Select(t => t.Texture).ToArray(), true, 3); - - ArmorSet[] armorSets = GetArmorSets(); IDictionary itemAnimations = GetItemAnimations(); - IDictionary blockAnimations = GetBlockAnimations(); return new DLCTexturePackage( name, description, identifier, metadata, resolution, - terrain, items, particles, painting, - armorSets, + terrain, items, particles, painting, moonPhases, mapIconsAtlas, additionalMapIconsAtlas, + new ArmorSet(ArmorSetDescription.CLOTH, Resources.cloth, Resources.cloth_b), + new ArmorSet(ArmorSetDescription.CHAIN, Resources.chain, default), + new ArmorSet(ArmorSetDescription.IRON, Resources.iron, default), + new ArmorSet(ArmorSetDescription.GOLD, Resources.gold, default), + new ArmorSet(ArmorSetDescription.DIAMOND, Resources.diamond, default), + new ArmorSet(ArmorSetDescription.TURTLE, Resources.turtle, default), + new EnvironmentData(Resources.clouds, Resources.rain, Resources.snow), colors, waterColors, - new ModelContainer(), - new MaterialContainer(), + new AbstractModelContainer(), + new Dictionary(), blockEntityBreakAnimation, itemAnimations, blockAnimations, + sun: null, + moon: null, parentPackage ); } @@ -200,19 +238,6 @@ namespace PckStudio.Core.DLC internal Atlas GetPaintingAtlas() => _paintingAtlas; - private static ArmorSet[] GetArmorSets() - { - return new ArmorSet[6] - { - new ArmorSet(ArmorSetDescription.CLOTH, Resources.cloth, Resources.cloth_b), - new ArmorSet(ArmorSetDescription.CHAIN, Resources.chain, default), - new ArmorSet(ArmorSetDescription.IRON, Resources.iron, default), - new ArmorSet(ArmorSetDescription.GOLD, Resources.gold, default), - new ArmorSet(ArmorSetDescription.DIAMOND, Resources.diamond, default), - new ArmorSet(ArmorSetDescription.TURTLE, Resources.turtle, default) - }; - } - private static IDictionary GetItemAnimations() { return new Dictionary() diff --git a/PckStudio.Core/DLC/PckFileCompiler.cs b/PckStudio.Core/DLC/PckFileCompiler.cs new file mode 100644 index 00000000..5e3ec54f --- /dev/null +++ b/PckStudio.Core/DLC/PckFileCompiler.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OMI; +using OMI.Formats.GameRule; +using OMI.Formats.Languages; +using OMI.Formats.Pck; +using OMI.Workers.GameRule; +using OMI.Workers.Language; +using OMI.Workers.Pck; +using PckStudio.Core.Extensions; +using PckStudio.Core.Interfaces; +using PckStudio.Core.IO.PckAudio; + +namespace PckStudio.Core.DLC +{ + internal sealed class PckFileCompiler + { + private ByteOrder _byteOrder; + private GameRuleFile.CompressionType _compressionType; + private GameRuleFile.CompressionLevel _compressionLevel; + + internal PckFileCompiler(ByteOrder byteOrder, GameRuleFile.CompressionType compressionType, GameRuleFile.CompressionLevel compressionLevel) + { + _byteOrder = byteOrder; + _compressionType = compressionType; + _compressionLevel = compressionLevel; + } + + private PckFile CreateRootPckFile(int packId, int packVerison, LOCFile localisation) + { + PckFile mainPck = new PckFile(); + PckAsset meta = mainPck.CreateNewAsset("0", PckAssetType.InfoFile); + meta.AddProperty("PACKID", packId); + meta.AddProperty("PACKVERSION", packVerison); + mainPck.CreateNewAsset("localisation.loc", PckAssetType.LocalisationFile, new LOCFileWriter(localisation, 2)); + return mainPck; + } + + internal DLCPackageContent CompileSkinPackage(IDLCPackage package, LOCFile localisation) + { + if (package is not DLCSkinPackage skinPackage) + return DLCPackageContent.Empty; + + PckFile skinsPck = skinPackage.IsRootPackage ? CreateRootPckFile(package.Identifier, 0, localisation) : new PckFile(); + foreach (KeyValuePair kv in skinPackage.GetCapes()) + { + PckAsset capeAsset = skinsPck.CreateNewAsset($"dlccape{kv.Key:08}.png", PckAssetType.CapeFile); + capeAsset.SetTexture(kv.Value); + } + foreach (Skin.Skin skin in skinPackage.GetSkins()) + { + skinsPck.AddSkin("", skin, localisation); + } + + return new DLCPackageContent(skinsPck); + } + + internal DLCPackageContent CompileTexturePackage(IDLCPackage package, LOCFile localisation) + { + if (package is not DLCTexturePackage texturePackage) + return DLCPackageContent.Empty; + + PckFile texturePackInfoPck = new PckFile(); + { + texturePackInfoPck.AddTexture("comparison.png", texturePackage.Info.ComparisonImg); + texturePackInfoPck.AddTexture("icon.png", texturePackage.Info.IconImg); + } + + PckFile texturePck = new PckFile(); + { + texturePck.AddTexture(ResourceLocations.GetPathFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.ParticleAtlas)), texturePackage.GetParticleAtlas()); + texturePck.AddTexture(ResourceLocations.GetPathFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.ItemAtlas)), texturePackage.GetItemsAtlas()); + texturePck.AddTexture(ResourceLocations.GetPathFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.BlockAtlas)), texturePackage.GetTerrainAtlas()); + texturePck.AddTexture(ResourceLocations.GetPathFromCategory(AtlasResource.GetId(AtlasResource.AtlasType.PaintingAtlas)), texturePackage.GetPaintingAtlas()); + } + + if (package.IsRootPackage) + { + PckFile mainPck = CreateRootPckFile(package.Identifier, 0, localisation); + DLCTexturePackage.TextureResolution res = texturePackage.GetResolution(); + PckAsset textureInfoAsset = mainPck.CreateNewAsset($"{res}/{res}Info.pck", PckAssetType.TexturePackInfoFile, new PckFileWriter(texturePackInfoPck, _byteOrder)); + textureInfoAsset.AddProperty("PACKID", "0"); + textureInfoAsset.AddProperty("DATAPATH", $"{res}Data.pck"); + return new DLCPackageContent(mainPck, new NamedData($"{res}Data.pck", texturePck), default); + } + + return new DLCPackageContent(texturePackInfoPck); + } + + internal DLCPackageContent CompileMashUpPackage(IDLCPackage package, LOCFile localisation) + { + if (package is not DLCMashUpPackage mashUpPackage) + return DLCPackageContent.Empty; + + PckFile skinsPck = CompileSkinPackage(mashUpPackage.GetSkinPackage(), localisation).MainPck; + + DLCTexturePackage texturePackage = mashUpPackage.GetTexturePackage() as DLCTexturePackage; + DLCPackageContent texturePackContent = CompileTexturePackage(texturePackage, localisation); + DLCTexturePackage.TextureResolution res = texturePackage.GetResolution(); + PckFile texturePackInfoPck = texturePackContent.MainPck; + PckFile texturePck = texturePackContent.DataFolder.TexturePck.Value; + + PckFile mainPck = CreateRootPckFile(package.Identifier, 0, localisation); + _ = mainPck.CreateNewAssetIf(skinsPck is PckFile && skinsPck.AssetCount > 0, "Skins.pck", PckAssetType.SkinDataFile, new PckFileWriter(skinsPck, _byteOrder)); + + if (texturePackInfoPck is PckFile && texturePackInfoPck.AssetCount > 0) + { + PckAsset textureInfoAsset = mainPck.CreateNewAsset($"{res}/{res}Info.pck", PckAssetType.TexturePackInfoFile, new PckFileWriter(texturePackInfoPck, _byteOrder)); + textureInfoAsset.AddProperty("PACKID", "0"); + textureInfoAsset.AddProperty("DATAPATH", texturePackContent.DataFolder.TexturePck.Name); + } + + + { + GameRuleFile grf = mashUpPackage.GetGameRule(); + grf.Header.CompressionType = _compressionType; + grf.Header.CompressionLevel = _compressionLevel; + mainPck.CreateNewAsset("GameRule.grf", PckAssetType.GameRulesFile, new GameRuleFileWriter(grf)); + } + + if (mashUpPackage.HasAudioData) + { + mainPck.CreateNewAsset("audio.pck", PckAssetType.AudioFile, new PckAudioFileWriter(mashUpPackage.GetAudioPack(), _byteOrder)); + texturePackContent.DataFolder.AddFiles(mashUpPackage.GetAudioFiles()); + } + + return new DLCPackageContent(mainPck, texturePackContent.DataFolder); + } + + internal DLCPackageContent CompileRawAssets(IDLCPackage package) + { + return package is RawAssetDLCPackage rawAssetDLCPackage ? new DLCPackageContent(rawAssetDLCPackage.PckFile) : DLCPackageContent.Empty; + } + } +} diff --git a/PckStudio.Core/Extensions/AnimationExtensions.cs b/PckStudio.Core/Extensions/AnimationExtensions.cs index 4229ba26..5d99c839 100644 --- a/PckStudio.Core/Extensions/AnimationExtensions.cs +++ b/PckStudio.Core/Extensions/AnimationExtensions.cs @@ -1,4 +1,7 @@ -using System.Drawing; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Threading.Tasks; using AnimatedGif; namespace PckStudio.Core.Extensions @@ -23,5 +26,26 @@ namespace PckStudio.Core.Extensions ms.Position = 0; return Image.FromStream(ms); } + + public static Animation Combine(this Animation first, Animation second, ImageLayoutDirection layoutDirection) + { + if (first == null) + return second; + if (second == null) + return first; + if (first.TextureCount != second.TextureCount) + return first; + if (first.FrameCount != second.FrameCount) + return first; + + Image[] secondTextures = second.GetTextures().ToArray(); + + Animation animation = new Animation(first.GetTextures().enumerate().Select(ift => ift.value.Combine(secondTextures[ift.index], layoutDirection))); + foreach ((int texId, int frameTime) item in first.GetFrames().Select(f => (texId: first.GetTextureIndex(f.Texture), frameTime: f.Ticks))) + { + animation.AddFrame(item.texId, item.frameTime); + } + return animation; + } } } diff --git a/PckStudio.Core/Extensions/ColorExtensions.cs b/PckStudio.Core/Extensions/ColorExtensions.cs index 4a64fa9e..0cb29dce 100644 --- a/PckStudio.Core/Extensions/ColorExtensions.cs +++ b/PckStudio.Core/Extensions/ColorExtensions.cs @@ -54,15 +54,15 @@ namespace PckStudio.Core.Extensions return MathExtensions.Clamp(resultValue, 0.0f, 1.0f); } - public static byte Mix(double ratio, byte val1, byte val2) + public static byte Mix(float ratio, byte val1, byte val2) { - ratio = MathExtensions.Clamp(ratio, 0.0, 1.0); + ratio = MathExtensions.Clamp(ratio, 0.0f, 1.0f); return (byte)(ratio * val1 + (1.0 - ratio) * val2); } - public static Color Mix(this Color c1, Color c2, double ratio) + public static Color Mix(this Color c1, Color c2, float ratio) { - ratio = MathExtensions.Clamp(ratio, 0.0, 1.0); + ratio = MathExtensions.Clamp(ratio, 0.0f, 1.0f); return Color.FromArgb(c1.A, Mix(ratio, c1.R, c2.R), Mix(ratio, c1.G, c2.G), diff --git a/PckStudio.Core/Extensions/ImageExtensions.cs b/PckStudio.Core/Extensions/ImageExtensions.cs index 6f9549f4..660e5727 100644 --- a/PckStudio.Core/Extensions/ImageExtensions.cs +++ b/PckStudio.Core/Extensions/ImageExtensions.cs @@ -273,9 +273,9 @@ namespace PckStudio.Core.Extensions return bitmapResult; } - public static Image Interpolate(this Image source, Image target, double delta) + public static Image Interpolate(this Image source, Image target, float delta) { - delta = MathExtensions.Clamp(delta, 0.0, 1.0); + delta = MathExtensions.Clamp(delta, 0.0f, 1.0f); if (source is not Bitmap baseImage || target is not Bitmap overlayImage || source.Width != target.Width || source.Height != target.Height) return source; diff --git a/PckStudio.Core/MapData.cs b/PckStudio.Core/MapData.cs index 655c0a0c..674b4927 100644 --- a/PckStudio.Core/MapData.cs +++ b/PckStudio.Core/MapData.cs @@ -39,8 +39,8 @@ namespace PckStudio.Core Thumbnail = thumbnail; World = world; - var levelData = MapReader.OpenSave(new MemoryStream(world.Value))["level.dat"]; - TagCompound? levelDat = NbtDocument.LoadDocument(new MemoryStream(levelData))!.DocumentRoot?["Data"] as TagCompound; + SaveData saveData = MapReader.OpenSaveData(new MemoryStream(world.Value)); + TagCompound? levelDat = saveData.LevelData.DocumentRoot?["Data"] as TagCompound; _ = levelDat ?? throw new NullReferenceException(nameof(levelDat)); Vector3 spawn = levelDat.GetVector3("Spawn"); diff --git a/PckStudio.Core/MapReader.cs b/PckStudio.Core/MapReader.cs index 6f2d8411..fd5ef342 100644 --- a/PckStudio.Core/MapReader.cs +++ b/PckStudio.Core/MapReader.cs @@ -1,13 +1,42 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using Cyotek.Data.Nbt; using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using OMI; +using PckStudio.Core.Extensions; namespace PckStudio.Core { + public class SaveData + { + public NbtDocument LevelData { get; } + public IDictionary Players { get; } + + + private IDictionary _worldArchive; + + public SaveData(IDictionary worldArchive) + { + _worldArchive = worldArchive; + if (_worldArchive.TryGetValue("levels.dat", out byte[] levelData)) + { + Stream stream = new MemoryStream(levelData); + LevelData = NbtDocument.LoadDocument(stream); + } + Players = _worldArchive + .Where(kv => kv.Key.StartsWith("players/")) + .ToDictionary( + kv => Guid.Parse(Path.GetFileNameWithoutExtension(kv.Key)), + kv => NbtDocument.LoadDocument(new MemoryStream(kv.Value))); + } + } + + public class MapReader { - public static IDictionary OpenSave(Stream stream) + public static SaveData OpenSaveData(Stream stream) { EndiannessAwareBinaryReader reader = new EndiannessAwareBinaryReader(stream, ByteOrder.BigEndian); _ = reader.ReadInt32(); @@ -35,7 +64,7 @@ namespace PckStudio.Core res.Add(path, data); } - return res; + return new SaveData(res); } } } diff --git a/PckStudio.Core/PckStudio.Core.csproj b/PckStudio.Core/PckStudio.Core.csproj index 6e09711c..928b5e1d 100644 --- a/PckStudio.Core/PckStudio.Core.csproj +++ b/PckStudio.Core/PckStudio.Core.csproj @@ -77,9 +77,12 @@ + + + diff --git a/PckStudio.Core/Skin/SkinIdentifier.cs b/PckStudio.Core/Skin/SkinIdentifier.cs index cec8e9aa..b7fba1ee 100644 --- a/PckStudio.Core/Skin/SkinIdentifier.cs +++ b/PckStudio.Core/Skin/SkinIdentifier.cs @@ -5,19 +5,19 @@ namespace PckStudio.Core.Skin { public sealed class SkinIdentifier : IFormattable { - public int Id { get; } + private readonly int _id; public SkinIdentifier(int id) { - Id = id; + _id = id; } - public static implicit operator int(SkinIdentifier _this) => _this.Id; + public static implicit operator int(SkinIdentifier @this) => @this._id; - public string ToString(string format, IFormatProvider formatProvider) => Id.ToString(format, formatProvider); + public string ToString(string format, IFormatProvider formatProvider) => _id.ToString(format, formatProvider); - public string ToString(string format) => Id.ToString(format, NumberFormatInfo.CurrentInfo); + public string ToString(string format) => _id.ToString(format, NumberFormatInfo.CurrentInfo); - public override string ToString() => Id.ToString(NumberFormatInfo.CurrentInfo); + public override string ToString() => _id.ToString(NumberFormatInfo.CurrentInfo); } }