ResourceLoader.java 15.5 KB
Newer Older
Melledy's avatar
Melledy committed
1
2
package emu.grasscutter.data;

3
import java.io.*;
4
import java.lang.reflect.Type;
5
6
import java.nio.file.Files;
import java.nio.file.Path;
KingRainbow44's avatar
KingRainbow44 committed
7
import java.util.*;
Melledy's avatar
Melledy committed
8
9
10
11
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

12
import com.google.gson.Gson;
13
import emu.grasscutter.data.binout.*;
14
import emu.grasscutter.scripts.SceneIndexManager;
KingRainbow44's avatar
KingRainbow44 committed
15
import emu.grasscutter.utils.Utils;
16
import lombok.SneakyThrows;
Melledy's avatar
Melledy committed
17
18
import org.reflections.Reflections;

Yazawazi's avatar
Yazawazi committed
19
import com.google.gson.JsonElement;
20
import com.google.gson.annotations.SerializedName;
Melledy's avatar
Melledy committed
21
22
23
import com.google.gson.reflect.TypeToken;

import emu.grasscutter.Grasscutter;
Melledy's avatar
Melledy committed
24
25
26
import emu.grasscutter.data.binout.AbilityModifier.AbilityConfigData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierActionType;
Yazawazi's avatar
Yazawazi committed
27
28
import emu.grasscutter.data.common.PointData;
import emu.grasscutter.data.common.ScenePointConfig;
29
import emu.grasscutter.game.world.SpawnDataEntry.*;
Melledy's avatar
Melledy committed
30
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
31
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
Melledy's avatar
Melledy committed
32

33
import static emu.grasscutter.Configuration.*;
34
import static emu.grasscutter.utils.Language.translate;
35

Melledy's avatar
Melledy committed
36
37
public class ResourceLoader {

38
	private static final List<String> loadedResources = new ArrayList<>();
39

Melledy's avatar
Melledy committed
40
41
	public static List<Class<?>> getResourceDefClasses() {
		Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName());
42
		Set<?> classes = reflections.getSubTypesOf(GameResource.class);
Melledy's avatar
Melledy committed
43
44
45
46
47
48
49
50
51

		List<Class<?>> classList = new ArrayList<>(classes.size());
		classes.forEach(o -> {
			Class<?> c = (Class<?>) o;
			if (c.getAnnotation(ResourceType.class) != null) {
				classList.add(c);
			}
		});

KingRainbow44's avatar
KingRainbow44 committed
52
		classList.sort((a, b) -> b.getAnnotation(ResourceType.class).loadPriority().value() - a.getAnnotation(ResourceType.class).loadPriority().value());
Melledy's avatar
Melledy committed
53
54
55

		return classList;
	}
56

Melledy's avatar
Melledy committed
57
	public static void loadAll() {
58
59
        Grasscutter.getLogger().info(translate("messages.status.resources.loading"));

Melledy's avatar
Melledy committed
60
61
62
		// Load ability lists
		loadAbilityEmbryos();
		loadOpenConfig();
Melledy's avatar
Melledy committed
63
		loadAbilityModifiers();
Melledy's avatar
Melledy committed
64
65
66
		// Load resources
		loadResources();
		// Process into depots
67
		GameDepot.load();
Melledy's avatar
Melledy committed
68
		// Load spawn data and quests
Melledy's avatar
Melledy committed
69
		loadSpawnData();
Melledy's avatar
Melledy committed
70
		loadQuests();
71
72
		// Load scene points - must be done AFTER resources are loaded
		loadScenePoints();
73
74
		// Load default home layout
		loadHomeworldDefaultSaveData();
Akka's avatar
Akka committed
75
		loadNpcBornData();
76
77

        Grasscutter.getLogger().info(translate("messages.status.resources.finish"));
Melledy's avatar
Melledy committed
78
79
80
	}

	public static void loadResources() {
81
82
83
84
		loadResources(false);
	}

	public static void loadResources(boolean doReload) {
Melledy's avatar
Melledy committed
85
86
87
88
89
90
91
92
		for (Class<?> resourceDefinition : getResourceDefClasses()) {
			ResourceType type = resourceDefinition.getAnnotation(ResourceType.class);

			if (type == null) {
				continue;
			}

			@SuppressWarnings("rawtypes")
93
			Int2ObjectMap map = GameData.getMapByResourceDef(resourceDefinition);
Melledy's avatar
Melledy committed
94
95
96
97
98
99

			if (map == null) {
				continue;
			}

			try {
100
				loadFromResource(resourceDefinition, type, map, doReload);
Melledy's avatar
Melledy committed
101
			} catch (Exception e) {
KingRainbow44's avatar
KingRainbow44 committed
102
				Grasscutter.getLogger().error("Error loading resource file: " + Arrays.toString(type.name()), e);
Melledy's avatar
Melledy committed
103
104
105
			}
		}
	}
106

Melledy's avatar
Melledy committed
107
	@SuppressWarnings("rawtypes")
108
109
110
111
112
113
	protected static void loadFromResource(Class<?> c, ResourceType type, Int2ObjectMap map, boolean doReload) throws Exception {
		if(!loadedResources.contains(c.getSimpleName()) || doReload) {
			for (String name : type.name()) {
				loadFromResource(c, name, map);
			}
			loadedResources.add(c.getSimpleName());
114
            Grasscutter.getLogger().debug("Loaded " + map.size() + " " + c.getSimpleName() + "s.");
Melledy's avatar
Melledy committed
115
116
		}
	}
117

Melledy's avatar
Melledy committed
118
119
	@SuppressWarnings({"rawtypes", "unchecked"})
	protected static void loadFromResource(Class<?> c, String fileName, Int2ObjectMap map) throws Exception {
Melledy's avatar
Melledy committed
120
121
122
123
124
125
126
		try (FileReader fileReader = new FileReader(RESOURCE("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);
127
			}
Melledy's avatar
Melledy committed
128
129
130
		}
	}

Yazawazi's avatar
Yazawazi committed
131
132
	private static void loadScenePoints() {
		Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)");
133
		File folder = new File(RESOURCE("BinOutput/Scene/Point"));
Yazawazi's avatar
Yazawazi committed
134
135
136
137
138
139

		if (!folder.isDirectory() || !folder.exists() || folder.listFiles() == null) {
			Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!");
			return;
		}

Yazawazi's avatar
Yazawazi committed
140
		List<ScenePointEntry> scenePointList = new ArrayList<>();
141
		for (File file : Objects.requireNonNull(folder.listFiles())) {
142
			ScenePointConfig config; Integer sceneId;
143

Yazawazi's avatar
Yazawazi committed
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
			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<String, JsonElement> entry : config.points.entrySet()) {
				PointData pointData = Grasscutter.getGsonFactory().fromJson(entry.getValue(), PointData.class);
Melledy's avatar
Melledy committed
164
				pointData.setId(Integer.parseInt(entry.getKey()));
Yazawazi's avatar
Yazawazi committed
165
166
167

				ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData);
				scenePointList.add(sl);
168
				GameData.getScenePointIdList().add(pointData.getId());
169

170
				pointData.updateDailyDungeon();
Yazawazi's avatar
Yazawazi committed
171
172
173
			}

			for (ScenePointEntry entry : scenePointList) {
174
				GameData.getScenePointEntries().put(entry.getName(), entry);
Yazawazi's avatar
Yazawazi committed
175
176
177
178
			}
		}
	}

Melledy's avatar
Melledy committed
179
180
	private static void loadAbilityEmbryos() {
		List<AbilityEmbryoEntry> embryoList = null;
181
182

		// Read from cached file if exists
183
		try (InputStream embryoCache = DataLoader.load("AbilityEmbryos.json", false)) {
184
			embryoList = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(embryoCache), TypeToken.getParameterized(Collection.class, AbilityEmbryoEntry.class).getType());
185
		} catch (Exception ignored) {}
186
187

		if(embryoList == null) {
Melledy's avatar
Melledy committed
188
189
			// Load from BinOutput
			Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)");
190

Melledy's avatar
Melledy committed
191
			embryoList = new LinkedList<>();
192
			File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Avatar/")));
KingRainbow44's avatar
KingRainbow44 committed
193
194
195
196
197
			File[] files = folder.listFiles();
			if(files == null) {
				Grasscutter.getLogger().error("Error loading ability embryos: no files found in " + folder.getAbsolutePath());
				return;
			}
198

KingRainbow44's avatar
KingRainbow44 committed
199
200
201
			for (File file : files) {
				AvatarConfig config;
				String avatarName;
202

Melledy's avatar
Melledy committed
203
204
205
206
207
208
				Matcher matcher = pattern.matcher(file.getName());
				if (matcher.find()) {
					avatarName = matcher.group(0);
				} else {
					continue;
				}
209

Melledy's avatar
Melledy committed
210
211
212
213
214
215
				try (FileReader fileReader = new FileReader(file)) {
					config = Grasscutter.getGsonFactory().fromJson(fileReader, AvatarConfig.class);
				} catch (Exception e) {
					e.printStackTrace();
					continue;
				}
216

Melledy's avatar
Melledy committed
217
218
219
				if (config.abilities == null) {
					continue;
				}
220

Melledy's avatar
Melledy committed
221
222
223
224
				int s = config.abilities.size();
				AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s]));
				embryoList.add(al);
			}
225

226
			File playerElementsFile = new File(Utils.toFilePath(RESOURCE("BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json")));
227

228
229
230
231
232
233
234
			if (playerElementsFile.exists()) {
				try (FileReader fileReader = new FileReader(playerElementsFile)) {
					GameDepot.setPlayerAbilities(Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<String, AvatarConfig>>(){}.getType()));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
Melledy's avatar
Melledy committed
235
		}
236

Melledy's avatar
Melledy committed
237
238
239
240
241
242
		if (embryoList == null || embryoList.isEmpty()) {
			Grasscutter.getLogger().error("No embryos loaded!");
			return;
		}

		for (AbilityEmbryoEntry entry : embryoList) {
243
			GameData.getAbilityEmbryoInfo().put(entry.getName(), entry);
Melledy's avatar
Melledy committed
244
245
		}
	}
246

Melledy's avatar
Melledy committed
247
248
	private static void loadAbilityModifiers() {
		// Load from BinOutput
249
		File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Ability/Temp/AvatarAbilities/")));
Melledy's avatar
Melledy committed
250
251
252
253
254
255
256
		File[] files = folder.listFiles();
		if (files == null) {
			Grasscutter.getLogger().error("Error loading ability modifiers: no files found in " + folder.getAbsolutePath());
			return;
		}

		for (File file : files) {
257
			List<AbilityConfigData> abilityConfigList;
258

Melledy's avatar
Melledy committed
259
260
261
262
263
264
			try (FileReader fileReader = new FileReader(file)) {
				abilityConfigList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityConfigData.class).getType());
			} catch (Exception e) {
				e.printStackTrace();
				continue;
			}
265

Melledy's avatar
Melledy committed
266
267
268
269
			for (AbilityConfigData data : abilityConfigList) {
				if (data.Default.modifiers == null || data.Default.modifiers.size() == 0) {
					continue;
				}
270

Melledy's avatar
Melledy committed
271
				AbilityModifierEntry modifierEntry = new AbilityModifierEntry(data.Default.abilityName);
272

Melledy's avatar
Melledy committed
273
274
				for (Entry<String, AbilityModifier> entry : data.Default.modifiers.entrySet()) {
					AbilityModifier modifier = entry.getValue();
275

Melledy's avatar
Melledy committed
276
277
278
279
280
281
282
283
284
					// Stare.
					if (modifier.onAdded != null) {
						for (AbilityModifierAction action : modifier.onAdded) {
							if (action.$type.contains("HealHP")) {
								action.type = AbilityModifierActionType.HealHP;
								modifierEntry.getOnAdded().add(action);
							}
						}
					}
285

Melledy's avatar
Melledy committed
286
287
288
289
290
291
292
293
					if (modifier.onThinkInterval != null) {
						for (AbilityModifierAction action : modifier.onThinkInterval) {
							if (action.$type.contains("HealHP")) {
								action.type = AbilityModifierActionType.HealHP;
								modifierEntry.getOnThinkInterval().add(action);
							}
						}
					}
294

Melledy's avatar
Melledy committed
295
296
297
298
299
300
301
302
303
					if (modifier.onRemoved != null) {
						for (AbilityModifierAction action : modifier.onRemoved) {
							if (action.$type.contains("HealHP")) {
								action.type = AbilityModifierActionType.HealHP;
								modifierEntry.getOnRemoved().add(action);
							}
						}
					}
				}
304

Melledy's avatar
Melledy committed
305
306
307
308
				GameData.getAbilityModifiers().put(modifierEntry.getName(), modifierEntry);
			}
		}
	}
309

Melledy's avatar
Melledy committed
310
	private static void loadSpawnData() {
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
		String[] spawnDataNames = {"Spawns.json", "GadgetSpawns.json"};
		Int2ObjectMap<SpawnGroupEntry> spawnEntryMap = new Int2ObjectOpenHashMap<>();

		for (String name : spawnDataNames) {
			// Load spawn entries from file
			try (InputStream spawnDataEntries = DataLoader.load(name)) {
				Type type = TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType();
				List<SpawnGroupEntry> list = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(spawnDataEntries), type);
				
				// Add spawns to group if it already exists in our spawn group map
				for (SpawnGroupEntry group : list) {
					if (spawnEntryMap.containsKey(group.getGroupId())) {
						spawnEntryMap.get(group.getGroupId()).getSpawns().addAll(group.getSpawns());
					} else {
						spawnEntryMap.put(group.getGroupId(), group);
					}
				}
			} catch (Exception ignored) {}
		}
		
		if (spawnEntryMap.isEmpty()) {
Melledy's avatar
Melledy committed
332
333
334
335
			Grasscutter.getLogger().error("No spawn data loaded!");
			return;
		}

336
		for (SpawnGroupEntry entry : spawnEntryMap.values()) {
337
			entry.getSpawns().forEach(s -> s.setGroup(entry));
338
			GameDepot.getSpawnListById(entry.getSceneId()).insert(entry, entry.getPos().getX(), entry.getPos().getZ());
Melledy's avatar
Melledy committed
339
340
		}
	}
341

Melledy's avatar
Melledy committed
342
343
344
	private static void loadOpenConfig() {
		// Read from cached file if exists
		List<OpenConfigEntry> list = null;
345
346
347
348
349
350

		try(InputStream openConfigCache = DataLoader.load("OpenConfig.json", false)) {
			list = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(openConfigCache), TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType());
		} catch (Exception ignored) {}

		if (list == null) {
Melledy's avatar
Melledy committed
351
352
			Map<String, OpenConfigEntry> map = new TreeMap<>();
			java.lang.reflect.Type type = new TypeToken<Map<String, OpenConfigData[]>>() {}.getType();
ayy lmao's avatar
ayy lmao committed
353
			String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"};
354

Melledy's avatar
Melledy committed
355
			for (String name : folderNames) {
356
				File folder = new File(Utils.toFilePath(RESOURCE(name)));
KingRainbow44's avatar
KingRainbow44 committed
357
358
359
360
				File[] files = folder.listFiles();
				if(files == null) {
					Grasscutter.getLogger().error("Error loading open config: no files found in " + folder.getAbsolutePath()); return;
				}
361

KingRainbow44's avatar
KingRainbow44 committed
362
				for (File file : files) {
Melledy's avatar
Melledy committed
363
364
365
					if (!file.getName().endsWith(".json")) {
						continue;
					}
366

KingRainbow44's avatar
KingRainbow44 committed
367
					Map<String, OpenConfigData[]> config;
368

Melledy's avatar
Melledy committed
369
370
371
372
373
374
					try (FileReader fileReader = new FileReader(file)) {
						config = Grasscutter.getGsonFactory().fromJson(fileReader, type);
					} catch (Exception e) {
						e.printStackTrace();
						continue;
					}
375

Melledy's avatar
Melledy committed
376
					for (Entry<String, OpenConfigData[]> e : config.entrySet()) {
377
						OpenConfigEntry entry = new OpenConfigEntry(e.getKey(), e.getValue());
Melledy's avatar
Melledy committed
378
379
380
381
						map.put(entry.getName(), entry);
					}
				}
			}
382

Melledy's avatar
Melledy committed
383
384
			list = new ArrayList<>(map.values());
		}
385

Melledy's avatar
Melledy committed
386
387
388
389
		if (list == null || list.isEmpty()) {
			Grasscutter.getLogger().error("No openconfig entries loaded!");
			return;
		}
390

Melledy's avatar
Melledy committed
391
		for (OpenConfigEntry entry : list) {
392
			GameData.getOpenConfigEntries().put(entry.getName(), entry);
Melledy's avatar
Melledy committed
393
394
		}
	}
395

Melledy's avatar
Melledy committed
396
	private static void loadQuests() {
Melledy's avatar
Melledy committed
397
		File folder = new File(RESOURCE("BinOutput/Quest/"));
398

Melledy's avatar
Melledy committed
399
400
401
		if (!folder.exists()) {
			return;
		}
402

Melledy's avatar
Melledy committed
403
		for (File file : folder.listFiles()) {
Melledy's avatar
Melledy committed
404
			MainQuestData mainQuest = null;
405

Melledy's avatar
Melledy committed
406
			try (FileReader fileReader = new FileReader(file)) {
Melledy's avatar
Melledy committed
407
				mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, MainQuestData.class);
Melledy's avatar
Melledy committed
408
409
410
411
			} catch (Exception e) {
				e.printStackTrace();
				continue;
			}
412

Melledy's avatar
Melledy committed
413
			GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
Melledy's avatar
Melledy committed
414
		}
415
416

		Grasscutter.getLogger().debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas.");
Melledy's avatar
Melledy committed
417
	}
418

419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
	@SneakyThrows
	private static void loadHomeworldDefaultSaveData(){
		var folder = Files.list(Path.of(RESOURCE("BinOutput/HomeworldDefaultSave"))).toList();
		var pattern = Pattern.compile("scene(.*)_home_config.json");

		for(var file : folder){
			var matcher = pattern.matcher(file.getFileName().toString());
			if(!matcher.find()){
				continue;
			}
			var sceneId = matcher.group(1);

			var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), HomeworldDefaultSaveData.class);

			GameData.getHomeworldDefaultSaveData().put(Integer.parseInt(sceneId), data);
		}

436
		Grasscutter.getLogger().debug("Loaded " + GameData.getHomeworldDefaultSaveData().size() + " HomeworldDefaultSaveDatas.");
437
438
	}

Akka's avatar
Akka committed
439
440
441
442
443
444
445
446
447
448
449
450
451
452
	@SneakyThrows
	private static void loadNpcBornData(){
		var folder = Files.list(Path.of(RESOURCE("BinOutput/Scene/SceneNpcBorn"))).toList();

		for(var file : folder){
			if(file.toFile().isDirectory()){
				continue;
			}

			var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), SceneNpcBornData.class);
			if(data.getBornPosList() == null || data.getBornPosList().size() == 0){
				continue;
			}

453
			data.setIndex(SceneIndexManager.buildIndex(3, data.getBornPosList(), item -> item.getPos().toPoint()));
Akka's avatar
Akka committed
454
455
456
			GameData.getSceneNpcBornData().put(data.getSceneId(), data);
		}

457
		Grasscutter.getLogger().debug("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas.");
Akka's avatar
Akka committed
458
	}
459

Melledy's avatar
Melledy committed
460
	// BinOutput configs
461

462
463
	public static class AvatarConfig {
		@SerializedName(value="abilities", alternate={"targetAbilities"})
Melledy's avatar
Melledy committed
464
		public ArrayList<AvatarConfigAbility> abilities;
465
	}
466

467
468
469
470
	public static class AvatarConfigAbility {
		public String abilityName;
		public String toString() {
			return abilityName;
Melledy's avatar
Melledy committed
471
472
		}
	}
473

Melledy's avatar
Melledy committed
474
475
476
	private static class OpenConfig {
		public OpenConfigData[] data;
	}
477

478
	public static class OpenConfigData {
Melledy's avatar
Melledy committed
479
480
		public String $type;
		public String abilityName;
481

482
		@SerializedName(value="talentIndex", alternate={"OJOFFKLNAHN"})
Melledy's avatar
Melledy committed
483
		public int talentIndex;
484

485
		@SerializedName(value="skillID", alternate={"overtime"})
486
		public int skillID;
487

488
		@SerializedName(value="pointDelta", alternate={"IGEBKIHPOIF"})
489
		public int pointDelta;
Melledy's avatar
Melledy committed
490
491
	}
}