package emu.grasscutter.data; import java.io.File; import java.io.FileReader; import java.util.*; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import emu.grasscutter.utils.Utils; import org.reflections.Reflections; import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.common.PointData; import emu.grasscutter.data.common.ScenePointConfig; import emu.grasscutter.data.custom.AbilityEmbryoEntry; import emu.grasscutter.data.custom.OpenConfigEntry; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; public class ResourceLoader { public static List> getResourceDefClasses() { Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName()); Set classes = reflections.getSubTypesOf(GameResource.class); List> 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; } public static void loadAll() { // Load ability lists loadAbilityEmbryos(); loadOpenConfig(); // Load resources loadResources(); loadScenePoints(); // Process into depots GameDepot.load(); // Load spawn data loadSpawnData(); // Custom - TODO move this somewhere else try { GameData.getAvatarSkillDepotDataMap().get(504).setAbilities( new AbilityEmbryoEntry( "", new String[] { "Avatar_PlayerBoy_ExtraAttack_Wind", "Avatar_Player_UziExplode_Mix", "Avatar_Player_UziExplode", "Avatar_Player_UziExplode_Strike_01", "Avatar_Player_UziExplode_Strike_02", "Avatar_Player_WindBreathe", "Avatar_Player_WindBreathe_CameraController" } )); GameData.getAvatarSkillDepotDataMap().get(704).setAbilities( new AbilityEmbryoEntry( "", new String[] { "Avatar_PlayerGirl_ExtraAttack_Wind", "Avatar_Player_UziExplode_Mix", "Avatar_Player_UziExplode", "Avatar_Player_UziExplode_Strike_01", "Avatar_Player_UziExplode_Strike_02", "Avatar_Player_WindBreathe", "Avatar_Player_WindBreathe_CameraController" } )); } catch (Exception e) { Grasscutter.getLogger().error("Error loading abilities", e); } } public static void loadResources() { for (Class resourceDefinition : getResourceDefClasses()) { ResourceType type = resourceDefinition.getAnnotation(ResourceType.class); if (type == null) { continue; } @SuppressWarnings("rawtypes") Int2ObjectMap map = GameData.getMapByResourceDef(resourceDefinition); if (map == null) { continue; } try { loadFromResource(resourceDefinition, type, map); } catch (Exception e) { Grasscutter.getLogger().error("Error loading resource file: " + Arrays.toString(type.name()), e); } } } @SuppressWarnings("rawtypes") protected static void loadFromResource(Class c, ResourceType type, Int2ObjectMap map) throws Exception { for (String name : type.name()) { loadFromResource(c, name, map); } Grasscutter.getLogger().info("Loaded " + map.size() + " " + c.getSimpleName() + "s."); } @SuppressWarnings({"rawtypes", "unchecked"}) protected static void loadFromResource(Class c, String fileName, Int2ObjectMap map) throws Exception { try (FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName)) { List list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, c).getType()); for (Object o : list) { GameResource res = (GameResource) o; res.onLoad(); map.put(res.getId(), res); } } } private static void loadScenePoints() { Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)"); File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Scene/Point"); if (!folder.isDirectory() || !folder.exists() || folder.listFiles() == null) { Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!"); return; } List scenePointList = new ArrayList<>(); for (File file : Objects.requireNonNull(folder.listFiles())) { ScenePointConfig config = null; Integer sceneId = null; Matcher matcher = pattern.matcher(file.getName()); if (matcher.find()) { sceneId = Integer.parseInt(matcher.group(1)); } else { continue; } try (FileReader fileReader = new FileReader(file)) { config = Grasscutter.getGsonFactory().fromJson(fileReader, ScenePointConfig.class); } catch (Exception e) { e.printStackTrace(); continue; } if (config.points == null) { continue; } for (Map.Entry entry : config.points.entrySet()) { PointData pointData = Grasscutter.getGsonFactory().fromJson(entry.getValue(), PointData.class); ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData); scenePointList.add(sl); } for (ScenePointEntry entry : scenePointList) { GameData.getScenePointEntries().put(entry.getName(), entry); } } } private static void loadAbilityEmbryos() { // Read from cached file if exists File embryoCache = new File(Grasscutter.getConfig().DATA_FOLDER + "AbilityEmbryos.json"); List embryoList = null; if (embryoCache.exists()) { // Load from cache try (FileReader fileReader = new FileReader(embryoCache)) { embryoList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityEmbryoEntry.class).getType()); } catch (Exception e) { e.printStackTrace(); } } else { // Load from BinOutput Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)"); embryoList = new LinkedList<>(); File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Avatar/")); File[] files = folder.listFiles(); if(files == null) { Grasscutter.getLogger().error("Error loading ability embryos: no files found in " + folder.getAbsolutePath()); return; } for (File file : files) { AvatarConfig config; String avatarName; Matcher matcher = pattern.matcher(file.getName()); if (matcher.find()) { avatarName = matcher.group(0); } else { continue; } try (FileReader fileReader = new FileReader(file)) { config = Grasscutter.getGsonFactory().fromJson(fileReader, AvatarConfig.class); } catch (Exception e) { e.printStackTrace(); continue; } if (config.abilities == null) { continue; } int s = config.abilities.size(); AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s])); embryoList.add(al); } } 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 loadSpawnData() { // Read from cached file if exists File spawnDataEntries = new File(Grasscutter.getConfig().DATA_FOLDER + "Spawns.json"); List spawnEntryList = null; if (spawnDataEntries.exists()) { // Load from cache try (FileReader fileReader = new FileReader(spawnDataEntries)) { spawnEntryList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType()); } catch (Exception e) { e.printStackTrace(); } } if (spawnEntryList == null || spawnEntryList.isEmpty()) { Grasscutter.getLogger().error("No spawn data loaded!"); return; } for (SpawnGroupEntry entry : spawnEntryList) { entry.getSpawns().stream().forEach(s -> { s.setGroup(entry); }); GameDepot.getSpawnListById(entry.getSceneId()).insert(entry, entry.getPos().getX(), entry.getPos().getZ()); } } private static void loadOpenConfig() { // Read from cached file if exists File openConfigCache = new File(Grasscutter.getConfig().DATA_FOLDER + "OpenConfig.json"); List list = null; if (openConfigCache.exists()) { try (FileReader fileReader = new FileReader(openConfigCache)) { list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, OpenConfigEntry.class).getType()); } catch (Exception e) { e.printStackTrace(); } } else { Map map = new TreeMap<>(); java.lang.reflect.Type type = new TypeToken>() {}.getType(); String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"}; for (String name : folderNames) { File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + name)); File[] files = folder.listFiles(); if(files == null) { Grasscutter.getLogger().error("Error loading open config: no files found in " + folder.getAbsolutePath()); return; } for (File file : files) { if (!file.getName().endsWith(".json")) { continue; } Map config; try (FileReader fileReader = new FileReader(file)) { config = Grasscutter.getGsonFactory().fromJson(fileReader, type); } catch (Exception e) { e.printStackTrace(); continue; } for (Entry e : config.entrySet()) { List abilityList = new ArrayList<>(); int extraTalentIndex = 0; for (OpenConfigData entry : e.getValue()) { if (entry.$type.contains("AddAbility")) { abilityList.add(entry.abilityName); } else if (entry.talentIndex > 0) { extraTalentIndex = entry.talentIndex; } } OpenConfigEntry entry = new OpenConfigEntry(e.getKey(), abilityList, extraTalentIndex); map.put(entry.getName(), entry); } } } 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); } } // BinOutput configs private static class AvatarConfig { public ArrayList abilities; private static class AvatarConfigAbility { public String abilityName; public String toString() { return abilityName; } } } private static class OpenConfig { public OpenConfigData[] data; } private static class OpenConfigData { public String $type; public String abilityName; public int talentIndex; } }