diff --git a/.editorconfig b/.editorconfig
index adcd2ed..5f3f6ee 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -5,3 +5,5 @@ dotnet_diagnostic.CS1591.severity = suggestion
dotnet_diagnostic.CS1573.severity = suggestion
dotnet_diagnostic.CS1570.severity = suggestion
+# CS8632: 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。
+dotnet_diagnostic.CS8632.severity = suggestion
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b31d69a..e8dcbef 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,4 +1,4 @@
-简体中文 | [English](./CONTRIBUTING_en.md)
+简体中文 | [繁體中文](./CONTRIBUTING_zht.md) | [English](./CONTRIBUTING_en.md)
## 参与开发
diff --git a/CONTRIBUTING_en.md b/CONTRIBUTING_en.md
index 820a9b4..996ee24 100644
--- a/CONTRIBUTING_en.md
+++ b/CONTRIBUTING_en.md
@@ -1,6 +1,6 @@
# Contributing
-[简体中文](./CONTRIBUTING.md) | English
+[简体中文](./CONTRIBUTING.md) | [繁體中文](./CONTRIBUTING_zht.md) | English
Welcome to participate in development! To ensure code maintainability and playability, if you wish to develop new features or gameplay, please first contact me (by sending a [mail](mailto:zoujin.dev@exlb.org) or opening an [Issue](https://github.com/LorisYounger/VPet/issues/new)) with your idea. This is to make sure your contribution fits the game, and will not get outright rejected for being unfitting (causing your effort to be wasted). You don't need to contact me regarding fixing errors or bugs - simply send a PR in that case.
@@ -88,4 +88,4 @@ You **must** inform users of the source of our animation files, and provide a li
* All of the above authorization information **must** be disclosed.
* You **must** provide a link to [this page](https://github.com/LorisYounger/VPet).
-* **No** profit should be made with our files.
\ No newline at end of file
+* **No** profit should be made with our files.
diff --git a/CONTRIBUTING_zht.md b/CONTRIBUTING_zht.md
new file mode 100644
index 0000000..7f0db07
--- /dev/null
+++ b/CONTRIBUTING_zht.md
@@ -0,0 +1,89 @@
+[简体中文](./CONTRIBUTING.md) | 繁體中文 | [English](./CONTRIBUTING_en.md)
+
+## 參與開發
+
+歡迎參與虛擬桌寵模擬器的開發!為了保證程式碼的可維護性及遊戲性,若想要開發新的功能,請先[電子郵件聯絡](mailto:zoujin.dev@exlb.org)或提交[Issue](https://github.com/LorisYounger/VPet/issues),標題為想要新增的功能/玩法,以確保該功能/玩法適用於虛擬桌寵模擬器,以免在您完成開發後,因不適合而被拒絕(而浪費您的時間)。
+如果是修正錯誤或BUG,則不需要先行聯絡,修好後直接提交即可。
+
+當您提供的想法被贊同後,您可以使用[Fork](https://github.com/LorisYounger/VPet/fork)功能,將專案程式碼整個複製至個人的Github上,以便撰寫自己的程式碼。撰寫完畢後,使用[Pull Requests](https://github.com/LorisYounger/VPet/compare)提交。
+若您的想法並未被同意,也可以另起爐灶,開發一個不同版本及功能的桌寵軟體。須遵守[Apache License 2.0](https://github.com/LorisYounger/VPet/blob/main/LICENSE)及[動畫版權聲明與授權](https://github.com/LorisYounger/VPet/blob/main/README_zht.md#%E5%8B%95%E7%95%AB%E7%89%88%E6%AC%8A%E8%81%B2%E6%98%8E%E8%88%87%E6%8E%88%E6%AC%8A)。
+註:一般而言,加入新功能都可以透過撰寫模組來達成,詳情請見:[VPet.Plugin.Demo](https://github.com/LorisYounger/VPet.Plugin.Demo)
+
+作者可能會修改、刪減部分您所提交的程式碼,以確保該功能/玩法適用於虛擬桌寵模擬器。
+
+## 動畫版權聲明與授權
+
+在Github中,[桌寵動畫檔案](https://github.com/LorisYounger/VPet/tree/main/VPet-Simulator.Windows/mod/0000_core/pet/vup)之動畫版權歸[虛擬主播模擬器製作組](https://www.exlb.net/VUP-Simulator)所有,在使用本類別庫時,您可能會需要自行準備動畫檔,或遵循下列協定:
+
+### 非商業用途授權
+
+* 需要向使用者告知動畫檔案的來源,並提供造訪[本頁面](https://github.com/LorisYounger/VPet)的連結
+* 當您完成上述要求後,可以免費使用動畫檔案
+
+### 商業用途授權(低於10萬)
+
+* 在使用者第一次使用時,需跳出視窗,並醒目向使用者告知動畫檔案的來源,並提供造訪[本頁面](https://github.com/LorisYounger/VPet)的連結
+* 在對應的頁面上(使用者能快速造訪的),向使用者告知動畫檔案的來源,並提供造訪[本頁面](https://github.com/LorisYounger/VPet)的連結
+* 當您完成上述要求後,可以免費使用動畫檔案
+
+### 商業用途授權(高於10萬或其他)
+
+* 請[電子郵件聯絡](mailto:zoujin.dev@exlb.org)本軟體作者
+
+### 轉發動畫檔案
+
+* 需要告知上述所有授權資訊
+* 需要提供造訪[本頁面](https://github.com/LorisYounger/VPet)的連結
+* 轉發動畫檔案時,禁止任何付費或收費行為
+
+## 桌面應用程式部署方式
+
+1. 下載本專案,透過VisualStudio開啟`VPet.sln`檔案
+2. 在「建置」選項中,選擇位元數`x64`及建置專案`Vpet-Simulator.Windows`
+ ![image-20230208004330895](README.assets/image-20230208004330895.png)
+3. 點擊「開始」,若一切順利將會報錯`缺少Core模組,無法啟動桌寵`
+4. 以管理員身分執行`mklink.bat`,這會讓模組檔案連結至產生的位置
+5. 再次點擊啟動即可正常執行
+
+## 軟體架構
+
+* **VPet-Simulator.Windows: 適用於桌面端的虛擬桌寵模擬器**
+ * *Function 功能性程式碼儲存位置*
+ * CoreMOD 模組管理
+ * MWController 視窗控制器
+
+ * *WinDesign 視窗及UI設計
+ * winBetterBuy 更好買視窗
+ * winCGPTSetting ChatGPT設定
+ * winSetting 軟體設定、模組視窗
+ * winConsole 開發控制台
+ * winGameSetting 遊戲設定
+ * winReport 意見回饋中心
+
+ * MainWindows 主視窗、儲存及展示Core
+ * PetHelper 快速切換圖示
+* **VPet-Simulator.Tool: 方便製作模組的工具(例如:產生動態圖片)**
+* **VPet-Simulator.Core: 軟體核心,方便內建至任何的WPF應用程式(例如:VUP-Simulator)**
+ * Handle 介面及控制項
+ * IController 視窗控制(呼叫相關功能及設定,例如:移動到側邊等)
+ * Function 通用功能
+ * GameCore 遊戲核心,包含各種資料數據等內容
+ * GameSave 遊戲存檔
+ * IFood 食物及物品介面
+ * PetLoader 寵物圖片載入器
+ * Graph 圖形渲染
+ * IGraph 動畫基本介面
+ * GraphCore 動畫顯示核心
+ * GraphHelper 動畫幫助
+ * GraphInfo 動畫資訊
+ * FoodAnimation 食物動畫,支援顯示前中後三層夾心動畫,不一定只用於食物,只是叫這個名字
+ * PNGAnimation 桌寵動態動畫元件
+ * Picture 桌寵靜態動畫元件
+ * Display 顯示
+ * basestyle/Theme 基礎風格主題
+ * Main.xaml 核心顯示元件
+ * MainDisplay 核心顯示方法
+ * MainLogic 核心顯示邏輯
+ * ToolBar 點擊人物時的工具欄
+ * MessageBar 人物說話時的對話框
+ * WorkTimer 運作計時器
diff --git a/README.md b/README.md
index 5c14df4..e10ba54 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# VPet-Simulator
-简体中文 | [English](./README_en.md) | [日本語](./README_ja.md)
+简体中文 | [繁體中文](./README_zht.md) | [English](./README_en.md) | [日本語](./README_ja.md)
虚拟桌宠模拟器 一个开源的桌宠软件, 可以内置到任何WPF应用程序
@@ -49,6 +49,8 @@
该游戏支持创意工坊,您可以制作别的人物桌宠动画或者互动,并上传至创意工坊分享给更多人使用.
+MOD制作器: https://github.com/LorisYounger/VPet.ModMaker
+
创意工坊支持添加/修改以下内容
* 桌宠动画
@@ -163,4 +165,4 @@
![image-20230208004330895](README.assets/image-20230208004330895.png)
3. 点击启动, 如果一切正常则会报错 `缺少模组Core,无法启动桌宠`
4. 以管理员身份运行 `mklink.bat`, 这会让mod文件链接到生成位置
-5. 再次点击启动即可正常运行
\ No newline at end of file
+5. 再次点击启动即可正常运行
diff --git a/README_en.md b/README_en.md
index 260e3a9..dcd0f95 100644
--- a/README_en.md
+++ b/README_en.md
@@ -1,6 +1,6 @@
# VPet
-[简体中文](./README.md) | English | [日本語](./README_ja.md)
+[简体中文](./README.md) | [繁體中文](./README_zht.md) | English | [日本語](./README_ja.md)
![Header](README.assets/%E4%B8%BB%E5%9B%BE.png)
@@ -47,6 +47,8 @@ Feature requests and pull requests are welcome! You can take our code and modify
Steam Workshop mods are supported. With mods, you can add your own pets (animations/interactions), and share them with others through the Workshop.
+MOD Maker: https://github.com/LorisYounger/VPet.ModMaker
+
The following contents can be added or modified by Workshop mods:
* Pet animations
diff --git a/README_ja.md b/README_ja.md
index 5afaf78..4e7fa1a 100644
--- a/README_ja.md
+++ b/README_ja.md
@@ -1,6 +1,6 @@
# VPet
-[简体中文](./README.md) | [English](./README_en.md) | 日本語
+[简体中文](./README.md) | [繁體中文](./README_zht.md) | [English](./README_en.md) | 日本語
![Header](README.assets/%E4%B8%BB%E5%9B%BE.png)
@@ -47,6 +47,8 @@ VPet を [Steam で](https://store.steampowered.com/app/1920960/VPet)無料で
Steam Workshop の MOD に対応しています。MOD を使用すると、独自のペット(アニメーション/インタラクション)を追加したり、Workshop を通じて他の人と共有したりすることができます。
+MOD プロデューサ: https://github.com/LorisYounger/VPet.ModMaker
+
以下のコンテンツは、Workshop の MOD によって追加または変更することができます:
* ペットアニメーション
diff --git a/README_zht.md b/README_zht.md
new file mode 100644
index 0000000..bab1a73
--- /dev/null
+++ b/README_zht.md
@@ -0,0 +1,167 @@
+# VPet-Simulator
+
+简体中文(./README.md) | 繁體中文 | [English](./README_en.md) | [日本語](./README_ja.md)
+
+《虛擬桌寵模擬器》一個開源的桌寵軟體,可以內建至任何WPF應用程式中。
+
+![主圖](README.assets/%E4%B8%BB%E5%9B%BE.png)
+
+在[Steam](https://store.steampowered.com/app/1920960/VPet)上取得虛擬桌寵模擬器(免費),或透過[Nuget](https://www.nuget.org/packages/VPet-Simulator.Core)內建至應用程式中。
+
+## 虛擬桌寵模擬器 - 詳細介紹
+
+虛擬桌寵模擬器是一款桌寵軟體,支援各種互動,例如投餵等。開源、並支援工作坊。
+
+既然都是免費的,那為何不試試呢?
+
+本遊戲從[虛擬主播模擬器](https://store.steampowered.com/app/1352140/_/)之桌寵功能獨立出來,如果喜歡,歡迎將[虛擬主播模擬器](https://store.steampowered.com/app/1352140/_/)加入至您的願望清單中。
+
+### 超多的互動及動畫
+
+多達32(種類)×4(狀態)×3(類型)種動畫。
+*註:部分種類並未有生病或循環等內容,實際上會略低於此數值。*
+
+#### 動畫範例:
+
+##### 摸頭
+
+![ss0](README.assets/ss0.gif)
+
+##### 提起
+
+![ss4](README.assets/ss4.gif)![ss4](README.assets/ss8.gif)
+
+##### 爬牆
+
+![ss7](README.assets/ss7.gif)
+
+### 免費
+
+本遊戲完全免費!既然都不用錢,那就試試看吧!
+本遊戲主要目的為宣傳[虛擬主播模擬器](https://store.steampowered.com/app/1352140/_/)中的Q版人物主角。
+
+### 開源
+
+本遊戲於Github上開源,歡迎提出自己的想法、創意,或直接參與開發!
+您還可以修改原始碼來製作自己專屬的桌寵!(雖然說大部分內容支援工作坊,毋須修改程式碼)
+專案位置:https://github.com/LorisYounger/VPet
+
+### 支援工作坊
+
+本遊戲支援工作坊,您可以自行製作桌寵角色、動畫或互動內容,並上傳至工作坊中分享給更多人使用。
+
+模組製作器:https://github.com/LorisYounger/VPet.ModMaker
+
+工作坊支援加入/修改下列內容
+
+* 桌寵動畫
+* 物品/食品/飲料等
+* 自訂桌寵工作內容
+* 對話內容
+* 主題
+* 程式碼外掛:編撰程式碼來加入新內容
+ * 加入新的動畫邏輯或顯示效果(例如:l2d/spine等)
+ * 加入新功能(鬧鐘、記事本等)
+ * 幾乎無所不能,範例請見[VPet.Plugin.Demo](https://github.com/LorisYounger/VPet.Plugin.Demo)
+
+###聯絡我們
+
+若有建議或意見,可以在Steam商店、Steam社群中評論,在Github發Issue,使用虛擬桌寵模擬器QQ群(907101442)或電子郵件[mailto:service@exlb.net](mailto:service@exlb.net)聯絡作者。
+
+
+## 軟體架構
+
+* **VPet-Simulator.Windows: 適用於桌面端的虛擬桌寵模擬器**
+ * *Function 功能性程式碼儲存位置*
+ * CoreMOD 模組管理
+ * MWController 視窗控制器
+
+ * *WinDesign 視窗及UI設計
+ * winBetterBuy 更好買視窗
+ * winCGPTSetting ChatGPT設定
+ * winSetting 軟體設定、模組視窗
+ * winConsole 開發控制台
+ * winGameSetting 遊戲設定
+ * winReport 意見回饋中心
+
+ * MainWindows 主視窗、儲存及展示Core
+ * PetHelper 快速切換圖示
+* **VPet-Simulator.Tool: 方便製作模組的工具(例如:產生動態圖片)**
+* **VPet-Simulator.Core: 軟體核心,方便內建至任何的WPF應用程式(例如:VUP-Simulator)**
+ * Handle 介面及控制項
+ * IController 視窗控制(呼叫相關功能及設定,例如:移動到側邊等)
+ * Function 通用功能
+ * GameCore 遊戲核心,包含各種資料數據等內容
+ * GameSave 遊戲存檔
+ * IFood 食物及物品介面
+ * PetLoader 寵物圖片載入器
+ * Graph 圖形渲染
+ * IGraph 動畫基本介面
+ * GraphCore 動畫顯示核心
+ * GraphHelper 動畫幫助
+ * GraphInfo 動畫資訊
+ * FoodAnimation 食物動畫,支援顯示前中後三層夾心動畫,不一定只用於食物,只是叫這個名字
+ * PNGAnimation 桌寵動態動畫元件
+ * Picture 桌寵靜態動畫元件
+ * Display 顯示
+ * basestyle/Theme 基礎風格主題
+ * Main.xaml 核心顯示元件
+ * MainDisplay 核心顯示方法
+ * MainLogic 核心顯示邏輯
+ * ToolBar 點擊人物時的工具欄
+ * MessageBar 人物說話時的對話框
+ * WorkTimer 運作計時器
+
+
+## 參與開發
+
+歡迎參與虛擬桌寵模擬器的開發!為了保證程式碼的可維護性及遊戲性,若想要開發新的功能,請先[電子郵件聯絡](mailto:zoujin.dev@exlb.org)或提交[Issue](https://github.com/LorisYounger/VPet/issues),標題為想要新增的功能/玩法,以確保該功能/玩法適用於虛擬桌寵模擬器,以免在您完成開發後,因不適合而被拒絕(而浪費您的時間)。
+如果是修正錯誤或BUG,則不需要先行聯絡,修好後直接提交即可。
+
+當您提供的想法被贊同後,您可以使用[Fork](https://github.com/LorisYounger/VPet/fork)功能,將專案程式碼整個複製至個人的Github上,以便撰寫自己的程式碼。撰寫完畢後,使用[Pull Requests](https://github.com/LorisYounger/VPet/compare)提交。
+若您的想法並未被同意,也可以另起爐灶,開發一個不同版本及功能的桌寵軟體。須遵守[Apache License 2.0](https://github.com/LorisYounger/VPet/blob/main/LICENSE)及[動畫版權聲明與授權](https://github.com/LorisYounger/VPet/blob/main/README_zht.md#%E5%8B%95%E7%95%AB%E7%89%88%E6%AC%8A%E8%81%B2%E6%98%8E%E8%88%87%E6%8E%88%E6%AC%8A)。
+註:一般而言,加入新功能都可以透過撰寫模組來達成,詳情請見:[VPet.Plugin.Demo](https://github.com/LorisYounger/VPet.Plugin.Demo)
+
+作者可能會修改、刪減部分您所提交的程式碼,以確保該功能/玩法適用於虛擬桌寵模擬器。
+
+
+感謝下列參與的開發及翻譯人員
+
+
+
+
+
+以及提供社群翻譯與更多內容的工作坊作者
+
+## 動畫版權聲明與授權
+
+在Github中,[桌寵動畫檔案](https://github.com/LorisYounger/VPet/tree/main/VPet-Simulator.Windows/mod/0000_core/pet/vup)之動畫版權歸[虛擬主播模擬器製作組](https://www.exlb.net/VUP-Simulator)所有,在使用本類別庫時,您可能會需要自行準備動畫檔,或遵循下列協定:
+*註:本聲明只限於桌寵自帶的動畫,其餘經工作坊等方式額外加入的內容並不適用*
+
+### 非商業用途授權
+
+* 需要向使用者告知動畫檔案的來源,並提供造訪[本頁面](https://github.com/LorisYounger/VPet)的連結
+* 當您完成上述要求後,可以免費使用動畫檔案
+
+### 商業用途授權(低於10萬)
+
+* 在使用者第一次使用時,需跳出視窗,並醒目向使用者告知動畫檔案的來源,並提供造訪[本頁面](https://github.com/LorisYounger/VPet)的連結
+* 在對應的頁面上(使用者能快速造訪的),向使用者告知動畫檔案的來源,並提供造訪[本頁面](https://github.com/LorisYounger/VPet)的連結
+* 不得透過出售動畫檔案營利
+* 請[電子郵件聯絡](mailto:zoujin.dev@exlb.org)本軟體作者
+* 當您完成上述要求後,可以免費使用動畫檔案
+
+### 轉發動畫檔案
+
+* 需要告知上述所有授權資訊
+* 需要提供造訪[本頁面](https://github.com/LorisYounger/VPet)的連結
+* 轉發動畫檔案時,禁止任何付費或收費行為
+
+## 桌面應用程式部署方式
+
+1. 下載本專案,透過VisualStudio開啟`VPet.sln`檔案
+2. 在「建置」選項中,選擇位元數`x64`及建置專案`Vpet-Simulator.Windows`
+ ![image-20230208004330895](README.assets/image-20230208004330895.png)
+3. 點擊「開始」,若一切順利將會報錯`缺少Core模組,無法啟動桌寵`
+4. 以管理員身分執行`mklink.bat`,這會讓模組檔案連結至產生的位置
+5. 再次點擊啟動即可正常執行
diff --git a/VPet-Simulator.Core/Display/Theme.xaml b/VPet-Simulator.Core/Display/Theme.xaml
index a3d43df..32e6bd5 100644
--- a/VPet-Simulator.Core/Display/Theme.xaml
+++ b/VPet-Simulator.Core/Display/Theme.xaml
@@ -1,42 +1,43 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
- #90caf9
+ #90caf9
\ No newline at end of file
diff --git a/VPet-Simulator.Core/Display/basestyle.xaml b/VPet-Simulator.Core/Display/basestyle.xaml
index a41cbee..3a42767 100644
--- a/VPet-Simulator.Core/Display/basestyle.xaml
+++ b/VPet-Simulator.Core/Display/basestyle.xaml
@@ -1,305 +1,364 @@
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
+
-
+
-
-
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
+
-
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VPet-Simulator.Core/Handle/Function.cs b/VPet-Simulator.Core/Handle/Function.cs
index 943d1d3..1e72700 100644
--- a/VPet-Simulator.Core/Handle/Function.cs
+++ b/VPet-Simulator.Core/Handle/Function.cs
@@ -5,7 +5,7 @@ using System.Windows.Media;
namespace VPet_Simulator.Core
{
- public static class Function
+ public static partial class Function
{
public static Random Rnd = new Random();
///
diff --git a/VPet-Simulator.Core/VPet-Simulator.Core.csproj b/VPet-Simulator.Core/VPet-Simulator.Core.csproj
index f719fda..502cf1f 100644
--- a/VPet-Simulator.Core/VPet-Simulator.Core.csproj
+++ b/VPet-Simulator.Core/VPet-Simulator.Core.csproj
@@ -23,10 +23,10 @@
-
+
-
-
+
+
diff --git a/VPet-Simulator.Tool/VPet-Simulator.Tool.csproj b/VPet-Simulator.Tool/VPet-Simulator.Tool.csproj
index d6f7f47..34cee4d 100644
--- a/VPet-Simulator.Tool/VPet-Simulator.Tool.csproj
+++ b/VPet-Simulator.Tool/VPet-Simulator.Tool.csproj
@@ -8,6 +8,7 @@
1.0.0.0
VPet-Simulator.Tool
Copyright © 2022
+ preview
diff --git a/VPet-Simulator.Windows.Interface/ExtensionFunction.cs b/VPet-Simulator.Windows.Interface/ExtensionFunction.cs
index 01c1213..29370d0 100644
--- a/VPet-Simulator.Windows.Interface/ExtensionFunction.cs
+++ b/VPet-Simulator.Windows.Interface/ExtensionFunction.cs
@@ -99,7 +99,7 @@ namespace VPet_Simulator.Windows.Interface
}
}
- public static class ExtensionValue
+ public static partial class ExtensionValue
{
///
/// 当前运行目录
diff --git a/VPet-Simulator.Windows.Interface/GameSave_v2.cs b/VPet-Simulator.Windows.Interface/GameSave_v2.cs
index 50d90cf..6d9dff7 100644
--- a/VPet-Simulator.Windows.Interface/GameSave_v2.cs
+++ b/VPet-Simulator.Windows.Interface/GameSave_v2.cs
@@ -1,9 +1,12 @@
using LinePutScript;
using LinePutScript.Dictionary;
+using LinePutScript.Localization.WPF;
+using Panuon.WPF.UI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Security.Cryptography;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
@@ -55,9 +58,28 @@ namespace VPet_Simulator.Windows.Interface
if (nohashcheck)
{
hash = lps.GetInt64("hash");
+ int ver = lps["hash"].GetInt("ver");
if (lps.Remove("hash"))
{
- HashCheck = Sub.GetHashCode(lps.ToString()) == hash;
+ if (ver == 2)
+ HashCheck = Sub.GetHashCode(lps.ToString()) == hash;
+ else
+ {
+ try
+ {
+ using (MD5 md5 = MD5.Create())
+ {
+ HashCheck = BitConverter.ToInt64(md5.ComputeHash(Encoding.UTF8.GetBytes(lps.ToString())), 0) == hash;
+ }
+ if (!HashCheck)
+ HashCheck = Sub.GetHashCode(lps.ToString()) == hash;
+ }
+ catch (Exception e)
+ {
+ HashCheck = false;
+ MessageBoxX.Show(e.ToString(), "当前存档Hash验证信息".Translate() + ":" + "失败".Translate());
+ }
+ }
}
}
@@ -109,16 +131,23 @@ namespace VPet_Simulator.Windows.Interface
if (HashCheck)
{
lps[(gi64)"hash"] = Sub.GetHashCode(lps.ToString());
+ lps["hash"][(gint)"ver"] = 2;
}
else
+ {
lps[(gint)"hash"] = -1;
+ lps["hash"][(gint)"ver"] = 2;
+ }
return lps;
}
+
///
/// Hash检查
///
public bool HashCheck { get; private set; } = true;
+ FInt64 IGetOBJ.this[gflt subName] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+
///
@@ -132,7 +161,7 @@ namespace VPet_Simulator.Windows.Interface
#region GETOBJ
public DateTime this[gdat subName] { get => Data[subName]; set => Data[subName] = value; }
- public double this[gflt subName] { get => Data[subName]; set => Data[subName] = value; }
+ public FInt64 this[gflt subName] { get => Data[subName]; set => Data[subName] = value; }
public double this[gdbe subName] { get => Data[subName]; set => Data[subName] = value; }
public long this[gi64 subName] { get => Data[subName]; set => Data[subName] = value; }
public int this[gint subName] { get => Data[subName]; set => Data[subName] = value; }
@@ -170,12 +199,12 @@ namespace VPet_Simulator.Windows.Interface
Data.SetInt64(subName, value);
}
- public double GetFloat(string subName, double defaultvalue = 0)
+ public FInt64 GetFloat(string subName, FInt64 defaultvalue = default)
{
return Data.GetFloat(subName, defaultvalue);
}
- public void SetFloat(string subName, double value)
+ public void SetFloat(string subName, FInt64 value)
{
Data.SetFloat(subName, value);
}
diff --git a/VPet-Simulator.Windows.Interface/Mod/Food.cs b/VPet-Simulator.Windows.Interface/Mod/Food.cs
index 65c8581..cecf865 100644
--- a/VPet-Simulator.Windows.Interface/Mod/Food.cs
+++ b/VPet-Simulator.Windows.Interface/Mod/Food.cs
@@ -50,13 +50,9 @@ namespace VPet_Simulator.Windows.Interface
///
Drug,
///
- /// 礼品 (没做)
+ /// 礼品
///
Gift,
- /////
- ///// 限定食物优先显示
- /////
- //Limit,
}
///
/// 食物类型
@@ -107,7 +103,6 @@ namespace VPet_Simulator.Windows.Interface
///
[Line(ignoreCase: true)]
public string Desc { get; set; }
- private string desc = null;
private string descs = null;
///
/// 描述(ToBetterBuy)
diff --git a/VPet-Simulator.Windows.Interface/ResourceStyle.xaml b/VPet-Simulator.Windows.Interface/ResourceStyle.xaml
index ae97856..8a3c64c 100644
--- a/VPet-Simulator.Windows.Interface/ResourceStyle.xaml
+++ b/VPet-Simulator.Windows.Interface/ResourceStyle.xaml
@@ -1,212 +1,139 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
+
+
+
+
-
-
-
+
+
+
+
+
+
-
+
+
+
+
+
\ No newline at end of file
diff --git a/VPet-Simulator.Windows.Interface/Setting.cs b/VPet-Simulator.Windows.Interface/Setting.cs
index 68f5b2d..83862b2 100644
--- a/VPet-Simulator.Windows.Interface/Setting.cs
+++ b/VPet-Simulator.Windows.Interface/Setting.cs
@@ -33,19 +33,20 @@ namespace VPet_Simulator.Windows.Interface
allowmove = !this["gameconfig"].GetBool("allowmove");
smartmove = this["gameconfig"].GetBool("smartmove");
enablefunction = !this["gameconfig"].GetBool("nofunction");
- Statistics_OLD = new Statistics(this["statistics"].ToList());
+ //Statistics_OLD = new Statistics(this["statistics"].ToList());
autobuy = this["gameconfig"].GetBool("autobuy");
autogift = this["gameconfig"].GetBool("autogift");
}
- public override string ToString()
- {//留作备份,未来版本删了
- this["statistics"] = new Line("statistics", "", "", Statistics_OLD.ToSubs().ToArray());
- return base.ToString();
- }
- ///
- /// 统计数据信息(旧)
- ///
- public Statistics Statistics_OLD;
+ //public override string ToString()
+ //{//留作备份,未来版本删了
+ // this["statistics"] = new Line("statistics", "", "", Statistics_OLD.ToSubs().ToArray());
+ // return base.ToString();
+ //}
+
+ /////
+ ///// 统计数据信息(旧)
+ /////
+ //public Statistics Statistics_OLD;
//public Size WindowsSize
//{
@@ -87,7 +88,7 @@ namespace VPet_Simulator.Windows.Interface
///
public double VoiceVolume
{
- get => GetFloat("voicevolume", 0.5);
+ get => (double)GetFloat("voicevolume", 0.5);
set => SetFloat("voicevolume", value);
}
///
@@ -275,7 +276,7 @@ namespace VPet_Simulator.Windows.Interface
///
public double PetHelpLeft
{
- get => this["pethelp"].GetFloat("left", 0);
+ get => (double)this["pethelp"].GetFloat("left", 0);
set => this["pethelp"].SetFloat("left", value);
}
///
@@ -283,7 +284,7 @@ namespace VPet_Simulator.Windows.Interface
///
public double PetHelpTop
{
- get => this["pethelp"].GetFloat("top", 0);
+ get => (double)this["pethelp"].GetFloat("top", 0);
set => this["pethelp"].SetFloat("top", value);
}
diff --git a/VPet-Simulator.Windows.Interface/Statistics.cs b/VPet-Simulator.Windows.Interface/Statistics.cs
index a1d0c06..153329f 100644
--- a/VPet-Simulator.Windows.Interface/Statistics.cs
+++ b/VPet-Simulator.Windows.Interface/Statistics.cs
@@ -45,7 +45,7 @@ namespace VPet_Simulator.Windows.Interface
get => GetDateTime((string)subName);
set => SetDateTime((string)subName, value);
}
- public double this[gflt subName]
+ public FInt64 this[gflt subName]
{
get => GetFloat((string)subName);
set => SetFloat((string)subName, value);
@@ -113,9 +113,9 @@ namespace VPet_Simulator.Windows.Interface
public void SetInt64(string subName, long value) => Set(subName, value);
- public double GetFloat(string subName, double defaultvalue = 0) => Find(subName)?.GetFloat() ?? defaultvalue;
+ public FInt64 GetFloat(string subName, FInt64 defaultvalue = default) => Find(subName)?.GetFloat() ?? defaultvalue;
- public void SetFloat(string subName, double value) => Set(subName, value);
+ public void SetFloat(string subName, FInt64 value) => Set(subName, new SetObject(value));
public DateTime GetDateTime(string subName, DateTime defaultvalue = default) => Find(subName)?.GetDateTime() ?? defaultvalue;
diff --git a/VPet-Simulator.Windows.Interface/VPet-Simulator.Windows.Interface.csproj b/VPet-Simulator.Windows.Interface/VPet-Simulator.Windows.Interface.csproj
index e893214..63b2c61 100644
--- a/VPet-Simulator.Windows.Interface/VPet-Simulator.Windows.Interface.csproj
+++ b/VPet-Simulator.Windows.Interface/VPet-Simulator.Windows.Interface.csproj
@@ -7,6 +7,7 @@
true
true
true
+ preview
true
@@ -15,9 +16,9 @@
-
+
-
-
+
+
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/App.xaml b/VPet-Simulator.Windows/App.xaml
index 9d1b4f7..c7f2e01 100644
--- a/VPet-Simulator.Windows/App.xaml
+++ b/VPet-Simulator.Windows/App.xaml
@@ -1,29 +1,31 @@
-
-
+
+
+
+
+
-
-
-
-
- /VPet-Simulator.Windows;component/Res/Font/#OPPOSans R
-
-
- /VPet-Simulator.Windows;component/Res/#remixicon
-
-
-
-
-
-
+
+ /VPet-Simulator.Windows;component/Res/Font/#OPPOSans R
+
+
+ /VPet-Simulator.Windows;component/Res/#remixicon
+
-
+
+
+
+
+
+
diff --git a/VPet-Simulator.Windows/Function/CoreMOD.cs b/VPet-Simulator.Windows/Function/CoreMOD.cs
index 206ee33..2a7fc16 100644
--- a/VPet-Simulator.Windows/Function/CoreMOD.cs
+++ b/VPet-Simulator.Windows/Function/CoreMOD.cs
@@ -9,6 +9,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
+using System.Xml.Linq;
using VPet_Simulator.Core;
using VPet_Simulator.Windows.Interface;
@@ -104,6 +105,13 @@ namespace VPet_Simulator.Windows
LocalizeCore.AddCulture(line.info, ls);
}
+ if (mw.CoreMODs.FirstOrDefault(x => x.Name == Name) != null)
+ {
+ Name += $"({"MOD名称重复".Translate()})";
+ ErrorMessage = "MOD名称重复".Translate();
+ return;
+ }
+
if (!IsOnMOD(mw))
{
Tag.Add("该模组已停用");
@@ -155,6 +163,8 @@ namespace VPet_Simulator.Windows
var tmp = new LpsDocument(File.ReadAllText(fi.FullName));
foreach (ILine li in tmp)
{
+ if (li.Name != "food")
+ continue;
string tmps = li.Find("name").info;
mw.Foods.RemoveAll(x => x.Name == tmps);
mw.Foods.Add(LPSConvert.DeserializeObject(li));
@@ -313,7 +323,11 @@ namespace VPet_Simulator.Windows
}
catch
{
- Process.Start("explorer.exe", url);
+ ProcessStartInfo startInfo = new ProcessStartInfo();
+ startInfo.FileName = "explorer.exe";
+ startInfo.UseShellExecute = false;
+ startInfo.Arguments = url;
+ Process.Start(startInfo);
}
}
diff --git a/VPet-Simulator.Windows/MainWindow.cs b/VPet-Simulator.Windows/MainWindow.cs
index 96ad353..c44223a 100644
--- a/VPet-Simulator.Windows/MainWindow.cs
+++ b/VPet-Simulator.Windows/MainWindow.cs
@@ -35,6 +35,8 @@ using Line = LinePutScript.Line;
using static VPet_Simulator.Windows.Interface.ExtensionFunction;
using Image = System.Windows.Controls.Image;
+using VPet.Solution;
+
namespace VPet_Simulator.Windows
{
public partial class MainWindow : IMainWindow
@@ -316,17 +318,37 @@ namespace VPet_Simulator.Windows
petHelper.Show();
}
- public static void RunDIY(string content)
+ public void RunDIY(string content)
{
- if (content.Contains("://") || content.Contains(@":\"))
+ if (content.Contains(@":\"))
{
try
{
- Process.Start(content);
+ if (!Set["v"][(gbol)"rundiy"])
+ {
+ MessageBoxX.Show("由于操作系统的设计,通过我们软件启动的程序可能会在任务管理器中归类为我们软件的子进程,这可能导致CPU/内存占用显示较高".Translate(),
+ "关于CPU/内存占用显示较高的一次性提示".Translate());
+ Set["v"][(gbol)"rundiy"] = true;
+ }
+ ProcessStartInfo startInfo = new ProcessStartInfo();
+ startInfo.FileName = content;
+ startInfo.UseShellExecute = false;
+ Process.Start(startInfo);
}
catch (Exception e)
{
- MessageBox.Show("快捷键运行失败:无法运行指定内容".Translate() + '\n' + e.Message);
+ MessageBoxX.Show("快捷键运行失败:无法运行指定内容".Translate() + '\n' + e.Message);
+ }
+ }
+ else if (content.Contains("://"))
+ {
+ try
+ {
+ ExtensionSetting.StartURL(content);
+ }
+ catch (Exception e)
+ {
+ MessageBoxX.Show("快捷键运行失败:无法运行指定内容".Translate() + '\n' + e.Message);
}
}
else
@@ -337,7 +359,7 @@ namespace VPet_Simulator.Windows
}
catch (Exception e)
{
- MessageBox.Show("快捷键运行失败:无法运行指定内容".Translate() + '\n' + e.Message);
+ MessageBoxX.Show("快捷键运行失败:无法运行指定内容".Translate() + '\n' + e.Message);
}
}
}
@@ -615,7 +637,7 @@ namespace VPet_Simulator.Windows
{
var stat = GameSavesData.Statistics;
var save = Core.Save;
- stat["stat_money"] = save.Money;
+ stat["stat_money"] = (SetObject)save.Money;
stat["stat_level"] = save.Level;
stat["stat_likability"] = save.Likability;
@@ -690,7 +712,7 @@ namespace VPet_Simulator.Windows
else
data.Add(new Line(item.Name, item.Info));
}
- tmp = new GameSave_v2(lps, Set.Statistics_OLD, olddata: data);
+ tmp = new GameSave_v2(lps, null, olddata: data);
}
if (tmp.GameSave == null)
return false;
@@ -1205,6 +1227,7 @@ namespace VPet_Simulator.Windows
public async void GameLoad(List Path)
{
+ Path = Path.Distinct().ToList();
await Dispatcher.InvokeAsync(new Action(() => LoadingText.Content = "Loading MOD"));
//加载mod
foreach (DirectoryInfo di in Path)
@@ -1402,12 +1425,16 @@ namespace VPet_Simulator.Windows
await Dispatcher.InvokeAsync(new Action(() => LoadingText.Content = "尝试加载游戏动画".Translate()));
await Dispatcher.InvokeAsync(new Action(() =>
{
- LoadingText.Content = "尝试加载动画和生成缓存".Translate();
+ LoadingText.Content = "尝试加载动画和生成缓存\n该步骤可能会耗时比较长\n请耐心等待".Translate();
Core.Graph = petloader.Graph(Set.Resolution);
Main = new Main(Core);
Main.NoFunctionMOD = Set.CalFunState;
+
+ LoadingText.Content = "正在加载游戏".Translate();
+
+
//加载数据合理化:工作
if (!Set["gameconfig"].GetBool("noAutoCal"))
{
@@ -1436,7 +1463,6 @@ namespace VPet_Simulator.Windows
}
- LoadingText.Content = "正在加载游戏".Translate();
var m = new System.Windows.Controls.MenuItem()
{
Header = "MOD管理".Translate(),
@@ -1712,11 +1738,38 @@ namespace VPet_Simulator.Windows
Thread.Sleep(2000);
Set["SingleTips"].SetBool("helloworld", true);
NoticeBox.Show("欢迎使用虚拟桌宠模拟器!\n如果遇到桌宠爬不见了,可以在我这里设置居中或退出桌宠".Translate(),
- "你好".Translate() + (IsSteamUser ? SteamClient.Name : Environment.UserName));
+ "你好".Translate() + (IsSteamUser ? SteamClient.Name : Environment.UserName), Panuon.WPF.UI.MessageBoxIcon.Info, true, 5000);
//Thread.Sleep(2000);
//Main.SayRnd("欢迎使用虚拟桌宠模拟器\n这是个中期的测试版,若有bug请多多包涵\n欢迎加群虚拟主播模拟器430081239或在菜单栏-管理-反馈中提交bug或建议".Translate());
});
}
+ if (Set["v"][(gint)"rank"] != DateTime.Now.Year && GameSavesData.Statistics[(gint)"stat_total_time"] > 3600)
+ {//年度报告提醒
+ Task.Run(() =>
+ {
+ Thread.Sleep(120000);
+ Set["v"][(gint)"rank"] = DateTime.Now.Year;
+ Dispatcher.Invoke(() =>
+ {
+ var button = new System.Windows.Controls.Button()
+ {
+ Content = "点击前往查看".Translate(),
+ FontSize = 20,
+ HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
+ Background = Function.ResourcesBrush(Function.BrushType.Primary),
+ Foreground = Function.ResourcesBrush(Function.BrushType.PrimaryText),
+ };
+ button.Click += (x, y) =>
+ {
+ var panelWindow = new winCharacterPanel(this);
+ panelWindow.MainTab.SelectedIndex = 2;
+ panelWindow.Show();
+ };
+ Main.MsgBar.MessageBoxContent.Children.Add(button);
+ });
+ Main.Say("哼哼~主人,我的考试成绩出炉了哦,快来和我一起看我的成绩单喵".Translate(), "shining");
+ });
+ }
#if DEMO
else
{
diff --git a/VPet-Simulator.Windows/MainWindow.xaml.cs b/VPet-Simulator.Windows/MainWindow.xaml.cs
index afc6244..7743a00 100644
--- a/VPet-Simulator.Windows/MainWindow.xaml.cs
+++ b/VPet-Simulator.Windows/MainWindow.xaml.cs
@@ -20,6 +20,7 @@ using Line = LinePutScript.Line;
using static VPet_Simulator.Core.GraphInfo;
using System.Globalization;
using LinePutScript.Dictionary;
+using Steamworks.Data;
namespace VPet_Simulator.Windows
{
@@ -111,6 +112,8 @@ namespace VPet_Simulator.Windows
{
if (IsSteamUser)//如果是steam用户,尝试加载workshop
{
+ //Leaderboard? leaderboard = await SteamUserStats.FindLeaderboardAsync("chatgpt_auth");
+ //leaderboard?.ReplaceScore(Function.Rnd.Next());
var workshop = new Line_D("workshop");
await Dispatcher.InvokeAsync(new Action(() =>
{
@@ -126,7 +129,7 @@ namespace VPet_Simulator.Windows
int i = 1;
while (true)
{
- var page = await Steamworks.Ugc.Query.ItemsReadyToUse.GetPageAsync(i++);
+ var page = await Steamworks.Ugc.Query.ItemsReadyToUse.GetPageAsync(i++);
if (page.HasValue && page.Value.ResultCount != 0)
{
foreach (Steamworks.Ugc.Item entry in page.Value.Entries)
@@ -260,7 +263,7 @@ namespace VPet_Simulator.Windows
if (App.MainWindows.Count <= 1)
{
try
- {
+ {
if (Core != null && Core.Graph != null)
{
foreach (var igs in Core.Graph.GraphsList.Values)
diff --git a/VPet-Simulator.Windows/Res/img/r_autobuy_1.png b/VPet-Simulator.Windows/Res/img/r_autobuy_1.png
new file mode 100644
index 0000000..7116d85
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_autobuy_1.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_autobuy_2.png b/VPet-Simulator.Windows/Res/img/r_autobuy_2.png
new file mode 100644
index 0000000..b001c41
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_autobuy_2.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_autobuy_3.png b/VPet-Simulator.Windows/Res/img/r_autobuy_3.png
new file mode 100644
index 0000000..a26da03
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_autobuy_3.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_autobuy_4.png b/VPet-Simulator.Windows/Res/img/r_autobuy_4.png
new file mode 100644
index 0000000..94b473c
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_autobuy_4.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_level_1.png b/VPet-Simulator.Windows/Res/img/r_level_1.png
new file mode 100644
index 0000000..e9cdf5c
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_level_1.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_level_2.png b/VPet-Simulator.Windows/Res/img/r_level_2.png
new file mode 100644
index 0000000..4801fa1
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_level_2.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_level_3.png b/VPet-Simulator.Windows/Res/img/r_level_3.png
new file mode 100644
index 0000000..5a9cc96
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_level_3.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_level_4.png b/VPet-Simulator.Windows/Res/img/r_level_4.png
new file mode 100644
index 0000000..29bce4a
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_level_4.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_level_5.png b/VPet-Simulator.Windows/Res/img/r_level_5.png
new file mode 100644
index 0000000..e5e7f29
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_level_5.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mod_1.png b/VPet-Simulator.Windows/Res/img/r_mod_1.png
new file mode 100644
index 0000000..c733232
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mod_1.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mod_2.png b/VPet-Simulator.Windows/Res/img/r_mod_2.png
new file mode 100644
index 0000000..7e647a3
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mod_2.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mod_3.png b/VPet-Simulator.Windows/Res/img/r_mod_3.png
new file mode 100644
index 0000000..4a80ab3
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mod_3.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mostfood_Drink.png b/VPet-Simulator.Windows/Res/img/r_mostfood_Drink.png
new file mode 100644
index 0000000..0d53178
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mostfood_Drink.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mostfood_Drug.png b/VPet-Simulator.Windows/Res/img/r_mostfood_Drug.png
new file mode 100644
index 0000000..7bf6e27
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mostfood_Drug.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mostfood_Food.png b/VPet-Simulator.Windows/Res/img/r_mostfood_Food.png
new file mode 100644
index 0000000..9ef12b5
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mostfood_Food.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mostfood_Functional.png b/VPet-Simulator.Windows/Res/img/r_mostfood_Functional.png
new file mode 100644
index 0000000..9ef12b5
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mostfood_Functional.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mostfood_Gift.png b/VPet-Simulator.Windows/Res/img/r_mostfood_Gift.png
new file mode 100644
index 0000000..433575e
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mostfood_Gift.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mostfood_Meal.png b/VPet-Simulator.Windows/Res/img/r_mostfood_Meal.png
new file mode 100644
index 0000000..353ae28
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mostfood_Meal.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_mostfood_Snack.png b/VPet-Simulator.Windows/Res/img/r_mostfood_Snack.png
new file mode 100644
index 0000000..9ba3de1
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_mostfood_Snack.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singleexp_1.png b/VPet-Simulator.Windows/Res/img/r_singleexp_1.png
new file mode 100644
index 0000000..ce7aa67
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singleexp_1.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singleexp_2.png b/VPet-Simulator.Windows/Res/img/r_singleexp_2.png
new file mode 100644
index 0000000..5bb6303
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singleexp_2.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singleexp_3.png b/VPet-Simulator.Windows/Res/img/r_singleexp_3.png
new file mode 100644
index 0000000..6e2f1ce
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singleexp_3.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singleexp_4.png b/VPet-Simulator.Windows/Res/img/r_singleexp_4.png
new file mode 100644
index 0000000..ae38407
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singleexp_4.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singleexp_5.png b/VPet-Simulator.Windows/Res/img/r_singleexp_5.png
new file mode 100644
index 0000000..dc46953
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singleexp_5.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singlemoney_1.png b/VPet-Simulator.Windows/Res/img/r_singlemoney_1.png
new file mode 100644
index 0000000..1f7366b
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singlemoney_1.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singlemoney_2.png b/VPet-Simulator.Windows/Res/img/r_singlemoney_2.png
new file mode 100644
index 0000000..50d91ec
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singlemoney_2.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singlemoney_3.png b/VPet-Simulator.Windows/Res/img/r_singlemoney_3.png
new file mode 100644
index 0000000..5c30f24
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singlemoney_3.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_singlemoney_4.png b/VPet-Simulator.Windows/Res/img/r_singlemoney_4.png
new file mode 100644
index 0000000..bbe5d86
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_singlemoney_4.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_timelength_1.png b/VPet-Simulator.Windows/Res/img/r_timelength_1.png
new file mode 100644
index 0000000..fc862ab
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_timelength_1.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_timelength_2.png b/VPet-Simulator.Windows/Res/img/r_timelength_2.png
new file mode 100644
index 0000000..2d34bdf
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_timelength_2.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_timelength_3.png b/VPet-Simulator.Windows/Res/img/r_timelength_3.png
new file mode 100644
index 0000000..5b8a461
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_timelength_3.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_timelength_4.png b/VPet-Simulator.Windows/Res/img/r_timelength_4.png
new file mode 100644
index 0000000..5c75a0e
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_timelength_4.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_timelength_5.png b/VPet-Simulator.Windows/Res/img/r_timelength_5.png
new file mode 100644
index 0000000..a9118a0
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_timelength_5.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_worktime_1.png b/VPet-Simulator.Windows/Res/img/r_worktime_1.png
new file mode 100644
index 0000000..ff82950
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_worktime_1.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_worktime_2.png b/VPet-Simulator.Windows/Res/img/r_worktime_2.png
new file mode 100644
index 0000000..1496830
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_worktime_2.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_worktime_3.png b/VPet-Simulator.Windows/Res/img/r_worktime_3.png
new file mode 100644
index 0000000..df9056a
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_worktime_3.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_worktime_4.png b/VPet-Simulator.Windows/Res/img/r_worktime_4.png
new file mode 100644
index 0000000..cd30e00
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_worktime_4.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_worktime_5.png b/VPet-Simulator.Windows/Res/img/r_worktime_5.png
new file mode 100644
index 0000000..b978651
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_worktime_5.png differ
diff --git a/VPet-Simulator.Windows/Res/img/r_worktime_6.png b/VPet-Simulator.Windows/Res/img/r_worktime_6.png
new file mode 100644
index 0000000..493bcff
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/r_worktime_6.png differ
diff --git a/VPet-Simulator.Windows/Res/img/rank1.png b/VPet-Simulator.Windows/Res/img/rank1.png
new file mode 100644
index 0000000..54a5147
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/rank1.png differ
diff --git a/VPet-Simulator.Windows/Res/img/rank2.png b/VPet-Simulator.Windows/Res/img/rank2.png
new file mode 100644
index 0000000..2ad2cc9
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/rank2.png differ
diff --git a/VPet-Simulator.Windows/Res/img/rank3.png b/VPet-Simulator.Windows/Res/img/rank3.png
new file mode 100644
index 0000000..468b7d5
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/rank3.png differ
diff --git a/VPet-Simulator.Windows/Res/img/rbgb.png b/VPet-Simulator.Windows/Res/img/rbgb.png
new file mode 100644
index 0000000..3e13010
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/rbgb.png differ
diff --git a/VPet-Simulator.Windows/Res/img/rbgt.png b/VPet-Simulator.Windows/Res/img/rbgt.png
new file mode 100644
index 0000000..0fd0254
Binary files /dev/null and b/VPet-Simulator.Windows/Res/img/rbgt.png differ
diff --git a/VPet-Simulator.Windows/VPet-Simulator.Windows.csproj b/VPet-Simulator.Windows/VPet-Simulator.Windows.csproj
index d344df3..851ce93 100644
--- a/VPet-Simulator.Windows/VPet-Simulator.Windows.csproj
+++ b/VPet-Simulator.Windows/VPet-Simulator.Windows.csproj
@@ -65,17 +65,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -132,6 +185,50 @@
false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -140,10 +237,10 @@
-
+
-
-
+
+
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/WinDesign/winBetterBuy.xaml b/VPet-Simulator.Windows/WinDesign/winBetterBuy.xaml
index 89bf127..9566a2e 100644
--- a/VPet-Simulator.Windows/WinDesign/winBetterBuy.xaml
+++ b/VPet-Simulator.Windows/WinDesign/winBetterBuy.xaml
@@ -319,8 +319,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet-Simulator.Windows/WinDesign/winBetterBuy.xaml.cs b/VPet-Simulator.Windows/WinDesign/winBetterBuy.xaml.cs
index 9ba5e50..23846d5 100644
--- a/VPet-Simulator.Windows/WinDesign/winBetterBuy.xaml.cs
+++ b/VPet-Simulator.Windows/WinDesign/winBetterBuy.xaml.cs
@@ -353,7 +353,10 @@ namespace VPet_Simulator.Windows
private void pagination_CurrentPageChanged(object sender, SelectedValueChangedRoutedEventArgs e)
{
+ if (!AllowChange)
+ return;
Search();
+ TbPage.Text = e.NewValue.ToString();
}
private void rMoney_Loaded(object sender, RoutedEventArgs e)
@@ -408,5 +411,14 @@ namespace VPet_Simulator.Windows
((Button)sender).Content = "更好买".Translate() + mw.PrefixSave;
;
}
+
+ private void TbPage_PreviewKeyDown(object sender, KeyEventArgs e)
+ {
+ if(e.Key == Key.Enter
+ && int.TryParse(TbPage.Text?.Trim(), out int page))
+ {
+ pagination.CurrentPage = Math.Max(0, Math.Min(pagination.MaxPage, page));
+ }
+ }
}
}
diff --git a/VPet-Simulator.Windows/WinDesign/winCharacterPanel.xaml b/VPet-Simulator.Windows/WinDesign/winCharacterPanel.xaml
index f63c7fe..148ef94 100644
--- a/VPet-Simulator.Windows/WinDesign/winCharacterPanel.xaml
+++ b/VPet-Simulator.Windows/WinDesign/winCharacterPanel.xaml
@@ -45,16 +45,16 @@
+
+ IsReadOnly="True" Width="300">
+ IsReadOnly="True" Width="160" />
@@ -199,6 +199,254 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @ ()
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/WinDesign/winCharacterPanel.xaml.cs b/VPet-Simulator.Windows/WinDesign/winCharacterPanel.xaml.cs
index 0a0c802..300fe90 100644
--- a/VPet-Simulator.Windows/WinDesign/winCharacterPanel.xaml.cs
+++ b/VPet-Simulator.Windows/WinDesign/winCharacterPanel.xaml.cs
@@ -1,13 +1,24 @@
-using LinePutScript.Localization.WPF;
+using LinePutScript;
+using LinePutScript.Localization.WPF;
+using Microsoft.Win32;
using Panuon.WPF.UI;
+using Steamworks;
+using Steamworks.Data;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
using System.Linq;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
+using System.Windows.Media.Effects;
+using System.Windows.Media.Imaging;
+using System.Xml.Linq;
+using VPet_Simulator.Windows.Interface;
namespace VPet_Simulator.Windows
{
@@ -30,19 +41,29 @@ namespace VPet_Simulator.Windows
}
DataGridStatic.ItemsSource = StatList;
mw.GameSavesData.Statistics.StatisticChanged += Statistics_StatisticChanged;
+
+ if (mw.GameSavesData.HashCheck && mw.GameSavesData.GameSave.Exp < int.MaxValue && mw.GameSavesData.GameSave.Money < int.MaxValue)
+ {
+ cb_NoCheat.IsEnabled = true;
+ if (mw.IsSteamUser)
+ cb_AgreeUpload.IsEnabled = true;
+ }
}
- private void Statistics_StatisticChanged(Interface.Statistics sender, string name, LinePutScript.SetObject value)
+ private void Statistics_StatisticChanged(Interface.Statistics sender, string name, SetObject value)
{
- var v = StatList.FirstOrDefault(x => x.StatId == name);
- if (v != null)
+ Dispatcher.Invoke(() =>
{
- v.StatCount = value.GetDouble();
- }
- else
- {
- StatList.Add(new StatInfo(name, value.GetDouble()));
- }
+ var v = StatList.FirstOrDefault(x => x.StatId == name);
+ if (v != null)
+ {
+ v.StatCount = value.GetDouble();
+ }
+ else
+ {
+ StatList.Add(new StatInfo(name, value.GetDouble()));
+ }
+ });
}
private ObservableCollection StatList { get; set; } = new();
@@ -206,5 +227,503 @@ namespace VPet_Simulator.Windows
mw.GameSavesData.Statistics.StatisticChanged -= Statistics_StatisticChanged;
mw.Windows.Remove(this);
}
+
+ private void btn_r_save_Click(object sender, RoutedEventArgs e)
+ {
+ SaveFileDialog saveFileDialog = new SaveFileDialog()
+ {
+ FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "VPet_Rank.png"),
+ Filter = "PNG Image File|*.png"
+ };
+ if (saveFileDialog.ShowDialog() != true)
+ return;
+ r_viewbox.ScrollToTop();
+ FrameworkElement outputbox;
+ if (r_output.ActualWidth > r_output_base.ActualWidth)
+ outputbox = r_output;
+ else
+ outputbox = r_output_base;
+ RenderTargetBitmap image = new RenderTargetBitmap((int)outputbox.ActualWidth, (int)outputbox.ActualHeight, 96, 96, PixelFormats.Pbgra32);
+ image.Render(outputbox);
+ var path = saveFileDialog.FileName;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ BitmapEncoder encoder = new PngBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(image));
+ encoder.Save(ms);
+ File.WriteAllBytes(path, ms.ToArray());
+ if (mw.IsSteamUser && cb_AgreeUpload.IsChecked == true)
+ SteamScreenshots.AddScreenshot(path, null, image.PixelWidth, image.PixelHeight);
+
+ Process.Start(path);
+ }
+ }
+
+ private void cb_AgreeUpload_Checked(object sender, RoutedEventArgs e)
+ {
+ cb_NoCheat.IsChecked = true;
+ }
+
+ private void cb_NoCheat_Unchecked(object sender, RoutedEventArgs e)
+ {
+ cb_AgreeUpload.IsChecked = false;
+ }
+
+ private void btn_r_genRank_Click(object sender, RoutedEventArgs e)
+ {
+ btn_r_genRank.IsEnabled = false;
+ pb_r_genRank.Value = 0;
+ pb_r_genRank.Visibility = Visibility.Visible;
+ Task.Run(GenRank);
+ }
+ private async void GenRank()
+ {
+ mw.Set["v"][(gint)"rank"] = DateTime.Now.Year;
+ bool useranking = mw.IsSteamUser && await Dispatcher.InvokeAsync(() => cb_AgreeUpload.IsChecked == true);
+
+ string petname = mw.GameSavesData.GameSave.Name;
+ string username = mw.IsSteamUser ? SteamClient.Name : Environment.UserName;
+
+ int timelength = mw.GameSavesData.Statistics[(gint)"stat_total_time"];
+ double timelength_h = (timelength / 3600.0);
+ double startdatelength = (DateTime.Now - mw.GameSavesData[(gdat)"birthday"]).TotalDays;
+ double startlengthrank = 0;
+ if (useranking)
+ {
+ Leaderboard? leaderboard = await SteamUserStats.FindOrCreateLeaderboardAsync("stat_total_time", LeaderboardSort.Descending, LeaderboardDisplay.Numeric);
+ var result = await leaderboard?.ReplaceScore(timelength);
+ var length = leaderboard?.EntryCount ?? 1.0;
+ startlengthrank = 1 - ((result?.NewGlobalRank - 1) ?? length) / length;
+ }
+ string startlengthranktext;
+ if (startlengthrank < 0.5)
+ startlengthranktext = '"' + "主人~多陪陪我~".Translate() + '"';
+ else
+ startlengthranktext = '"' + "主人~感谢陪伴~".Translate() + '"';
+
+ double timelengthph = timelength_h / startdatelength;
+ string timelengthphtext;
+ string timelengthtext;
+ int timelength_i;
+ if (timelengthph < 2)
+ {
+ timelengthphtext = "同学".Translate();
+ timelengthtext = '"' + "学长~前辈~".Translate() + '"';
+ timelength_i = 1;
+ }
+ else if (timelengthph < 4)
+ {
+ timelengthphtext = "朋友".Translate();
+ timelengthtext = '"' + "兄弟!".Translate() + '"';
+ timelength_i = 2;
+ }
+ else if (timelengthph < 7)
+ {
+ timelengthphtext = "挚友".Translate();
+ timelengthtext = '"' + "不求同年同月同日生,但求同年同月同日打开《虚拟桌宠模拟器》".Translate() + '"';
+ timelength_i = 3;
+ }
+ else if (timelengthph < 10)
+ {
+ timelengthphtext = "家人".Translate();
+ timelengthtext = '"' + "We are 伐木累~".Translate() + '"';
+ timelength_i = 4;
+ }
+ else
+ {
+ timelengthphtext = "女鹅".Translate();
+ timelengthtext = '"' + "爸妈~ 这么叫好像不太好".Translate() + '"';
+ timelength_i = 5;
+ }
+
+ await Dispatcher.InvokeAsync(() => pb_r_genRank.Value = 10);
+ string studytext;
+ int study_i;
+ if (mw.GameSavesData.GameSave.Level < 20)
+ {
+ studytext = "相当于桌宠的小学学历哦\n\"肃清! {0}的安魂曲☆\"".Translate(petname);
+ study_i = 1;
+ }
+ else if (mw.GameSavesData.GameSave.Level < 40)
+ {
+ studytext = "相当于桌宠的中学学历哦\n<高考桌宠100天>".Translate();
+ study_i = 2;
+ }
+ else if (mw.GameSavesData.GameSave.Level < 60)
+ {
+ studytext = "相当于桌宠的大学学历哦\n\"大学生上课吃饭睡觉, {0}学习吃饭睡觉, {0}=大学生\"".Translate(petname);
+ study_i = 3;
+ }
+ else if (mw.GameSavesData.GameSave.Level < 80)
+ {
+ studytext = "相当于桌宠的博士学历哦\n\"大学生上课吃饭睡觉, 人家和那个带兜帽的没关系啦\"".Translate();
+ study_i = 4;
+ }
+ else
+ {
+ studytext = "<虚拟桌宠模拟器砖家>\n\"一定是{0}干的!\"".Translate(username);
+ study_i = 5;
+ }
+
+ int studyexpmax, studymoneymax;
+ double studyexpmaxrank = 0, studymoneymaxrank = 0;
+ if (mw.IsSteamUser)
+ {
+ studyexpmax = SteamUserStats.GetStatInt("stat_single_profit_exp");
+ studymoneymax = SteamUserStats.GetStatInt("stat_single_profit_money");
+ }
+ else
+ {
+ studyexpmax = mw.GameSavesData.Statistics[(gint)"stat_single_profit_exp"];
+ studymoneymax = mw.GameSavesData.Statistics[(gint)"stat_single_profit_money"];
+ }
+ await Dispatcher.InvokeAsync(() => pb_r_genRank.Value = 20);
+ if (useranking)
+ {
+ Leaderboard? leaderboard = await SteamUserStats.FindOrCreateLeaderboardAsync("stat_single_profit_exp", LeaderboardSort.Descending, LeaderboardDisplay.Numeric);
+ var result = await leaderboard?.ReplaceScore(studyexpmax);
+ var length = leaderboard?.EntryCount ?? 1.0;
+ studyexpmaxrank = 1 - ((result?.NewGlobalRank - 1) ?? length) / length;
+
+ leaderboard = await SteamUserStats.FindOrCreateLeaderboardAsync("stat_single_profit_money", LeaderboardSort.Descending, LeaderboardDisplay.Numeric);
+ result = await leaderboard?.ReplaceScore(studymoneymax);
+ length = leaderboard?.EntryCount ?? 1.0;
+ studymoneymaxrank = 1 - ((result?.NewGlobalRank - 1) ?? length) / length;
+ }
+ string studyexptext, workmoneytext;
+ int studyexp_i, workmoney_i;
+ if (studyexpmaxrank < 0.25)
+ {
+ studyexptext = '"' + "在你这个年纪,你怎么睡得着觉的?".Translate() + '"';
+ studyexp_i = 5;
+ }
+ else if (studyexpmaxrank < 0.4)
+ {
+ studyexptext = '"' + "孩子学习老不好,多半是废了,快来试试思维驰学习机".Translate() + '"';
+ studyexp_i = 4;
+ }
+ else if (studyexpmaxrank < 0.55)
+ {
+ studyexptext = '"' + "孩子学习老不好,多半是废了,快来试试思维驰学习机".Translate() + '"';
+ studyexp_i = 3;
+ }
+ else if (studyexpmaxrank < 0.75)
+ {
+ studyexptext = '"' + "学而不思则罔,思而不学则die".Translate() + '"';
+ studyexp_i = 2;
+ }
+ else
+ {
+ studyexptext = '"' + "看我量子速读法!".Translate() + '"';
+ studyexp_i = 1;
+ }
+
+ if (studymoneymaxrank < 0.25)
+ {
+ workmoneytext = '"' + "钱钱乃身外之物".Translate() + '"';
+ workmoney_i = 4;
+ }
+ else if (studymoneymaxrank < 0.5)
+ {
+ workmoneytext = '"' + "风声雨声读书声声声入耳,日结月结次次结钱钱入账".Translate() + '"';
+ workmoney_i = 3;
+ }
+ else if (studymoneymaxrank < 0.75)
+ {
+ workmoneytext = '"' + "有钱能使磨推鬼".Translate() + '"';
+ workmoney_i = 2;
+ }
+ else
+ {
+ workmoneytext = '"' + "可是,我真的很需要那些钱钱!".Translate() + '"';
+ workmoney_i = 1;
+ }
+
+ await Dispatcher.InvokeAsync(() => pb_r_genRank.Value = 40);
+
+ int worktime = mw.GameSavesData.Statistics[(gint)"stat_work_time"];
+ double worktimeph = (double)worktime / timelength;
+ double worktimephrank = 0;
+ if (useranking)
+ {
+ Leaderboard? leaderboard = await SteamUserStats.FindOrCreateLeaderboardAsync("stat_work_time_ph", LeaderboardSort.Descending, LeaderboardDisplay.Numeric);
+ var result = await leaderboard?.ReplaceScore((int)(worktimeph * 10000));
+ var length = leaderboard?.EntryCount ?? 1.0;
+ worktimephrank = 1 - ((result?.NewGlobalRank - 1) ?? length) / length;
+ }
+ string worktimephtext;
+ int worktime_i;
+ if (worktimephrank < 0.25)
+ {
+ worktimephtext = '"' + "干一天来歇一天, 能混一天是一天".Translate() + '"';
+ worktime_i = 1;
+ }
+ else if (worktimephrank < 0.35)
+ {
+ worktimephtext = '"' + "早8晚5,快乐回家".Translate() + '"';
+ worktime_i = 2;
+ }
+ else if (worktimephrank < 0.45)
+ {
+ worktimephtext = '"' + "早8晚5,快乐回家".Translate() + '"';
+ worktime_i = 3;
+ }
+ else if (worktimephrank < 0.55)
+ {
+ worktimephtext = '"' + "早8晚5,快乐回家".Translate() + '"';
+ worktime_i = 4;
+ }
+ else if (worktimephrank < 0.75)
+ {
+ worktimephtext = '"' + "加班没有加班费不是基本常识吗?".Translate() + '"';
+ worktime_i = 5;
+ }
+ else
+ {
+ worktimephtext = '"' + "老板! 路灯已经准备好了!".Translate() + '"';
+ worktime_i = 6;
+ }
+
+ int betterbuytimes = mw.GameSavesData.Statistics[(gint)"stat_buytimes"];
+ int betterbuycount = (int)mw.GameSavesData.Statistics[(gdbe)"stat_betterbuy"];
+
+ Food mostfood = new Food()
+ {
+ Name = "None",
+ };
+
+ foreach (var pair in mw.GameSavesData.Statistics.Data.Where(x => x.Key.StartsWith("buy_")).OrderByDescending(x => ((int)x.Value)))
+ {
+ var fn = pair.Key.Substring(4);
+ var f = mw.Foods.FirstOrDefault(x => x.Name == fn);
+ if (f != null)
+ {
+ mostfood = f;
+ break;
+ }
+ }
+
+ string foodtext = "啥也没吃,{0}都饿坏了".Translate(petname);
+ switch (mostfood.Type)
+ {
+ case Food.FoodType.Meal:
+ foodtext = '"' + "人是铁饭是钢, 四菜一汤吃得香".Translate() + '"';
+ break;
+ case Food.FoodType.Drug:
+ foodtext = '"' + "自动购买又忘开了吧?".Translate() + '"';
+ break;
+ case Food.FoodType.Drink:
+ foodtext = '"' + "多喝热水".Translate() + '"';
+ break;
+ case Food.FoodType.Functional:
+ foodtext = '"' + "不是正餐买不起, 而是功能性更有性价比".Translate() + '"';
+ break;
+ case Food.FoodType.Snack:
+ foodtext = '"' + "多吃零食有益心理健康".Translate() + '"';
+ break;
+ case Food.FoodType.Gift:
+ foodtext = '"' + "公若不弃,{0}愿拜为义父!".Translate(petname) + '"';
+ break;
+ }
+
+ await Dispatcher.InvokeAsync(() => pb_r_genRank.Value = 60);
+
+ int autobuytimes = mw.GameSavesData.Statistics[(gint)"stat_autobuy"];
+ double autobuytimesph = (double)autobuytimes / betterbuytimes;
+ double autobuytimesphrank = 0;
+ if (useranking)
+ {
+ Leaderboard? leaderboard = await SteamUserStats.FindOrCreateLeaderboardAsync("stat_autobuy_ph", LeaderboardSort.Descending, LeaderboardDisplay.Numeric);
+ var result = await leaderboard?.ReplaceScore((int)(autobuytimesph * 10000));
+ var length = leaderboard?.EntryCount ?? 1.0;
+ autobuytimesphrank = 1 - ((result?.NewGlobalRank - 1) ?? length) / length;
+ }
+ string autobuytext;
+ int autobuy_i;
+ if (autobuytimesph < 0.25)
+ {
+ autobuytext = '"' + "主人, 是担心我乱买东西嘛".Translate() + '"';
+ autobuy_i = 4;
+ }
+ else if (autobuytimesph < 0.5)
+ {
+ autobuytext = '"' + "自己赚的钱自己花".Translate() + '"';
+ autobuy_i = 3;
+ }
+ else if (autobuytimesph < 0.75)
+ {
+ autobuytext = '"' + "不要小看我的情报网! 你自动购买礼物没关,对不对?".Translate() + '"';
+ autobuy_i = 2;
+ }
+ else
+ {
+ autobuytext = '"' + "诚招保姆,工资面议".Translate() + '"';
+ autobuy_i = 1;
+ }
+
+ await Dispatcher.InvokeAsync(() => pb_r_genRank.Value = 70);
+
+ var modworkshoplist = mw.CoreMODs.FindAll(x => x.Path.FullName.Contains("workshop"));
+ int modworkshop = modworkshoplist.Count;
+ int modon = modworkshoplist.FindAll(x => x.IsOnMOD(mw)).Count;
+ double modworkshoprank = 0;
+ if (useranking)
+ {
+ Leaderboard? leaderboard = await SteamUserStats.FindOrCreateLeaderboardAsync("workshop", LeaderboardSort.Descending, LeaderboardDisplay.Numeric);
+ var result = await leaderboard?.ReplaceScore(modworkshop);
+ var length = leaderboard?.EntryCount ?? 1.0;
+ modworkshoprank = 1 - ((result?.NewGlobalRank - 1) ?? length) / length;
+ }
+ string modworkshoptext;
+ int modworkshop_i;
+ if (modworkshop == 0)
+ {
+ modworkshoptext = '"' + "桌宠的steam创意工坊里有许多的mod喵, 主人快去试试吧".Translate() + '"';
+ modworkshop_i = 3;
+ }
+ else if (modworkshoprank < 0.3)
+ {
+ modworkshoptext = '"' + "主人还可以再去创意工坊体验更多MOD喵".Translate() + '"';
+ modworkshop_i = 3;
+ }
+ else if (modworkshoprank < 0.7)
+ { modworkshoptext = '"' + "创意工坊又更新了很多有趣的mod喵, 主人要不要去看看?".Translate() + '"'; modworkshop_i = 2; }
+ else
+ { modworkshoptext = '"' + "主人已经是mod大师了喵,要不要试试mod制作器,给我做mod喵!".Translate() + '"'; modworkshop_i = 1; }
+
+ await Dispatcher.InvokeAsync(() => pb_r_genRank.Value = 80);
+
+ int like = (int)mw.GameSavesData.GameSave.Likability;
+ string liketext = "";
+ while (like > 100)
+ {
+ like -= 100;
+ liketext += '\uEE0E';
+ }
+ while (like > 50)
+ {
+ like -= 50;
+ liketext += '\uEE0F';
+ }
+ if (liketext.Length == 0)
+ {
+ liketext = "\uEECA";
+ }
+ double likerank = 0;
+ if (useranking)
+ {
+ Leaderboard? leaderboard = await SteamUserStats.FindOrCreateLeaderboardAsync("stat_likability", LeaderboardSort.Descending, LeaderboardDisplay.Numeric);
+ var result = await leaderboard?.ReplaceScore((int)mw.GameSavesData.GameSave.Likability);
+ var length = leaderboard?.EntryCount ?? 1.0;
+ likerank = 1 - ((result?.NewGlobalRank - 1) ?? length) / length;
+ }
+ await Dispatcher.InvokeAsync(() => pb_r_genRank.Value = 88);
+
+ await Dispatcher.InvokeAsync(() =>
+ {
+ r_r_startday.Text = mw.GameSavesData[(gdat)"birthday"].ToLongDateString();
+ r_r_startlength.Text = startdatelength.ToString("f1");
+ r_r_length_h.Text = timelength_h.ToString("f1");
+ r_r_length_p.Text = startlengthrank.ToString("p1");
+ r_r_lenghranktext.Text = startlengthranktext;
+
+ r_r_lengthph.Text = timelengthph.ToString("f1");
+ r_r_lengthphtext.Text = timelengthphtext;
+ r_r_lenghtext.Text = timelengthtext;
+ r_i_timelength.Source = new BitmapImage(new Uri($"pack://application:,,,/Res/img/r_timelength_{timelength_i}.png"));
+
+ r_r_level.Text = mw.GameSavesData.GameSave.Level.ToString();
+ r_r_exp.Text = mw.GameSavesData.GameSave.Exp.ToString("f0");
+ r_r_studytime.Text = (mw.GameSavesData.Statistics[(gint)"stat_study_time"] / 60).ToString();
+ r_r_studytext.Text = studytext;
+ r_i_exp.Source = new BitmapImage(new Uri($"pack://application:,,,/Res/img/r_level_{study_i}.png"));
+
+ r_r_studyexpmax.Text = studyexpmax.ToString();
+ r_r_studyexpmaxrank.Text = studyexpmaxrank.ToString("p1");
+ r_r_studyexptext.Text = studyexptext;
+ r_i_singleexp.Source = new BitmapImage(new Uri($"pack://application:,,,/Res/img/r_singleexp_{studyexp_i}.png"));
+
+ r_r_worktime.Text = (worktime / 60).ToString();
+ r_r_worktimeps.Text = worktimeph.ToString("p1");
+ r_r_worktimepsrank.Text = worktimephrank.ToString("p1");
+ r_r_worktext.Text = worktimephtext;
+ r_i_money.Source = new BitmapImage(new Uri($"pack://application:,,,/Res/img/r_worktime_{worktime_i}.png"));
+
+ r_r_workmoneymax.Text = studymoneymax.ToString();
+ r_r_workmoneyrank.Text = studymoneymaxrank.ToString("p1");
+ r_r_workmoneytext.Text = workmoneytext;
+ r_i_singlemoney.Source = new BitmapImage(new Uri($"pack://application:,,,/Res/img/r_singlemoney_{workmoney_i}.png"));
+
+ r_r_username.Text = username;
+ r_r_petname.Text = r_r_petname_2.Text = r_r_petname_3.Text = r_r_petname_4.Text = petname;
+ r_r_now.Text = DateTime.Now.ToShortDateString();
+
+ r_r_betterbuytimes.Text = betterbuytimes.ToString();
+ r_r_betterbuycount.Text = betterbuycount.ToString();
+ r_r_betterbuymosttype.Text = mostfood.Type.ToString().Translate();
+ r_r_betterbuymostitem.Text = mostfood.TranslateName;
+ r_r_betterbuymosttext.Text = foodtext;
+ r_i_mostfood.Source = new BitmapImage(new Uri($"pack://application:,,,/Res/img/r_mostfood_{mostfood.Type}.png"));
+
+ r_r_autobuy.Text = autobuytimes.ToString();
+ r_r_autobuypres.Text = autobuytimesph.ToString("p1");
+ r_r_autobuyrank.Text = autobuytimesphrank.ToString("p1");
+ r_r_autobuytext.Text = autobuytext;
+ r_i_autobuy.Source = new BitmapImage(new Uri($"pack://application:,,,/Res/img/r_autobuy_{autobuy_i}.png"));
+
+ r_r_modcount.Text = modworkshop.ToString();
+ r_r_modenablecount.Text = modon.ToString();
+ r_r_modcountrank.Text = modworkshoprank.ToString("p1");
+ r_r_modcounttext.Text = modworkshoptext;
+ r_i_mod.Source = new BitmapImage(new Uri($"pack://application:,,,/Res/img/r_mod_{modworkshop_i}.png"));
+
+ r_r_sleeplength.Text = (mw.GameSavesData.Statistics[(gint)"stat_sleep_time"] / 3600.0).ToString("f1");
+ r_r_movelength.Text = px_tocm(mw.GameSavesData.Statistics[(gi64)"stat_move_length"], out string cm);
+ r_r_movelengthcm.Text = cm;
+ r_r_saycount.Text = mw.GameSavesData.Statistics[(gint)"stat_say_times"].ToString();
+ r_r_musiccount.Text = mw.GameSavesData.Statistics[(gint)"stat_music"].ToString();
+ r_r_touchtotal.Text = (mw.GameSavesData.Statistics[(gint)"stat_touch_body"] + mw.GameSavesData.Statistics[(gint)"stat_touch_head"]).ToString();
+
+ if (mw.GameSavesData.GameSave.Likability > 100)
+ r_i_like.Visibility = Visibility.Visible;
+ else
+ r_i_like.Visibility = Visibility.Collapsed;
+
+ r_r_opencount.Text = mw.GameSavesData.Statistics[(gint)"stat_open_times"].ToString();
+ r_r_bettercount.Text = mw.GameSavesData.Statistics[(gint)"stat_100_all"].ToString();
+ r_r_likecount.Text = liketext;
+ r_r_likecountrank.Text = likerank.ToString("p1");
+
+ r_viewbox.Visibility = Visibility.Visible;
+ btn_r_genRank.IsEnabled = true;
+ btn_r_save.IsEnabled = true;
+ pb_r_genRank.Visibility = Visibility.Collapsed;
+ Width = 800;
+ Height = 800;
+ });
+ }
+ private string px_tocm(long px, out string cm)
+ {
+ if (px < 37795)
+ {
+ cm = "px";
+ return px.ToString();
+ }
+ else if (px < 3779527)
+ {
+ cm = "cm";
+ return (px * 2.54 / 96).ToString("f1");
+ }
+ else if (px < 377952755)
+ {
+ cm = "m";
+ return (px * 2.54 / 9600).ToString("f1");
+ }
+ else
+ {
+ cm = "km";
+ return (px * 2.54 / 9600000).ToString("f1");
+ }
+ }
}
}
diff --git a/VPet-Simulator.Windows/WinDesign/winGameSetting.xaml.cs b/VPet-Simulator.Windows/WinDesign/winGameSetting.xaml.cs
index 0e15645..422d5b6 100644
--- a/VPet-Simulator.Windows/WinDesign/winGameSetting.xaml.cs
+++ b/VPet-Simulator.Windows/WinDesign/winGameSetting.xaml.cs
@@ -1439,7 +1439,14 @@ namespace VPet_Simulator.Windows
mw.GameSavesData = new GameSave_v2(mw.Core.Save.Name);
mw.Core.Save = mw.GameSavesData.GameSave;
if (oldsave.HashCheck) // 对于重开无作弊的玩家保留统计
+ {
mw.GameSavesData.Statistics = oldsave.Statistics;
+ if(oldsave.GameSave.Money > 10000000 || oldsave.GameSave.Money < -1000000000 || oldsave.GameSave.Exp > 100000000 || oldsave.GameSave.Exp < -10000000000)
+ {
+ mw.Core.Save.Money = 10000;
+ mw.Core.Save.Exp = 10000;
+ }
+ }
mw.HashCheck = true;
MessageBoxX.Show("重置成功".Translate());
}
diff --git a/VPet-Simulator.Windows/mklink.bat b/VPet-Simulator.Windows/mklink.bat
index b47d029..1ce6ea0 100644
--- a/VPet-Simulator.Windows/mklink.bat
+++ b/VPet-Simulator.Windows/mklink.bat
@@ -1,11 +1,12 @@
-chcp 65001
+%1 mshta vbscript:createobject("shell.application").shellexecute("%~s0","::","","runas",1)(window.close)&exit
+cd /d %~dp0
mklink /d "%~dp0\bin\x64\Debug\net462\mod" "%~dp0\mod"
-echo ^"以下是其他相关MOD的自动链接生成, 若提示错误为正常现象,无需理会"
-echo "The following is the automatic link generation for other related MODs. If an error is prompted, it is a normal phenomenon and should not be ignored"
+echo The following is the automatic link generation for other related MODs. If an error is prompted, it is a normal phenomenon and can be ignored
mklink /d "%~dp0\bin\x64\Release\net462\mod" "%~dp0\mod"
+mklink /d "%~dp0\..\VPet.Solution\bin\Debug\mod" "%~dp0\mod"
mklink /d "%~dp0\mod\0001_ModMaker" "%~dp0\..\..\VPet.ModMaker\0001_ModMaker"
mklink /d "%~dp0\mod\1100_DemoClock" "%~dp0\..\..\VPet.Plugin.Demo\VPet.Plugin.DemoClock\1100_DemoClock"
@@ -13,5 +14,4 @@ mklink /d "%~dp0\mod\1111_ChatGPTPlus" "%~dp0\..\..\VPet.Plugin.ChatGPTPlus\VPet
mklink /d "%~dp0\mod\1101_EdgeTTS" "%~dp0\..\..\VPet.Plugin.Demo\VPet.Plugin.EdgeTTS\1101_EdgeTTS"
mklink /d "%~dp0\mod\1110_ChatGPT" "%~dp0\..\..\VPet.Plugin.Demo\VPet.Plugin.ChatGPT\1110_ChatGPT"
-
pause
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/food/timelimit.lps b/VPet-Simulator.Windows/mod/0000_core/food/timelimit.lps
index af1dd71..9bd6da5 100644
--- a/VPet-Simulator.Windows/mod/0000_core/food/timelimit.lps
+++ b/VPet-Simulator.Windows/mod/0000_core/food/timelimit.lps
@@ -1,3 +1,9 @@
-food:|name#莲蓉蛋黄月饼:|type#Snack:|Exp#100:|Strength#40:|StrengthDrink#-3:|StrengthFood#80:|Likability#1:|Health#5:|Feeling#40:|price#26:|graph#eat:|desc#海上生明月,天涯共此时。无论近况如何,前途多难,计划如不如意,未来迷不迷茫,萝莉丝都为你做了莲蓉蛋黄月饼,快说谢谢萝莉丝。:|
-food:|name#五仁月饼:|type#Meal:|Exp#100:|Strength#80:|StrengthDrink#-6:|StrengthFood#160:|Likability#1:|Health#5:|Feeling#0:|price#38:|graph#eat:|desc#举头望明月,低头思故乡。无论身在何处,离家多远,家人在不在身边,心情好不好,萝莉丝都为你做了五仁月饼,快说谢谢萝莉丝。:|
-food:|name#巧克力花生月饼:|type#Functional:|Exp#400:|Strength#20:|StrengthDrink#0:|StrengthFood#40:|Likability#2:|Health#5:|Feeling#20:|price#62:|graph#eat:|desc#明月几时有,把酒问青天。无论考的如何,作业有多少,老师怎么说,家长怎么批评,萝莉丝都为你做了巧克力花生月饼,快说谢谢萝莉丝。:|
\ No newline at end of file
+/// food:|name#莲蓉蛋黄月饼:|type#Snack:|Exp#100:|Strength#40:|StrengthDrink#-3:|StrengthFood#80:|Likability#1:|Health#5:|Feeling#40:|price#26:|graph#eat:|desc#海上生明月,天涯共此时。无论近况如何,前途多难,计划如不如意,未来迷不迷茫,萝莉丝都为你做了莲蓉蛋黄月饼,快说谢谢萝莉丝。:|
+/// food:|name#五仁月饼:|type#Meal:|Exp#100:|Strength#80:|StrengthDrink#-6:|StrengthFood#160:|Likability#1:|Health#5:|Feeling#0:|price#38:|graph#eat:|desc#举头望明月,低头思故乡。无论身在何处,离家多远,家人在不在身边,心情好不好,萝莉丝都为你做了五仁月饼,快说谢谢萝莉丝。:|
+/// food:|name#巧克力花生月饼:|type#Functional:|Exp#400:|Strength#20:|StrengthDrink#0:|StrengthFood#40:|Likability#2:|Health#5:|Feeling#20:|price#62:|graph#eat:|desc#明月几时有,把酒问青天。无论考的如何,作业有多少,老师怎么说,家长怎么批评,萝莉丝都为你做了巧克力花生月饼,快说谢谢萝莉丝。:|
+
+food:|name#圣诞帽:|type#Gift:|Exp#200:|Likability#4:|Health#5:|Feeling#50:|price#45.0:|graph#gift:|desc#圣诞帽_giftintor:|
+food:|name#礼物盒子:|type#Gift:|Exp#1000:|Likability#5:|Feeling#100:|price#150.0:|graph#gift:|desc#礼物盒子_giftintor:|
+food:|name#糖果棒:|type#Snack:|Exp#200:|Strength#40:|StrengthFood#40:|Likability#2:|Feeling#40:|price#35:|graph#eat:|desc#糖果棒_giftintor:|
+food:|name#圣诞草莓奶茶:|type#Drink:|Exp#200:|StrengthDrink#80:|Likability#2:|Feeling#60:|price#35:|graph#drink:|desc#圣诞草莓奶茶_giftintor:|
+food:|name#萝莉丝姜饼人:|type#Snack:|Exp#600:|StrengthFood#80:|Likability#2:|Health#2:|Feeling#50:|price#128.0:|graph#eat:|desc#萝莉丝姜饼人_giftintor:|
diff --git a/VPet-Simulator.Windows/mod/0000_core/image/food/圣诞帽.png b/VPet-Simulator.Windows/mod/0000_core/image/food/圣诞帽.png
new file mode 100644
index 0000000..c394dab
Binary files /dev/null and b/VPet-Simulator.Windows/mod/0000_core/image/food/圣诞帽.png differ
diff --git a/VPet-Simulator.Windows/mod/0000_core/image/food/圣诞草莓奶茶.png b/VPet-Simulator.Windows/mod/0000_core/image/food/圣诞草莓奶茶.png
new file mode 100644
index 0000000..8fa9e4b
Binary files /dev/null and b/VPet-Simulator.Windows/mod/0000_core/image/food/圣诞草莓奶茶.png differ
diff --git a/VPet-Simulator.Windows/mod/0000_core/image/food/礼物盒子.png b/VPet-Simulator.Windows/mod/0000_core/image/food/礼物盒子.png
new file mode 100644
index 0000000..365ae64
Binary files /dev/null and b/VPet-Simulator.Windows/mod/0000_core/image/food/礼物盒子.png differ
diff --git a/VPet-Simulator.Windows/mod/0000_core/image/food/糖果棒.png b/VPet-Simulator.Windows/mod/0000_core/image/food/糖果棒.png
new file mode 100644
index 0000000..309e3ae
Binary files /dev/null and b/VPet-Simulator.Windows/mod/0000_core/image/food/糖果棒.png differ
diff --git a/VPet-Simulator.Windows/mod/0000_core/image/food/萝莉丝姜饼人.png b/VPet-Simulator.Windows/mod/0000_core/image/food/萝莉丝姜饼人.png
new file mode 100644
index 0000000..b755557
Binary files /dev/null and b/VPet-Simulator.Windows/mod/0000_core/image/food/萝莉丝姜饼人.png differ
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/en/Base2401.lps b/VPet-Simulator.Windows/mod/0000_core/lang/en/Base2401.lps
new file mode 100644
index 0000000..a768d75
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/en/Base2401.lps
@@ -0,0 +1,3 @@
+由于操作系统的设计,通过我们软件启动的程序可能会在任务管理器中归类为我们软件的子进程,这可能导致CPU/内存占用显示较高#Due to the design of the operating system, programs launched by our software may be categorized as sub-processes of our software in the task manager, which may result in higher displayed CPU/memory usage.:|
+关于CPU/内存占用显示较高的一次性提示#One-time Notice Regarding Higher Displayed CPU/Memory Usage:|
+尝试加载动画和生成缓存\n该步骤可能会耗时比较长\n请耐心等待#Attempting to load animations and generate cache\nThis step may take a while\nPlease be patient:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/en/Limit.lps b/VPet-Simulator.Windows/mod/0000_core/lang/en/Limit.lps
index 835706f..9449cdf 100644
--- a/VPet-Simulator.Windows/mod/0000_core/lang/en/Limit.lps
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/en/Limit.lps
@@ -7,4 +7,14 @@
主人,sbeam秋季促销开始了哦,还有游戏大奖赛,快去给{name}去投一票吧。#Master, Sbeam's autumn promotion has begun, and there is also a Awards . Go and vote for {name}.:|
主人主人,{name}参加了sbeam大奖赛哦,给人家投一票喵#Master, {name} is participating in the Sbeam Awards , vote for me meow:|
那个。。主人。。\n人家参加了sbeam大奖赛哦。能不能。。给{name}投一票呢~#That... Master... I'm participating in the Sbeam Awards . Can you... vote for {name}~:|
-电脑里有一款《虚拟桌宠模拟器》的游戏正在参加2023的sbeam大奖赛,快来给桌宠投一票吧#There is a game called "VPet Simulator" in the computer that is participating in the 2023 Sbeam Awards , come and vote for the desktop pet:|
\ No newline at end of file
+电脑里有一款《虚拟桌宠模拟器》的游戏正在参加2023的sbeam大奖赛,快来给桌宠投一票吧#There is a game called "VPet Simulator" in the computer that is participating in the 2023 Sbeam Awards , come and vote for the desktop pet:|
+圣诞帽#Christmas hat:|
+圣诞帽_giftintor#An ordinary hat, but anyone can be Santa with it.:|
+礼物盒子#Gift box:|
+礼物盒子_giftintor#Surprise!!:|
+糖果棒#Candy bar:|
+糖果棒_giftintor#You can eat it, play with it, and use it to spank bad kids.:|
+圣诞草莓奶茶#Christmas strawberry milk tea:|
+圣诞草莓奶茶_giftintor#I don't know why it's Christmas cream tea and not Christmas cake, maybe the artist likes Christmas strawberry cream tea:|
+萝莉丝姜饼人#Gingerbread Lolis:|
+萝莉丝姜饼人_giftintor#Lolis made her own gingerbread man, and Lolis would get mad if she couldn't eat it because it was too cute!:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/en/Prog2312.lps b/VPet-Simulator.Windows/mod/0000_core/lang/en/Prog2312.lps
new file mode 100644
index 0000000..76961ce
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/en/Prog2312.lps
@@ -0,0 +1,107 @@
+统计#Statistics:|
+主人~多陪陪我~#Master, stay with me!:|
+主人~感谢陪伴~#Master, thank you for your company.:|
+同学#Classmates:|
+学长~前辈~#Senior:|
+朋友#Friend!:|
+兄弟!#Brother!:|
+挚友#Best friend!:|
+不求同年同月同日生,但求同年同月同日打开《虚拟桌宠模拟器》#We don't want to be born in the same year, month and day, but we want to open VPet Simulator in the same year, month and day.:|
+家人#Family:|
+We are 伐木累~#We are Family.:|
+女鹅#Daughter:|
+爸妈~ 这么叫好像不太好#Mom and Dad!, I don't think it's a good idea to call them that.:|
+相当于桌宠的中学学历哦\n<高考桌宠100天>#It's the equivalent of VPet's high school education.\n:|
+在你这个年纪,你怎么睡得着觉的?#How do you sleep at your age?:|
+学而不思则罔,思而不学则die#Learning without thought means labour lost; thought without learning is perilous.:|
+学习?#Learning?:|
+看我量子速读法!#Look at my quantum speed reading method!:|
+钱钱乃身外之物#Money is nothing.:|
+风声雨声读书声声声入耳,日结月结次次结钱钱入账#The sound of the wind and rain, the sound of reading, the sound of the day, the sound of the month, the sound of the day, the sound of the month, the sound of the money.:|
+有钱能使磨推鬼#Money can make things go away.:|
+可是,我真的很需要那些钱钱!#But I really need that money!:|
+干一天来歇一天, 能混一天是一天#One day at a time, one day off, one day at a time.:|
+早8晚5,快乐回家#8 a.m. to 5 p.m., go home happy:|
+加班没有加班费不是基本常识吗?#Isn't it basic knowledge that overtime work is not paid?:|
+老板! 路灯已经准备好了!#Boss! The streetlights are ready!:|
+啥也没吃,{0}都饿坏了#haven't eaten anything. {0} is starving.:|
+人是铁饭是钢, 四菜一汤吃得香#Iron is steel, four dishes and one soup.:|
+自动购买又忘开了吧?#Did you forget to turn on the auto-purchase?:|
+多喝热水#Drink more hot water.:|
+不是正餐买不起, 而是功能性更有性价比#It's not that you can't afford to eat, it's that functionality is more cost-effective.:|
+多吃零食有益心理健康#Snacking is good for your mental health.:|
+公若不弃,{0}愿拜为义父!#If you don't give me up, May {0} be worshipped as adoptive father:|
+主人, 是担心我乱买东西嘛#Master, Are you worried that I will buy things randomly?:|
+自己赚的钱自己花#I spend what I earn.:|
+不要小看我的情报网! 你自动购买礼物没关,对不对?#Don't underestimate my intelligence network! You're not buying gifts automatically, are you?:|
+诚招保姆,工资面议#We're looking for a nanny. Salary negotiable.:|
+桌宠的steam创意工坊里有许多的mod喵, 主人快去试试吧#VPet's steam workshop is full of mods, so go try them out.:|
+创意工坊又更新了很多有趣的mod喵, 主人要不要去看看?#There's a lot of interesting mods in the steam workshop. Would you like to take a look?:|
+主人已经是mod大师了喵,要不要试试mod制作器,给我做mod喵!#Master is already a master modder, try the mod maker and make mods for me!:|
+统计总结#Stats Summary:|
+生成统计#Generate stats:|
+保存图片#Save image:|
+未使用过作弊模组和修改游戏数据#Have not used any cheat mods or modified game stats.:|
+同意上传统计数据至Steam排行榜#Agree to upload stats to Steam Leaderboards:|
+你第一次遇到了这只可爱的小东西#Your first encounter with this cute little thing:|
+初次见面,主人~#First encounter, master~:|
+你和#You and :|
+一共生活了# lived together for:|
+r天#Days:|
+你陪伴她的时长是#The length of time you stayed with her was:|
+r小时#Hours.:|
+超过了全球#More than:|
+r的主人#of the world's players:|
+平均一天有#Spend an average of:|
+小时在一起#hours a day together.:|
+相当于平均和#Equivalent to the average time spent with:|
+在一起的时间#time spent together:|
+你的等级是#Your Level is:|
+r级#:|
+累计获得经验#Cumulative experience gained:|
+累计学习时间是#The cumulative learning time is:|
+r分钟#minutes:|
+单次学习最大获得#Maximum single learning gain:|
+累计工作时间是#Cumulative work time is:|
+工作占总时间#Work accounts for of total time:|
+单次工作最大获得#Maximum acquisition of work in a single session:|
+累计购买#Accumulated:|
+次更好买商品#times purchases of better products:|
+其中最多购买的是#Among the most purchased are:|
+在分类#under the category:|
+自动购买#Automatic purchases:|
+次#times:|
+占全部购买#of all purchases:|
+你订阅了#You subscribed to:|
+个mod#mods:|
+启用的其中的#of which are enabled:|
+你订阅mod数超过了全球#You subscribed to more mods than:|
+睡了#Slept:|
+小时的觉#hours:|
+移动了#Moved:|
+的距离#distance:|
+说了#Said:|
+句话#Sentence:|
+跳了#Danced:|
+次舞蹈#times:|
+摸了#Touched:|
+次头#times:|
+吃喝玩乐睡, 惨了养成猪了#Eat, drink, play and sleep.:|
+打开游戏次数是#The number of times the game is opened is:|
+你照顾的#The full states of:|
+满状态次数是#being taken care of:|
+对你的好感度是#'s favorability towards you is:|
+最喜欢你了主人~ 新的一年请多多关照喵~#You're my favorite, master. Please take care of me in the new year.:|
+MOD名称重复#Mod name duplicated:|
+Food#Food:|
+Star#Star:|
+Meal#Meal:|
+Snack#Snack:|
+Drink#Drink:|
+Functional#Functional:|
+Drug#Drug:|
+Gift#Gift:|
+孩子学习老不好,多半是废了,快来试试思维驰学习机#The child's learning is not good, mostly waste, come and try the Nintendo switch learning machine:|
+主人还可以再去创意工坊体验更多MOD喵#Master can also go to the workshop to experience more MOD:|
+点击前往查看#Click to view:|
+哼哼~主人,我的考试成绩出炉了哦,快来和我一起看我的成绩单喵#Humph~Master, my exam results are out, come and see my report card with me:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/en/Solution.lps b/VPet-Simulator.Windows/mod/0000_core/lang/en/Solution.lps
new file mode 100644
index 0000000..9366144
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/en/Solution.lps
@@ -0,0 +1,48 @@
+VPET 设置编辑器#VPET Setup Editor:|
+打开文件#Opening Files:|
+从资源管理器打开文件#Opening a file from Explorer:|
+重置#Reset:|
+全部保存#Save All:|
+Mod管理#Mod Management:|
+保存为退出位置#Save as Exit Location:|
+设为当前位置#Set as current position:|
+设为当前窗口左上角顶点坐标的位置#Set to the current position of the top-left corner of the window in vertex coordinates.:|
+每次间隔#Interval:|
+备份设置#Backup Settings:|
+桌宠设置#VPet Settings:|
+桌宠状态#VPet Status:|
+启用桌宠状态#Enable VPet status:|
+分钟左右主动进行一次互动 (走路发呆爬墙等) #Interact actively once every minute or so (walk, dawdle, climb walls, etc.) :|
+清空全部#Clear all:|
+搜索名称#Search Name:|
+链接#Link:|
+每周期一次#Once per cycle:|
+搜索模组#Search Module:|
+清除失效模组#Clear invalid modules:|
+清除全部模组#Clear all modules:|
+模组名称:#Module Name.:|
+作者:#Author.:|
+模组版本:#Module Version.:|
+游戏版本:#Game Version.:|
+模组路径:#Path to Module.:|
+启用模组#Enable Module:|
+启用模组代码#Enable Module Code:|
+打开所在文件夹#Open the folder:|
+打开创意工坊页面#Open Creative Workshop Page:|
+VPET 存档查看器#VPET Archive Viewer:|
+搜索存档#Search Archives:|
+保存时间#Save Time:|
+游玩时长#Play Duration:|
+数据#Data:|
+保存日期#Save Dates:|
+模式#Mode:|
+等级#Grade:|
+哈希检查#Hash Check:|
+值#Value:|
+VPET 问题解决工具#VPET Problem Solving Tool:|
+打开设置编辑器#Open Settings Editor:|
+打开存档查看器#Open Archive Viewer:|
+打开翻译文本#Open Translated Text:|
+全部重置#Reset All:|
+哈希#Hash:|
+第一次启动桌宠打不开?#Can't open the first time you start the desktop pet?:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Base2401.lps b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Base2401.lps
new file mode 100644
index 0000000..1829f98
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Base2401.lps
@@ -0,0 +1,3 @@
+由于操作系统的设计,通过我们软件启动的程序可能会在任务管理器中归类为我们软件的子进程,这可能导致CPU/内存占用显示较高#由于操作系统的设计,通过我们软件启动的程序可能会在任务管理器中归类为我们软件的子进程,这可能导致CPU/内存占用显示较高:|
+关于CPU/内存占用显示较高的一次性提示#关于CPU/内存占用显示较高的一次性提示:|
+尝试加载动画和生成缓存\n该步骤可能会耗时比较长\n请耐心等待#尝试加载动画和生成缓存\n该步骤可能会耗时比较长\n请耐心等待:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Limit.lps b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Limit.lps
index 5d01424..04dae7b 100644
--- a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Limit.lps
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Limit.lps
@@ -4,7 +4,17 @@
海上生明月,天涯共此时。无论近况如何,前途多难,计划如不如意,未来迷不迷茫,萝莉丝都为你做了莲蓉蛋黄月饼,快说谢谢萝莉丝。#海上生明月,天涯共此时。无论近况如何,前途多难,计划如不如意,未来迷不迷茫,萝莉丝都为你做了莲蓉蛋黄月饼,快说谢谢萝莉丝。:|
巧克力花生月饼#巧克力花生月饼:|
明月几时有,把酒问青天。无论考的如何,作业有多少,老师怎么说,家长怎么批评,萝莉丝都为你做了巧克力花生月饼,快说谢谢萝莉丝。#明月几时有,把酒问青天。无论考的如何,作业有多少,老师怎么说,家长怎么批评,萝莉丝都为你做了巧克力花生月饼,快说谢谢萝莉丝。:|
-主人,sbema秋季促销开始了哦,还有游戏大奖赛,快去给{name}去投一票吧。
-主人主人,{name}参加了sbeam大奖赛哦,给人家投一票喵
-那个。。主人。。\n人家参加了sbeam大奖赛哦。能不能。。给{name}投一票呢~
-电脑里有一款《虚拟桌宠模拟器》的游戏正在参加2023的sbeam大奖赛,快来给桌宠投一票吧
\ No newline at end of file
+主人,sbema秋季促销开始了哦,还有游戏大奖赛,快去给{name}去投一票吧。#主人,sbema秋季促销开始了哦,还有游戏大奖赛,快去给{name}去投一票吧。:|
+主人主人,{name}参加了sbeam大奖赛哦,给人家投一票喵#主人主人,{name}参加了sbeam大奖赛哦,给人家投一票喵:|
+那个。。主人。。\n人家参加了sbeam大奖赛哦。能不能。。给{name}投一票呢~#那个。。主人。。\n人家参加了sbeam大奖赛哦。能不能。。给{name}投一票呢~:|
+电脑里有一款《虚拟桌宠模拟器》的游戏正在参加2023的sbeam大奖赛,快来给桌宠投一票吧#电脑里有一款《虚拟桌宠模拟器》的游戏正在参加2023的sbeam大奖赛,快来给桌宠投一票吧:|
+圣诞帽#圣诞帽:|
+圣诞帽_giftintor#一顶普通的帽子,但带上它谁都可以成为圣诞老人:|
+礼物盒子#礼物盒子:|
+礼物盒子_giftintor#Surprise!!:|
+糖果棒#糖果棒:|
+糖果棒_giftintor#能吃能玩,还能用来打坏小孩的屁股:|
+圣诞草莓奶茶#圣诞草莓奶茶:|
+圣诞草莓奶茶_giftintor#我不知道为什么是圣诞奶茶而不是圣诞蛋糕,可能画师喜欢圣诞草莓奶茶吧:|
+萝莉丝姜饼人#萝莉丝姜饼人:|
+萝莉丝姜饼人_giftintor#萝莉丝做的自己样子的姜饼人,要是因为太可爱舍不得吃萝莉丝会生气的哦:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Prog2312.lps b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Prog2312.lps
new file mode 100644
index 0000000..32211ee
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Prog2312.lps
@@ -0,0 +1,107 @@
+统计#统计:|
+主人~多陪陪我~#主人~多陪陪我~:|
+主人~感谢陪伴~#主人~感谢陪伴~:|
+同学#同学:|
+学长~前辈~#学长~前辈~:|
+朋友#朋友:|
+兄弟!#兄弟!:|
+挚友#挚友:|
+不求同年同月同日生,但求同年同月同日打开《虚拟桌宠模拟器》#不求同年同月同日生,但求同年同月同日打开《虚拟桌宠模拟器》:|
+家人#家人:|
+We are 伐木累~#We are 伐木累~:|
+女鹅#女鹅:|
+爸妈~ 这么叫好像不太好#爸妈~ 这么叫好像不太好:|
+相当于桌宠的中学学历哦\n<高考桌宠100天>#相当于桌宠的中学学历哦\n<高考桌宠100天>:|
+在你这个年纪,你怎么睡得着觉的?#在你这个年纪,你怎么睡得着觉的?:|
+学而不思则罔,思而不学则die#学而不思则罔,思而不学则die:|
+学习?#学习?:|
+看我量子速读法!#看我量子速读法!:|
+钱钱乃身外之物#钱钱乃身外之物:|
+风声雨声读书声声声入耳,日结月结次次结钱钱入账#风声雨声读书声声声入耳,日结月结次次结钱钱入账:|
+有钱能使磨推鬼#有钱能使磨推鬼:|
+可是,我真的很需要那些钱钱!#可是,我真的很需要那些钱钱!:|
+干一天来歇一天, 能混一天是一天#干一天来歇一天, 能混一天是一天:|
+早8晚5,快乐回家#早8晚5,快乐回家:|
+加班没有加班费不是基本常识吗?#加班没有加班费不是基本常识吗?:|
+老板! 路灯已经准备好了!#老板! 路灯已经准备好了!:|
+啥也没吃,{0}都饿坏了#啥也没吃,{0}都饿坏了:|
+人是铁饭是钢, 四菜一汤吃得香#人是铁饭是钢, 四菜一汤吃得香:|
+自动购买又忘开了吧?#自动购买又忘开了吧?:|
+多喝热水#多喝热水:|
+不是正餐买不起, 而是功能性更有性价比#不是正餐买不起, 而是功能性更有性价比:|
+多吃零食有益心理健康#多吃零食有益心理健康:|
+公若不弃,{0}愿拜为义父!#公若不弃,{0}愿拜为义父!:|
+主人, 是担心我乱买东西嘛#主人, 是担心我乱买东西嘛:|
+自己赚的钱自己花#自己赚的钱自己花:|
+不要小看我的情报网! 你自动购买礼物没关,对不对?#不要小看我的情报网! 你自动购买礼物没关,对不对?:|
+诚招保姆,工资面议#诚招保姆,工资面议:|
+桌宠的steam创意工坊里有许多的mod喵, 主人快去试试吧#桌宠的steam创意工坊里有许多的mod喵, 主人快去试试吧:|
+创意工坊又更新了很多有趣的mod喵, 主人要不要去看看?#创意工坊又更新了很多有趣的mod喵, 主人要不要去看看?:|
+主人已经是mod大师了喵,要不要试试mod制作器,给我做mod喵!#主人已经是mod大师了喵,要不要试试mod制作器,给我做mod喵!:|
+统计总结#统计总结:|
+生成统计#生成统计:|
+保存图片#保存图片:|
+未使用过作弊模组和修改游戏数据#未使用过作弊模组和修改游戏数据:|
+同意上传统计数据至Steam排行榜#同意上传统计数据至Steam排行榜:|
+你第一次遇到了这只可爱的小东西#你第一次遇到了这只可爱的小东西:|
+初次见面,主人~#初次见面,主人~:|
+你和#你和:|
+一共生活了#一共生活了:|
+r天#天:|
+你陪伴她的时长是#你陪伴她的时长是:|
+r小时#小时:|
+超过了全球#超过了全球:|
+r的主人#的主人:|
+平均一天有#平均一天有:|
+小时在一起#小时在一起:|
+相当于平均和#相当于平均和:|
+在一起的时间#在一起的时间:|
+你的等级是#你的等级是:|
+r级#级:|
+累计获得经验#累计获得经验:|
+累计学习时间是#累计学习时间是:|
+r分钟#分钟:|
+单次学习最大获得#单次学习最大获得:|
+累计工作时间是#累计工作时间是:|
+工作占总时间#工作占总时间:|
+单次工作最大获得#单次工作最大获得:|
+累计购买#累计购买:|
+次更好买商品#次更好买商品:|
+其中最多购买的是#其中最多购买的是:|
+在分类#在分类:|
+自动购买#自动购买:|
+次#次:|
+占全部购买#占全部购买:|
+你订阅了#你订阅了:|
+个mod#个mod:|
+启用的其中的#启用的其中的:|
+你订阅mod数超过了全球#你订阅mod数超过了全球:|
+睡了#睡了:|
+小时的觉#小时的觉:|
+移动了#移动了:|
+的距离#的距离:|
+说了#说了:|
+句话#句话:|
+跳了#跳了:|
+次舞蹈#次舞蹈:|
+摸了#摸了:|
+次头#次头:|
+吃喝玩乐睡, 惨了养成猪了#吃喝玩乐睡, 惨了养成猪了:|
+打开游戏次数是#打开游戏次数是:|
+你照顾的#你照顾的:|
+满状态次数是#满状态次数是:|
+对你的好感度是#对你的好感度是:|
+最喜欢你了主人~ 新的一年请多多关照喵~#最喜欢你了主人~ 新的一年请多多关照喵~:|
+MOD名称重复#MOD名称重复:|
+Food#食物:|
+Star#收藏:|
+Meal#正餐:|
+Snack#零食:|
+Drink#饮料:|
+Functional#功能性:|
+Drug#药品:|
+Gift#礼品:|
+孩子学习老不好,多半是废了,快来试试思维驰学习机#孩子学习老不好,多半是废了,快来试试思维驰学习机:|
+主人还可以再去创意工坊体验更多MOD喵#主人还可以再去创意工坊体验更多MOD喵:|
+点击前往查看#点击前往查看:|
+哼哼~主人,我的考试成绩出炉了哦,快来和我一起看我的成绩单喵#哼哼~主人,我的考试成绩出炉了哦,快来和我一起看我的成绩单喵:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Solution.lps b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Solution.lps
new file mode 100644
index 0000000..9c16a07
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hans/Solution.lps
@@ -0,0 +1,48 @@
+VPET 设置编辑器#VPET 设置编辑器:|
+打开文件#打开文件:|
+从资源管理器打开文件#从资源管理器打开文件:|
+重置#重置:|
+全部保存#全部保存:|
+Mod管理#Mod管理:|
+保存为退出位置#保存为退出位置:|
+设为当前位置#设为当前位置:|
+设为当前窗口左上角顶点坐标的位置#设为当前窗口左上角顶点坐标的位置:|
+每次间隔#每次间隔:|
+备份设置#备份设置:|
+桌宠设置#桌宠设置:|
+桌宠状态#桌宠状态:|
+启用桌宠状态#启用桌宠状态:|
+分钟左右主动进行一次互动 (走路发呆爬墙等) #分钟左右主动进行一次互动 (走路发呆爬墙等) :|
+清空全部#清空全部:|
+搜索名称#搜索名称:|
+链接#链接:|
+每周期一次#每周期一次:|
+搜索模组#搜索模组:|
+清除失效模组#清除失效模组:|
+清除全部模组#清除全部模组:|
+模组名称:#模组名称::|
+作者:#作者::|
+模组版本:#模组版本::|
+游戏版本:#游戏版本::|
+模组路径:#模组路径::|
+启用模组#启用模组:|
+启用模组代码#启用模组代码:|
+打开所在文件夹#打开所在文件夹:|
+打开创意工坊页面#打开创意工坊页面:|
+VPET 存档查看器#VPET 存档查看器:|
+搜索存档#搜索存档:|
+保存时间#保存时间:|
+游玩时长#游玩时长:|
+数据#数据:|
+保存日期#保存日期:|
+模式#模式:|
+等级#等级:|
+哈希检查#哈希检查:|
+值#值:|
+VPET 问题解决工具#VPET 问题解决工具:|
+打开设置编辑器#打开设置编辑器:|
+打开存档查看器#打开存档查看器:|
+打开翻译文本#打开翻译文本:|
+全部重置#全部重置:|
+哈希#哈希:|
+第一次启动桌宠打不开?#第一次启动桌宠打不开?:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Base2401.lps b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Base2401.lps
new file mode 100644
index 0000000..80cfd49
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Base2401.lps
@@ -0,0 +1,3 @@
+由于操作系统的设计,通过我们软件启动的程序可能会在任务管理器中归类为我们软件的子进程,这可能导致CPU/内存占用显示较高#由於操作系統的設計,透過我們軟體啟動的程序可能會在任務管理器中歸類為我們軟體的子進程,這可能導致CPU/內存占用顯示較高:|
+关于CPU/内存占用显示较高的一次性提示#關於CPU/內存占用顯示較高的一次性提示:|
+尝试加载动画和生成缓存\n该步骤可能会耗时比较长\n请耐心等待#嘗試加載動畫和生成緩存\n該步驟可能會耗時比較長\n請耐心等待:|
\ No newline at end of file
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Limit.lps b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Limit.lps
index b1ec887..4c7cf73 100644
--- a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Limit.lps
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Limit.lps
@@ -7,4 +7,14 @@
主人,sbeam秋季促销开始了哦,还有游戏大奖赛,快去给{name}去投一票吧。#主人,sbeam秋季促銷開始了哦,還有遊戲大獎賽,快去給{name}去投一票吧。:|
主人主人,{name}参加了sbeam大奖赛哦,给人家投一票喵#主人主人,{name}參加了sbeam大獎賽哦,給人家投一票喵:|
那个。。主人。。\n人家参加了sbeam大奖赛哦。能不能。。给{name}投一票呢~#那個。。主人。。\n人家參加了sbeam大獎賽哦。能不能。。給{name}投一票呢~:|
-电脑里有一款《虚拟桌宠模拟器》的游戏正在参加2023的sbeam大奖赛,快来给桌宠投一票吧#電腦裡有一款《虛擬桌寵模擬器》的遊戲正在參加2023的sbeam大獎賽,快來給桌寵投一票吧:|
\ No newline at end of file
+电脑里有一款《虚拟桌宠模拟器》的游戏正在参加2023的sbeam大奖赛,快来给桌宠投一票吧#電腦裡有一款《虛擬桌寵模擬器》的遊戲正在參加2023的sbeam大獎賽,快來給桌寵投一票吧:|
+圣诞帽#聖誕帽:|
+圣诞帽_giftintor#一頂普通的帽子,但帶上它誰都可以成為聖誕老人:|
+礼物盒子#禮物盒子:|
+礼物盒子_giftintor#Surprise!!:|
+糖果棒#糖果棒:|
+糖果棒_giftintor#能吃能玩,還能用來打壞小孩的屁股:|
+圣诞草莓奶茶#聖誕草莓奶茶:|
+圣诞草莓奶茶_giftintor#我不知道為什麼是聖誕奶茶而不是聖誕蛋糕,可能繪師喜歡聖誕草莓奶茶吧:|
+萝莉丝姜饼人#蘿莉斯薑餅人:|
+萝莉丝姜饼人_giftintor#蘿莉絲做的自己樣子的薑餅人,要是因為太可愛捨不得吃蘿莉絲會生氣的哦:|
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Prog2312.lps b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Prog2312.lps
new file mode 100644
index 0000000..420c276
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Prog2312.lps
@@ -0,0 +1,107 @@
+统计#統計:|
+主人~多陪陪我~#主人~多陪陪我~:|
+主人~感谢陪伴~#主人~感謝陪伴~:|
+同学#同學。:|
+学长~前辈~#學長~前輩~:|
+朋友#朋友。:|
+兄弟!#兄弟!:|
+挚友#摯友。:|
+不求同年同月同日生,但求同年同月同日打开《虚拟桌宠模拟器》#不求同年同月同日生,但求同年同月同日打開《虛擬桌寵模擬器》:|
+家人#家人。:|
+We are 伐木累~#We are 伐木累~:|
+女鹅#女鵝。:|
+爸妈~ 这么叫好像不太好#爸媽~嗯…這麼叫好像不太洽當…:|
+相当于桌宠的中学学历哦\n<高考桌宠100天>#相當於桌寵的國中學歷哦\n<學測桌寵100天>:|
+在你这个年纪,你怎么睡得着觉的?#在你這個年紀,你怎麼睡得著覺的?:|
+学而不思则罔,思而不学则die#學而不思則罔,思而不學則die。:|
+学习?#學習?:|
+看我量子速读法!#看我量子速讀法!:|
+钱钱乃身外之物#錢錢乃身外之物。:|
+风声雨声读书声声声入耳,日结月结次次结钱钱入账#風聲雨聲讀書聲聲聲入耳,日結月結次次結錢錢入帳。:|
+有钱能使磨推鬼#有錢能使磨推鬼。:|
+可是,我真的很需要那些钱钱!#可是,我真的很需要那些錢錢!:|
+干一天来歇一天, 能混一天是一天#幹一天來歇一天,能混一天是一天。:|
+早8晚5,快乐回家#早八晚五,快樂回家。:|
+加班没有加班费不是基本常识吗?#加班沒有加班費不是基本常識嗎?:|
+老板! 路灯已经准备好了!#老闆!路燈已經準備好了!:|
+啥也没吃,{0}都饿坏了#啥也沒吃,{0}都餓壞了。:|
+人是铁饭是钢, 四菜一汤吃得香#人是鐵飯是鋼,四菜一湯吃得香。:|
+自动购买又忘开了吧?#自動購買又忘記開了吧?:|
+多喝热水#多喝熱水。:|
+不是正餐买不起, 而是功能性更有性价比#不是正餐買不起,而是功能性更有性價比。:|
+多吃零食有益心理健康#多吃零食有益心理健康。:|
+公若不弃,{0}愿拜为义父!#公若不棄,{0}願拜為義父!:|
+主人, 是担心我乱买东西嘛#主人,是擔心我亂買東西嗎?:|
+自己赚的钱自己花#自己賺的錢自己花。:|
+不要小看我的情报网! 你自动购买礼物没关,对不对?#不要小看我的情報網!你自動購買禮物沒關,對不對?:|
+诚招保姆,工资面议#誠徵保姆,薪水面議。:|
+桌宠的steam创意工坊里有许多的mod喵, 主人快去试试吧#桌寵的Steam工作坊裡有許多的模組喵,主人快去試試吧!:|
+创意工坊又更新了很多有趣的mod喵, 主人要不要去看看?#工作坊又更新了很多有趣的模組喵,主人要不要去看看?:|
+主人已经是mod大师了喵,要不要试试mod制作器,给我做mod喵!#主人已經是模組大師了喵,要不要試試模組製作器,給我做模組喵!:|
+统计总结#統計總結:|
+生成统计#生成統計:|
+保存图片#儲存圖片:|
+未使用过作弊模组和修改游戏数据#從未使用作弊模組或修改遊戲數據:|
+同意上传统计数据至Steam排行榜#同意上傳統計資料至Steam排行榜:|
+你第一次遇到了这只可爱的小东西#你第一次遇到了這隻可愛的小東西:|
+初次见面,主人~#初次見面,主人~:|
+你和#你和:|
+一共生活了#一共生活了:|
+r天#天:|
+你陪伴她的时长是#你陪伴她的時數為:|
+r小时#小時:|
+超过了全球#超過了全球:|
+r的主人#的主人:|
+平均一天有#平均一天有:|
+小时在一起#小時在一起:|
+相当于平均和#相當於平均和:|
+在一起的时间#在一起的時間:|
+你的等级是#你的等級是:|
+r级#等:|
+累计获得经验#累計獲得經驗:|
+累计学习时间是#累計學習時間:|
+r分钟#分鐘:|
+单次学习最大获得#單次學習最高獲得:|
+累计工作时间是#累計工作時長:|
+工作占总时间#工作占總時間:|
+单次工作最大获得#單次工作最高獲得:|
+累计购买#累計購買:|
+次更好买商品#次更好買的商品:|
+其中最多购买的是#其中最多購買的是:|
+在分类#類別為:|
+自动购买#自動購買:|
+次#次:|
+占全部购买#占全部購買次數的:|
+你订阅了#你訂閱了:|
+个mod#個模組:|
+启用的其中的#啟用了其中的:|
+你订阅mod数超过了全球#你訂閱的模組數超過了全球:|
+睡了#睡了:|
+小时的觉#小時的覺:|
+移动了#移動了:|
+的距离#的距離:|
+说了#說了:|
+句话#句話:|
+跳了#跳了:|
+次舞蹈#次舞蹈:|
+摸了#摸了:|
+次头#次頭:|
+吃喝玩乐睡, 惨了养成猪了#吃喝玩樂睡,慘了養成猪了:|
+打开游戏次数是#打開遊戲的次數是:|
+你照顾的#你照顧的:|
+满状态次数是#狀態全滿次數是:|
+对你的好感度是#對你的好感度是:|
+最喜欢你了主人~ 新的一年请多多关照喵~#最喜歡你了主人~新的一年請多多關照喵~:|
+MOD名称重复#模組名稱重複:|
+Food#食物:|
+Star#收藏:|
+Meal#正餐:|
+Snack#零食:|
+Drink#飲料:|
+Functional#功能性物品:|
+Drug#藥品:|
+Gift#禮品:|
+孩子学习老不好,多半是废了,快来试试思维驰学习机#孩子學習老不好,多半是廢了,快來試試「思維馳」學習機!:|
+主人还可以再去创意工坊体验更多MOD喵#主人還可以再去工作坊體驗更多模組喵!:|
+点击前往查看#點擊前往查看:|
+哼哼~主人,我的考试成绩出炉了哦,快来和我一起看我的成绩单喵#哼哼~主人,我的考試成績出爐了哦,快來和我一起看我的成績單喵!:|
diff --git a/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Solution.lps b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Solution.lps
new file mode 100644
index 0000000..df3396a
--- /dev/null
+++ b/VPet-Simulator.Windows/mod/0000_core/lang/zh-Hant/Solution.lps
@@ -0,0 +1,48 @@
+VPET 设置编辑器#VPET設定編輯器:|
+打开文件#打開文件:|
+从资源管理器打开文件#從資料總管打開文件:|
+重置#重置:|
+全部保存#全部保存:|
+Mod管理#Mod管理:|
+保存为退出位置#保存為退出位置:|
+设为当前位置#設為當前位置:|
+设为当前窗口左上角顶点坐标的位置#設為當前視窗左上角頂點座標的位置:|
+每次间隔#每次間隔:|
+备份设置#備份設定:|
+桌宠设置#桌寵設定:|
+桌宠状态#桌寵狀態:|
+启用桌宠状态#啟用桌寵狀態:|
+分钟左右主动进行一次互动 (走路发呆爬墙等) #分鐘左右主動進行一次互動(走路發呆爬牆等):|
+清空全部#清空全部:|
+搜索名称#蒐索名稱:|
+链接#鏈接:|
+每周期一次#每週期一次:|
+搜索模组#蒐索模組:|
+清除失效模组#清除失效模組:|
+清除全部模组#清除全部模組:|
+模组名称:#模組名稱::|
+作者:#作者::|
+模组版本:#模組版本::|
+游戏版本:#遊戲版本::|
+模组路径:#模組路徑::|
+启用模组#啟用模組:|
+启用模组代码#啟用模組程式碼:|
+打开所在文件夹#打開所在資料夾:|
+打开创意工坊页面#打開創意工坊頁面:|
+VPET 存档查看器#VPET存檔檢視器:|
+搜索存档#蒐索存檔:|
+保存时间#保存時間:|
+游玩时长#遊玩時長:|
+数据#數據:|
+保存日期#保存日期:|
+模式#模式:|
+等级#等級:|
+哈希检查#雜湊檢查:|
+值#值:|
+VPET 问题解决工具#VPET問題解决工具:|
+打开设置编辑器#打開設定編輯器:|
+打开存档查看器#打開存檔檢視器:|
+打开翻译文本#打開翻譯文字:|
+全部重置#全部重置:|
+哈希#雜湊:|
+第一次启动桌宠打不开?#第一次啟動桌寵打不開?:|
\ No newline at end of file
diff --git a/VPet.Solution/App.xaml b/VPet.Solution/App.xaml
index 085dea1..f8cd87d 100644
--- a/VPet.Solution/App.xaml
+++ b/VPet.Solution/App.xaml
@@ -1,9 +1,17 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VPet.Solution/App.xaml.cs b/VPet.Solution/App.xaml.cs
index a7005a0..bde1717 100644
--- a/VPet.Solution/App.xaml.cs
+++ b/VPet.Solution/App.xaml.cs
@@ -1,42 +1,36 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
+using System.Diagnostics;
using System.Windows;
-using VPet_Simulator.Windows.Interface;
-namespace VPet.Solution
+namespace VPet.Solution;
+
+///
+/// App.xaml 的交互逻辑
+///
+public partial class App : Application
{
- ///
- /// App.xaml 的交互逻辑
- ///
- public partial class App : Application
+ public static bool IsDone { get; set; } = false;
+
+ protected override void OnStartup(StartupEventArgs e)
{
- public static bool IsDone { get; set; } = false;
- protected override void OnStartup(StartupEventArgs e)
+ if (e.Args != null && e.Args.Count() > 0)
{
- if (e.Args != null && e.Args.Count() > 0)
+ IsDone = true;
+ switch (e.Args[0].ToLower())
{
- IsDone = true;
- switch (e.Args[0].ToLower())
- {
- case "removestarup":
- var path = Environment.GetFolderPath(Environment.SpecialFolder.Startup) + @"\VPET_Simulator.lnk";
- if (File.Exists(path))
- {
- File.Delete(path);
- }
- return;
- case "launchsteam":
- Process.Start("steam://rungameid/1920960");
- return;
- }
+ case "removestarup":
+ var path =
+ Environment.GetFolderPath(Environment.SpecialFolder.Startup)
+ + @"\VPET_Simulator.lnk";
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+ return;
+ case "launchsteam":
+ Process.Start("steam://rungameid/1920960");
+ return;
}
- IsDone = false;
}
+ IsDone = false;
}
}
diff --git a/VPet.Solution/Converters.xaml b/VPet.Solution/Converters.xaml
new file mode 100644
index 0000000..71ef646
--- /dev/null
+++ b/VPet.Solution/Converters.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VPet.Solution/Converters/AllIsBoolConverter.cs b/VPet.Solution/Converters/AllIsBoolConverter.cs
new file mode 100644
index 0000000..80a9a09
--- /dev/null
+++ b/VPet.Solution/Converters/AllIsBoolConverter.cs
@@ -0,0 +1,52 @@
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 全部为布尔值转换器
+///
+public class AllIsBoolConverter : MultiValueToBoolConverter
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty BoolOnNullProperty = DependencyProperty.Register(
+ nameof(BoolOnNull),
+ typeof(bool),
+ typeof(AllIsBoolConverter),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 目标为空时的指定值
+ ///
+ [DefaultValue(false)]
+ public bool BoolOnNull
+ {
+ get => (bool)GetValue(BoolOnNullProperty);
+ set => SetValue(BoolOnNullProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object Convert(
+ object[] values,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ var boolValue = TargetBoolValue;
+ var nullValue = BoolOnNull;
+ return values.All(o => Utils.GetBool(o, boolValue, nullValue));
+ }
+}
diff --git a/VPet.Solution/Converters/AllIsBoolToVisibilityConverter.cs b/VPet.Solution/Converters/AllIsBoolToVisibilityConverter.cs
new file mode 100644
index 0000000..b2ec4c9
--- /dev/null
+++ b/VPet.Solution/Converters/AllIsBoolToVisibilityConverter.cs
@@ -0,0 +1,97 @@
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 全部为布尔值转换器
+///
+public class AllIsBoolToVisibilityConverter
+ : MultiValueToBoolConverter
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty BoolOnNullProperty = DependencyProperty.Register(
+ nameof(BoolOnNull),
+ typeof(bool),
+ typeof(AllIsBoolToVisibilityConverter),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 目标为空时的指定值
+ ///
+ [DefaultValue(false)]
+ public bool BoolOnNull
+ {
+ get => (bool)GetValue(BoolOnNullProperty);
+ set => SetValue(BoolOnNullProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty VisibilityOnTrueProperty =
+ DependencyProperty.Register(
+ nameof(VisibilityOnTrue),
+ typeof(Visibility),
+ typeof(AllIsBoolToVisibilityConverter),
+ new PropertyMetadata(Visibility.Visible)
+ );
+
+ ///
+ /// 目标为空时的指定值
+ ///
+ [DefaultValue(Visibility.Visible)]
+ public Visibility VisibilityOnTrue
+ {
+ get => (Visibility)GetValue(TargetBoolValueProperty);
+ set => SetValue(TargetBoolValueProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty VisibilityOnFalseProperty =
+ DependencyProperty.Register(
+ nameof(VisibilityOnFalse),
+ typeof(Visibility),
+ typeof(AllIsBoolToVisibilityConverter),
+ new PropertyMetadata(Visibility.Hidden)
+ );
+
+ ///
+ /// 目标为空时的指定值
+ ///
+ [DefaultValue(Visibility.Hidden)]
+ public Visibility VisibilityOnFalse
+ {
+ get => (Visibility)GetValue(TargetBoolValueProperty);
+ set => SetValue(TargetBoolValueProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object Convert(
+ object[] values,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ var boolValue = TargetBoolValue;
+ var nullValue = BoolOnNull;
+ return values.All(o => Utils.GetBool(o, boolValue, nullValue))
+ ? VisibilityOnTrue
+ : VisibilityOnFalse;
+ }
+}
diff --git a/VPet.Solution/Converters/BoolInverter.cs b/VPet.Solution/Converters/BoolInverter.cs
new file mode 100644
index 0000000..7033fbf
--- /dev/null
+++ b/VPet.Solution/Converters/BoolInverter.cs
@@ -0,0 +1,41 @@
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace HKW.WPF.Converters;
+
+public class BoolInverter : ValueConverterBase
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty NullValueProperty = DependencyProperty.Register(
+ nameof(NullValue),
+ typeof(bool),
+ typeof(BoolInverter),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 为空值时布尔值
+ ///
+ [DefaultValue(false)]
+ public bool NullValue
+ {
+ get => (bool)GetValue(NullValueProperty);
+ set => SetValue(NullValueProperty, value);
+ }
+
+ public override object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (value is not bool b)
+ return NullValue;
+ return !b;
+ }
+}
diff --git a/VPet.Solution/Converters/BoolToVisibilityConverter.cs b/VPet.Solution/Converters/BoolToVisibilityConverter.cs
new file mode 100644
index 0000000..a24c4c9
--- /dev/null
+++ b/VPet.Solution/Converters/BoolToVisibilityConverter.cs
@@ -0,0 +1,63 @@
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace HKW.WPF.Converters;
+
+public class BoolToVisibilityConverter : BoolToValueConverterBase
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty TrueVisibilityValueProperty =
+ DependencyProperty.Register(
+ nameof(TrueVisibilityValue),
+ typeof(Visibility),
+ typeof(BoolToVisibilityConverter),
+ new PropertyMetadata(Visibility.Visible)
+ );
+
+ ///
+ /// 为真时的可见度
+ ///
+ [DefaultValue(Visibility.Visible)]
+ public Visibility TrueVisibilityValue
+ {
+ get => (Visibility)GetValue(TrueVisibilityValueProperty);
+ set => SetValue(TrueVisibilityValueProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty FalseVisibilityValueProperty =
+ DependencyProperty.Register(
+ nameof(FalseVisibilityValue),
+ typeof(Visibility),
+ typeof(BoolToVisibilityConverter),
+ new PropertyMetadata(Visibility.Hidden)
+ );
+
+ ///
+ /// 为假时的可见度
+ ///
+ [DefaultValue(Visibility.Hidden)]
+ public Visibility FalseVisibilityValue
+ {
+ get => (Visibility)GetValue(FalseVisibilityValueProperty);
+ set => SetValue(FalseVisibilityValueProperty, value);
+ }
+
+ public override object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ return Utils.GetBool(value, BoolValue, NullValue)
+ ? TrueVisibilityValue
+ : FalseVisibilityValue;
+ }
+}
diff --git a/VPet.Solution/Converters/BrushToMediaColorConverter.cs b/VPet.Solution/Converters/BrushToMediaColorConverter.cs
new file mode 100644
index 0000000..7597d5c
--- /dev/null
+++ b/VPet.Solution/Converters/BrushToMediaColorConverter.cs
@@ -0,0 +1,39 @@
+using System.Globalization;
+using System.Windows.Media;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 画笔颜色至媒体颜色转换器
+///
+public class BrushToMediaColorConverter : ValueConverterBase
+{
+ ///
+ public override object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (value is not SolidColorBrush brush)
+ throw new ArgumentException(
+ $"Not type: {typeof(SolidColorBrush).FullName}",
+ nameof(value)
+ );
+ return brush.Color;
+ }
+
+ ///
+ public override object ConvertBack(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (value is not Color color)
+ throw new ArgumentException($"Not type: {typeof(Color).FullName}", nameof(value));
+ return new SolidColorBrush(color);
+ }
+}
diff --git a/VPet.Solution/Converters/CalculatorConverter.cs b/VPet.Solution/Converters/CalculatorConverter.cs
new file mode 100644
index 0000000..7590d99
--- /dev/null
+++ b/VPet.Solution/Converters/CalculatorConverter.cs
@@ -0,0 +1,100 @@
+using System.Globalization;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 计算器转换器
+/// 示例:
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+/// //
+///
+///
+///
+///
+///
+///
+///
+/// ]]>
+///
+/// 绑定的数量不正确
+public class CalculatorConverter : MultiValueConverterBase
+{
+ ///
+ public override object Convert(
+ object[] values,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (values.Any(i => i == DependencyProperty.UnsetValue))
+ return 0.0;
+ if (values.Length == 1)
+ return values[0];
+ double result = System.Convert.ToDouble(values[0]);
+ if (parameter is string operators)
+ {
+ if (operators.Length != values.Length - 1)
+ throw new Exception("Parameter error: operator must be one more than parameter");
+ for (int i = 1; i < values.Length - 1; i++)
+ result = Operation(result, operators[i - 1], System.Convert.ToDouble(values[i]));
+ result = Operation(result, operators.Last(), System.Convert.ToDouble(values.Last()));
+ }
+ else
+ {
+ if (System.Convert.ToBoolean(values.Length & 1) is false)
+ throw new Exception("Parameter error: Incorrect quantity");
+ bool isNumber = false;
+ char currentOperator = '0';
+ for (int i = 1; i < values.Length - 1; i++)
+ {
+ if (isNumber is false)
+ {
+ currentOperator = ((string)values[i])[0];
+ isNumber = true;
+ }
+ else
+ {
+ var value = System.Convert.ToDouble(values[i]);
+ result = Operation(result, currentOperator, value);
+ isNumber = false;
+ }
+ }
+ result = Operation(result, currentOperator, System.Convert.ToDouble(values.Last()));
+ }
+ return result;
+ }
+
+ ///
+ /// 计算
+ ///
+ /// 值1
+ /// 符号
+ /// 值2
+ /// 结果
+ ///
+ public static double Operation(double value1, char operatorChar, double value2)
+ {
+ return operatorChar switch
+ {
+ '+' => value1 + value2,
+ '-' => value1 - value2,
+ '*' => value1 * value2,
+ '/' => value1 / value2,
+ '%' => value1 % value2,
+ _ => throw new NotImplementedException(),
+ };
+ }
+}
diff --git a/VPet.Solution/Converters/ConverterBase/BoolToValueConverterBase.cs b/VPet.Solution/Converters/ConverterBase/BoolToValueConverterBase.cs
new file mode 100644
index 0000000..94ebd02
--- /dev/null
+++ b/VPet.Solution/Converters/ConverterBase/BoolToValueConverterBase.cs
@@ -0,0 +1,47 @@
+using System.ComponentModel;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+public abstract class BoolToValueConverterBase : ValueConverterBase
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty BoolValueProperty = DependencyProperty.Register(
+ nameof(BoolValue),
+ typeof(bool),
+ typeof(T),
+ new PropertyMetadata(true)
+ );
+
+ ///
+ /// 目标布尔值
+ ///
+ [DefaultValue(true)]
+ public bool BoolValue
+ {
+ get => (bool)GetValue(BoolValueProperty);
+ set => SetValue(BoolValueProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty NullValueProperty = DependencyProperty.Register(
+ nameof(NullValue),
+ typeof(bool),
+ typeof(T),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 为空值时布尔值
+ ///
+ [DefaultValue(false)]
+ public bool NullValue
+ {
+ get => (bool)GetValue(NullValueProperty);
+ set => SetValue(NullValueProperty, value);
+ }
+}
diff --git a/VPet.Solution/Converters/ConverterBase/CanInverterMultiValueConverter.cs b/VPet.Solution/Converters/ConverterBase/CanInverterMultiValueConverter.cs
new file mode 100644
index 0000000..7736882
--- /dev/null
+++ b/VPet.Solution/Converters/ConverterBase/CanInverterMultiValueConverter.cs
@@ -0,0 +1,31 @@
+using System.ComponentModel;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 可反转值转换器
+///
+public abstract class CanInverterMultiValueConverter : MultiValueConverterBase
+ where T : CanInverterMultiValueConverter
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty InverterProperty = DependencyProperty.Register(
+ nameof(Inverter),
+ typeof(bool),
+ typeof(AllIsBoolToVisibilityConverter),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 反转
+ ///
+ [DefaultValue(false)]
+ public bool Inverter
+ {
+ get => (bool)GetValue(InverterProperty);
+ set => SetValue(InverterProperty, value);
+ }
+}
diff --git a/VPet.Solution/Converters/ConverterBase/CanInverterValueConverter.cs b/VPet.Solution/Converters/ConverterBase/CanInverterValueConverter.cs
new file mode 100644
index 0000000..9c7a9a9
--- /dev/null
+++ b/VPet.Solution/Converters/ConverterBase/CanInverterValueConverter.cs
@@ -0,0 +1,31 @@
+using System.ComponentModel;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 可反转值转换器
+///
+public abstract class CanInverterValueConverter : ValueConverterBase
+ where T : CanInverterValueConverter
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty InverterProperty = DependencyProperty.Register(
+ nameof(Inverter),
+ typeof(bool),
+ typeof(AllIsBoolToVisibilityConverter),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 反转
+ ///
+ [DefaultValue(false)]
+ public bool Inverter
+ {
+ get => (bool)GetValue(InverterProperty);
+ set => SetValue(InverterProperty, value);
+ }
+}
diff --git a/VPet.Solution/Converters/ConverterBase/ConverterBase.cs b/VPet.Solution/Converters/ConverterBase/ConverterBase.cs
new file mode 100644
index 0000000..875fc41
--- /dev/null
+++ b/VPet.Solution/Converters/ConverterBase/ConverterBase.cs
@@ -0,0 +1,15 @@
+using System.Globalization;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 值转换器基础
+///
+public abstract class ConverterBase : DependencyObject
+{
+ ///
+ /// 未设置值
+ ///
+ public static readonly object UnsetValue = DependencyProperty.UnsetValue;
+}
diff --git a/VPet.Solution/Converters/ConverterBase/HaveRatioConverter.cs b/VPet.Solution/Converters/ConverterBase/HaveRatioConverter.cs
new file mode 100644
index 0000000..bb3ff52
--- /dev/null
+++ b/VPet.Solution/Converters/ConverterBase/HaveRatioConverter.cs
@@ -0,0 +1,32 @@
+using System.ComponentModel;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 含有比率的转换器
+///
+/// 转换器类型
+public abstract class HaveRatioConverter : MultiValueConverterBase
+ where T : HaveRatioConverter
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty HaveRatioProperty = DependencyProperty.Register(
+ nameof(HaveRatio),
+ typeof(bool),
+ typeof(T),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 含有比例
+ ///
+ [DefaultValue(false)]
+ public bool HaveRatio
+ {
+ get => (bool)GetValue(HaveRatioProperty);
+ set => SetValue(HaveRatioProperty, value);
+ }
+}
diff --git a/VPet.Solution/Converters/ConverterBase/MultiValueConverterBase.cs b/VPet.Solution/Converters/ConverterBase/MultiValueConverterBase.cs
new file mode 100644
index 0000000..c2700c4
--- /dev/null
+++ b/VPet.Solution/Converters/ConverterBase/MultiValueConverterBase.cs
@@ -0,0 +1,45 @@
+using System.Globalization;
+using System.Windows.Data;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 多个值转换器
+///
+public abstract class MultiValueConverterBase : ConverterBase, IMultiValueConverter
+{
+ ///
+ /// 转换
+ ///
+ /// 值
+ /// 目标类型
+ /// 参数
+ /// 文化
+ /// 转换后的值
+ public abstract object Convert(
+ object[] values,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ );
+
+ ///
+ /// 转换回调
+ ///
+ /// 值
+ /// 目标类型
+ /// 参数
+ /// 文化
+ /// 转换后的值
+ public virtual object[] ConvertBack(
+ object value,
+ Type[] targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ throw new NotSupportedException(
+ "Converter '" + this.GetType().Name + "' does not support backward conversion."
+ );
+ }
+}
diff --git a/VPet.Solution/Converters/ConverterBase/MultiValueToBoolConverter.cs b/VPet.Solution/Converters/ConverterBase/MultiValueToBoolConverter.cs
new file mode 100644
index 0000000..abd981c
--- /dev/null
+++ b/VPet.Solution/Converters/ConverterBase/MultiValueToBoolConverter.cs
@@ -0,0 +1,31 @@
+using System.ComponentModel;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 转换至布尔转换器基础
+///
+public abstract class MultiValueToBoolConverter : MultiValueConverterBase
+ where T : MultiValueToBoolConverter
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty TargetBoolValueProperty = DependencyProperty.Register(
+ nameof(TargetBoolValue),
+ typeof(bool),
+ typeof(T),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 指定值
+ ///
+ [DefaultValue(false)]
+ public bool TargetBoolValue
+ {
+ get => (bool)GetValue(TargetBoolValueProperty);
+ set => SetValue(TargetBoolValueProperty, value);
+ }
+}
diff --git a/VPet.Solution/Converters/ConverterBase/ValueConverterBase.cs b/VPet.Solution/Converters/ConverterBase/ValueConverterBase.cs
new file mode 100644
index 0000000..f3b56ff
--- /dev/null
+++ b/VPet.Solution/Converters/ConverterBase/ValueConverterBase.cs
@@ -0,0 +1,45 @@
+using System.Globalization;
+using System.Windows.Data;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 值转换器
+///
+public abstract class ValueConverterBase : ConverterBase, IValueConverter
+{
+ ///
+ /// 转换
+ ///
+ /// 值
+ /// 目标类型
+ /// 参数
+ /// 文化
+ /// 转换后的值
+ public abstract object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ );
+
+ ///
+ /// 转换回调
+ ///
+ /// 值
+ /// 目标类型
+ /// 参数
+ /// 文化
+ /// 转换后的值
+ public virtual object ConvertBack(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ throw new NotSupportedException(
+ "Converter '" + GetType().Name + "' does not support backward conversion."
+ );
+ }
+}
diff --git a/VPet.Solution/Converters/EqualsConverter.cs b/VPet.Solution/Converters/EqualsConverter.cs
new file mode 100644
index 0000000..62a8b20
--- /dev/null
+++ b/VPet.Solution/Converters/EqualsConverter.cs
@@ -0,0 +1,37 @@
+using System.Globalization;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 相等转换器
+/// 示例:
+///
+///
+///
+///
+/// ]]>
+///
+public class EqualsConverter : CanInverterMultiValueConverter
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object Convert(
+ object[] values,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (values.Length != 2)
+ throw new NotImplementedException("Values length must be 2");
+ return values[0].Equals(values[1]) ^ Inverter;
+ }
+}
diff --git a/VPet.Solution/Converters/IsBoolConverter.cs b/VPet.Solution/Converters/IsBoolConverter.cs
new file mode 100644
index 0000000..cff414f
--- /dev/null
+++ b/VPet.Solution/Converters/IsBoolConverter.cs
@@ -0,0 +1,62 @@
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 是布尔值转换器
+///
+public class IsBoolConverter : ValueConverterBase
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty BoolValueProperty = DependencyProperty.Register(
+ nameof(BoolValue),
+ typeof(bool),
+ typeof(AllIsBoolToVisibilityConverter),
+ new PropertyMetadata(true)
+ );
+
+ ///
+ /// 目标布尔值
+ ///
+ [DefaultValue(true)]
+ public bool BoolValue
+ {
+ get => (bool)GetValue(BoolValueProperty);
+ set => SetValue(BoolValueProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty NullValueProperty = DependencyProperty.Register(
+ nameof(NullValue),
+ typeof(bool),
+ typeof(AllIsBoolToVisibilityConverter),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 为空值时布尔值
+ ///
+ [DefaultValue(false)]
+ public bool NullValue
+ {
+ get => (bool)GetValue(NullValueProperty);
+ set => SetValue(NullValueProperty, value);
+ }
+
+ ///
+ public override object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ return Utils.GetBool(value, BoolValue, NullValue);
+ }
+}
diff --git a/VPet.Solution/Converters/MarginConverter.cs b/VPet.Solution/Converters/MarginConverter.cs
new file mode 100644
index 0000000..83feab4
--- /dev/null
+++ b/VPet.Solution/Converters/MarginConverter.cs
@@ -0,0 +1,138 @@
+using System.Globalization;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 边距转换器
+/// 示例:
+///
+///
+///
+///
+///
+///
+/// ]]>
+///
+public class MarginConverter : HaveRatioConverter
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object Convert(
+ object[] values,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (values.Any(i => i == DependencyProperty.UnsetValue))
+ return new Thickness();
+ if (values.Length == 0)
+ {
+ return new Thickness();
+ }
+ if (HaveRatio)
+ {
+ if (values.Length == 1)
+ {
+ return new Thickness();
+ }
+ var ratio = System.Convert.ToDouble(values[0]);
+ if (values.Length == 2)
+ {
+ return new Thickness()
+ {
+ Left = System.Convert.ToDouble(values[1]) * ratio,
+ Top = default,
+ Right = default,
+ Bottom = default
+ };
+ }
+ else if (values.Length == 3)
+ {
+ return new Thickness()
+ {
+ Left = System.Convert.ToDouble(values[1]) * ratio,
+ Top = System.Convert.ToDouble(values[2]) * ratio,
+ Right = default,
+ Bottom = default
+ };
+ }
+ else if (values.Length == 4)
+ {
+ return new Thickness()
+ {
+ Left = System.Convert.ToDouble(values[1]) * ratio,
+ Top = System.Convert.ToDouble(values[2]) * ratio,
+ Right = System.Convert.ToDouble(values[3]) * ratio,
+ Bottom = default
+ };
+ }
+ else if (values.Length == 5)
+ {
+ return new Thickness()
+ {
+ Left = System.Convert.ToDouble(values[1]) * ratio,
+ Top = System.Convert.ToDouble(values[2]) * ratio,
+ Right = System.Convert.ToDouble(values[3]) * ratio,
+ Bottom = System.Convert.ToDouble(values[4]) * ratio
+ };
+ }
+ else
+ throw new NotImplementedException();
+ }
+ else
+ {
+ if (values.Length == 1)
+ {
+ return new Thickness()
+ {
+ Left = System.Convert.ToDouble(values[0]),
+ Top = default,
+ Right = default,
+ Bottom = default
+ };
+ }
+ else if (values.Length == 2)
+ {
+ return new Thickness()
+ {
+ Left = System.Convert.ToDouble(values[0]),
+ Top = System.Convert.ToDouble(values[1]),
+ Right = default,
+ Bottom = default
+ };
+ }
+ else if (values.Length == 3)
+ {
+ return new Thickness()
+ {
+ Left = System.Convert.ToDouble(values[0]),
+ Top = System.Convert.ToDouble(values[1]),
+ Right = System.Convert.ToDouble(values[2]),
+ Bottom = default
+ };
+ }
+ else if (values.Length == 4)
+ {
+ return new Thickness()
+ {
+ Left = System.Convert.ToDouble(values[0]),
+ Top = System.Convert.ToDouble(values[1]),
+ Right = System.Convert.ToDouble(values[2]),
+ Bottom = System.Convert.ToDouble(values[3])
+ };
+ }
+ else
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/VPet.Solution/Converters/MaxConverter.cs b/VPet.Solution/Converters/MaxConverter.cs
new file mode 100644
index 0000000..a91d8fb
--- /dev/null
+++ b/VPet.Solution/Converters/MaxConverter.cs
@@ -0,0 +1,18 @@
+using System.Windows.Data;
+
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+public class MaxConverter : MultiValueConverterBase
+{
+ public override object Convert(
+ object[] values,
+ Type targetType,
+ object parameter,
+ System.Globalization.CultureInfo culture
+ )
+ {
+ return values.Max(i => (double)i);
+ }
+}
diff --git a/VPet.Solution/Converters/MediaColorToBrushConverter.cs b/VPet.Solution/Converters/MediaColorToBrushConverter.cs
new file mode 100644
index 0000000..8d11f30
--- /dev/null
+++ b/VPet.Solution/Converters/MediaColorToBrushConverter.cs
@@ -0,0 +1,55 @@
+using System.Globalization;
+using System.Windows.Media;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 媒体颜色至画笔颜色转换器
+///
+public class MediaColorToBrushConverter : ValueConverterBase
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (value is not Color color)
+ throw new ArgumentException($"Not type: {typeof(Color).FullName}", nameof(value));
+ return new SolidColorBrush(color);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object ConvertBack(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (value is not SolidColorBrush brush)
+ throw new ArgumentException(
+ $"Not type: {typeof(SolidColorBrush).FullName}",
+ nameof(value)
+ );
+ return brush.Color;
+ }
+}
diff --git a/VPet.Solution/Converters/NullToVisibilityConverter.cs b/VPet.Solution/Converters/NullToVisibilityConverter.cs
new file mode 100644
index 0000000..5b7ee71
--- /dev/null
+++ b/VPet.Solution/Converters/NullToVisibilityConverter.cs
@@ -0,0 +1,60 @@
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+public class NullToVisibilityConverter : ValueConverterBase
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty NullVisibilityValueProperty =
+ DependencyProperty.Register(
+ nameof(NullVisibilityValue),
+ typeof(Visibility),
+ typeof(NullToVisibilityConverter),
+ new PropertyMetadata(Visibility.Hidden)
+ );
+
+ ///
+ /// NULL时的可见度
+ ///
+ [DefaultValue(Visibility.Hidden)]
+ public Visibility NullVisibilityValue
+ {
+ get => (Visibility)GetValue(NullVisibilityValueProperty);
+ set => SetValue(NullVisibilityValueProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty NotNullVisibilityValueProperty =
+ DependencyProperty.Register(
+ nameof(NotNullVisibilityValue),
+ typeof(Visibility),
+ typeof(NullToVisibilityConverter),
+ new PropertyMetadata(Visibility.Visible)
+ );
+
+ ///
+ /// 不为NULL时的可见度
+ ///
+ [DefaultValue(Visibility.Visible)]
+ public Visibility NotNullVisibilityValue
+ {
+ get => (Visibility)GetValue(NotNullVisibilityValueProperty);
+ set => SetValue(NotNullVisibilityValueProperty, value);
+ }
+
+ public override object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ return value is null ? NullVisibilityValue : NotNullVisibilityValue;
+ }
+}
diff --git a/VPet.Solution/Converters/StringFormatConverter.cs b/VPet.Solution/Converters/StringFormatConverter.cs
new file mode 100644
index 0000000..67eff9c
--- /dev/null
+++ b/VPet.Solution/Converters/StringFormatConverter.cs
@@ -0,0 +1,49 @@
+using System.Globalization;
+
+namespace HKW.WPF.Converters;
+
+///
+/// 字符串格式化器
+/// 示例:
+///
+///
+///
+///
+///
+/// OR
+///
+///
+///
+///
+/// ]]>
+///
+public class StringFormatConverter : MultiValueConverterBase
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object Convert(
+ object[] values,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ var formatStr = (string)parameter;
+ if (string.IsNullOrWhiteSpace(formatStr))
+ {
+ formatStr = (string)values[0];
+ return string.Format(formatStr, values.Skip(1).ToArray());
+ }
+ else
+ {
+ return string.Format(formatStr, values);
+ }
+ }
+}
diff --git a/VPet.Solution/Converters/ValueToBoolConverter.cs b/VPet.Solution/Converters/ValueToBoolConverter.cs
new file mode 100644
index 0000000..eb8f9b0
--- /dev/null
+++ b/VPet.Solution/Converters/ValueToBoolConverter.cs
@@ -0,0 +1,86 @@
+using HKW.WPF.Converters;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace HKW.WPF.Converters;
+
+public class ValueToBoolConverter : ValueConverterBase
+{
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty TargetValueProperty = DependencyProperty.Register(
+ nameof(TargetValue),
+ typeof(object),
+ typeof(ValueToBoolConverter),
+ new PropertyMetadata(null)
+ );
+
+ ///
+ /// 目标值
+ ///
+ [DefaultValue(true)]
+ public object TargetValue
+ {
+ get => (object)GetValue(TargetValueProperty);
+ set => SetValue(TargetValueProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty InvertProperty = DependencyProperty.Register(
+ nameof(Invert),
+ typeof(bool),
+ typeof(ValueToBoolConverter),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 颠倒
+ ///
+ [DefaultValue(false)]
+ public bool Invert
+ {
+ get => (bool)GetValue(InvertProperty);
+ set => SetValue(InvertProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty NullValueProperty = DependencyProperty.Register(
+ nameof(NullValue),
+ typeof(bool),
+ typeof(ValueToBoolConverter),
+ new PropertyMetadata(false)
+ );
+
+ ///
+ /// 为空值时布尔值
+ ///
+ [DefaultValue(false)]
+ public bool NullValue
+ {
+ get => (bool)GetValue(NullValueProperty);
+ set => SetValue(NullValueProperty, value);
+ }
+
+ public override object Convert(
+ object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture
+ )
+ {
+ if (value is null)
+ return NullValue;
+ return value.Equals(TargetValue) ^ Invert;
+ }
+}
diff --git a/VPet.Solution/MainWindow.xaml b/VPet.Solution/MainWindow.xaml
deleted file mode 100644
index d01cfd8..0000000
--- a/VPet.Solution/MainWindow.xaml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
diff --git a/VPet.Solution/Models/ModLoader.cs b/VPet.Solution/Models/ModLoader.cs
new file mode 100644
index 0000000..2fcfb49
--- /dev/null
+++ b/VPet.Solution/Models/ModLoader.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using LinePutScript;
+using LinePutScript.Converter;
+using LinePutScript.Dictionary;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using VPet_Simulator.Core;
+using VPet_Simulator.Windows.Interface;
+using System.Windows.Media.Imaging;
+using LinePutScript.Localization.WPF;
+
+namespace VPet.Solution.Models;
+
+///
+/// 模组加载器
+///
+public class ModLoader
+{
+ ///
+ /// 名称
+ ///
+ public string Name { get; }
+
+ ///
+ /// 作者
+ ///
+ public string Author { get; }
+
+ ///
+ /// 如果是上传至Steam,则为SteamUserID
+ ///
+ public long AuthorID { get; }
+
+ ///
+ /// 上传至Steam的ItemID
+ ///
+ public ulong ItemID { get; }
+
+ ///
+ /// 简介
+ ///
+ public string Intro { get; }
+
+ ///
+ /// 模组路径
+ ///
+ public string ModPath { get; }
+
+ ///
+ /// 支持的游戏版本
+ ///
+ public int GameVer { get; }
+
+ ///
+ /// 版本
+ ///
+ public int Ver { get; }
+
+ ///
+ /// 标签
+ ///
+ public HashSet Tags { get; } = new();
+
+ ///
+ /// 缓存数据
+ ///
+ public DateTime CacheDate { get; } = DateTime.MinValue;
+
+ public BitmapImage? Image { get; } = null;
+
+ ///
+ /// 读取成功
+ ///
+ public bool IsSuccesses { get; } = true;
+
+ public ModLoader(string path)
+ {
+ ModPath = path;
+ var infoFile = Path.Combine(path, "info.lps");
+ if (File.Exists(infoFile) is false)
+ {
+ Name = Path.GetFileName(path);
+ IsSuccesses = false;
+ return;
+ }
+ var modlps = new LpsDocument(File.ReadAllText(infoFile));
+ 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);
+ var imagePath = Path.Combine(path, "icon.png");
+ //加载翻译
+ foreach (var line in modlps.FindAllLine("lang"))
+ {
+ var lps = new LPS();
+ foreach (var sub in line)
+ lps.Add(new Line(sub.Name, sub.Info));
+ LocalizeCore.AddCulture(line.Info, lps);
+ }
+ if (File.Exists(imagePath))
+ {
+ try
+ {
+ Image = Utils.LoadImageToStream(imagePath);
+ }
+ catch { }
+ }
+ foreach (var dir in Directory.EnumerateDirectories(path))
+ {
+ var dirName = Path.GetFileName(dir);
+ switch (dirName.ToLower())
+ {
+ case "pet":
+ //宠物模型
+ Tags.Add("pet");
+ break;
+ case "food":
+ Tags.Add("food");
+ break;
+ case "image":
+ Tags.Add("image");
+ break;
+ case "text":
+ Tags.Add("text");
+ break;
+ case "lang":
+ Tags.Add("lang");
+ foreach (var langFile in Directory.EnumerateFiles(dir, "*.lps"))
+ {
+ LocalizeCore.AddCulture(
+ Path.GetFileNameWithoutExtension(dir),
+ new LPS_D(File.ReadAllText(langFile))
+ );
+ }
+ foreach (var langDir in Directory.EnumerateDirectories(dir))
+ {
+ foreach (var langFile in Directory.EnumerateFiles(langDir, "*.lps"))
+ {
+ LocalizeCore.AddCulture(
+ Path.GetFileNameWithoutExtension(langDir),
+ new LPS_D(File.ReadAllText(langFile))
+ );
+ }
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/VPet.Solution/Models/SaveViewer/SaveModel.cs b/VPet.Solution/Models/SaveViewer/SaveModel.cs
new file mode 100644
index 0000000..58e26d0
--- /dev/null
+++ b/VPet.Solution/Models/SaveViewer/SaveModel.cs
@@ -0,0 +1,226 @@
+using HKW.HKWUtils.Observable;
+using LinePutScript.Localization.WPF;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VPet_Simulator.Windows.Interface;
+
+namespace VPet.Solution.Models.SaveViewer;
+
+///
+/// 存档模型
+///
+public class SaveModel : ObservableClass
+{
+ ///
+ /// 名称
+ ///
+ [ReflectionPropertyIgnore]
+ public string Name { get; set; }
+
+ ///
+ /// 文件路径
+ ///
+ public string FilePath { get; set; }
+
+ ///
+ /// 统计数据
+ ///
+ public ObservableCollection Statistics { get; set; } = new();
+
+ ///
+ /// 是损坏的
+ ///
+ public bool IsDamaged { get; set; }
+
+ #region DateSaved
+ private DateTime _dateSaved;
+ public DateTime DateSaved
+ {
+ get => _dateSaved;
+ set => SetProperty(ref _dateSaved, value);
+ }
+ #endregion
+
+
+ #region PetName
+ private string _petName;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Name))]
+ public string PetName
+ {
+ get => _petName;
+ set => SetProperty(ref _petName, value);
+ }
+ #endregion
+
+ #region Level
+ private int _level;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Level))]
+ public int Level
+ {
+ get => _level;
+ set => SetProperty(ref _level, value);
+ }
+ #endregion
+
+ #region Money
+ private double _money = 100;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Money))]
+ public double Money
+ {
+ get => _money;
+ set => SetProperty(ref _money, value);
+ }
+ #endregion
+
+ #region Exp
+ private double _exp = 0;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Exp))]
+ public double Exp
+ {
+ get => _exp;
+ set => SetProperty(ref _exp, value);
+ }
+ #endregion
+
+ #region Feeling
+ private double _feeling = 60;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Feeling))]
+ public double Feeling
+ {
+ get => _feeling;
+ set => SetProperty(ref _feeling, value);
+ }
+ #endregion
+
+ #region Health
+ private double _health = 100;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Health))]
+ public double Health
+ {
+ get => _health;
+ set => SetProperty(ref _health, value);
+ }
+ #endregion
+
+ #region Likability
+ private double _likability = 0;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Likability))]
+ public double Likability
+ {
+ get => _likability;
+ set => SetProperty(ref _likability, value);
+ }
+ #endregion
+
+ #region Mode
+ private VPet_Simulator.Core.GameSave.ModeType _mode;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Mode))]
+ public VPet_Simulator.Core.GameSave.ModeType Mode
+ {
+ get => _mode;
+ set => SetProperty(ref _mode, value);
+ }
+ #endregion
+
+ #region Strength
+ private double _strength = 100;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.Strength))]
+ public double Strength
+ {
+ get => _strength;
+ set => SetProperty(ref _strength, value);
+ }
+ #endregion
+
+ #region StrengthFood
+ private double _strengthFood = 100;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.StrengthFood))]
+ public double StrengthFood
+ {
+ get => _strengthFood;
+ set => SetProperty(ref _strengthFood, value);
+ }
+ #endregion
+
+ #region StrengthDrink
+ private double _strengthDrink = 100;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Core.GameSave.StrengthDrink))]
+ public double StrengthDrink
+ {
+ get => _strengthDrink;
+ set => SetProperty(ref _strengthDrink, value);
+ }
+ #endregion
+
+
+ #region HashChecked
+ private bool _hashChecked;
+
+ ///
+ /// Hash已检查
+ ///
+ public bool HashChecked
+ {
+ get => _hashChecked;
+ set => SetProperty(ref _hashChecked, value);
+ }
+ #endregion
+
+
+
+ #region TotalTime
+ private long _totalTime;
+
+ ///
+ /// 游玩总时长
+ ///
+ public long TotalTime
+ {
+ get => _totalTime;
+ set => SetProperty(ref _totalTime, value);
+ }
+ #endregion
+
+
+ public SaveModel(string filePath, GameSave_v2 save)
+ {
+ Name = Path.GetFileNameWithoutExtension(filePath);
+ FilePath = filePath;
+ DateSaved = File.GetLastWriteTime(filePath);
+ LoadSave(save.GameSave);
+ if (save.Statistics.Data.TryGetValue("stat_total_time", out var time))
+ TotalTime = time.GetInteger64();
+ HashChecked = save.HashCheck;
+ foreach (var data in save.Statistics.Data)
+ {
+ Statistics.Add(
+ new()
+ {
+ Id = data.Key,
+ Name = data.Key.Translate(),
+ Value = data.Value
+ }
+ );
+ }
+ }
+
+ private void LoadSave(VPet_Simulator.Core.GameSave save)
+ {
+ ReflectionUtils.SetValue(save, this);
+ }
+}
diff --git a/VPet.Solution/Models/SaveViewer/StatisticDataModel.cs b/VPet.Solution/Models/SaveViewer/StatisticDataModel.cs
new file mode 100644
index 0000000..37358bc
--- /dev/null
+++ b/VPet.Solution/Models/SaveViewer/StatisticDataModel.cs
@@ -0,0 +1,46 @@
+namespace VPet.Solution.Models.SaveViewer;
+
+///
+/// 统计数据模型
+///
+public class StatisticDataModel : ObservableClass
+{
+ #region Id
+ private string _id;
+
+ ///
+ /// ID
+ ///
+ public string Id
+ {
+ get => _id;
+ set => SetProperty(ref _id, value);
+ }
+ #endregion
+
+ #region Name
+ private string _name;
+
+ ///
+ /// 名称
+ ///
+ public string Name
+ {
+ get => _name;
+ set => SetProperty(ref _name, value);
+ }
+ #endregion
+
+ #region Value
+ private object _value;
+
+ ///
+ /// 值
+ ///
+ public object Value
+ {
+ get => _value;
+ set => SetProperty(ref _value, value);
+ }
+ #endregion
+}
diff --git a/VPet.Solution/Models/SettingEditor/CustomizedSettingModel.cs b/VPet.Solution/Models/SettingEditor/CustomizedSettingModel.cs
new file mode 100644
index 0000000..3cdc518
--- /dev/null
+++ b/VPet.Solution/Models/SettingEditor/CustomizedSettingModel.cs
@@ -0,0 +1,55 @@
+using System.Collections.ObjectModel;
+
+namespace VPet.Solution.Models.SettingEditor;
+
+public class CustomizedSettingModel : ObservableClass
+{
+ public const string TargetName = "diy";
+
+ #region Links
+ private ObservableCollection _links = new();
+ public ObservableCollection Links
+ {
+ get => _links;
+ set => SetProperty(ref _links, value);
+ }
+ #endregion
+}
+
+public class LinkModel : ObservableClass
+{
+ #region Name
+ private string _name;
+
+ ///
+ /// 名称
+ ///
+ public string Name
+ {
+ get => _name;
+ set => SetProperty(ref _name, value);
+ }
+ #endregion
+
+
+ #region Link
+ private string _link;
+
+ ///
+ /// 链接
+ ///
+ public string Link
+ {
+ get => _link;
+ set => SetProperty(ref _link, value);
+ }
+ #endregion
+
+ public LinkModel() { }
+
+ public LinkModel(string name, string link)
+ {
+ Name = name;
+ Link = link;
+ }
+}
diff --git a/VPet.Solution/Models/SettingEditor/DiagnosticSettingModel.cs b/VPet.Solution/Models/SettingEditor/DiagnosticSettingModel.cs
new file mode 100644
index 0000000..b070ba9
--- /dev/null
+++ b/VPet.Solution/Models/SettingEditor/DiagnosticSettingModel.cs
@@ -0,0 +1,68 @@
+using HKW.HKWUtils.Observable;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VPet_Simulator.Windows.Interface;
+
+namespace VPet.Solution.Models.SettingEditor;
+
+public class DiagnosticSettingModel : ObservableClass
+{
+ #region AutoCal
+ private bool _autoCal;
+
+ ///
+ /// 自动修复超模
+ ///
+ public bool AutoCal
+ {
+ get => _autoCal;
+ set => SetProperty(ref _autoCal, value);
+ }
+ #endregion
+
+ #region Diagnosis
+ private bool _diagnosis;
+
+ ///
+ /// 是否启用数据收集
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.Diagnosis))]
+ public bool Diagnosis
+ {
+ get => _diagnosis;
+ set => SetProperty(ref _diagnosis, value);
+ }
+ #endregion
+
+ #region DiagnosisInterval
+ private int _diagnosisInterval = 500;
+
+ ///
+ /// 数据收集频率
+ ///
+ [DefaultValue(500)]
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.DiagnosisInterval))]
+ public int DiagnosisInterval
+ {
+ get => _diagnosisInterval;
+ set => SetProperty(ref _diagnosisInterval, value);
+ }
+ public static ObservableCollection DiagnosisIntervals { get; } =
+ new() { 200, 500, 1000, 2000, 5000, 10000, 20000 };
+ #endregion
+
+ public void GetAutoCalFromSetting(Setting setting)
+ {
+ AutoCal = setting["gameconfig"].GetBool("noAutoCal") is false;
+ }
+
+ public void SetAutoCalToSetting(Setting setting)
+ {
+ setting["gameconfig"].SetBool("noAutoCal", AutoCal is false);
+ }
+}
diff --git a/VPet.Solution/Models/SettingEditor/GraphicsSettingModel.cs b/VPet.Solution/Models/SettingEditor/GraphicsSettingModel.cs
new file mode 100644
index 0000000..ea6047a
--- /dev/null
+++ b/VPet.Solution/Models/SettingEditor/GraphicsSettingModel.cs
@@ -0,0 +1,296 @@
+using HKW.HKWUtils.Observable;
+using LinePutScript.Localization.WPF;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows;
+
+namespace VPet.Solution.Models.SettingEditor;
+
+public class GraphicsSettingModel : ObservableClass
+{
+ #region ZoomLevel
+ private double _zoomLevel = 1;
+
+ ///
+ /// 缩放倍率
+ ///
+ [DefaultValue(1)]
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.ZoomLevel))]
+ public double ZoomLevel
+ {
+ get => _zoomLevel;
+ set => SetProperty(ref _zoomLevel, value);
+ }
+
+ private double _zoomLevelMinimum = 0.5;
+
+ [DefaultValue(0.5)]
+ public double ZoomLevelMinimum
+ {
+ get => _zoomLevelMinimum;
+ set => SetProperty(ref _zoomLevelMinimum, value);
+ }
+
+ private double _zoomLevelMaximum = 3;
+
+ [DefaultValue(3)]
+ public double ZoomLevelMaximum
+ {
+ get => _zoomLevelMaximum;
+ set => SetProperty(ref _zoomLevelMaximum, value);
+ }
+ #endregion
+
+ #region Resolution
+ private int _resolution = 1000;
+
+ ///
+ /// 桌宠图形渲染的分辨率,越高图形越清晰
+ ///
+ [DefaultValue(1000)]
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.Resolution))]
+ public int Resolution
+ {
+ get => _resolution;
+ set => SetProperty(ref _resolution, value);
+ }
+ #endregion
+
+ #region IsBiggerScreen
+ private bool _isBiggerScreen;
+
+ ///
+ /// 是否为更大的屏幕
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.IsBiggerScreen))]
+ public bool IsBiggerScreen
+ {
+ get => _isBiggerScreen;
+ set
+ {
+ SetProperty(ref _isBiggerScreen, value);
+ if (value is true)
+ ZoomLevelMaximum = 8;
+ else
+ ZoomLevelMaximum = 3;
+ }
+ }
+ #endregion
+
+ #region TopMost
+ private bool _topMost;
+
+ ///
+ /// 是否置于顶层
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.TopMost))]
+ public bool TopMost
+ {
+ get => _topMost;
+ set => SetProperty(ref _topMost, value);
+ }
+ #endregion
+
+ #region HitThrough
+ private bool _hitThrough;
+
+ ///
+ /// 是否鼠标穿透
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.HitThrough))]
+ public bool HitThrough
+ {
+ get => _hitThrough;
+ set => SetProperty(ref _hitThrough, value);
+ }
+ #endregion
+
+ #region Language
+ private string _language = Languages.First();
+
+ ///
+ /// 语言
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.Language))]
+ public string Language
+ {
+ get => _language;
+ set => SetProperty(ref _language, value);
+ }
+
+ public static ObservableCollection Languages { get; } =
+ new() { "zh-Hans", "zh-Hant", "en" };
+ // NOTE: 实际上这里面并没有文化 new(LocalizeCore.AvailableCultures);
+
+ #endregion
+
+ #region Font
+ private string _font;
+
+ ///
+ /// 字体
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.Font))]
+ public string Font
+ {
+ get => _font;
+ set => SetProperty(ref _font, value);
+ }
+ #endregion
+
+ #region Theme
+ private string _theme;
+
+ ///
+ /// 主题
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.Theme))]
+ public string Theme
+ {
+ get => _theme;
+ set => SetProperty(ref _theme, value);
+ }
+ #endregion
+
+ #region StartUPBoot
+ private bool _startUPBoot;
+
+ ///
+ /// 开机启动
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.StartUPBoot))]
+ public bool StartUPBoot
+ {
+ get => _startUPBoot;
+ set => SetProperty(ref _startUPBoot, value);
+ }
+ #endregion
+
+ #region StartUPBootSteam
+ private bool _startUPBootSteam;
+
+ ///
+ /// 开机启动 Steam
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.StartUPBootSteam))]
+ public bool StartUPBootSteam
+ {
+ get => _startUPBootSteam;
+ set => SetProperty(ref _startUPBootSteam, value);
+ }
+ #endregion
+
+ #region StartRecordLast
+ private bool _startRecordLast = true;
+
+ ///
+ /// 是否记录游戏退出位置
+ ///
+ [DefaultValue(true)]
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.StartRecordLast))]
+ public bool StartRecordLast
+ {
+ get => _startRecordLast;
+ set => SetProperty(ref _startRecordLast, value);
+ }
+ #endregion
+ //private Point _startRecordLastPoint;
+
+ /////
+ ///// 记录上次退出位置
+ /////
+ //public Point StartRecordLastPoint
+ //{
+ // get => _startRecordLastPoint;
+ // set => SetProperty(ref _startRecordLastPoint, value);
+ //}
+
+ #region StartRecordPoint
+ private ObservablePoint _startRecordPoint;
+
+ ///
+ /// 设置中桌宠启动的位置
+ ///
+ [ReflectionProperty]
+ [ReflectionPropertyConverter(typeof(ObservablePointToPointConverter))]
+ public ObservablePoint StartRecordPoint
+ {
+ get => _startRecordPoint;
+ set => SetProperty(ref _startRecordPoint, value);
+ }
+ #endregion
+
+ #region HideFromTaskControl
+ private bool _hideFromTaskControl;
+
+ ///
+ /// 在任务切换器(Alt+Tab)中隐藏窗口
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.HideFromTaskControl))]
+ public bool HideFromTaskControl
+ {
+ get => _hideFromTaskControl;
+ set => SetProperty(ref _hideFromTaskControl, value);
+ }
+ #endregion
+
+ #region MessageBarOutside
+ private bool _messageBarOutside;
+
+ ///
+ /// 消息框外置
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.MessageBarOutside))]
+ public bool MessageBarOutside
+ {
+ get => _messageBarOutside;
+ set => SetProperty(ref _messageBarOutside, value);
+ }
+ #endregion
+
+ #region PetHelper
+ private bool _petHelper;
+
+ ///
+ /// 是否显示宠物帮助窗口
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.PetHelper))]
+ public bool PetHelper
+ {
+ get => _petHelper;
+ set => SetProperty(ref _petHelper, value);
+ }
+ #endregion
+
+ #region PetHelpLeft
+ private double _petHelpLeft;
+
+ // TODO 加入 PetHelpLeft
+
+ ///
+ /// 快捷穿透按钮X坐标
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.PetHelpLeft))]
+ public double PetHelpLeft
+ {
+ get => _petHelpLeft;
+ set => SetProperty(ref _petHelpLeft, value);
+ }
+ #endregion
+
+ #region PetHelpTop
+ private double _petHelpTop;
+
+ // TODO 加入 PetHelpTop
+
+ ///
+ /// 快捷穿透按钮Y坐标
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.PetHelpTop))]
+ public double PetHelpTop
+ {
+ get => _petHelpTop;
+ set => SetProperty(ref _petHelpTop, value);
+ }
+ #endregion
+}
diff --git a/VPet.Solution/Models/SettingEditor/InteractiveSettingModel.cs b/VPet.Solution/Models/SettingEditor/InteractiveSettingModel.cs
new file mode 100644
index 0000000..6ab7376
--- /dev/null
+++ b/VPet.Solution/Models/SettingEditor/InteractiveSettingModel.cs
@@ -0,0 +1,311 @@
+using HKW.HKWUtils.Observable;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using VPet_Simulator.Core;
+
+namespace VPet.Solution.Models.SettingEditor;
+
+public class InteractiveSettingModel : ObservableClass
+{
+ // NOTE: 这玩意其实在存档里 而不是设置里
+ //#region PetName
+ //private string _petName;
+
+ /////
+ ///// 宠物名称
+ /////
+ //[ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.PetName))]
+ //public string PetName
+ //{
+ // get => _petName;
+ // set => SetProperty(ref _petName, value);
+ //}
+ //#endregion
+
+ #region VoiceVolume
+ private double _voiceVolume;
+
+ ///
+ /// 播放声音大小
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.VoiceVolume))]
+ public double VoiceVolume
+ {
+ get => _voiceVolume;
+ set => SetProperty(ref _voiceVolume, value);
+ }
+ #endregion
+
+ #region EnableFunction
+ private bool _enableFunction;
+
+ ///
+ /// 启用计算等数据功能
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.EnableFunction))]
+ public bool EnableFunction
+ {
+ get => _enableFunction;
+ set => SetProperty(ref _enableFunction, value);
+ }
+ #endregion
+
+ #region CalFunState
+ private GameSave.ModeType _calFunState;
+
+ ///
+ /// 非计算模式下默认模式
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.CalFunState))]
+ public GameSave.ModeType CalFunState
+ {
+ get => _calFunState;
+ set => SetProperty(ref _calFunState, value);
+ }
+
+ public ObservableCollection ModeTypes { get; } =
+ new(Enum.GetValues(typeof(GameSave.ModeType)).Cast());
+ #endregion
+
+ #region LastCacheDate
+ private DateTime _lastCacheDate;
+
+ ///
+ /// 上次清理缓存日期
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.LastCacheDate))]
+ public DateTime LastCacheDate
+ {
+ get => _lastCacheDate;
+ set => SetProperty(ref _lastCacheDate, value);
+ }
+ #endregion
+
+ #region SaveTimes
+ private int _saveTimes;
+
+ ///
+ /// 储存顺序次数
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.SaveTimes))]
+ public int SaveTimes
+ {
+ get => _saveTimes;
+ set => SetProperty(ref _saveTimes, value);
+ }
+ #endregion
+
+ #region PressLength
+ private int _pressLength;
+
+ ///
+ /// 按多久视为长按 单位毫秒
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.PressLength))]
+ public int PressLength
+ {
+ get => _pressLength;
+ set => SetProperty(ref _pressLength, value);
+ }
+ #endregion
+
+ #region InteractionCycle
+ private int _interactionCycle;
+
+ ///
+ /// 互动周期
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.InteractionCycle))]
+ public int InteractionCycle
+ {
+ get => _interactionCycle;
+ set => SetProperty(ref _interactionCycle, value);
+ }
+ #endregion
+
+ #region LogicInterval
+ private double _logicInterval;
+
+ ///
+ /// 计算间隔 (秒)
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.LogicInterval))]
+ public double LogicInterval
+ {
+ get => _logicInterval;
+ set => SetProperty(ref _logicInterval, value);
+ }
+ #endregion
+
+ #region AllowMove
+ private bool _allowMove;
+
+ ///
+ /// 允许移动事件
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.AllowMove))]
+ public bool AllowMove
+ {
+ get => _allowMove;
+ set => SetProperty(ref _allowMove, value);
+ }
+ #endregion
+
+ #region SmartMove
+ private bool _smartMove;
+
+ ///
+ /// 智能移动
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.SmartMove))]
+ public bool SmartMove
+ {
+ get => _smartMove;
+ set => SetProperty(ref _smartMove, value);
+ }
+ #endregion
+
+ #region SmartMoveInterval
+ private int _smartMoveInterval = 0;
+
+ ///
+ /// 智能移动周期 (秒)
+ ///
+ [DefaultValue(1)]
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.SmartMoveInterval))]
+ [ReflectionPropertyConverter(typeof(SecondToMinuteConverter))]
+ public int SmartMoveInterval
+ {
+ get => _smartMoveInterval;
+ set => SetProperty(ref _smartMoveInterval, value);
+ }
+
+ public static ObservableCollection SmartMoveIntervals { get; } =
+ new() { 1, 2, 5, 10, 20, 30, 40, 50, 60 };
+ #endregion
+
+ #region PetGraph
+ private string _petGraph;
+
+ ///
+ /// 桌宠选择内容
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.PetGraph))]
+ public string PetGraph
+ {
+ get => _petGraph;
+ set => SetProperty(ref _petGraph, value);
+ }
+ #endregion
+
+ #region MusicCatch
+ private int _musicCatch;
+
+ ///
+ /// 当实时播放音量达到该值时运行音乐动作
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.MusicCatch))]
+ [ReflectionPropertyConverter(typeof(DoubleToInt32Converter))]
+ public int MusicCatch
+ {
+ get => _musicCatch;
+ set => SetProperty(ref _musicCatch, value);
+ }
+ #endregion
+
+ #region MusicMax
+ private int _musicMax;
+
+ ///
+ /// 当实时播放音量达到该值时运行特殊音乐动作
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.MusicMax))]
+ [ReflectionPropertyConverter(typeof(DoubleToInt32Converter))]
+ public int MusicMax
+ {
+ get => _musicMax;
+ set => SetProperty(ref _musicMax, value);
+ }
+ #endregion
+
+ #region AutoBuy
+ private bool _autoBuy;
+
+ // TODO 加入 AutoBuy
+ ///
+ /// 允许桌宠自动购买食品
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.AutoBuy))]
+ public bool AutoBuy
+ {
+ get => _autoBuy;
+ set => SetProperty(ref _autoBuy, value);
+ }
+ #endregion
+
+ #region AutoGift
+ private bool _autoGift;
+
+ // TODO 加入 AutoGift
+ ///
+ /// 允许桌宠自动购买礼物
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.AutoGift))]
+ public bool AutoGift
+ {
+ get => _autoGift;
+ set => SetProperty(ref _autoGift, value);
+ }
+ #endregion
+
+ #region MoveAreaDefault
+ private bool _moveAreaDefault;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.MoveAreaDefault))]
+ public bool MoveAreaDefault
+ {
+ get => _moveAreaDefault;
+ set => SetProperty(ref _moveAreaDefault, value);
+ }
+ #endregion
+
+ #region MoveArea
+ private System.Drawing.Rectangle _moveArea;
+
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.MoveArea))]
+ public System.Drawing.Rectangle MoveArea
+ {
+ get => _moveArea;
+ set => SetProperty(ref _moveArea, value);
+ }
+ #endregion
+}
+
+public class SecondToMinuteConverter : ReflectionConverterBase
+{
+ public override int Convert(int sourceValue)
+ {
+ return sourceValue * 60;
+ }
+
+ public override int ConvertBack(int targetValue)
+ {
+ if (targetValue == 30)
+ return 1;
+ else
+ return targetValue / 60;
+ }
+}
+
+public class DoubleToInt32Converter : ReflectionConverterBase
+{
+ public override double Convert(int sourceValue)
+ {
+ return sourceValue;
+ }
+
+ public override int ConvertBack(double targetValue)
+ {
+ return System.Convert.ToInt32(targetValue);
+ }
+}
diff --git a/VPet.Solution/Models/SettingEditor/ModSettingModel.cs b/VPet.Solution/Models/SettingEditor/ModSettingModel.cs
new file mode 100644
index 0000000..7619f7b
--- /dev/null
+++ b/VPet.Solution/Models/SettingEditor/ModSettingModel.cs
@@ -0,0 +1,320 @@
+using HKW.HKWUtils.Observable;
+using LinePutScript;
+using LinePutScript.Localization.WPF;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using VPet_Simulator.Windows.Interface;
+
+namespace VPet.Solution.Models.SettingEditor;
+
+public class ModSettingModel : ObservableClass
+{
+ public const string ModLineName = "onmod";
+ public const string PassModLineName = "passmod";
+ public const string MsgModLineName = "msgmod";
+ public const string WorkShopLineName = "workshop";
+ public static string ModDirectory = Path.Combine(Environment.CurrentDirectory, "mod");
+ public static Dictionary LocalMods = Directory.Exists(ModDirectory) is false
+ ? new(StringComparer.OrdinalIgnoreCase)
+ : new(
+ Directory
+ .EnumerateDirectories(ModDirectory)
+ .Select(d => new ModLoader(d))
+ .ToDictionary(m => m.Name, m => m),
+ StringComparer.OrdinalIgnoreCase
+ );
+ #region Mods
+ private ObservableCollection _mods = new();
+ public ObservableCollection Mods
+ {
+ get => _mods;
+ set => SetProperty(ref _mods, value);
+ }
+
+ public ModSettingModel(Setting setting)
+ {
+ foreach (var item in setting[ModLineName])
+ {
+ var modName = item.Name;
+ if (LocalMods.TryGetValue(modName, out var loader) && loader.IsSuccesses)
+ {
+ var modModel = new ModModel(loader);
+ modModel.IsPass = setting[PassModLineName].Contains(modName);
+ modModel.IsMsg = setting[MsgModLineName].Contains(modModel.Name);
+ Mods.Add(modModel);
+ }
+ else
+ {
+ Mods.Add(
+ new()
+ {
+ Name = modName,
+ ModPath = "未知, 可能是{0}".Translate(Path.Combine(ModDirectory, modName))
+ }
+ );
+ }
+ }
+ foreach (var modPath in setting[WorkShopLineName])
+ {
+ var loader = new ModLoader(modPath.Name);
+ if (loader.IsSuccesses is false)
+ {
+ Mods.Add(new() { Name = loader.Name, ModPath = loader.ModPath });
+ return;
+ }
+ var modModel = new ModModel(loader);
+ modModel.IsPass = setting[PassModLineName].Contains(modModel.Name);
+ modModel.IsMsg = setting[MsgModLineName].Contains(modModel.Name);
+ Mods.Add(modModel);
+ }
+ }
+
+ public void Close()
+ {
+ foreach (var modLoader in LocalMods)
+ {
+ modLoader.Value.Image.CloseStream();
+ }
+ }
+
+ public void Save(Setting setting)
+ {
+ setting.Remove(ModLineName);
+ setting.Remove(PassModLineName);
+ setting.Remove(MsgModLineName);
+ if (Mods.Any() is false)
+ return;
+ foreach (var mod in Mods)
+ {
+ setting[ModLineName].Add(new Sub(mod.Name.ToLower()));
+ setting[MsgModLineName].Add(new Sub(mod.Name, "True"));
+ if (mod.IsPass)
+ setting[PassModLineName].Add(new Sub(mod.Name.ToLower()));
+ }
+ }
+ #endregion
+}
+
+public class ModModel : ObservableClass
+{
+ #region Name
+ private string _name;
+
+ ///
+ /// 名称
+ ///
+ [ReflectionProperty(nameof(ModLoader.Name))]
+ public string Name
+ {
+ get => _name;
+ set => SetProperty(ref _name, value);
+ }
+ #endregion
+
+ #region Description
+ private string _description;
+
+ ///
+ /// 描述
+ ///
+ [ReflectionProperty(nameof(ModLoader.Intro))]
+ public string Description
+ {
+ get => _description;
+ set => SetProperty(ref _description, value);
+ }
+ #endregion
+
+ #region Author
+ private string _author;
+
+ ///
+ /// 作者
+ ///
+ [ReflectionProperty(nameof(ModLoader.Author))]
+ public string Author
+ {
+ get => _author;
+ set => SetProperty(ref _author, value);
+ }
+ #endregion
+
+ #region ModVersion
+ private int _modVersion;
+
+ ///
+ /// 模组版本
+ ///
+ [ReflectionProperty(nameof(ModLoader.Ver))]
+ public int ModVersion
+ {
+ get => _modVersion;
+ set => SetProperty(ref _modVersion, value);
+ }
+ #endregion
+
+ #region GameVersion
+ private int _gameVersion;
+
+ ///
+ /// 游戏版本
+ ///
+ [ReflectionProperty(nameof(ModLoader.GameVer))]
+ public int GameVersion
+ {
+ get => _gameVersion;
+ set => SetProperty(ref _gameVersion, value);
+ }
+ #endregion
+
+ #region Tags
+ private HashSet _tags;
+
+ ///
+ /// 功能
+ ///
+ [ReflectionProperty(nameof(ModLoader.Tags))]
+ public HashSet Tags
+ {
+ get => _tags;
+ set => SetProperty(ref _tags, value);
+ }
+ #endregion
+
+ #region Image
+ private BitmapImage _image;
+
+ ///
+ /// 图像
+ ///
+ [ReflectionProperty(nameof(ModLoader.Image))]
+ public BitmapImage Image
+ {
+ get => _image;
+ set => SetProperty(ref _image, value);
+ }
+ #endregion
+
+ #region ItemId
+ private ulong _itemId;
+
+ [ReflectionProperty(nameof(ModLoader.ItemID))]
+ public ulong ItemId
+ {
+ get => _itemId;
+ set => SetProperty(ref _itemId, value);
+ }
+ #endregion
+
+
+ #region ModPath
+
+ private string _modPath;
+
+ [ReflectionProperty(nameof(ModLoader.ModPath))]
+ public string ModPath
+ {
+ get => _modPath;
+ set => SetProperty(ref _modPath, value);
+ }
+ #endregion
+
+ #region IsEnabled
+ private bool? _isEnabled = true;
+
+ ///
+ /// 已启用
+ ///
+ public bool? IsEnabled
+ {
+ get => _isEnabled;
+ set => SetProperty(ref _isEnabled, value);
+ }
+ #endregion
+
+ #region IsPass
+ private bool _isPass;
+
+ ///
+ /// 是通过检查的代码模组
+ ///
+ public bool IsPass
+ {
+ get => _isPass;
+ set => SetProperty(ref _isPass, value);
+ }
+ #endregion
+
+ #region IsMsg
+ private bool _isMsg;
+
+ ///
+ /// 是含有代码的模组
+ ///
+ public bool IsMsg
+ {
+ get => _isMsg;
+ set => SetProperty(ref _isMsg, value);
+ }
+ #endregion
+
+ #region State
+ private string _state;
+ public string State
+ {
+ get => _state;
+ set => SetProperty(ref _state, value);
+ }
+ #endregion
+
+
+ public ModModel()
+ {
+ IsEnabled = null;
+ RefreshState();
+ }
+
+ private void ModModel_PropertyChanged(
+ object sender,
+ System.ComponentModel.PropertyChangedEventArgs e
+ )
+ {
+ if (e.PropertyName == nameof(IsEnabled))
+ {
+ RefreshState();
+ }
+ }
+
+ public ModModel(ModLoader loader)
+ {
+ PropertyChanged += ModModel_PropertyChanged;
+ ReflectionUtils.SetValue(loader, this);
+ RefreshState();
+ Name = Name.Translate();
+ Description = Description.Translate();
+ LocalizeCore.BindingNotify.PropertyChanged += BindingNotify_PropertyChanged;
+ }
+
+ private void BindingNotify_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ Name = Name.Translate();
+ Description = Description.Translate();
+ }
+
+ public void RefreshState()
+ {
+ if (IsEnabled is true)
+ State = "已启用".Translate();
+ else if (IsEnabled is false)
+ State = "已关闭".Translate();
+ else
+ State = "已损坏".Translate();
+ }
+}
diff --git a/VPet.Solution/Models/SettingEditor/SettingModel.cs b/VPet.Solution/Models/SettingEditor/SettingModel.cs
new file mode 100644
index 0000000..a3be916
--- /dev/null
+++ b/VPet.Solution/Models/SettingEditor/SettingModel.cs
@@ -0,0 +1,146 @@
+using FastMember;
+using HKW.HKWUtils.Observable;
+using LinePutScript;
+using System.ComponentModel;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using VPet.Solution.Properties;
+using VPet_Simulator.Windows.Interface;
+
+namespace VPet.Solution.Models.SettingEditor;
+
+public class SettingModel : ObservableClass
+{
+ ///
+ /// 名称
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// 文件路径
+ ///
+ public string FilePath { get; set; }
+
+ #region GraphicsSetting
+ private GraphicsSettingModel _graphicsSetting;
+ public GraphicsSettingModel GraphicsSetting
+ {
+ get => _graphicsSetting;
+ set => SetProperty(ref _graphicsSetting, value);
+ }
+ #endregion
+
+ #region SystemSetting
+ private SystemSettingModel _systemSetting;
+
+ public SystemSettingModel SystemSetting
+ {
+ get => _systemSetting;
+ set => SetProperty(ref _systemSetting, value);
+ }
+ #endregion
+
+ #region InteractiveSetting
+ private InteractiveSettingModel _interactiveSetting;
+ public InteractiveSettingModel InteractiveSetting
+ {
+ get => _interactiveSetting;
+ set => SetProperty(ref _interactiveSetting, value);
+ }
+ #endregion
+
+ #region CustomizedSetting
+ private CustomizedSettingModel _CustomizedSetting;
+ public CustomizedSettingModel CustomizedSetting
+ {
+ get => _CustomizedSetting;
+ set => SetProperty(ref _CustomizedSetting, value);
+ }
+ #endregion
+
+ #region DiagnosticSetting
+ private DiagnosticSettingModel _diagnosticSetting;
+ public DiagnosticSettingModel DiagnosticSetting
+ {
+ get => _diagnosticSetting;
+ set => SetProperty(ref _diagnosticSetting, value);
+ }
+ #endregion
+
+ #region ModSetting
+ private ModSettingModel _modSetting;
+ public ModSettingModel ModSetting
+ {
+ get => _modSetting;
+ set => SetProperty(ref _modSetting, value);
+ }
+ #endregion
+
+
+ private readonly Setting _setting;
+
+ private readonly ReflectionOptions _saveReflectionOptions = new() { CheckValueEquals = true };
+
+ public SettingModel()
+ : this(new("")) { }
+
+ public SettingModel(Setting setting)
+ {
+ _setting = setting;
+ GraphicsSetting = LoadSetting();
+ InteractiveSetting = LoadSetting();
+ SystemSetting = LoadSetting();
+ CustomizedSetting = LoadCustomizedSetting(setting);
+ DiagnosticSetting = LoadSetting();
+ DiagnosticSetting.SetAutoCalToSetting(setting);
+ ModSetting = LoadModSetting(setting);
+ }
+
+ private ModSettingModel LoadModSetting(Setting setting)
+ {
+ var settingModel = new ModSettingModel(setting);
+ return settingModel;
+ }
+
+ private CustomizedSettingModel LoadCustomizedSetting(Setting setting)
+ {
+ var model = new CustomizedSettingModel();
+ if (setting[CustomizedSettingModel.TargetName] is ILine line && line.Count > 0)
+ {
+ foreach (var sub in line)
+ model.Links.Add(new(sub.Name, sub.Info));
+ }
+ else
+ {
+ setting.Remove(CustomizedSettingModel.TargetName);
+ }
+ return model;
+ }
+
+ private T LoadSetting()
+ where T : new()
+ {
+ var settingModel = new T();
+ ReflectionUtils.SetValue(_setting, settingModel);
+ return settingModel;
+ }
+
+ public void Save()
+ {
+ SaveSetting(GraphicsSetting);
+ SaveSetting(InteractiveSetting);
+ SaveSetting(SystemSetting);
+ SaveSetting(DiagnosticSetting);
+ DiagnosticSetting.SetAutoCalToSetting(_setting);
+ foreach (var link in CustomizedSetting.Links)
+ _setting[CustomizedSettingModel.TargetName].Add(new Sub(link.Name, link.Link));
+ ModSetting.Save(_setting);
+ File.WriteAllText(FilePath, _setting.ToString());
+ }
+
+ private void SaveSetting(object settingModel)
+ {
+ ReflectionUtils.SetValue(settingModel, _setting, _saveReflectionOptions);
+ }
+}
diff --git a/VPet.Solution/Models/SettingEditor/SystemSettingModel.cs b/VPet.Solution/Models/SettingEditor/SystemSettingModel.cs
new file mode 100644
index 0000000..87b974a
--- /dev/null
+++ b/VPet.Solution/Models/SettingEditor/SystemSettingModel.cs
@@ -0,0 +1,42 @@
+using System.Collections.ObjectModel;
+
+namespace VPet.Solution.Models.SettingEditor;
+
+public class SystemSettingModel : ObservableClass
+{
+ ///
+ /// 数据收集是否被禁止(当日)
+ ///
+ public bool DiagnosisDayEnable { get; } = true;
+
+ #region AutoSaveInterval
+ private int _autoSaveInterval;
+
+ ///
+ /// 自动保存频率 (min)
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.AutoSaveInterval))]
+ public int AutoSaveInterval
+ {
+ get => _autoSaveInterval;
+ set => SetProperty(ref _autoSaveInterval, value);
+ }
+
+ public static ObservableCollection SaveIntervals { get; } =
+ new() { -1, 2, 5, 10, 20, 30, 60 };
+ #endregion
+
+ #region BackupSaveMaxNum
+ private int _backupSaveMaxNum;
+
+ ///
+ /// 备份保存最大数量
+ ///
+ [ReflectionProperty(nameof(VPet_Simulator.Windows.Interface.Setting.BackupSaveMaxNum))]
+ public int BackupSaveMaxNum
+ {
+ get => _backupSaveMaxNum;
+ set => SetProperty(ref _backupSaveMaxNum, value);
+ }
+ #endregion
+}
diff --git a/VPet.Solution/NativeStyles.xaml b/VPet.Solution/NativeStyles.xaml
new file mode 100644
index 0000000..4710477
--- /dev/null
+++ b/VPet.Solution/NativeStyles.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VPet.Solution/NativeStyles.xaml.cs b/VPet.Solution/NativeStyles.xaml.cs
new file mode 100644
index 0000000..ed6f24c
--- /dev/null
+++ b/VPet.Solution/NativeStyles.xaml.cs
@@ -0,0 +1,11 @@
+using System.Windows;
+
+namespace VPet.Solution;
+
+public partial class NativeStyles : ResourceDictionary
+{
+ public NativeStyles()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/VPet.Solution/Properties/AssemblyInfo.cs b/VPet.Solution/Properties/AssemblyInfo.cs
index c186668..e6391f8 100644
--- a/VPet.Solution/Properties/AssemblyInfo.cs
+++ b/VPet.Solution/Properties/AssemblyInfo.cs
@@ -1,6 +1,4 @@
using System.Reflection;
-using System.Resources;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
@@ -10,9 +8,9 @@ using System.Windows;
[assembly: AssemblyTitle("VPet.Solution")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
+[assembly: AssemblyCompany("LBGame")]
[assembly: AssemblyProduct("VPet.Solution")]
-[assembly: AssemblyCopyright("Copyright © 2023")]
+[assembly: AssemblyCopyright("Copyright © LBGame 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -33,15 +31,14 @@ using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //主题特定资源词典所处位置
- //(未在页面中找到资源时使用,
- //或应用程序资源字典中找到时使用)
+ //(未在页面中找到资源时使用,
+ //或应用程序资源字典中找到时使用)
ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
- //(未在页面中找到资源时使用,
- //、应用程序或任何主题专用资源字典中找到时使用)
+//(未在页面中找到资源时使用,
+//、应用程序或任何主题专用资源字典中找到时使用)
)]
-
-// 程序集的版本信息由下列四个值组成:
+// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
diff --git a/VPet.Solution/Resources/NativeResources.cs b/VPet.Solution/Resources/NativeResources.cs
new file mode 100644
index 0000000..09f7b1d
--- /dev/null
+++ b/VPet.Solution/Resources/NativeResources.cs
@@ -0,0 +1,72 @@
+using System.Reflection;
+
+namespace VPet.House.Resources;
+
+///
+/// 本地资源
+///
+internal class NativeResources
+{
+ #region Resources
+
+ public const string Wall = ResourcePath + "Wall.png";
+ public const string Floor = ResourcePath + "Floor.png";
+ public const string Chair = ResourcePath + "Chair.png";
+ public const string Table = ResourcePath + "Table.png";
+ public const string Bed = ResourcePath + "Bed.png";
+
+ public const string OakPlanks = ResourcePath + "oak_planks.png";
+ public const string Stone = ResourcePath + "stone.png";
+
+ #endregion Resources
+
+ ///
+ /// 资源基路径
+ ///
+ public const string ResourcePath = $"{nameof(VPet)}.{nameof(House)}.{nameof(Resources)}.";
+
+ #region Native
+
+ private static readonly Assembly _assembly = Assembly.GetExecutingAssembly();
+
+ ///
+ /// 获取资源流
+ ///
+ /// 资源名
+ /// 资源流
+ public static Stream GetStream(string resourceName) =>
+ _assembly.GetManifestResourceStream(resourceName)!;
+
+ ///
+ /// 尝试获取资源流
+ ///
+ /// 资源名
+ /// 资源流
+ /// 成功为 失败为
+ public static bool TryGetStream(string resourceName, out Stream resourceStream)
+ {
+ resourceStream = null;
+ if (_assembly.GetManifestResourceStream(resourceName) is not Stream stream)
+ return false;
+ resourceStream = stream;
+ return true;
+ }
+
+ ///
+ /// 将流保存至文件
+ ///
+ /// 资源名
+ /// 文件路径
+ /// 成功为 失败为
+ public static bool SaveTo(string resourceName, string path)
+ {
+ if (_assembly.GetManifestResourceStream(resourceName) is not Stream stream)
+ return false;
+ using var sr = new StreamReader(stream);
+ using var sw = new StreamWriter(path);
+ sr.BaseStream.CopyTo(sw.BaseStream);
+ return true;
+ }
+
+ #endregion Native
+}
diff --git a/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangedX.cs b/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangedX.cs
new file mode 100644
index 0000000..141f509
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangedX.cs
@@ -0,0 +1,12 @@
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 通知属性改变后接口
+///
+public interface INotifyPropertyChangedX
+{
+ ///
+ /// 通知属性改变后事件
+ ///
+ public event PropertyChangedXEventHandler? PropertyChangedX;
+}
diff --git a/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangingX.cs b/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangingX.cs
new file mode 100644
index 0000000..8b47081
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangingX.cs
@@ -0,0 +1,12 @@
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 通知属性改变前接口
+///
+public interface INotifyPropertyChangingX
+{
+ ///
+ /// 属性改变前事件
+ ///
+ public event PropertyChangingXEventHandler? PropertyChangingX;
+}
diff --git a/VPet.Solution/SimpleObservable/ObservableClass/ObservableClass.cs b/VPet.Solution/SimpleObservable/ObservableClass/ObservableClass.cs
new file mode 100644
index 0000000..f33b86d
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableClass/ObservableClass.cs
@@ -0,0 +1,114 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 可观察对象
+/// 示例:
+/// {
+/// int _value = 0;
+/// public int Value
+/// {
+/// get => _value;
+/// set => SetProperty(ref _value, value);
+/// }
+/// }]]>
+///
+public abstract class ObservableClass
+ : INotifyPropertyChanging,
+ INotifyPropertyChanged,
+ INotifyPropertyChangingX,
+ INotifyPropertyChangedX
+ where TObject : ObservableClass
+{
+ public ObservableClass()
+ {
+ if (GetType() != typeof(TObject))
+ throw new InvalidCastException(
+ $"Inconsistency between target type [{GetType().FullName}] and generic type [{typeof(TObject).FullName}]"
+ );
+ }
+
+ #region OnPropertyChange
+ ///
+ /// 设置属性值
+ ///
+ /// 值
+ /// 新值
+ /// 属性名称
+ /// 成功为 失败为
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected virtual bool SetProperty(
+ ref TValue value,
+ TValue newValue,
+ [CallerMemberName] string propertyName = null!
+ )
+ {
+ if (EqualityComparer.Default.Equals(value, newValue) is true)
+ return false;
+ var oldValue = value;
+ if (OnPropertyChanging(oldValue, newValue, propertyName))
+ return false;
+ value = newValue;
+ OnPropertyChanged(oldValue, newValue, propertyName);
+ return true;
+ }
+
+ ///
+ /// 属性改变前
+ ///
+ /// 旧值
+ /// 新值
+ /// 属性名称
+ /// 取消为 否则为
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected virtual bool OnPropertyChanging(
+ object? oldValue,
+ object? newValue,
+ [CallerMemberName] string propertyName = null!
+ )
+ {
+ PropertyChanging?.Invoke(this, new(propertyName));
+ if (PropertyChangingX is null)
+ return false;
+ var e = new PropertyChangingXEventArgs(propertyName, oldValue, newValue);
+ PropertyChangingX?.Invoke((TObject)this, e);
+ if (e.Cancel)
+ PropertyChanged?.Invoke(this, new(propertyName));
+ return e.Cancel;
+ }
+
+ ///
+ /// 属性改变后
+ ///
+ /// 旧值
+ /// 新值
+ /// 属性名称
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected virtual void OnPropertyChanged(
+ object? oldValue,
+ object? newValue,
+ [CallerMemberName] string propertyName = null!
+ )
+ {
+ PropertyChanged?.Invoke(this, new(propertyName));
+ PropertyChangedX?.Invoke((TObject)this, new(propertyName, oldValue, newValue));
+ }
+ #endregion
+
+ #region Event
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ public event PropertyChangingXEventHandler? PropertyChangingX;
+
+ ///
+ public event PropertyChangedXEventHandler? PropertyChangedX;
+ #endregion
+}
diff --git a/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventArgs.cs b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventArgs.cs
new file mode 100644
index 0000000..d9f80c2
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventArgs.cs
@@ -0,0 +1,33 @@
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 属性改变后事件参数
+///
+public class PropertyChangedXEventArgs : EventArgs
+{
+ ///
+ /// 属性名
+ ///
+ public string PropertyName { get; }
+
+ ///
+ /// 旧值
+ ///
+ public object? OldValue { get; }
+
+ ///
+ /// 新值
+ ///
+ public object? NewValue { get; }
+
+ ///
+ /// 属性名
+ /// 旧值
+ /// 新值
+ public PropertyChangedXEventArgs(string propertyName, object? oldValue, object? newValue)
+ {
+ PropertyName = propertyName;
+ OldValue = oldValue;
+ NewValue = newValue;
+ }
+}
diff --git a/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventHandler.cs
new file mode 100644
index 0000000..178e3c9
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventHandler.cs
@@ -0,0 +1,11 @@
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 属性改变后事件
+///
+/// 发送者
+/// 参数
+public delegate void PropertyChangedXEventHandler(
+ TSender sender,
+ PropertyChangedXEventArgs e
+);
diff --git a/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventArgs.cs b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventArgs.cs
new file mode 100644
index 0000000..bbd6eed
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventArgs.cs
@@ -0,0 +1,35 @@
+using System.ComponentModel;
+
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 属性改变前事件参数
+///
+public class PropertyChangingXEventArgs : CancelEventArgs
+{
+ ///
+ /// 属性名
+ ///
+ public string PropertyName { get; }
+
+ ///
+ /// 旧值
+ ///
+ public object? OldValue { get; }
+
+ ///
+ /// 新值
+ ///
+ public object? NewValue { get; }
+
+ ///
+ /// 属性名
+ /// 旧值
+ /// 新值
+ public PropertyChangingXEventArgs(string propertyName, object? oldValue, object? newValue)
+ {
+ PropertyName = propertyName;
+ OldValue = oldValue;
+ NewValue = newValue;
+ }
+}
diff --git a/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventHandler.cs
new file mode 100644
index 0000000..9a1e472
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventHandler.cs
@@ -0,0 +1,11 @@
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 属性改变前事件
+///
+/// 发送者
+/// 参数
+public delegate void PropertyChangingXEventHandler(
+ TSender sender,
+ PropertyChangingXEventArgs e
+);
diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteAsyncEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteAsyncEventHandler.cs
new file mode 100644
index 0000000..6cb2552
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteAsyncEventHandler.cs
@@ -0,0 +1,12 @@
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 异步执行命令事件
+///
+public delegate Task ExecuteAsyncEventHandler();
+
+///
+/// 异步执行命令事件
+///
+/// 参数
+public delegate Task ExecuteAsyncEventHandler(T parameter);
diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteEventHandler.cs
new file mode 100644
index 0000000..52075fe
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteEventHandler.cs
@@ -0,0 +1,12 @@
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 执行事件
+///
+public delegate void ExecuteEventHandler();
+
+///
+/// 执行事件
+///
+/// 参数
+public delegate void ExecuteEventHandler(T parameter);
diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/NotifyReceivedEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableCommand/NotifyReceivedEventHandler.cs
new file mode 100644
index 0000000..ba2a50e
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableCommand/NotifyReceivedEventHandler.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel;
+using System.Windows.Input;
+
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 通知接收器
+///
+/// 发送者
+/// 参数
+public delegate void NotifyReceivedEventHandler(ICommand sender, CancelEventArgs e);
diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommand.cs b/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommand.cs
new file mode 100644
index 0000000..7dc7e3d
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommand.cs
@@ -0,0 +1,115 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Windows.Input;
+
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 可观察命令
+///
+[DebuggerDisplay("\\{ObservableCommand, CanExecute = {IsCanExecute.Value}\\}")]
+public class ObservableCommand : ObservableClass, ICommand
+{
+ bool _isCanExecute = true;
+
+ ///
+ /// 能执行的属性
+ ///
+ public bool IsCanExecute
+ {
+ get => _isCanExecute;
+ set => SetProperty(ref _isCanExecute, value);
+ }
+
+ bool _currentCanExecute = true;
+
+ ///
+ /// 当前可执行状态
+ ///
+ /// 在执行异步事件时会强制为 , 但异步结束后会恢复为 的值
+ ///
+ ///
+ public bool CurrentCanExecute
+ {
+ get => _currentCanExecute;
+ private set => SetProperty(ref _currentCanExecute, value);
+ }
+
+ ///
+ public ObservableCommand()
+ {
+ PropertyChanged += OnCanExecuteChanged;
+ }
+
+ private void OnCanExecuteChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ CanExecuteChanged?.Invoke(this, new());
+ }
+
+ #region ICommand
+ ///
+ /// 能否被执行
+ ///
+ /// 参数
+ /// 能被执行为 否则为
+ public bool CanExecute(object? parameter)
+ {
+ return CurrentCanExecute && IsCanExecute;
+ }
+
+ ///
+ /// 执行方法
+ ///
+ /// 参数
+ public async void Execute(object? parameter)
+ {
+ if (IsCanExecute is not true)
+ return;
+ ExecuteCommand?.Invoke();
+ await ExecuteAsync();
+ }
+
+ ///
+ /// 执行异步方法, 会在等待中修改 , 完成后恢复
+ ///
+ /// 若要在执行此方法时触发 事件, 请将 设置为
+ ///
+ ///
+ /// 设置为 时触发 事件
+ /// 任务
+ public async Task ExecuteAsync(bool runAlone = false)
+ {
+ if (IsCanExecute is not true)
+ return;
+ if (runAlone)
+ ExecuteCommand?.Invoke();
+ if (ExecuteAsyncCommand is null)
+ return;
+ CurrentCanExecute = false;
+ foreach (
+ var asyncEvent in ExecuteAsyncCommand
+ .GetInvocationList()
+ .Cast()
+ )
+ await asyncEvent.Invoke();
+ CurrentCanExecute = true;
+ }
+ #endregion
+
+ #region Event
+ ///
+ /// 能否执行属性改变后事件
+ ///
+ public event EventHandler? CanExecuteChanged;
+
+ ///
+ /// 执行事件
+ ///
+ public event ExecuteEventHandler? ExecuteCommand;
+
+ ///
+ /// 异步执行事件
+ ///
+ public event ExecuteAsyncEventHandler? ExecuteAsyncCommand;
+ #endregion
+}
diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommandT.cs b/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommandT.cs
new file mode 100644
index 0000000..abddffe
--- /dev/null
+++ b/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommandT.cs
@@ -0,0 +1,116 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Windows.Input;
+
+namespace HKW.HKWUtils.Observable;
+
+///
+/// 具有参数的可观察命令
+///
+[DebuggerDisplay("\\{ObservableCommand, CanExecute = {IsCanExecute.Value}\\}")]
+public class ObservableCommand : ObservableClass>, ICommand
+{
+ bool _isCanExecute = true;
+
+ ///
+ /// 能执行的属性
+ ///
+ public bool IsCanExecute
+ {
+ get => _isCanExecute;
+ set => SetProperty(ref _isCanExecute, value);
+ }
+
+ bool _currentCanExecute = true;
+
+ ///
+ /// 当前可执行状态
+ ///
+ /// 在执行异步事件时会强制为 , 但异步结束后会恢复为 的值
+ ///
+ ///
+ public bool CurrentCanExecute
+ {
+ get => _currentCanExecute;
+ private set => SetProperty(ref _currentCanExecute, value);
+ }
+
+ ///
+ public ObservableCommand()
+ {
+ PropertyChanged += OnCanExecuteChanged;
+ }
+
+ private void OnCanExecuteChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ CanExecuteChanged?.Invoke(this, new());
+ }
+
+ #region ICommand
+ ///
+ /// 能否被执行
+ ///
+ /// 参数
+ /// 能被执行为 否则为
+ public bool CanExecute(object? parameter)
+ {
+ return CurrentCanExecute && IsCanExecute;
+ }
+
+ ///
+ /// 执行方法
+ ///
+ /// 参数
+ public async void Execute(object? parameter)
+ {
+ if (IsCanExecute is not true)
+ return;
+ ExecuteCommand?.Invoke((T)parameter!);
+ await ExecuteAsync((T)parameter!);
+ }
+
+ ///
+ /// 执行异步方法, 会在等待中修改 , 完成后恢复
+ ///
+ /// 若要在执行此方法时触发 事件, 请将 设置为
+ ///
+ ///
+ /// 参数
+ /// 设置为 时触发 事件
+ /// 任务
+ public async Task ExecuteAsync(T parameter, bool runAlone = false)
+ {
+ if (IsCanExecute is not true)
+ return;
+ if (runAlone)
+ ExecuteCommand?.Invoke(parameter);
+ if (ExecuteAsyncCommand is null)
+ return;
+ CurrentCanExecute = false;
+ foreach (
+ var asyncEvent in ExecuteAsyncCommand
+ .GetInvocationList()
+ .Cast>()
+ )
+ await asyncEvent.Invoke(parameter);
+ CurrentCanExecute = true;
+ }
+ #endregion
+
+ #region Event
+ ///
+ /// 能否执行属性改变后事件
+ ///
+ public event EventHandler? CanExecuteChanged;
+
+ ///
+ /// 执行事件
+ ///
+ public event ExecuteEventHandler? ExecuteCommand;
+
+ ///
+ /// 异步执行事件
+ ///
+ public event ExecuteAsyncEventHandler? ExecuteAsyncCommand;
+ #endregion
+}
diff --git a/VPet.Solution/Styles.xaml b/VPet.Solution/Styles.xaml
new file mode 100644
index 0000000..98cecb4
--- /dev/null
+++ b/VPet.Solution/Styles.xaml
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VPet.Solution/Templates.xaml b/VPet.Solution/Templates.xaml
new file mode 100644
index 0000000..ac14da7
--- /dev/null
+++ b/VPet.Solution/Templates.xaml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VPet.Solution/Usings.cs b/VPet.Solution/Usings.cs
new file mode 100644
index 0000000..ae09c80
--- /dev/null
+++ b/VPet.Solution/Usings.cs
@@ -0,0 +1,7 @@
+global using global::HKW.HKWUtils;
+global using global::HKW.HKWUtils.Observable;
+global using global::System;
+global using global::System.Collections.Generic;
+global using global::System.IO;
+global using global::System.Linq;
+global using global::System.Threading.Tasks;
diff --git a/VPet.Solution/Utils/ClearFocus.cs b/VPet.Solution/Utils/ClearFocus.cs
new file mode 100644
index 0000000..f5a06dc
--- /dev/null
+++ b/VPet.Solution/Utils/ClearFocus.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Input;
+
+namespace HKW.WPF.Extensions;
+
+public static partial class WPFExtensions
+{
+ ///
+ /// 清除元素焦点
+ ///
+ /// 元素
+ public static void ClearFocus(this DependencyObject obj)
+ {
+ FocusManager.SetFocusedElement(FocusManager.GetFocusScope(obj), null);
+ }
+}
diff --git a/VPet.Solution/Utils/ElementHelper.cs b/VPet.Solution/Utils/ElementHelper.cs
new file mode 100644
index 0000000..d270394
--- /dev/null
+++ b/VPet.Solution/Utils/ElementHelper.cs
@@ -0,0 +1,271 @@
+using HKW.WPF.Extensions;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Input;
+
+namespace HKW.WPF.Helpers;
+
+///
+///
+///
+public static class ElementHelper
+{
+ #region IsEnabled
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool GetIsEnabled(FrameworkElement element)
+ {
+ return (bool)element.GetValue(IsEnabledProperty);
+ }
+
+ ///
+ ///
+ ///
+ public static void SetIsEnabled(FrameworkElement element, bool value)
+ {
+ element.SetValue(IsEnabledProperty, value);
+ }
+
+ ///
+ /// 在按下指定按键时清除选中状态
+ ///
+ public static readonly DependencyProperty IsEnabledProperty =
+ DependencyProperty.RegisterAttached(
+ "IsEnabled",
+ typeof(bool),
+ typeof(ElementHelper),
+ new FrameworkPropertyMetadata(default(bool), IsEnabledPropertyChangedCallback)
+ );
+
+ private static void IsEnabledPropertyChangedCallback(
+ DependencyObject obj,
+ DependencyPropertyChangedEventArgs e
+ )
+ {
+ if (obj is not FrameworkElement element)
+ return;
+ element.IsEnabled = GetIsEnabled(element);
+ }
+ #endregion
+
+ #region ClearFocusOnKeyDown
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string GetClearFocusOnKeyDown(FrameworkElement element)
+ {
+ return (string)element.GetValue(ClearFocusOnKeyDownProperty);
+ }
+
+ ///
+ ///
+ ///
+ /// 禁止使用此方法
+ public static void SetClearFocusOnKeyDown(FrameworkElement element, string value)
+ {
+ element.SetValue(ClearFocusOnKeyDownProperty, value);
+ }
+
+ ///
+ /// 在按下指定按键时清除选中状态
+ ///
+ public static readonly DependencyProperty ClearFocusOnKeyDownProperty =
+ DependencyProperty.RegisterAttached(
+ "ClearFocusOnKeyDown",
+ typeof(string),
+ typeof(ElementHelper),
+ new FrameworkPropertyMetadata(default(string), ClearFocusOnKeyDownPropertyChanged)
+ );
+
+ private static void ClearFocusOnKeyDownPropertyChanged(
+ DependencyObject obj,
+ DependencyPropertyChangedEventArgs e
+ )
+ {
+ if (obj is not FrameworkElement element)
+ return;
+ var keyName = GetClearFocusOnKeyDown(element);
+ if (Enum.TryParse(keyName, false, out _) is false)
+ throw new Exception($"Unknown key {keyName}");
+ element.KeyDown -= Element_KeyDown;
+ element.KeyDown += Element_KeyDown;
+
+ static void Element_KeyDown(object sender, KeyEventArgs e)
+ {
+ if (sender is not FrameworkElement element)
+ return;
+ var key = (Key)Enum.Parse(typeof(Key), GetClearFocusOnKeyDown(element));
+ if (e.Key == key)
+ {
+ // 清除控件焦点
+ element.ClearFocus();
+ // 清除键盘焦点
+ Keyboard.ClearFocus();
+ }
+ }
+ }
+ #endregion
+
+ #region UniformMinWidthGroup
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string GetUniformMinWidthGroup(FrameworkElement element)
+ {
+ return (string)element.GetValue(UniformWidthGroupProperty);
+ }
+
+ ///
+ ///
+ ///
+ public static void SetUniformMinWidthGroup(FrameworkElement element, string value)
+ {
+ element.SetValue(UniformWidthGroupProperty, value);
+ }
+
+ ///
+ ///
+ ///
+ public static readonly DependencyProperty UniformWidthGroupProperty =
+ DependencyProperty.RegisterAttached(
+ "UniformMinWidthGroup",
+ typeof(string),
+ typeof(ElementHelper),
+ new FrameworkPropertyMetadata(null, UniformMinWidthGroupPropertyChanged)
+ );
+
+ ///
+ /// (TopParent ,(GroupName, UniformMinWidthGroupInfo))
+ ///
+ private readonly static Dictionary<
+ FrameworkElement,
+ Dictionary
+ > _uniformMinWidthGroups = new();
+
+ private static void UniformMinWidthGroupPropertyChanged(
+ DependencyObject obj,
+ DependencyPropertyChangedEventArgs e
+ )
+ {
+ if (obj is not FrameworkElement element)
+ return;
+ var groupName = GetUniformMinWidthGroup(element);
+ var topParent = element.FindTopParentOnVisualTree();
+ // 在设计器中会无法获取顶级元素, 会提示错误, 忽略即可
+ if (topParent is null)
+ return;
+ if (_uniformMinWidthGroups.TryGetValue(topParent, out var groups) is false)
+ {
+ topParent.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
+ groups = _uniformMinWidthGroups[topParent] = new();
+ }
+ if (groups.TryGetValue(groupName, out var group) is false)
+ group = groups[groupName] = new();
+ group.Elements.Add(element);
+
+ element.SizeChanged -= Element_SizeChanged;
+ element.SizeChanged += Element_SizeChanged;
+
+ #region TopParent
+
+ static void Dispatcher_ShutdownStarted(object? sender, EventArgs e)
+ {
+ if (sender is not FrameworkElement element)
+ return;
+ _uniformMinWidthGroups.Remove(element);
+ }
+ #endregion
+
+ #region Element
+ static void Element_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ if (sender is not FrameworkElement element)
+ return;
+ var groupName = GetUniformMinWidthGroup(element);
+ var topParent = element.FindTopParentOnVisualTree();
+ var groups = _uniformMinWidthGroups[topParent];
+ var group = groups[groupName];
+ var maxWidthElement = group.Elements.MaxBy(i => i.ActualWidth);
+ if (maxWidthElement is null)
+ return;
+
+ if (maxWidthElement.ActualWidth == element.ActualWidth)
+ maxWidthElement = element;
+ if (maxWidthElement.ActualWidth > group.MaxWidth)
+ {
+ // 如果当前控件最大宽度的超过历史最大宽度, 表明非最大宽度列表中的控件超过了历史最大宽度
+ foreach (var item in group.Elements)
+ item.MinWidth = maxWidthElement.ActualWidth;
+ // 将当前控件最小宽度设为0
+ maxWidthElement.MinWidth = 0;
+ group.MaxWidthElements.Clear();
+ // 设为最大宽度的唯一控件
+ group.MaxWidthElements.Add(maxWidthElement);
+ group.MaxWidth = maxWidthElement.ActualWidth;
+ }
+ else if (group.MaxWidthElements.Count == 1)
+ {
+ maxWidthElement = group.MaxWidthElements.First();
+ // 当最大宽度控件只有一个时, 并且当前控件宽度小于历史最大宽度时, 表明需要降低全体宽度
+ if (group.MaxWidth > maxWidthElement.ActualWidth)
+ {
+ // 最小宽度设为0以自适应宽度
+ foreach (var item in group.Elements)
+ item.MinWidth = 0;
+ // 清空最大宽度列表, 让其刷新
+ group.MaxWidthElements.Clear();
+ }
+ }
+ else
+ {
+ // 将 MaxWidth 设置为 double.MaxValue 时, 可以让首次加载时进入此处
+ foreach (var item in group.Elements)
+ {
+ // 当控件最小宽度为0(表示其为主导宽度的控件), 并且其宽度等于最大宽度, 加入最大宽度列表
+ if (item.MinWidth == 0 && item.ActualWidth == maxWidthElement.ActualWidth)
+ {
+ group.MaxWidthElements.Add(item);
+ }
+ else
+ {
+ // 如果不是, 则从最大宽度列表删除, 并设置最小宽度为当前最大宽度
+ group.MaxWidthElements.Remove(item);
+ item.MinWidth = maxWidthElement.ActualWidth;
+ }
+ }
+ group.MaxWidth = maxWidthElement.ActualWidth;
+ }
+ }
+ #endregion
+ }
+
+ #endregion
+}
+
+///
+/// 统一最小宽度分组信息
+///
+public class UniformMinWidthGroupInfo
+{
+ ///
+ /// 最后一个最大宽度
+ ///
+ public double MaxWidth { get; set; } = double.MaxValue;
+
+ ///
+ /// 所有控件
+ ///
+ public HashSet Elements { get; } = new();
+
+ ///
+ /// 最大宽度的控件
+ ///
+ public HashSet MaxWidthElements { get; } = new();
+}
diff --git a/VPet.Solution/Utils/Expansions.cs b/VPet.Solution/Utils/Expansions.cs
new file mode 100644
index 0000000..fbe15d4
--- /dev/null
+++ b/VPet.Solution/Utils/Expansions.cs
@@ -0,0 +1,419 @@
+using System.Collections;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace HKW.HKWUtils;
+
+///
+/// 拓展
+///
+public static class Extensions
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool Contains(this string source, string value, StringComparison comparisonType)
+ {
+ return source.IndexOf(value, comparisonType) >= 0;
+ }
+
+ public static TSource MaxBy(
+ this IEnumerable source,
+ Func keySelector
+ )
+ {
+ using IEnumerator e = source.GetEnumerator();
+ if (e.MoveNext() is false)
+ return default;
+ TSource value = e.Current;
+ TKey key = keySelector(value);
+ while (e.MoveNext())
+ {
+ TSource nextValue = e.Current;
+ TKey nextKey = keySelector(nextValue);
+ if (Comparer.Default.Compare(nextKey, key) > 0)
+ {
+ key = nextKey;
+ value = nextValue;
+ }
+ }
+ return value;
+ }
+
+ //public static string GetSourceFile(this BitmapImage image)
+ //{
+ // return ((FileStream)image.StreamSource).Name;
+ //}
+
+ ///
+ /// 关闭流
+ ///
+ /// 图像资源
+ public static void CloseStream(this ImageSource source)
+ {
+ if (source is not BitmapImage image)
+ return;
+ image.StreamSource?.Close();
+ }
+
+ ///
+ /// 图像复制
+ ///
+ /// 图像
+ /// 复制的图像
+ public static BitmapImage Copy(this BitmapImage image)
+ {
+ if (image is null)
+ return null;
+ BitmapImage newImage = new();
+ newImage.BeginInit();
+ newImage.DecodePixelWidth = image.DecodePixelWidth;
+ newImage.DecodePixelHeight = image.DecodePixelHeight;
+ try
+ {
+ using var bitmap = new Bitmap(image.StreamSource);
+ var ms = new MemoryStream();
+ bitmap.Save(ms, ImageFormat.Png);
+ image.StreamSource.CopyTo(ms);
+ newImage.StreamSource = ms;
+ }
+ finally
+ {
+ newImage.EndInit();
+ }
+ return newImage;
+ }
+
+ ///
+ /// 保存至Png图片
+ ///
+ /// 图片资源
+ /// 路径
+ //public static void SaveToPng(this BitmapImage image, string path)
+ //{
+ // if (image is null)
+ // return;
+ // if (path.EndsWith(".png") is false)
+ // path += ".png";
+ // var encoder = new PngBitmapEncoder();
+ // var stream = image.StreamSource;
+ // // 保存位置
+ // var position = stream.Position;
+ // // 必须要重置位置, 否则EndInit将出错
+ // stream.Seek(0, SeekOrigin.Begin);
+ // encoder.Frames.Add(BitmapFrame.Create(image.StreamSource));
+ // // 恢复位置
+ // stream.Seek(position, SeekOrigin.Begin);
+ // using var fs = new FileStream(path, FileMode.Create);
+ // encoder.Save(fs);
+ //}
+ public static void SaveToPng(this BitmapImage image, string path)
+ {
+ if (image is null)
+ return;
+ if (path.EndsWith(".png") is false)
+ path += ".png";
+ var stream = image.StreamSource;
+ // 保存位置
+ var position = stream.Position;
+ // 必须要重置位置, 否则EndInit将出错
+ stream.Seek(0, SeekOrigin.Begin);
+ using var fs = new FileStream(path, FileMode.Create);
+ stream.CopyTo(fs);
+ // 恢复位置
+ stream.Seek(position, SeekOrigin.Begin);
+ }
+
+ ///
+ /// 尝试添加
+ ///
+ /// 键类型
+ ///
+ ///
+ /// 键
+ /// 值
+ /// 成功为 失败为
+ public static bool TryAdd(
+ this IDictionary dictionary,
+ TKey key,
+ TValue value
+ )
+ {
+ if (dictionary.ContainsKey(key))
+ return false;
+ dictionary.Add(key, value);
+ return true;
+ }
+
+ ///
+ /// 流内容对比
+ ///
+ /// 原始流
+ /// 目标流
+ /// 缓冲区大小 (越大速度越快(流内容越大效果越明显), 但会提高内存占用 (bufferSize = bufferLength * sizeof(long) * 2))
+ /// 内容相同为 否则为
+ public static bool ContentsEqual(this Stream source, Stream target, int bufferLength = 8)
+ {
+ int bufferSize = bufferLength * sizeof(long);
+ var sourceBuffer = new byte[bufferSize];
+ var targetBuffer = new byte[bufferSize];
+ while (true)
+ {
+ int sourceCount = ReadFullBuffer(source, sourceBuffer);
+ int targetCount = ReadFullBuffer(target, targetBuffer);
+ if (sourceCount != targetCount)
+ return false;
+ if (sourceCount == 0)
+ return true;
+
+ for (int i = 0; i < sourceCount; i += sizeof(long))
+ if (BitConverter.ToInt64(sourceBuffer, i) != BitConverter.ToInt64(targetBuffer, i))
+ return false;
+ }
+ static int ReadFullBuffer(Stream stream, byte[] buffer)
+ {
+ int bytesRead = 0;
+ while (bytesRead < buffer.Length)
+ {
+ int read = stream.Read(buffer, bytesRead, buffer.Length - bytesRead);
+ if (read == 0)
+ return bytesRead;
+ bytesRead += read;
+ }
+ return bytesRead;
+ }
+ }
+
+ public static T? FindVisualChild(this DependencyObject obj)
+ where T : DependencyObject
+ {
+ if (obj is null)
+ return null;
+ var count = VisualTreeHelper.GetChildrenCount(obj);
+ for (int i = 0; i < count; i++)
+ {
+ var child = VisualTreeHelper.GetChild(obj, i);
+ if (child is T t)
+ return t;
+ if (FindVisualChild(child) is T childItem)
+ return childItem;
+ }
+ return null;
+ }
+
+ public static T FindParent(this DependencyObject obj)
+ where T : class
+ {
+ while (obj != null)
+ {
+ if (obj is T)
+ return obj as T;
+ obj = VisualTreeHelper.GetParent(obj);
+ }
+ return null;
+ }
+
+ public static string GetFullInfo(this CultureInfo cultureInfo)
+ {
+ return $"{cultureInfo.DisplayName} [{cultureInfo.Name}]";
+ }
+
+ ///
+ /// 尝试使用索引获取值
+ ///
+ /// 值类型
+ /// 列表
+ /// 索引
+ /// 值
+ /// 成功为 失败为
+ public static bool TryGetValue(this IList list, int index, out T value)
+ {
+ value = default;
+ if (index < 0 || index >= list.Count)
+ return false;
+ value = list[index];
+ return true;
+ }
+
+ ///
+ /// 尝试使用索引获取值
+ ///
+ /// 值类型
+ /// 列表
+ /// 索引
+ /// 值
+ /// 成功为 失败为
+ public static bool TryGetValue(this IList list, int index, out object value)
+ {
+ value = default;
+ if (index < 0 || index >= list.Count)
+ return false;
+ value = list[index];
+ return true;
+ }
+
+ ///
+ /// 获取目标
+ ///
+ /// 类型
+ /// 弱引用
+ /// 获取成功返回目标值, 获取失败则返回
+ public static T? GetTarget(this WeakReference weakReference)
+ where T : class
+ {
+ return weakReference.TryGetTarget(out var t) ? t : null;
+ }
+
+ ///
+ /// 枚举出带有索引值的枚举值
+ ///
+ /// 值类型
+ /// 集合
+ /// 带有索引的枚举值
+ public static IEnumerable> Enumerate(this IEnumerable collection)
+ {
+ var index = 0;
+ foreach (var item in collection)
+ yield return new(index++, item);
+ }
+
+ ///
+ /// 设置视图模型
+ ///
+ /// 视图模型类型
+ /// 窗口
+ public static T SetViewModel(this Window window, EventHandler? closedEvent = null)
+ where T : new()
+ {
+ if (window.DataContext is null)
+ {
+ window.DataContext = new T();
+ window.Closed += (s, e) =>
+ {
+ try
+ {
+ window.DataContext = null;
+ }
+ catch { }
+ };
+ window.Closed += closedEvent;
+ }
+ return (T)window.DataContext;
+ }
+
+ ///
+ /// 设置视图模型
+ ///
+ /// 视图模型类型
+ /// 页面
+ public static T SetViewModel(this Page page)
+ where T : new()
+ {
+ return (T)(page.DataContext ??= new T());
+ }
+
+ private static Dictionary _windowCloseStates = new();
+
+ ///
+ /// 设置关闭状态
+ ///
+ ///
+ /// 关闭状态
+ public static void SetCloseState(this Window window, WindowCloseState state)
+ {
+ window.Closing -= WindowCloseState_Closing;
+ window.Closing += WindowCloseState_Closing;
+ _windowCloseStates[window] = state;
+ }
+
+ ///
+ /// 强制关闭
+ ///
+ ///
+ public static void CloseX(this Window? window)
+ {
+ if (window is null)
+ return;
+ _windowCloseStates.Remove(window);
+ window.Closing -= WindowCloseState_Closing;
+ window.Close();
+ }
+
+ ///
+ /// 显示或者聚焦
+ ///
+ ///
+ public static void ShowOrActivate(this Window? window)
+ {
+ if (window is null)
+ return;
+ if (window.IsVisible is false)
+ window.Show();
+ window.Activate();
+ }
+
+ private static void WindowCloseState_Closing(object sender, CancelEventArgs e)
+ {
+ if (sender is not Window window)
+ return;
+ if (_windowCloseStates.TryGetValue(window, out var state) is false)
+ return;
+ if (state is WindowCloseState.Close)
+ return;
+ e.Cancel = true;
+ window.Visibility =
+ state is WindowCloseState.Hidden ? Visibility.Hidden : Visibility.Collapsed;
+ return;
+ }
+}
+
+public enum WindowCloseState
+{
+ Close,
+ Hidden,
+ Collapsed
+}
+
+///
+/// 项信息
+///
+///
+[DebuggerDisplay("[{Index}, {Value}]")]
+public readonly struct ItemInfo
+{
+ ///
+ /// 索引值
+ ///
+ public int Index { get; }
+
+ ///
+ /// 值
+ ///
+ public T Value { get; }
+
+ ///
+ /// 值
+ /// 索引值
+ public ItemInfo(int index, T value)
+ {
+ Index = index;
+ Value = value;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"[{Index}, {Value}]";
+ }
+}
diff --git a/VPet.Solution/Utils/FindTopParent.cs b/VPet.Solution/Utils/FindTopParent.cs
new file mode 100644
index 0000000..f848c2b
--- /dev/null
+++ b/VPet.Solution/Utils/FindTopParent.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace HKW.WPF.Extensions;
+
+public static partial class WPFExtensions
+{
+ ///
+ /// 判断是否是顶级元素, 它必须是 , , 中的一个
+ ///
+ /// 元素
+ public static bool IsTopParent(this FrameworkElement element)
+ {
+ if (element is Window || element is Page || element is UserControl)
+ return true;
+ return false;
+ }
+
+ ///
+ /// 寻找它的顶级元素, 它肯定是 , , 中的一个
+ ///
+ /// 元素
+ public static FrameworkElement FindTopParent(this FrameworkElement element)
+ {
+ if (element.IsTopParent())
+ return element;
+ var parent = element.Parent as FrameworkElement;
+ while (parent is not null)
+ {
+ if (parent.IsTopParent())
+ return parent;
+ parent = parent.Parent as FrameworkElement;
+ }
+ return null!;
+ }
+
+ ///
+ /// 尝试寻找它的顶级元素, 它肯定是 , , 中的一个
+ ///
+ /// 元素
+ /// 顶级元素
+ /// 成功为 失败为
+ public static bool TryFindTopParent(
+ this FrameworkElement element,
+ out FrameworkElement topParent
+ )
+ {
+ var result = element.FindTopParent();
+ if (result is null)
+ {
+ topParent = null;
+ return false;
+ }
+ topParent = result;
+ return true;
+ }
+
+ ///
+ /// 在视觉树上寻找它的顶级元素, 它肯定是 , , 中的一个
+ ///
+ /// 用于 无法获取到顶级元素的情况下使用, 此元素通常位于 中
+ ///
+ ///
+ /// 元素
+ public static FrameworkElement FindTopParentOnVisualTree(this FrameworkElement element)
+ {
+ if (element.TryFindTopParent(out var top))
+ return top;
+ var temp = (DependencyObject)element;
+ while ((temp = VisualTreeHelper.GetParent(temp)) is not null)
+ {
+ if (temp is FrameworkElement fe && fe.TryFindTopParent(out var topParent))
+ return topParent;
+ }
+ return null!;
+ }
+
+ ///
+ /// 尝试在视觉树上寻找它的顶级元素, 它肯定是 , , 中的一个
+ ///
+ /// 元素
+ /// 顶级元素
+ /// 成功为 失败为
+ public static bool FindTopParentOnVisualTree(
+ this FrameworkElement element,
+ out FrameworkElement topParent
+ )
+ {
+ var result = element.FindTopParentOnVisualTree();
+ if (result is null)
+ {
+ topParent = null;
+ return false;
+ }
+ topParent = result;
+ return true;
+ }
+
+ ///
+ /// 寻找它的顶级元素, 它肯定是 , , 中的一个
+ ///
+ /// 元素
+ public static TParent FindTopParent(this FrameworkElement element)
+ where TParent : FrameworkElement
+ {
+ var type = typeof(TParent);
+ if (type != typeof(Window) && type != typeof(Page) && type != typeof(UserControl))
+ throw new Exception("TParent type must be Window, Page or UserControl");
+ return (TParent)FindTopParent(element);
+ }
+}
diff --git a/VPet.Solution/Utils/HashCode.cs b/VPet.Solution/Utils/HashCode.cs
new file mode 100644
index 0000000..b88ff89
--- /dev/null
+++ b/VPet.Solution/Utils/HashCode.cs
@@ -0,0 +1,66 @@
+namespace HKW.HKWUtils;
+
+///
+/// 哈希值
+///
+public class HashCode
+{
+ ///
+ /// 默认种子
+ ///
+ public const int DefaultSeed = 114514;
+
+ ///
+ /// 默认系数
+ ///
+ public const int DefaultFactor = 1919810;
+
+ ///
+ /// 组合哈希值
+ ///
+ /// 值
+ /// 组合的哈希值
+ public static int Combine(params object[] values)
+ {
+ return CustomHash(DefaultSeed, DefaultFactor, values.Select(v => v.GetHashCode()));
+ }
+
+ ///
+ /// 组合哈希值
+ ///
+ /// 种子
+ /// 系数
+ /// 值
+ /// 组合的哈希值
+ public static int Combine(int seed, int factor, params object[] values)
+ {
+ return CustomHash(seed, factor, values.Select(v => v.GetHashCode()));
+ }
+
+ ///
+ /// 自定义组合哈希
+ ///
+ /// 种子
+ /// 系数
+ /// 哈希集合
+ /// 组合的哈希
+ public static int CustomHash(int seed, int factor, IEnumerable collection)
+ {
+ int hash = seed;
+ foreach (int i in collection)
+ hash = unchecked((hash * factor) + i);
+ return hash;
+ }
+
+ ///
+ /// 自定义组合哈希
+ ///
+ /// 种子
+ /// 系数
+ /// 哈希集合
+ /// 组合的哈希
+ public static int CustomHash(int seed, int factor, params int[] values)
+ {
+ return CustomHash(seed, factor, collection: values);
+ }
+}
diff --git a/VPet.Solution/Utils/ObservableEnumFlags.cs b/VPet.Solution/Utils/ObservableEnumFlags.cs
new file mode 100644
index 0000000..3e863ce
--- /dev/null
+++ b/VPet.Solution/Utils/ObservableEnumFlags.cs
@@ -0,0 +1,72 @@
+namespace HKW.HKWUtils;
+
+///
+/// 可观察的枚举标签模型
+///
+/// 枚举类型
+public class ObservableEnumFlags : ObservableClass>
+ where T : Enum
+{
+ private T _EnumValue;
+ public T EnumValue
+ {
+ get => _EnumValue;
+ set => SetProperty(ref _EnumValue, value);
+ }
+
+ ///
+ /// 添加枚举命令
+ ///
+ public ObservableCommand AddCommand { get; } = new();
+
+ ///
+ /// 删除枚举命令
+ ///
+ public ObservableCommand RemoveCommand { get; } = new();
+
+ ///
+ /// 枚举类型
+ ///
+ public Type EnumType = typeof(T);
+
+ ///
+ /// 枚举基类
+ ///
+ public Type UnderlyingType { get; } = Enum.GetUnderlyingType(typeof(T));
+
+ public ObservableEnumFlags()
+ {
+ if (Attribute.IsDefined(EnumType, typeof(FlagsAttribute)) is false)
+ throw new Exception($"此枚举类型未使用特性 [{nameof(FlagsAttribute)}]");
+ AddCommand.ExecuteCommand += AddCommand_Execute;
+ RemoveCommand.ExecuteCommand += RemoveCommand_Execute;
+ }
+
+ public ObservableEnumFlags(T value)
+ : this()
+ {
+ EnumValue = value;
+ }
+
+ private void AddCommand_Execute(T v)
+ {
+ if (UnderlyingType == typeof(int))
+ {
+ EnumValue = (T)
+ Enum.Parse(EnumType, (Convert.ToInt32(EnumValue) | Convert.ToInt32(v)).ToString());
+ }
+ else
+ throw new NotImplementedException($"Value type: {UnderlyingType}");
+ }
+
+ private void RemoveCommand_Execute(T v)
+ {
+ if (UnderlyingType == typeof(int))
+ {
+ EnumValue = (T)
+ Enum.Parse(EnumType, (Convert.ToInt32(EnumValue) & ~Convert.ToInt32(v)).ToString());
+ }
+ else
+ throw new NotImplementedException($"Value type: {UnderlyingType}");
+ }
+}
diff --git a/VPet.Solution/Utils/ObservablePoint.cs b/VPet.Solution/Utils/ObservablePoint.cs
new file mode 100644
index 0000000..4b886bc
--- /dev/null
+++ b/VPet.Solution/Utils/ObservablePoint.cs
@@ -0,0 +1,181 @@
+using System.Diagnostics;
+using System.Windows;
+
+namespace HKW.HKWUtils;
+
+///
+/// 可观察地点
+///
+[DebuggerDisplay("X = {X}, Y = {Y}")]
+public class ObservablePoint : ObservableClass, IEquatable
+{
+ private double _x;
+ public double X
+ {
+ get => _x;
+ set => SetProperty(ref _x, value);
+ }
+
+ private double _y;
+ public double Y
+ {
+ get => _y;
+ set => SetProperty(ref _y, value);
+ }
+
+ public ObservablePoint() { }
+
+ public ObservablePoint(double x, double y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ ///
+ /// 复制一个新的对象
+ ///
+ /// 新对象
+ public ObservablePoint Copy()
+ {
+ return new(X, Y);
+ }
+
+ public Point ToPoint()
+ {
+ return new Point(X, Y);
+ }
+
+ #region Other
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(X, Y);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is ObservablePoint temp
+ && EqualityComparer.Default.Equals(X, temp.X)
+ && EqualityComparer.Default.Equals(Y, temp.Y);
+ }
+
+ ///
+ public bool Equals(ObservablePoint? other)
+ {
+ return Equals(obj: other);
+ }
+
+ ///
+ public static bool operator ==(ObservablePoint a, ObservablePoint b)
+ {
+ return Equals(a, b);
+ }
+
+ ///
+ public static bool operator !=(ObservablePoint a, ObservablePoint b)
+ {
+ return Equals(a, b) is not true;
+ }
+
+ #endregion
+}
+
+public class ObservablePointToPointConverter : ReflectionConverterBase
+{
+ public override Point Convert(ObservablePoint sourceValue)
+ {
+ return new(sourceValue.X, sourceValue.Y);
+ }
+
+ public override ObservablePoint ConvertBack(Point targetValue)
+ {
+ return new(targetValue.X, targetValue.Y);
+ }
+}
+
+/////
+///// 可观察地点
+/////
+///// 类型
+//public class ObservablePoint
+// : ObservableClass>,
+// IEquatable>
+//{
+// private T _x;
+// public T X
+// {
+// get => _x;
+// set => SetProperty(ref _x, value);
+// }
+
+// private T _y;
+// public T Y
+// {
+// get => _y;
+// set => SetProperty(ref _y, value);
+// }
+
+// public ObservablePoint() { }
+
+// public ObservablePoint(T x, T y)
+// {
+// X = x;
+// Y = y;
+// }
+
+// ///
+// /// 复制一个新的对象
+// ///
+// /// 新对象
+// public ObservablePoint Copy()
+// {
+// return new(X, Y);
+// }
+
+// #region Create
+
+// public static ObservablePoint Create(Point point)
+// {
+// return new(point.X, point.Y);
+// }
+
+// #endregion
+
+// #region Other
+
+// ///
+// public override int GetHashCode()
+// {
+// return HashCode.Combine(X, Y);
+// }
+
+// ///
+// public override bool Equals(object? obj)
+// {
+// return obj is ObservablePoint temp
+// && EqualityComparer.Default.Equals(X, temp.X)
+// && EqualityComparer.Default.Equals(Y, temp.Y);
+// }
+
+// ///
+// public bool Equals(ObservablePoint? other)
+// {
+// return Equals(obj: other);
+// }
+
+// ///
+// public static bool operator ==(ObservablePoint a, ObservablePoint b)
+// {
+// return Equals(a, b);
+// }
+
+// ///
+// public static bool operator !=(ObservablePoint a, ObservablePoint b)
+// {
+// return Equals(a, b) is not true;
+// }
+
+// #endregion
+//}
diff --git a/VPet.Solution/Utils/ObservableRange.cs b/VPet.Solution/Utils/ObservableRange.cs
new file mode 100644
index 0000000..76a1d6f
--- /dev/null
+++ b/VPet.Solution/Utils/ObservableRange.cs
@@ -0,0 +1,77 @@
+namespace HKW.HKWUtils;
+
+///
+/// 可观察的范围
+///
+/// 类型
+public class ObservableRange
+ : ObservableClass>,
+ IEquatable>
+{
+ private T _min;
+ public T Min
+ {
+ get => _min;
+ set => SetProperty(ref _min, value);
+ }
+
+ private T _max;
+ public T Max
+ {
+ get => _max;
+ set => SetProperty(ref _max, value);
+ }
+
+ public ObservableRange() { }
+
+ public ObservableRange(T min, T max)
+ {
+ _min = min;
+ _max = max;
+ }
+
+ ///
+ /// 复制一个新的对象
+ ///
+ /// 新对象
+ public ObservableRange Copy()
+ {
+ return new(Min, Max);
+ }
+
+ #region Other
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Min, Max);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is ObservableRange temp
+ && EqualityComparer.Default.Equals(Min, temp.Min)
+ && EqualityComparer.Default.Equals(Max, temp.Max);
+ }
+
+ ///
+ public bool Equals(ObservableRange? other)
+ {
+ return Equals(obj: other);
+ }
+
+ ///
+ public static bool operator ==(ObservableRange a, ObservableRange b)
+ {
+ return Equals(a, b);
+ }
+
+ ///
+ public static bool operator !=(ObservableRange a, ObservableRange b)
+ {
+ return Equals(a, b) is not true;
+ }
+
+ #endregion
+}
diff --git a/VPet.Solution/Utils/ObservableRect.cs b/VPet.Solution/Utils/ObservableRect.cs
new file mode 100644
index 0000000..da0d563
--- /dev/null
+++ b/VPet.Solution/Utils/ObservableRect.cs
@@ -0,0 +1,89 @@
+namespace HKW.HKWUtils;
+
+public class ObservableRect : ObservableClass, IEquatable
+{
+ private double _x;
+ public double X
+ {
+ get => _x;
+ set => SetProperty(ref _x, value);
+ }
+
+ private double _y;
+ public double Y
+ {
+ get => _y;
+ set => SetProperty(ref _y, value);
+ }
+
+ private double _width;
+ public double Width
+ {
+ get => _width;
+ set => SetProperty(ref _width, value);
+ }
+
+ private double _heigth;
+ public double Height
+ {
+ get => _heigth;
+ set => SetProperty(ref _heigth, value);
+ }
+
+ public ObservableRect() { }
+
+ public ObservableRect(double x, double y, double width, double hetght)
+ {
+ X = x;
+ Y = y;
+ Width = width;
+ Height = hetght;
+ }
+
+ ///
+ /// 复制一个新的对象
+ ///
+ /// 新对象
+ public ObservableRect Copy()
+ {
+ return new(X, Y, Width, Height);
+ }
+
+ #region Other
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(X, Y, Width, Height);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is ObservableRect temp
+ && EqualityComparer.Default.Equals(X, temp.X)
+ && EqualityComparer.Default.Equals(Y, temp.Y)
+ && EqualityComparer.Default.Equals(Width, temp.Width)
+ && EqualityComparer.Default.Equals(Height, temp.Height);
+ }
+
+ ///
+ public bool Equals(ObservableRect? other)
+ {
+ return Equals(obj: other);
+ }
+
+ ///
+ public static bool operator ==(ObservableRect a, ObservableRect b)
+ {
+ return Equals(a, b);
+ }
+
+ ///
+ public static bool operator !=(ObservableRect a, ObservableRect b)
+ {
+ return Equals(a, b) is not true;
+ }
+
+ #endregion
+}
diff --git a/VPet.Solution/Utils/ReflectionUtils.cs b/VPet.Solution/Utils/ReflectionUtils.cs
new file mode 100644
index 0000000..b271418
--- /dev/null
+++ b/VPet.Solution/Utils/ReflectionUtils.cs
@@ -0,0 +1,280 @@
+using FastMember;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HKW.HKWUtils;
+
+public static class ReflectionUtils
+{
+ private static readonly BindingFlags _propertyBindingFlags =
+ BindingFlags.Instance | BindingFlags.Public;
+
+ private static readonly Dictionary _reflectionConverters = new();
+
+ ///
+ /// 类型信息
+ ///
+ /// (TargetType, (PropertyName, TargetPropertyName))
+ ///
+ ///
+ private static readonly Dictionary _typePropertyReflectionInfos =
+ new();
+
+ public static void SetValue(object source, object target, ReflectionOptions options = null!)
+ {
+ options ??= new();
+ var sourceType = source.GetType();
+ var targetType = target.GetType();
+ if (_typePropertyReflectionInfos.TryGetValue(sourceType, out var sourceInfo) is false)
+ sourceInfo = _typePropertyReflectionInfos[sourceType] = GetReflectionObjectInfo(
+ sourceType
+ );
+ if (_typePropertyReflectionInfos.TryGetValue(targetType, out var targetInfo) is false)
+ targetInfo = _typePropertyReflectionInfos[targetType] = GetReflectionObjectInfo(
+ targetType
+ );
+
+ var sourceAccessor = ObjectAccessor.Create(source);
+ var targetAccessor = ObjectAccessor.Create(target);
+
+ foreach (var property in targetType.GetProperties(_propertyBindingFlags))
+ {
+ // 尝试获取目标属性信息
+ targetInfo.PropertyInfos.TryGetValue(property.Name, out var targetReflectionInfo);
+ // 检测忽视
+ if (targetReflectionInfo?.IsIgnore is true)
+ continue;
+ // 获取源属性名
+ var sourcePropertyName = targetReflectionInfo is null
+ ? property.Name
+ : targetReflectionInfo.TargetName;
+ // 获取源属性信息
+ sourceInfo.PropertyInfos.TryGetValue(sourcePropertyName, out var sourceReflectionInfo);
+ if (sourceInfo.PropertyNames.Contains(sourcePropertyName) is false)
+ {
+ if (targetReflectionInfo?.IsRequired is true)
+ options.UnassignedRequiredProperties.Add(property.Name);
+ continue;
+ }
+
+ // 获取源值
+ var sourceValue = sourceAccessor[sourcePropertyName];
+ // 转换源值
+ if (sourceReflectionInfo?.Converter is IReflectionConverter sourceConverter)
+ sourceValue = sourceConverter.Convert(sourceValue);
+ else if (targetReflectionInfo?.Converter is IReflectionConverter targetConverter)
+ sourceValue = targetConverter.ConvertBack(sourceValue);
+ // 比较源值和目标值
+ if (options.CheckValueEquals)
+ {
+ var targetValue = targetAccessor[property.Name];
+ if (sourceValue.Equals(targetValue))
+ continue;
+ }
+ targetAccessor[property.Name] = sourceValue;
+ }
+ }
+
+ private static ReflectionObjectInfo GetReflectionObjectInfo(Type type)
+ {
+ var objectInfo = new ReflectionObjectInfo(type);
+ foreach (var property in type.GetProperties(_propertyBindingFlags))
+ {
+ // 获取是否被忽视
+ if (property.IsDefined(typeof(ReflectionPropertyIgnoreAttribute)))
+ {
+ objectInfo.PropertyInfos[property.Name] = new(property.Name) { IsIgnore = true };
+ continue;
+ }
+ if (
+ property.IsDefined(typeof(ReflectionPropertyAttribute)) is false
+ && property.IsDefined(typeof(ReflectionPropertyConverterAttribute)) is false
+ )
+ continue;
+ var propertyInfo = new ReflectionPropertyInfo(property.Name);
+ // 获取属性信息
+ if (
+ property.GetCustomAttribute()
+ is ReflectionPropertyAttribute propertyInfoAttribute
+ )
+ {
+ if (string.IsNullOrWhiteSpace(propertyInfoAttribute.TargetPropertyName) is false)
+ propertyInfo.TargetName = propertyInfoAttribute.TargetPropertyName;
+ propertyInfo.IsRequired = propertyInfoAttribute.IsRequired;
+ }
+ // 获取属性转换器
+ if (
+ property.GetCustomAttribute()
+ is ReflectionPropertyConverterAttribute propertyConverterAttribute
+ )
+ {
+ if (
+ _reflectionConverters.TryGetValue(
+ propertyConverterAttribute.ConverterType,
+ out var converter
+ )
+ is false
+ )
+ converter = _reflectionConverters[propertyConverterAttribute.ConverterType] =
+ (IReflectionConverter)
+ TypeAccessor
+ .Create(propertyConverterAttribute.ConverterType)
+ .CreateNew();
+ propertyInfo.Converter = converter;
+ }
+ objectInfo.PropertyInfos[property.Name] = propertyInfo;
+ }
+ return objectInfo;
+ }
+}
+
+///
+/// 反射对象信息
+///
+public class ReflectionObjectInfo
+{
+ public HashSet PropertyNames { get; }
+
+ public Dictionary PropertyInfos { get; } = new();
+
+ public ReflectionObjectInfo(Type type)
+ {
+ PropertyNames = new(
+ type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(p => p.Name)
+ );
+ }
+}
+
+public class ReflectionPropertyInfo
+{
+ ///
+ /// 目标属性名称
+ ///
+ public string TargetName { get; set; }
+
+ ///
+ /// 是必要的
+ ///
+ [DefaultValue(false)]
+ public bool IsRequired { get; set; } = false;
+
+ ///
+ /// 是忽视的
+ ///
+ public bool IsIgnore { get; set; } = false;
+
+ ///
+ /// 反射值转换器
+ ///
+ public IReflectionConverter? Converter { get; set; } = null;
+
+ public ReflectionPropertyInfo(string propertyName)
+ {
+ TargetName = propertyName;
+ }
+}
+
+///
+/// 反射属性信息
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ReflectionPropertyAttribute : Attribute
+{
+ ///
+ /// 属性名称
+ ///
+ public string TargetPropertyName { get; }
+
+ ///
+ /// 是必要的
+ ///
+ [DefaultValue(true)]
+ public bool IsRequired { get; } = true;
+
+ public ReflectionPropertyAttribute(bool isRequired = true)
+ {
+ IsRequired = isRequired;
+ }
+
+ public ReflectionPropertyAttribute(string targetPropertyName, bool isRequired = true)
+ {
+ TargetPropertyName = targetPropertyName;
+ IsRequired = isRequired;
+ }
+}
+
+///
+/// 反射属性转换器
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ReflectionPropertyConverterAttribute : Attribute
+{
+ ///
+ /// 反射转换器
+ ///
+ public Type ConverterType { get; }
+
+ public ReflectionPropertyConverterAttribute(Type converterType)
+ {
+ ConverterType = converterType;
+ }
+}
+
+///
+/// 反射属性忽视
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ReflectionPropertyIgnoreAttribute : Attribute { }
+
+///
+/// 反射设置
+///
+public class ReflectionOptions
+{
+ ///
+ /// 检查值是否相等, 若相等则跳过赋值
+ ///
+ [DefaultValue(false)]
+ public bool CheckValueEquals { get; set; } = false;
+
+ ///
+ /// 未赋值的必要属性
+ ///
+ public List UnassignedRequiredProperties { get; set; } = new();
+}
+
+///
+/// 反射转换器
+///
+public interface IReflectionConverter
+{
+ public object Convert(object sourceValue);
+ public object ConvertBack(object targetValue);
+}
+
+///
+/// 反射转换器
+///
+/// 源值类型
+/// 目标值类型
+public abstract class ReflectionConverterBase : IReflectionConverter
+{
+ public abstract TTarget Convert(TSource sourceValue);
+
+ public abstract TSource ConvertBack(TTarget targetValue);
+
+ object IReflectionConverter.Convert(object sourceValue)
+ {
+ return Convert((TSource)sourceValue);
+ }
+
+ object IReflectionConverter.ConvertBack(object targetValue)
+ {
+ return ConvertBack((TTarget)targetValue);
+ }
+}
diff --git a/VPet.Solution/Utils/Utils.cs b/VPet.Solution/Utils/Utils.cs
new file mode 100644
index 0000000..7b1aec3
--- /dev/null
+++ b/VPet.Solution/Utils/Utils.cs
@@ -0,0 +1,126 @@
+using System.Windows.Media.Imaging;
+
+namespace HKW.HKWUtils;
+
+///
+/// 工具
+///
+public static class Utils
+{
+ ///
+ /// 解码像素宽度
+ ///
+ public const int DecodePixelWidth = 250;
+
+ ///
+ /// 解码像素高度
+ ///
+ public const int DecodePixelHeight = 250;
+ public static char[] Separator { get; } = new char[] { '_' };
+
+ ///
+ /// 载入图片到流
+ ///
+ /// 图片路径
+ /// 图片
+ public static BitmapImage LoadImageToStream(string imagePath)
+ {
+ if (string.IsNullOrWhiteSpace(imagePath) || File.Exists(imagePath) is false)
+ return null;
+ BitmapImage bitmapImage = new();
+ bitmapImage.BeginInit();
+ try
+ {
+ bitmapImage.StreamSource = new StreamReader(imagePath).BaseStream;
+ }
+ finally
+ {
+ bitmapImage.EndInit();
+ }
+ return bitmapImage;
+ }
+
+ ///
+ /// 载入图片至内存流
+ ///
+ /// 图片路径
+ ///
+ public static BitmapImage LoadImageToMemoryStream(string imagePath)
+ {
+ BitmapImage bitmapImage = new();
+ bitmapImage.BeginInit();
+ try
+ {
+ var bytes = File.ReadAllBytes(imagePath);
+ bitmapImage.StreamSource = new MemoryStream(bytes);
+ bitmapImage.DecodePixelWidth = DecodePixelWidth;
+ }
+ finally
+ {
+ bitmapImage.EndInit();
+ }
+ return bitmapImage;
+ }
+
+ ///
+ /// 载入图片至内存流
+ ///
+ /// 图片流
+ ///
+ public static BitmapImage LoadImageToMemoryStream(Stream imageStream)
+ {
+ BitmapImage bitmapImage = new();
+ bitmapImage.BeginInit();
+ try
+ {
+ bitmapImage.StreamSource = imageStream;
+ bitmapImage.DecodePixelWidth = DecodePixelWidth;
+ }
+ finally
+ {
+ bitmapImage.EndInit();
+ }
+ return bitmapImage;
+ }
+
+ ///
+ /// 获取布尔值
+ ///
+ /// 值
+ /// 目标布尔值
+ /// 为空时布尔值
+ ///
+ public static bool GetBool(object value, bool boolValue, bool nullValue)
+ {
+ if (value is null)
+ return nullValue;
+ else if (value is bool b)
+ return b == boolValue;
+ else if (bool.TryParse(value.ToString(), out b))
+ return b == boolValue;
+ else
+ return false;
+ }
+
+ ///
+ /// 打开文件
+ ///
+ /// 文件路径
+ public static void OpenLink(string filePath)
+ {
+ System.Diagnostics.Process
+ .Start(new System.Diagnostics.ProcessStartInfo(filePath) { UseShellExecute = true })
+ ?.Close();
+ }
+
+ ///
+ /// 从资源管理器打开文件
+ ///
+ /// 文件路径
+ public static void OpenFileInExplorer(string filePath)
+ {
+ System.Diagnostics.Process
+ .Start("Explorer", $"/select,{Path.GetFullPath(filePath)}")
+ ?.Close();
+ }
+}
diff --git a/VPet.Solution/VPet.Solution.csproj b/VPet.Solution/VPet.Solution.csproj
index daa478f..7a6db90 100644
--- a/VPet.Solution/VPet.Solution.csproj
+++ b/VPet.Solution/VPet.Solution.csproj
@@ -14,6 +14,7 @@
4
true
true
+ latest
AnyCPU
@@ -34,21 +35,28 @@
prompt
4
+
+ icon.ico
+
+
+ ..\packages\FastMember.1.5.0\lib\net461\FastMember.dll
+
- ..\packages\LinePutScript.1.9.2\lib\net462\LinePutScript.dll
+ ..\packages\LinePutScript.1.10.2\lib\net462\LinePutScript.dll
..\packages\LinePutScript.Localization.WPF.1.0.6\lib\net462\LinePutScript.Localization.WPF.dll
-
- ..\packages\Panuon.WPF.1.0.2\lib\net462\Panuon.WPF.dll
+
+ ..\packages\Panuon.WPF.1.0.3\lib\net462\Panuon.WPF.dll
-
- ..\packages\Panuon.WPF.UI.1.1.15.8\lib\net462\Panuon.WPF.UI.dll
+
+ ..\packages\Panuon.WPF.UI.1.1.16.5\lib\net462\Panuon.WPF.UI.dll
+
@@ -67,7 +75,120 @@
MSBuild:Compile
Designer
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SaveDataPage.xaml
+
+
+ SaveStatisticPage.xaml
+
+
+ SaveWindow.xaml
+
+
+ SettingWindow.xaml
+
+
+ CustomizedSettingPage.xaml
+
+
+ DiagnosticSettingPage.xaml
+
+
+ ModSettingPage.xaml
+
+
+ SystemSettingPage.xaml
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
MSBuild:Compile
Designer
@@ -75,10 +196,55 @@
App.xaml
Code
-
- MainWindow.xaml
- Code
+
+
+
+
+ GraphicsSettingPage.xaml
+
+ InteractiveSettingPage.xaml
+
+
+ MainWindow.xaml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+ true
+
+
+ NativeStyles.xaml
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
@@ -117,5 +283,8 @@
VPet-Simulator.Windows.Interface
+
+
+
\ No newline at end of file
diff --git a/VPet.Solution/ViewModels/MainWindowVM.cs b/VPet.Solution/ViewModels/MainWindowVM.cs
new file mode 100644
index 0000000..08b817b
--- /dev/null
+++ b/VPet.Solution/ViewModels/MainWindowVM.cs
@@ -0,0 +1,59 @@
+using HKW.HKWUtils.Observable;
+using LinePutScript.Localization.WPF;
+using Panuon.WPF.UI;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace VPet.Solution.ViewModels;
+
+public class MainWindowVM : ObservableClass
+{
+ public MainWindowVM()
+ {
+ LocalizeCore.StoreTranslation = true;
+ LocalizeCore.LoadDefaultCulture();
+ CurrentCulture = LocalizeCore.CurrentCulture;
+ FirstStartFailedCommand.ExecuteCommand += FirstStartFailedCommand_ExecuteCommand;
+ OpenLocalTextCommand.ExecuteCommand += OpenLocalTextCommand_ExecuteCommand;
+ }
+
+ private void OpenLocalTextCommand_ExecuteCommand()
+ {
+ var sb = new StringBuilder();
+ foreach (var a in LocalizeCore.StoreTranslationList)
+ sb.AppendLine(a.Replace("\r\n", "\\r\\n"));
+ MessageBoxX.Show(sb.ToString());
+ }
+
+ private void FirstStartFailedCommand_ExecuteCommand()
+ {
+ if (LocalizeCore.CurrentCulture == "zh-Hans")
+ Utils.OpenLink("https://www.bilibili.com/read/cv26510496/");
+ else
+ Utils.OpenLink("https://steamcommunity.com/games/1920960/announcements/detail/3681184905256253203");
+ }
+
+ #region Property
+ public IEnumerable AvailableCultures => LocalizeCore.AvailableCultures;
+ #region CurrentCulture
+ private string _currentCulture = string.Empty;
+ public string CurrentCulture
+ {
+ get => _currentCulture;
+ set
+ {
+ SetProperty(ref _currentCulture, value);
+ LocalizeCore.LoadCulture(_currentCulture);
+ }
+ }
+ #endregion
+ #endregion
+
+ #region Command
+ public ObservableCommand FirstStartFailedCommand { get; } = new();
+ public ObservableCommand OpenLocalTextCommand { get; } = new();
+ #endregion
+}
diff --git a/VPet.Solution/ViewModels/SaveViewer/SaveDataPageVM.cs b/VPet.Solution/ViewModels/SaveViewer/SaveDataPageVM.cs
new file mode 100644
index 0000000..9bc73ee
--- /dev/null
+++ b/VPet.Solution/ViewModels/SaveViewer/SaveDataPageVM.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VPet.Solution.Models.SaveViewer;
+using VPet.Solution.Views.SaveViewer;
+
+namespace VPet.Solution.ViewModels.SaveViewer;
+
+public class SaveDataPageVM : ObservableClass
+{
+ private SaveModel _save;
+ public SaveModel Save
+ {
+ get => _save;
+ set => SetProperty(ref _save, value);
+ }
+
+ public SaveDataPageVM()
+ {
+ SaveWindowVM.Current.PropertyChangedX += Current_PropertyChangedX;
+ }
+
+ private void Current_PropertyChangedX(SaveWindowVM sender, PropertyChangedXEventArgs e)
+ {
+ if (e.PropertyName == nameof(SaveWindowVM.CurrentSave) && sender.CurrentSave is not null)
+ {
+ Save = sender.CurrentSave;
+ }
+ }
+}
diff --git a/VPet.Solution/ViewModels/SaveViewer/SaveStatisticPageVM.cs b/VPet.Solution/ViewModels/SaveViewer/SaveStatisticPageVM.cs
new file mode 100644
index 0000000..fd5f8ed
--- /dev/null
+++ b/VPet.Solution/ViewModels/SaveViewer/SaveStatisticPageVM.cs
@@ -0,0 +1,71 @@
+using HKW.HKWUtils.Observable;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VPet.Solution.Models.SaveViewer;
+
+namespace VPet.Solution.ViewModels.SaveViewer;
+
+public class SaveStatisticPageVM : ObservableClass
+{
+ #region Properties
+ #region Save
+ private SaveModel _save;
+ public SaveModel Save
+ {
+ get => _save;
+ set => SetProperty(ref _save, value);
+ }
+ #endregion
+
+ #region ShowStatistics
+ private IEnumerable _showStatistics;
+ public IEnumerable ShowStatistics
+ {
+ get => _showStatistics;
+ set => SetProperty(ref _showStatistics, value);
+ }
+ #endregion
+
+ #region SearchStatistic
+ private string _searchStatistic;
+ public string SearchStatistic
+ {
+ get => _searchStatistic;
+ set
+ {
+ SetProperty(ref _searchStatistic, value);
+ RefreshShowStatistics(value);
+ }
+ }
+ #endregion
+ #endregion
+
+
+ public SaveStatisticPageVM()
+ {
+ SaveWindowVM.Current.PropertyChangedX += Current_PropertyChangedX;
+ }
+
+ private void Current_PropertyChangedX(SaveWindowVM sender, PropertyChangedXEventArgs e)
+ {
+ if (e.PropertyName == nameof(SaveWindowVM.CurrentSave) && sender.CurrentSave is not null)
+ {
+ Save = sender.CurrentSave;
+ ShowStatistics = Save.Statistics;
+ }
+ }
+
+ public void RefreshShowStatistics(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ ShowStatistics = Save.Statistics;
+ else
+ ShowStatistics = Save.Statistics.Where(
+ s => s.Name.Contains(SearchStatistic, StringComparison.OrdinalIgnoreCase)
+ );
+ }
+}
diff --git a/VPet.Solution/ViewModels/SaveViewer/SaveWindowVM.cs b/VPet.Solution/ViewModels/SaveViewer/SaveWindowVM.cs
new file mode 100644
index 0000000..f3a04ea
--- /dev/null
+++ b/VPet.Solution/ViewModels/SaveViewer/SaveWindowVM.cs
@@ -0,0 +1,114 @@
+using HKW.HKWUtils.Observable;
+using LinePutScript;
+using LinePutScript.Localization.WPF;
+using Panuon.WPF.UI;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using VPet.Solution.Models;
+using VPet.Solution.Models.SaveViewer;
+using VPet.Solution.Models.SettingEditor;
+using VPet.Solution.Views.SettingEditor;
+using VPet_Simulator.Windows.Interface;
+
+namespace VPet.Solution.ViewModels.SaveViewer;
+
+public class SaveWindowVM : ObservableClass
+{
+ public static SaveWindowVM Current { get; private set; }
+
+ #region Properties
+ private SaveModel _currentSave;
+ public SaveModel CurrentSave
+ {
+ get => _currentSave;
+ set => SetProperty(ref _currentSave, value);
+ }
+
+ private readonly ObservableCollection _saves = new();
+
+ private IEnumerable _showSaves;
+ public IEnumerable ShowSaves
+ {
+ get => _showSaves;
+ set => SetProperty(ref _showSaves, value);
+ }
+
+ private string _searchSave;
+ public string SearchSave
+ {
+ get => _searchSave;
+ set => SetProperty(ref _searchSave, value);
+ }
+
+ #endregion
+
+ #region Command
+ ///
+ /// 打开文件
+ ///
+ public ObservableCommand OpenFileCommand { get; } = new();
+
+ ///
+ /// 从资源管理器打开
+ ///
+ public ObservableCommand OpenFileInExplorerCommand { get; } = new();
+ #endregion
+ public SaveWindowVM()
+ {
+ Current = this;
+ ShowSaves = _saves;
+ LoadSaves();
+
+ PropertyChanged += SaveWindowVM_PropertyChanged;
+ OpenFileCommand.ExecuteCommand += OpenFileCommand_ExecuteCommand;
+ OpenFileInExplorerCommand.ExecuteCommand += OpenFileInExplorerCommand_ExecuteCommand;
+ }
+
+ private void OpenFileInExplorerCommand_ExecuteCommand(SaveModel parameter)
+ {
+ Utils.OpenFileInExplorer(parameter.FilePath);
+ }
+
+ private void OpenFileCommand_ExecuteCommand(SaveModel parameter)
+ {
+ Utils.OpenLink(parameter.FilePath);
+ }
+
+ public void RefreshShowSaves(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ ShowSaves = _saves;
+ else
+ ShowSaves = _saves.Where(
+ s => s.Name.Contains(SearchSave, StringComparison.OrdinalIgnoreCase)
+ );
+ }
+
+ private void SaveWindowVM_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(SearchSave))
+ {
+ RefreshShowSaves(SearchSave);
+ }
+ }
+
+ private void LoadSaves()
+ {
+ var saveDirectory = Path.Combine(Environment.CurrentDirectory, "Saves");
+ if (Directory.Exists(saveDirectory) is false)
+ return;
+ foreach (var file in Directory.EnumerateFiles(saveDirectory).Where(s => s.EndsWith(".lps")))
+ {
+ var lps = new LPS(File.ReadAllText(file));
+ var save = new GameSave_v2(lps);
+ var saveModel = new SaveModel(file, save);
+ _saves.Add(saveModel);
+ }
+ }
+}
diff --git a/VPet.Solution/ViewModels/SettingEditor/CustomizedSettingPageVM.cs b/VPet.Solution/ViewModels/SettingEditor/CustomizedSettingPageVM.cs
new file mode 100644
index 0000000..7ba1b9f
--- /dev/null
+++ b/VPet.Solution/ViewModels/SettingEditor/CustomizedSettingPageVM.cs
@@ -0,0 +1,112 @@
+using HKW.HKWUtils.Observable;
+using LinePutScript.Localization.WPF;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using VPet.Solution.Models.SettingEditor;
+
+namespace VPet.Solution.ViewModels.SettingEditor;
+
+public class CustomizedSettingPageVM : ObservableClass
+{
+ #region ObservableProperty
+ #region CustomizedSetting
+
+ private CustomizedSettingModel _customizedSetting;
+ public CustomizedSettingModel CustomizedSetting
+ {
+ get => _customizedSetting;
+ set => SetProperty(ref _customizedSetting, value);
+ }
+ #endregion
+
+ #region SearchSetting
+ private string _searchSetting;
+ public string SearchLink
+ {
+ get => _searchSetting;
+ set
+ {
+ SetProperty(ref _searchSetting, value);
+ RefreshShowLinks(value);
+ }
+ }
+ #endregion
+
+ #region ShowLinks
+ private IEnumerable _showLinks;
+ public IEnumerable ShowLinks
+ {
+ get => _showLinks;
+ set => SetProperty(ref _showLinks, value);
+ }
+ #endregion
+ #endregion
+
+ #region Command
+ public ObservableCommand AddLinkCommand { get; } = new();
+ public ObservableCommand RemoveLinkCommand { get; } = new();
+ public ObservableCommand ClearLinksCommand { get; } = new();
+ #endregion
+ public CustomizedSettingPageVM()
+ {
+ SettingWindowVM.Current.PropertyChangedX += Current_PropertyChangedX;
+ AddLinkCommand.ExecuteCommand += AddLinkCommand_ExecuteCommand;
+ RemoveLinkCommand.ExecuteCommand += RemoveLinkCommand_ExecuteCommand;
+ ClearLinksCommand.ExecuteCommand += ClearLinksCommand_ExecuteCommand;
+ }
+
+ private void ClearLinksCommand_ExecuteCommand()
+ {
+ if (
+ MessageBox.Show(
+ "确定清空吗".Translate(),
+ "",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ )
+ is not MessageBoxResult.Yes
+ )
+ return;
+ SearchLink = string.Empty;
+ CustomizedSetting.Links.Clear();
+ }
+
+ private void AddLinkCommand_ExecuteCommand()
+ {
+ SearchLink = string.Empty;
+ CustomizedSetting.Links.Add(new());
+ }
+
+ private void RemoveLinkCommand_ExecuteCommand(LinkModel parameter)
+ {
+ CustomizedSetting.Links.Remove(parameter);
+ }
+
+ private void Current_PropertyChangedX(SettingWindowVM sender, PropertyChangedXEventArgs e)
+ {
+ if (
+ e.PropertyName == nameof(SettingWindowVM.CurrentSetting)
+ && sender.CurrentSetting is not null
+ )
+ {
+ CustomizedSetting = sender.CurrentSetting.CustomizedSetting;
+ SearchLink = string.Empty;
+ }
+ }
+
+ public void RefreshShowLinks(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ ShowLinks = CustomizedSetting.Links;
+ else
+ ShowLinks = CustomizedSetting.Links.Where(
+ s => s.Name.Contains(SearchLink, StringComparison.OrdinalIgnoreCase)
+ );
+ }
+}
diff --git a/VPet.Solution/ViewModels/SettingEditor/DiagnosticSettingPageVM.cs b/VPet.Solution/ViewModels/SettingEditor/DiagnosticSettingPageVM.cs
new file mode 100644
index 0000000..4e92bfa
--- /dev/null
+++ b/VPet.Solution/ViewModels/SettingEditor/DiagnosticSettingPageVM.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VPet.Solution.Models.SettingEditor;
+
+namespace VPet.Solution.ViewModels.SettingEditor;
+
+public class DiagnosticSettingPageVM : ObservableClass
+{
+ private DiagnosticSettingModel _diagnosticSetting;
+ public DiagnosticSettingModel DiagnosticSetting
+ {
+ get => _diagnosticSetting;
+ set => SetProperty(ref _diagnosticSetting, value);
+ }
+
+ public DiagnosticSettingPageVM()
+ {
+ SettingWindowVM.Current.PropertyChangedX += Current_PropertyChangedX;
+ }
+
+ private void Current_PropertyChangedX(SettingWindowVM sender, PropertyChangedXEventArgs e)
+ {
+ if (
+ e.PropertyName == nameof(SettingWindowVM.CurrentSetting)
+ && sender.CurrentSetting is not null
+ )
+ {
+ DiagnosticSetting = sender.CurrentSetting.DiagnosticSetting;
+ }
+ }
+}
diff --git a/VPet.Solution/ViewModels/SettingEditor/GraphicsSettingPageVM.cs b/VPet.Solution/ViewModels/SettingEditor/GraphicsSettingPageVM.cs
new file mode 100644
index 0000000..3c1e95c
--- /dev/null
+++ b/VPet.Solution/ViewModels/SettingEditor/GraphicsSettingPageVM.cs
@@ -0,0 +1,36 @@
+using HKW.HKWUtils.Observable;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VPet.Solution.Models;
+using VPet.Solution.Models.SettingEditor;
+
+namespace VPet.Solution.ViewModels.SettingEditor;
+
+public class GraphicsSettingPageVM : ObservableClass
+{
+ private GraphicsSettingModel _graphicsSetting;
+ public GraphicsSettingModel GraphicsSetting
+ {
+ get => _graphicsSetting;
+ set => SetProperty(ref _graphicsSetting, value);
+ }
+
+ public GraphicsSettingPageVM()
+ {
+ SettingWindowVM.Current.PropertyChangedX += Current_PropertyChangedX;
+ }
+
+ private void Current_PropertyChangedX(SettingWindowVM sender, PropertyChangedXEventArgs e)
+ {
+ if (
+ e.PropertyName == nameof(SettingWindowVM.CurrentSetting)
+ && sender.CurrentSetting is not null
+ )
+ {
+ GraphicsSetting = sender.CurrentSetting.GraphicsSetting;
+ }
+ }
+}
diff --git a/VPet.Solution/ViewModels/SettingEditor/InteractiveSettingPageVM.cs b/VPet.Solution/ViewModels/SettingEditor/InteractiveSettingPageVM.cs
new file mode 100644
index 0000000..2eda9c8
--- /dev/null
+++ b/VPet.Solution/ViewModels/SettingEditor/InteractiveSettingPageVM.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VPet.Solution.Models;
+using VPet.Solution.Models.SettingEditor;
+
+namespace VPet.Solution.ViewModels.SettingEditor;
+
+public class InteractiveSettingPageVM : ObservableClass
+{
+ private InteractiveSettingModel _systemSetting;
+ public InteractiveSettingModel InteractiveSetting
+ {
+ get => _systemSetting;
+ set => SetProperty(ref _systemSetting, value);
+ }
+
+ public InteractiveSettingPageVM()
+ {
+ SettingWindowVM.Current.PropertyChangedX += Current_PropertyChangedX;
+ }
+
+ private void Current_PropertyChangedX(SettingWindowVM sender, PropertyChangedXEventArgs e)
+ {
+ if (
+ e.PropertyName == nameof(SettingWindowVM.CurrentSetting)
+ && sender.CurrentSetting is not null
+ )
+ {
+ InteractiveSetting = sender.CurrentSetting.InteractiveSetting;
+ }
+ }
+}
diff --git a/VPet.Solution/ViewModels/SettingEditor/ModSettingModelModel.cs b/VPet.Solution/ViewModels/SettingEditor/ModSettingModelModel.cs
new file mode 100644
index 0000000..709f687
--- /dev/null
+++ b/VPet.Solution/ViewModels/SettingEditor/ModSettingModelModel.cs
@@ -0,0 +1,5 @@
+namespace VPet.Solution.ViewModels.SettingEditor;
+
+internal class ModSettingModelModel
+{
+}
\ No newline at end of file
diff --git a/VPet.Solution/ViewModels/SettingEditor/ModSettingPageVM.cs b/VPet.Solution/ViewModels/SettingEditor/ModSettingPageVM.cs
new file mode 100644
index 0000000..cf262e1
--- /dev/null
+++ b/VPet.Solution/ViewModels/SettingEditor/ModSettingPageVM.cs
@@ -0,0 +1,171 @@
+using HKW.HKWUtils.Observable;
+using LinePutScript.Localization.WPF;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using VPet.Solution.Models.SettingEditor;
+
+namespace VPet.Solution.ViewModels.SettingEditor;
+
+public class ModSettingPageVM : ObservableClass
+{
+ #region ObservableProperty
+ private ModSettingModel _modSetting;
+ public ModSettingModel ModSetting
+ {
+ get => _modSetting;
+ set => SetProperty(ref _modSetting, value);
+ }
+
+ #region ShowMods
+ private IEnumerable _showMods;
+ public IEnumerable ShowMods
+ {
+ get => _showMods;
+ set => SetProperty(ref _showMods, value);
+ }
+ #endregion
+
+ #region SearchMod
+ private string _searchMod;
+ public string SearchMod
+ {
+ get => _searchMod;
+ set
+ {
+ SetProperty(ref _searchMod, value);
+ RefreshShowMods(value);
+ }
+ }
+ #endregion
+
+ #region CurrentModMoel
+ private ModModel _currentModModel;
+ public ModModel CurrentModMoel
+ {
+ get => _currentModModel;
+ set
+ {
+ if (_currentModModel is not null)
+ _currentModModel.PropertyChangingX -= CurrentModModel_PropertyChangingX;
+ SetProperty(ref _currentModModel, value);
+ if (value is not null)
+ _currentModModel.PropertyChangingX += CurrentModModel_PropertyChangingX;
+ }
+ }
+
+ private void CurrentModModel_PropertyChangingX(ModModel sender, PropertyChangingXEventArgs e)
+ {
+ if (e.PropertyName == nameof(ModModel.IsPass) && e.NewValue is true)
+ {
+ if (
+ MessageBox.Show(
+ "是否启用 {0} 的代码插件?\n一经启用,该插件将会允许访问该系统(包括外部系统)的所有数据\n如果您不确定,请先使用杀毒软件查杀检查".Translate(
+ sender.Name
+ ),
+ "启用 {0} 的代码插件?".Translate(sender.Name),
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ ) is MessageBoxResult.Yes
+ )
+ {
+ sender.IsEnabled = true;
+ }
+ else
+ e.Cancel = true;
+ }
+ }
+ #endregion
+
+
+ #endregion
+
+ #region Command
+ //public ObservableCommand AddModCommand { get; } = new();
+ //public ObservableCommand RemoveModCommand { get; } = new();
+ public ObservableCommand ClearFailModsCommand { get; } = new();
+ public ObservableCommand ClearModsCommand { get; } = new();
+ public ObservableCommand OpenModPathCommand { get; } = new();
+ public ObservableCommand OpenSteamCommunityCommand { get; } = new();
+ #endregion
+
+
+ public ModSettingPageVM()
+ {
+ SettingWindowVM.Current.PropertyChangedX += Current_PropertyChangedX;
+ ClearFailModsCommand.ExecuteCommand += ClearFailModsCommand_ExecuteCommand;
+ ClearModsCommand.ExecuteCommand += ClearModsCommand_ExecuteCommand;
+ OpenModPathCommand.ExecuteCommand += OpenModPathCommand_ExecuteCommand;
+ OpenSteamCommunityCommand.ExecuteCommand += OpenSteamCommunityCommand_ExecuteCommand;
+ }
+
+ private void ClearModsCommand_ExecuteCommand()
+ {
+ if (
+ MessageBox.Show("确定清除全部模组吗", "", MessageBoxButton.YesNo, MessageBoxImage.Warning)
+ is not MessageBoxResult.Yes
+ )
+ return;
+ ModSetting.Mods.Clear();
+ SearchMod = string.Empty;
+ }
+
+ private void ClearFailModsCommand_ExecuteCommand()
+ {
+ if (
+ MessageBox.Show("确定清除全部失效模组吗", "", MessageBoxButton.YesNo, MessageBoxImage.Warning)
+ is not MessageBoxResult.Yes
+ )
+ return;
+ foreach (var mod in ModSetting.Mods.AsEnumerable())
+ {
+ if (mod.IsEnabled is null)
+ ModSetting.Mods.Remove(mod);
+ }
+ SearchMod = string.Empty;
+ }
+
+ private void OpenSteamCommunityCommand_ExecuteCommand(ModModel parameter)
+ {
+ Utils.OpenLink(
+ "https://steamcommunity.com/sharedfiles/filedetails/?id=" + parameter.ItemId
+ );
+ }
+
+ private void OpenModPathCommand_ExecuteCommand(ModModel parameter)
+ {
+ try
+ {
+ Utils.OpenLink(parameter.ModPath);
+ }
+ catch
+ {
+ MessageBox.Show("未在路径\n{0}\n中找到模组".Translate(parameter.ModPath));
+ }
+ }
+
+ private void Current_PropertyChangedX(SettingWindowVM sender, PropertyChangedXEventArgs e)
+ {
+ if (
+ e.PropertyName == nameof(SettingWindowVM.CurrentSetting)
+ && sender.CurrentSetting is not null
+ )
+ {
+ ModSetting = sender.CurrentSetting.ModSetting;
+ SearchMod = string.Empty;
+ }
+ }
+
+ public void RefreshShowMods(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ ShowMods = ModSetting.Mods;
+ else
+ ShowMods = ModSetting.Mods.Where(
+ s => s.Name.Contains(SearchMod, StringComparison.OrdinalIgnoreCase)
+ );
+ }
+}
diff --git a/VPet.Solution/ViewModels/SettingEditor/SettingWindowVM.cs b/VPet.Solution/ViewModels/SettingEditor/SettingWindowVM.cs
new file mode 100644
index 0000000..14265f6
--- /dev/null
+++ b/VPet.Solution/ViewModels/SettingEditor/SettingWindowVM.cs
@@ -0,0 +1,227 @@
+using HKW.HKWUtils.Observable;
+using LinePutScript;
+using LinePutScript.Localization.WPF;
+using Panuon.WPF.UI;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using VPet.Solution.Models;
+using VPet.Solution.Models.SettingEditor;
+using VPet.Solution.Views.SettingEditor;
+using VPet_Simulator.Windows.Interface;
+
+namespace VPet.Solution.ViewModels.SettingEditor;
+
+public class SettingWindowVM : ObservableClass
+{
+ public static SettingWindowVM Current { get; private set; }
+
+ #region Properties
+ private SettingModel _currentSettings;
+ public SettingModel CurrentSetting
+ {
+ get => _currentSettings;
+ set => SetProperty(ref _currentSettings, value);
+ }
+
+ private readonly ObservableCollection _settings = new();
+
+ private IEnumerable _showSettings;
+ public IEnumerable ShowSettings
+ {
+ get => _showSettings;
+ set => SetProperty(ref _showSettings, value);
+ }
+
+ private string _searchSetting;
+ public string SearchSetting
+ {
+ get => _searchSetting;
+ set => SetProperty(ref _searchSetting, value);
+ }
+
+ #endregion
+
+ #region Command
+ ///
+ /// 打开文件
+ ///
+ public ObservableCommand OpenFileCommand { get; } = new();
+
+ ///
+ /// 从资源管理器打开
+ ///
+ public ObservableCommand OpenFileInExplorerCommand { get; } = new();
+
+ ///
+ /// 重置
+ ///
+ public ObservableCommand ResetSettingCommand { get; } = new();
+
+ ///
+ /// 保存
+ ///
+ public ObservableCommand SaveSettingCommand { get; } = new();
+
+ ///
+ /// 保存全部
+ ///
+ public ObservableCommand SaveAllSettingCommand { get; } = new();
+
+ ///
+ /// 重置全部
+ ///
+ public ObservableCommand ResetAllSettingCommand { get; } = new();
+ #endregion
+ public SettingWindowVM()
+ {
+ Current = this;
+ ShowSettings = _settings;
+ LoadSettings();
+
+ PropertyChanged += MainWindowVM_PropertyChanged;
+ OpenFileCommand.ExecuteCommand += OpenFileCommand_ExecuteCommand;
+ OpenFileInExplorerCommand.ExecuteCommand += OpenFileInExplorerCommand_ExecuteCommand;
+ ResetSettingCommand.ExecuteCommand += ResetSettingCommand_ExecuteCommand;
+ SaveSettingCommand.ExecuteCommand += SaveSettingCommand_ExecuteCommand;
+ SaveAllSettingCommand.ExecuteCommand += SaveAllSettingCommand_ExecuteCommand;
+ ResetAllSettingCommand.ExecuteCommand += ResetAllSettingCommand_ExecuteCommand;
+ }
+
+ private void ResetAllSettingCommand_ExecuteCommand()
+ {
+ if (
+ MessageBox.Show(
+ SettingWindow.Instance,
+ "确定全部重置吗".Translate(),
+ "",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ )
+ is not MessageBoxResult.Yes
+ )
+ return;
+ for (var i = 0; i < _settings.Count; i++)
+ _settings[i] = new SettingModel();
+ }
+
+ private void OpenFileInExplorerCommand_ExecuteCommand(SettingModel parameter)
+ {
+ Utils.OpenFileInExplorer(parameter.FilePath);
+ }
+
+ private void OpenFileCommand_ExecuteCommand(SettingModel parameter)
+ {
+ Utils.OpenLink(parameter.FilePath);
+ }
+
+ private void SaveAllSettingCommand_ExecuteCommand()
+ {
+ if (
+ MessageBox.Show(
+ SettingWindow.Instance,
+ "确定全部保存吗".Translate(),
+ "",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ )
+ is not MessageBoxResult.Yes
+ )
+ return;
+ foreach (var setting in _settings)
+ setting.Save();
+ }
+
+ private void SaveSettingCommand_ExecuteCommand(SettingModel parameter)
+ {
+ parameter.Save();
+ }
+
+ private void ResetSettingCommand_ExecuteCommand(SettingModel parameter)
+ {
+ if (
+ MessageBox.Show(
+ SettingWindow.Instance,
+ "确定重置设置吗\n名称: {0}\n路径: {1}".Translate(parameter.Name, parameter.FilePath),
+ "",
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ )
+ is not MessageBoxResult.Yes
+ )
+ return;
+ CurrentSetting = _settings[_settings.IndexOf(CurrentSetting)] = new SettingModel()
+ {
+ Name = CurrentSetting.Name,
+ FilePath = CurrentSetting.FilePath
+ };
+ RefreshShowSettings(SearchSetting);
+ }
+
+ public void RefreshShowSettings(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ ShowSettings = _settings;
+ else
+ ShowSettings = _settings.Where(
+ s => s.Name.Contains(SearchSetting, StringComparison.OrdinalIgnoreCase)
+ );
+ }
+
+ private void MainWindowVM_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(SearchSetting))
+ {
+ RefreshShowSettings(SearchSetting);
+ }
+ }
+
+ private void LoadSettings()
+ {
+ foreach (var file in GetSettingFiles())
+ {
+ var fileName = Path.GetFileNameWithoutExtension(file);
+ try
+ {
+ var setting = new Setting(File.ReadAllText(file));
+ var settingModel = new SettingModel(setting) { Name = fileName, FilePath = file };
+ _settings.Add(settingModel);
+ }
+ catch (Exception ex)
+ {
+ if (
+ MessageBox.Show(
+ "设置载入失败, 是否强制载入并重置\n[是]: 载入并重置\t[否]: 取消载入\n名称: {0}\n路径: {1}\n异常: {2}".Translate(
+ fileName,
+ file,
+ ex.ToString()
+ ),
+ "载入设置出错".Translate(),
+ MessageBoxButton.YesNo,
+ MessageBoxImage.Warning
+ ) is MessageBoxResult.Yes
+ )
+ _settings.Add(new SettingModel() { Name = fileName, FilePath = file });
+ }
+ }
+ }
+
+ private static IEnumerable GetSettingFiles()
+ {
+ return Directory
+ .EnumerateFiles(Environment.CurrentDirectory)
+ .Where(
+ (s) =>
+ {
+ if (s.EndsWith(".lps") is false)
+ return false;
+ return Path.GetFileName(s).StartsWith("Setting");
+ }
+ );
+ }
+}
diff --git a/VPet.Solution/ViewModels/SettingEditor/SystemSettingPageVM.cs b/VPet.Solution/ViewModels/SettingEditor/SystemSettingPageVM.cs
new file mode 100644
index 0000000..9556195
--- /dev/null
+++ b/VPet.Solution/ViewModels/SettingEditor/SystemSettingPageVM.cs
@@ -0,0 +1,39 @@
+using HKW.HKWUtils.Observable;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VPet.Solution.Models;
+using VPet.Solution.Models.SettingEditor;
+
+namespace VPet.Solution.ViewModels.SettingEditor;
+
+public class SystemSettingPageVM : ObservableClass
+{
+ #region SystemSetting
+
+ private SystemSettingModel _systemSetting;
+ public SystemSettingModel SystemSetting
+ {
+ get => _systemSetting;
+ set => SetProperty(ref _systemSetting, value);
+ }
+ #endregion
+
+ public SystemSettingPageVM()
+ {
+ SettingWindowVM.Current.PropertyChangedX += Current_PropertyChangedX;
+ }
+
+ private void Current_PropertyChangedX(SettingWindowVM sender, PropertyChangedXEventArgs e)
+ {
+ if (
+ e.PropertyName == nameof(SettingWindowVM.CurrentSetting)
+ && sender.CurrentSetting is not null
+ )
+ {
+ SystemSetting = sender.CurrentSetting.SystemSetting;
+ }
+ }
+}
diff --git a/VPet.Solution/Views/MainWindow.xaml b/VPet.Solution/Views/MainWindow.xaml
new file mode 100644
index 0000000..1fba1ae
--- /dev/null
+++ b/VPet.Solution/Views/MainWindow.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/MainWindow.xaml.cs b/VPet.Solution/Views/MainWindow.xaml.cs
new file mode 100644
index 0000000..28e700b
--- /dev/null
+++ b/VPet.Solution/Views/MainWindow.xaml.cs
@@ -0,0 +1,51 @@
+using HKW.HKWUtils;
+using LinePutScript.Localization.WPF;
+using Panuon.WPF.UI;
+using System.ComponentModel;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using VPet.Solution.ViewModels;
+using VPet.Solution.Views.SaveViewer;
+using VPet.Solution.Views.SettingEditor;
+
+namespace VPet.Solution.Views;
+
+///
+/// MainWindow.xaml 的交互逻辑
+///
+public partial class MainWindow : WindowX
+{
+ public MainWindowVM ViewModel => (MainWindowVM)DataContext;
+
+ public SettingWindow SettingWindow { get; } = new();
+ public SaveWindow SaveWindow { get; } = new();
+
+ public MainWindow()
+ {
+ if (App.IsDone)
+ {
+ Close();
+ return;
+ }
+ InitializeComponent();
+ this.SetViewModel();
+ Closed += MainWindow_Closed;
+ }
+
+ private void MainWindow_Closed(object sender, EventArgs e)
+ {
+ SettingWindow.CloseX();
+ SaveWindow.CloseX();
+ }
+
+ private void Button_OpenSettingEditor_Click(object sender, RoutedEventArgs e)
+ {
+ SettingWindow.ShowOrActivate();
+ }
+
+ private void Button_OpenSaveViewer_Click(object sender, RoutedEventArgs e)
+ {
+ SaveWindow.ShowOrActivate();
+ }
+}
diff --git a/VPet.Solution/Views/SaveViewer/SaveDataPage.xaml b/VPet.Solution/Views/SaveViewer/SaveDataPage.xaml
new file mode 100644
index 0000000..95fde9e
--- /dev/null
+++ b/VPet.Solution/Views/SaveViewer/SaveDataPage.xaml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/MainWindow.xaml.cs b/VPet.Solution/Views/SaveViewer/SaveDataPage.xaml.cs
similarity index 52%
rename from VPet.Solution/MainWindow.xaml.cs
rename to VPet.Solution/Views/SaveViewer/SaveDataPage.xaml.cs
index f6d127e..386591b 100644
--- a/VPet.Solution/MainWindow.xaml.cs
+++ b/VPet.Solution/Views/SaveViewer/SaveDataPage.xaml.cs
@@ -12,22 +12,20 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
+using VPet.Solution.ViewModels.SaveViewer;
-namespace VPet.Solution
+namespace VPet.Solution.Views.SaveViewer;
+
+///
+/// SaveDataPage.xaml 的交互逻辑
+///
+public partial class SaveDataPage : Page
{
- ///
- /// MainWindow.xaml 的交互逻辑
- ///
- public partial class MainWindow : Window
+ public SaveDataPageVM ViewModel => (SaveDataPageVM)DataContext;
+
+ public SaveDataPage()
{
- public MainWindow()
- {
- if (App.IsDone)
- {
- Close();
- return;
- }
- InitializeComponent();
- }
+ InitializeComponent();
+ this.SetViewModel();
}
}
diff --git a/VPet.Solution/Views/SaveViewer/SaveStatisticPage.xaml b/VPet.Solution/Views/SaveViewer/SaveStatisticPage.xaml
new file mode 100644
index 0000000..a31f8a3
--- /dev/null
+++ b/VPet.Solution/Views/SaveViewer/SaveStatisticPage.xaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SaveViewer/SaveStatisticPage.xaml.cs b/VPet.Solution/Views/SaveViewer/SaveStatisticPage.xaml.cs
new file mode 100644
index 0000000..37ce432
--- /dev/null
+++ b/VPet.Solution/Views/SaveViewer/SaveStatisticPage.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VPet.Solution.ViewModels.SaveViewer;
+
+namespace VPet.Solution.Views.SaveViewer;
+
+///
+/// SaveStatisticPage.xaml 的交互逻辑
+///
+public partial class SaveStatisticPage : Page
+{
+ public SaveStatisticPageVM ViewModel => (SaveStatisticPageVM)DataContext;
+
+ public SaveStatisticPage()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ }
+}
diff --git a/VPet.Solution/Views/SaveViewer/SaveWindow.xaml b/VPet.Solution/Views/SaveViewer/SaveWindow.xaml
new file mode 100644
index 0000000..008b7f5
--- /dev/null
+++ b/VPet.Solution/Views/SaveViewer/SaveWindow.xaml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SaveViewer/SaveWindow.xaml.cs b/VPet.Solution/Views/SaveViewer/SaveWindow.xaml.cs
new file mode 100644
index 0000000..310ee87
--- /dev/null
+++ b/VPet.Solution/Views/SaveViewer/SaveWindow.xaml.cs
@@ -0,0 +1,40 @@
+using HKW.HKWUtils;
+using Panuon.WPF.UI;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using VPet.Solution.ViewModels.SaveViewer;
+using VPet.Solution.ViewModels.SettingEditor;
+
+namespace VPet.Solution.Views.SaveViewer;
+
+///
+/// MainWindow.xaml 的交互逻辑
+///
+public partial class SaveWindow : WindowX
+{
+ public static SaveWindow Instance { get; private set; }
+ public SaveWindowVM ViewModel => (SaveWindowVM)DataContext;
+
+ public SaveWindow()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ this.SetCloseState(WindowCloseState.Hidden);
+
+ ListBoxItem_SaveData.Tag = new SaveDataPage();
+ ListBoxItem_SaveStatistic.Tag = new SaveStatisticPage();
+ ListBox_Pages.SelectedIndex = 0;
+ Instance = this;
+ }
+
+ private void Frame_Main_ContentRendered(object sender, EventArgs e)
+ {
+ if (sender is not Frame frame)
+ return;
+ // 清理过时页面
+ while (frame.CanGoBack)
+ frame.RemoveBackEntry();
+ GC.Collect();
+ }
+}
diff --git a/VPet.Solution/Views/SettingEditor/CustomizedSettingPage.xaml b/VPet.Solution/Views/SettingEditor/CustomizedSettingPage.xaml
new file mode 100644
index 0000000..23c9235
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/CustomizedSettingPage.xaml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SettingEditor/CustomizedSettingPage.xaml.cs b/VPet.Solution/Views/SettingEditor/CustomizedSettingPage.xaml.cs
new file mode 100644
index 0000000..5aedf93
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/CustomizedSettingPage.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VPet.Solution.ViewModels.SettingEditor;
+
+namespace VPet.Solution.Views.SettingEditor;
+
+///
+/// CustomizedSettingsPage.xaml 的交互逻辑
+///
+public partial class CustomizedSettingPage : Page
+{
+ public CustomizedSettingPageVM ViewModel => (CustomizedSettingPageVM)DataContext;
+
+ public CustomizedSettingPage()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ }
+}
diff --git a/VPet.Solution/Views/SettingEditor/DiagnosticSettingPage.xaml b/VPet.Solution/Views/SettingEditor/DiagnosticSettingPage.xaml
new file mode 100644
index 0000000..04755b5
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/DiagnosticSettingPage.xaml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SettingEditor/DiagnosticSettingPage.xaml.cs b/VPet.Solution/Views/SettingEditor/DiagnosticSettingPage.xaml.cs
new file mode 100644
index 0000000..add4b88
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/DiagnosticSettingPage.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VPet.Solution.ViewModels.SettingEditor;
+
+namespace VPet.Solution.Views.SettingEditor;
+
+///
+/// DiagnosticSettingsPage.xaml 的交互逻辑
+///
+public partial class DiagnosticSettingPage : Page
+{
+ public DiagnosticSettingPageVM ViewModel => (DiagnosticSettingPageVM)DataContext;
+
+ public DiagnosticSettingPage()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ }
+}
diff --git a/VPet.Solution/Views/SettingEditor/GraphicsSettingPage.xaml b/VPet.Solution/Views/SettingEditor/GraphicsSettingPage.xaml
new file mode 100644
index 0000000..f249265
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/GraphicsSettingPage.xaml
@@ -0,0 +1,350 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SettingEditor/GraphicsSettingPage.xaml.cs b/VPet.Solution/Views/SettingEditor/GraphicsSettingPage.xaml.cs
new file mode 100644
index 0000000..f00a53e
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/GraphicsSettingPage.xaml.cs
@@ -0,0 +1,40 @@
+using HKW.HKWUtils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VPet.Solution.ViewModels.SettingEditor;
+
+namespace VPet.Solution.Views.SettingEditor;
+
+///
+/// GraphicsSettingsPage.xaml 的交互逻辑
+///
+public partial class GraphicsSettingPage : Page
+{
+ public GraphicsSettingPageVM ViewModel => (GraphicsSettingPageVM)DataContext;
+
+ public GraphicsSettingPage()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ }
+
+ private void Button_StartPoint_Click(object sender, RoutedEventArgs e)
+ {
+ ViewModel.GraphicsSetting.StartRecordPoint = new(
+ SettingWindow.Instance.Left,
+ SettingWindow.Instance.Top
+ );
+ }
+}
diff --git a/VPet.Solution/Views/SettingEditor/InteractiveSettingPage.xaml b/VPet.Solution/Views/SettingEditor/InteractiveSettingPage.xaml
new file mode 100644
index 0000000..6570286
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/InteractiveSettingPage.xaml
@@ -0,0 +1,348 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SettingEditor/InteractiveSettingPage.xaml.cs b/VPet.Solution/Views/SettingEditor/InteractiveSettingPage.xaml.cs
new file mode 100644
index 0000000..1ebffcd
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/InteractiveSettingPage.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VPet.Solution.ViewModels.SettingEditor;
+
+namespace VPet.Solution.Views.SettingEditor;
+
+///
+/// InteractiveSettingsPage.xaml 的交互逻辑
+///
+public partial class InteractiveSettingPage : Page
+{
+ public InteractiveSettingPageVM ViewModel => (InteractiveSettingPageVM)DataContext;
+
+ public InteractiveSettingPage()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ }
+}
diff --git a/VPet.Solution/Views/SettingEditor/ModSettingPage.xaml b/VPet.Solution/Views/SettingEditor/ModSettingPage.xaml
new file mode 100644
index 0000000..9e35474
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/ModSettingPage.xaml
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SettingEditor/ModSettingPage.xaml.cs b/VPet.Solution/Views/SettingEditor/ModSettingPage.xaml.cs
new file mode 100644
index 0000000..3c22d77
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/ModSettingPage.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VPet.Solution.ViewModels.SettingEditor;
+
+namespace VPet.Solution.Views.SettingEditor;
+
+///
+/// ModSettingsPage.xaml 的交互逻辑
+///
+public partial class ModSettingPage : Page
+{
+ public ModSettingPageVM ViewModel => (ModSettingPageVM)DataContext;
+
+ public ModSettingPage()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ }
+}
diff --git a/VPet.Solution/Views/SettingEditor/SettingWindow.xaml b/VPet.Solution/Views/SettingEditor/SettingWindow.xaml
new file mode 100644
index 0000000..d9f5c5a
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/SettingWindow.xaml
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SettingEditor/SettingWindow.xaml.cs b/VPet.Solution/Views/SettingEditor/SettingWindow.xaml.cs
new file mode 100644
index 0000000..58541f6
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/SettingWindow.xaml.cs
@@ -0,0 +1,43 @@
+using HKW.HKWUtils;
+using Panuon.WPF.UI;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using VPet.Solution.ViewModels.SettingEditor;
+
+namespace VPet.Solution.Views.SettingEditor;
+
+///
+/// MainWindow.xaml 的交互逻辑
+///
+public partial class SettingWindow : WindowX
+{
+ public static SettingWindow Instance { get; private set; }
+ public SettingWindowVM ViewModel => (SettingWindowVM)DataContext;
+
+ public SettingWindow()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ this.SetCloseState(WindowCloseState.Hidden);
+
+ ListBoxItem_GraphicsSettings.Tag = new GraphicsSettingPage();
+ ListBoxItem_SystemSettings.Tag = new SystemSettingPage();
+ ListBoxItem_InteractiveSettings.Tag = new InteractiveSettingPage();
+ ListBoxItem_CustomizedSettings.Tag = new CustomizedSettingPage();
+ ListBoxItem_DiagnosticSettings.Tag = new DiagnosticSettingPage();
+ ListBoxItem_ModSettings.Tag = new ModSettingPage();
+ ListBox_Pages.SelectedIndex = 0;
+ Instance = this;
+ }
+
+ private void Frame_Main_ContentRendered(object sender, EventArgs e)
+ {
+ if (sender is not Frame frame)
+ return;
+ // 清理过时页面
+ while (frame.CanGoBack)
+ frame.RemoveBackEntry();
+ GC.Collect();
+ }
+}
diff --git a/VPet.Solution/Views/SettingEditor/SystemSettingPage.xaml b/VPet.Solution/Views/SettingEditor/SystemSettingPage.xaml
new file mode 100644
index 0000000..b27e0ce
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/SystemSettingPage.xaml
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/SettingEditor/SystemSettingPage.xaml.cs b/VPet.Solution/Views/SettingEditor/SystemSettingPage.xaml.cs
new file mode 100644
index 0000000..ad65367
--- /dev/null
+++ b/VPet.Solution/Views/SettingEditor/SystemSettingPage.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VPet.Solution.ViewModels.SettingEditor;
+
+namespace VPet.Solution.Views.SettingEditor;
+
+///
+/// SystemSettingsPage.xaml 的交互逻辑
+///
+public partial class SystemSettingPage : Page
+{
+ public SystemSettingPageVM ViewModel => (SystemSettingPageVM)DataContext;
+
+ public SystemSettingPage()
+ {
+ InitializeComponent();
+ this.SetViewModel();
+ }
+}
diff --git a/VPet.Solution/icon.ico b/VPet.Solution/icon.ico
new file mode 100644
index 0000000..d071638
Binary files /dev/null and b/VPet.Solution/icon.ico differ
diff --git a/VPet.Solution/packages.config b/VPet.Solution/packages.config
index 55a0c70..1ed94dc 100644
--- a/VPet.Solution/packages.config
+++ b/VPet.Solution/packages.config
@@ -1,7 +1,8 @@
-
+
+
-
-
+
+
\ No newline at end of file
diff --git a/vpeticon.ase b/vpeticon.ase
index 46eb52b..23411b6 100644
Binary files a/vpeticon.ase and b/vpeticon.ase differ