diff --git a/build.gradle b/build.gradle
index 8695593d..460f10ef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -72,5 +72,9 @@ project(":core") {
api "com.alibaba:fastjson:2.0.39"
// Log4j
api "apache-log4j:log4j:1.2.15"
+ // TiniPinyin
+ api 'com.github.promeg:tinypinyin:2.0.3'
+ // OpenCC4j
+ api 'com.github.houbb:opencc4j:1.8.1'
}
}
diff --git a/core/src/cn/harryh/arkpets/ArkChar.java b/core/src/cn/harryh/arkpets/ArkChar.java
index 97919d37..3fad7a5a 100644
--- a/core/src/cn/harryh/arkpets/ArkChar.java
+++ b/core/src/cn/harryh/arkpets/ArkChar.java
@@ -8,7 +8,7 @@
import cn.harryh.arkpets.animations.AnimClipGroup;
import cn.harryh.arkpets.animations.AnimComposer;
import cn.harryh.arkpets.animations.AnimData;
-import cn.harryh.arkpets.assets.AssetItem.AssetAccessor;
+import cn.harryh.arkpets.assets.ModelItem.ModelAssetAccessor;
import cn.harryh.arkpets.transitions.EasingFunction;
import cn.harryh.arkpets.transitions.TransitionFloat;
import cn.harryh.arkpets.transitions.TransitionVector3;
@@ -87,9 +87,9 @@ public ArkChar(ArkConfig config, float scale) {
SkeletonData skeletonData;
try {
String assetLocation = config.character_asset;
- AssetAccessor assetAccessor = new AssetAccessor(config.character_files);
- String path2atlas = assetLocation + separator + assetAccessor.getFirstFileOf(".atlas");
- String path2skel = assetLocation + separator + assetAccessor.getFirstFileOf(".skel");
+ ModelAssetAccessor modelAssetAccessor = new ModelAssetAccessor(config.character_files);
+ String path2atlas = assetLocation + separator + modelAssetAccessor.getFirstFileOf(".atlas");
+ String path2skel = assetLocation + separator + modelAssetAccessor.getFirstFileOf(".skel");
// Load atlas
TextureAtlas atlas = new TextureAtlas(Gdx.files.internal(path2atlas));
// Load skel (use SkeletonJson instead of SkeletonBinary if the file type is JSON)
diff --git a/core/src/cn/harryh/arkpets/assets/AssetItemGroup.java b/core/src/cn/harryh/arkpets/assets/AssetItemGroup.java
deleted file mode 100644
index 4fa07d5d..00000000
--- a/core/src/cn/harryh/arkpets/assets/AssetItemGroup.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/** Copyright (c) 2022-2025, Harry Huang
- * At GPL-3.0 License
- */
-package cn.harryh.arkpets.assets;
-
-import cn.harryh.arkpets.assets.AssetItem.PropertyExtractor;
-
-import java.util.*;
-import java.util.function.Predicate;
-
-
-/** The class implements the Collection of {@link AssetItem}.
- *
- * The structure of the root directory may be like what is shown below.
- * Each {@code SubDir} represents an {@code AssetItem}.
- *
- *
- * +-RootDir
- * |-+-SubDir1 (whose name is the model name of xxx)
- * | |---xxx.atlas
- * | |---xxx.png
- * | |---xxx.skel
- * |---SubDir2
- * |---SubDir3
- * |---...
- *
- *
- * @since ArkPets 2.4
- */
-public class AssetItemGroup implements Collection {
- protected final ArrayList assetItemList;
-
- public AssetItemGroup(Collection assetItemList) {
- this.assetItemList = new ArrayList<>(assetItemList);
- }
-
- public AssetItemGroup() {
- this(new ArrayList<>());
- }
-
- /** Searches the Asset Items whose {@code name} and {@code appellation} match the given keywords.
- * @param keyWords The given keywords. Each keyword should be separated by a blank.
- * @return An Asset Item Group. Returns {@code this} if the parameter {@code keyWords} is {@code null} or empty.
- */
- public AssetItemGroup searchByKeyWords(String keyWords) {
- if (keyWords == null || keyWords.isEmpty())
- return this;
- String[] wordList = keyWords.split(" ");
- AssetItemGroup result = new AssetItemGroup();
- for (AssetItem asset : this) {
- for (String word : wordList) {
- if (asset.name != null &&
- asset.name.toLowerCase().contains(word.toLowerCase())) {
- result.add(asset);
- }
- }
- for (String word : wordList) {
- if (asset.appellation != null &&
- asset.appellation.toLowerCase().contains(word.toLowerCase())) {
- if (!result.contains(asset))
- result.add(asset);
- }
- }
- }
- return result;
- }
-
- /** Searches the Asset Item whose relative path provided by {@code getLocation} matches the given path string.
- * @param relPath The given path string.
- * @return The first matched Asset Item. Returns {@code null} if no one matched.
- */
- public AssetItem searchByRelPath(String relPath) {
- if (relPath == null || relPath.isEmpty())
- return null;
- for (AssetItem asset : this)
- if (asset.getLocation().equalsIgnoreCase(relPath))
- return asset;
- return null;
- }
-
- /** Collects the values of a specified property of the Asset Items.
- * @param property A property extractor.
- * @return A Set that contains all the possible values of the property.
- * @param The type of the property value.
- */
- public Set extract(PropertyExtractor property) {
- HashSet result = new HashSet<>();
- for (AssetItem item : this)
- result.addAll(property.apply(item));
- return result;
- }
-
- /** Returns a new Asset Item Group consisting of the Asset Items that match the given predicate.
- * @param predicate A predicate to apply to each Asset Item to determine if it should be included.
- * @return An Asset Item Group.
- */
- public AssetItemGroup filter(Predicate predicate) {
- return new AssetItemGroup(assetItemList.stream().filter(predicate).toList());
- }
-
- /** Returns a new Asset Item Group consisting of the Asset Items whose property satisfied the requirements.
- * @param property A property extractor.
- * @param filterValues The property values to be matched.
- * @param mode The {@link AssetItemGroup.FilterMode}.
- * @return An Asset Item Group.
- * @param The type of the property value.
- */
- public AssetItemGroup filter(PropertyExtractor property, Set filterValues, int mode) {
- final boolean TRUE = (mode & FilterMode.MATCH_REVERSE) == 0;
- return filter(assetItem -> {
- Set itemValues = property.apply(assetItem);
- if ((mode & FilterMode.MATCH_ANY) != 0) {
- for (T value : itemValues)
- if (filterValues.contains(value))
- return TRUE;
- } else {
- if (itemValues.containsAll(filterValues))
- return TRUE;
- }
- return !TRUE;
- });
- }
-
- /** Returns a new Asset Item Group consisting of the Asset Items whose property satisfied the requirements.
- * @param property A property extractor.
- * @param filterValues The property values to be matched.
- * @return An Asset Item Group.
- * @param The type of the property value.
- */
- public AssetItemGroup filter(PropertyExtractor property, Set filterValues) {
- return filter(property, filterValues, 0);
- }
-
- /** Sorts the Asset Items by their {@code assetDir} in natural order.
- */
- public void sort() {
- assetItemList.sort(Comparator.comparing(asset -> asset.assetDir, Comparator.naturalOrder()));
- }
-
-
- public static class FilterMode {
- public static final int MATCH_ANY = 0b1;
- public static final int MATCH_REVERSE = 0b10;
- }
-
- @Override
- public Iterator iterator() {
- return assetItemList.iterator();
- }
-
- @Override
- public boolean add(AssetItem assetItem) {
- return !assetItemList.contains(assetItem) && assetItemList.add(assetItem);
- }
-
- @Override
- public boolean addAll(Collection extends AssetItem> c) {
- int size = size();
- c.forEach(this::add);
- return size != size();
- }
-
- @Override
- public boolean contains(Object o) {
- return assetItemList.contains(o);
- }
-
- @Override
- public boolean containsAll(Collection> c) {
- return assetItemList.containsAll(c);
- }
-
- @Override
- public boolean remove(Object o) {
- return assetItemList.remove(o);
- }
-
- @Override
- public boolean removeAll(Collection> c) {
- return assetItemList.removeAll(c);
- }
-
- @Override
- public boolean retainAll(Collection> c) {
- return assetItemList.retainAll(c);
- }
-
- @Override
- public void clear() {
- assetItemList.clear();
- }
-
- @Override
- public boolean isEmpty() {
- return assetItemList.isEmpty();
- }
-
- @Override
- public int size() {
- return assetItemList.size();
- }
-
- @Override
- public Object[] toArray() {
- return assetItemList.toArray();
- }
-
- @Override
- public T[] toArray(T[] a) {
- return assetItemList.toArray(a);
- }
-}
diff --git a/core/src/cn/harryh/arkpets/assets/AssetItem.java b/core/src/cn/harryh/arkpets/assets/ModelItem.java
similarity index 77%
rename from core/src/cn/harryh/arkpets/assets/AssetItem.java
rename to core/src/cn/harryh/arkpets/assets/ModelItem.java
index 0c50a06e..9707a8d7 100644
--- a/core/src/cn/harryh/arkpets/assets/AssetItem.java
+++ b/core/src/cn/harryh/arkpets/assets/ModelItem.java
@@ -7,6 +7,7 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
+import com.github.promeg.pinyinhelper.Pinyin;
import java.io.File;
import java.io.Serializable;
@@ -14,9 +15,9 @@
import java.util.function.Function;
-/** One Asset Item is corresponding to one certain local Spine asset.
+/** One Model Item is corresponding to one certain local Spine model.
*/
-public class AssetItem implements Serializable {
+public class ModelItem implements Serializable {
@JSONField(serialize = false)
public String key;
@JSONField(serialize = false)
@@ -42,11 +43,14 @@ public class AssetItem implements Serializable {
/** @deprecated Legacy field in old version dataset */ @JSONField @Deprecated
public JSONObject checksum;
- private AssetAccessor accessor;
+ // Lazy generated fields:
+ private ModelAssetAccessor accessor;
+ private String pinyinQuanpin;
+ private String pinyinSuoxie;
protected static final String[] extensions = {".atlas", ".png", ".skel"};
- private AssetItem() {
+ private ModelItem() {
}
/** Gets the directory where the asset files located in.
@@ -57,20 +61,50 @@ public String getLocation() {
return assetDir.toString();
}
- /** Gets the Asset Accessor of this asset.
- * @return An Asset Accessor instance.
+ /** Gets the Model Asset Accessor of the model's asset files.
+ * @return A Model Asset Accessor instance.
*/
@JSONField(serialize = false)
- public AssetAccessor getAccessor() {
+ public ModelAssetAccessor getAccessor() {
if (accessor == null)
- accessor = new AssetAccessor(assetList);
+ accessor = new ModelAssetAccessor(assetList);
return accessor;
}
- /** Verifies the integrity of the necessary fields of this {@code AssetItem}.
+ /** Gets the 拼音全拼 (Pinyin Quanpin, full Pinyin transcription) of the model's name.
+ * @return A String.
+ */
+ @JSONField(serialize = false)
+ public String getPinyinQuanpin() {
+ if (pinyinQuanpin == null)
+ pinyinQuanpin = Pinyin.toPinyin(name, "");
+ return pinyinQuanpin;
+ }
+
+ /** Gets the 拼音缩写 (Pinyin Suoxie, abbreviate Pinyin transcription) of the model's name.
+ * @return A String.
+ */
+ @JSONField(serialize = false)
+ public String getPinyinSuoxie() {
+ if (pinyinSuoxie == null) {
+ String quanpin = Pinyin.toPinyin(name, " ").trim();
+ if (!quanpin.isEmpty()) {
+ StringBuilder builder = new StringBuilder();
+ for (String word : quanpin.split("\\s+")) {
+ builder.append(word.charAt(0));
+ }
+ pinyinSuoxie = builder.toString();
+ } else {
+ pinyinSuoxie = "";
+ }
+ }
+ return pinyinSuoxie;
+ }
+
+ /** Verifies the integrity of the necessary fields of this {@code ModelItem}.
* @return {@code true} if all the following conditions are satisfied, otherwise {@code false}:
* 1. Both {@code assetDir} and {@code type} are not {@code null}.
- * 2. The {@code AssetAccessor} is available.
+ * 2. The {@code ModelAssetAccessor} is available.
*/
@JSONField(serialize = false)
public boolean isValid() {
@@ -127,21 +161,21 @@ public int hashCode() {
@Override
public boolean equals(Object obj) {
- if (obj instanceof AssetItem) {
- return ((AssetItem)obj).assetDir.equals(assetDir);
+ if (obj instanceof ModelItem) {
+ return ((ModelItem)obj).assetDir.equals(assetDir);
}
return false;
}
- /** The Asset Accessor providing methods to get the resource files of the asset.
+ /** The Model Asset Accessor providing methods to get the resource files of the model's asset files.
* @since ArkPets 2.2
*/
- public static class AssetAccessor {
+ public static class ModelAssetAccessor {
private final ArrayList list;
private final HashMap> map;
- public AssetAccessor(JSONObject fileMap) {
+ public ModelAssetAccessor(JSONObject fileMap) {
ArrayList list = new ArrayList<>();
HashMap> map = new HashMap<>();
try {
@@ -197,17 +231,17 @@ public boolean isAvailable() {
}
- /** The Asset Property Extractor specializing in extracting a specified property.
+ /** The Model Property Extractor specializing in extracting a specified property.
* @param The type of the specified property, typically {@code String}.
* @since ArkPets 2.2
*/
- public interface PropertyExtractor extends Function> {
- /** Extracts the specified property of the given Asset Item.
- * @param assetItem The given Asset Item.
+ public interface PropertyExtractor extends Function> {
+ /** Extracts the specified property of the given Model Item.
+ * @param modelItem The given Model Item.
* @return A value {@link Set} of the extracted property.
*/
@Override
- Set apply(AssetItem assetItem);
+ Set apply(ModelItem modelItem);
PropertyExtractor ASSET_ITEM_KEY = item -> item.key == null ? Set.of() : Set.of(item.key);
PropertyExtractor ASSET_ITEM_TYPE = item -> item.type == null ? Set.of() : Set.of(item.type);
@@ -216,7 +250,8 @@ public interface PropertyExtractor extends Function> {
PropertyExtractor ASSET_ITEM_SORT_TAGS = item -> new HashSet<>(item.sortTags.toJavaList(String.class));
}
- /** The Asset Prefab storing the user prefab of the specific asset.
+
+ /** The Model Prefab storing the user prefab of the specific model.
* @since ArkPets 3.5
*/
public static class AssetPrefab {
diff --git a/core/src/cn/harryh/arkpets/assets/ModelItemGroup.java b/core/src/cn/harryh/arkpets/assets/ModelItemGroup.java
new file mode 100644
index 00000000..250e7eeb
--- /dev/null
+++ b/core/src/cn/harryh/arkpets/assets/ModelItemGroup.java
@@ -0,0 +1,284 @@
+/** Copyright (c) 2022-2025, Harry Huang
+ * At GPL-3.0 License
+ */
+package cn.harryh.arkpets.assets;
+
+import cn.harryh.arkpets.assets.ModelItem.PropertyExtractor;
+import com.github.houbb.opencc4j.util.ZhConverterUtil;
+
+import java.util.*;
+import java.util.function.Predicate;
+
+
+/** The class implements the Collection of {@link ModelItem}.
+ *
+ * The structure of the root directory may be like what is shown below.
+ * Each {@code SubDir} represents an {@code ModelItem}.
+ *
+ *
+ * +-RootDir
+ * |-+-SubDir1 (whose name is the model name of xxx)
+ * | |---xxx.atlas
+ * | |---xxx.png
+ * | |---xxx.skel
+ * |---SubDir2
+ * |---SubDir3
+ * |---...
+ *
+ *
+ * @since ArkPets 2.4
+ */
+public class ModelItemGroup implements Collection {
+ protected final ArrayList modelItemList;
+
+ public ModelItemGroup(Collection modelItemList) {
+ this.modelItemList = new ArrayList<>(modelItemList);
+ }
+
+ public ModelItemGroup() {
+ this(new ArrayList<>());
+ }
+
+ /** Searches the Model Items whose {@code name} and {@code appellation} match the given keywords.
+ * @param keyWords The given keywords. Each keyword should be separated by a blank.
+ * @return A Model Item Group. Returns {@code this} if the parameter {@code keyWords} is {@code null} or empty.
+ */
+ public ModelItemGroup searchByKeyWords(String keyWords) {
+ if (keyWords == null || keyWords.isEmpty())
+ return this;
+ // Word list: uppercase and deduplicate
+ String[] wordList = deduplicateArray(keyWords.toUpperCase().split(" "));
+ // Word list: extend with zh-Hans and zh-Hant conversions
+ String[] wordListST = deduplicateArray(concatArrays(
+ wordList,
+ ZhConverterUtil.toSimple(keyWords).toUpperCase().split(" "),
+ ZhConverterUtil.toTraditional(keyWords).toUpperCase().split(" ")
+ ));
+ ModelItemGroup result = new ModelItemGroup();
+
+ // Rule: match name
+ for (ModelItem model : this) {
+ if (!result.contains(model) && model.name != null) {
+ String upper = model.name.toUpperCase();
+ for (String word : wordListST) {
+ if (upper.contains(word)) {
+ result.add(model);
+ break;
+ }
+ }
+ }
+ }
+
+ // Rule: match appellation
+ for (ModelItem model : this) {
+ if (!result.contains(model) && model.appellation != null) {
+ String upper = model.appellation.toUpperCase();
+ for (String word : wordList) {
+ if (upper.contains(word)) {
+ result.add(model);
+ break;
+ }
+ }
+ }
+ }
+
+ // Rule: match Pinyin suoxie
+ for (ModelItem model : this) {
+ if (!result.contains(model) && model.getPinyinSuoxie() != null) {
+ String upper = model.getPinyinSuoxie().toUpperCase();
+ for (String word : wordList) {
+ if (upper.contains(word)) {
+ result.add(model);
+ break;
+ }
+ }
+ }
+ }
+
+ // Rule: match Pinyin quanpin
+ for (ModelItem model : this) {
+ if (!result.contains(model) && model.getPinyinQuanpin() != null) {
+ String upper = model.getPinyinQuanpin().toUpperCase();
+ for (String word : wordList) {
+ if (upper.contains(word)) {
+ result.add(model);
+ break;
+ }
+ }
+ }
+ }
+
+ // Rule: match skin group name
+ for (ModelItem model : this) {
+ if (!result.contains(model) && model.skinGroupName != null) {
+ String upper = model.skinGroupName.toUpperCase();
+ for (String word : wordListST) {
+ if (upper.contains(word)) {
+ result.add(model);
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /** Searches the Model Item whose relative path provided by {@code getLocation} matches the given path string.
+ * @param relPath The given path string.
+ * @return The first matched Model Item. Returns {@code null} if no one matched.
+ */
+ public ModelItem searchByRelPath(String relPath) {
+ if (relPath == null || relPath.isEmpty())
+ return null;
+ for (ModelItem model : this)
+ if (model.getLocation().equalsIgnoreCase(relPath))
+ return model;
+ return null;
+ }
+
+ /** Collects the values of a specified property of the Model Items.
+ * @param property A property extractor.
+ * @return A Set that contains all the possible values of the property.
+ * @param The type of the property value.
+ */
+ public Set extract(PropertyExtractor property) {
+ HashSet result = new HashSet<>();
+ for (ModelItem item : this)
+ result.addAll(property.apply(item));
+ return result;
+ }
+
+ /** Returns a new Model Item Group consisting of the Model Items that match the given predicate.
+ * @param predicate A predicate to apply to each Model Item to determine if it should be included.
+ * @return A Model Item Group.
+ */
+ public ModelItemGroup filter(Predicate predicate) {
+ return new ModelItemGroup(modelItemList.stream().filter(predicate).toList());
+ }
+
+ /** Returns a new Model Item Group consisting of the Model Items whose property satisfied the requirements.
+ * @param property A property extractor.
+ * @param filterValues The property values to be matched.
+ * @param mode The {@link ModelItemGroup.FilterMode}.
+ * @return A Model Item Group.
+ * @param The type of the property value.
+ */
+ public ModelItemGroup filter(PropertyExtractor property, Set filterValues, int mode) {
+ final boolean TRUE = (mode & FilterMode.MATCH_REVERSE) == 0;
+ return filter(item -> {
+ Set itemValues = property.apply(item);
+ if ((mode & FilterMode.MATCH_ANY) != 0) {
+ for (T value : itemValues)
+ if (filterValues.contains(value))
+ return TRUE;
+ } else {
+ if (itemValues.containsAll(filterValues))
+ return TRUE;
+ }
+ return !TRUE;
+ });
+ }
+
+ /** Returns a new Model Item Group consisting of the Model Items whose property satisfied the requirements.
+ * @param property A property extractor.
+ * @param filterValues The property values to be matched.
+ * @return A Model Item Group.
+ * @param The type of the property value.
+ */
+ public ModelItemGroup filter(PropertyExtractor property, Set filterValues) {
+ return filter(property, filterValues, 0);
+ }
+
+ /** Sorts the Model Items by their {@code assetDir} in natural order.
+ */
+ public void sort() {
+ modelItemList.sort(Comparator.comparing(model -> model.assetDir, Comparator.naturalOrder()));
+ }
+
+ protected static String[] concatArrays(String[] array1, String[] array2, String[] array3) {
+ String[] result = new String[array1.length + array2.length + array3.length];
+ System.arraycopy(array1, 0, result, 0, array1.length);
+ System.arraycopy(array2, 0, result, array1.length, array2.length);
+ System.arraycopy(array3, 0, result, array1.length + array2.length, array3.length);
+ return result;
+ }
+
+ protected static String[] deduplicateArray(String[] array) {
+ return Arrays.stream(array).distinct().toArray(String[]::new);
+ }
+
+
+ public static class FilterMode {
+ public static final int MATCH_ANY = 0b1;
+ public static final int MATCH_REVERSE = 0b10;
+ }
+
+
+ @Override
+ public Iterator iterator() {
+ return modelItemList.iterator();
+ }
+
+ @Override
+ public boolean add(ModelItem modelItem) {
+ return !modelItemList.contains(modelItem) && modelItemList.add(modelItem);
+ }
+
+ @Override
+ public boolean addAll(Collection extends ModelItem> c) {
+ int size = size();
+ c.forEach(this::add);
+ return size != size();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return modelItemList.contains(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ return modelItemList.containsAll(c);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return modelItemList.remove(o);
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ return modelItemList.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ return modelItemList.retainAll(c);
+ }
+
+ @Override
+ public void clear() {
+ modelItemList.clear();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return modelItemList.isEmpty();
+ }
+
+ @Override
+ public int size() {
+ return modelItemList.size();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return modelItemList.toArray();
+ }
+
+ @Override
+ public T[] toArray(T[] a) {
+ return modelItemList.toArray(a);
+ }
+}
diff --git a/core/src/cn/harryh/arkpets/assets/ModelsDataset.java b/core/src/cn/harryh/arkpets/assets/ModelsDataset.java
index 288fdd05..1840395c 100644
--- a/core/src/cn/harryh/arkpets/assets/ModelsDataset.java
+++ b/core/src/cn/harryh/arkpets/assets/ModelsDataset.java
@@ -19,7 +19,7 @@ public class ModelsDataset {
public final HashMap sortTags;
public final String gameDataVersionDescription;
public final String gameDataServerRegion;
- public final AssetItemGroup data;
+ public final ModelItemGroup data;
public final Version arkPetsCompatibility;
public ModelsDataset(JSONObject jsonObject) {
@@ -41,23 +41,23 @@ protected ModelsDataset(ModelsDatasetBean bean) {
if (bean.data == null || bean.data.isEmpty())
throw new DatasetKeyException("data");
- data = new AssetItemGroup();
+ data = new ModelItemGroup();
for (String key : bean.data.keySet()) {
// Pre deserialization
- AssetItem assetItem = bean.data.get(key).toJavaObject(AssetItem.class);
+ ModelItem modelItem = bean.data.get(key).toJavaObject(ModelItem.class);
// Make up for `assetDir` field
- if (assetItem == null || !storageDirectory.containsKey(assetItem.type))
+ if (modelItem == null || !storageDirectory.containsKey(modelItem.type))
throw new DatasetKeyException("type");
- assetItem.key = key;
- assetItem.assetDir = Path.of(storageDirectory.get(assetItem.type).toString(), key).toFile();
+ modelItem.key = key;
+ modelItem.assetDir = Path.of(storageDirectory.get(modelItem.type).toString(), key).toFile();
// Compatible to lower version dataset
- if (assetItem.assetList == null && assetItem.assetId != null && assetItem.checksum != null) {
+ if (modelItem.assetList == null && modelItem.assetId != null && modelItem.checksum != null) {
HashMap defaultFileMap = new HashMap<>();
- for (String fileType : AssetItem.extensions)
- defaultFileMap.put(fileType, assetItem.assetId + fileType);
- assetItem.assetList = new JSONObject(defaultFileMap);
+ for (String fileType : ModelItem.extensions)
+ defaultFileMap.put(fileType, modelItem.assetId + fileType);
+ modelItem.assetList = new JSONObject(defaultFileMap);
}
- data.add(assetItem);
+ data.add(modelItem);
}
data.sort();
diff --git a/desktop/src/cn/harryh/arkpets/controllers/ModelsModule.java b/desktop/src/cn/harryh/arkpets/controllers/ModelsModule.java
index 21310f87..793d7175 100644
--- a/desktop/src/cn/harryh/arkpets/controllers/ModelsModule.java
+++ b/desktop/src/cn/harryh/arkpets/controllers/ModelsModule.java
@@ -4,8 +4,8 @@
package cn.harryh.arkpets.controllers;
import cn.harryh.arkpets.ArkHomeFX;
-import cn.harryh.arkpets.assets.AssetItem;
-import cn.harryh.arkpets.assets.AssetItemGroup;
+import cn.harryh.arkpets.assets.ModelItem;
+import cn.harryh.arkpets.assets.ModelItemGroup;
import cn.harryh.arkpets.assets.ModelsDataset;
import cn.harryh.arkpets.guitasks.*;
import cn.harryh.arkpets.utils.*;
@@ -60,7 +60,7 @@ public final class ModelsModule implements Controller {
@FXML
private Label searchModelStatus;
@FXML
- private JFXListView> searchModelView;
+ private JFXListView> searchModelView;
@FXML
private Label selectedModelName;
@FXML
@@ -108,9 +108,9 @@ public final class ModelsModule implements Controller {
@FXML
private Label modelHelp;
- private AssetItemGroup assetItemList;
- private JFXListCell selectedModelCell;
- private ArrayList> modelCellList = new ArrayList<>();
+ private ModelItemGroup assetItemList;
+ private JFXListCell selectedModelCell;
+ private ArrayList> modelCellList = new ArrayList<>();
private ObservableSet filterTagSet = FXCollections.observableSet();
private GuiPrefabs.PeerNodeComposer infoPaneComposer;
@@ -158,7 +158,7 @@ public boolean initModelsDataset(boolean doPopNotice) {
IOUtils.FileUtil.readString(new File(PathConfig.fileModelsDataPath), charsetDefault)
)
);
- app.modelsDataset.data.removeIf(Predicate.not(AssetItem::isValid));
+ app.modelsDataset.data.removeIf(Predicate.not(ModelItem::isValid));
try {
// Check the dataset compatibility
Version compatibleVersion = app.modelsDataset.arkPetsCompatibility;
@@ -415,7 +415,7 @@ private void initModelFavorite() {
modelFavorite.setGraphic(favIcon);
Logger.debug("ModelManager", "Remove favorite model " + key);
} else {
- app.config.character_favorites.put(key, new AssetItem.AssetPrefab());
+ app.config.character_favorites.put(key, new ModelItem.AssetPrefab());
selectedModelCell.getStyleClass().add("Search-models-item-favorite");
modelFavorite.setGraphic(favFillIcon);
Logger.debug("ModelManager", "Add favorite model " + key);
@@ -433,9 +433,9 @@ private void initModelFavorite() {
}
filterFavorite = !filterFavorite;
modelSearch(searchModelInput.getText());
- AssetItem recentSelected = assetItemList.searchByRelPath(app.config.character_asset);
+ ModelItem recentSelected = assetItemList.searchByRelPath(app.config.character_asset);
if (recentSelected != null)
- for (JFXListCell cell : searchModelView.getItems())
+ for (JFXListCell cell : searchModelView.getItems())
if (recentSelected.equals(cell.getItem())) {
searchModelView.scrollTo(cell);
searchModelView.getSelectionModel().select(cell);
@@ -447,21 +447,25 @@ public void modelSearch(String keyWords) {
searchModelView.getItems().clear();
searchModelStatus.setText("");
if (assertModelLoaded(false)) {
- // Filter and search assets
+ // Filter assets
int rawSize = assetItemList.size();
- AssetItemGroup favoured = !filterFavorite ? assetItemList :
- assetItemList.filter(AssetItem.PropertyExtractor.ASSET_ITEM_KEY, app.config.character_favorites.keySet(), AssetItemGroup.FilterMode.MATCH_ANY);
- AssetItemGroup filtered = filterTagSet.isEmpty() ? favoured :
- favoured.filter(AssetItem.PropertyExtractor.ASSET_ITEM_SORT_TAGS, filterTagSet);
- AssetItemGroup searched = filtered.searchByKeyWords(keyWords);
+ ModelItemGroup favoured = !filterFavorite ? assetItemList :
+ assetItemList.filter(ModelItem.PropertyExtractor.ASSET_ITEM_KEY, app.config.character_favorites.keySet(), ModelItemGroup.FilterMode.MATCH_ANY);
+ ModelItemGroup filtered = filterTagSet.isEmpty() ? favoured :
+ favoured.filter(ModelItem.PropertyExtractor.ASSET_ITEM_SORT_TAGS, filterTagSet);
+ // Search assets
+ long tStart = System.nanoTime();
+ ModelItemGroup searched = filtered.searchByKeyWords(keyWords);
+ long tEnd = System.nanoTime();
int curSize = searched.size();
searchModelStatus.setText((rawSize == curSize ? rawSize : curSize + " / " + rawSize) + " 个模型");
// Add cells
- for (JFXListCell cell : modelCellList)
+ for (JFXListCell cell : modelCellList)
if (searched.contains(cell.getItem()))
searchModelView.getItems().add(cell);
+ Logger.info("ModelManager", "Search \"%s\" (%d results, %.1f ms)"
+ .formatted(keyWords, curSize, (tEnd - tStart) / 1000000f));
}
- Logger.info("ModelManager", "Search \"" + keyWords + "\" (" + searchModelView.getItems().size() + ")");
searchModelView.refresh();
}
@@ -479,19 +483,19 @@ public void modelReload(boolean doPopNotice) {
Logger.info("ModelManager", "Reloading");
boolean willGc = modelCellList != null;
modelCellList = new ArrayList<>();
- assetItemList = new AssetItemGroup();
+ assetItemList = new ModelItemGroup();
if (initModelsDataset(doPopNotice)) {
// 1. Update list cells and asset items:
try {
// Find every model assets.
- assetItemList.addAll(app.modelsDataset.data.filter(AssetItem::isExisted));
+ assetItemList.addAll(app.modelsDataset.data.filter(ModelItem::isExisted));
if (assetItemList.isEmpty())
throw new IOException("Found no assets in the target directories.");
// Initialize list view.
searchModelView.getSelectionModel().getSelectedItems().addListener(
- (ListChangeListener>) (observable -> observable.getList().forEach(
- (Consumer>) cell -> selectModel(cell.getItem(), cell))
+ (ListChangeListener>) (observable -> observable.getList().forEach(
+ (Consumer>) cell -> selectModel(cell.getItem(), cell))
)
);
searchModelView.setFixedCellSize(30);
@@ -502,7 +506,7 @@ public void modelReload(boolean doPopNotice) {
// Explicitly set all lists to empty.
Logger.error("ModelManager", "Failed to initialize model assets due to unknown reasons, details see below.", ex);
modelCellList = new ArrayList<>();
- assetItemList = new AssetItemGroup();
+ assetItemList = new ModelItemGroup();
if (doPopNotice)
GuiPrefabs.Dialogs.createCommonDialog(app.body,
GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_WARNING_ALT, GuiPrefabs.COLOR_WARNING),
@@ -529,7 +533,7 @@ public void modelReload(boolean doPopNotice) {
});
filterPaneTagFlow.getChildren().clear();
if (assetItemList != null && app.modelsDataset != null) {
- ArrayList sortTags = new ArrayList<>(assetItemList.extract(AssetItem.PropertyExtractor.ASSET_ITEM_SORT_TAGS));
+ ArrayList sortTags = new ArrayList<>(assetItemList.extract(ModelItem.PropertyExtractor.ASSET_ITEM_SORT_TAGS));
sortTags.sort(Comparator.naturalOrder());
sortTags.forEach(s -> {
String t = app.modelsDataset.sortTags == null ? s : app.modelsDataset.sortTags.getOrDefault(s, s);
@@ -553,9 +557,9 @@ public void modelReload(boolean doPopNotice) {
if (assetItemList != null && !modelCellList.isEmpty() &&
app.config.character_asset != null && !app.config.character_asset.isEmpty()) {
// Scroll to recent selected model and then select it.
- AssetItem recentSelected = assetItemList.searchByRelPath(app.config.character_asset);
+ ModelItem recentSelected = assetItemList.searchByRelPath(app.config.character_asset);
if (recentSelected != null) {
- for (JFXListCell cell : searchModelView.getItems())
+ for (JFXListCell cell : searchModelView.getItems())
if (recentSelected.equals(cell.getItem())) {
searchModelView.scrollTo(cell);
searchModelView.getSelectionModel().select(cell);
@@ -577,20 +581,20 @@ public void modelReload(boolean doPopNotice) {
});
}
- private JFXListCell getMenuItem(AssetItem assetItem, JFXListView> container) {
+ private JFXListCell getMenuItem(ModelItem modelItem, JFXListView> container) {
double width = container.getPrefWidth() - 50;
double height = 30;
double divide = 0.618;
- JFXListCell item = new JFXListCell<>();
+ JFXListCell item = new JFXListCell<>();
item.getStyleClass().addAll("Search-models-item");
- Label name = new Label(assetItem.toString());
+ Label name = new Label(modelItem.toString());
name.getStyleClass().addAll("Search-models-label", "Search-models-label-primary");
- name.setPrefSize(assetItem.skinGroupName == null ? width : width * divide, height);
+ name.setPrefSize(modelItem.skinGroupName == null ? width : width * divide, height);
name.setLayoutX(15);
- Label alias1 = new Label(assetItem.skinGroupName);
+ Label alias1 = new Label(modelItem.skinGroupName);
alias1.getStyleClass().addAll("Search-models-label", "Search-models-label-secondary");
alias1.setPrefSize(width * (1 - divide), height);
- alias1.setLayoutX(assetItem.skinGroupName == null ? 0 : width * divide);
+ alias1.setLayoutX(modelItem.skinGroupName == null ? 0 : width * divide);
SVGPath fav = GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_STAR_FILLED, GuiPrefabs.COLOR_WARNING);
fav.getStyleClass().add("Search-models-star");
fav.setLayoutX(0);
@@ -599,14 +603,14 @@ private JFXListCell getMenuItem(AssetItem assetItem, JFXListView item) {
+ private void selectModel(ModelItem asset, JFXListCell item) {
// Reset
if (selectedModelCell != null) {
selectedModelCell.getStyleClass().setAll("Search-models-item");
diff --git a/desktop/src/cn/harryh/arkpets/guitasks/VerifyModelsTask.java b/desktop/src/cn/harryh/arkpets/guitasks/VerifyModelsTask.java
index 30ee36a3..a841a0b9 100644
--- a/desktop/src/cn/harryh/arkpets/guitasks/VerifyModelsTask.java
+++ b/desktop/src/cn/harryh/arkpets/guitasks/VerifyModelsTask.java
@@ -3,8 +3,8 @@
*/
package cn.harryh.arkpets.guitasks;
-import cn.harryh.arkpets.assets.AssetItem;
-import cn.harryh.arkpets.assets.AssetItemGroup;
+import cn.harryh.arkpets.assets.ModelItem;
+import cn.harryh.arkpets.assets.ModelItemGroup;
import cn.harryh.arkpets.assets.ModelsDataset;
import cn.harryh.arkpets.utils.GuiPrefabs;
import cn.harryh.arkpets.utils.Logger;
@@ -39,12 +39,12 @@ protected Task getTask() {
return new Task<>() {
@Override
protected Boolean call() {
- AssetItemGroup validModelAssets = modelsDataset.data.filter(AssetItem::isValid);
+ ModelItemGroup validModelAssets = modelsDataset.data.filter(ModelItem::isValid);
int currentProgress = 0;
int totalProgress = validModelAssets.size();
boolean flag = false;
- for (AssetItem item : validModelAssets) {
+ for (ModelItem item : validModelAssets) {
this.updateProgress(currentProgress++, totalProgress);
if (this.isCancelled()) {
// Cancelled: