Apply additional data from specific main quest lines

This commit is contained in:
KingRainbow44 2023-04-24 01:44:58 -04:00
parent 0d680a6310
commit 1a8d7e901a
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
4 changed files with 258 additions and 243 deletions

View File

@ -1,153 +1,153 @@
package emu.grasscutter.data; package emu.grasscutter.data;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.http.handlers.GachaHandler; import emu.grasscutter.server.http.handlers.GachaHandler;
import emu.grasscutter.tools.Tools; import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils; import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.TsvUtils; import emu.grasscutter.utils.TsvUtils;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.val; import lombok.val;
public class DataLoader { public class DataLoader {
/** /**
* Load a data file by its name. If the file isn't found within the /data directory then it will * Load a data file by its name. If the file isn't found within the /data directory then it will
* fallback to the default within the jar resources * fallback to the default within the jar resources
* *
* @param resourcePath The path to the data file to be loaded. * @param resourcePath The path to the data file to be loaded.
* @return InputStream of the data file. * @return InputStream of the data file.
* @throws FileNotFoundException * @throws FileNotFoundException
* @see #load(String, boolean) * @see #load(String, boolean)
*/ */
public static InputStream load(String resourcePath) throws FileNotFoundException { public static InputStream load(String resourcePath) throws FileNotFoundException {
return load(resourcePath, true); return load(resourcePath, true);
} }
/** /**
* Creates an input stream reader for a data file. If the file isn't found within the /data * Creates an input stream reader for a data file. If the file isn't found within the /data
* directory then it will fallback to the default within the jar resources * directory then it will fallback to the default within the jar resources
* *
* @param resourcePath The path to the data file to be loaded. * @param resourcePath The path to the data file to be loaded.
* @return InputStreamReader of the data file. * @return InputStreamReader of the data file.
* @throws IOException * @throws IOException
* @throws FileNotFoundException * @throws FileNotFoundException
* @see #load(String, boolean) * @see #load(String, boolean)
*/ */
public static InputStreamReader loadReader(String resourcePath) public static InputStreamReader loadReader(String resourcePath)
throws IOException, FileNotFoundException { throws IOException, FileNotFoundException {
try { try {
InputStream is = load(resourcePath, true); InputStream is = load(resourcePath, true);
return new InputStreamReader(is); return new InputStreamReader(is);
} catch (FileNotFoundException exception) { } catch (FileNotFoundException exception) {
throw exception; throw exception;
} }
} }
/** /**
* Load a data file by its name. * Load a data file by its name.
* *
* @param resourcePath The path to the data file to be loaded. * @param resourcePath The path to the data file to be loaded.
* @param useFallback If the file does not exist in the /data directory, should it use the default * @param useFallback If the file does not exist in the /data directory, should it use the default
* file in the jar? * file in the jar?
* @return InputStream of the data file. * @return InputStream of the data file.
* @throws FileNotFoundException * @throws FileNotFoundException
*/ */
public static InputStream load(String resourcePath, boolean useFallback) public static InputStream load(String resourcePath, boolean useFallback)
throws FileNotFoundException { throws FileNotFoundException {
Path path = Path path =
useFallback ? FileUtils.getDataPath(resourcePath) : FileUtils.getDataUserPath(resourcePath); useFallback ? FileUtils.getDataPath(resourcePath) : FileUtils.getDataUserPath(resourcePath);
if (Files.exists(path)) { if (Files.exists(path)) {
// Data is in the resource directory // Data is in the resource directory
try { try {
return Files.newInputStream(path); return Files.newInputStream(path);
} catch (IOException e) { } catch (IOException e) {
throw new FileNotFoundException( throw new FileNotFoundException(
e.getMessage()); // This is evil but so is changing the function signature at this point e.getMessage()); // This is evil but so is changing the function signature at this point
} }
} }
return null; return null;
} }
public static <T> T loadClass(String resourcePath, Class<T> classType) throws IOException { public static <T> T loadClass(String resourcePath, Class<T> classType) throws IOException {
try (InputStreamReader reader = loadReader(resourcePath)) { try (InputStreamReader reader = loadReader(resourcePath)) {
return JsonUtils.loadToClass(reader, classType); return JsonUtils.loadToClass(reader, classType);
} }
} }
public static <T> List<T> loadList(String resourcePath, Class<T> classType) throws IOException { public static <T> List<T> loadList(String resourcePath, Class<T> classType) throws IOException {
try (InputStreamReader reader = loadReader(resourcePath)) { try (var reader = loadReader(resourcePath)) {
return JsonUtils.loadToList(reader, classType); return JsonUtils.loadToList(reader, classType);
} }
} }
public static <T1, T2> Map<T1, T2> loadMap( public static <T1, T2> Map<T1, T2> loadMap(
String resourcePath, Class<T1> keyType, Class<T2> valueType) throws IOException { String resourcePath, Class<T1> keyType, Class<T2> valueType) throws IOException {
try (InputStreamReader reader = loadReader(resourcePath)) { try (InputStreamReader reader = loadReader(resourcePath)) {
return JsonUtils.loadToMap(reader, keyType, valueType); return JsonUtils.loadToMap(reader, keyType, valueType);
} }
} }
public static <T> List<T> loadTableToList(String resourcePath, Class<T> classType) public static <T> List<T> loadTableToList(String resourcePath, Class<T> classType)
throws IOException { throws IOException {
val path = FileUtils.getDataPathTsjJsonTsv(resourcePath); val path = FileUtils.getDataPathTsjJsonTsv(resourcePath);
Grasscutter.getLogger().debug("Loading data table from: " + path); Grasscutter.getLogger().debug("Loading data table from: " + path);
return switch (FileUtils.getFileExtension(path)) { return switch (FileUtils.getFileExtension(path)) {
case "json" -> JsonUtils.loadToList(path, classType); case "json" -> JsonUtils.loadToList(path, classType);
case "tsj" -> TsvUtils.loadTsjToListSetField(path, classType); case "tsj" -> TsvUtils.loadTsjToListSetField(path, classType);
case "tsv" -> TsvUtils.loadTsvToListSetField(path, classType); case "tsv" -> TsvUtils.loadTsvToListSetField(path, classType);
default -> null; default -> null;
}; };
} }
public static void checkAllFiles() { public static void checkAllFiles() {
try { try {
List<Path> filenames = FileUtils.getPathsFromResource("/defaults/data/"); List<Path> filenames = FileUtils.getPathsFromResource("/defaults/data/");
if (filenames == null) { if (filenames == null) {
Grasscutter.getLogger().error("We were unable to locate your default data files."); Grasscutter.getLogger().error("We were unable to locate your default data files.");
} // else for (Path file : filenames) { } // else for (Path file : filenames) {
// String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1]; // String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1];
// checkAndCopyData(relativePath); // checkAndCopyData(relativePath);
// } // }
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e); Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e);
} }
generateGachaMappings(); generateGachaMappings();
} }
private static void checkAndCopyData(String name) { private static void checkAndCopyData(String name) {
// TODO: Revisit this if default dumping is ever reintroduced // TODO: Revisit this if default dumping is ever reintroduced
Path filePath = FileUtils.getDataPath(name); Path filePath = FileUtils.getDataPath(name);
if (!Files.exists(filePath)) { if (!Files.exists(filePath)) {
var root = filePath.getParent(); var root = filePath.getParent();
if (root.toFile().mkdirs()) if (root.toFile().mkdirs())
Grasscutter.getLogger().info("Created data folder '" + root + "'"); Grasscutter.getLogger().info("Created data folder '" + root + "'");
Grasscutter.getLogger().debug("Creating default '" + name + "' data"); Grasscutter.getLogger().debug("Creating default '" + name + "' data");
FileUtils.copyResource("/defaults/data/" + name, filePath.toString()); FileUtils.copyResource("/defaults/data/" + name, filePath.toString());
} }
} }
private static void generateGachaMappings() { private static void generateGachaMappings() {
var path = GachaHandler.getGachaMappingsPath(); var path = GachaHandler.getGachaMappingsPath();
if (!Files.exists(path)) { if (!Files.exists(path)) {
try { try {
Grasscutter.getLogger().debug("Creating default '" + path + "' data"); Grasscutter.getLogger().debug("Creating default '" + path + "' data");
Tools.createGachaMappings(path); Tools.createGachaMappings(path);
} catch (Exception exception) { } catch (Exception exception) {
Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception); Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception);
} }
} }
} }
} }

View File

@ -489,17 +489,15 @@ public final class ResourceLoader {
} }
private static void loadQuests() { private static void loadQuests() {
try { try (var files = Files.list(getResourcePath("BinOutput/Quest/"))) {
Files.list(getResourcePath("BinOutput/Quest/")) files.forEach(path -> {
.forEach( try {
path -> { val mainQuest = JsonUtils.loadToClass(path, MainQuestData.class);
try { GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
val mainQuest = JsonUtils.loadToClass(path, MainQuestData.class);
GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
} catch (IOException e) {
} mainQuest.onLoad(); // Load the quest data.
}); } catch (IOException ignored) { }
});
} catch (IOException e) { } catch (IOException e) {
Grasscutter.getLogger().error("Quest data missing"); Grasscutter.getLogger().error("Quest data missing");
return; return;
@ -507,17 +505,19 @@ public final class ResourceLoader {
try { try {
val questEncryptionMap = GameData.getMainQuestEncryptionMap(); val questEncryptionMap = GameData.getMainQuestEncryptionMap();
String path = "QuestEncryptionKeys.json"; var path = "QuestEncryptionKeys.json";
try { try {
JsonUtils.loadToList(getResourcePath(path), QuestEncryptionKey.class) JsonUtils.loadToList(getResourcePath(path), QuestEncryptionKey.class)
.forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key)); .forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key));
} catch (IOException | NullPointerException ignored) { } catch (IOException | NullPointerException ignored) {
} }
try { try {
DataLoader.loadList(path, QuestEncryptionKey.class) DataLoader.loadList(path, QuestEncryptionKey.class)
.forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key)); .forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key));
} catch (IOException | NullPointerException ignored) { } catch (IOException | NullPointerException ignored) {
} }
Grasscutter.getLogger().debug("Loaded {} quest keys.", questEncryptionMap.size()); Grasscutter.getLogger().debug("Loaded {} quest keys.", questEncryptionMap.size());
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Unable to load quest keys.", e); Grasscutter.getLogger().error("Unable to load quest keys.", e);

View File

@ -1,78 +1,87 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import emu.grasscutter.game.quest.enums.QuestType; import emu.grasscutter.data.GameData;
import java.util.List; import emu.grasscutter.game.quest.enums.QuestType;
import java.util.Objects;
import lombok.Data; import java.util.Arrays;
import java.util.List;
public class MainQuestData { import java.util.Objects;
private int id; import lombok.Data;
private int ICLLDPJFIMA;
private int series; public class MainQuestData {
private QuestType type; private int id;
private int ICLLDPJFIMA;
private long titleTextMapHash; private int series;
private int[] suggestTrackMainQuestList; private QuestType type;
private int[] rewardIdList;
private long titleTextMapHash;
private SubQuestData[] subQuests; private int[] suggestTrackMainQuestList;
private List<TalkData> talks; private int[] rewardIdList;
private long[] preloadLuaList;
private SubQuestData[] subQuests;
public int getId() { private List<TalkData> talks;
return id; private long[] preloadLuaList;
}
public int getId() {
public int getSeries() { return id;
return series; }
}
public int getSeries() {
public QuestType getType() { return series;
return type; }
}
public QuestType getType() {
public long getTitleTextMapHash() { return type;
return titleTextMapHash; }
}
public long getTitleTextMapHash() {
public int[] getSuggestTrackMainQuestList() { return titleTextMapHash;
return suggestTrackMainQuestList; }
}
public int[] getSuggestTrackMainQuestList() {
public int[] getRewardIdList() { return suggestTrackMainQuestList;
return rewardIdList; }
}
public int[] getRewardIdList() {
public SubQuestData[] getSubQuests() { return rewardIdList;
return subQuests; }
}
public SubQuestData[] getSubQuests() {
public List<TalkData> getTalks() { return subQuests;
return talks; }
}
public List<TalkData> getTalks() {
public void onLoad() { return talks;
this.talks = talks.stream().filter(Objects::nonNull).toList(); }
}
public void onLoad() {
@Data this.talks = talks.stream().filter(Objects::nonNull).toList();
public static class SubQuestData { Arrays.stream(this.subQuests).forEach(quest -> {
private int subId; var questData = GameData.getQuestDataMap().get(quest.getSubId());
private int order; if (questData != null) questData.applyFrom(quest);
} });
}
@Data
@Entity @Data
public static class TalkData { public static class SubQuestData {
private int id; private int subId;
private String heroTalk; private int order;
private boolean isRewind;
public TalkData() {} private boolean finishParent;
}
public TalkData(int id, String heroTalk) {
this.id = id; @Data
this.heroTalk = heroTalk; @Entity
} public static class TalkData {
} private int id;
} private String heroTalk;
public TalkData() {}
public TalkData(int id, String heroTalk) {
this.id = id;
this.heroTalk = heroTalk;
}
}
}

View File

@ -5,6 +5,7 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.binout.MainQuestData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.quest.enums.*; import emu.grasscutter.game.quest.enums.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -69,7 +70,12 @@ public class QuestData extends GameResource {
if (this.gainItems == null) this.gainItems = Collections.emptyList(); if (this.gainItems == null) this.gainItems = Collections.emptyList();
addToCache(); this.addToCache();
}
public void applyFrom(MainQuestData.SubQuestData additionalData) {
this.isRewind = additionalData.isRewind();
this.finishParent = additionalData.isFinishParent();
} }
private void addToCache() { private void addToCache() {