This commit is contained in:
Hakoyu 2023-10-06 18:36:49 +08:00
parent ba74693de0
commit 5b602d864d
19 changed files with 661 additions and 253 deletions

View File

@ -2,29 +2,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace VPet.ModMaker.Models;
public class ObservableEnumFlagsVM<T>
/// <summary>
/// 可观察的枚举标签模型
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObservableEnumFlags<T>
where T : Enum
{
/// <summary>
/// 枚举值
/// </summary>
public ObservableValue<T> EnumValue { get; } = new();
/// <summary>
/// 添加枚举命令
/// </summary>
public ObservableCommand<T> AddCommand { get; } = new();
/// <summary>
/// 删除枚举命令
/// </summary>
public ObservableCommand<T> RemoveCommand { get; } = new();
/// <summary>
/// 枚举类型
/// </summary>
public Type EnumType = typeof(T);
/// <summary>
/// 枚举基类
/// </summary>
public Type UnderlyingType { get; } = Enum.GetUnderlyingType(typeof(T));
public ObservableEnumFlagsVM()
public ObservableEnumFlags()
{
if (Attribute.IsDefined(EnumType, typeof(FlagsAttribute)) is false)
throw new Exception("此枚举类型未使用特性 [Flags]");
AddCommand.ExecuteEvent += AddCommand_ExecuteEvent;
RemoveCommand.ExecuteEvent += RemoveCommand_ExecuteEvent;
}
public ObservableEnumFlagsVM(T value)
public ObservableEnumFlags(T value)
: this()
{
EnumValue.Value = value;

View File

@ -13,8 +13,18 @@ using VPet_Simulator.Core;
namespace VPet.ModMaker.Models;
/// <summary>
/// 拓展
/// </summary>
public static class Extensions
{
/// <summary>
///
/// </summary>
/// <param name="source"></param>
/// <param name="value"></param>
/// <param name="comparisonType"></param>
/// <returns></returns>
public static bool Contains(this string source, string value, StringComparison comparisonType)
{
return source.IndexOf(value, comparisonType) >= 0;
@ -25,6 +35,10 @@ public static class Extensions
// return ((FileStream)image.StreamSource).Name;
//}
/// <summary>
/// 关闭流
/// </summary>
/// <param name="source">图像资源</param>
public static void CloseStream(this ImageSource source)
{
if (source is BitmapImage image)
@ -33,6 +47,11 @@ public static class Extensions
}
}
/// <summary>
/// 图像复制
/// </summary>
/// <param name="image">图像</param>
/// <returns>复制的图像</returns>
public static BitmapImage Copy(this BitmapImage image)
{
BitmapImage newImage = new();
@ -54,6 +73,11 @@ public static class Extensions
return newImage;
}
/// <summary>
/// 保存至Png图片
/// </summary>
/// <param name="image">图片资源</param>
/// <param name="path">路径</param>
public static void SaveToPng(this BitmapSource image, string path)
{
if (path.EndsWith(".png") is false)
@ -64,6 +88,15 @@ public static class Extensions
encoder.Save(fs);
}
/// <summary>
/// 尝试添加
/// </summary>
/// <typeparam name="TKey">键类型</typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns>成功为 <see langword="true"/> 失败为 <see langword="false"/></returns>
public static bool TryAdd<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary,
TKey key,
@ -76,6 +109,11 @@ public static class Extensions
return true;
}
/// <summary>
/// 是含有名称的动画
/// </summary>
/// <param name="graphType"></param>
/// <returns></returns>
public static bool IsHasNameAnime(this GraphInfo.GraphType graphType)
{
return AnimeTypeModel.HasNameAnimes.Contains(graphType);

View File

@ -8,10 +8,24 @@ using System.Threading.Tasks;
namespace VPet.ModMaker.Models;
/// <summary>
/// I18n助手
/// </summary>
public class I18nHelper
{
/// <summary>
/// 当前数据
/// </summary>
public static I18nHelper Current { get; set; } = new();
/// <summary>
/// 当前文化名称
/// </summary>
public ObservableValue<string> CultureName { get; } = new();
/// <summary>
/// 文化列表
/// </summary>
public ObservableCollection<string> CultureNames { get; } = new();
public I18nHelper()
@ -42,8 +56,19 @@ public class I18nHelper
}
}
/// <summary>
/// 添加文化事件
/// </summary>
public event CultureEventHandler AddCulture;
/// <summary>
/// 删除文化事件
/// </summary>
public event CultureEventHandler RemoveCulture;
/// <summary>
/// 修改文化事件
/// </summary>
public event ReplaceCultureEventHandler ReplaceCulture;
public delegate void CultureEventHandler(string culture);

View File

@ -7,18 +7,29 @@ using System.Threading.Tasks;
namespace VPet.ModMaker.Models;
/// <summary>
/// I18n模型
/// </summary>
/// <typeparam name="T">类型</typeparam>
public class I18nModel<T>
where T : class, new()
{
/// <summary>
/// 当前I18n数据
/// </summary>
public ObservableValue<T> CurrentI18nData { get; } = new();
/// <summary>
/// 所有I18n数据
/// </summary>
public Dictionary<string, T> I18nDatas { get; } = new();
public I18nModel()
{
I18nHelper.Current.CultureName.ValueChanged += LangChanged;
I18nHelper.Current.AddCulture += AddLang;
I18nHelper.Current.RemoveCulture += RemoveLang;
I18nHelper.Current.ReplaceCulture += ReplaceLang;
I18nHelper.Current.CultureName.ValueChanged += CultureChanged;
I18nHelper.Current.AddCulture += AddCulture;
I18nHelper.Current.RemoveCulture += RemoveCulture;
I18nHelper.Current.ReplaceCulture += ReplaceCulture;
if (I18nHelper.Current.CultureNames.Count == 0)
return;
foreach (var item in I18nHelper.Current.CultureNames)
@ -28,27 +39,45 @@ public class I18nModel<T>
CurrentI18nData.Value = I18nDatas[I18nHelper.Current.CultureName.Value];
}
private void LangChanged(string oldValue, string newValue)
/// <summary>
/// 文化改变
/// </summary>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
private void CultureChanged(string oldValue, string newValue)
{
if (I18nDatas.TryGetValue(newValue, out var result))
CurrentI18nData.Value = result;
}
private void AddLang(string lang)
/// <summary>
/// 添加文化
/// </summary>
/// <param name="culture">文化名称</param>
private void AddCulture(string culture)
{
if (I18nDatas.ContainsKey(lang) is false)
I18nDatas.Add(lang, new());
if (I18nDatas.ContainsKey(culture) is false)
I18nDatas.Add(culture, new());
}
private void RemoveLang(string lang)
/// <summary>
/// 删除文化
/// </summary>
/// <param name="culture">文化名称</param>
private void RemoveCulture(string culture)
{
I18nDatas.Remove(lang);
I18nDatas.Remove(culture);
}
private void ReplaceLang(string oldLang, string newLang)
/// <summary>
/// 替换文化
/// </summary>
/// <param name="oldCulture">旧文化名称</param>
/// <param name="newCulture">新文化名称</param>
private void ReplaceCulture(string oldCulture, string newCulture)
{
var item = I18nDatas[oldLang];
I18nDatas.Remove(oldLang);
I18nDatas.Add(newLang, item);
var item = I18nDatas[oldCulture];
I18nDatas.Remove(oldCulture);
I18nDatas.Add(newCulture, item);
}
}

View File

@ -12,9 +12,19 @@ using VPet_Simulator.Windows.Interface;
namespace VPet.ModMaker.Models;
/// <summary>
/// 模组加载器
/// </summary>
public class ModLoader
{
/// <summary>
/// 名称
/// </summary>
public string Name { get; }
/// <summary>
/// 作者
/// </summary>
public string Author { get; }
/// <summary>
@ -26,193 +36,231 @@ public class ModLoader
/// 上传至Steam的ItemID
/// </summary>
public ulong ItemID { get; }
public string Intro { get; }
public DirectoryInfo ModPath { get; }
public int GameVer { get; }
public int Ver { get; }
public HashSet<string> Tag { get; } = new();
public bool SuccessLoad { get; } = true;
public DateTime CacheDate { get; } = DateTime.MinValue;
public List<PetLoader> Pets { get; } = new();
public List<Food> Foods { get; } = new();
public List<LowText> LowTexts { get; } = new();
public List<ClickText> ClickTexts { get; } = new();
public List<SelectText> SelectTexts { get; } = new();
public Dictionary<string, GraphCore> MultiGraphs { get; } = new();
/// <summary>
/// 简介
/// </summary>
public string Intro { get; }
/// <summary>
/// 模组路径
/// </summary>
public DirectoryInfo ModPath { get; }
/// <summary>
/// 支持的游戏版本
/// </summary>
public int GameVer { get; }
/// <summary>
/// 版本
/// </summary>
public int Ver { get; }
/// <summary>
/// 标签
/// </summary>
public HashSet<string> Tag { get; } = new();
/// <summary>
/// 缓存数据
/// </summary>
public DateTime CacheDate { get; } = DateTime.MinValue;
/// <summary>
/// 宠物列表
/// </summary>
public List<PetLoader> Pets { get; } = new();
/// <summary>
/// 食物列表
/// </summary>
public List<Food> Foods { get; } = new();
/// <summary>
/// 低状态文本列表
/// </summary>
public List<LowText> LowTexts { get; } = new();
/// <summary>
/// 点击文本列表
/// </summary>
public List<ClickText> ClickTexts { get; } = new();
/// <summary>
/// 选择文本列表
/// </summary>
public List<SelectText> SelectTexts { get; } = new();
/// <summary>
/// I18n数据
/// </summary>
public Dictionary<string, I18nModInfoModel> I18nDatas { get; } = new();
/// <summary>
/// 其它I18n数据
/// </summary>
public Dictionary<string, Dictionary<string, string>> OtherI18nDatas { get; } = new();
public ModLoader(DirectoryInfo path)
{
try
ModPath = path;
LpsDocument modlps = new LpsDocument(File.ReadAllText(path.FullName + @"\info.lps"));
Name = modlps.FindLine("vupmod").Info;
Intro = modlps.FindLine("intro").Info;
GameVer = modlps.FindSub("gamever").InfoToInt;
Ver = modlps.FindSub("ver").InfoToInt;
Author = modlps.FindSub("author").Info.Split('[').First();
if (modlps.FindLine("authorid") != null)
AuthorID = modlps.FindLine("authorid").InfoToInt64;
else
AuthorID = 0;
if (modlps.FindLine("itemid") != null)
ItemID = Convert.ToUInt64(modlps.FindLine("itemid").info);
else
ItemID = 0;
CacheDate = modlps.GetDateTime("cachedate", DateTime.MinValue);
//MOD未加载时支持翻译
foreach (var line in modlps.FindAllLine("lang"))
{
ModPath = path;
LpsDocument modlps = new LpsDocument(File.ReadAllText(path.FullName + @"\info.lps"));
Name = modlps.FindLine("vupmod").Info;
Intro = modlps.FindLine("intro").Info;
GameVer = modlps.FindSub("gamever").InfoToInt;
Ver = modlps.FindSub("ver").InfoToInt;
Author = modlps.FindSub("author").Info.Split('[').First();
if (modlps.FindLine("authorid") != null)
AuthorID = modlps.FindLine("authorid").InfoToInt64;
else
AuthorID = 0;
if (modlps.FindLine("itemid") != null)
ItemID = Convert.ToUInt64(modlps.FindLine("itemid").info);
else
ItemID = 0;
CacheDate = modlps.GetDateTime("cachedate", DateTime.MinValue);
//MOD未加载时支持翻译
foreach (var line in modlps.FindAllLine("lang"))
var i18nData = new I18nModInfoModel();
foreach (var sub in line)
{
var i18nData = new I18nModInfoModel();
foreach (var sub in line)
{
if (sub.Name == Name)
i18nData.Name.Value = sub.Info;
else if (sub.Name == Intro)
i18nData.Description.Value = sub.Info;
}
I18nDatas.Add(line.Info, i18nData);
if (sub.Name == Name)
i18nData.Name.Value = sub.Info;
else if (sub.Name == Intro)
i18nData.Description.Value = sub.Info;
}
DirectoryInfo? langDirectory = null;
foreach (DirectoryInfo di in path.EnumerateDirectories())
I18nDatas.Add(line.Info, i18nData);
}
DirectoryInfo? langDirectory = null;
foreach (DirectoryInfo di in path.EnumerateDirectories())
{
switch (di.Name.ToLower())
{
switch (di.Name.ToLower())
{
case "pet":
//宠物模型
Tag.Add("pet");
foreach (FileInfo fi in di.EnumerateFiles("*.lps"))
{
var lps = new LpsDocument(File.ReadAllText(fi.FullName));
if (lps.First().Name.ToLower() == "pet")
{
var name = lps.First().Info;
var pet = new PetLoader(lps, di);
Pets.Add(pet);
// ! : 此方法会导致 LoadImageToStream 无法使用
//var graphCore = new GraphCore(0);
//foreach (var p in pet.path)
// PetLoader.LoadGraph(graphCore, di, p);
//MultiGraphs.Add(pet.Name, graphCore);
}
}
break;
case "food":
Tag.Add("food");
foreach (FileInfo fi in di.EnumerateFiles("*.lps"))
{
var tmp = new LpsDocument(File.ReadAllText(fi.FullName));
foreach (ILine li in tmp)
{
var food = LPSConvert.DeserializeObject<Food>(li);
var imagePath = $"{path.FullName}\\image\\food\\{food.Name}.png";
if (File.Exists(imagePath))
food.Image = imagePath;
Foods.Add(food);
//string tmps = li.Find("name").info;
//mw.Foods.RemoveAll(x => x.Id == tmps);
//mw.Foods.Add(LPSConvert.DeserializeObject<Food>(li));
}
}
break;
case "image":
Tag.Add("image");
break;
case "text":
Tag.Add("text");
foreach (FileInfo fi in di.EnumerateFiles("*.lps"))
{
var tmp = new LpsDocument(File.ReadAllText(fi.FullName));
foreach (ILine li in tmp)
{
switch (li.Name.ToLower())
{
case "lowfoodtext":
LowTexts.Add(LPSConvert.DeserializeObject<LowText>(li));
break;
case "lowdrinktext":
LowTexts.Add(LPSConvert.DeserializeObject<LowText>(li));
break;
case "clicktext":
ClickTexts.Add(LPSConvert.DeserializeObject<ClickText>(li));
break;
case "selecttext":
SelectTexts.Add(
LPSConvert.DeserializeObject<SelectText>(li)
);
break;
}
}
}
break;
case "lang":
Tag.Add("lang");
langDirectory = di;
//foreach (FileInfo fi in di.EnumerateFiles("*.lps"))
//{
// //LocalizeCore.AddCulture(
// // fi.Id.Substring(0, fi.Id.Length - fi.Extension.Length),
// // new LPS_D(File.ReadAllText(fi.FullName))
// //);
//}
//foreach (DirectoryInfo dis in di.EnumerateDirectories())
//{
// foreach (FileInfo fi in dis.EnumerateFiles("*.lps"))
// {
// //LocalizeCore.AddCulture(
// // dis.Id,
// // new LPS_D(File.ReadAllText(fi.FullName))
// //);
// }
//}
//if (mw.Set.Language == "null")
//{
// LocalizeCore.LoadDefaultCulture();
//}
//else
// LocalizeCore.LoadCulture(mw.Set.Language);
break;
}
}
if (langDirectory is null)
return;
foreach (DirectoryInfo dis in langDirectory.EnumerateDirectories())
{
OtherI18nDatas.Add(dis.Name, new());
foreach (FileInfo fi in dis.EnumerateFiles("*.lps"))
{
var lps = new LPS(File.ReadAllText(fi.FullName));
foreach (var item in lps)
case "pet":
//宠物模型
Tag.Add("pet");
foreach (FileInfo fi in di.EnumerateFiles("*.lps"))
{
if (OtherI18nDatas[dis.Name].ContainsKey(item.Name) is false)
OtherI18nDatas[dis.Name].TryAdd(item.Name, item.Info);
var lps = new LpsDocument(File.ReadAllText(fi.FullName));
if (lps.First().Name.ToLower() == "pet")
{
var name = lps.First().Info;
var pet = new PetLoader(lps, di);
Pets.Add(pet);
// ! : 此方法会导致 LoadImageToStream 无法使用
//var graphCore = new GraphCore(0);
//foreach (var p in pet.path)
// PetLoader.LoadGraph(graphCore, di, p);
//MultiGraphs.Add(pet.Name, graphCore);
}
}
break;
case "food":
Tag.Add("food");
foreach (FileInfo fi in di.EnumerateFiles("*.lps"))
{
var tmp = new LpsDocument(File.ReadAllText(fi.FullName));
foreach (ILine li in tmp)
{
var food = LPSConvert.DeserializeObject<Food>(li);
var imagePath = $"{path.FullName}\\image\\food\\{food.Name}.png";
if (File.Exists(imagePath))
food.Image = imagePath;
Foods.Add(food);
//string tmps = li.Find("name").info;
//mw.Foods.RemoveAll(x => x.Id == tmps);
//mw.Foods.Add(LPSConvert.DeserializeObject<Food>(li));
}
}
break;
case "image":
Tag.Add("image");
break;
case "text":
Tag.Add("text");
foreach (FileInfo fi in di.EnumerateFiles("*.lps"))
{
var tmp = new LpsDocument(File.ReadAllText(fi.FullName));
foreach (ILine li in tmp)
{
switch (li.Name.ToLower())
{
case "lowfoodtext":
LowTexts.Add(LPSConvert.DeserializeObject<LowText>(li));
break;
case "lowdrinktext":
LowTexts.Add(LPSConvert.DeserializeObject<LowText>(li));
break;
case "clicktext":
ClickTexts.Add(LPSConvert.DeserializeObject<ClickText>(li));
break;
case "selecttext":
SelectTexts.Add(LPSConvert.DeserializeObject<SelectText>(li));
break;
}
}
}
break;
case "lang":
Tag.Add("lang");
langDirectory = di;
//foreach (FileInfo fi in di.EnumerateFiles("*.lps"))
//{
// //LocalizeCore.AddCulture(
// // fi.Id.Substring(0, fi.Id.Length - fi.Extension.Length),
// // new LPS_D(File.ReadAllText(fi.FullName))
// //);
//}
//foreach (DirectoryInfo dis in di.EnumerateDirectories())
//{
// foreach (FileInfo fi in dis.EnumerateFiles("*.lps"))
// {
// //LocalizeCore.AddCulture(
// // dis.Id,
// // new LPS_D(File.ReadAllText(fi.FullName))
// //);
// }
//}
//if (mw.Set.Language == "null")
//{
// LocalizeCore.LoadDefaultCulture();
//}
//else
// LocalizeCore.LoadCulture(mw.Set.Language);
break;
}
}
if (langDirectory is null)
return;
foreach (DirectoryInfo dis in langDirectory.EnumerateDirectories())
{
OtherI18nDatas.Add(dis.Name, new());
foreach (FileInfo fi in dis.EnumerateFiles("*.lps"))
{
var lps = new LPS(File.ReadAllText(fi.FullName));
foreach (var item in lps)
{
if (OtherI18nDatas[dis.Name].ContainsKey(item.Name) is false)
OtherI18nDatas[dis.Name].TryAdd(item.Name, item.Info);
}
}
}
catch (Exception ex)
{
Tag.Add("该模组已损坏");
SuccessLoad = false;
}
}
public void WriteFile()
{
var lps = new LpsDocument(File.ReadAllText(ModPath.FullName + @"\info.lps"));
lps.FindLine("vupmod").Info = Name;
lps.FindLine("intro").Info = Intro;
lps.FindSub("gamever").InfoToInt = GameVer;
lps.FindSub("ver").InfoToInt = Ver;
lps.FindSub("author").Info = Author;
lps.FindorAddLine("authorid").InfoToInt64 = AuthorID;
lps.FindorAddLine("itemid").info = ItemID.ToString();
File.WriteAllText(ModPath.FullName + @"\info.lps", lps.ToString());
}
//public void WriteFile()
//{
// var lps = new LpsDocument(File.ReadAllText(ModPath.FullName + @"\info.lps"));
// lps.FindLine("vupmod").Info = Name;
// lps.FindLine("intro").Info = Intro;
// lps.FindSub("gamever").InfoToInt = GameVer;
// lps.FindSub("ver").InfoToInt = Ver;
// lps.FindSub("author").Info = Author;
// lps.FindorAddLine("authorid").InfoToInt64 = AuthorID;
// lps.FindorAddLine("itemid").info = ItemID.ToString();
// File.WriteAllText(ModPath.FullName + @"\info.lps", lps.ToString());
//}
}

View File

@ -9,15 +9,30 @@ using System.Windows.Media.Imaging;
namespace VPet.ModMaker.Models;
public class ModMakerHistory
/// <summary>
/// 模组制作历史
/// </summary>
public class ModMakeHistory
{
/// <summary>
/// 图片
/// </summary>
public BitmapImage Image { get; set; }
/// <summary>
/// Id
/// </summary>
[Line(ignoreCase: true)]
public string Id { get; set; }
/// <summary>
/// 路径
/// </summary>
private string _path;
/// <summary>
/// 资源路径
/// </summary>
[Line(ignoreCase: true)]
public string SourcePath
{
@ -31,8 +46,14 @@ public class ModMakerHistory
}
}
/// <summary>
/// 模组信息文件
/// </summary>
public string InfoFile => Path.Combine(SourcePath, "info.lps");
/// <summary>
/// 最后编辑时间
/// </summary>
[Line(ignoreCase: true)]
public DateTime LastTime { get; set; }
}

View File

@ -6,9 +6,23 @@ using System.Threading.Tasks;
namespace VPet.ModMaker.Models;
/// <summary>
/// 模组制作器信息
/// </summary>
public static class ModMakerInfo
{
/// <summary>
/// 基础目录
/// </summary>
public const string BaseDirectory = nameof(ModMaker);
/// <summary>
/// 历史文件
/// </summary>
public const string HistoryFile = $"{BaseDirectory}\\history.lps";
/// <summary>
/// 信息文件
/// </summary>
public const string InfoFile = "info.lps";
}

View File

@ -24,9 +24,9 @@ public class ClickTextModel : I18nModel<I18nClickTextModel>
public ObservableValue<string> Id { get; } = new();
public ObservableValue<string> Working { get; } = new();
public ObservableEnumFlagsVM<ClickText.ModeType> Mode { get; } = new();
public ObservableEnumFlags<ClickText.ModeType> Mode { get; } = new();
public ObservableValue<VPet_Simulator.Core.Main.WorkingState> WorkingState { get; } = new();
public ObservableEnumFlagsVM<ClickText.DayTime> DayTime { get; } = new();
public ObservableEnumFlags<ClickText.DayTime> DayTime { get; } = new();
public ObservableRange<double> Like { get; } = new(0, int.MaxValue);
public ObservableRange<double> Health { get; } = new(0, int.MaxValue);

View File

@ -36,12 +36,12 @@ public class MoveModel
public ObservableValue<int> TriggerTop { get; } = new(100);
public ObservableValue<int> TriggerBottom { get; } = new(100);
public ObservableEnumFlagsVM<GraphHelper.Move.DirectionType> LocateType { get; } =
public ObservableEnumFlags<GraphHelper.Move.DirectionType> LocateType { get; } =
new(GraphHelper.Move.DirectionType.None);
public ObservableEnumFlagsVM<GraphHelper.Move.DirectionType> TriggerType { get; } =
public ObservableEnumFlags<GraphHelper.Move.DirectionType> TriggerType { get; } =
new(GraphHelper.Move.DirectionType.None);
public ObservableEnumFlagsVM<GraphHelper.Move.ModeType> ModeType { get; } =
public ObservableEnumFlags<GraphHelper.Move.ModeType> ModeType { get; } =
new(GraphHelper.Move.ModeType.Nomal);
public MoveModel() { }

View File

@ -28,7 +28,7 @@ public class SelectTextModel : I18nModel<I18nSelectTextModel>
public ObservableValue<string> Id { get; } = new();
public ObservableValue<string> ChooseId { get; } = new();
public ObservableEnumFlagsVM<ClickText.ModeType> Mode { get; } = new();
public ObservableEnumFlags<ClickText.ModeType> Mode { get; } = new();
//public ObservableValue<string> Working { get; } = new();
//public ObservableValue<VPet_Simulator.Core.Main.WorkingState> WorkingState { get; } = new();

View File

@ -2,11 +2,25 @@
namespace VPet.ModMaker.Models;
/// <summary>
/// 可观察的范围
/// </summary>
/// <typeparam name="T">类型</typeparam>
public class ObservableRange<T>
{
/// <summary>
/// 最小值
/// </summary>
public ObservableValue<T> Min { get; } = new();
/// <summary>
/// 最大值
/// </summary>
public ObservableValue<T> Max { get; } = new();
/// <summary>
/// 信息
/// </summary>
public ObservableValue<string> Info { get; } = new();
public ObservableRange()
@ -15,23 +29,32 @@ public class ObservableRange<T>
Max.ValueChanged += ValueChanged;
}
private void ValueChanged(T oldValue, T newValue)
{
Info.Value = $"({Min.Value}, {Max.Value})";
}
public ObservableRange(T min, T max)
: this()
{
SetValue(min, max);
}
private void ValueChanged(T oldValue, T newValue)
{
Info.Value = $"({Min.Value}, {Max.Value})";
}
/// <summary>
/// 设置值
/// </summary>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public void SetValue(T min, T max)
{
Min.Value = min;
Max.Value = max;
}
/// <summary>
/// 复制
/// </summary>
/// <returns></returns>
public ObservableRange<T> Copy()
{
return new(Min.Value, Max.Value);

View File

@ -9,9 +9,19 @@ using System.Windows.Media.Imaging;
namespace VPet.ModMaker.Models;
/// <summary>
/// 工具
/// </summary>
public static class Utils
{
/// <summary>
/// 解码像素宽度
/// </summary>
public const int DecodePixelWidth = 250;
/// <summary>
/// 节码像素高度
/// </summary>
public const int DecodePixelHeight = 250;
public static char[] Separator { get; } = new char[] { '_' };
@ -31,6 +41,11 @@ public static class Utils
// return bitmapImage;
//}
/// <summary>
/// 载入图片至内存流
/// </summary>
/// <param name="imagePath"></param>
/// <returns></returns>
public static BitmapImage LoadImageToMemoryStream(string imagePath)
{
BitmapImage bitmapImage = new();

View File

@ -110,7 +110,7 @@
<Compile Include="Models\ModModel\ImageModel.cs" />
<Compile Include="Models\ModModel\LowTextModel.cs" />
<Compile Include="Models\ModLoader.cs" />
<Compile Include="Models\ModMakerHistory.cs" />
<Compile Include="Models\ModMakeHistory.cs" />
<Compile Include="Models\ModMakerInfo.cs" />
<Compile Include="Models\ModModel\MoveModel.cs" />
<Compile Include="Models\ObservableRange.cs" />

View File

@ -3,6 +3,7 @@ using LinePutScript.Localization.WPF;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
@ -16,27 +17,91 @@ namespace VPet.ModMaker.ViewModels.ModEdit.AnimeEdit;
public class AnimeEditWindowVM
{
/// <summary>
/// 当前宠物
/// </summary>
public PetModel CurrentPet { get; set; }
/// <summary>
/// 旧动画
/// </summary>
public AnimeTypeModel OldAnime { get; set; }
/// <summary>
/// 动画
/// </summary>
public ObservableValue<AnimeTypeModel> Anime { get; } = new(new());
/// <summary>
/// 当前图像模型
/// </summary>
public ObservableValue<ImageModel> CurrentImageModel { get; } = new();
/// <summary>
/// 当前动画模型
/// </summary>
public ObservableValue<AnimeModel> CurrentAnimeModel { get; } = new();
/// <summary>
/// 当前模式
/// </summary>
public GameSave.ModeType CurrentMode { get; set; }
/// <summary>
/// 循环
/// </summary>
public ObservableValue<bool> Loop { get; } = new();
/// <summary>
/// 含有多个状态 参见 <see cref="AnimeTypeModel.HasMultiTypeAnimes"/>
/// </summary>
public ObservableValue<bool> HasMultiType { get; } = new(false);
/// <summary>
/// 含有动画名称 参见 <see cref="AnimeTypeModel.HasNameAnimes"/>
/// </summary>
public ObservableValue<bool> HasAnimeName { get; } = new(false);
#region Command
/// <summary>
/// 播放命令
/// </summary>
public ObservableCommand PlayCommand { get; } = new();
/// <summary>
/// 停止命令
/// </summary>
public ObservableCommand StopCommand { get; } = new();
/// <summary>
/// 添加图片命令
/// </summary>
public ObservableCommand<AnimeModel> AddImageCommand { get; } = new();
/// <summary>
/// 清除图片命令
/// </summary>
public ObservableCommand<AnimeModel> ClearImageCommand { get; } = new();
/// <summary>
/// 删除动画命令
/// </summary>
public ObservableCommand<AnimeModel> RemoveAnimeCommand { get; } = new();
/// <summary>
/// 删除图片命令
/// </summary>
public ObservableCommand<AnimeModel> RemoveImageCommand { get; } = new();
#endregion
/// <summary>
/// 正在播放
/// </summary>
private bool _playing = false;
/// <summary>
/// 动画任务
/// </summary>
private Task _playerTask;
public AnimeEditWindowVM()
@ -55,6 +120,7 @@ public class AnimeEditWindowVM
Anime.ValueChanged += Anime_ValueChanged;
}
#region LoadAnime
private void Anime_ValueChanged(AnimeTypeModel oldValue, AnimeTypeModel newValue)
{
CheckGraphType(newValue);
@ -68,29 +134,11 @@ public class AnimeEditWindowVM
if (AnimeTypeModel.HasNameAnimes.Contains(model.GraphType.Value))
HasAnimeName.Value = true;
}
private void CurrentAnimeModel_ValueChanged(AnimeModel oldValue, AnimeModel newValue)
{
StopCommand_ExecuteEvent();
if (oldValue is not null)
oldValue.Images.CollectionChanged -= Images_CollectionChanged;
if (newValue is not null)
newValue.Images.CollectionChanged += Images_CollectionChanged;
}
private void Images_CollectionChanged(
object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e
)
{
StopCommand_ExecuteEvent();
}
private void RemoveImageCommand_ExecuteEvent(AnimeModel value)
{
CurrentImageModel.Value.Close();
value.Images.Remove(CurrentImageModel.Value);
}
#endregion
/// <summary>
/// 删除动画
/// </summary>
/// <param name="value">动画模型</param>
private void RemoveAnimeCommand_ExecuteEvent(AnimeModel value)
{
@ -110,6 +158,10 @@ public class AnimeEditWindowVM
}
}
/// <summary>
/// 清空图片
/// </summary>
/// <param name="value">动画模型</param>
private void ClearImageCommand_ExecuteEvent(AnimeModel value)
{
if (
@ -121,20 +173,25 @@ public class AnimeEditWindowVM
}
}
/// <summary>
/// 添加图片
/// </summary>
/// <param name="value">动画模型</param>
private void AddImageCommand_ExecuteEvent(AnimeModel value)
{
OpenFileDialog openFileDialog =
new()
{
Title = "选择图片".Translate(),
Filter = $"图片|*.jpg;*.jpeg;*.png;*.bmp".Translate()
};
new() { Title = "选择图片".Translate(), Filter = $"图片|*.png".Translate() };
if (openFileDialog.ShowDialog() is true)
{
value.Images.Add(new(Utils.LoadImageToMemoryStream(openFileDialog.FileName)));
}
}
/// <summary>
/// 添加图片
/// </summary>
/// <param name="model">动画模型</param>
/// <param name="paths">路径</param>
public void AddImages(AnimeModel model, IEnumerable<string> paths)
{
try
@ -160,6 +217,34 @@ public class AnimeEditWindowVM
}
}
/// <summary>
/// 删除图片
/// </summary>
/// <param name="value">动画模型</param>
private void RemoveImageCommand_ExecuteEvent(AnimeModel value)
{
CurrentImageModel.Value.Close();
value.Images.Remove(CurrentImageModel.Value);
}
#region Player
private void CurrentAnimeModel_ValueChanged(AnimeModel oldValue, AnimeModel newValue)
{
StopCommand_ExecuteEvent();
if (oldValue is not null)
oldValue.Images.CollectionChanged -= Images_CollectionChanged;
if (newValue is not null)
newValue.Images.CollectionChanged += Images_CollectionChanged;
}
private void Images_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
StopCommand_ExecuteEvent();
}
/// <summary>
/// 停止播放
/// </summary>
private void StopCommand_ExecuteEvent()
{
if (_playing is false)
@ -167,6 +252,9 @@ public class AnimeEditWindowVM
Reset();
}
/// <summary>
/// 开始播放
/// </summary>
private void PlayCommand_ExecuteEvent()
{
if (_playing)
@ -183,6 +271,9 @@ public class AnimeEditWindowVM
_playerTask.Start();
}
/// <summary>
/// 播放
/// </summary>
private void Play()
{
do
@ -198,9 +289,13 @@ public class AnimeEditWindowVM
Reset();
}
/// <summary>
/// 重置
/// </summary>
private void Reset()
{
_playing = false;
_playerTask = new(Play);
}
#endregion
}

View File

@ -16,16 +16,45 @@ namespace VPet.ModMaker.ViewModels.ModEdit.AnimeEdit;
public class AnimePageVM
{
#region Value
/// <summary>
/// 显示的动画
/// </summary>
public ObservableValue<ObservableCollection<AnimeTypeModel>> ShowAnimes { get; } = new();
/// <summary>
/// 动画
/// </summary>
public ObservableCollection<AnimeTypeModel> Animes => CurrentPet.Value.Animes;
/// <summary>
/// 宠物列表
/// </summary>
public ObservableCollection<PetModel> Pets => ModInfoModel.Current.Pets;
/// <summary>
/// 当前宠物
/// </summary>
public ObservableValue<PetModel> CurrentPet { get; } = new(new());
/// <summary>
/// 搜索
/// </summary>
public ObservableValue<string> Search { get; } = new();
#endregion
#region Command
/// <summary>
/// 添加命令
/// </summary>
public ObservableCommand AddCommand { get; } = new();
/// <summary>
/// 编辑命令
/// </summary>
public ObservableCommand<AnimeTypeModel> EditCommand { get; } = new();
/// <summary>
/// 删除命令
/// </summary>
public ObservableCommand<AnimeTypeModel> RemoveCommand { get; } = new();
#endregion
public AnimePageVM()
@ -58,8 +87,9 @@ public class AnimePageVM
}
}
public void Close() { }
/// <summary>
/// 添加动画
/// </summary>
private void Add()
{
var selectGraphTypeWindow = new SelectGraphTypeWindow();
@ -79,6 +109,10 @@ public class AnimePageVM
Animes.Add(vm.Anime.Value);
}
/// <summary>
/// 编辑动画
/// </summary>
/// <param name="model">动画类型模型</param>
public void Edit(AnimeTypeModel model)
{
var window = new AnimeEditWindow();
@ -100,18 +134,22 @@ public class AnimePageVM
}
}
private void Remove(AnimeTypeModel food)
/// <summary>
/// 删除动画
/// </summary>
/// <param name="model">动画类型模型</param>
private void Remove(AnimeTypeModel model)
{
if (MessageBox.Show("确定删除吗".Translate(), "", MessageBoxButton.YesNo) is MessageBoxResult.No)
return;
if (ShowAnimes.Value.Count == Animes.Count)
{
Animes.Remove(food);
Animes.Remove(model);
}
else
{
ShowAnimes.Value.Remove(food);
Animes.Remove(food);
ShowAnimes.Value.Remove(model);
Animes.Remove(model);
}
}
}

View File

@ -15,8 +15,14 @@ public class ClickTextEditWindowVM
public I18nHelper I18nData => I18nHelper.Current;
#region Value
/// <summary>
/// 旧点击文本
/// </summary>
public ClickTextModel OldClickText { get; set; }
/// <summary>
/// 点击文本
/// </summary>
public ObservableValue<ClickTextModel> ClickText { get; } = new(new());
#endregion
public ClickTextEditWindowVM() { }

View File

@ -15,13 +15,35 @@ namespace VPet.ModMaker.ViewModels.ModEdit.ClickTextEdit;
public class ClickTextPageVM
{
#region Value
/// <summary>
/// 显示的点击文本
/// </summary>
public ObservableValue<ObservableCollection<ClickTextModel>> ShowClickTexts { get; } = new();
/// <summary>
/// 点击文本
/// </summary>
public ObservableCollection<ClickTextModel> ClickTexts => ModInfoModel.Current.ClickTexts;
/// <summary>
/// 搜索
/// </summary>
public ObservableValue<string> Search { get; } = new();
#endregion
#region Command
/// <summary>
/// 添加命令
/// </summary>
public ObservableCommand AddCommand { get; } = new();
/// <summary>
/// 编辑命令
/// </summary>
public ObservableCommand<ClickTextModel> EditCommand { get; } = new();
/// <summary>
/// 删除命令
/// </summary>
public ObservableCommand<ClickTextModel> RemoveCommand { get; } = new();
#endregion
@ -50,6 +72,9 @@ public class ClickTextPageVM
}
}
/// <summary>
/// 添加点击文本
/// </summary>
private void Add()
{
var window = new ClickTextEditWindow();
@ -60,6 +85,10 @@ public class ClickTextPageVM
ClickTexts.Add(vm.ClickText.Value);
}
/// <summary>
/// 编辑点击文本
/// </summary>
/// <param name="model">模型</param>
public void Edit(ClickTextModel model)
{
var window = new ClickTextEditWindow();
@ -80,6 +109,10 @@ public class ClickTextPageVM
}
}
/// <summary>
/// 删除点击文本
/// </summary>
/// <param name="model">模型</param>
private void Remove(ClickTextModel model)
{
if (MessageBox.Show("确定删除吗".Translate(), "", MessageBoxButton.YesNo) is MessageBoxResult.No)

View File

@ -33,12 +33,12 @@ public class ModMakerWindowVM
/// <summary>
/// 显示的历史
/// </summary>
public ObservableValue<ObservableCollection<ModMakerHistory>> ShowHistories { get; } = new();
public ObservableValue<ObservableCollection<ModMakeHistory>> ShowHistories { get; } = new();
/// <summary>
/// 历史
/// </summary>
public ObservableCollection<ModMakerHistory> Histories { get; } = new();
public ObservableCollection<ModMakeHistory> Histories { get; } = new();
#endregion
#region Command
/// <summary>
@ -59,7 +59,7 @@ public class ModMakerWindowVM
/// <summary>
/// 删除历史命令
/// </summary>
public ObservableCommand<ModMakerHistory> RemoveHistoryCommand { get; } = new();
public ObservableCommand<ModMakeHistory> RemoveHistoryCommand { get; } = new();
#endregion
public ModMakerWindowVM(ModMakerWindow window)
@ -83,7 +83,7 @@ public class ModMakerWindowVM
}
#region History
private void RemoveHistory(ModMakerHistory value)
private void RemoveHistory(ModMakeHistory value)
{
Histories.Remove(value);
SaveHistories();
@ -99,7 +99,7 @@ public class ModMakerWindowVM
var lps = new LPS(File.ReadAllText(ModMakerInfo.HistoryFile));
foreach (var line in lps)
{
var history = LPSConvert.DeserializeObject<ModMakerHistory>(line);
var history = LPSConvert.DeserializeObject<ModMakeHistory>(line);
if (Histories.All(h => h.InfoFile != history.InfoFile))
Histories.Add(history);
}
@ -136,7 +136,7 @@ public class ModMakerWindowVM
{
if (
Histories.FirstOrDefault(h => h.SourcePath == modInfo.SourcePath.Value)
is ModMakerHistory history
is ModMakeHistory history
)
{
history.Id = modInfo.Id.Value;
@ -240,23 +240,22 @@ public class ModMakerWindowVM
/// <param name="path">位置</param>
public void LoadMod(string path)
{
ModLoader? loader = null;
try
{
var mod = new ModLoader(new DirectoryInfo(path));
if (mod.SuccessLoad is false)
{
MessageBox.Show("模组载入失败".Translate());
return;
}
var pendingHandler = PendingBox.Show("载入中".Translate());
var modInfo = new ModInfoModel(mod);
EditMod(modInfo);
pendingHandler.Close();
loader = new ModLoader(new DirectoryInfo(path));
}
catch (Exception ex)
{
MessageBox.Show("模组载入失败:\n{0}".Translate(ex));
}
if (loader is not null)
{
var pendingHandler = PendingBox.Show("载入中".Translate());
var modInfo = new ModInfoModel(loader);
EditMod(modInfo);
pendingHandler.Close();
}
}
#endregion
}

View File

@ -43,7 +43,7 @@ public partial class ModMakerWindow : Window
{
if (sender is not ListBoxItem item)
return;
if (item.DataContext is not ModMakerHistory history)
if (item.DataContext is not ModMakeHistory history)
return;
if (Directory.Exists(history.SourcePath) is false)
{