-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathBasicMod.java
More file actions
274 lines (248 loc) · 11.6 KB
/
BasicMod.java
File metadata and controls
274 lines (248 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
package basicmod;
import basemod.BaseMod;
import basemod.interfaces.AddAudioSubscriber;
import basemod.interfaces.EditKeywordsSubscriber;
import basemod.interfaces.EditStringsSubscriber;
import basemod.interfaces.PostInitializeSubscriber;
import basicmod.util.GeneralUtils;
import basicmod.util.KeywordInfo;
import basicmod.util.Sounds;
import basicmod.util.TextureLoader;
import com.badlogic.gdx.Files;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.LwjglFileHandle;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.evacipated.cardcrawl.modthespire.Loader;
import com.evacipated.cardcrawl.modthespire.ModInfo;
import com.evacipated.cardcrawl.modthespire.Patcher;
import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer;
import com.google.gson.Gson;
import com.megacrit.cardcrawl.core.Settings;
import com.megacrit.cardcrawl.localization.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.scannotation.AnnotationDB;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.*;
@SpireInitializer
public class BasicMod implements
EditStringsSubscriber,
EditKeywordsSubscriber,
AddAudioSubscriber,
PostInitializeSubscriber {
public static ModInfo info;
public static String modID; //Edit your pom.xml to change this
static { loadModInfo(); }
private static final String resourcesFolder = checkResourcesPath();
public static final Logger logger = LogManager.getLogger(modID); //Used to output to the console.
//This is used to prefix the IDs of various objects like cards and relics,
//to avoid conflicts between different mods using the same name for things.
public static String makeID(String id) {
return modID + ":" + id;
}
//This will be called by ModTheSpire because of the @SpireInitializer annotation at the top of the class.
public static void initialize() {
new BasicMod();
}
public BasicMod() {
BaseMod.subscribe(this); //This will make BaseMod trigger all the subscribers at their appropriate times.
logger.info(modID + " subscribed to BaseMod.");
}
@Override
public void receivePostInitialize() {
//This loads the image used as an icon in the in-game mods menu.
Texture badgeTexture = TextureLoader.getTexture(imagePath("badge.png"));
//Set up the mod information displayed in the in-game mods menu.
//The information used is taken from your pom.xml file.
//If you want to set up a config panel, that will be done here.
//You can find information about this on the BaseMod wiki page "Mod Config and Panel".
BaseMod.registerModBadge(badgeTexture, info.Name, GeneralUtils.arrToString(info.Authors), info.Description, null);
}
/*----------Localization----------*/
//This is used to load the appropriate localization files based on language.
private static String getLangString()
{
return Settings.language.name().toLowerCase();
}
private static final String defaultLanguage = "eng";
public static final Map<String, KeywordInfo> keywords = new HashMap<>();
@Override
public void receiveEditStrings() {
/*
First, load the default localization.
Then, if the current language is different, attempt to load localization for that language.
This results in the default localization being used for anything that might be missing.
The same process is used to load keywords slightly below.
*/
loadLocalization(defaultLanguage); //no exception catching for default localization; you better have at least one that works.
if (!defaultLanguage.equals(getLangString())) {
try {
loadLocalization(getLangString());
}
catch (GdxRuntimeException e) {
e.printStackTrace();
}
}
}
private void loadLocalization(String lang) {
//While this does load every type of localization, most of these files are just outlines so that you can see how they're formatted.
//Feel free to comment out/delete any that you don't end up using.
BaseMod.loadCustomStringsFile(CardStrings.class,
localizationPath(lang, "CardStrings.json"));
BaseMod.loadCustomStringsFile(CharacterStrings.class,
localizationPath(lang, "CharacterStrings.json"));
BaseMod.loadCustomStringsFile(EventStrings.class,
localizationPath(lang, "EventStrings.json"));
BaseMod.loadCustomStringsFile(OrbStrings.class,
localizationPath(lang, "OrbStrings.json"));
BaseMod.loadCustomStringsFile(PotionStrings.class,
localizationPath(lang, "PotionStrings.json"));
BaseMod.loadCustomStringsFile(PowerStrings.class,
localizationPath(lang, "PowerStrings.json"));
BaseMod.loadCustomStringsFile(RelicStrings.class,
localizationPath(lang, "RelicStrings.json"));
BaseMod.loadCustomStringsFile(UIStrings.class,
localizationPath(lang, "UIStrings.json"));
}
@Override
public void receiveEditKeywords()
{
Gson gson = new Gson();
String json = Gdx.files.internal(localizationPath(defaultLanguage, "Keywords.json")).readString(String.valueOf(StandardCharsets.UTF_8));
KeywordInfo[] keywords = gson.fromJson(json, KeywordInfo[].class);
for (KeywordInfo keyword : keywords) {
keyword.prep();
registerKeyword(keyword);
}
if (!defaultLanguage.equals(getLangString())) {
try
{
json = Gdx.files.internal(localizationPath(getLangString(), "Keywords.json")).readString(String.valueOf(StandardCharsets.UTF_8));
keywords = gson.fromJson(json, KeywordInfo[].class);
for (KeywordInfo keyword : keywords) {
keyword.prep();
registerKeyword(keyword);
}
}
catch (Exception e)
{
logger.warn(modID + " does not support " + getLangString() + " keywords.");
}
}
}
private void registerKeyword(KeywordInfo info) {
BaseMod.addKeyword(modID.toLowerCase(), info.PROPER_NAME, info.NAMES, info.DESCRIPTION, info.COLOR);
if (!info.ID.isEmpty())
{
keywords.put(info.ID, info);
}
}
@Override
public void receiveAddAudio() {
loadAudio(Sounds.class);
}
private static final String[] AUDIO_EXTENSIONS = { ".ogg", ".wav", ".mp3" }; //There are more valid types, but not really worth checking them all here
private void loadAudio(Class<?> cls) {
try {
Field[] fields = cls.getDeclaredFields();
outer:
for (Field f : fields) {
int modifiers = f.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && f.getType().equals(String.class)) {
String s = (String) f.get(null);
if (s == null) { //If no defined value, determine path using field name
s = audioPath(f.getName());
for (String ext : AUDIO_EXTENSIONS) {
String testPath = s + ext;
if (Gdx.files.internal(testPath).exists()) {
s = testPath;
BaseMod.addAudio(s, s);
f.set(null, s);
continue outer;
}
}
throw new Exception("Failed to find an audio file \"" + f.getName() + "\" in " + resourcesFolder + "/audio; check to ensure the capitalization and filename are correct.");
}
else { //Otherwise, load defined path
if (Gdx.files.internal(s).exists()) {
BaseMod.addAudio(s, s);
}
else {
throw new Exception("Failed to find audio file \"" + s + "\"; check to ensure this is the correct filepath.");
}
}
}
}
}
catch (Exception e) {
logger.error("Exception occurred in loadAudio: ", e);
}
}
//These methods are used to generate the correct filepaths to various parts of the resources folder.
public static String localizationPath(String lang, String file) {
return resourcesFolder + "/localization/" + lang + "/" + file;
}
public static String audioPath(String file) {
return resourcesFolder + "/audio/" + file;
}
public static String imagePath(String file) {
return resourcesFolder + "/images/" + file;
}
public static String characterPath(String file) {
return resourcesFolder + "/images/character/" + file;
}
public static String powerPath(String file) {
return resourcesFolder + "/images/powers/" + file;
}
public static String relicPath(String file) {
return resourcesFolder + "/images/relics/" + file;
}
/**
* Checks the expected resources path based on the package name.
*/
private static String checkResourcesPath() {
String name = BasicMod.class.getName(); //getPackage can be iffy with patching, so class name is used instead.
int separator = name.indexOf('.');
if (separator > 0)
name = name.substring(0, separator);
FileHandle resources = new LwjglFileHandle(name, Files.FileType.Internal);
if (!resources.exists()) {
throw new RuntimeException("\n\tFailed to find resources folder; expected it to be at \"resources/" + name + "\"." +
" Either make sure the folder under resources has the same name as your mod's package, or change the line\n" +
"\t\"private static final String resourcesFolder = checkResourcesPath();\"\n" +
"\tat the top of the " + BasicMod.class.getSimpleName() + " java file.");
}
if (!resources.child("images").exists()) {
throw new RuntimeException("\n\tFailed to find the 'images' folder in the mod's 'resources/" + name + "' folder; Make sure the " +
"images folder is in the correct location.");
}
if (!resources.child("localization").exists()) {
throw new RuntimeException("\n\tFailed to find the 'localization' folder in the mod's 'resources/" + name + "' folder; Make sure the " +
"localization folder is in the correct location.");
}
return name;
}
/**
* This determines the mod's ID based on information stored by ModTheSpire.
*/
private static void loadModInfo() {
Optional<ModInfo> infos = Arrays.stream(Loader.MODINFOS).filter((modInfo)->{
AnnotationDB annotationDB = Patcher.annotationDBMap.get(modInfo.jarURL);
if (annotationDB == null)
return false;
Set<String> initializers = annotationDB.getAnnotationIndex().getOrDefault(SpireInitializer.class.getName(), Collections.emptySet());
return initializers.contains(BasicMod.class.getName());
}).findFirst();
if (infos.isPresent()) {
info = infos.get();
modID = info.ID;
}
else {
throw new RuntimeException("Failed to determine mod info/ID based on initializer.");
}
}
}