mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-01-12 13:15:10 +08:00
0b5329514b
* Deserialization support for tsv files * Benchmarking * Apparently moving the setter out of the lambda fixed the setAccessible issue * Thread it * Use AllArgsConstructor instead of field reflection * Clean up AllArgsConstructor TSV deserialization * Refactor TsvUtils * Remove AllArgsConstructors from Excels * Set field accessible * [WIP] TSJ improvements * [WIP] More TSV stuff * [WIP] More TSV stuff * Working TSV parser (slow) * Load Excels in TSJ > JSON > TSV priority
559 lines
23 KiB
Java
559 lines
23 KiB
Java
package emu.grasscutter.data;
|
|
|
|
import com.google.gson.annotations.SerializedName;
|
|
import emu.grasscutter.Grasscutter;
|
|
import emu.grasscutter.data.binout.*;
|
|
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
|
|
import emu.grasscutter.data.common.PointData;
|
|
import emu.grasscutter.game.managers.blossom.BlossomConfig;
|
|
import emu.grasscutter.game.quest.QuestEncryptionKey;
|
|
import emu.grasscutter.game.world.SpawnDataEntry;
|
|
import emu.grasscutter.game.world.SpawnDataEntry.GridBlockId;
|
|
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
|
|
import emu.grasscutter.scripts.SceneIndexManager;
|
|
import emu.grasscutter.utils.FileUtils;
|
|
import emu.grasscutter.utils.JsonUtils;
|
|
import emu.grasscutter.utils.TsvUtils;
|
|
import it.unimi.dsi.fastutil.Pair;
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
import it.unimi.dsi.fastutil.ints.IntArraySet;
|
|
import lombok.val;
|
|
|
|
import org.reflections.Reflections;
|
|
|
|
import java.io.*;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.*;
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
|
import java.util.regex.Pattern;
|
|
import java.util.stream.Stream;
|
|
|
|
import static emu.grasscutter.utils.FileUtils.getDataPath;
|
|
import static emu.grasscutter.utils.FileUtils.getResourcePath;
|
|
import static emu.grasscutter.utils.Language.translate;
|
|
|
|
public class ResourceLoader {
|
|
|
|
private static final Set<String> loadedResources = new CopyOnWriteArraySet<>();
|
|
|
|
// Get a list of all resource classes, sorted by loadPriority
|
|
public static List<Class<?>> getResourceDefClasses() {
|
|
Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName());
|
|
Set<?> classes = reflections.getSubTypesOf(GameResource.class);
|
|
|
|
List<Class<?>> classList = new ArrayList<>(classes.size());
|
|
classes.forEach(o -> {
|
|
Class<?> c = (Class<?>) o;
|
|
if (c.getAnnotation(ResourceType.class) != null) {
|
|
classList.add(c);
|
|
}
|
|
});
|
|
|
|
classList.sort((a, b) -> b.getAnnotation(ResourceType.class).loadPriority().value() - a.getAnnotation(ResourceType.class).loadPriority().value());
|
|
|
|
return classList;
|
|
}
|
|
|
|
// Get a list containing sets of all resource classes, sorted by loadPriority
|
|
protected static List<Set<Class<?>>> getResourceDefClassesPrioritySets() {
|
|
val reflections = new Reflections(ResourceLoader.class.getPackage().getName());
|
|
val classes = reflections.getSubTypesOf(GameResource.class);
|
|
val priorities = ResourceType.LoadPriority.getInOrder();
|
|
Grasscutter.getLogger().debug("Priorities are "+priorities);
|
|
val map = new LinkedHashMap<ResourceType.LoadPriority, Set<Class<?>>>(priorities.size());
|
|
priorities.forEach(p -> map.put(p, new HashSet<>()));
|
|
|
|
classes.forEach(c -> {
|
|
// val c = (Class<?>) o;
|
|
val annotation = c.getAnnotation(ResourceType.class);
|
|
if (annotation != null) {
|
|
map.get(annotation.loadPriority()).add(c);
|
|
}
|
|
});
|
|
return List.copyOf(map.values());
|
|
}
|
|
|
|
private static boolean loadedAll = false;
|
|
public static void loadAll() {
|
|
if (loadedAll) return;
|
|
Grasscutter.getLogger().info(translate("messages.status.resources.loading"));
|
|
|
|
// Load ability lists
|
|
loadAbilityEmbryos();
|
|
loadOpenConfig();
|
|
loadAbilityModifiers();
|
|
// Load resources
|
|
loadResources(true);
|
|
// Process into depots
|
|
GameDepot.load();
|
|
// Load spawn data and quests
|
|
loadGadgetConfigData();
|
|
loadSpawnData();
|
|
loadQuests();
|
|
loadScriptSceneData();
|
|
// Load scene points - must be done AFTER resources are loaded
|
|
loadScenePoints();
|
|
// Load default home layout
|
|
loadHomeworldDefaultSaveData();
|
|
loadNpcBornData();
|
|
loadBlossomResources();
|
|
cacheTalentLevelSets();
|
|
|
|
Grasscutter.getLogger().info(translate("messages.status.resources.finish"));
|
|
loadedAll = true;
|
|
}
|
|
|
|
public static void loadResources() {
|
|
loadResources(false);
|
|
}
|
|
|
|
public static void loadResources(boolean doReload) {
|
|
long startTime = System.nanoTime();
|
|
val errors = new ConcurrentLinkedQueue<Pair<String, Exception>>(); // Logger in a parallel stream will deadlock
|
|
|
|
getResourceDefClassesPrioritySets().forEach(classes -> {
|
|
classes.stream()
|
|
.parallel().unordered()
|
|
.forEach(c -> {
|
|
val type = c.getAnnotation(ResourceType.class);
|
|
if (type == null) return;
|
|
|
|
val map = GameData.getMapByResourceDef(c);
|
|
if (map == null) return;
|
|
|
|
try {
|
|
loadFromResource(c, type, map, doReload);
|
|
} catch (Exception e) {
|
|
errors.add(Pair.of(Arrays.toString(type.name()), e));
|
|
}
|
|
});
|
|
});
|
|
errors.forEach(pair -> Grasscutter.getLogger().error("Error loading resource file: " + pair.left(), pair.right()));
|
|
long endTime = System.nanoTime();
|
|
long ns = (endTime - startTime); //divide by 1000000 to get milliseconds.
|
|
Grasscutter.getLogger().debug("Loading resources took "+ns+"ns == "+ns/1000000+"ms");
|
|
}
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
protected static void loadFromResource(Class<?> c, ResourceType type, Int2ObjectMap map, boolean doReload) throws Exception {
|
|
val simpleName = c.getSimpleName();
|
|
if (doReload || !loadedResources.contains(simpleName)) {
|
|
for (String name : type.name()) {
|
|
loadFromResource(c, FileUtils.getExcelPath(name), map);
|
|
}
|
|
loadedResources.add(simpleName);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
protected static <T> void loadFromResource(Class<T> c, Path filename, Int2ObjectMap map) throws Exception {
|
|
val results = switch (FileUtils.getFileExtension(filename)) {
|
|
case "json" -> JsonUtils.loadToList(filename, c);
|
|
case "tsj" -> TsvUtils.loadTsjToListSetField(c, filename);
|
|
case "tsv" -> TsvUtils.loadTsvToListSetField(c, filename);
|
|
default -> null;
|
|
};
|
|
if (results == null) return;
|
|
results.forEach(o -> {
|
|
GameResource res = (GameResource) o;
|
|
res.onLoad();
|
|
map.put(res.getId(), res);
|
|
});
|
|
}
|
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
protected static <T> void loadFromResource(Class<T> c, String fileName, Int2ObjectMap map) throws Exception {
|
|
JsonUtils.loadToList(getResourcePath("ExcelBinOutput/" + fileName), c).forEach(o -> {
|
|
GameResource res = (GameResource) o;
|
|
res.onLoad();
|
|
map.put(res.getId(), res);
|
|
});
|
|
}
|
|
|
|
public class ScenePointConfig { // Sadly this doesn't work as a local class in loadScenePoints()
|
|
public Map<Integer, PointData> points;
|
|
}
|
|
private static void loadScenePoints() {
|
|
val pattern = Pattern.compile("scene([0-9]+)_point\\.json");
|
|
try {
|
|
Files.newDirectoryStream(getResourcePath("BinOutput/Scene/Point"), "scene*_point.json").forEach(path -> {
|
|
val matcher = pattern.matcher(path.getFileName().toString());
|
|
if (!matcher.find()) return;
|
|
int sceneId = Integer.parseInt(matcher.group(1));
|
|
|
|
ScenePointConfig config;
|
|
try {
|
|
config = JsonUtils.loadToClass(path, ScenePointConfig.class);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return;
|
|
}
|
|
|
|
if (config.points == null) return;
|
|
|
|
val scenePoints = new IntArrayList();
|
|
config.points.forEach((pointId, pointData) -> {
|
|
val scenePoint = new ScenePointEntry(sceneId, pointData);
|
|
scenePoints.add(pointId);
|
|
pointData.setId(pointId);
|
|
|
|
GameData.getScenePointIdList().add(pointId);
|
|
GameData.getScenePointEntries().put(scenePoint.getName(), scenePoint);
|
|
GameData.scenePointEntryMap.put((sceneId << 16) + pointId, scenePoint);
|
|
|
|
pointData.updateDailyDungeon();
|
|
});
|
|
GameData.getScenePointsPerScene().put(sceneId, scenePoints);
|
|
});
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private static void cacheTalentLevelSets() {
|
|
// All known levels, keyed by proudSkillGroupId
|
|
GameData.getProudSkillDataMap().forEach((id, data) ->
|
|
GameData.proudSkillGroupLevels
|
|
.computeIfAbsent(data.getProudSkillGroupId(), i -> new IntArraySet())
|
|
.add(data.getLevel()));
|
|
// All known levels, keyed by avatarSkillId
|
|
GameData.getAvatarSkillDataMap().forEach((id, data) ->
|
|
GameData.avatarSkillLevels.put((int) id, GameData.proudSkillGroupLevels.get(data.getProudSkillGroupId())));
|
|
// Maximum known levels, keyed by proudSkillGroupId
|
|
GameData.proudSkillGroupLevels.forEach((id, set) ->
|
|
GameData.proudSkillGroupMaxLevels.put((int) id, set.intStream().max().getAsInt()));
|
|
}
|
|
|
|
private static void loadAbilityEmbryos() {
|
|
List<AbilityEmbryoEntry> embryoList = null;
|
|
|
|
// Read from cached file if exists
|
|
try {
|
|
embryoList = JsonUtils.loadToList(getDataPath("AbilityEmbryos.json"), AbilityEmbryoEntry.class);
|
|
} catch (Exception ignored) {}
|
|
|
|
if (embryoList == null) {
|
|
// Load from BinOutput
|
|
val pattern = Pattern.compile("ConfigAvatar_(.+?)\\.json");
|
|
|
|
val l = new ArrayList<AbilityEmbryoEntry>();
|
|
try {
|
|
Files.newDirectoryStream(getResourcePath("BinOutput/Avatar/"), "ConfigAvatar_*.json").forEach(path -> {
|
|
val matcher = pattern.matcher(path.getFileName().toString());
|
|
if (!matcher.find()) return;
|
|
String avatarName = matcher.group(1);
|
|
AvatarConfig config;
|
|
|
|
try {
|
|
config = JsonUtils.loadToClass(path, AvatarConfig.class);
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger().error("Error loading player ability embryos:", e);
|
|
return;
|
|
}
|
|
|
|
if (config.abilities == null) return;
|
|
|
|
int s = config.abilities.size();
|
|
AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s]));
|
|
l.add(al);
|
|
});
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Error loading ability embryos: no files found");
|
|
return;
|
|
}
|
|
|
|
embryoList = l;
|
|
|
|
try {
|
|
GameDepot.setPlayerAbilities(JsonUtils.loadToMap(getResourcePath("BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json"), String.class, AvatarConfig.class));
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Error loading player abilities:", e);
|
|
}
|
|
}
|
|
|
|
if (embryoList == null || embryoList.isEmpty()) {
|
|
Grasscutter.getLogger().error("No embryos loaded!");
|
|
return;
|
|
}
|
|
|
|
for (AbilityEmbryoEntry entry : embryoList) {
|
|
GameData.getAbilityEmbryoInfo().put(entry.getName(), entry);
|
|
}
|
|
}
|
|
|
|
// private static HashSet<String> modifierActionTypes = new HashSet<>();
|
|
public static class AbilityConfigData {
|
|
public AbilityData Default;
|
|
}
|
|
private static void loadAbilityModifiers() {
|
|
// Load from BinOutput
|
|
try (Stream<Path> paths = Files.walk(getResourcePath("BinOutput/Ability/Temp/"))) {
|
|
paths.filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".json")).forEach(ResourceLoader::loadAbilityModifiers);
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Error loading ability modifiers: ", e);
|
|
return;
|
|
}
|
|
// System.out.println("Loaded modifiers, found types:");
|
|
// modifierActionTypes.stream().sorted().forEach(s -> System.out.printf("%s, ", s));
|
|
// System.out.println("[End]");
|
|
}
|
|
private static void loadAbilityModifiers(Path path) {
|
|
try {
|
|
JsonUtils.loadToList(path, AbilityConfigData.class).forEach(data -> loadAbilityData(data.Default));
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Error loading ability modifiers from path " + path.toString() + ": ", e);
|
|
return;
|
|
}
|
|
}
|
|
private static void loadAbilityData(AbilityData data) {
|
|
GameData.abilityDataMap.put(data.abilityName, data);
|
|
|
|
val modifiers = data.modifiers;
|
|
if (modifiers == null || modifiers.size() == 0) return;
|
|
|
|
String name = data.abilityName;
|
|
AbilityModifierEntry modifierEntry = new AbilityModifierEntry(name);
|
|
modifiers.forEach((key, modifier) -> {
|
|
Stream.ofNullable(modifier.onAdded).flatMap(Stream::of)
|
|
// .map(action -> {modifierActionTypes.add(action.$type); return action;})
|
|
.filter(action -> action.type == AbilityModifierAction.Type.HealHP)
|
|
.forEach(action -> modifierEntry.getOnAdded().add(action));
|
|
Stream.ofNullable(modifier.onThinkInterval).flatMap(Stream::of)
|
|
// .map(action -> {modifierActionTypes.add(action.$type); return action;})
|
|
.filter(action -> action.type == AbilityModifierAction.Type.HealHP)
|
|
.forEach(action -> modifierEntry.getOnThinkInterval().add(action));
|
|
Stream.ofNullable(modifier.onRemoved).flatMap(Stream::of)
|
|
// .map(action -> {modifierActionTypes.add(action.$type); return action;})
|
|
.filter(action -> action.type == AbilityModifierAction.Type.HealHP)
|
|
.forEach(action -> modifierEntry.getOnRemoved().add(action));
|
|
});
|
|
|
|
GameData.getAbilityModifiers().put(name, modifierEntry);
|
|
}
|
|
|
|
private static void loadSpawnData() {
|
|
String[] spawnDataNames = {"Spawns.json", "GadgetSpawns.json"};
|
|
ArrayList<SpawnGroupEntry> spawnEntryMap = new ArrayList<>();
|
|
|
|
for (String name : spawnDataNames) {
|
|
// Load spawn entries from file
|
|
try (InputStreamReader reader = DataLoader.loadReader(name)) {
|
|
// Add spawns to group if it already exists in our spawn group map
|
|
spawnEntryMap.addAll(JsonUtils.loadToList(reader, SpawnGroupEntry.class));
|
|
} catch (Exception ignored) {}
|
|
}
|
|
|
|
if (spawnEntryMap.isEmpty()) {
|
|
Grasscutter.getLogger().error("No spawn data loaded!");
|
|
return;
|
|
}
|
|
|
|
HashMap<GridBlockId, ArrayList<SpawnDataEntry>> areaSort = new HashMap<>();
|
|
//key = sceneId,x,z , value = ArrayList<SpawnDataEntry>
|
|
for (SpawnGroupEntry entry : spawnEntryMap) {
|
|
entry.getSpawns().forEach(
|
|
s -> {
|
|
s.setGroup(entry);
|
|
GridBlockId point = s.getBlockId();
|
|
if (!areaSort.containsKey(point)) {
|
|
areaSort.put(point, new ArrayList<>());
|
|
}
|
|
areaSort.get(point).add(s);
|
|
}
|
|
);
|
|
}
|
|
GameDepot.addSpawnListById(areaSort);
|
|
}
|
|
|
|
private static void loadOpenConfig() {
|
|
// Read from cached file if exists
|
|
List<OpenConfigEntry> list = null;
|
|
|
|
try {
|
|
list = JsonUtils.loadToList(getDataPath("OpenConfig.json"), OpenConfigEntry.class);
|
|
} catch (Exception ignored) {}
|
|
|
|
if (list == null) {
|
|
Map<String, OpenConfigEntry> map = new TreeMap<>();
|
|
String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"};
|
|
|
|
for (String folderName : folderNames) {
|
|
try {
|
|
Files.newDirectoryStream(getResourcePath(folderName), "*.json").forEach(path -> {
|
|
try {
|
|
JsonUtils.loadToMap(path, String.class, OpenConfigData[].class)
|
|
.forEach((name, data) -> map.put(name, new OpenConfigEntry(name, data)));
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return;
|
|
}
|
|
});
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Error loading open config: no files found in " + folderName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
list = new ArrayList<>(map.values());
|
|
}
|
|
|
|
if (list == null || list.isEmpty()) {
|
|
Grasscutter.getLogger().error("No openconfig entries loaded!");
|
|
return;
|
|
}
|
|
|
|
for (OpenConfigEntry entry : list) {
|
|
GameData.getOpenConfigEntries().put(entry.getName(), entry);
|
|
}
|
|
}
|
|
|
|
private static void loadQuests() {
|
|
try {
|
|
Files.list(getResourcePath("BinOutput/Quest/")).forEach(path -> {
|
|
try {
|
|
val mainQuest = JsonUtils.loadToClass(path, MainQuestData.class);
|
|
GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
|
|
} catch (IOException e) {
|
|
|
|
}
|
|
});
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Quest data missing");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
val questEncryptionMap = GameData.getMainQuestEncryptionMap();
|
|
String path = "QuestEncryptionKeys.json";
|
|
try {
|
|
JsonUtils.loadToList(getResourcePath(path), QuestEncryptionKey.class).forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key));
|
|
} catch (IOException | NullPointerException ignored) {}
|
|
try {
|
|
DataLoader.loadList(path, QuestEncryptionKey.class).forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key));
|
|
} catch (IOException | NullPointerException ignored) {}
|
|
Grasscutter.getLogger().debug("Loaded {} quest keys.", questEncryptionMap.size());
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger().error("Unable to load quest keys.", e);
|
|
}
|
|
|
|
Grasscutter.getLogger().debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas.");
|
|
}
|
|
|
|
public static void loadScriptSceneData() {
|
|
try {
|
|
Files.list(getResourcePath("ScriptSceneData/")).forEach(path -> {
|
|
try {
|
|
GameData.getScriptSceneDataMap().put(path.getFileName().toString(), JsonUtils.loadToClass(path, ScriptSceneData.class));
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
return;
|
|
}
|
|
});
|
|
Grasscutter.getLogger().debug("Loaded " + GameData.getScriptSceneDataMap().size() + " ScriptSceneDatas.");
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().debug("ScriptSceneData folder missing or empty.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private static void loadHomeworldDefaultSaveData() {
|
|
val pattern = Pattern.compile("scene([0-9]+)_home_config\\.json");
|
|
try {
|
|
Files.newDirectoryStream(getResourcePath("BinOutput/HomeworldDefaultSave"), "scene*_home_config.json").forEach(path -> {
|
|
val matcher = pattern.matcher(path.getFileName().toString());
|
|
if (!matcher.find()) return;
|
|
|
|
try {
|
|
val sceneId = Integer.parseInt(matcher.group(1));
|
|
val data = JsonUtils.loadToClass(path, HomeworldDefaultSaveData.class);
|
|
GameData.getHomeworldDefaultSaveData().put(sceneId, data);
|
|
} catch (Exception ignored) {}
|
|
});
|
|
Grasscutter.getLogger().debug("Loaded " + GameData.getHomeworldDefaultSaveData().size() + " HomeworldDefaultSaveDatas.");
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Failed to load HomeworldDefaultSave folder.");
|
|
}
|
|
}
|
|
|
|
private static void loadNpcBornData() {
|
|
try {
|
|
Files.newDirectoryStream(getResourcePath("BinOutput/Scene/SceneNpcBorn/"), "*.json").forEach(path -> {
|
|
try {
|
|
val data = JsonUtils.loadToClass(path, SceneNpcBornData.class);
|
|
if (data.getBornPosList() == null || data.getBornPosList().size() == 0) {
|
|
return;
|
|
}
|
|
|
|
data.setIndex(SceneIndexManager.buildIndex(3, data.getBornPosList(), item -> item.getPos().toPoint()));
|
|
GameData.getSceneNpcBornData().put(data.getSceneId(), data);
|
|
} catch (IOException ignored) {}
|
|
});
|
|
Grasscutter.getLogger().debug("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas.");
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Failed to load SceneNpcBorn folder.");
|
|
}
|
|
}
|
|
|
|
private static void loadGadgetConfigData() {
|
|
try {
|
|
Files.newDirectoryStream(getResourcePath("BinOutput/Gadget/"), "*.json").forEach(path -> {
|
|
try {
|
|
GameData.getGadgetConfigData().putAll(JsonUtils.loadToMap(path, String.class, ConfigGadget.class));
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger().error("failed to load ConfigGadget entries for " + path.toString(), e);
|
|
return;
|
|
}
|
|
});
|
|
|
|
Grasscutter.getLogger().debug("Loaded {} ConfigGadget entries.", GameData.getGadgetConfigData().size());
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Failed to load ConfigGadget folder.");
|
|
}
|
|
}
|
|
|
|
private static void loadBlossomResources() {
|
|
try {
|
|
GameDepot.setBlossomConfig(DataLoader.loadClass("BlossomConfig.json", BlossomConfig.class));
|
|
Grasscutter.getLogger().debug("Loaded BlossomConfig.");
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().warn("Failed to load BlossomConfig.");
|
|
}
|
|
}
|
|
|
|
// BinOutput configs
|
|
|
|
public static class AvatarConfig {
|
|
@SerializedName(value="abilities", alternate={"targetAbilities"})
|
|
public ArrayList<AvatarConfigAbility> abilities;
|
|
}
|
|
|
|
public static class AvatarConfigAbility {
|
|
public String abilityName;
|
|
public String toString() {
|
|
return abilityName;
|
|
}
|
|
}
|
|
|
|
private static class OpenConfig {
|
|
public OpenConfigData[] data;
|
|
}
|
|
|
|
public static class OpenConfigData {
|
|
public String $type;
|
|
public String abilityName;
|
|
|
|
@SerializedName(value="talentIndex", alternate={"OJOFFKLNAHN"})
|
|
public int talentIndex;
|
|
|
|
@SerializedName(value="skillID", alternate={"overtime"})
|
|
public int skillID;
|
|
|
|
@SerializedName(value="pointDelta", alternate={"IGEBKIHPOIF"})
|
|
public int pointDelta;
|
|
}
|
|
}
|