diff --git a/VPet.ModMaker/Models/ModModel/FoodAnimeModel.cs b/VPet.ModMaker/Models/ModModel/FoodAnimeModel.cs index 7714dca..4514fa5 100644 --- a/VPet.ModMaker/Models/ModModel/FoodAnimeModel.cs +++ b/VPet.ModMaker/Models/ModModel/FoodAnimeModel.cs @@ -23,19 +23,19 @@ public class FoodAnimeModel /// /// 后图像列表 /// - public ObservableCollection BackImages { get; } = new(); - public ObservableValue BackImagesPath { get; } = new(); + public ObservableCollection BackImages { get; set; } = new(); + public ObservableValue BackImagesPath { get; } = new(); /// /// 前图像列表 /// - public ObservableCollection FrontImages { get; } = new(); - public ObservableValue FrontImagesPath { get; } = new(); + public ObservableCollection FrontImages { get; set; } = new(); + public ObservableValue FrontImagesPath { get; } = new(); /// /// 食物定位列表 /// - public ObservableCollection FoodLocations { get; } = new(); + public ObservableCollection FoodLocations { get; } = new(); public FoodAnimeModel() { } @@ -46,7 +46,7 @@ public class FoodAnimeModel { //var index = int.Parse(item.Name.Substring(1)); var infos = item.Info.Split(','); - var foodLocationInfo = new FoodLocationInfoModel(); + var foodLocationInfo = new FoodLocationModel(); foodLocationInfo.Duration.Value = int.Parse(infos[0]); if (infos.Length > 1) { diff --git a/VPet.ModMaker/Models/ModModel/FoodAnimeTypeModel.cs b/VPet.ModMaker/Models/ModModel/FoodAnimeTypeModel.cs index b9af468..1296a74 100644 --- a/VPet.ModMaker/Models/ModModel/FoodAnimeTypeModel.cs +++ b/VPet.ModMaker/Models/ModModel/FoodAnimeTypeModel.cs @@ -4,6 +4,7 @@ using LinePutScript.Localization.WPF; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; @@ -24,6 +25,11 @@ public class FoodAnimeTypeModel /// public ObservableValue Name { get; } = new(); + /// + /// 动作类型 + /// + public GraphInfo.GraphType GraphType => GraphInfo.GraphType.Common; + /// /// 开心动画 /// @@ -46,28 +52,73 @@ public class FoodAnimeTypeModel public FoodAnimeTypeModel() { + HappyAnimes.CollectionChanged += Animes_CollectionChanged; //Name.ValueChanged += (_, _) => //{ // Id.Value = $"{GraphType.Value}_{Name.Value}"; //}; } + private void Animes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems is not null) + { + foreach (var model in e.NewItems.Cast()) + { + SetImagesPathValueChanged(model); + } + } + if (e.OldItems is not null) + { + foreach (var model in e.OldItems.Cast()) + { + SetImagesPathValueChanged(model); + } + } + } + + private void SetImagesPathValueChanged(FoodAnimeModel model) + { + model.FrontImagesPath.ValueChanged += (o, n) => + { + if (n is null) + return; + if (n.Mode.Value is GameSave.ModeType.Happy) + model.FrontImages = HappyAnimes[n.Index.Value].FrontImages; + else if (n.Mode.Value is GameSave.ModeType.Nomal) + model.FrontImages = NomalAnimes[n.Index.Value].FrontImages; + else if (n.Mode.Value is GameSave.ModeType.PoorCondition) + model.FrontImages = PoorConditionAnimes[n.Index.Value].FrontImages; + else if (n.Mode.Value is GameSave.ModeType.Ill) + model.FrontImages = IllAnimes[n.Index.Value].FrontImages; + }; + model.BackImagesPath.ValueChanged += (o, n) => + { + if (n is null) + return; + if (n.Mode.Value is GameSave.ModeType.Happy) + model.BackImages = HappyAnimes[n.Index.Value].BackImages; + else if (n.Mode.Value is GameSave.ModeType.Nomal) + model.BackImages = NomalAnimes[n.Index.Value].BackImages; + else if (n.Mode.Value is GameSave.ModeType.PoorCondition) + model.BackImages = PoorConditionAnimes[n.Index.Value].BackImages; + else if (n.Mode.Value is GameSave.ModeType.Ill) + model.BackImages = IllAnimes[n.Index.Value].BackImages; + }; + } + public FoodAnimeTypeModel(string path) : this() { Name.Value = Path.GetFileName(path); var infoFile = Path.Combine(path, ModMakerInfo.InfoFile); - if (File.Exists(infoFile) is false) + if ( + Directory.EnumerateFiles(path, ModMakerInfo.InfoFile, SearchOption.AllDirectories).Any() + is false + ) throw new Exception("信息文件\n{0}\n不存在".Translate(infoFile)); - var lps = new LPS(infoFile); - var foodAnime = lps.FindAllLineInfo(nameof(FoodAnimation)); - if (foodAnime.Any() is false) - throw new Exception("信息文件\n{0}\n未包含食物动画信息".Translate(infoFile)); - var anime = lps.FindAllLineInfo(nameof(PNGAnimation)); - foreach (var foodAnimation in foodAnime) - { - ParseFoodAnimeInfo(foodAnimation); - } + if (File.Exists(infoFile)) + ParseInfoFile(path, infoFile); } public FoodAnimeTypeModel(FoodAnimeTypeModel model) @@ -104,21 +155,115 @@ public class FoodAnimeTypeModel } } - public void ParseFoodAnimeInfo(ILine line) + public void ParseInfoFile(string path, string infoPath) { - var mode = (GameSave.ModeType)Enum.Parse(typeof(GameSave.ModeType), line.Find("mode").Info); - if (mode is GameSave.ModeType.Happy) - AddModeAnime(HappyAnimes, line); - else if (mode is GameSave.ModeType.Nomal) - AddModeAnime(NomalAnimes, line); - else if (mode is GameSave.ModeType.PoorCondition) - AddModeAnime(PoorConditionAnimes, line); - else if (mode is GameSave.ModeType.Ill) - AddModeAnime(IllAnimes, line); + var lps = new LPS(infoPath); + var foodAnimeInfos = lps.FindAllLineInfo(nameof(FoodAnimation)); + if (foodAnimeInfos.Any() is false) + throw new Exception("信息文件\n{0}\n未包含食物动画信息".Translate(infoPath)); + var pngAnimeInfos = lps.FindAllLineInfo(nameof(PNGAnimation)) + .Select( + i => + new PNGAnimeInfo( + i.Info, + i.Find("infoPath").Info, + (GameSave.ModeType) + Enum.Parse(typeof(GameSave.ModeType), i.Find("mode").Info, true) + ) + ) + .ToList(); + foreach (var foodAnimation in foodAnimeInfos) + { + ParseFoodAnimeInfo(path, foodAnimation, pngAnimeInfos); + } } - public void AddModeAnime(ObservableCollection foodAnimes, ILine line) + public void ParseFoodAnimeInfo(string path, ILine line, List pngAnimeInfos) { - foodAnimes.Add(new(line)); + var mode = (GameSave.ModeType) + Enum.Parse(typeof(GameSave.ModeType), line.Find("mode").Info, true); + if (mode is GameSave.ModeType.Happy) + AddModeAnime(path, GameSave.ModeType.Happy, HappyAnimes, line, pngAnimeInfos); + else if (mode is GameSave.ModeType.Nomal) + AddModeAnime(path, GameSave.ModeType.Nomal, NomalAnimes, line, pngAnimeInfos); + else if (mode is GameSave.ModeType.PoorCondition) + AddModeAnime( + path, + GameSave.ModeType.PoorCondition, + PoorConditionAnimes, + line, + pngAnimeInfos + ); + else if (mode is GameSave.ModeType.Ill) + AddModeAnime(path, GameSave.ModeType.Ill, IllAnimes, line, pngAnimeInfos); + } + + public void AddModeAnime( + string path, + GameSave.ModeType mode, + ObservableCollection foodAnimes, + ILine line, + List pngAnimeInfos + ) + { + var anime = new FoodAnimeModel(line); + var frontLay = line.Find("front_lay").Info; + var backLay = line.Find("back_lay").Info; + var frontLayAnimes = pngAnimeInfos.Where(i => i.Name == frontLay).ToList(); + var backLayAnimes = pngAnimeInfos.Where(i => i.Name == backLay).ToList(); + // 尝试获取相同模式的动画 + if (frontLayAnimes.FirstOrDefault(i => i.Mode == mode) is PNGAnimeInfo frontAnimeInfo) + { + anime.FrontImages = GetImages(path, frontAnimeInfo); + } + else + { + // 若没有则获取通用动画 + anime.FrontImages = GetImages( + path, + frontLayAnimes.First(i => i.Mode == GameSave.ModeType.Nomal) + ); + } + if (backLayAnimes.FirstOrDefault(i => i.Mode == mode) is PNGAnimeInfo backAnimeInfo) + { + anime.BackImages = GetImages(path, backAnimeInfo); + } + else + { + anime.BackImages = GetImages( + path, + backLayAnimes.First(i => i.Mode == GameSave.ModeType.Nomal) + ); + } + foodAnimes.Add(anime); + + static ObservableCollection GetImages(string path, PNGAnimeInfo pngAnimeInfo) + { + return new( + Directory + .EnumerateFiles(Path.Combine(path, pngAnimeInfo.Path)) + .Select( + i => + new ImageModel( + Utils.LoadImageToMemoryStream(i), + int.Parse(Path.GetFileNameWithoutExtension(i).Split('_')[1]) + ) + ) + ); + } + } +} + +public class PNGAnimeInfo +{ + public string Name { get; } + public string Path { get; } + public GameSave.ModeType Mode { get; } + + public PNGAnimeInfo(string name, string path, GameSave.ModeType mode) + { + Name = name; + Path = path; + Mode = mode; } } diff --git a/VPet.ModMaker/Models/ModModel/FoodLocationInfoModel.cs b/VPet.ModMaker/Models/ModModel/FoodLocationModel.cs similarity index 87% rename from VPet.ModMaker/Models/ModModel/FoodLocationInfoModel.cs rename to VPet.ModMaker/Models/ModModel/FoodLocationModel.cs index 077a95d..c307681 100644 --- a/VPet.ModMaker/Models/ModModel/FoodLocationInfoModel.cs +++ b/VPet.ModMaker/Models/ModModel/FoodLocationModel.cs @@ -10,7 +10,7 @@ namespace VPet.ModMaker.Models.ModModel; /// /// 食物图像模型 /// -public class FoodLocationInfoModel +public class FoodLocationModel { /// /// 持续时间 @@ -32,7 +32,7 @@ public class FoodLocationInfoModel /// public ObservableValue Opacity { get; } = new(100); - public FoodLocationInfoModel() + public FoodLocationModel() { Rect.Width.ValueChanged += (o, n) => { @@ -40,9 +40,9 @@ public class FoodLocationInfoModel }; } - public FoodLocationInfoModel Copy() + public FoodLocationModel Copy() { - var model = new FoodLocationInfoModel(); + var model = new FoodLocationModel(); model.Duration.Value = Duration.Value; model.Rect.SetValue(Rect.X.Value, Rect.Y.Value, Rect.Width.Value, Rect.Height.Value); model.Rotate.Value = Rotate.Value; diff --git a/VPet.ModMaker/Models/ModModel/PetModel.cs b/VPet.ModMaker/Models/ModModel/PetModel.cs index 8c43a09..e3bec2d 100644 --- a/VPet.ModMaker/Models/ModModel/PetModel.cs +++ b/VPet.ModMaker/Models/ModModel/PetModel.cs @@ -70,6 +70,11 @@ public class PetModel : I18nModel /// public ObservableCollection Animes { get; } = new(); + /// + ///食物动画 + /// + public ObservableCollection FoodAnimes { get; } = new(); + public bool IsSimplePetModel { get; } = false; public PetModel() diff --git a/VPet.ModMaker/SimpleObservable/ObservableValue.cs b/VPet.ModMaker/SimpleObservable/ObservableValue.cs index c1a152a..6fb88f7 100644 --- a/VPet.ModMaker/SimpleObservable/ObservableValue.cs +++ b/VPet.ModMaker/SimpleObservable/ObservableValue.cs @@ -34,6 +34,11 @@ public class ObservableValue : INotifyPropertyChanging, INotifyPropertyChange } } + /// + /// 含有值 + /// + public bool HasValue => Value != null; + #region Ctor /// public ObservableValue() { } diff --git a/VPet.ModMaker/VPet.ModMaker.csproj b/VPet.ModMaker/VPet.ModMaker.csproj index 31297fc..6ead5ed 100644 --- a/VPet.ModMaker/VPet.ModMaker.csproj +++ b/VPet.ModMaker/VPet.ModMaker.csproj @@ -106,7 +106,7 @@ - + diff --git a/VPet.ModMaker/ViewModels/ModEdit/AnimeEdit/AnimeEditWindowVM.cs b/VPet.ModMaker/ViewModels/ModEdit/AnimeEdit/AnimeEditWindowVM.cs index a8ffa1f..0905bd3 100644 --- a/VPet.ModMaker/ViewModels/ModEdit/AnimeEdit/AnimeEditWindowVM.cs +++ b/VPet.ModMaker/ViewModels/ModEdit/AnimeEdit/AnimeEditWindowVM.cs @@ -286,7 +286,6 @@ public class AnimeEditWindowVM return; } } while (Loop.Value); - Reset(); } /// diff --git a/VPet.ModMaker/ViewModels/ModEdit/AnimeEdit/FoodAnimeEditWindowVM.cs b/VPet.ModMaker/ViewModels/ModEdit/AnimeEdit/FoodAnimeEditWindowVM.cs index 4ae76ae..9371f88 100644 --- a/VPet.ModMaker/ViewModels/ModEdit/AnimeEdit/FoodAnimeEditWindowVM.cs +++ b/VPet.ModMaker/ViewModels/ModEdit/AnimeEdit/FoodAnimeEditWindowVM.cs @@ -7,8 +7,10 @@ using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows; +using System.Windows.Media.Imaging; using VPet.ModMaker.Models; using VPet.ModMaker.Models.ModModel; using VPet_Simulator.Core; @@ -22,25 +24,45 @@ public class FoodAnimeEditWindowVM /// public PetModel CurrentPet { get; set; } + /// + /// 食物图片 + /// + public ObservableValue FoodImage { get; } = new(); + + /// + /// 比例 + /// + public ObservableValue LengthRatio { get; } = new(250.0 / 500.0); + /// /// 旧动画 /// - public AnimeTypeModel OldAnime { get; set; } + public FoodAnimeTypeModel OldAnime { get; set; } /// /// 动画 /// - public ObservableValue Anime { get; } = new(new()); + public ObservableValue Anime { get; } = new(new()); /// - /// 当前图像模型 + /// 当前顶层图像模型 /// - public ObservableValue CurrentImageModel { get; } = new(); + public ObservableValue CurrentFrontImageModel { get; } = new(); + + /// + /// 当前底层图像模型 + /// + public ObservableValue CurrentBackImageModel { get; } = new(); + + /// + /// 当前食物定位模型 + /// + public ObservableValue CurrentFoodLocationModel { get; } = new(); /// /// 当前动画模型 /// - public ObservableValue CurrentAnimeModel { get; } = new(); + public ObservableValue CurrentAnimeModel { get; } = new(); /// /// 当前模式 @@ -100,13 +122,25 @@ public class FoodAnimeEditWindowVM private bool _playing = false; /// - /// 动画任务 + /// 顶层动画任务 /// - private Task _playerTask; + private Task _frontPlayerTask; + + /// + /// 底层动画任务 + /// + private Task _backPlayerTask; + + /// + /// 食物动画任务 + /// + private Task _foodPlayerTask; public FoodAnimeEditWindowVM() { - _playerTask = new(Play); + _frontPlayerTask = new(FrontPlay); + _backPlayerTask = new(BackPlay); + _foodPlayerTask = new(FoodPlay); CurrentAnimeModel.ValueChanged += CurrentAnimeModel_ValueChanged; @@ -121,18 +155,18 @@ public class FoodAnimeEditWindowVM } #region LoadAnime - private void Anime_ValueChanged(AnimeTypeModel oldValue, AnimeTypeModel newValue) + private void Anime_ValueChanged(FoodAnimeTypeModel oldValue, FoodAnimeTypeModel newValue) { CheckGraphType(newValue); } - private void CheckGraphType(AnimeTypeModel model) + private void CheckGraphType(FoodAnimeTypeModel model) { - if (AnimeTypeModel.HasMultiTypeAnimes.Contains(model.GraphType.Value)) - HasMultiType.Value = true; + //if (FoodAnimeTypeModel.HasMultiTypeAnimes.Contains(model.GraphType.Value)) + // HasMultiType.Value = true; - if (AnimeTypeModel.HasNameAnimes.Contains(model.GraphType.Value)) - HasAnimeName.Value = true; + //if (FoodAnimeTypeModel.HasNameAnimes.Contains(model.GraphType.Value)) + // HasAnimeName.Value = true; } #endregion /// @@ -146,14 +180,14 @@ public class FoodAnimeEditWindowVM MessageBox.Show("确定删除吗".Translate(), "", MessageBoxButton.YesNo) is MessageBoxResult.Yes ) { - if (CurrentMode is GameSave.ModeType.Happy) - Anime.Value.HappyAnimes.Remove(value); - else if (CurrentMode is GameSave.ModeType.Nomal) - Anime.Value.NomalAnimes.Remove(value); - else if (CurrentMode is GameSave.ModeType.PoorCondition) - Anime.Value.PoorConditionAnimes.Remove(value); - else if (CurrentMode is GameSave.ModeType.Ill) - Anime.Value.IllAnimes.Remove(value); + //if (CurrentMode is GameSave.ModeType.Happy) + // Anime.Value.HappyAnimes.Remove(value); + //else if (CurrentMode is GameSave.ModeType.Nomal) + // Anime.Value.NomalAnimes.Remove(value); + //else if (CurrentMode is GameSave.ModeType.PoorCondition) + // Anime.Value.PoorConditionAnimes.Remove(value); + //else if (CurrentMode is GameSave.ModeType.Ill) + // Anime.Value.IllAnimes.Remove(value); value.Close(); } } @@ -223,18 +257,26 @@ public class FoodAnimeEditWindowVM /// 动画模型 private void RemoveImageCommand_ExecuteEvent(AnimeModel value) { - CurrentImageModel.Value.Close(); - value.Images.Remove(CurrentImageModel.Value); + CurrentFrontImageModel.Value.Close(); + value.Images.Remove(CurrentFrontImageModel.Value); } - #region Player - private void CurrentAnimeModel_ValueChanged(AnimeModel oldValue, AnimeModel newValue) + #region FrontPlayer + private void CurrentAnimeModel_ValueChanged(FoodAnimeModel oldValue, FoodAnimeModel newValue) { StopCommand_ExecuteEvent(); if (oldValue is not null) - oldValue.Images.CollectionChanged -= Images_CollectionChanged; + { + oldValue.FrontImages.CollectionChanged -= Images_CollectionChanged; + oldValue.BackImages.CollectionChanged -= Images_CollectionChanged; + oldValue.FoodLocations.CollectionChanged -= Images_CollectionChanged; + } if (newValue is not null) - newValue.Images.CollectionChanged += Images_CollectionChanged; + { + newValue.FrontImages.CollectionChanged += Images_CollectionChanged; + newValue.BackImages.CollectionChanged += Images_CollectionChanged; + newValue.FoodLocations.CollectionChanged += Images_CollectionChanged; + } } private void Images_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -268,25 +310,60 @@ public class FoodAnimeEditWindowVM return; } _playing = true; - _playerTask.Start(); + _frontPlayerTask.Start(); + _backPlayerTask.Start(); + _foodPlayerTask.Start(); } /// - /// 播放 + /// 顶层播放 /// - private void Play() + private void FrontPlay() { do { - foreach (var model in CurrentAnimeModel.Value.Images) + foreach (var model in CurrentAnimeModel.Value.FrontImages) { - CurrentImageModel.Value = model; + CurrentFrontImageModel.Value = model; + Task.Delay(model.Duration.Value).Wait(); + if (_playing is false) + return; + } + } while (Loop.Value); + } + + /// + /// 底层 + /// + private void BackPlay() + { + do + { + foreach (var model in CurrentAnimeModel.Value.BackImages) + { + CurrentBackImageModel.Value = model; + Task.Delay(model.Duration.Value).Wait(); + if (_playing is false) + return; + } + } while (Loop.Value); + } + + /// + /// 食物 + /// + private void FoodPlay() + { + do + { + foreach (var model in CurrentAnimeModel.Value.FoodLocations) + { + CurrentFoodLocationModel.Value = model; Task.Delay(model.Duration.Value).Wait(); if (_playing is false) return; } } while (Loop.Value); - Reset(); } /// @@ -295,7 +372,9 @@ public class FoodAnimeEditWindowVM private void Reset() { _playing = false; - _playerTask = new(Play); + _frontPlayerTask = new(FrontPlay); + _backPlayerTask = new(BackPlay); + _foodPlayerTask = new(FoodPlay); } #endregion } diff --git a/VPet.ModMaker/Views/ModEdit/AnimeEdit/AnimeEditWindow.xaml b/VPet.ModMaker/Views/ModEdit/AnimeEdit/AnimeEditWindow.xaml index 1f7cd72..8d14c0a 100644 --- a/VPet.ModMaker/Views/ModEdit/AnimeEdit/AnimeEditWindow.xaml +++ b/VPet.ModMaker/Views/ModEdit/AnimeEdit/AnimeEditWindow.xaml @@ -46,18 +46,21 @@ + - - - + + + Header="{ll:Str 添加图片}" /> + Header="{ll:Str 清空图片}" /> + Header="{ll:Str 删除此项}" /> @@ -50,14 +50,17 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -134,38 +216,85 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -194,7 +323,7 @@