mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-01-10 17:33:02 +08:00
Update GM Handbook with Spincrystal names and Monster strings
This commit is contained in:
parent
4da4c03ecc
commit
f801fe0305
@ -11,7 +11,11 @@ import java.nio.file.Path;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import emu.grasscutter.GameConstants;
|
import emu.grasscutter.GameConstants;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
@ -21,37 +25,53 @@ import emu.grasscutter.data.GameData;
|
|||||||
import emu.grasscutter.data.ResourceLoader;
|
import emu.grasscutter.data.ResourceLoader;
|
||||||
import emu.grasscutter.data.excels.AvatarData;
|
import emu.grasscutter.data.excels.AvatarData;
|
||||||
import emu.grasscutter.data.excels.ItemData;
|
import emu.grasscutter.data.excels.ItemData;
|
||||||
import emu.grasscutter.data.excels.QuestData;
|
|
||||||
import emu.grasscutter.utils.Language;
|
import emu.grasscutter.utils.Language;
|
||||||
import emu.grasscutter.utils.Language.TextStrings;
|
import emu.grasscutter.utils.Language.TextStrings;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntRBTreeMap;
|
import it.unimi.dsi.fastutil.ints.Int2IntRBTreeMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntList;
|
||||||
|
import lombok.val;
|
||||||
|
|
||||||
import static emu.grasscutter.config.Configuration.*;
|
import static emu.grasscutter.config.Configuration.*;
|
||||||
import static emu.grasscutter.utils.FileUtils.getResourcePath;
|
import static emu.grasscutter.utils.FileUtils.getResourcePath;
|
||||||
|
|
||||||
public final class Tools {
|
public final class Tools {
|
||||||
public static void createGmHandbooks() throws Exception {
|
public static void createGmHandbooks() throws Exception {
|
||||||
final List<Language> languages = Language.TextStrings.getLanguages();
|
val languages = Language.TextStrings.getLanguages();
|
||||||
final Int2ObjectMap<TextStrings> textMaps = Language.getTextMapStrings();
|
val textMaps = Language.getTextMapStrings();
|
||||||
|
|
||||||
ResourceLoader.loadAll();
|
ResourceLoader.loadAll();
|
||||||
final Int2IntSortedMap avatarNames = new Int2IntRBTreeMap(GameData.getAvatarDataMap().int2ObjectEntrySet().stream().collect(Collectors.toMap(e -> (int) e.getIntKey(), e -> (int) e.getValue().getNameTextMapHash())));
|
val mainQuestTitles = new Int2IntRBTreeMap(GameData.getMainQuestDataMap().int2ObjectEntrySet().stream().collect(Collectors.toMap(e -> (int) e.getIntKey(), e -> (int) e.getValue().getTitleTextMapHash())));
|
||||||
final Int2IntSortedMap itemNames = new Int2IntRBTreeMap(GameData.getItemDataMap().int2ObjectEntrySet().stream().collect(Collectors.toMap(e -> (int) e.getIntKey(), e -> (int) e.getValue().getNameTextMapHash())));
|
// val questDescs = new Int2IntRBTreeMap(GameData.getQuestDataMap().int2ObjectEntrySet().stream().collect(Collectors.toMap(e -> (int) e.getIntKey(), e -> (int) e.getValue().getDescTextMapHash())));
|
||||||
final Int2IntSortedMap monsterNames = new Int2IntRBTreeMap(GameData.getMonsterDataMap().int2ObjectEntrySet().stream().collect(Collectors.toMap(e -> (int) e.getIntKey(), e -> (int) e.getValue().getNameTextMapHash())));
|
|
||||||
final Int2IntSortedMap mainQuestTitles = new Int2IntRBTreeMap(GameData.getMainQuestDataMap().int2ObjectEntrySet().stream().collect(Collectors.toMap(e -> (int) e.getIntKey(), e -> (int) e.getValue().getTitleTextMapHash())));
|
val avatarDataMap = new Int2ObjectRBTreeMap<>(GameData.getAvatarDataMap());
|
||||||
// Int2IntSortedMap questDescs = new Int2IntRBTreeMap(GameData.getQuestDataMap().int2ObjectEntrySet().stream().collect(Collectors.toMap(e -> (int) e.getIntKey(), e -> (int) e.getValue().getDescTextMapHash())));
|
val itemDataMap = new Int2ObjectRBTreeMap<>(GameData.getItemDataMap());
|
||||||
|
val monsterDataMap = new Int2ObjectRBTreeMap<>(GameData.getMonsterDataMap());
|
||||||
|
val sceneDataMap = new Int2ObjectRBTreeMap<>(GameData.getSceneDataMap());
|
||||||
|
val questDataMap = new Int2ObjectRBTreeMap<>(GameData.getQuestDataMap());
|
||||||
|
|
||||||
|
Function<SortedMap, String> getPad = m -> "%" + m.lastKey().toString().length() + "s : ";
|
||||||
|
|
||||||
|
// Create builders and helper functions
|
||||||
|
val handbookBuilders = IntStream.range(0, TextStrings.NUM_LANGUAGES).mapToObj(i -> new StringBuilder()).toList();
|
||||||
|
Consumer<String> newSection = title -> handbookBuilders.forEach(b -> b.append("\n\n// " + title + "\n"));
|
||||||
|
Consumer<String> newLine = line -> handbookBuilders.forEach(b -> b.append(line + "\n"));
|
||||||
|
BiConsumer<String, IntList> newTranslatedLine = (template, textmapHashes) -> {
|
||||||
|
val textstrings = textmapHashes.intStream().mapToObj(textMaps::get).toList();
|
||||||
|
for (int i = 0; i < TextStrings.NUM_LANGUAGES; i++) {
|
||||||
|
String s = template;
|
||||||
|
for (int j = 0; j < textstrings.size(); j++)
|
||||||
|
s = s.replace("{"+j+"}", textstrings.get(j).strings[i]);
|
||||||
|
handbookBuilders.get(i).append(s + "\n");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Preamble
|
// Preamble
|
||||||
final List<StringBuilder> handbookBuilders = new ArrayList<>(TextStrings.NUM_LANGUAGES);
|
newLine.accept("// Grasscutter " + GameConstants.VERSION + " GM Handbook");
|
||||||
final String now = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now());
|
newLine.accept("// Created " + DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now()));
|
||||||
for (int i = 0; i < TextStrings.NUM_LANGUAGES; i++)
|
|
||||||
handbookBuilders.add(new StringBuilder()
|
|
||||||
.append("// Grasscutter " + GameConstants.VERSION + " GM Handbook\n")
|
|
||||||
.append("// Created " + now + "\n\n")
|
|
||||||
.append("// Commands\n"));
|
|
||||||
// Commands
|
// Commands
|
||||||
|
newSection.accept("Commands");
|
||||||
final List<CommandHandler> cmdList = CommandMap.getInstance().getHandlersAsList();
|
final List<CommandHandler> cmdList = CommandMap.getInstance().getHandlersAsList();
|
||||||
final String padCmdLabel = "%" + cmdList.stream().map(CommandHandler::getLabel).map(String::length).max(Integer::compare).get().toString() + "s : ";
|
final String padCmdLabel = "%" + cmdList.stream().map(CommandHandler::getLabel).map(String::length).max(Integer::compare).get().toString() + "s : ";
|
||||||
for (CommandHandler cmd : cmdList) {
|
for (CommandHandler cmd : cmdList) {
|
||||||
@ -62,42 +82,52 @@ public final class Tools {
|
|||||||
handbookBuilders.get(i).append(label + desc + "\n");
|
handbookBuilders.get(i).append(label + desc + "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Avatars, Items, Monsters
|
// Avatars
|
||||||
final String[] handbookSections = {"Avatars", "Items", "Monsters"};
|
newSection.accept("Avatars");
|
||||||
final Int2IntSortedMap[] handbookNames = {avatarNames, itemNames, monsterNames};
|
val avatarPre = getPad.apply(avatarDataMap);
|
||||||
for (int section = 0; section < handbookSections.length; section++) {
|
avatarDataMap.forEach((id, data) -> newTranslatedLine.accept(avatarPre.formatted(id) + "{0}", IntList.of((int) data.getNameTextMapHash())));
|
||||||
final var h = handbookNames[section];
|
// Items
|
||||||
final String s = "\n\n// " + handbookSections[section] + "\n";
|
newSection.accept("Items");
|
||||||
handbookBuilders.forEach(b -> b.append(s));
|
val itemPre = getPad.apply(itemDataMap);
|
||||||
final String padId = "%" + Integer.toString(h.keySet().lastInt()).length() + "s : ";
|
itemDataMap.forEach((id, data) -> {
|
||||||
h.forEach((id, hash) -> {
|
val nameHash = (int) data.getNameTextMapHash();
|
||||||
final String sId = padId.formatted(id);
|
switch (data.getMaterialType()) {
|
||||||
final TextStrings t = textMaps.get((int) hash);
|
case MATERIAL_BGM:
|
||||||
for (int i = 0; i < TextStrings.NUM_LANGUAGES; i++)
|
val bgmNameHash = Optional.ofNullable(data.getItemUse())
|
||||||
handbookBuilders.get(i).append(sId + t.strings[i] + "\n");
|
.map(u -> u.get(0))
|
||||||
});
|
.map(u -> u.getUseParam())
|
||||||
|
.filter(u -> u.length > 0)
|
||||||
|
.map(u -> Integer.parseInt(u[0]))
|
||||||
|
.map(bgmId -> GameData.getHomeWorldBgmDataMap().get(bgmId))
|
||||||
|
.map(bgm -> bgm.getBgmNameTextMapHash());
|
||||||
|
if (bgmNameHash.isPresent()) {
|
||||||
|
int trackNameHash = (int) ((long) bgmNameHash.get()); // Textmap hashes are u32, we index as i32 but store most of them as Long :')
|
||||||
|
if (textMaps.containsKey(trackNameHash)) {
|
||||||
|
newTranslatedLine.accept(itemPre.formatted(id) + "{0} - {1}", IntList.of(nameHash, trackNameHash));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
} // Fall-through
|
||||||
|
default:
|
||||||
|
newTranslatedLine.accept(itemPre.formatted(id) + "{0}", IntList.of(nameHash));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Monsters
|
||||||
|
newSection.accept("Monsters");
|
||||||
|
val monsterPre = getPad.apply(monsterDataMap);
|
||||||
|
monsterDataMap.forEach((id, data) -> newTranslatedLine.accept(
|
||||||
|
monsterPre.formatted(id) + data.getMonsterName() + " - {0}",
|
||||||
|
IntList.of((int) data.getNameTextMapHash())));
|
||||||
// Scenes - no translations
|
// Scenes - no translations
|
||||||
handbookBuilders.forEach(b -> b.append("\n\n// Scenes\n"));
|
newSection.accept("Scenes");
|
||||||
final var sceneDataMap = GameData.getSceneDataMap();
|
val padSceneId = getPad.apply(sceneDataMap);
|
||||||
final String padSceneId = "%" + Integer.toString(sceneDataMap.keySet().intStream().max().getAsInt()).length() + "d : ";
|
sceneDataMap.forEach((id, data) -> newLine.accept(padSceneId.formatted(id) + data.getScriptData()));
|
||||||
sceneDataMap.keySet().intStream().sorted().forEach(id -> {
|
|
||||||
final String sId = padSceneId.formatted(id);
|
|
||||||
final String data = sceneDataMap.get(id).getScriptData();
|
|
||||||
handbookBuilders.forEach(b -> b.append(sId + data + "\n"));
|
|
||||||
});
|
|
||||||
// Quests
|
// Quests
|
||||||
handbookBuilders.forEach(b -> b.append("\n\n// Quests\n"));
|
newSection.accept("Quests");
|
||||||
final var questDataMap = GameData.getQuestDataMap();
|
val padQuestId = getPad.apply(questDataMap);
|
||||||
final String padQuestId = "%" + Integer.toString(questDataMap.keySet().intStream().max().getAsInt()).length() + "d : ";
|
questDataMap.forEach((id, data) -> newTranslatedLine.accept(
|
||||||
questDataMap.keySet().intStream().sorted().forEach(id -> {
|
padQuestId.formatted(id) + "{0} - {1}",
|
||||||
final String sId = padQuestId.formatted(id);
|
IntList.of((int) mainQuestTitles.get(data.getMainId()), (int) data.getDescTextMapHash())));
|
||||||
final QuestData data = questDataMap.get(id);
|
|
||||||
final TextStrings title = textMaps.get((int) mainQuestTitles.get(data.getMainId()));
|
|
||||||
final TextStrings desc = textMaps.get((int) data.getDescTextMapHash());
|
|
||||||
for (int i = 0; i < TextStrings.NUM_LANGUAGES; i++)
|
|
||||||
handbookBuilders.get(i).append(sId + title.strings[i] + " - " + desc.strings[i] + "\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Write txt files
|
// Write txt files
|
||||||
for (int i = 0; i < TextStrings.NUM_LANGUAGES; i++) {
|
for (int i = 0; i < TextStrings.NUM_LANGUAGES; i++) {
|
||||||
|
@ -20,7 +20,6 @@ import static emu.grasscutter.utils.FileUtils.getResourcePath;
|
|||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
@ -46,6 +45,7 @@ public final class Language {
|
|||||||
|
|
||||||
private final String languageCode;
|
private final String languageCode;
|
||||||
private final Map<String, String> translations = new ConcurrentHashMap<>();
|
private final Map<String, String> translations = new ConcurrentHashMap<>();
|
||||||
|
private static boolean scannedTextmaps = false; // Ensure that we don't infinitely rescan on cache misses that don't exist
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a language instance from a code.
|
* Creates a language instance from a code.
|
||||||
@ -242,7 +242,7 @@ public final class Language {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int TEXTMAP_CACHE_VERSION = 0x9CCACE03;
|
private static final int TEXTMAP_CACHE_VERSION = 0x9CCACE04;
|
||||||
@EqualsAndHashCode public static class TextStrings implements Serializable {
|
@EqualsAndHashCode public static class TextStrings implements Serializable {
|
||||||
public static final String[] ARR_LANGUAGES = {"EN", "CHS", "CHT", "JP", "KR", "DE", "ES", "FR", "ID", "PT", "RU", "TH", "VI"};
|
public static final String[] ARR_LANGUAGES = {"EN", "CHS", "CHT", "JP", "KR", "DE", "ES", "FR", "ID", "PT", "RU", "TH", "VI"};
|
||||||
public static final String[] ARR_GC_LANGUAGES = {"en-US", "zh-CN", "zh-TW", "ja-JP", "ko-KR", "en-US", "es-ES", "fr-FR", "en-US", "en-US", "ru-RU", "en-US", "en-US"}; // TODO: Update the placeholder en-US entries if we ever add GC translations for the missing client languages
|
public static final String[] ARR_GC_LANGUAGES = {"en-US", "zh-CN", "zh-TW", "ja-JP", "ko-KR", "en-US", "es-ES", "fr-FR", "en-US", "en-US", "ru-RU", "en-US", "en-US"}; // TODO: Update the placeholder en-US entries if we ever add GC translations for the missing client languages
|
||||||
@ -385,9 +385,10 @@ public final class Language {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static TextStrings getTextMapKey(long hash) {
|
public static TextStrings getTextMapKey(long hash) {
|
||||||
if (textMapStrings == null)
|
int key = (int) hash;
|
||||||
|
if ((textMapStrings == null) || (!scannedTextmaps && !textMapStrings.containsKey(key)))
|
||||||
loadTextMaps();
|
loadTextMaps();
|
||||||
return textMapStrings.get((int) hash);
|
return textMapStrings.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadTextMaps() {
|
public static void loadTextMaps() {
|
||||||
@ -429,6 +430,7 @@ public final class Language {
|
|||||||
usedHashes.add((int) v.getDescTextMapHash());
|
usedHashes.add((int) v.getDescTextMapHash());
|
||||||
});
|
});
|
||||||
GameData.getItemDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash()));
|
GameData.getItemDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash()));
|
||||||
|
GameData.getHomeWorldBgmDataMap().forEach((k, v) -> usedHashes.add((int) v.getBgmNameTextMapHash()));
|
||||||
GameData.getMonsterDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash()));
|
GameData.getMonsterDataMap().forEach((k, v) -> usedHashes.add((int) v.getNameTextMapHash()));
|
||||||
GameData.getMainQuestDataMap().forEach((k, v) -> usedHashes.add((int) v.getTitleTextMapHash()));
|
GameData.getMainQuestDataMap().forEach((k, v) -> usedHashes.add((int) v.getTitleTextMapHash()));
|
||||||
GameData.getQuestDataMap().forEach((k, v) -> usedHashes.add((int) v.getDescTextMapHash()));
|
GameData.getQuestDataMap().forEach((k, v) -> usedHashes.add((int) v.getDescTextMapHash()));
|
||||||
@ -441,6 +443,7 @@ public final class Language {
|
|||||||
usedHashes.add((int) 2864268523L); // Weapon Event Wish
|
usedHashes.add((int) 2864268523L); // Weapon Event Wish
|
||||||
|
|
||||||
textMapStrings = loadTextMapFiles(usedHashes);
|
textMapStrings = loadTextMapFiles(usedHashes);
|
||||||
|
scannedTextmaps = true;
|
||||||
try {
|
try {
|
||||||
saveTextMapsCache(textMapStrings);
|
saveTextMapsCache(textMapStrings);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
Loading…
Reference in New Issue
Block a user