mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-01-12 06:42:56 +08:00
921 lines
40 KiB
Java
921 lines
40 KiB
Java
package emu.grasscutter.data;
|
|
|
|
import static emu.grasscutter.utils.FileUtils.getDataPath;
|
|
import static emu.grasscutter.utils.FileUtils.getResourcePath;
|
|
import static emu.grasscutter.utils.Language.translate;
|
|
|
|
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.binout.config.*;
|
|
import emu.grasscutter.data.common.PointData;
|
|
import emu.grasscutter.data.custom.TrialAvatarActivityCustomData;
|
|
import emu.grasscutter.data.custom.TrialAvatarCustomData;
|
|
import emu.grasscutter.data.excels.trial.TrialAvatarActivityDataData;
|
|
import emu.grasscutter.data.server.ActivityCondGroup;
|
|
import emu.grasscutter.data.server.GadgetMapping;
|
|
import emu.grasscutter.game.managers.blossom.BlossomConfig;
|
|
import emu.grasscutter.game.quest.QuestEncryptionKey;
|
|
import emu.grasscutter.game.quest.RewindData;
|
|
import emu.grasscutter.game.quest.TeleportData;
|
|
import emu.grasscutter.game.world.GroupReplacementData;
|
|
import emu.grasscutter.game.world.SpawnDataEntry;
|
|
import emu.grasscutter.game.world.SpawnDataEntry.GridBlockId;
|
|
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
|
|
import emu.grasscutter.scripts.EntityControllerScriptManager;
|
|
import emu.grasscutter.scripts.SceneIndexManager;
|
|
import emu.grasscutter.scripts.ScriptLoader;
|
|
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 java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.*;
|
|
import java.util.Map.Entry;
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
|
import java.util.regex.Pattern;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
import javax.script.Bindings;
|
|
import javax.script.CompiledScript;
|
|
import lombok.SneakyThrows;
|
|
import lombok.val;
|
|
import org.reflections.Reflections;
|
|
|
|
public final class ResourceLoader {
|
|
|
|
private static final Set<String> loadedResources = new CopyOnWriteArraySet<>();
|
|
private static boolean loadedAll = false;
|
|
|
|
// 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());
|
|
}
|
|
|
|
@SneakyThrows
|
|
public static void loadAll() {
|
|
if (loadedAll) return;
|
|
Grasscutter.getLogger().info(translate("messages.status.resources.loading"));
|
|
|
|
// Initialize the script loader.
|
|
ScriptLoader.init();
|
|
|
|
loadConfigData();
|
|
// Load ability lists
|
|
loadAbilityEmbryos();
|
|
loadOpenConfig();
|
|
loadAbilityModifiers();
|
|
// Load resources
|
|
loadResources(true);
|
|
// Process into depots
|
|
GameDepot.load();
|
|
// Load spawn data and quests
|
|
loadSpawnData();
|
|
loadQuests();
|
|
loadScriptSceneData();
|
|
// Load scene points - must be done AFTER resources are loaded
|
|
loadScenePoints();
|
|
// Load default home layout
|
|
loadHomeworldDefaultSaveData();
|
|
loadNpcBornData();
|
|
loadBlossomResources();
|
|
cacheTalentLevelSets();
|
|
|
|
// Load custom server resources.
|
|
loadConfigLevelEntityData();
|
|
loadQuestShareConfig();
|
|
loadGadgetMappings();
|
|
loadActivityCondGroups();
|
|
loadGroupReplacements();
|
|
loadTrialAvatarCustomData();
|
|
|
|
EntityControllerScriptManager.load();
|
|
|
|
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(filename, c);
|
|
case "tsv" -> TsvUtils.loadTsvToListSetField(filename, c);
|
|
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);
|
|
});
|
|
}
|
|
|
|
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.getScenePointEntryMap().put((sceneId << 16) + pointId, scenePoint);
|
|
|
|
pointData.updateDailyDungeon();
|
|
});
|
|
GameData.getScenePointsPerScene().put(sceneId, scenePoints);
|
|
});
|
|
} catch (IOException ignored) {
|
|
Grasscutter.getLogger()
|
|
.error("Scene point files cannot be found, you cannot use teleport waypoints!");
|
|
}
|
|
}
|
|
|
|
private static void cacheTalentLevelSets() {
|
|
// All known levels, keyed by proudSkillGroupId
|
|
GameData.getProudSkillDataMap()
|
|
.forEach(
|
|
(id, data) ->
|
|
GameData.getProudSkillGroupLevels()
|
|
.computeIfAbsent(data.getProudSkillGroupId(), i -> new IntArraySet())
|
|
.add(data.getLevel()));
|
|
// All known levels, keyed by avatarSkillId
|
|
GameData.getAvatarSkillDataMap()
|
|
.forEach(
|
|
(id, data) ->
|
|
GameData.getAvatarSkillLevels()
|
|
.put(
|
|
(int) id,
|
|
GameData.getProudSkillGroupLevels().get(data.getProudSkillGroupId())));
|
|
// Maximum known levels, keyed by proudSkillGroupId
|
|
GameData.getProudSkillGroupLevels()
|
|
.forEach(
|
|
(id, set) ->
|
|
GameData.getProudSkillGroupMaxLevels()
|
|
.put((int) id, set.intStream().max().orElse(-1)));
|
|
}
|
|
|
|
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
|
|
var pattern = Pattern.compile("ConfigAvatar_(.+?)\\.json");
|
|
|
|
var entries = new ArrayList<AbilityEmbryoEntry>();
|
|
try (var stream =
|
|
Files.newDirectoryStream(getResourcePath("BinOutput/Avatar/"), "ConfigAvatar_*.json")) {
|
|
|
|
stream.forEach(
|
|
path -> {
|
|
var matcher = pattern.matcher(path.getFileName().toString());
|
|
if (!matcher.find()) return;
|
|
|
|
var 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;
|
|
|
|
entries.add(
|
|
new AbilityEmbryoEntry(
|
|
avatarName,
|
|
config.abilities.stream()
|
|
.map(Object::toString)
|
|
.toArray(size -> new String[config.abilities.size()])));
|
|
});
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Error loading ability embryos: no files found");
|
|
return;
|
|
}
|
|
|
|
embryoList = entries;
|
|
|
|
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 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);
|
|
}
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
private static void loadAbilityData(AbilityData data) {
|
|
GameData.getAbilityDataMap().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();
|
|
}
|
|
});
|
|
} 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 (var files = Files.list(getResourcePath("BinOutput/Quest/"))) {
|
|
files.forEach(
|
|
path -> {
|
|
try {
|
|
val mainQuest = JsonUtils.loadToClass(path, MainQuestData.class);
|
|
GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
|
|
|
|
mainQuest.onLoad(); // Load the quest data.
|
|
} catch (IOException ignored) {
|
|
}
|
|
});
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Quest data missing");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
val questEncryptionMap = GameData.getMainQuestEncryptionMap();
|
|
var 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();
|
|
}
|
|
});
|
|
Grasscutter.getLogger()
|
|
.debug("Loaded " + GameData.getScriptSceneDataMap().size() + " ScriptSceneDatas.");
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().debug("ScriptSceneData folder missing or empty.");
|
|
}
|
|
}
|
|
|
|
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 loadConfigData() {
|
|
loadConfigData(GameData.getAvatarConfigData(), "BinOutput/Avatar/", ConfigEntityAvatar.class);
|
|
loadConfigData(
|
|
GameData.getMonsterConfigData(), "BinOutput/Monster/", ConfigEntityMonster.class);
|
|
loadConfigDataMap(
|
|
GameData.getGadgetConfigData(), "BinOutput/Gadget/", ConfigEntityGadget.class);
|
|
}
|
|
|
|
private static <T extends ConfigEntityBase> void loadConfigData(
|
|
Map<String, T> targetMap, String folderPath, Class<T> configClass) {
|
|
val className = configClass.getName();
|
|
try (val stream = Files.newDirectoryStream(getResourcePath(folderPath), "*.json")) {
|
|
stream.forEach(
|
|
path -> {
|
|
try {
|
|
val name = path.getFileName().toString().replace(".json", "");
|
|
targetMap.put(name, JsonUtils.loadToClass(path, configClass));
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger()
|
|
.error("failed to load {} entries for {}", className, path.toString(), e);
|
|
}
|
|
});
|
|
|
|
Grasscutter.getLogger()
|
|
.debug("Loaded {} {} entries.", GameData.getMonsterConfigData().size(), className);
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Failed to load {} folder.", className);
|
|
}
|
|
}
|
|
|
|
private static <T extends ConfigEntityBase> void loadConfigDataMap(
|
|
Map<String, T> targetMap, String folderPath, Class<T> configClass) {
|
|
val className = configClass.getName();
|
|
try (val stream = Files.newDirectoryStream(getResourcePath(folderPath), "*.json")) {
|
|
stream.forEach(
|
|
path -> {
|
|
try {
|
|
targetMap.putAll(JsonUtils.loadToMap(path, String.class, configClass));
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger()
|
|
.error("failed to load {} entries for {}", className, path.toString(), e);
|
|
}
|
|
});
|
|
|
|
Grasscutter.getLogger()
|
|
.debug("Loaded {} {} entries.", GameData.getMonsterConfigData().size(), className);
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Failed to load {} folder.", className);
|
|
}
|
|
}
|
|
|
|
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.");
|
|
}
|
|
}
|
|
|
|
private static void loadConfigLevelEntityData() {
|
|
// Load from BinOutput
|
|
val pattern = Pattern.compile("ConfigLevelEntity_(.+?)\\.json");
|
|
|
|
try {
|
|
var stream =
|
|
Files.newDirectoryStream(
|
|
getResourcePath("BinOutput/LevelEntity/"), "ConfigLevelEntity_*.json");
|
|
stream.forEach(
|
|
path -> {
|
|
val matcher = pattern.matcher(path.getFileName().toString());
|
|
if (!matcher.find()) return;
|
|
Map<String, ConfigLevelEntity> config;
|
|
|
|
try {
|
|
config = JsonUtils.loadToMap(path, String.class, ConfigLevelEntity.class);
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger().error("Error loading player ability embryos:", e);
|
|
return;
|
|
}
|
|
GameData.getConfigLevelEntityDataMap().putAll(config);
|
|
});
|
|
stream.close();
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Error loading config level entity: no files found");
|
|
return;
|
|
}
|
|
|
|
if (GameData.getConfigLevelEntityDataMap() == null
|
|
|| GameData.getConfigLevelEntityDataMap().isEmpty()) {
|
|
Grasscutter.getLogger().error("No config level entity loaded!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private static void loadQuestShareConfig() {
|
|
// Load from BinOutput
|
|
val pattern = Pattern.compile("Q(.+?)\\ShareConfig.lua");
|
|
|
|
try {
|
|
var bindings = ScriptLoader.getEngine().createBindings();
|
|
var stream =
|
|
Files.newDirectoryStream(getResourcePath("Scripts/Quest/Share/"), "Q*ShareConfig.lua");
|
|
stream.forEach(
|
|
path -> {
|
|
val matcher = pattern.matcher(path.getFileName().toString());
|
|
if (!matcher.find()) return;
|
|
|
|
var cs = ScriptLoader.getScript("Quest/Share/" + path.getFileName().toString());
|
|
if (cs == null) return;
|
|
|
|
try {
|
|
cs.eval(bindings);
|
|
// these are Map<String, class>
|
|
var teleportDataMap =
|
|
ScriptLoader.getSerializer()
|
|
.toMap(TeleportData.class, bindings.get("quest_data"));
|
|
var rewindDataMap =
|
|
ScriptLoader.getSerializer().toMap(RewindData.class, bindings.get("rewind_data"));
|
|
// convert them to Map<Integer, class> and cache
|
|
GameData.getTeleportDataMap()
|
|
.putAll(
|
|
teleportDataMap.entrySet().stream()
|
|
.collect(
|
|
Collectors.toMap(
|
|
entry -> Integer.valueOf(entry.getKey()), Entry::getValue)));
|
|
GameData.getRewindDataMap()
|
|
.putAll(
|
|
rewindDataMap.entrySet().stream()
|
|
.collect(
|
|
Collectors.toMap(
|
|
entry -> Integer.valueOf(entry.getKey()), Entry::getValue)));
|
|
} catch (Throwable e) {
|
|
Grasscutter.getLogger()
|
|
.error(
|
|
"Error while loading Quest Share Config: {}", path.getFileName().toString());
|
|
}
|
|
});
|
|
stream.close();
|
|
} catch (IOException e) {
|
|
Grasscutter.getLogger().error("Error loading Quest Share Config: no files found");
|
|
return;
|
|
}
|
|
if (GameData.getTeleportDataMap() == null
|
|
|| GameData.getTeleportDataMap().isEmpty()
|
|
|| GameData.getRewindDataMap() == null
|
|
|| GameData.getRewindDataMap().isEmpty()) {
|
|
Grasscutter.getLogger().error("No Quest Share Config loaded!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private static void loadGadgetMappings() {
|
|
try {
|
|
val gadgetMap = GameData.getGadgetMappingMap();
|
|
try {
|
|
JsonUtils.loadToList(getResourcePath("Server/GadgetMapping.json"), GadgetMapping.class)
|
|
.forEach(entry -> gadgetMap.put(entry.getGadgetId(), entry));
|
|
} catch (IOException | NullPointerException ignored) {
|
|
}
|
|
Grasscutter.getLogger().debug("Loaded {} gadget mappings.", gadgetMap.size());
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger().error("Unable to load gadget mappings.", e);
|
|
}
|
|
}
|
|
|
|
private static void loadActivityCondGroups() {
|
|
try {
|
|
val gadgetMap = GameData.getActivityCondGroupMap();
|
|
try {
|
|
JsonUtils.loadToList(
|
|
getResourcePath("Server/ActivityCondGroups.json"), ActivityCondGroup.class)
|
|
.forEach(entry -> gadgetMap.put(entry.getCondGroupId(), entry));
|
|
} catch (IOException | NullPointerException ignored) {
|
|
}
|
|
Grasscutter.getLogger().debug("Loaded {} ActivityCondGroups.", gadgetMap.size());
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger().error("Unable to load ActivityCondGroups.", e);
|
|
}
|
|
}
|
|
|
|
private static void loadTrialAvatarCustomData() {
|
|
try {
|
|
String pathName = "CustomResources/TrialAvatarExcels/";
|
|
try {
|
|
JsonUtils.loadToList(
|
|
getResourcePath(pathName + "TrialAvatarActivityDataExcelConfigData.json"),
|
|
TrialAvatarActivityDataData.class)
|
|
.forEach(
|
|
instance -> {
|
|
instance.onLoad();
|
|
GameData.getTrialAvatarActivityDataCustomData()
|
|
.put(instance.getTrialAvatarIndexId(), instance);
|
|
});
|
|
} catch (IOException | NullPointerException ignored) {
|
|
}
|
|
Grasscutter.getLogger().debug("Loaded trial activity custom data.");
|
|
try {
|
|
JsonUtils.loadToList(
|
|
getResourcePath(pathName + "TrialAvatarActivityExcelConfigData.json"),
|
|
TrialAvatarActivityCustomData.class)
|
|
.forEach(
|
|
instance -> {
|
|
instance.onLoad();
|
|
GameData.getTrialAvatarActivityCustomData()
|
|
.put(instance.getScheduleId(), instance);
|
|
});
|
|
} catch (IOException | NullPointerException ignored) {
|
|
}
|
|
Grasscutter.getLogger().debug("Loaded trial activity schedule custom data.");
|
|
try {
|
|
JsonUtils.loadToList(
|
|
getResourcePath(pathName + "TrialAvatarData.json"), TrialAvatarCustomData.class)
|
|
.forEach(
|
|
instance -> {
|
|
instance.onLoad();
|
|
GameData.getTrialAvatarCustomData().put(instance.getTrialAvatarId(), instance);
|
|
});
|
|
} catch (IOException | NullPointerException ignored) {
|
|
}
|
|
Grasscutter.getLogger().debug("Loaded trial avatar custom data.");
|
|
} catch (Exception e) {
|
|
Grasscutter.getLogger().error("Unable to load trial avatar custom data.", e);
|
|
}
|
|
}
|
|
|
|
private static void loadGroupReplacements() {
|
|
Bindings bindings = ScriptLoader.getEngine().createBindings();
|
|
|
|
CompiledScript cs = ScriptLoader.getScript("Scene/groups_replacement.lua");
|
|
if (cs == null) {
|
|
Grasscutter.getLogger().error("Error while loading Group Replacements: file not found");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
cs.eval(bindings);
|
|
// these are Map<String, class>
|
|
var replacementsMap =
|
|
ScriptLoader.getSerializer()
|
|
.toMap(GroupReplacementData.class, bindings.get("replacements"));
|
|
// convert them to Map<Integer, class> and cache
|
|
GameData.getGroupReplacements()
|
|
.putAll(
|
|
replacementsMap.entrySet().stream()
|
|
.collect(
|
|
Collectors.toMap(
|
|
entry -> Integer.valueOf(entry.getValue().getId()), Entry::getValue)));
|
|
|
|
} catch (Throwable e) {
|
|
Grasscutter.getLogger().error("Error while loading Group Replacements");
|
|
}
|
|
|
|
if (GameData.getGroupReplacements() == null || GameData.getGroupReplacements().isEmpty()) {
|
|
Grasscutter.getLogger().error("No Group Replacements loaded!");
|
|
} else {
|
|
Grasscutter.getLogger()
|
|
.debug("Loaded {} group replacements.", GameData.getGroupReplacements().size());
|
|
}
|
|
}
|
|
|
|
// private static HashSet<String> modifierActionTypes = new HashSet<>();
|
|
public static class AbilityConfigData {
|
|
public AbilityData Default;
|
|
}
|
|
|
|
public static class AvatarConfig {
|
|
@SerializedName(
|
|
value = "abilities",
|
|
alternate = {"targetAbilities"})
|
|
public ArrayList<AvatarConfigAbility> abilities;
|
|
}
|
|
|
|
// BinOutput configs
|
|
|
|
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;
|
|
}
|
|
|
|
public class ScenePointConfig { // Sadly this doesn't work as a local class in loadScenePoints()
|
|
public Map<Integer, PointData> points;
|
|
}
|
|
}
|