diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d14ebf3e0..89d029f19 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,6 +25,16 @@ jobs:
with:
distribution: temurin
java-version: '17'
+ - name: Cache gradle files
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ ./.gradle/loom-cache
+ key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle', 'gradle.properties', '**/*.accesswidener') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
- name: Run Gradle
run: ./gradlew && ./gradlew jar
- name: Upload build
diff --git a/.gitignore b/.gitignore
index 30ac9f0d1..ede33151b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,7 +17,7 @@
*.nar
*.ear
*.zip
-*.tar.gz
+*.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
@@ -52,21 +52,23 @@ tmp/
.vscode
# Grasscutter
-resources/
-logs/
-plugins/
-data/AbilityEmbryos.json
-data/OpenConfig.json
+/resources
+/logs
+/plugins
+/data
+/keys
+/language
+/languages
+/src/generated
+
+/*.jar
+/*.sh
+
GM Handbook.txt
config.json
mitmdump.exe
-*.jar
-!lib/*.jar
mongod.exe
-/src/generated/
-/*.sh
-language/
-languages/
+
gacha-mapping.js
mappings.js
BuildConfig.java
diff --git a/data/documentation/handbook.html b/data/documentation/handbook.html
new file mode 100644
index 000000000..6a77d3806
--- /dev/null
+++ b/data/documentation/handbook.html
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+ GM Handbook
+
+
+
+
+
{{TITLE}}
+
+
{{TITLE_COMMANDS}}
+
+
+
+
+ {{HEADER_COMMAND}} |
+ {{HEADER_DESCRIPTION}} |
+
+
+ {{COMMANDS_TABLE}}
+
+
+
{{TITLE_AVATARS}}
+
+
+
+
+ {{HEADER_ID}} |
+ {{HEADER_AVATAR}} |
+
+
+ {{AVATARS_TABLE}}
+
+
+
{{TITLE_ITEMS}}
+
+
+
+
+ {{HEADER_ID}} |
+ {{HEADER_ITEM}} |
+
+
+ {{ITEMS_TABLE}}
+
+
+
+
{{TITLE_SCENES}}
+
+
+
+
+ {{HEADER_ID}} |
+ {{HEADER_SCENE}} |
+
+
+ {{SCENES_TABLE}}
+
+
+
{{TITLE_MONSTERS}}
+
+
+
+
+ {{HEADER_ID}} |
+ {{HEADER_MONSTER}} |
+
+
+ {{MONSTERS_TABLE}}
+
+
+
+
+
+
diff --git a/data/documentation/index.html b/data/documentation/index.html
new file mode 100644
index 000000000..f89dbe258
--- /dev/null
+++ b/data/documentation/index.html
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+ Documentation
+
+
+
+
+
+
diff --git a/src/main/java/emu/grasscutter/Configuration.java b/src/main/java/emu/grasscutter/Configuration.java
index 486ca8739..66fc2fe37 100644
--- a/src/main/java/emu/grasscutter/Configuration.java
+++ b/src/main/java/emu/grasscutter/Configuration.java
@@ -28,7 +28,6 @@ public final class Configuration extends ConfigContainer {
public static final Locale FALLBACK_LANGUAGE = config.language.fallback;
private static final String DATA_FOLDER = config.folderStructure.data;
private static final String RESOURCES_FOLDER = config.folderStructure.resources;
- private static final String KEYS_FOLDER = config.folderStructure.keys;
private static final String PLUGINS_FOLDER = config.folderStructure.plugins;
private static final String SCRIPTS_FOLDER = config.folderStructure.scripts;
private static final String PACKETS_FOLDER = config.folderStructure.packets;
@@ -62,10 +61,6 @@ public final class Configuration extends ConfigContainer {
public static String RESOURCE(String path) {
return Paths.get(RESOURCES_FOLDER, path).toString();
}
-
- public static String KEY(String path) {
- return Paths.get(KEYS_FOLDER, path).toString();
- }
public static String PLUGIN() {
return PLUGINS_FOLDER;
diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java
index 316c2030e..3cf8363e7 100644
--- a/src/main/java/emu/grasscutter/Grasscutter.java
+++ b/src/main/java/emu/grasscutter/Grasscutter.java
@@ -14,6 +14,7 @@ import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.dispatch.DispatchHandler;
import emu.grasscutter.server.http.handlers.*;
import emu.grasscutter.server.http.dispatch.RegionHandler;
+import emu.grasscutter.server.http.documentation.DocumentationServerHandler;
import emu.grasscutter.utils.ConfigContainer;
import emu.grasscutter.utils.Utils;
import org.jline.reader.EndOfFileException;
@@ -129,6 +130,7 @@ public final class Grasscutter {
httpServer.addRouter(AnnouncementsHandler.class);
httpServer.addRouter(DispatchHandler.class);
httpServer.addRouter(GachaHandler.class);
+ httpServer.addRouter(DocumentationServerHandler.class);
// TODO: find a better place?
StaminaManager.initialize();
diff --git a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java
index 41aba1c8e..ee27dbca0 100644
--- a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java
+++ b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java
@@ -1,5 +1,6 @@
package emu.grasscutter.auth;
+import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.*;
import express.http.Request;
import express.http.Response;
@@ -30,10 +31,10 @@ public interface AuthenticationSystem {
/**
* Called by plugins to internally verify a user's identity.
- * @param details A unique, one-time token to verify the user.
- * @return True if the user is verified, False otherwise.
+ * @param details A unique identifier to identify the user. (For example: a JWT token)
+ * @return The user's account if the verification was successful, null if the user was unable to be verified.
*/
- boolean verifyUser(String details);
+ Account verifyUser(String details);
/**
* This is the authenticator used for password authentication.
@@ -59,6 +60,12 @@ public interface AuthenticationSystem {
*/
ExternalAuthenticator getExternalAuthenticator();
+ /**
+ * This is the authenticator used for handling OAuth authentication requests.
+ * @return An authenticator.
+ */
+ OAuthAuthenticator getOAuthAuthenticator();
+
/**
* A data container that holds relevant data for authenticating a client.
*/
@@ -124,4 +131,16 @@ public interface AuthenticationSystem {
return AuthenticationRequest.builder().request(request)
.response(response).build();
}
+
+
+ /**
+ * Generates an authentication request from a {@link Response} object.
+ * @param request The Express request.
+ * @param jsonData The JSON data.
+ * @return An authentication request.
+ */
+ static AuthenticationRequest fromOAuthRequest(Request request, Response response) {
+ return AuthenticationRequest.builder().request(request)
+ .response(response).build();
+ }
}
diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java b/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java
index 08958d8e9..ba77e7d6e 100644
--- a/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java
+++ b/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java
@@ -2,6 +2,7 @@ package emu.grasscutter.auth;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.DefaultAuthenticators.*;
+import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.ComboTokenResJson;
import emu.grasscutter.server.http.objects.LoginResultJson;
@@ -16,6 +17,7 @@ public final class DefaultAuthentication implements AuthenticationSystem {
private final Authenticator tokenAuthenticator = new TokenAuthenticator();
private final Authenticator sessionKeyAuthenticator = new SessionKeyAuthenticator();
private final ExternalAuthenticator externalAuthenticator = new ExternalAuthentication();
+ private final OAuthAuthenticator oAuthAuthenticator = new OAuthAuthentication();
@Override
public void createAccount(String username, String password) {
@@ -26,11 +28,11 @@ public final class DefaultAuthentication implements AuthenticationSystem {
public void resetPassword(String username) {
// Unhandled. The default authenticator doesn't store passwords.
}
-
+
@Override
- public boolean verifyUser(String details) {
+ public Account verifyUser(String details) {
Grasscutter.getLogger().info(translate("dispatch.authentication.default_unable_to_verify"));
- return false;
+ return null;
}
@Override
@@ -52,4 +54,9 @@ public final class DefaultAuthentication implements AuthenticationSystem {
public ExternalAuthenticator getExternalAuthenticator() {
return this.externalAuthenticator;
}
+
+ @Override
+ public OAuthAuthenticator getOAuthAuthenticator() {
+ return this.oAuthAuthenticator;
+ }
}
diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java
index e1d5fddf0..0a9916a59 100644
--- a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java
+++ b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java
@@ -41,10 +41,6 @@ public final class DefaultAuthenticators {
responseMessage = translate("messages.dispatch.account.username_create_error");
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", address));
} else {
- // Add default permissions.
- for (var permission : ACCOUNT.defaultPermissions)
- account.addPermission(permission);
-
// Continue with login.
successfulLogin = true;
@@ -178,4 +174,29 @@ public final class DefaultAuthenticators {
request.getResponse().send("Authentication is not available with the default authentication method.");
}
}
+
+ /**
+ * Handles authentication requests from OAuth sources.
+ */
+ public static class OAuthAuthentication implements OAuthAuthenticator {
+ @Override public void handleLogin(AuthenticationRequest request) {
+ assert request.getResponse() != null;
+ request.getResponse().send("Authentication is not available with the default authentication method.");
+ }
+
+ @Override public void handleDesktopRedirection(AuthenticationRequest request) {
+ assert request.getResponse() != null;
+ request.getResponse().send("Authentication is not available with the default authentication method.");
+ }
+
+ @Override public void handleMobileRedirection(AuthenticationRequest request) {
+ assert request.getResponse() != null;
+ request.getResponse().send("Authentication is not available with the default authentication method.");
+ }
+
+ @Override public void handleTokenProcess(AuthenticationRequest request) {
+ assert request.getResponse() != null;
+ request.getResponse().send("Authentication is not available with the default authentication method.");
+ }
+ }
}
diff --git a/src/main/java/emu/grasscutter/auth/OAuthAuthenticator.java b/src/main/java/emu/grasscutter/auth/OAuthAuthenticator.java
new file mode 100644
index 000000000..394d14371
--- /dev/null
+++ b/src/main/java/emu/grasscutter/auth/OAuthAuthenticator.java
@@ -0,0 +1,28 @@
+package emu.grasscutter.auth;
+
+import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest;
+
+/**
+ * Handles authentication via OAuth routes.
+ */
+public interface OAuthAuthenticator {
+
+ /**
+ * Called when an OAuth login request is made.
+ * @param request The authentication request.
+ */
+ void handleLogin(AuthenticationRequest request);
+
+ /**
+ * Called when an client requests to redirect to login page.
+ * @param request The authentication request.
+ */
+ void handleDesktopRedirection(AuthenticationRequest request);
+ void handleMobileRedirection(AuthenticationRequest request);
+
+ /**
+ * Called when an OAuth login requests callback.
+ * @param request The authentication request.
+ */
+ void handleTokenProcess(AuthenticationRequest request);
+}
diff --git a/src/main/java/emu/grasscutter/command/CommandHandler.java b/src/main/java/emu/grasscutter/command/CommandHandler.java
index f4fe12b3f..6e19e7bc9 100644
--- a/src/main/java/emu/grasscutter/command/CommandHandler.java
+++ b/src/main/java/emu/grasscutter/command/CommandHandler.java
@@ -2,6 +2,8 @@ package emu.grasscutter.command;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player;
+import emu.grasscutter.server.event.game.CommandResponseEvent;
+import emu.grasscutter.server.event.types.ServerEvent;
import java.util.List;
@@ -19,6 +21,8 @@ public interface CommandHandler {
} else {
player.dropMessage(message);
}
+ CommandResponseEvent event = new CommandResponseEvent(ServerEvent.Type.GAME,player, message);
+ event.call();
}
/**
diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java
index d2b910811..9c1564e78 100644
--- a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java
+++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java
@@ -9,7 +9,7 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate;
-@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", permissionTargeted = "server.resetshop.others", description = "commands.resetshop.description")
+@Command(label = "resetshop", usage = "resetshop ", permission = "server.resetshop", permissionTargeted = "server.resetshop.others", description = "commands.resetShopLimit.description")
public final class ResetShopLimitCommand implements CommandHandler {
@Override
@@ -19,6 +19,11 @@ public final class ResetShopLimitCommand implements CommandHandler {
return;
}
+ if (args.isEmpty()) {
+ CommandHandler.sendMessage(sender, translate(sender, "commands.resetShopLimit.usage"));
+ return;
+ }
+
targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0));
targetPlayer.save();
CommandHandler.sendMessage(sender, translate(sender, "commands.status.success"));
diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java
new file mode 100644
index 000000000..dc7e67281
--- /dev/null
+++ b/src/main/java/emu/grasscutter/data/DataLoader.java
@@ -0,0 +1,101 @@
+package emu.grasscutter.data;
+
+import emu.grasscutter.Grasscutter;
+import emu.grasscutter.server.http.handlers.GachaHandler;
+import emu.grasscutter.tools.Tools;
+import emu.grasscutter.utils.FileUtils;
+import emu.grasscutter.utils.Utils;
+
+import java.io.*;
+import java.nio.file.Path;
+import java.util.List;
+
+import static emu.grasscutter.Configuration.DATA;
+
+public class DataLoader {
+
+ /**
+ * 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
+ * @see #load(String, boolean)
+ * @param resourcePath The path to the data file to be loaded.
+ * @return InputStream of the data file.
+ * @throws FileNotFoundException
+ */
+ public static InputStream load(String resourcePath) throws FileNotFoundException {
+ return load(resourcePath, true);
+ }
+
+ /**
+ * Load a data file by its name.
+ * @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 file in the jar?
+ * @return InputStream of the data file.
+ * @throws FileNotFoundException
+ */
+ public static InputStream load(String resourcePath, boolean useFallback) throws FileNotFoundException {
+ if(Utils.fileExists(DATA(resourcePath))) {
+ // Data is in the resource directory
+ return new FileInputStream(DATA(resourcePath));
+ } else {
+ if(useFallback) {
+ return FileUtils.readResourceAsStream("/defaults/data/" + resourcePath);
+ }
+ }
+
+ return null;
+ }
+
+ public static void CheckAllFiles() {
+
+ try {
+ List filenames = FileUtils.getPathsFromResource("/defaults/data/");
+
+ for (Path file : filenames) {
+ String relativePath = String.valueOf(file).split("/defaults/data/")[1];
+
+ CheckAndCopyData(relativePath);
+ }
+ } catch (Exception e) {
+ Grasscutter.getLogger().error("An error occurred while trying to check the data folder. \n" + e);
+ }
+
+ GenerateGachaMappings();
+ }
+
+ private static void CheckAndCopyData(String name) {
+ String filePath = Utils.toFilePath(DATA(name));
+
+ if (!Utils.fileExists(filePath)) {
+ // Check if file is in subdirectory
+ if (name.indexOf("/") != -1) {
+ String[] path = name.split("/");
+
+ String folder = "";
+ for(int i = 0; i < (path.length - 1); i++) {
+ folder += path[i] + "/";
+
+ // Make sure the current folder exists
+ String folderToCreate = Utils.toFilePath(DATA(folder));
+ if(!Utils.fileExists(folderToCreate)) {
+ Grasscutter.getLogger().info("Creating data folder '" + folder + "'");
+ Utils.createFolder(folderToCreate);
+ }
+ }
+ }
+
+ Grasscutter.getLogger().info("Creating default '" + name + "' data");
+ FileUtils.copyResource("/defaults/data/" + name, filePath);
+ }
+ }
+
+ private static void GenerateGachaMappings() {
+ if (!Utils.fileExists(GachaHandler.gachaMappings)) {
+ try {
+ Grasscutter.getLogger().info("Creating default '" + GachaHandler.gachaMappings + "' data");
+ Tools.createGachaMapping(GachaHandler.gachaMappings);
+ } catch (Exception exception) {
+ Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception);
+ }
+ }
+ }
+}
diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java
index 4b940c44d..6fe9b19fb 100644
--- a/src/main/java/emu/grasscutter/data/ResourceLoader.java
+++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java
@@ -1,7 +1,6 @@
package emu.grasscutter.data;
-import java.io.File;
-import java.io.FileReader;
+import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
@@ -33,6 +32,8 @@ import static emu.grasscutter.Configuration.*;
public class ResourceLoader {
+ private static List loadedResources = new ArrayList();
+
public static List> getResourceDefClasses() {
Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName());
Set> classes = reflections.getSubTypesOf(GameResource.class);
@@ -98,6 +99,10 @@ public class ResourceLoader {
}
public static void loadResources() {
+ loadResources(false);
+ }
+
+ public static void loadResources(boolean doReload) {
for (Class> resourceDefinition : getResourceDefClasses()) {
ResourceType type = resourceDefinition.getAnnotation(ResourceType.class);
@@ -113,7 +118,7 @@ public class ResourceLoader {
}
try {
- loadFromResource(resourceDefinition, type, map);
+ loadFromResource(resourceDefinition, type, map, doReload);
} catch (Exception e) {
Grasscutter.getLogger().error("Error loading resource file: " + Arrays.toString(type.name()), e);
}
@@ -121,13 +126,16 @@ public class ResourceLoader {
}
@SuppressWarnings("rawtypes")
- protected static void loadFromResource(Class> c, ResourceType type, Int2ObjectMap map) throws Exception {
- for (String name : type.name()) {
- loadFromResource(c, name, map);
+ 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);
+ }
+ Grasscutter.getLogger().info("Loaded " + map.size() + " " + c.getSimpleName() + "s.");
+ loadedResources.add(c.getSimpleName());
}
- Grasscutter.getLogger().info("Loaded " + map.size() + " " + c.getSimpleName() + "s.");
}
-
+
@SuppressWarnings({"rawtypes", "unchecked"})
protected static void loadFromResource(Class> c, String fileName, Int2ObjectMap map) throws Exception {
FileReader fileReader = new FileReader(RESOURCE("ExcelBinOutput/" + fileName));
@@ -138,6 +146,9 @@ public class ResourceLoader {
Map tempMap = Utils.switchPropertiesUpperLowerCase((Map) o, c);
GameResource res = gson.fromJson(gson.toJson(tempMap), TypeToken.get(c).getType());
res.onLoad();
+ if(map.containsKey(res.getId())) {
+ map.remove(res.getId());
+ }
map.put(res.getId(), res);
}
}
@@ -191,18 +202,14 @@ public class ResourceLoader {
}
private static void loadAbilityEmbryos() {
- // Read from cached file if exists
- File embryoCache = new File(DATA("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 {
+
+ // Read from cached file if exists
+ try(InputStream embryoCache = DataLoader.load("AbilityEmbryos.json", false)) {
+ embryoList = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(embryoCache), TypeToken.getParameterized(Collection.class, AbilityEmbryoEntry.class).getType());
+ } catch(Exception ignored) {}
+
+ if(embryoList == null) {
// Load from BinOutput
Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)");
@@ -316,18 +323,12 @@ public class ResourceLoader {
}
private static void loadSpawnData() {
- // Read from cached file if exists
- File spawnDataEntries = new File(DATA("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();
- }
- }
+
+ // Read from cached file if exists
+ try(InputStream spawnDataEntries = DataLoader.load("Spawns.json")) {
+ spawnEntryList = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(spawnDataEntries), TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType());
+ } catch (Exception ignored) {}
if (spawnEntryList == null || spawnEntryList.isEmpty()) {
Grasscutter.getLogger().error("No spawn data loaded!");
@@ -342,16 +343,13 @@ public class ResourceLoader {
private static void loadOpenConfig() {
// Read from cached file if exists
- File openConfigCache = new File(DATA("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 {
+
+ 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) {
Map map = new TreeMap<>();
java.lang.reflect.Type type = new TypeToken