Run Spotless on src/main

This commit is contained in:
KingRainbow44 2023-03-31 22:30:45 -04:00
parent 99822b0e22
commit fc05602128
No known key found for this signature in database
GPG Key ID: FC2CB64B00D257BE
1003 changed files with 60650 additions and 58050 deletions

View File

@ -1,30 +1,35 @@
package emu.grasscutter; package emu.grasscutter;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import java.util.Arrays;
import java.util.Arrays;
public final class GameConstants {
public final class GameConstants { public static final int DEFAULT_TEAMS = 4;
public static final int DEFAULT_TEAMS = 4; public static final int MAX_TEAMS = 10;
public static final int MAX_TEAMS = 10; public static final int MAIN_CHARACTER_MALE = 10000005;
public static final int MAIN_CHARACTER_MALE = 10000005; public static final int MAIN_CHARACTER_FEMALE = 10000007;
public static final int MAIN_CHARACTER_FEMALE = 10000007; public static final Position START_POSITION = new Position(2747, 194, -1719);
public static final Position START_POSITION = new Position(2747, 194, -1719); public static final int MAX_FRIENDS = 60;
public static final int MAX_FRIENDS = 60; public static final int MAX_FRIEND_REQUESTS = 50;
public static final int MAX_FRIEND_REQUESTS = 50; public static final int SERVER_CONSOLE_UID = 99; // The UID of the server console's "player".
public static final int SERVER_CONSOLE_UID = 99; // The UID of the server console's "player". public static final int BATTLE_PASS_MAX_LEVEL = 50;
public static final int BATTLE_PASS_MAX_LEVEL = 50; public static final int BATTLE_PASS_POINT_PER_LEVEL = 1000;
public static final int BATTLE_PASS_POINT_PER_LEVEL = 1000; public static final int BATTLE_PASS_POINT_PER_WEEK = 10000;
public static final int BATTLE_PASS_POINT_PER_WEEK = 10000; public static final int BATTLE_PASS_LEVEL_PRICE = 150;
public static final int BATTLE_PASS_LEVEL_PRICE = 150; public static final int BATTLE_PASS_CURRENT_INDEX = 2;
public static final int BATTLE_PASS_CURRENT_INDEX = 2; // Default entity ability hashes.
// Default entity ability hashes. public static final String[] DEFAULT_ABILITY_STRINGS = {
public static final String[] DEFAULT_ABILITY_STRINGS = { "Avatar_DefaultAbility_VisionReplaceDieInvincible",
"Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible", "Avatar_DefaultAbility_AvartarInShaderChange",
"Avatar_Freeze_Duration_Reducer", "Avatar_Attack_ReviveEnergy", "Avatar_Component_Initializer", "Avatar_FallAnthem_Achievement_Listener" "Avatar_SprintBS_Invincible",
}; "Avatar_Freeze_Duration_Reducer",
public static final int[] DEFAULT_ABILITY_HASHES = Arrays.stream(DEFAULT_ABILITY_STRINGS).mapToInt(Utils::abilityHash).toArray(); "Avatar_Attack_ReviveEnergy",
public static final int DEFAULT_ABILITY_NAME = Utils.abilityHash("Default"); "Avatar_Component_Initializer",
public static String VERSION = "3.5.0"; "Avatar_FallAnthem_Achievement_Listener"
} };
public static final int[] DEFAULT_ABILITY_HASHES =
Arrays.stream(DEFAULT_ABILITY_STRINGS).mapToInt(Utils::abilityHash).toArray();
public static final int DEFAULT_ABILITY_NAME = Utils.abilityHash("Default");
public static String VERSION = "3.5.0";
}

View File

@ -1,323 +1,322 @@
package emu.grasscutter; package emu.grasscutter;
import ch.qos.logback.classic.Level; import static emu.grasscutter.config.Configuration.SERVER;
import ch.qos.logback.classic.Logger; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.auth.DefaultAuthentication; import ch.qos.logback.classic.Level;
import emu.grasscutter.command.CommandMap; import ch.qos.logback.classic.Logger;
import emu.grasscutter.command.DefaultPermissionHandler; import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.command.PermissionHandler; import emu.grasscutter.auth.DefaultAuthentication;
import emu.grasscutter.config.ConfigContainer; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.command.DefaultPermissionHandler;
import emu.grasscutter.database.DatabaseManager; import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.config.ConfigContainer;
import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.plugin.PluginManager;
import emu.grasscutter.server.http.HttpServer; import emu.grasscutter.plugin.api.ServerHook;
import emu.grasscutter.server.http.dispatch.DispatchHandler; import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.server.http.dispatch.RegionHandler; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.http.documentation.DocumentationServerHandler; import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.handlers.AnnouncementsHandler; import emu.grasscutter.server.http.dispatch.DispatchHandler;
import emu.grasscutter.server.http.handlers.GachaHandler; import emu.grasscutter.server.http.dispatch.RegionHandler;
import emu.grasscutter.server.http.handlers.GenericHandler; import emu.grasscutter.server.http.documentation.DocumentationServerHandler;
import emu.grasscutter.server.http.handlers.LogHandler; import emu.grasscutter.server.http.handlers.AnnouncementsHandler;
import emu.grasscutter.tools.Tools; import emu.grasscutter.server.http.handlers.GachaHandler;
import emu.grasscutter.utils.*; import emu.grasscutter.server.http.handlers.GenericHandler;
import lombok.Getter; import emu.grasscutter.server.http.handlers.LogHandler;
import lombok.Setter; import emu.grasscutter.tools.Tools;
import org.jline.reader.EndOfFileException; import emu.grasscutter.utils.*;
import org.jline.reader.LineReader; import java.io.File;
import org.jline.reader.LineReaderBuilder; import java.io.FileWriter;
import org.jline.reader.UserInterruptException; import java.io.IOError;
import org.jline.terminal.Terminal; import java.io.IOException;
import org.jline.terminal.TerminalBuilder; import java.util.Calendar;
import org.reflections.Reflections; import javax.annotation.Nullable;
import org.slf4j.LoggerFactory; import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable; import org.jline.reader.EndOfFileException;
import java.io.File; import org.jline.reader.LineReader;
import java.io.FileWriter; import org.jline.reader.LineReaderBuilder;
import java.io.IOError; import org.jline.reader.UserInterruptException;
import java.io.IOException; import org.jline.terminal.Terminal;
import java.util.Calendar; import org.jline.terminal.TerminalBuilder;
import org.reflections.Reflections;
import static emu.grasscutter.config.Configuration.SERVER; import org.slf4j.LoggerFactory;
import static emu.grasscutter.utils.Language.translate;
public final class Grasscutter {
public final class Grasscutter { public static final File configFile = new File("./config.json");
public static final File configFile = new File("./config.json"); public static final Reflections reflector = new Reflections("emu.grasscutter");
public static final Reflections reflector = new Reflections("emu.grasscutter"); @Getter private static final Logger logger = (Logger) LoggerFactory.getLogger(Grasscutter.class);
@Getter private static final Logger logger =
(Logger) LoggerFactory.getLogger(Grasscutter.class); @Getter public static ConfigContainer config;
@Getter public static ConfigContainer config; @Getter @Setter private static Language language;
@Getter @Setter private static String preferredLanguage;
@Getter @Setter private static Language language;
@Getter @Setter private static String preferredLanguage; @Getter private static int currentDayOfWeek;
@Setter private static ServerRunMode runModeOverride = null; // Config override for run mode
@Getter private static int currentDayOfWeek;
@Setter private static ServerRunMode runModeOverride = null; // Config override for run mode @Getter private static HttpServer httpServer;
@Getter private static GameServer gameServer;
@Getter private static HttpServer httpServer; @Getter private static PluginManager pluginManager;
@Getter private static GameServer gameServer; @Getter private static CommandMap commandMap;
@Getter private static PluginManager pluginManager;
@Getter private static CommandMap commandMap; @Getter @Setter private static AuthenticationSystem authenticationSystem;
@Getter @Setter private static PermissionHandler permissionHandler;
@Getter @Setter private static AuthenticationSystem authenticationSystem;
@Getter @Setter private static PermissionHandler permissionHandler; private static LineReader consoleLineReader = null;
private static LineReader consoleLineReader = null; static {
// Declare logback configuration.
static { System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
// Declare logback configuration.
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); // Disable the MongoDB logger.
var mongoLogger = (Logger) LoggerFactory.getLogger("org.mongodb.driver");
// Disable the MongoDB logger. mongoLogger.setLevel(Level.OFF);
var mongoLogger = (Logger) LoggerFactory.getLogger("org.mongodb.driver");
mongoLogger.setLevel(Level.OFF); // Load server configuration.
Grasscutter.loadConfig();
// Load server configuration. // Attempt to update configuration.
Grasscutter.loadConfig(); ConfigContainer.updateConfig();
// Attempt to update configuration.
ConfigContainer.updateConfig(); // Load translation files.
Grasscutter.loadLanguage();
// Load translation files.
Grasscutter.loadLanguage(); // Check server structure.
Utils.startupCheck();
// Check server structure. }
Utils.startupCheck();
} public static void main(String[] args) throws Exception {
Crypto.loadKeys(); // Load keys from buffers.
public static void main(String[] args) throws Exception {
Crypto.loadKeys(); // Load keys from buffers. // Parse start-up arguments.
if (StartupArguments.parse(args)) {
// Parse start-up arguments. System.exit(0); // Exit early.
if (StartupArguments.parse(args)) { }
System.exit(0); // Exit early.
} // Create command map.
commandMap = new CommandMap(true);
// Create command map.
commandMap = new CommandMap(true); // Initialize server.
logger.info(translate("messages.status.starting"));
// Initialize server. logger.info(translate("messages.status.game_version", GameConstants.VERSION));
logger.info(translate("messages.status.starting")); logger.info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH));
logger.info(translate("messages.status.game_version", GameConstants.VERSION));
logger.info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH)); // Load all resources.
Grasscutter.updateDayOfWeek();
// Load all resources. ResourceLoader.loadAll();
Grasscutter.updateDayOfWeek(); ScriptLoader.init();
ResourceLoader.loadAll();
ScriptLoader.init(); // Generate handbooks.
Tools.createGmHandbooks(false);
// Generate handbooks.
Tools.createGmHandbooks(false); // Initialize database.
DatabaseManager.initialize();
// Initialize database.
DatabaseManager.initialize(); // Initialize the default systems.
authenticationSystem = new DefaultAuthentication();
// Initialize the default systems. permissionHandler = new DefaultPermissionHandler();
authenticationSystem = new DefaultAuthentication();
permissionHandler = new DefaultPermissionHandler(); // Create server instances.
httpServer = new HttpServer();
// Create server instances. gameServer = new GameServer();
httpServer = new HttpServer(); // Create a server hook instance with both servers.
gameServer = new GameServer(); new ServerHook(gameServer, httpServer);
// Create a server hook instance with both servers.
new ServerHook(gameServer, httpServer); // Create plugin manager instance.
pluginManager = new PluginManager();
// Create plugin manager instance. // Add HTTP routes after loading plugins.
pluginManager = new PluginManager(); httpServer.addRouter(HttpServer.UnhandledRequestRouter.class);
// Add HTTP routes after loading plugins. httpServer.addRouter(HttpServer.DefaultRequestRouter.class);
httpServer.addRouter(HttpServer.UnhandledRequestRouter.class); httpServer.addRouter(RegionHandler.class);
httpServer.addRouter(HttpServer.DefaultRequestRouter.class); httpServer.addRouter(LogHandler.class);
httpServer.addRouter(RegionHandler.class); httpServer.addRouter(GenericHandler.class);
httpServer.addRouter(LogHandler.class); httpServer.addRouter(AnnouncementsHandler.class);
httpServer.addRouter(GenericHandler.class); httpServer.addRouter(DispatchHandler.class);
httpServer.addRouter(AnnouncementsHandler.class); httpServer.addRouter(GachaHandler.class);
httpServer.addRouter(DispatchHandler.class); httpServer.addRouter(DocumentationServerHandler.class);
httpServer.addRouter(GachaHandler.class);
httpServer.addRouter(DocumentationServerHandler.class); // Start servers.
var runMode = Grasscutter.getRunMode();
// Start servers. if (runMode == ServerRunMode.HYBRID) {
var runMode = Grasscutter.getRunMode(); httpServer.start();
if (runMode == ServerRunMode.HYBRID) { gameServer.start();
httpServer.start(); } else if (runMode == ServerRunMode.DISPATCH_ONLY) {
gameServer.start(); httpServer.start();
} else if (runMode == ServerRunMode.DISPATCH_ONLY) { } else if (runMode == ServerRunMode.GAME_ONLY) {
httpServer.start(); gameServer.start();
} else if (runMode == ServerRunMode.GAME_ONLY) { } else {
gameServer.start(); logger.error(translate("messages.status.run_mode_error", runMode));
} else { logger.error(translate("messages.status.run_mode_help"));
logger.error(translate("messages.status.run_mode_error", runMode)); logger.error(translate("messages.status.shutdown"));
logger.error(translate("messages.status.run_mode_help")); System.exit(1);
logger.error(translate("messages.status.shutdown")); }
System.exit(1);
} // Enable all plugins.
pluginManager.enablePlugins();
// Enable all plugins.
pluginManager.enablePlugins(); // Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
// Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown)); // Open console.
Grasscutter.startConsole();
// Open console. }
Grasscutter.startConsole();
} /** Server shutdown event. */
private static void onShutdown() {
/** // Disable all plugins.
* Server shutdown event. if (pluginManager != null) pluginManager.disablePlugins();
*/ }
private static void onShutdown() {
// Disable all plugins. /*
if (pluginManager != null) * Methods for the language system component.
pluginManager.disablePlugins(); */
}
public static void loadLanguage() {
/* var locale = config.language.language;
* Methods for the language system component. language = Language.getLanguage(Utils.getLanguageCode(locale));
*/ }
public static void loadLanguage() { /*
var locale = config.language.language; * Methods for the configuration system component.
language = Language.getLanguage(Utils.getLanguageCode(locale)); */
}
/** Attempts to load the configuration from a file. */
/* public static void loadConfig() {
* Methods for the configuration system component. // Check if config.json exists. If not, we generate a new config.
*/ if (!configFile.exists()) {
getLogger().info("config.json could not be found. Generating a default configuration ...");
/** config = new ConfigContainer();
* Attempts to load the configuration from a file. Grasscutter.saveConfig(config);
*/ return;
public static void loadConfig() { }
// Check if config.json exists. If not, we generate a new config.
if (!configFile.exists()) { // If the file already exists, we attempt to load it.
getLogger().info("config.json could not be found. Generating a default configuration ..."); try {
config = new ConfigContainer(); config = JsonUtils.loadToClass(configFile.toPath(), ConfigContainer.class);
Grasscutter.saveConfig(config); } catch (Exception exception) {
return; getLogger()
} .error(
"There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json.");
// If the file already exists, we attempt to load it. System.exit(1);
try { }
config = JsonUtils.loadToClass(configFile.toPath(), ConfigContainer.class); }
} catch (Exception exception) {
getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json."); /**
System.exit(1); * Saves the provided server configuration.
} *
} * @param config The configuration to save, or null for a new one.
*/
/** public static void saveConfig(@Nullable ConfigContainer config) {
* Saves the provided server configuration. if (config == null) config = new ConfigContainer();
*
* @param config The configuration to save, or null for a new one. try (FileWriter file = new FileWriter(configFile)) {
*/ file.write(JsonUtils.encode(config));
public static void saveConfig(@Nullable ConfigContainer config) { } catch (IOException ignored) {
if (config == null) config = new ConfigContainer(); logger.error("Unable to write to config file.");
} catch (Exception e) {
try (FileWriter file = new FileWriter(configFile)) { logger.error("Unable to save config file.", e);
file.write(JsonUtils.encode(config)); }
} catch (IOException ignored) { }
logger.error("Unable to write to config file.");
} catch (Exception e) { /*
logger.error("Unable to save config file.", e); * Getters for the various server components.
} */
}
public static Language getLanguage(String langCode) {
/* return Language.getLanguage(langCode);
* Getters for the various server components. }
*/
public static ServerRunMode getRunMode() {
public static Language getLanguage(String langCode) { return Grasscutter.runModeOverride != null ? Grasscutter.runModeOverride : SERVER.runMode;
return Language.getLanguage(langCode); }
}
public static LineReader getConsole() {
public static ServerRunMode getRunMode() { if (consoleLineReader == null) {
return Grasscutter.runModeOverride != null ? Grasscutter.runModeOverride : SERVER.runMode; Terminal terminal = null;
} try {
terminal = TerminalBuilder.builder().jna(true).build();
public static LineReader getConsole() { } catch (Exception e) {
if (consoleLineReader == null) { try {
Terminal terminal = null; // Fallback to a dumb jline terminal.
try { terminal = TerminalBuilder.builder().dumb(true).build();
terminal = TerminalBuilder.builder().jna(true).build(); } catch (Exception ignored) {
} catch (Exception e) { // When dumb is true, build() never throws.
try { }
// Fallback to a dumb jline terminal. }
terminal = TerminalBuilder.builder().dumb(true).build();
} catch (Exception ignored) { consoleLineReader = LineReaderBuilder.builder().terminal(terminal).build();
// When dumb is true, build() never throws. }
}
} return consoleLineReader;
}
consoleLineReader = LineReaderBuilder.builder()
.terminal(terminal) /*
.build(); * Utility methods.
} */
return consoleLineReader; public static void updateDayOfWeek() {
} Calendar calendar = Calendar.getInstance();
Grasscutter.currentDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
/* logger.debug("Set day of week to " + currentDayOfWeek);
* Utility methods. }
*/
public static void startConsole() {
public static void updateDayOfWeek() { // Console should not start in dispatch only mode.
Calendar calendar = Calendar.getInstance(); if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) {
Grasscutter.currentDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); logger.info(translate("messages.dispatch.no_commands_error"));
logger.debug("Set day of week to " + currentDayOfWeek); return;
} } else {
logger.info(translate("messages.status.done"));
public static void startConsole() { }
// Console should not start in dispatch only mode.
if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { String input = null;
logger.info(translate("messages.dispatch.no_commands_error")); var isLastInterrupted = false;
return; while (config.server.game.enableConsole) {
} else { try {
logger.info(translate("messages.status.done")); input = consoleLineReader.readLine("> ");
} } catch (UserInterruptException e) {
if (!isLastInterrupted) {
String input = null; isLastInterrupted = true;
var isLastInterrupted = false; logger.info("Press Ctrl-C again to shutdown.");
while (config.server.game.enableConsole) { continue;
try { } else {
input = consoleLineReader.readLine("> "); Runtime.getRuntime().exit(0);
} catch (UserInterruptException e) { }
if (!isLastInterrupted) { } catch (EndOfFileException e) {
isLastInterrupted = true; logger.info("EOF detected.");
logger.info("Press Ctrl-C again to shutdown."); continue;
continue; } catch (IOError e) {
} else { logger.error("An IO error occurred while trying to read from console.", e);
Runtime.getRuntime().exit(0); return;
} }
} catch (EndOfFileException e) {
logger.info("EOF detected."); isLastInterrupted = false;
continue;
} catch (IOError e) { try {
logger.error("An IO error occurred while trying to read from console.", e); commandMap.invoke(null, null, input);
return; } catch (Exception e) {
} logger.error(translate("messages.game.command_error"), e);
}
isLastInterrupted = false; }
}
try {
commandMap.invoke(null, null, input); /*
} catch (Exception e) { * Enums for the configuration.
logger.error(translate("messages.game.command_error"), e); */
}
} public enum ServerRunMode {
} HYBRID,
DISPATCH_ONLY,
/* GAME_ONLY
* Enums for the configuration. }
*/
public enum ServerDebugMode {
public enum ServerRunMode { ALL,
HYBRID, DISPATCH_ONLY, GAME_ONLY MISSING,
} WHITELIST,
BLACKLIST,
public enum ServerDebugMode { NONE
ALL, MISSING, WHITELIST, BLACKLIST, NONE }
} }
}

View File

@ -1,148 +1,133 @@
package emu.grasscutter.auth; package emu.grasscutter.auth;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.*; import emu.grasscutter.server.http.objects.*;
import io.javalin.http.Context; import io.javalin.http.Context;
import lombok.AllArgsConstructor; import javax.annotation.Nullable;
import lombok.Builder; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Builder;
import lombok.Getter;
import javax.annotation.Nullable;
/** Defines an authenticator for the server. Can be changed by plugins. */
/** public interface AuthenticationSystem {
* Defines an authenticator for the server.
* Can be changed by plugins. /**
*/ * Generates an authentication request from a {@link LoginAccountRequestJson} object.
public interface AuthenticationSystem { *
* @param ctx The Javalin context.
/** * @param jsonData The JSON data.
* Generates an authentication request from a {@link LoginAccountRequestJson} object. * @return An authentication request.
* */
* @param ctx The Javalin context. static AuthenticationRequest fromPasswordRequest(Context ctx, LoginAccountRequestJson jsonData) {
* @param jsonData The JSON data. return AuthenticationRequest.builder().context(ctx).passwordRequest(jsonData).build();
* @return An authentication request. }
*/
static AuthenticationRequest fromPasswordRequest(Context ctx, LoginAccountRequestJson jsonData) { /**
return AuthenticationRequest.builder() * Generates an authentication request from a {@link LoginTokenRequestJson} object.
.context(ctx) *
.passwordRequest(jsonData) * @param ctx The Javalin context.
.build(); * @param jsonData The JSON data.
} * @return An authentication request.
*/
/** static AuthenticationRequest fromTokenRequest(Context ctx, LoginTokenRequestJson jsonData) {
* Generates an authentication request from a {@link LoginTokenRequestJson} object. return AuthenticationRequest.builder().context(ctx).tokenRequest(jsonData).build();
* }
* @param ctx The Javalin context.
* @param jsonData The JSON data. /**
* @return An authentication request. * Generates an authentication request from a {@link ComboTokenReqJson} object.
*/ *
static AuthenticationRequest fromTokenRequest(Context ctx, LoginTokenRequestJson jsonData) { * @param ctx The Javalin context.
return AuthenticationRequest.builder() * @param jsonData The JSON data.
.context(ctx) * @return An authentication request.
.tokenRequest(jsonData) */
.build(); static AuthenticationRequest fromComboTokenRequest(
} Context ctx, ComboTokenReqJson jsonData, ComboTokenReqJson.LoginTokenData tokenData) {
return AuthenticationRequest.builder()
/** .context(ctx)
* Generates an authentication request from a {@link ComboTokenReqJson} object. .sessionKeyRequest(jsonData)
* .sessionKeyData(tokenData)
* @param ctx The Javalin context. .build();
* @param jsonData The JSON data. }
* @return An authentication request.
*/ /**
static AuthenticationRequest fromComboTokenRequest(Context ctx, ComboTokenReqJson jsonData, * Generates an authentication request from a {@link Context} object.
ComboTokenReqJson.LoginTokenData tokenData) { *
return AuthenticationRequest.builder() * @param ctx The Javalin context.
.context(ctx) * @return An authentication request.
.sessionKeyRequest(jsonData) */
.sessionKeyData(tokenData) static AuthenticationRequest fromExternalRequest(Context ctx) {
.build(); return AuthenticationRequest.builder().context(ctx).build();
} }
/** /**
* Generates an authentication request from a {@link Context} object. * Called when a user requests to make an account.
* *
* @param ctx The Javalin context. * @param username The provided username.
* @return An authentication request. * @param password The provided password. (SHA-256'ed)
*/ */
static AuthenticationRequest fromExternalRequest(Context ctx) { void createAccount(String username, String password);
return AuthenticationRequest.builder().context(ctx).build();
} /**
* Called when a user requests to reset their password.
/** *
* Called when a user requests to make an account. * @param username The username of the account to reset.
* */
* @param username The provided username. void resetPassword(String username);
* @param password The provided password. (SHA-256'ed)
*/ /**
void createAccount(String username, String password); * Called by plugins to internally verify a user's identity.
*
/** * @param details A unique identifier to identify the user. (For example: a JWT token)
* Called when a user requests to reset their password. * @return The user's account if the verification was successful, null if the user was unable to
* * be verified.
* @param username The username of the account to reset. */
*/ Account verifyUser(String details);
void resetPassword(String username);
/**
/** * This is the authenticator used for password authentication.
* Called by plugins to internally verify a user's identity. *
* * @return An authenticator.
* @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. Authenticator<LoginResultJson> getPasswordAuthenticator();
*/
Account verifyUser(String details); /**
* This is the authenticator used for token authentication.
/** *
* This is the authenticator used for password authentication. * @return An authenticator.
* */
* @return An authenticator. Authenticator<LoginResultJson> getTokenAuthenticator();
*/
Authenticator<LoginResultJson> getPasswordAuthenticator(); /**
* This is the authenticator used for session authentication.
/** *
* This is the authenticator used for token authentication. * @return An authenticator.
* */
* @return An authenticator. Authenticator<ComboTokenResJson> getSessionKeyAuthenticator();
*/
Authenticator<LoginResultJson> getTokenAuthenticator(); /**
* This is the authenticator used for handling external authentication requests.
/** *
* This is the authenticator used for session authentication. * @return An authenticator.
* */
* @return An authenticator. ExternalAuthenticator getExternalAuthenticator();
*/
Authenticator<ComboTokenResJson> getSessionKeyAuthenticator(); /**
* This is the authenticator used for handling OAuth authentication requests.
/** *
* This is the authenticator used for handling external authentication requests. * @return An authenticator.
* */
* @return An authenticator. OAuthAuthenticator getOAuthAuthenticator();
*/
ExternalAuthenticator getExternalAuthenticator(); /** A data container that holds relevant data for authenticating a client. */
@Builder
/** @AllArgsConstructor
* This is the authenticator used for handling OAuth authentication requests. @Getter
* class AuthenticationRequest {
* @return An authenticator. private final Context context;
*/
OAuthAuthenticator getOAuthAuthenticator(); @Nullable private final LoginAccountRequestJson passwordRequest;
@Nullable private final LoginTokenRequestJson tokenRequest;
/** @Nullable private final ComboTokenReqJson sessionKeyRequest;
* A data container that holds relevant data for authenticating a client. @Nullable private final ComboTokenReqJson.LoginTokenData sessionKeyData;
*/ }
@Builder }
@AllArgsConstructor
@Getter
class AuthenticationRequest {
private final Context context;
@Nullable
private final LoginAccountRequestJson passwordRequest;
@Nullable
private final LoginTokenRequestJson tokenRequest;
@Nullable
private final ComboTokenReqJson sessionKeyRequest;
@Nullable
private final ComboTokenReqJson.LoginTokenData sessionKeyData;
}
}

View File

@ -1,20 +1,22 @@
package emu.grasscutter.auth; package emu.grasscutter.auth;
import emu.grasscutter.server.http.objects.ComboTokenResJson; import emu.grasscutter.server.http.objects.ComboTokenResJson;
import emu.grasscutter.server.http.objects.LoginResultJson; import emu.grasscutter.server.http.objects.LoginResultJson;
/** /**
* Handles username/password authentication from the client. * Handles username/password authentication from the client.
* *
* @param <T> The response object type. Should be {@link LoginResultJson} or {@link ComboTokenResJson} * @param <T> The response object type. Should be {@link LoginResultJson} or {@link
*/ * ComboTokenResJson}
public interface Authenticator<T> { */
public interface Authenticator<T> {
/**
* Attempt to authenticate the client with the provided credentials. /**
* * Attempt to authenticate the client with the provided credentials.
* @param request The authentication request wrapped in a {@link AuthenticationSystem.AuthenticationRequest} object. *
* @return The result of the login in an object. * @param request The authentication request wrapped in a {@link
*/ * AuthenticationSystem.AuthenticationRequest} object.
T authenticate(AuthenticationSystem.AuthenticationRequest request); * @return The result of the login in an object.
} */
T authenticate(AuthenticationSystem.AuthenticationRequest request);
}

View File

@ -1,71 +1,72 @@
package emu.grasscutter.auth; package emu.grasscutter.auth;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.config.Configuration.ACCOUNT;
import emu.grasscutter.auth.DefaultAuthenticators.*; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.ComboTokenResJson; import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.http.objects.LoginResultJson; import emu.grasscutter.auth.DefaultAuthenticators.*;
import emu.grasscutter.game.Account;
import static emu.grasscutter.config.Configuration.ACCOUNT; import emu.grasscutter.server.http.objects.ComboTokenResJson;
import static emu.grasscutter.utils.Language.translate; import emu.grasscutter.server.http.objects.LoginResultJson;
/** /**
* The default Grasscutter authentication implementation. * The default Grasscutter authentication implementation. Allows all users to access any account.
* Allows all users to access any account. */
*/ public final class DefaultAuthentication implements AuthenticationSystem {
public final class DefaultAuthentication implements AuthenticationSystem { private final Authenticator<LoginResultJson> passwordAuthenticator;
private final Authenticator<LoginResultJson> passwordAuthenticator; private final Authenticator<LoginResultJson> tokenAuthenticator = new TokenAuthenticator();
private final Authenticator<LoginResultJson> tokenAuthenticator = new TokenAuthenticator(); private final Authenticator<ComboTokenResJson> sessionKeyAuthenticator =
private final Authenticator<ComboTokenResJson> sessionKeyAuthenticator = new SessionKeyAuthenticator(); new SessionKeyAuthenticator();
private final ExternalAuthenticator externalAuthenticator = new ExternalAuthentication(); private final ExternalAuthenticator externalAuthenticator = new ExternalAuthentication();
private final OAuthAuthenticator oAuthAuthenticator = new OAuthAuthentication(); private final OAuthAuthenticator oAuthAuthenticator = new OAuthAuthentication();
public DefaultAuthentication() { public DefaultAuthentication() {
if (ACCOUNT.EXPERIMENTAL_RealPassword) { if (ACCOUNT.EXPERIMENTAL_RealPassword) {
passwordAuthenticator = new ExperimentalPasswordAuthenticator(); passwordAuthenticator = new ExperimentalPasswordAuthenticator();
} else { } else {
passwordAuthenticator = new PasswordAuthenticator(); passwordAuthenticator = new PasswordAuthenticator();
} }
} }
@Override @Override
public void createAccount(String username, String password) { public void createAccount(String username, String password) {
// Unhandled. The default authenticator doesn't store passwords. // Unhandled. The default authenticator doesn't store passwords.
} }
@Override @Override
public void resetPassword(String username) { public void resetPassword(String username) {
// Unhandled. The default authenticator doesn't store passwords. // Unhandled. The default authenticator doesn't store passwords.
} }
@Override @Override
public Account verifyUser(String details) { public Account verifyUser(String details) {
Grasscutter.getLogger().info(translate("messages.dispatch.authentication.default_unable_to_verify")); Grasscutter.getLogger()
return null; .info(translate("messages.dispatch.authentication.default_unable_to_verify"));
} return null;
}
@Override
public Authenticator<LoginResultJson> getPasswordAuthenticator() { @Override
return this.passwordAuthenticator; public Authenticator<LoginResultJson> getPasswordAuthenticator() {
} return this.passwordAuthenticator;
}
@Override
public Authenticator<LoginResultJson> getTokenAuthenticator() { @Override
return this.tokenAuthenticator; public Authenticator<LoginResultJson> getTokenAuthenticator() {
} return this.tokenAuthenticator;
}
@Override
public Authenticator<ComboTokenResJson> getSessionKeyAuthenticator() { @Override
return this.sessionKeyAuthenticator; public Authenticator<ComboTokenResJson> getSessionKeyAuthenticator() {
} return this.sessionKeyAuthenticator;
}
@Override
public ExternalAuthenticator getExternalAuthenticator() { @Override
return this.externalAuthenticator; public ExternalAuthenticator getExternalAuthenticator() {
} return this.externalAuthenticator;
}
@Override
public OAuthAuthenticator getOAuthAuthenticator() { @Override
return this.oAuthAuthenticator; public OAuthAuthenticator getOAuthAuthenticator() {
} return this.oAuthAuthenticator;
} }
}

View File

@ -1,346 +1,363 @@
package emu.grasscutter.auth; package emu.grasscutter.auth;
import at.favre.lib.crypto.bcrypt.BCrypt; import static emu.grasscutter.config.Configuration.ACCOUNT;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest;
import emu.grasscutter.database.DatabaseHelper; import at.favre.lib.crypto.bcrypt.BCrypt;
import emu.grasscutter.game.Account; import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.http.objects.ComboTokenResJson; import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest;
import emu.grasscutter.server.http.objects.LoginResultJson; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.game.Account;
import emu.grasscutter.utils.Utils; import emu.grasscutter.server.http.objects.ComboTokenResJson;
import emu.grasscutter.server.http.objects.LoginResultJson;
import javax.crypto.Cipher; import emu.grasscutter.utils.FileUtils;
import java.nio.charset.StandardCharsets; import emu.grasscutter.utils.Utils;
import java.security.KeyFactory; import java.nio.charset.StandardCharsets;
import java.security.interfaces.RSAPrivateKey; import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import static emu.grasscutter.config.Configuration.ACCOUNT; import javax.crypto.Cipher;
import static emu.grasscutter.utils.Language.translate;
/** A class containing default authenticators. */
/** public final class DefaultAuthenticators {
* A class containing default authenticators.
*/ /** Handles the authentication request from the username and password form. */
public final class DefaultAuthenticators { public static class PasswordAuthenticator implements Authenticator<LoginResultJson> {
@Override
/** public LoginResultJson authenticate(AuthenticationRequest request) {
* Handles the authentication request from the username and password form. var response = new LoginResultJson();
*/
public static class PasswordAuthenticator implements Authenticator<LoginResultJson> { var requestData = request.getPasswordRequest();
@Override assert requestData != null; // This should never be null.
public LoginResultJson authenticate(AuthenticationRequest request) { int playerCount = Grasscutter.getGameServer().getPlayers().size();
var response = new LoginResultJson();
boolean successfulLogin = false;
var requestData = request.getPasswordRequest(); String address = request.getContext().ip();
assert requestData != null; // This should never be null. String responseMessage = translate("messages.dispatch.account.username_error");
int playerCount = Grasscutter.getGameServer().getPlayers().size(); String loggerMessage = "";
boolean successfulLogin = false; // Get account from database.
String address = request.getContext().ip(); Account account = DatabaseHelper.getAccountByName(requestData.account);
String responseMessage = translate("messages.dispatch.account.username_error"); if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
String loggerMessage = ""; // Check if account exists.
if (account == null && ACCOUNT.autoCreate) {
// Get account from database. // This account has been created AUTOMATICALLY. There will be no permissions added.
Account account = DatabaseHelper.getAccountByName(requestData.account); account = DatabaseHelper.createAccountWithUid(requestData.account, 0);
if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
// Check if account exists. // Check if the account was created successfully.
if (account == null && ACCOUNT.autoCreate) { if (account == null) {
// This account has been created AUTOMATICALLY. There will be no permissions added. responseMessage = translate("messages.dispatch.account.username_create_error");
account = DatabaseHelper.createAccountWithUid(requestData.account, 0); Grasscutter.getLogger()
.info(translate("messages.dispatch.account.account_login_create_error", address));
// Check if the account was created successfully. } else {
if (account == null) { // Continue with login.
responseMessage = translate("messages.dispatch.account.username_create_error"); successfulLogin = true;
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", address));
} else { // Log the creation.
// Continue with login. Grasscutter.getLogger()
successfulLogin = true; .info(
translate(
// Log the creation. "messages.dispatch.account.account_login_create_success",
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", address, response.data.account.uid)); address,
} response.data.account.uid));
} else if (account != null) }
successfulLogin = true; } else if (account != null) successfulLogin = true;
else else
loggerMessage = translate("messages.dispatch.account.account_login_exist_error", address); loggerMessage = translate("messages.dispatch.account.account_login_exist_error", address);
} else { } else {
responseMessage = translate("messages.dispatch.account.server_max_player_limit"); responseMessage = translate("messages.dispatch.account.server_max_player_limit");
loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address); loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
} }
// Set response data.
// Set response data. if (successfulLogin) {
if (successfulLogin) { response.message = "OK";
response.message = "OK"; response.data.account.uid = account.getId();
response.data.account.uid = account.getId(); response.data.account.token = account.generateSessionKey();
response.data.account.token = account.generateSessionKey(); response.data.account.email = account.getEmail();
response.data.account.email = account.getEmail();
loggerMessage =
loggerMessage = translate("messages.dispatch.account.login_success", address, account.getId()); translate("messages.dispatch.account.login_success", address, account.getId());
} else { } else {
response.retcode = -201; response.retcode = -201;
response.message = responseMessage; response.message = responseMessage;
}
} Grasscutter.getLogger().info(loggerMessage);
Grasscutter.getLogger().info(loggerMessage);
return response;
return response; }
} }
}
public static class ExperimentalPasswordAuthenticator implements Authenticator<LoginResultJson> {
public static class ExperimentalPasswordAuthenticator implements Authenticator<LoginResultJson> { @Override
@Override public LoginResultJson authenticate(AuthenticationRequest request) {
public LoginResultJson authenticate(AuthenticationRequest request) { var response = new LoginResultJson();
var response = new LoginResultJson();
var requestData = request.getPasswordRequest();
var requestData = request.getPasswordRequest(); assert requestData != null; // This should never be null.
assert requestData != null; // This should never be null. int playerCount = Grasscutter.getGameServer().getPlayers().size();
int playerCount = Grasscutter.getGameServer().getPlayers().size();
boolean successfulLogin = false;
boolean successfulLogin = false; String address = request.getContext().ip();
String address = request.getContext().ip(); String responseMessage = translate("messages.dispatch.account.username_error");
String responseMessage = translate("messages.dispatch.account.username_error"); String loggerMessage = "";
String loggerMessage = ""; String decryptedPassword = "";
String decryptedPassword = ""; try {
try { byte[] key = FileUtils.readResource("/keys/auth_private-key.der");
byte[] key = FileUtils.readResource("/keys/auth_private-key.der"); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); KeyFactory keyFactory = KeyFactory.getInstance("RSA");
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPrivateKey private_key = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
RSAPrivateKey private_key = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, private_key);
cipher.init(Cipher.DECRYPT_MODE, private_key);
decryptedPassword =
decryptedPassword = new String(cipher.doFinal(Utils.base64Decode(request.getPasswordRequest().password)), StandardCharsets.UTF_8); new String(
} catch (Exception ignored) { cipher.doFinal(Utils.base64Decode(request.getPasswordRequest().password)),
decryptedPassword = request.getPasswordRequest().password; StandardCharsets.UTF_8);
} } catch (Exception ignored) {
decryptedPassword = request.getPasswordRequest().password;
if (decryptedPassword == null) { }
successfulLogin = false;
loggerMessage = translate("messages.dispatch.account.login_password_error", address); if (decryptedPassword == null) {
responseMessage = translate("messages.dispatch.account.password_error"); successfulLogin = false;
} loggerMessage = translate("messages.dispatch.account.login_password_error", address);
responseMessage = translate("messages.dispatch.account.password_error");
// Get account from database. }
Account account = DatabaseHelper.getAccountByName(requestData.account);
if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) { // Get account from database.
// Check if account exists. Account account = DatabaseHelper.getAccountByName(requestData.account);
if (account == null && ACCOUNT.autoCreate) { if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
// This account has been created AUTOMATICALLY. There will be no permissions added. // Check if account exists.
if (decryptedPassword.length() >= 8) { if (account == null && ACCOUNT.autoCreate) {
account = DatabaseHelper.createAccountWithUid(requestData.account, 0); // This account has been created AUTOMATICALLY. There will be no permissions added.
account.setPassword(BCrypt.withDefaults().hashToString(12, decryptedPassword.toCharArray())); if (decryptedPassword.length() >= 8) {
account.save(); account = DatabaseHelper.createAccountWithUid(requestData.account, 0);
account.setPassword(
// Check if the account was created successfully. BCrypt.withDefaults().hashToString(12, decryptedPassword.toCharArray()));
if (account == null) { account.save();
responseMessage = translate("messages.dispatch.account.username_create_error");
loggerMessage = translate("messages.dispatch.account.account_login_create_error", address); // Check if the account was created successfully.
} else { if (account == null) {
// Continue with login. responseMessage = translate("messages.dispatch.account.username_create_error");
successfulLogin = true; loggerMessage =
translate("messages.dispatch.account.account_login_create_error", address);
// Log the creation. } else {
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", address, response.data.account.uid)); // Continue with login.
} successfulLogin = true;
} else {
successfulLogin = false; // Log the creation.
loggerMessage = translate("messages.dispatch.account.login_password_error", address); Grasscutter.getLogger()
responseMessage = translate("messages.dispatch.account.password_length_error"); .info(
} translate(
} else if (account != null) { "messages.dispatch.account.account_login_create_success",
if (account.getPassword() != null && !account.getPassword().isEmpty()) { address,
if (BCrypt.verifyer().verify(decryptedPassword.toCharArray(), account.getPassword()).verified) { response.data.account.uid));
successfulLogin = true; }
} else { } else {
successfulLogin = false; successfulLogin = false;
loggerMessage = translate("messages.dispatch.account.login_password_error", address); loggerMessage = translate("messages.dispatch.account.login_password_error", address);
responseMessage = translate("messages.dispatch.account.password_error"); responseMessage = translate("messages.dispatch.account.password_length_error");
} }
} else { } else if (account != null) {
successfulLogin = false; if (account.getPassword() != null && !account.getPassword().isEmpty()) {
loggerMessage = translate("messages.dispatch.account.login_password_storage_error", address); if (BCrypt.verifyer()
responseMessage = translate("messages.dispatch.account.password_storage_error"); .verify(decryptedPassword.toCharArray(), account.getPassword())
} .verified) {
} else { successfulLogin = true;
loggerMessage = translate("messages.dispatch.account.account_login_exist_error", address); } else {
} successfulLogin = false;
} else { loggerMessage = translate("messages.dispatch.account.login_password_error", address);
responseMessage = translate("messages.dispatch.account.server_max_player_limit"); responseMessage = translate("messages.dispatch.account.password_error");
loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address); }
} } else {
successfulLogin = false;
loggerMessage =
// Set response data. translate("messages.dispatch.account.login_password_storage_error", address);
if (successfulLogin) { responseMessage = translate("messages.dispatch.account.password_storage_error");
response.message = "OK"; }
response.data.account.uid = account.getId(); } else {
response.data.account.token = account.generateSessionKey(); loggerMessage = translate("messages.dispatch.account.account_login_exist_error", address);
response.data.account.email = account.getEmail(); }
} else {
loggerMessage = translate("messages.dispatch.account.login_success", address, account.getId()); responseMessage = translate("messages.dispatch.account.server_max_player_limit");
} else { loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
response.retcode = -201; }
response.message = responseMessage;
// Set response data.
} if (successfulLogin) {
Grasscutter.getLogger().info(loggerMessage); response.message = "OK";
response.data.account.uid = account.getId();
return response; response.data.account.token = account.generateSessionKey();
} response.data.account.email = account.getEmail();
}
loggerMessage =
/** translate("messages.dispatch.account.login_success", address, account.getId());
* Handles the authentication request from the game when using a registry token. } else {
*/ response.retcode = -201;
public static class TokenAuthenticator implements Authenticator<LoginResultJson> { response.message = responseMessage;
@Override }
public LoginResultJson authenticate(AuthenticationRequest request) { Grasscutter.getLogger().info(loggerMessage);
var response = new LoginResultJson();
return response;
var requestData = request.getTokenRequest(); }
assert requestData != null; }
boolean successfulLogin; /** Handles the authentication request from the game when using a registry token. */
String address = request.getContext().ip(); public static class TokenAuthenticator implements Authenticator<LoginResultJson> {
String loggerMessage; @Override
int playerCount = Grasscutter.getGameServer().getPlayers().size(); public LoginResultJson authenticate(AuthenticationRequest request) {
var response = new LoginResultJson();
// Log the attempt.
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_attempt", address)); var requestData = request.getTokenRequest();
assert requestData != null;
if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
boolean successfulLogin;
// Get account from database. String address = request.getContext().ip();
Account account = DatabaseHelper.getAccountById(requestData.uid); String loggerMessage;
int playerCount = Grasscutter.getGameServer().getPlayers().size();
// Check if account exists/token is valid.
successfulLogin = account != null && account.getSessionKey().equals(requestData.token); // Log the attempt.
Grasscutter.getLogger()
// Set response data. .info(translate("messages.dispatch.account.login_token_attempt", address));
if (successfulLogin) {
response.message = "OK"; if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
response.data.account.uid = account.getId();
response.data.account.token = account.getSessionKey(); // Get account from database.
response.data.account.email = account.getEmail(); Account account = DatabaseHelper.getAccountById(requestData.uid);
// Log the login. // Check if account exists/token is valid.
loggerMessage = translate("messages.dispatch.account.login_token_success", address, requestData.uid); successfulLogin = account != null && account.getSessionKey().equals(requestData.token);
} else {
response.retcode = -201; // Set response data.
response.message = translate("messages.dispatch.account.account_cache_error"); if (successfulLogin) {
response.message = "OK";
// Log the failure. response.data.account.uid = account.getId();
loggerMessage = translate("messages.dispatch.account.login_token_error", address); response.data.account.token = account.getSessionKey();
} response.data.account.email = account.getEmail();
} else { // Log the login.
response.retcode = -201; loggerMessage =
response.message = translate("messages.dispatch.account.server_max_player_limit"); translate("messages.dispatch.account.login_token_success", address, requestData.uid);
} else {
loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address); response.retcode = -201;
} response.message = translate("messages.dispatch.account.account_cache_error");
Grasscutter.getLogger().info(loggerMessage); // Log the failure.
return response; loggerMessage = translate("messages.dispatch.account.login_token_error", address);
} }
}
} else {
/** response.retcode = -201;
* Handles the authentication request from the game when using a combo token/session key. response.message = translate("messages.dispatch.account.server_max_player_limit");
*/
public static class SessionKeyAuthenticator implements Authenticator<ComboTokenResJson> { loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
@Override }
public ComboTokenResJson authenticate(AuthenticationRequest request) {
var response = new ComboTokenResJson(); Grasscutter.getLogger().info(loggerMessage);
return response;
var requestData = request.getSessionKeyRequest(); }
var loginData = request.getSessionKeyData(); }
assert requestData != null;
assert loginData != null; /** Handles the authentication request from the game when using a combo token/session key. */
public static class SessionKeyAuthenticator implements Authenticator<ComboTokenResJson> {
boolean successfulLogin; @Override
String address = request.getContext().ip(); public ComboTokenResJson authenticate(AuthenticationRequest request) {
String loggerMessage; var response = new ComboTokenResJson();
int playerCount = Grasscutter.getGameServer().getPlayers().size();
var requestData = request.getSessionKeyRequest();
if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) { var loginData = request.getSessionKeyData();
// Get account from database. assert requestData != null;
Account account = DatabaseHelper.getAccountById(loginData.uid); assert loginData != null;
// Check if account exists/token is valid. boolean successfulLogin;
successfulLogin = account != null && account.getSessionKey().equals(loginData.token); String address = request.getContext().ip();
String loggerMessage;
// Set response data. int playerCount = Grasscutter.getGameServer().getPlayers().size();
if (successfulLogin) {
response.message = "OK"; if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
response.data.open_id = account.getId(); // Get account from database.
response.data.combo_id = "157795300"; Account account = DatabaseHelper.getAccountById(loginData.uid);
response.data.combo_token = account.generateLoginToken();
// Check if account exists/token is valid.
// Log the login. successfulLogin = account != null && account.getSessionKey().equals(loginData.token);
loggerMessage = translate("messages.dispatch.account.combo_token_success", address);
// Set response data.
} else { if (successfulLogin) {
response.retcode = -201; response.message = "OK";
response.message = translate("messages.dispatch.account.session_key_error"); response.data.open_id = account.getId();
response.data.combo_id = "157795300";
// Log the failure. response.data.combo_token = account.generateLoginToken();
loggerMessage = translate("messages.dispatch.account.combo_token_error", address);
} // Log the login.
} else { loggerMessage = translate("messages.dispatch.account.combo_token_success", address);
response.retcode = -201;
response.message = translate("messages.dispatch.account.server_max_player_limit"); } else {
response.retcode = -201;
loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address); response.message = translate("messages.dispatch.account.session_key_error");
}
// Log the failure.
Grasscutter.getLogger().info(loggerMessage); loggerMessage = translate("messages.dispatch.account.combo_token_error", address);
return response; }
} } else {
} response.retcode = -201;
response.message = translate("messages.dispatch.account.server_max_player_limit");
/**
* Handles authentication requests from external sources. loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
*/ }
public static class ExternalAuthentication implements ExternalAuthenticator {
@Override Grasscutter.getLogger().info(loggerMessage);
public void handleLogin(AuthenticationRequest request) { return response;
request.getContext().result("Authentication is not available with the default authentication method."); }
} }
@Override /** Handles authentication requests from external sources. */
public void handleAccountCreation(AuthenticationRequest request) { public static class ExternalAuthentication implements ExternalAuthenticator {
request.getContext().result("Authentication is not available with the default authentication method."); @Override
} public void handleLogin(AuthenticationRequest request) {
request
@Override .getContext()
public void handlePasswordReset(AuthenticationRequest request) { .result("Authentication is not available with the default authentication method.");
request.getContext().result("Authentication is not available with the default authentication method."); }
}
} @Override
public void handleAccountCreation(AuthenticationRequest request) {
/** request
* Handles authentication requests from OAuth sources.Zenlith .getContext()
*/ .result("Authentication is not available with the default authentication method.");
public static class OAuthAuthentication implements OAuthAuthenticator { }
@Override
public void handleLogin(AuthenticationRequest request) { @Override
request.getContext().result("Authentication is not available with the default authentication method."); public void handlePasswordReset(AuthenticationRequest request) {
} request
.getContext()
@Override .result("Authentication is not available with the default authentication method.");
public void handleRedirection(AuthenticationRequest request, ClientType type) { }
request.getContext().result("Authentication is not available with the default authentication method."); }
}
/** Handles authentication requests from OAuth sources.Zenlith */
@Override public static class OAuthAuthentication implements OAuthAuthenticator {
public void handleTokenProcess(AuthenticationRequest request) { @Override
request.getContext().result("Authentication is not available with the default authentication method."); public void handleLogin(AuthenticationRequest request) {
} request
} .getContext()
} .result("Authentication is not available with the default authentication method.");
}
@Override
public void handleRedirection(AuthenticationRequest request, ClientType type) {
request
.getContext()
.result("Authentication is not available with the default authentication method.");
}
@Override
public void handleTokenProcess(AuthenticationRequest request) {
request
.getContext()
.result("Authentication is not available with the default authentication method.");
}
}
}

View File

@ -1,36 +1,32 @@
package emu.grasscutter.auth; package emu.grasscutter.auth;
import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest; import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest;
/** /** Handles authentication via external routes. */
* Handles authentication via external routes. public interface ExternalAuthenticator {
*/
public interface ExternalAuthenticator { /**
* Called when an external login request is made.
/** *
* Called when an external login request is made. * @param request The authentication request.
* */
* @param request The authentication request. void handleLogin(AuthenticationRequest request);
*/
void handleLogin(AuthenticationRequest request); /**
* Called when an external account creation request is made.
/** *
* Called when an external account creation request is made. * @param request The authentication request.
* * <p>For developers: Use AuthenticationRequest#getRequest() to get the request body. Use
* @param request The authentication request. * AuthenticationRequest#getResponse() to get the response body.
* <p> */
* For developers: Use AuthenticationRequest#getRequest() to get the request body. void handleAccountCreation(AuthenticationRequest request);
* Use AuthenticationRequest#getResponse() to get the response body.
*/ /**
void handleAccountCreation(AuthenticationRequest request); * Called when an external password reset request is made.
*
/** * @param request The authentication request.
* Called when an external password reset request is made. * <p>For developers: Use AuthenticationRequest#getRequest() to get the request body. Use
* * AuthenticationRequest#getResponse() to get the response body.
* @param request The authentication request. */
* <p> void handlePasswordReset(AuthenticationRequest request);
* For developers: Use AuthenticationRequest#getRequest() to get the request body. }
* Use AuthenticationRequest#getResponse() to get the response body.
*/
void handlePasswordReset(AuthenticationRequest request);
}

View File

@ -1,38 +1,34 @@
package emu.grasscutter.auth; package emu.grasscutter.auth;
import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest; import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest;
/** /** Handles authentication via OAuth routes. */
* Handles authentication via OAuth routes. public interface OAuthAuthenticator {
*/
public interface OAuthAuthenticator { /**
* Called when an OAuth login request is made.
/** *
* Called when an OAuth login request is made. * @param request The authentication request.
* */
* @param request The authentication request. void handleLogin(AuthenticationRequest request);
*/
void handleLogin(AuthenticationRequest request); /**
* Called when a client requests to redirect to login page.
/** *
* Called when a client requests to redirect to login page. * @param request The authentication request.
* */
* @param request The authentication request. void handleRedirection(AuthenticationRequest request, ClientType clientType);
*/
void handleRedirection(AuthenticationRequest request, ClientType clientType); /**
* Called when an OAuth login requests callback.
/** *
* Called when an OAuth login requests callback. * @param request The authentication request.
* */
* @param request The authentication request. void handleTokenProcess(AuthenticationRequest request);
*/
void handleTokenProcess(AuthenticationRequest request); /** The type of the client. Used for handling redirection. */
enum ClientType {
/** DESKTOP,
* The type of the client. MOBILE
* Used for handling redirection. }
*/ }
enum ClientType {
DESKTOP, MOBILE
}
}

View File

@ -1,28 +1,28 @@
package emu.grasscutter.command; package emu.grasscutter.command;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Command { public @interface Command {
String label() default ""; String label() default "";
String[] aliases() default {}; String[] aliases() default {};
String[] usage() default {""}; String[] usage() default {""};
String permission() default ""; String permission() default "";
String permissionTargeted() default ""; String permissionTargeted() default "";
TargetRequirement targetRequirement() default TargetRequirement.ONLINE; TargetRequirement targetRequirement() default TargetRequirement.ONLINE;
boolean threading() default false; boolean threading() default false;
enum TargetRequirement { enum TargetRequirement {
NONE, // targetPlayer is not required NONE, // targetPlayer is not required
OFFLINE, // targetPlayer must be offline OFFLINE, // targetPlayer must be offline
PLAYER, // targetPlayer can be online or offline PLAYER, // targetPlayer can be online or offline
ONLINE // targetPlayer must be online ONLINE // targetPlayer must be online
} }
} }

View File

@ -1,90 +1,88 @@
package emu.grasscutter.command; package emu.grasscutter.command;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player;
import java.util.List; import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent;
import java.util.StringJoiner; import java.util.List;
import java.util.StringJoiner;
import static emu.grasscutter.utils.Language.translate;
public interface CommandHandler {
public interface CommandHandler {
/**
/** * Send a message to the target.
* Send a message to the target. *
* * @param player The player to send the message to, or null for the server console.
* @param player The player to send the message to, or null for the server console. * @param message The message to send.
* @param message The message to send. */
*/ static void sendMessage(Player player, String message) {
static void sendMessage(Player player, String message) { // Call command feedback event.
// Call command feedback event. ReceiveCommandFeedbackEvent event = new ReceiveCommandFeedbackEvent(player, message);
ReceiveCommandFeedbackEvent event = new ReceiveCommandFeedbackEvent(player, message); event.call();
event.call(); if (event.isCanceled()) { // If event is not cancelled, continue.
if (event.isCanceled()) { // If event is not cancelled, continue. return;
return; }
}
// Send message to target.
// Send message to target. if (player == null) {
if (player == null) { Grasscutter.getLogger().info(event.getMessage());
Grasscutter.getLogger().info(event.getMessage()); } else {
} else { player.dropMessage(event.getMessage().replace("\n\t", "\n\n"));
player.dropMessage(event.getMessage().replace("\n\t", "\n\n")); }
} }
}
static void sendTranslatedMessage(Player player, String messageKey, Object... args) {
static void sendTranslatedMessage(Player player, String messageKey, Object... args) { sendMessage(player, translate(player, messageKey, args));
sendMessage(player, translate(player, messageKey, args)); }
}
default String getUsageString(Player player, String... args) {
default String getUsageString(Player player, String... args) { Command annotation = this.getClass().getAnnotation(Command.class);
Command annotation = this.getClass().getAnnotation(Command.class); String usage_prefix = translate(player, "commands.execution.usage_prefix");
String usage_prefix = translate(player, "commands.execution.usage_prefix"); String command = annotation.label();
String command = annotation.label(); for (String alias : annotation.aliases()) {
for (String alias : annotation.aliases()) { if (alias.length() < command.length()) command = alias;
if (alias.length() < command.length()) }
command = alias; if (player != null) {
} command = "/" + command;
if (player != null) { }
command = "/" + command; String target =
} switch (annotation.targetRequirement()) {
String target = switch (annotation.targetRequirement()) { case NONE -> "";
case NONE -> ""; case OFFLINE -> "@<UID> "; // TODO: make translation keys for offline and online players
case OFFLINE -> "@<UID> "; // TODO: make translation keys for offline and online players case ONLINE -> (player == null)
case ONLINE -> ? "@<UID> "
(player == null) ? "@<UID> " : "[@<UID>] "; // TODO: make translation keys for offline and online players : "[@<UID>] "; // TODO: make translation keys for offline and online players
case PLAYER -> (player == null) ? "@<UID> " : "[@<UID>] "; case PLAYER -> (player == null) ? "@<UID> " : "[@<UID>] ";
}; };
String[] usages = annotation.usage(); String[] usages = annotation.usage();
StringJoiner joiner = new StringJoiner("\n\t"); StringJoiner joiner = new StringJoiner("\n\t");
for (String usage : usages) for (String usage : usages) joiner.add(usage_prefix + command + " " + target + usage);
joiner.add(usage_prefix + command + " " + target + usage); return joiner.toString();
return joiner.toString(); }
}
default void sendUsageMessage(Player player, String... args) {
default void sendUsageMessage(Player player, String... args) { sendMessage(player, getUsageString(player, args));
sendMessage(player, getUsageString(player, args)); }
}
default String getLabel() {
default String getLabel() { return this.getClass().getAnnotation(Command.class).label();
return this.getClass().getAnnotation(Command.class).label(); }
}
default String getDescriptionKey() {
default String getDescriptionKey() { Command annotation = this.getClass().getAnnotation(Command.class);
Command annotation = this.getClass().getAnnotation(Command.class); return "commands.%s.description".formatted(annotation.label());
return "commands.%s.description".formatted(annotation.label()); }
}
default String getDescriptionString(Player player) {
default String getDescriptionString(Player player) { return translate(player, getDescriptionKey());
return translate(player, getDescriptionKey()); }
}
/**
/** * Called when a player/console invokes a command.
* Called when a player/console invokes a command. *
* * @param sender The player/console that invoked the command.
* @param sender The player/console that invoked the command. * @param args The arguments to the command.
* @param args The arguments to the command. */
*/ default void execute(Player sender, Player targetPlayer, List<String> args) {}
default void execute(Player sender, Player targetPlayer, List<String> args) { }
}
}

View File

@ -1,50 +1,57 @@
package emu.grasscutter.command; package emu.grasscutter.command;
import javax.annotation.Nonnull; import java.util.List;
import java.util.List; import java.util.Map;
import java.util.Map; import java.util.function.BiConsumer;
import java.util.function.BiConsumer; import java.util.regex.Matcher;
import java.util.regex.Matcher; import java.util.regex.Pattern;
import java.util.regex.Pattern; import javax.annotation.Nonnull;
public class CommandHelpers { public class CommandHelpers {
public static final Pattern lvlRegex = Pattern.compile("(?<!\\w)l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :( public static final Pattern lvlRegex =
public static final Pattern amountRegex = Pattern.compile("((?<=(?<!\\w)x)\\d+|\\d+(?=x)(?!x\\d))"); Pattern.compile("(?<!\\w)l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :(
public static final Pattern refineRegex = Pattern.compile("(?<!\\w)r(\\d+)"); public static final Pattern amountRegex =
public static final Pattern rankRegex = Pattern.compile("(\\d+)\\*"); Pattern.compile("((?<=(?<!\\w)x)\\d+|\\d+(?=x)(?!x\\d))");
public static final Pattern constellationRegex = Pattern.compile("(?<!\\w)c(\\d+)"); public static final Pattern refineRegex = Pattern.compile("(?<!\\w)r(\\d+)");
public static final Pattern skillLevelRegex = Pattern.compile("sl(\\d+)"); public static final Pattern rankRegex = Pattern.compile("(\\d+)\\*");
public static final Pattern stateRegex = Pattern.compile("state(\\d+)"); public static final Pattern constellationRegex = Pattern.compile("(?<!\\w)c(\\d+)");
public static final Pattern blockRegex = Pattern.compile("blk(\\d+)"); public static final Pattern skillLevelRegex = Pattern.compile("sl(\\d+)");
public static final Pattern groupRegex = Pattern.compile("grp(\\d+)"); public static final Pattern stateRegex = Pattern.compile("state(\\d+)");
public static final Pattern configRegex = Pattern.compile("cfg(\\d+)"); public static final Pattern blockRegex = Pattern.compile("blk(\\d+)");
public static final Pattern hpRegex = Pattern.compile("(?<!\\w)hp(\\d+)"); public static final Pattern groupRegex = Pattern.compile("grp(\\d+)");
public static final Pattern maxHPRegex = Pattern.compile("maxhp(\\d+)"); public static final Pattern configRegex = Pattern.compile("cfg(\\d+)");
public static final Pattern atkRegex = Pattern.compile("atk(\\d+)"); public static final Pattern hpRegex = Pattern.compile("(?<!\\w)hp(\\d+)");
public static final Pattern defRegex = Pattern.compile("def(\\d+)"); public static final Pattern maxHPRegex = Pattern.compile("maxhp(\\d+)");
public static final Pattern aiRegex = Pattern.compile("ai(\\d+)"); public static final Pattern atkRegex = Pattern.compile("atk(\\d+)");
public static final Pattern defRegex = Pattern.compile("def(\\d+)");
public static int matchIntOrNeg(Pattern pattern, String arg) { public static final Pattern aiRegex = Pattern.compile("ai(\\d+)");
Matcher match = pattern.matcher(arg);
if (match.find()) { public static int matchIntOrNeg(Pattern pattern, String arg) {
return Integer.parseInt(match.group(1)); // This should be exception-safe as only \d+ can be passed to it (i.e. non-empty string of pure digits) Matcher match = pattern.matcher(arg);
} if (match.find()) {
return -1; return Integer.parseInt(
} match.group(
1)); // This should be exception-safe as only \d+ can be passed to it (i.e. non-empty
public static <T> List<String> parseIntParameters(List<String> args, @Nonnull T params, Map<Pattern, BiConsumer<T, Integer>> map) { // string of pure digits)
args.removeIf(arg -> { }
var argL = arg.toLowerCase(); return -1;
boolean deleteArg = false; }
for (var entry : map.entrySet()) {
int argNum = matchIntOrNeg(entry.getKey(), argL); public static <T> List<String> parseIntParameters(
if (argNum != -1) { List<String> args, @Nonnull T params, Map<Pattern, BiConsumer<T, Integer>> map) {
entry.getValue().accept(params, argNum); args.removeIf(
deleteArg = true; arg -> {
} var argL = arg.toLowerCase();
} boolean deleteArg = false;
return deleteArg; for (var entry : map.entrySet()) {
}); int argNum = matchIntOrNeg(entry.getKey(), argL);
return args; if (argNum != -1) {
} entry.getValue().accept(params, argNum);
} deleteArg = true;
}
}
return deleteArg;
});
return args;
}
}

View File

@ -1,330 +1,354 @@
package emu.grasscutter.command; package emu.grasscutter.command;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.config.Configuration.SERVER;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player; import emu.grasscutter.Grasscutter;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import emu.grasscutter.database.DatabaseHelper;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import emu.grasscutter.game.player.Player;
import org.reflections.Reflections; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.*; import java.util.*;
import org.reflections.Reflections;
import static emu.grasscutter.config.Configuration.SERVER;
@SuppressWarnings({"UnusedReturnValue", "unused"})
@SuppressWarnings({"UnusedReturnValue", "unused"}) public final class CommandMap {
public final class CommandMap { private static final int INVALID_UID = Integer.MIN_VALUE;
private static final int INVALID_UID = Integer.MIN_VALUE; private static final String consoleId = "console";
private static final String consoleId = "console"; private final Map<String, CommandHandler> commands = new TreeMap<>();
private final Map<String, CommandHandler> commands = new TreeMap<>(); private final Map<String, CommandHandler> aliases = new TreeMap<>();
private final Map<String, CommandHandler> aliases = new TreeMap<>(); private final Map<String, Command> annotations = new TreeMap<>();
private final Map<String, Command> annotations = new TreeMap<>(); private final Object2IntMap<String> targetPlayerIds = new Object2IntOpenHashMap<>();
private final Object2IntMap<String> targetPlayerIds = new Object2IntOpenHashMap<>();
public CommandMap() {
public CommandMap() { this(false);
this(false); }
}
public CommandMap(boolean scan) {
public CommandMap(boolean scan) { if (scan) this.scan();
if (scan) this.scan(); }
}
public static CommandMap getInstance() {
public static CommandMap getInstance() { return Grasscutter.getCommandMap();
return Grasscutter.getCommandMap(); }
}
private static int getUidFromString(String input) {
private static int getUidFromString(String input) { try {
try { return Integer.parseInt(input);
return Integer.parseInt(input); } catch (NumberFormatException ignored) {
} catch (NumberFormatException ignored) { var account = DatabaseHelper.getAccountByName(input);
var account = DatabaseHelper.getAccountByName(input); if (account == null) return INVALID_UID;
if (account == null) return INVALID_UID; var player = DatabaseHelper.getPlayerByAccount(account);
var player = DatabaseHelper.getPlayerByAccount(account); if (player == null) return INVALID_UID;
if (player == null) return INVALID_UID; // We will be immediately fetching the player again after this,
// We will be immediately fetching the player again after this, // but offline vs online Player safety is more important than saving a lookup
// but offline vs online Player safety is more important than saving a lookup return player.getUid();
return player.getUid(); }
} }
}
/**
/** * Register a command handler.
* Register a command handler. *
* * @param label The command label.
* @param label The command label. * @param command The command handler.
* @param command The command handler. * @return Instance chaining.
* @return Instance chaining. */
*/ public CommandMap registerCommand(String label, CommandHandler command) {
public CommandMap registerCommand(String label, CommandHandler command) { Grasscutter.getLogger().debug("Registered command: " + label);
Grasscutter.getLogger().debug("Registered command: " + label); label = label.toLowerCase();
label = label.toLowerCase();
// Get command data.
// Get command data. Command annotation = command.getClass().getAnnotation(Command.class);
Command annotation = command.getClass().getAnnotation(Command.class); this.annotations.put(label, annotation);
this.annotations.put(label, annotation); this.commands.put(label, command);
this.commands.put(label, command);
// Register aliases.
// Register aliases. for (String alias : annotation.aliases()) {
for (String alias : annotation.aliases()) { this.aliases.put(alias, command);
this.aliases.put(alias, command); this.annotations.put(alias, annotation);
this.annotations.put(alias, annotation); }
} return this;
return this; }
}
/**
/** * Removes a registered command handler.
* Removes a registered command handler. *
* * @param label The command label.
* @param label The command label. * @return Instance chaining.
* @return Instance chaining. */
*/ public CommandMap unregisterCommand(String label) {
public CommandMap unregisterCommand(String label) { Grasscutter.getLogger().debug("Unregistered command: " + label);
Grasscutter.getLogger().debug("Unregistered command: " + label);
CommandHandler handler = this.commands.get(label);
CommandHandler handler = this.commands.get(label); if (handler == null) return this;
if (handler == null) return this;
Command annotation = handler.getClass().getAnnotation(Command.class);
Command annotation = handler.getClass().getAnnotation(Command.class); this.annotations.remove(label);
this.annotations.remove(label); this.commands.remove(label);
this.commands.remove(label);
// Unregister aliases.
// Unregister aliases. for (String alias : annotation.aliases()) {
for (String alias : annotation.aliases()) { this.aliases.remove(alias);
this.aliases.remove(alias); this.annotations.remove(alias);
this.annotations.remove(alias); }
}
return this;
return this; }
}
public List<Command> getAnnotationsAsList() {
public List<Command> getAnnotationsAsList() { return new ArrayList<>(this.annotations.values());
return new ArrayList<>(this.annotations.values()); }
}
public Map<String, Command> getAnnotations() {
public Map<String, Command> getAnnotations() { return new LinkedHashMap<>(this.annotations);
return new LinkedHashMap<>(this.annotations); }
}
/**
/** * Returns a list of all registered commands.
* Returns a list of all registered commands. *
* * @return All command handlers as a list.
* @return All command handlers as a list. */
*/ public List<CommandHandler> getHandlersAsList() {
public List<CommandHandler> getHandlersAsList() { return new ArrayList<>(this.commands.values());
return new ArrayList<>(this.commands.values()); }
}
public Map<String, CommandHandler> getHandlers() {
public Map<String, CommandHandler> getHandlers() { return this.commands;
return this.commands; }
}
/**
/** * Returns a handler by label/alias.
* Returns a handler by label/alias. *
* * @param label The command label.
* @param label The command label. * @return The command handler.
* @return The command handler. */
*/ public CommandHandler getHandler(String label) {
public CommandHandler getHandler(String label) { CommandHandler handler = this.commands.get(label);
CommandHandler handler = this.commands.get(label); if (handler == null) {
if (handler == null) { // Try getting by alias
// Try getting by alias handler = this.aliases.get(label);
handler = this.aliases.get(label); }
} return handler;
return handler; }
}
private Player getTargetPlayer(
private Player getTargetPlayer(String playerId, Player player, Player targetPlayer, List<String> args) { String playerId, Player player, Player targetPlayer, List<String> args) {
// Top priority: If any @UID argument is present, override targetPlayer with it. // Top priority: If any @UID argument is present, override targetPlayer with it.
for (int i = 0; i < args.size(); i++) { for (int i = 0; i < args.size(); i++) {
String arg = args.get(i); String arg = args.get(i);
if (arg.startsWith("@")) { if (arg.startsWith("@")) {
arg = args.remove(i).substring(1); arg = args.remove(i).substring(1);
if (arg.equals("")) { if (arg.equals("")) {
// This is a special case to target nothing, distinct from failing to assign a target. // This is a special case to target nothing, distinct from failing to assign a target.
// This is specifically to allow in-game players to run a command without targeting themselves or anyone else. // This is specifically to allow in-game players to run a command without targeting
return null; // themselves or anyone else.
} return null;
int uid = getUidFromString(arg); }
if (uid == INVALID_UID) { int uid = getUidFromString(arg);
CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid"); if (uid == INVALID_UID) {
throw new IllegalArgumentException(); CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
} throw new IllegalArgumentException();
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true); }
if (targetPlayer == null) { targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error"); if (targetPlayer == null) {
throw new IllegalArgumentException(); CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
} throw new IllegalArgumentException();
return targetPlayer; }
} return targetPlayer;
} }
}
// Next priority: If we invoked with a target, use that.
// By default, this only happens when you message another player in-game with a command. // Next priority: If we invoked with a target, use that.
if (targetPlayer != null) { // By default, this only happens when you message another player in-game with a command.
return targetPlayer; if (targetPlayer != null) {
} return targetPlayer;
}
// Next priority: Use previously-set target. (see /target [[@]UID])
if (targetPlayerIds.containsKey(playerId)) { // Next priority: Use previously-set target. (see /target [[@]UID])
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.getInt(playerId), true); if (targetPlayerIds.containsKey(playerId)) {
// We check every time in case the target is deleted after being targeted targetPlayer =
if (targetPlayer == null) { Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.getInt(playerId), true);
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error"); // We check every time in case the target is deleted after being targeted
throw new IllegalArgumentException(); if (targetPlayer == null) {
} CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
return targetPlayer; throw new IllegalArgumentException();
} }
return targetPlayer;
// Lowest priority: Target the player invoking the command. In the case of the console, this will return null. }
return player;
} // Lowest priority: Target the player invoking the command. In the case of the console, this
// will return null.
private boolean setPlayerTarget(String playerId, Player player, String targetUid) { return player;
if (targetUid.equals("")) { // Clears the default targetPlayer. }
targetPlayerIds.removeInt(playerId);
CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target"); private boolean setPlayerTarget(String playerId, Player player, String targetUid) {
return true; if (targetUid.equals("")) { // Clears the default targetPlayer.
} targetPlayerIds.removeInt(playerId);
CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
// Sets default targetPlayer to the UID provided. return true;
int uid = getUidFromString(targetUid); }
if (uid == INVALID_UID) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid"); // Sets default targetPlayer to the UID provided.
return false; int uid = getUidFromString(targetUid);
} if (uid == INVALID_UID) {
Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true); CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
if (targetPlayer == null) { return false;
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error"); }
return false; Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
} if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
targetPlayerIds.put(playerId, uid); return false;
String target = uid + " (" + targetPlayer.getAccount().getUsername() + ")"; }
CommandHandler.sendTranslatedMessage(player, "commands.execution.set_target", target);
CommandHandler.sendTranslatedMessage(player, targetPlayer.isOnline() ? "commands.execution.set_target_online" : "commands.execution.set_target_offline", target); targetPlayerIds.put(playerId, uid);
return true; String target = uid + " (" + targetPlayer.getAccount().getUsername() + ")";
} CommandHandler.sendTranslatedMessage(player, "commands.execution.set_target", target);
CommandHandler.sendTranslatedMessage(
/** player,
* Invoke a command handler with the given arguments. targetPlayer.isOnline()
* ? "commands.execution.set_target_online"
* @param player The player invoking the command or null for the server console. : "commands.execution.set_target_offline",
* @param rawMessage The messaged used to invoke the command. target);
*/ return true;
public void invoke(Player player, Player targetPlayer, String rawMessage) { }
// The console outputs in-game command. [{Account Username} (Player UID: {Player Uid})]
if (SERVER.logCommands) { /**
if (player != null) { * Invoke a command handler with the given arguments.
Grasscutter.getLogger().info("Command used by [" + player.getAccount().getUsername() + " (Player UID: " + player.getUid() + ")]: " + rawMessage); *
} else { * @param player The player invoking the command or null for the server console.
Grasscutter.getLogger().info("Command used by server console: " + rawMessage); * @param rawMessage The messaged used to invoke the command.
} */
} public void invoke(Player player, Player targetPlayer, String rawMessage) {
// The console outputs in-game command. [{Account Username} (Player UID: {Player Uid})]
rawMessage = rawMessage.trim(); if (SERVER.logCommands) {
if (rawMessage.length() == 0) { if (player != null) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.not_specified"); Grasscutter.getLogger()
return; .info(
} "Command used by ["
+ player.getAccount().getUsername()
// Parse message. + " (Player UID: "
String[] split = rawMessage.split(" "); + player.getUid()
String label = split[0].toLowerCase(); + ")]: "
List<String> args = new ArrayList<>(Arrays.asList(split).subList(1, split.length)); + rawMessage);
String playerId = (player == null) ? consoleId : player.getAccount().getId(); } else {
Grasscutter.getLogger().info("Command used by server console: " + rawMessage);
// Check for special cases - currently only target command. }
if (label.startsWith("@")) { // @[UID] }
this.setPlayerTarget(playerId, player, label.substring(1));
return; rawMessage = rawMessage.trim();
} else if (label.equalsIgnoreCase("target")) { // target [[@]UID] if (rawMessage.length() == 0) {
if (args.size() > 0) { CommandHandler.sendTranslatedMessage(player, "commands.generic.not_specified");
String targetUidStr = args.get(0); return;
if (targetUidStr.startsWith("@")) { }
targetUidStr = targetUidStr.substring(1);
} // Parse message.
this.setPlayerTarget(playerId, player, targetUidStr); String[] split = rawMessage.split(" ");
return; String label = split[0].toLowerCase();
} else { List<String> args = new ArrayList<>(Arrays.asList(split).subList(1, split.length));
this.setPlayerTarget(playerId, player, ""); String playerId = (player == null) ? consoleId : player.getAccount().getId();
return;
} // Check for special cases - currently only target command.
} if (label.startsWith("@")) { // @[UID]
this.setPlayerTarget(playerId, player, label.substring(1));
// Get command handler. return;
CommandHandler handler = this.getHandler(label); } else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
if (args.size() > 0) {
// Check if the handler is null. String targetUidStr = args.get(0);
if (handler == null) { if (targetUidStr.startsWith("@")) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label); targetUidStr = targetUidStr.substring(1);
return; }
} this.setPlayerTarget(playerId, player, targetUidStr);
return;
// Get the command's annotation. } else {
Command annotation = this.annotations.get(label); this.setPlayerTarget(playerId, player, "");
return;
// Resolve targetPlayer }
try { }
targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args);
} catch (IllegalArgumentException e) { // Get command handler.
return; CommandHandler handler = this.getHandler(label);
}
// Check if the handler is null.
// Check for permissions. if (handler == null) {
if (!Grasscutter.getPermissionHandler().checkPermission(player, targetPlayer, annotation.permission(), this.annotations.get(label).permissionTargeted())) { CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label);
return; return;
} }
// Check if command has unfulfilled constraints on targetPlayer // Get the command's annotation.
Command.TargetRequirement targetRequirement = annotation.targetRequirement(); Command annotation = this.annotations.get(label);
if (targetRequirement != Command.TargetRequirement.NONE) {
if (targetPlayer == null) { // Resolve targetPlayer
handler.sendUsageMessage(player); try {
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target"); targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args);
return; } catch (IllegalArgumentException e) {
} return;
}
if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) {
handler.sendUsageMessage(player); // Check for permissions.
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online"); if (!Grasscutter.getPermissionHandler()
return; .checkPermission(
} player,
targetPlayer,
if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) { annotation.permission(),
handler.sendUsageMessage(player); this.annotations.get(label).permissionTargeted())) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline"); return;
return; }
}
} // Check if command has unfulfilled constraints on targetPlayer
Command.TargetRequirement targetRequirement = annotation.targetRequirement();
// Copy player and handler to final properties. if (targetRequirement != Command.TargetRequirement.NONE) {
final Player targetPlayerF = targetPlayer; // Is there a better way to do this? if (targetPlayer == null) {
final CommandHandler handlerF = handler; // Is there a better way to do this? handler.sendUsageMessage(player);
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target");
// Invoke execute method for handler. return;
Runnable runnable = () -> handlerF.execute(player, targetPlayerF, args); }
if (annotation.threading()) {
new Thread(runnable).start(); if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) {
} else { handler.sendUsageMessage(player);
runnable.run(); CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online");
} return;
} }
/** if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) {
* Scans for all classes annotated with {@link Command} and registers them. handler.sendUsageMessage(player);
*/ CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline");
private void scan() { return;
Reflections reflector = Grasscutter.reflector; }
Set<Class<?>> classes = reflector.getTypesAnnotatedWith(Command.class); }
classes.forEach(annotated -> { // Copy player and handler to final properties.
try { final Player targetPlayerF = targetPlayer; // Is there a better way to do this?
Command cmdData = annotated.getAnnotation(Command.class); final CommandHandler handlerF = handler; // Is there a better way to do this?
Object object = annotated.getDeclaredConstructor().newInstance();
if (object instanceof CommandHandler) // Invoke execute method for handler.
this.registerCommand(cmdData.label(), (CommandHandler) object); Runnable runnable = () -> handlerF.execute(player, targetPlayerF, args);
else Grasscutter.getLogger().error("Class " + annotated.getName() + " is not a CommandHandler!"); if (annotation.threading()) {
} catch (Exception exception) { new Thread(runnable).start();
Grasscutter.getLogger().error("Failed to register command handler for " + annotated.getSimpleName(), exception); } else {
} runnable.run();
}); }
} }
}
/** Scans for all classes annotated with {@link Command} and registers them. */
private void scan() {
Reflections reflector = Grasscutter.reflector;
Set<Class<?>> classes = reflector.getTypesAnnotatedWith(Command.class);
classes.forEach(
annotated -> {
try {
Command cmdData = annotated.getAnnotation(Command.class);
Object object = annotated.getDeclaredConstructor().newInstance();
if (object instanceof CommandHandler)
this.registerCommand(cmdData.label(), (CommandHandler) object);
else
Grasscutter.getLogger()
.error("Class " + annotated.getName() + " is not a CommandHandler!");
} catch (Exception exception) {
Grasscutter.getLogger()
.error(
"Failed to register command handler for " + annotated.getSimpleName(),
exception);
}
});
}
}

View File

@ -1,32 +1,33 @@
package emu.grasscutter.command; package emu.grasscutter.command;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
public class DefaultPermissionHandler implements PermissionHandler { public class DefaultPermissionHandler implements PermissionHandler {
@Override @Override
public boolean EnablePermissionCommand() { public boolean EnablePermissionCommand() {
return true; return true;
} }
@Override @Override
public boolean checkPermission(Player player, Player targetPlayer, String permissionNode, String permissionNodeTargeted) { public boolean checkPermission(
if (player == null) { Player player, Player targetPlayer, String permissionNode, String permissionNodeTargeted) {
return true; if (player == null) {
} return true;
}
Account account = player.getAccount();
if (player != targetPlayer) { // Additional permission required for targeting another player Account account = player.getAccount();
if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) { if (player != targetPlayer) { // Additional permission required for targeting another player
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error"); if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) {
return false; CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
} return false;
} }
if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) { }
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error"); if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) {
return false; CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
} return false;
}
return true;
} return true;
} }
}

View File

@ -1,9 +1,10 @@
package emu.grasscutter.command; package emu.grasscutter.command;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
public interface PermissionHandler { public interface PermissionHandler {
boolean EnablePermissionCommand(); boolean EnablePermissionCommand();
boolean checkPermission(Player player, Player targetPlayer, String permissionNode, String permissionNodeTargeted); boolean checkPermission(
} Player player, Player targetPlayer, String permissionNode, String permissionNodeTargeted);
}

View File

@ -1,139 +1,145 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import at.favre.lib.crypto.bcrypt.BCrypt; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import at.favre.lib.crypto.bcrypt.BCrypt;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.Grasscutter;
import emu.grasscutter.config.Configuration; import emu.grasscutter.command.Command;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.Account; import emu.grasscutter.config.Configuration;
import emu.grasscutter.game.player.Player; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import java.util.List; import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command( label = "account",
label = "account", usage = {
usage = { "create <username> [<UID>]", // Only with EXPERIMENTAL_RealPassword == false
"create <username> [<UID>]", // Only with EXPERIMENTAL_RealPassword == false "delete <username>",
"delete <username>", "create <username> <password> [<UID>]", // Only with EXPERIMENTAL_RealPassword == true
"create <username> <password> [<UID>]", // Only with EXPERIMENTAL_RealPassword == true "resetpass <username> <password>"
"resetpass <username> <password>"}, // Only with EXPERIMENTAL_RealPassword == true }, // Only with EXPERIMENTAL_RealPassword == true
targetRequirement = Command.TargetRequirement.NONE) targetRequirement = Command.TargetRequirement.NONE)
public final class AccountCommand implements CommandHandler { public final class AccountCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender != null) { if (sender != null) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.console_execute_error"); CommandHandler.sendTranslatedMessage(sender, "commands.generic.console_execute_error");
return; return;
} }
if (args.size() < 2) { if (args.size() < 2) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
String action = args.get(0); String action = args.get(0);
String username = args.get(1); String username = args.get(1);
switch (action) { switch (action) {
default: default:
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
case "create": case "create":
int uid = 0; int uid = 0;
String password = ""; String password = "";
if (Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) { if (Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) {
if (args.size() < 3) { if (args.size() < 3) {
CommandHandler.sendMessage(sender, "EXPERIMENTAL_RealPassword requires a password argument"); CommandHandler.sendMessage(
CommandHandler.sendMessage(sender, "Usage: account create <username> <password> [uid]"); sender, "EXPERIMENTAL_RealPassword requires a password argument");
return; CommandHandler.sendMessage(sender, "Usage: account create <username> <password> [uid]");
} return;
password = args.get(2); }
password = args.get(2);
if (args.size() == 4) {
try { if (args.size() == 4) {
uid = Integer.parseInt(args.get(3)); try {
} catch (NumberFormatException ignored) { uid = Integer.parseInt(args.get(3));
CommandHandler.sendMessage(sender, translate(sender, "commands.account.invalid")); } catch (NumberFormatException ignored) {
if (Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) { CommandHandler.sendMessage(sender, translate(sender, "commands.account.invalid"));
CommandHandler.sendMessage(sender, "EXPERIMENTAL_RealPassword requires argument 2 to be a password, not a uid"); if (Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) {
CommandHandler.sendMessage(sender, "Usage: account create <username> <password> [uid]"); CommandHandler.sendMessage(
} sender,
return; "EXPERIMENTAL_RealPassword requires argument 2 to be a password, not a uid");
} CommandHandler.sendMessage(
} sender, "Usage: account create <username> <password> [uid]");
} else { }
if (args.size() > 2) { return;
try { }
uid = Integer.parseInt(args.get(2)); }
} catch (NumberFormatException ignored) { } else {
CommandHandler.sendMessage(sender, translate(sender, "commands.account.invalid")); if (args.size() > 2) {
return; try {
} uid = Integer.parseInt(args.get(2));
} } catch (NumberFormatException ignored) {
} CommandHandler.sendMessage(sender, translate(sender, "commands.account.invalid"));
return;
emu.grasscutter.game.Account account = DatabaseHelper.createAccountWithUid(username, uid); }
if (account == null) { }
CommandHandler.sendMessage(sender, translate(sender, "commands.account.exists")); }
return;
} else { emu.grasscutter.game.Account account = DatabaseHelper.createAccountWithUid(username, uid);
if (Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) { if (account == null) {
account.setPassword(BCrypt.withDefaults().hashToString(12, password.toCharArray())); CommandHandler.sendMessage(sender, translate(sender, "commands.account.exists"));
} return;
account.addPermission("*"); } else {
account.save(); // Save account to database. if (Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) {
account.setPassword(BCrypt.withDefaults().hashToString(12, password.toCharArray()));
CommandHandler.sendMessage(sender, translate(sender, "commands.account.create", account.getReservedPlayerUid())); }
} account.addPermission("*");
return; account.save(); // Save account to database.
case "delete":
// Get the account we want to delete. CommandHandler.sendMessage(
Account toDelete = DatabaseHelper.getAccountByName(username); sender, translate(sender, "commands.account.create", account.getReservedPlayerUid()));
}
if (toDelete == null) { return;
CommandHandler.sendMessage(sender, translate(sender, "commands.account.no_account")); case "delete":
return; // Get the account we want to delete.
} Account toDelete = DatabaseHelper.getAccountByName(username);
DatabaseHelper.deleteAccount(toDelete); if (toDelete == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.account.delete")); CommandHandler.sendMessage(sender, translate(sender, "commands.account.no_account"));
return; return;
case "resetpass": }
if (!Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) {
CommandHandler.sendMessage(sender, "resetpass requires EXPERIMENTAL_RealPassword to be true."); DatabaseHelper.deleteAccount(toDelete);
return; CommandHandler.sendMessage(sender, translate(sender, "commands.account.delete"));
} return;
case "resetpass":
if (args.size() != 3) { if (!Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) {
CommandHandler.sendMessage(sender, "Invalid Args"); CommandHandler.sendMessage(
CommandHandler.sendMessage(sender, "Usage: account resetpass <username> <password>"); sender, "resetpass requires EXPERIMENTAL_RealPassword to be true.");
return; return;
} }
Account toUpdate = DatabaseHelper.getAccountByName(username); if (args.size() != 3) {
CommandHandler.sendMessage(sender, "Invalid Args");
if (toUpdate == null) { CommandHandler.sendMessage(sender, "Usage: account resetpass <username> <password>");
CommandHandler.sendMessage(sender, translate(sender, "commands.account.no_account")); return;
return; }
}
Account toUpdate = DatabaseHelper.getAccountByName(username);
// Make sure player can't stay logged in with old password.
kickAccount(toUpdate); if (toUpdate == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.account.no_account"));
toUpdate.setPassword(BCrypt.withDefaults().hashToString(12, args.get(2).toCharArray())); return;
toUpdate.save(); }
CommandHandler.sendMessage(sender, "Password Updated.");
} // Make sure player can't stay logged in with old password.
} kickAccount(toUpdate);
private void kickAccount(Account account) { toUpdate.setPassword(BCrypt.withDefaults().hashToString(12, args.get(2).toCharArray()));
Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId()); toUpdate.save();
if (player != null) { CommandHandler.sendMessage(sender, "Password Updated.");
player.getSession().close(); }
} }
}
} private void kickAccount(Account account) {
Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId());
if (player != null) {
player.getSession().close();
}
}
}

View File

@ -1,133 +1,158 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AchievementData; import emu.grasscutter.data.excels.AchievementData;
import emu.grasscutter.game.achievement.AchievementControlReturns; import emu.grasscutter.game.achievement.AchievementControlReturns;
import emu.grasscutter.game.achievement.Achievements; import emu.grasscutter.game.achievement.Achievements;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.List; import java.util.Optional;
import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicInteger;
@Command(
@Command( label = "achievement",
label = "achievement", usage = {
usage = {"(grant|revoke) <achievementId>", "progress <achievementId> <progress>", "grantall", "revokeall"}, "(grant|revoke) <achievementId>",
aliases = {"am"}, "progress <achievementId> <progress>",
permission = "player.achievement", "grantall",
permissionTargeted = "player.achievement.others", "revokeall"
targetRequirement = Command.TargetRequirement.PLAYER, },
threading = true) aliases = {"am"},
public class AchievementCommand implements CommandHandler { permission = "player.achievement",
private static void sendSuccessMessage(Player sender, String cmd, Object... args) { permissionTargeted = "player.achievement.others",
CommandHandler.sendTranslatedMessage(sender, AchievementControlReturns.Return.SUCCESS.getKey() + cmd, args); targetRequirement = Command.TargetRequirement.PLAYER,
} threading = true)
public class AchievementCommand implements CommandHandler {
private static Optional<Integer> parseInt(String s) { private static void sendSuccessMessage(Player sender, String cmd, Object... args) {
try { CommandHandler.sendTranslatedMessage(
return Optional.of(Integer.parseInt(s)); sender, AchievementControlReturns.Return.SUCCESS.getKey() + cmd, args);
} catch (NumberFormatException e) { }
return Optional.empty();
} private static Optional<Integer> parseInt(String s) {
} try {
return Optional.of(Integer.parseInt(s));
private static void grantAll(Player sender, Player targetPlayer, Achievements achievements) { } catch (NumberFormatException e) {
var counter = new AtomicInteger(); return Optional.empty();
GameData.getAchievementDataMap().values().stream() }
.filter(AchievementData::isUsed) }
.filter(AchievementData::isParent)
.forEach(data -> { private static void grantAll(Player sender, Player targetPlayer, Achievements achievements) {
var success = achievements.grant(data.getId()); var counter = new AtomicInteger();
if (success.getRet() == AchievementControlReturns.Return.SUCCESS) { GameData.getAchievementDataMap().values().stream()
counter.addAndGet(success.getChangedAchievementStatusNum()); .filter(AchievementData::isUsed)
} .filter(AchievementData::isParent)
}); .forEach(
data -> {
sendSuccessMessage(sender, "grantall", counter.intValue(), targetPlayer.getNickname()); var success = achievements.grant(data.getId());
} if (success.getRet() == AchievementControlReturns.Return.SUCCESS) {
counter.addAndGet(success.getChangedAchievementStatusNum());
private static void revokeAll(Player sender, Player targetPlayer, Achievements achievements) { }
var counter = new AtomicInteger(); });
GameData.getAchievementDataMap().values().stream()
.filter(AchievementData::isUsed) sendSuccessMessage(sender, "grantall", counter.intValue(), targetPlayer.getNickname());
.filter(AchievementData::isParent) }
.forEach(data -> {
var success = achievements.revoke(data.getId()); private static void revokeAll(Player sender, Player targetPlayer, Achievements achievements) {
if (success.getRet() == AchievementControlReturns.Return.SUCCESS) { var counter = new AtomicInteger();
counter.addAndGet(success.getChangedAchievementStatusNum()); GameData.getAchievementDataMap().values().stream()
} .filter(AchievementData::isUsed)
}); .filter(AchievementData::isParent)
.forEach(
sendSuccessMessage(sender, "revokeall", counter.intValue(), targetPlayer.getNickname()); data -> {
} var success = achievements.revoke(data.getId());
if (success.getRet() == AchievementControlReturns.Return.SUCCESS) {
@Override counter.addAndGet(success.getChangedAchievementStatusNum());
public void execute(Player sender, Player targetPlayer, List<String> args) { }
if (args.size() < 1) { });
this.sendUsageMessage(sender);
return; sendSuccessMessage(sender, "revokeall", counter.intValue(), targetPlayer.getNickname());
} }
var command = args.remove(0).toLowerCase(); @Override
var achievements = Achievements.getByPlayer(targetPlayer); public void execute(Player sender, Player targetPlayer, List<String> args) {
switch (command) { if (args.size() < 1) {
case "grant" -> this.grant(sender, targetPlayer, achievements, args); this.sendUsageMessage(sender);
case "revoke" -> this.revoke(sender, targetPlayer, achievements, args); return;
case "progress" -> this.progress(sender, targetPlayer, achievements, args); }
case "grantall" -> grantAll(sender, targetPlayer, achievements);
case "revokeall" -> revokeAll(sender, targetPlayer, achievements); var command = args.remove(0).toLowerCase();
default -> this.sendUsageMessage(sender); var achievements = Achievements.getByPlayer(targetPlayer);
} switch (command) {
} case "grant" -> this.grant(sender, targetPlayer, achievements, args);
case "revoke" -> this.revoke(sender, targetPlayer, achievements, args);
private void grant(Player sender, Player targetPlayer, Achievements achievements, List<String> args) { case "progress" -> this.progress(sender, targetPlayer, achievements, args);
if (args.size() < 1) { case "grantall" -> grantAll(sender, targetPlayer, achievements);
this.sendUsageMessage(sender); case "revokeall" -> revokeAll(sender, targetPlayer, achievements);
} default -> this.sendUsageMessage(sender);
}
parseInt(args.remove(0)).ifPresentOrElse(integer -> { }
var ret = achievements.grant(integer);
switch (ret.getRet()) { private void grant(
case SUCCESS -> sendSuccessMessage(sender, "grant", targetPlayer.getNickname()); Player sender, Player targetPlayer, Achievements achievements, List<String> args) {
case ACHIEVEMENT_NOT_FOUND -> CommandHandler.sendTranslatedMessage(sender, ret.getRet().getKey()); if (args.size() < 1) {
case ALREADY_ACHIEVED -> this.sendUsageMessage(sender);
CommandHandler.sendTranslatedMessage(sender, ret.getRet().getKey(), targetPlayer.getNickname()); }
}
}, () -> this.sendUsageMessage(sender)); parseInt(args.remove(0))
} .ifPresentOrElse(
integer -> {
private void revoke(Player sender, Player targetPlayer, Achievements achievements, List<String> args) { var ret = achievements.grant(integer);
if (args.size() < 1) { switch (ret.getRet()) {
this.sendUsageMessage(sender); case SUCCESS -> sendSuccessMessage(sender, "grant", targetPlayer.getNickname());
} case ACHIEVEMENT_NOT_FOUND -> CommandHandler.sendTranslatedMessage(
sender, ret.getRet().getKey());
parseInt(args.remove(0)).ifPresentOrElse(integer -> { case ALREADY_ACHIEVED -> CommandHandler.sendTranslatedMessage(
var ret = achievements.revoke(integer); sender, ret.getRet().getKey(), targetPlayer.getNickname());
switch (ret.getRet()) { }
case SUCCESS -> sendSuccessMessage(sender, "revoke", targetPlayer.getNickname()); },
case ACHIEVEMENT_NOT_FOUND -> CommandHandler.sendTranslatedMessage(sender, ret.getRet().getKey()); () -> this.sendUsageMessage(sender));
case NOT_YET_ACHIEVED -> }
CommandHandler.sendTranslatedMessage(sender, ret.getRet().getKey(), targetPlayer.getNickname());
} private void revoke(
}, () -> this.sendUsageMessage(sender)); Player sender, Player targetPlayer, Achievements achievements, List<String> args) {
} if (args.size() < 1) {
this.sendUsageMessage(sender);
private void progress(Player sender, Player targetPlayer, Achievements achievements, List<String> args) { }
if (args.size() < 2) {
this.sendUsageMessage(sender); parseInt(args.remove(0))
} .ifPresentOrElse(
integer -> {
parseInt(args.remove(0)).ifPresentOrElse(integer -> { var ret = achievements.revoke(integer);
parseInt(args.remove(0)).ifPresentOrElse(progress -> { switch (ret.getRet()) {
var ret = achievements.progress(integer, progress); case SUCCESS -> sendSuccessMessage(sender, "revoke", targetPlayer.getNickname());
switch (ret.getRet()) { case ACHIEVEMENT_NOT_FOUND -> CommandHandler.sendTranslatedMessage(
case SUCCESS -> sender, ret.getRet().getKey());
sendSuccessMessage(sender, "progress", targetPlayer.getNickname(), integer, progress); case NOT_YET_ACHIEVED -> CommandHandler.sendTranslatedMessage(
case ACHIEVEMENT_NOT_FOUND -> CommandHandler.sendTranslatedMessage(sender, ret.getRet().getKey()); sender, ret.getRet().getKey(), targetPlayer.getNickname());
} }
}, () -> this.sendUsageMessage(sender)); },
}, () -> this.sendUsageMessage(sender)); () -> this.sendUsageMessage(sender));
} }
}
private void progress(
Player sender, Player targetPlayer, Achievements achievements, List<String> args) {
if (args.size() < 2) {
this.sendUsageMessage(sender);
}
parseInt(args.remove(0))
.ifPresentOrElse(
integer -> {
parseInt(args.remove(0))
.ifPresentOrElse(
progress -> {
var ret = achievements.progress(integer, progress);
switch (ret.getRet()) {
case SUCCESS -> sendSuccessMessage(
sender, "progress", targetPlayer.getNickname(), integer, progress);
case ACHIEVEMENT_NOT_FOUND -> CommandHandler.sendTranslatedMessage(
sender, ret.getRet().getKey());
}
},
() -> this.sendUsageMessage(sender));
},
() -> this.sendUsageMessage(sender));
}
}

View File

@ -1,73 +1,78 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.server.packet.send.PacketServerAnnounceNotify; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.Collections; import emu.grasscutter.server.packet.send.PacketServerAnnounceNotify;
import java.util.List; import java.util.Collections;
import java.util.Random; import java.util.List;
import java.util.Random;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "announce", label = "announce",
usage = {"<content>", "refresh", "(tpl|revoke) <templateId>"}, usage = {"<content>", "refresh", "(tpl|revoke) <templateId>"},
permission = "server.announce", permission = "server.announce",
aliases = {"a"}, aliases = {"a"},
targetRequirement = Command.TargetRequirement.NONE) targetRequirement = Command.TargetRequirement.NONE)
public final class AnnounceCommand implements CommandHandler { public final class AnnounceCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
var manager = Grasscutter.getGameServer().getAnnouncementSystem(); var manager = Grasscutter.getGameServer().getAnnouncementSystem();
if (args.size() < 1) { if (args.size() < 1) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
switch (args.get(0)) { switch (args.get(0)) {
case "tpl": case "tpl":
if (args.size() < 2) { if (args.size() < 2) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
var templateId = Integer.parseInt(args.get(1)); var templateId = Integer.parseInt(args.get(1));
var tpl = manager.getAnnounceConfigItemMap().get(templateId); var tpl = manager.getAnnounceConfigItemMap().get(templateId);
if (tpl == null) { if (tpl == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.not_found", templateId)); CommandHandler.sendMessage(
return; sender, translate(sender, "commands.announce.not_found", templateId));
} return;
}
manager.broadcast(Collections.singletonList(tpl));
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.send_success", tpl.getTemplateId())); manager.broadcast(Collections.singletonList(tpl));
break; CommandHandler.sendMessage(
sender, translate(sender, "commands.announce.send_success", tpl.getTemplateId()));
case "refresh": break;
var num = manager.refresh();
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.refresh_success", num)); case "refresh":
break; var num = manager.refresh();
CommandHandler.sendMessage(
case "revoke": sender, translate(sender, "commands.announce.refresh_success", num));
if (args.size() < 2) { break;
sendUsageMessage(sender);
return; case "revoke":
} if (args.size() < 2) {
sendUsageMessage(sender);
var templateId1 = Integer.parseInt(args.get(1)); return;
manager.revoke(templateId1); }
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.revoke_done", templateId1));
break; var templateId1 = Integer.parseInt(args.get(1));
manager.revoke(templateId1);
default: CommandHandler.sendMessage(
var id = new Random().nextInt(10000, 99999); sender, translate(sender, "commands.announce.revoke_done", templateId1));
var text = String.join(" ", args); break;
manager.getOnlinePlayers().forEach(i -> i.sendPacket(new PacketServerAnnounceNotify(text, id)));
default:
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.send_success", id)); var id = new Random().nextInt(10000, 99999);
} var text = String.join(" ", args);
manager
} .getOnlinePlayers()
} .forEach(i -> i.sendPacket(new PacketServerAnnounceNotify(text, id)));
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.send_success", id));
}
}
}

View File

@ -1,64 +1,62 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import java.util.List;
import java.util.List;
@Command(
@Command( label = "ban",
label = "ban", usage = {"[<time> [<reason>]]"},
usage = {"[<time> [<reason>]]"}, permission = "server.ban",
permission = "server.ban", targetRequirement = Command.TargetRequirement.PLAYER)
targetRequirement = Command.TargetRequirement.PLAYER public final class BanCommand implements CommandHandler {
)
public final class BanCommand implements CommandHandler { private boolean banAccount(Player targetPlayer, int time, String reason) {
Account account = targetPlayer.getAccount();
private boolean banAccount(Player targetPlayer, int time, String reason) {
Account account = targetPlayer.getAccount(); if (account == null) {
return false;
if (account == null) { }
return false;
} account.setBanReason(reason);
account.setBanEndTime(time);
account.setBanReason(reason); account.setBanStartTime((int) System.currentTimeMillis() / 1000);
account.setBanEndTime(time); account.setBanned(true);
account.setBanStartTime((int) System.currentTimeMillis() / 1000); account.save();
account.setBanned(true);
account.save(); GameSession session = targetPlayer.getSession();
if (session != null) {
GameSession session = targetPlayer.getSession(); session.close();
if (session != null) { }
session.close(); return true;
} }
return true;
} @Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
@Override int time = 2051190000;
public void execute(Player sender, Player targetPlayer, List<String> args) { String reason = "Reason not specified.";
int time = 2051190000;
String reason = "Reason not specified."; switch (args.size()) {
case 2:
switch (args.size()) { reason = args.get(1); // Fall-through
case 2: case 1:
reason = args.get(1); // Fall-through try {
case 1: time = Integer.parseInt(args.get(0));
try { } catch (NumberFormatException ignored) {
time = Integer.parseInt(args.get(0)); CommandHandler.sendTranslatedMessage(sender, "commands.ban.invalid_time");
} catch (NumberFormatException ignored) { return;
CommandHandler.sendTranslatedMessage(sender, "commands.ban.invalid_time"); } // Fall-through, unimportant
return; default:
} // Fall-through, unimportant break;
default: }
break;
} if (banAccount(targetPlayer, time, reason)) {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.success");
if (banAccount(targetPlayer, time, reason)) { } else {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.success"); CommandHandler.sendTranslatedMessage(sender, "commands.ban.failure");
} else { }
CommandHandler.sendTranslatedMessage(sender, "commands.ban.failure"); }
} }
}
}

View File

@ -1,103 +1,105 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.command.CommandHelpers.*;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.command.Command;
import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.inventory.Inventory;
import lombok.Setter; import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import lombok.Setter;
import static emu.grasscutter.command.CommandHelpers.*;
@Command(
@Command( label = "clear",
label = "clear", usage = {"(all|wp|art|mat) [lv<max level>] [r<max refinement>] [<max rarity>*]"},
usage = {"(all|wp|art|mat) [lv<max level>] [r<max refinement>] [<max rarity>*]"}, permission = "player.clearinv",
permission = "player.clearinv", permissionTargeted = "player.clearinv.others")
permissionTargeted = "player.clearinv.others") public final class ClearCommand implements CommandHandler {
public final class ClearCommand implements CommandHandler {
private static final Map<Pattern, BiConsumer<ClearItemParameters, Integer>> intCommandHandlers =
private static final Map<Pattern, BiConsumer<ClearItemParameters, Integer>> intCommandHandlers = Map.ofEntries( Map.ofEntries(
Map.entry(lvlRegex, ClearItemParameters::setLvl), Map.entry(lvlRegex, ClearItemParameters::setLvl),
Map.entry(refineRegex, ClearItemParameters::setRefinement), Map.entry(refineRegex, ClearItemParameters::setRefinement),
Map.entry(rankRegex, ClearItemParameters::setRank) Map.entry(rankRegex, ClearItemParameters::setRank));
);
private Stream<GameItem> getOther(
private Stream<GameItem> getOther(ItemType type, Inventory playerInventory, ClearItemParameters param) { ItemType type, Inventory playerInventory, ClearItemParameters param) {
return playerInventory.getItems().values().stream() return playerInventory.getItems().values().stream()
.filter(item -> item.getItemType() == type) .filter(item -> item.getItemType() == type)
.filter(item -> item.getItemData().getRankLevel() <= param.rank) .filter(item -> item.getItemData().getRankLevel() <= param.rank)
.filter(item -> !item.isLocked() && !item.isEquipped()); .filter(item -> !item.isLocked() && !item.isEquipped());
} }
private Stream<GameItem> getWeapons(Inventory playerInventory, ClearItemParameters param) { private Stream<GameItem> getWeapons(Inventory playerInventory, ClearItemParameters param) {
return getOther(ItemType.ITEM_WEAPON, playerInventory, param) return getOther(ItemType.ITEM_WEAPON, playerInventory, param)
.filter(item -> item.getLevel() <= param.lvl) .filter(item -> item.getLevel() <= param.lvl)
.filter(item -> item.getRefinement() < param.refinement); .filter(item -> item.getRefinement() < param.refinement);
} }
private Stream<GameItem> getRelics(Inventory playerInventory, ClearItemParameters param) { private Stream<GameItem> getRelics(Inventory playerInventory, ClearItemParameters param) {
return getOther(ItemType.ITEM_RELIQUARY, playerInventory, param) return getOther(ItemType.ITEM_RELIQUARY, playerInventory, param)
.filter(item -> item.getLevel() <= param.lvl + 1); .filter(item -> item.getLevel() <= param.lvl + 1);
} }
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
Inventory playerInventory = targetPlayer.getInventory(); Inventory playerInventory = targetPlayer.getInventory();
ClearItemParameters param = new ClearItemParameters(); ClearItemParameters param = new ClearItemParameters();
// Extract any tagged int arguments (e.g. "lv90", "x100", "r5") // Extract any tagged int arguments (e.g. "lv90", "x100", "r5")
parseIntParameters(args, param, intCommandHandlers); parseIntParameters(args, param, intCommandHandlers);
if (args.size() < 1) { if (args.size() < 1) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
String playerString = targetPlayer.getNickname(); // Should probably be UID instead but whatever String playerString = targetPlayer.getNickname(); // Should probably be UID instead but whatever
switch (args.get(0)) { switch (args.get(0)) {
case "wp" -> { case "wp" -> {
playerInventory.removeItems(getWeapons(playerInventory, param).toList()); playerInventory.removeItems(getWeapons(playerInventory, param).toList());
CommandHandler.sendTranslatedMessage(sender, "commands.clear.weapons", playerString); CommandHandler.sendTranslatedMessage(sender, "commands.clear.weapons", playerString);
} }
case "art" -> { case "art" -> {
playerInventory.removeItems(getRelics(playerInventory, param).toList()); playerInventory.removeItems(getRelics(playerInventory, param).toList());
CommandHandler.sendTranslatedMessage(sender, "commands.clear.artifacts", playerString); CommandHandler.sendTranslatedMessage(sender, "commands.clear.artifacts", playerString);
} }
case "mat" -> { case "mat" -> {
playerInventory.removeItems(getOther(ItemType.ITEM_MATERIAL, playerInventory, param).toList()); playerInventory.removeItems(
CommandHandler.sendTranslatedMessage(sender, "commands.clear.materials", playerString); getOther(ItemType.ITEM_MATERIAL, playerInventory, param).toList());
} CommandHandler.sendTranslatedMessage(sender, "commands.clear.materials", playerString);
case "all" -> { }
playerInventory.removeItems(getRelics(playerInventory, param).toList()); case "all" -> {
CommandHandler.sendTranslatedMessage(sender, "commands.clear.artifacts", playerString); playerInventory.removeItems(getRelics(playerInventory, param).toList());
playerInventory.removeItems(getWeapons(playerInventory, param).toList()); CommandHandler.sendTranslatedMessage(sender, "commands.clear.artifacts", playerString);
CommandHandler.sendTranslatedMessage(sender, "commands.clear.weapons", playerString); playerInventory.removeItems(getWeapons(playerInventory, param).toList());
playerInventory.removeItems(getOther(ItemType.ITEM_MATERIAL, playerInventory, param).toList()); CommandHandler.sendTranslatedMessage(sender, "commands.clear.weapons", playerString);
CommandHandler.sendTranslatedMessage(sender, "commands.clear.materials", playerString); playerInventory.removeItems(
playerInventory.removeItems(getOther(ItemType.ITEM_FURNITURE, playerInventory, param).toList()); getOther(ItemType.ITEM_MATERIAL, playerInventory, param).toList());
CommandHandler.sendTranslatedMessage(sender, "commands.clear.furniture", playerString); CommandHandler.sendTranslatedMessage(sender, "commands.clear.materials", playerString);
playerInventory.removeItems(getOther(ItemType.ITEM_DISPLAY, playerInventory, param).toList()); playerInventory.removeItems(
CommandHandler.sendTranslatedMessage(sender, "commands.clear.displays", playerString); getOther(ItemType.ITEM_FURNITURE, playerInventory, param).toList());
playerInventory.removeItems(getOther(ItemType.ITEM_VIRTUAL, playerInventory, param).toList()); CommandHandler.sendTranslatedMessage(sender, "commands.clear.furniture", playerString);
CommandHandler.sendTranslatedMessage(sender, "commands.clear.virtuals", playerString); playerInventory.removeItems(
CommandHandler.sendTranslatedMessage(sender, "commands.clear.everything", playerString); getOther(ItemType.ITEM_DISPLAY, playerInventory, param).toList());
} CommandHandler.sendTranslatedMessage(sender, "commands.clear.displays", playerString);
} playerInventory.removeItems(
} getOther(ItemType.ITEM_VIRTUAL, playerInventory, param).toList());
CommandHandler.sendTranslatedMessage(sender, "commands.clear.virtuals", playerString);
private static class ClearItemParameters { CommandHandler.sendTranslatedMessage(sender, "commands.clear.everything", playerString);
@Setter }
public int lvl = 1; }
@Setter }
public int refinement = 1;
@Setter private static class ClearItemParameters {
public int rank = 4; @Setter public int lvl = 1;
} @Setter public int refinement = 1;
} @Setter public int rank = 4;
}
}

View File

@ -1,49 +1,57 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.List;
@Command(
@Command(label = "coop", usage = {"[<host UID>]"}, permission = "server.coop", permissionTargeted = "server.coop.others") label = "coop",
public final class CoopCommand implements CommandHandler { usage = {"[<host UID>]"},
permission = "server.coop",
@Override permissionTargeted = "server.coop.others")
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class CoopCommand implements CommandHandler {
Player host = sender;
switch (args.size()) { @Override
case 0: // Summon target to self public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender == null) { // Console doesn't have a self to summon to Player host = sender;
sendUsageMessage(sender); switch (args.size()) {
return; case 0: // Summon target to self
} if (sender == null) { // Console doesn't have a self to summon to
break; sendUsageMessage(sender);
case 1: // Summon target to argument return;
try { }
int hostId = Integer.parseInt(args.get(0)); break;
host = Grasscutter.getGameServer().getPlayerByUid(hostId); case 1: // Summon target to argument
if (host == null) { try {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.player_offline_error"); int hostId = Integer.parseInt(args.get(0));
return; host = Grasscutter.getGameServer().getPlayerByUid(hostId);
} if (host == null) {
break; CommandHandler.sendTranslatedMessage(sender, "commands.execution.player_offline_error");
} catch (NumberFormatException ignored) { return;
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.uid"); }
return; break;
} } catch (NumberFormatException ignored) {
default: CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.uid");
sendUsageMessage(sender); return;
return; }
} default:
sendUsageMessage(sender);
// There's no target==host check but this just places them in multiplayer in their own world which seems fine. return;
if (targetPlayer.isInMultiplayer()) { }
targetPlayer.getServer().getMultiplayerSystem().leaveCoop(targetPlayer);
} // There's no target==host check but this just places them in multiplayer in their own world
host.getServer().getMultiplayerSystem().applyEnterMp(targetPlayer, host.getUid()); // which seems fine.
targetPlayer.getServer().getMultiplayerSystem().applyEnterMpReply(host, targetPlayer.getUid(), true); if (targetPlayer.isInMultiplayer()) {
CommandHandler.sendTranslatedMessage(sender, "commands.coop.success", targetPlayer.getNickname(), host.getNickname()); targetPlayer.getServer().getMultiplayerSystem().leaveCoop(targetPlayer);
} }
} host.getServer().getMultiplayerSystem().applyEnterMp(targetPlayer, host.getUid());
targetPlayer
.getServer()
.getMultiplayerSystem()
.applyEnterMpReply(host, targetPlayer.getUid(), true);
CommandHandler.sendTranslatedMessage(
sender, "commands.coop.success", targetPlayer.getNickname(), host.getNickname());
}
}

View File

@ -1,39 +1,50 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import java.util.List; import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "enter_dungeon", aliases = {"enterdungeon", "dungeon"}, usage = {"<dungeonId>"}, permission = "player.enterdungeon", permissionTargeted = "player.enterdungeon.others") label = "enter_dungeon",
public final class EnterDungeonCommand implements CommandHandler { aliases = {"enterdungeon", "dungeon"},
usage = {"<dungeonId>"},
@Override permission = "player.enterdungeon",
public void execute(Player sender, Player targetPlayer, List<String> args) { permissionTargeted = "player.enterdungeon.others")
if (args.size() < 1) { public final class EnterDungeonCommand implements CommandHandler {
sendUsageMessage(sender);
return; @Override
} public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
try { sendUsageMessage(sender);
int dungeonId = Integer.parseInt(args.get(0)); return;
if (dungeonId == targetPlayer.getSceneId()) { }
CommandHandler.sendMessage(sender, translate(sender, "commands.enter_dungeon.in_dungeon_error"));
return; try {
} int dungeonId = Integer.parseInt(args.get(0));
if (dungeonId == targetPlayer.getSceneId()) {
boolean result = targetPlayer.getServer().getDungeonSystem().enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId); CommandHandler.sendMessage(
sender, translate(sender, "commands.enter_dungeon.in_dungeon_error"));
if (!result) { return;
CommandHandler.sendMessage(sender, translate(sender, "commands.enter_dungeon.not_found_error")); }
} else {
CommandHandler.sendMessage(sender, translate(sender, "commands.enter_dungeon.changed", dungeonId)); boolean result =
} targetPlayer
} catch (Exception e) { .getServer()
sendUsageMessage(sender); .getDungeonSystem()
} .enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId);
}
} if (!result) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.enter_dungeon.not_found_error"));
} else {
CommandHandler.sendMessage(
sender, translate(sender, "commands.enter_dungeon.changed", dungeonId));
}
} catch (Exception e) {
sendUsageMessage(sender);
}
}
}

View File

@ -1,32 +1,44 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import java.util.List; import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "heal", aliases = {"h"}, permission = "player.heal", permissionTargeted = "player.heal.others") label = "heal",
public final class HealCommand implements CommandHandler { aliases = {"h"},
permission = "player.heal",
@Override permissionTargeted = "player.heal.others")
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class HealCommand implements CommandHandler {
targetPlayer.getTeamManager().getActiveTeam().forEach(entity -> {
boolean isAlive = entity.isAlive(); @Override
entity.setFightProperty( public void execute(Player sender, Player targetPlayer, List<String> args) {
FightProperty.FIGHT_PROP_CUR_HP, targetPlayer
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) .getTeamManager()
); .getActiveTeam()
entity.getWorld().broadcastPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); .forEach(
if (!isAlive) { entity -> {
entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); boolean isAlive = entity.isAlive();
} entity.setFightProperty(
}); FightProperty.FIGHT_PROP_CUR_HP,
CommandHandler.sendMessage(sender, translate(sender, "commands.heal.success")); entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP));
} entity
} .getWorld()
.broadcastPacket(
new PacketAvatarFightPropUpdateNotify(
entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP));
if (!isAlive) {
entity
.getWorld()
.broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
}
});
CommandHandler.sendMessage(sender, translate(sender, "commands.heal.success"));
}
}

View File

@ -1,83 +1,92 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.Command;
import emu.grasscutter.game.Account; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.Account;
import java.util.ArrayList; import emu.grasscutter.game.player.Player;
import java.util.List; import java.util.ArrayList;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "help", usage = {"[<command>]"}, targetRequirement = Command.TargetRequirement.NONE) label = "help",
public final class HelpCommand implements CommandHandler { usage = {"[<command>]"},
private final boolean SHOW_COMMANDS_WITHOUT_PERMISSIONS = false; // TODO: Make this into a server config key targetRequirement = Command.TargetRequirement.NONE)
public final class HelpCommand implements CommandHandler {
private String createCommand(Player player, CommandHandler command, List<String> args) { private final boolean SHOW_COMMANDS_WITHOUT_PERMISSIONS =
StringBuilder builder = new StringBuilder(command.getLabel()) false; // TODO: Make this into a server config key
.append(" - ")
.append(command.getDescriptionString(player)) private String createCommand(Player player, CommandHandler command, List<String> args) {
.append("\n\t") StringBuilder builder =
.append(command.getUsageString(player, args.toArray(new String[0]))); new StringBuilder(command.getLabel())
.append(" - ")
Command annotation = command.getClass().getAnnotation(Command.class); .append(command.getDescriptionString(player))
if (annotation.aliases().length > 0) { .append("\n\t")
builder.append("\n\t").append(translate(player, "commands.help.aliases")); .append(command.getUsageString(player, args.toArray(new String[0])));
for (String alias : annotation.aliases()) {
builder.append(alias).append(" "); Command annotation = command.getClass().getAnnotation(Command.class);
} if (annotation.aliases().length > 0) {
} builder.append("\n\t").append(translate(player, "commands.help.aliases"));
for (String alias : annotation.aliases()) {
builder.append("\n\t").append(translate(player, "commands.help.tip_need_permission")); builder.append(alias).append(" ");
if (!annotation.permission().isEmpty()) { }
builder.append(annotation.permission()); }
} else {
builder.append(translate(player, "commands.help.tip_need_no_permission")); builder.append("\n\t").append(translate(player, "commands.help.tip_need_permission"));
} if (!annotation.permission().isEmpty()) {
builder.append(annotation.permission());
if (!annotation.permissionTargeted().isEmpty()) { } else {
String permissionTargeted = annotation.permissionTargeted(); builder.append(translate(player, "commands.help.tip_need_no_permission"));
builder.append(" ").append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted)); }
}
return builder.toString(); if (!annotation.permissionTargeted().isEmpty()) {
} String permissionTargeted = annotation.permissionTargeted();
builder
@Override .append(" ")
public void execute(Player player, Player targetPlayer, List<String> args) { .append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted));
Account account = (player == null) ? null : player.getAccount(); }
var commandMap = CommandMap.getInstance(); return builder.toString();
List<String> commands = new ArrayList<>(); }
List<String> commands_no_permission = new ArrayList<>();
if (args.isEmpty()) { @Override
commandMap.getHandlers().forEach((key, command) -> { public void execute(Player player, Player targetPlayer, List<String> args) {
Command annotation = command.getClass().getAnnotation(Command.class); Account account = (player == null) ? null : player.getAccount();
if (player == null || account.hasPermission(annotation.permission())) { var commandMap = CommandMap.getInstance();
commands.add(createCommand(player, command, args)); List<String> commands = new ArrayList<>();
} else if (SHOW_COMMANDS_WITHOUT_PERMISSIONS) { List<String> commands_no_permission = new ArrayList<>();
commands_no_permission.add(createCommand(player, command, args)); if (args.isEmpty()) {
} commandMap
}); .getHandlers()
CommandHandler.sendTranslatedMessage(player, "commands.help.available_commands"); .forEach(
} else { (key, command) -> {
String command_str = args.remove(0).toLowerCase(); Command annotation = command.getClass().getAnnotation(Command.class);
CommandHandler command = commandMap.getHandler(command_str); if (player == null || account.hasPermission(annotation.permission())) {
if (command == null) { commands.add(createCommand(player, command, args));
CommandHandler.sendTranslatedMessage(player, "commands.generic.command_exist_error"); } else if (SHOW_COMMANDS_WITHOUT_PERMISSIONS) {
CommandHandler.sendMessage(player, "Command: " + command_str); commands_no_permission.add(createCommand(player, command, args));
return; }
} else { });
Command annotation = command.getClass().getAnnotation(Command.class); CommandHandler.sendTranslatedMessage(player, "commands.help.available_commands");
if (player == null || account.hasPermission(annotation.permission())) { } else {
commands.add(createCommand(player, command, args)); String command_str = args.remove(0).toLowerCase();
} else { CommandHandler command = commandMap.getHandler(command_str);
commands_no_permission.add(createCommand(player, command, args)); if (command == null) {
} CommandHandler.sendTranslatedMessage(player, "commands.generic.command_exist_error");
} CommandHandler.sendMessage(player, "Command: " + command_str);
} return;
final String suf = "\n\t" + translate(player, "commands.help.warn_player_has_no_permission"); } else {
commands.forEach(s -> CommandHandler.sendMessage(player, s)); Command annotation = command.getClass().getAnnotation(Command.class);
commands_no_permission.forEach(s -> CommandHandler.sendMessage(player, s + suf)); if (player == null || account.hasPermission(annotation.permission())) {
} commands.add(createCommand(player, command, args));
} } else {
commands_no_permission.add(createCommand(player, command, args));
}
}
}
final String suf = "\n\t" + translate(player, "commands.help.warn_player_has_no_permission");
commands.forEach(s -> CommandHandler.sendMessage(player, s));
commands_no_permission.forEach(s -> CommandHandler.sendMessage(player, s + suf));
}
}

View File

@ -1,25 +1,34 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.List;
@Command(
@Command(label = "kick", aliases = {"restart"}, permissionTargeted = "server.kick") label = "kick",
public final class KickCommand implements CommandHandler { aliases = {"restart"},
permissionTargeted = "server.kick")
@Override public final class KickCommand implements CommandHandler {
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender != null) { @Override
CommandHandler.sendTranslatedMessage(sender, "commands.kick.player_kick_player", public void execute(Player sender, Player targetPlayer, List<String> args) {
sender.getUid(), sender.getAccount().getUsername(), if (sender != null) {
targetPlayer.getUid(), targetPlayer.getAccount().getUsername()); CommandHandler.sendTranslatedMessage(
} else { sender,
CommandHandler.sendTranslatedMessage(sender, "commands.kick.server_kick_player", "commands.kick.player_kick_player",
targetPlayer.getUid(), targetPlayer.getAccount().getUsername()); sender.getUid(),
} sender.getAccount().getUsername(),
targetPlayer.getUid(),
targetPlayer.getSession().close(); targetPlayer.getAccount().getUsername());
} } else {
} CommandHandler.sendTranslatedMessage(
sender,
"commands.kick.server_kick_player",
targetPlayer.getUid(),
targetPlayer.getAccount().getUsername());
}
targetPlayer.getSession().close();
}
}

View File

@ -1,47 +1,54 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.command.Command;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import java.util.List; import emu.grasscutter.game.world.Scene;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "killall", usage = {"[<sceneId>]"}, permission = "server.killall", permissionTargeted = "server.killall.others") label = "killall",
public final class KillAllCommand implements CommandHandler { usage = {"[<sceneId>]"},
permission = "server.killall",
@Override permissionTargeted = "server.killall.others")
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class KillAllCommand implements CommandHandler {
Scene scene = targetPlayer.getScene();
try { @Override
switch (args.size()) { public void execute(Player sender, Player targetPlayer, List<String> args) {
case 0: // *No args* Scene scene = targetPlayer.getScene();
break; try {
case 1: // [sceneId] switch (args.size()) {
scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0))); case 0: // *No args*
break; break;
default: case 1: // [sceneId]
sendUsageMessage(sender); scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0)));
return; break;
} default:
} catch (NumberFormatException ignored) { sendUsageMessage(sender);
CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); return;
} }
if (scene == null) { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.killall.scene_not_found_in_player_world")); CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error"));
return; }
} if (scene == null) {
CommandHandler.sendMessage(
// Separate into list to avoid concurrency issue sender, translate(sender, "commands.killall.scene_not_found_in_player_world"));
final Scene sceneF = scene; return;
List<GameEntity> toKill = sceneF.getEntities().values().stream() }
.filter(entity -> entity instanceof EntityMonster)
.toList(); // Separate into list to avoid concurrency issue
toKill.forEach(entity -> sceneF.killEntity(entity, 0)); final Scene sceneF = scene;
CommandHandler.sendMessage(sender, translate(sender, "commands.killall.kill_monsters_in_scene", toKill.size(), scene.getId())); List<GameEntity> toKill =
} sceneF.getEntities().values().stream()
} .filter(entity -> entity instanceof EntityMonster)
.toList();
toKill.forEach(entity -> sceneF.killEntity(entity, 0));
CommandHandler.sendMessage(
sender,
translate(sender, "commands.killall.kill_monsters_in_scene", toKill.size(), scene.getId()));
}
}

View File

@ -1,32 +1,41 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.command.Command;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.List; import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "killCharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", permissionTargeted = "player.killcharacter.others") label = "killCharacter",
public final class KillCharacterCommand implements CommandHandler { aliases = {"suicide", "kill"},
permission = "player.killcharacter",
@Override permissionTargeted = "player.killcharacter.others")
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class KillCharacterCommand implements CommandHandler {
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); @Override
// Packets public void execute(Player sender, Player targetPlayer, List<String> args) {
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
// remove // Packets
targetPlayer.getScene().removeEntity(entity); entity
entity.onDeath(0); .getWorld()
.broadcastPacket(
CommandHandler.sendMessage(sender, translate(sender, "commands.killCharacter.success", targetPlayer.getNickname())); new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
} entity
} .getWorld()
.broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD));
// remove
targetPlayer.getScene().removeEntity(entity);
entity.onDeath(0);
CommandHandler.sendMessage(
sender, translate(sender, "commands.killCharacter.success", targetPlayer.getNickname()));
}
}

View File

@ -1,53 +1,58 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.utils.Utils; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List; import emu.grasscutter.utils.Utils;
import java.util.Locale; import java.util.List;
import java.util.Locale;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "language", usage = {"[<language code>]"}, aliases = {"lang"}, targetRequirement = Command.TargetRequirement.NONE) label = "language",
public final class LanguageCommand implements CommandHandler { usage = {"[<language code>]"},
aliases = {"lang"},
@Override targetRequirement = Command.TargetRequirement.NONE)
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class LanguageCommand implements CommandHandler {
if (args.isEmpty()) {
String curLangCode = null; @Override
if (sender != null) { public void execute(Player sender, Player targetPlayer, List<String> args) {
curLangCode = Utils.getLanguageCode(sender.getAccount().getLocale()); if (args.isEmpty()) {
} else { String curLangCode = null;
curLangCode = Grasscutter.getLanguage().getLanguageCode(); if (sender != null) {
} curLangCode = Utils.getLanguageCode(sender.getAccount().getLocale());
CommandHandler.sendMessage(sender, translate(sender, "commands.language.current_language", curLangCode)); } else {
return; curLangCode = Grasscutter.getLanguage().getLanguageCode();
} }
CommandHandler.sendMessage(
String langCode = args.get(0); sender, translate(sender, "commands.language.current_language", curLangCode));
return;
var languageInst = Grasscutter.getLanguage(langCode); }
var actualLangCode = languageInst.getLanguageCode();
var locale = Locale.forLanguageTag(actualLangCode); String langCode = args.get(0);
if (sender != null) {
var account = sender.getAccount(); var languageInst = Grasscutter.getLanguage(langCode);
account.setLocale(locale); var actualLangCode = languageInst.getLanguageCode();
account.save(); var locale = Locale.forLanguageTag(actualLangCode);
} else { if (sender != null) {
Grasscutter.setLanguage(languageInst); var account = sender.getAccount();
var config = Grasscutter.getConfig(); account.setLocale(locale);
config.language.language = locale; account.save();
Grasscutter.saveConfig(config); } else {
} Grasscutter.setLanguage(languageInst);
var config = Grasscutter.getConfig();
if (!langCode.equals(actualLangCode)) { config.language.language = locale;
CommandHandler.sendMessage(sender, translate(sender, "commands.language.language_not_found", langCode)); Grasscutter.saveConfig(config);
} }
CommandHandler.sendMessage(sender, translate(sender, "commands.language.language_changed", actualLangCode)); if (!langCode.equals(actualLangCode)) {
CommandHandler.sendMessage(
} sender, translate(sender, "commands.language.language_not_found", langCode));
} }
CommandHandler.sendMessage(
sender, translate(sender, "commands.language.language_changed", actualLangCode));
}
}

View File

@ -1,53 +1,56 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import java.util.List; import emu.grasscutter.game.player.Player;
import java.util.Map; import java.util.List;
import java.util.Map;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "list", aliases = {"players"}, usage = {"[<UID>]"}, targetRequirement = Command.TargetRequirement.NONE) label = "list",
public final class ListCommand implements CommandHandler { aliases = {"players"},
usage = {"[<UID>]"},
@Override targetRequirement = Command.TargetRequirement.NONE)
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class ListCommand implements CommandHandler {
Map<Integer, Player> playersMap = Grasscutter.getGameServer().getPlayers();
boolean needUID = false; @Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() > 0) { Map<Integer, Player> playersMap = Grasscutter.getGameServer().getPlayers();
needUID = args.get(0).equals("uid"); boolean needUID = false;
}
if (args.size() > 0) {
CommandHandler.sendMessage(sender, translate(sender, "commands.list.success", playersMap.size())); needUID = args.get(0).equals("uid");
}
if (playersMap.size() != 0) {
StringBuilder playerSet = new StringBuilder(); CommandHandler.sendMessage(
boolean finalNeedUID = needUID; sender, translate(sender, "commands.list.success", playersMap.size()));
playersMap.values().forEach(player -> { if (playersMap.size() != 0) {
playerSet.append(player.getNickname()); StringBuilder playerSet = new StringBuilder();
boolean finalNeedUID = needUID;
if (finalNeedUID) {
if (sender != null) { playersMap
playerSet.append(" <color=green>(") .values()
.append(player.getUid()) .forEach(
.append(")</color>"); player -> {
} else { playerSet.append(player.getNickname());
playerSet.append(" (")
.append(player.getUid()) if (finalNeedUID) {
.append(")"); if (sender != null) {
} playerSet.append(" <color=green>(").append(player.getUid()).append(")</color>");
} } else {
playerSet.append(" (").append(player.getUid()).append(")");
playerSet.append(", "); }
}); }
String players = playerSet.toString(); playerSet.append(", ");
CommandHandler.sendMessage(sender, players.substring(0, players.length() - 2)); });
}
} String players = playerSet.toString();
} CommandHandler.sendMessage(sender, players.substring(0, players.length() - 2));
}
}
}

View File

@ -1,73 +1,74 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.Command.TargetRequirement; import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.Command;
import emu.grasscutter.game.Account; import emu.grasscutter.command.Command.TargetRequirement;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.Account;
import java.util.List; import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "permission", usage = { label = "permission",
"add <permission>", usage = {"add <permission>", "remove <permission>", "clear", "list"},
"remove <permission>", permission = "permission",
"clear", targetRequirement = TargetRequirement.PLAYER)
"list" public final class PermissionCommand implements CommandHandler {
}, permission = "permission", targetRequirement = TargetRequirement.PLAYER)
public final class PermissionCommand implements CommandHandler { @Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
@Override if (args.isEmpty() || args.size() > 2) {
public void execute(Player sender, Player targetPlayer, List<String> args) { sendUsageMessage(sender);
if (args.isEmpty() || args.size() > 2) { return;
sendUsageMessage(sender); }
return;
} if (!Grasscutter.getPermissionHandler().EnablePermissionCommand()) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.permission_error");
if (!Grasscutter.getPermissionHandler().EnablePermissionCommand()) { return;
CommandHandler.sendTranslatedMessage(sender, "commands.generic.permission_error"); }
return;
} String action = args.get(0);
String permission = "";
String action = args.get(0); if (args.size() > 1) {
String permission = ""; permission = args.get(1);
if (args.size() > 1) { }
permission = args.get(1);
} Account account = targetPlayer.getAccount();
if (account == null) {
Account account = targetPlayer.getAccount(); CommandHandler.sendMessage(sender, translate(sender, "commands.permission.account_error"));
if (account == null) { return;
CommandHandler.sendMessage(sender, translate(sender, "commands.permission.account_error")); }
return;
} switch (action) {
default:
switch (action) { sendUsageMessage(sender);
default: break;
sendUsageMessage(sender); case "add":
break; if (permission.isEmpty()) {
case "add": sendUsageMessage(sender);
if (permission.isEmpty()) { } else if (account.addPermission(permission)) {
sendUsageMessage(sender); CommandHandler.sendMessage(sender, translate(sender, "commands.permission.add"));
} else if (account.addPermission(permission)) { } else
CommandHandler.sendMessage(sender, translate(sender, "commands.permission.add")); CommandHandler.sendMessage(sender, translate(sender, "commands.permission.has_error"));
} else CommandHandler.sendMessage(sender, translate(sender, "commands.permission.has_error")); break;
break; case "remove":
case "remove": if (account.removePermission(permission)) {
if (account.removePermission(permission)) { CommandHandler.sendMessage(sender, translate(sender, "commands.permission.remove"));
CommandHandler.sendMessage(sender, translate(sender, "commands.permission.remove")); } else
} else CommandHandler.sendMessage(sender, translate(sender, "commands.permission.not_have_error")); CommandHandler.sendMessage(
break; sender, translate(sender, "commands.permission.not_have_error"));
case "clear": break;
account.clearPermission(); case "clear":
CommandHandler.sendMessage(sender, translate(sender, "commands.permission.remove")); account.clearPermission();
break; CommandHandler.sendMessage(sender, translate(sender, "commands.permission.remove"));
case "list": break;
CommandHandler.sendMessage(sender, String.join("\n", account.getPermissions())); case "list":
break; CommandHandler.sendMessage(sender, String.join("\n", account.getPermissions()));
} break;
}
account.save();
} account.save();
} }
}

View File

@ -1,20 +1,29 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import java.util.List;
import java.util.List;
@Command(
@Command(label = "position", aliases = {"pos"}) label = "position",
public final class PositionCommand implements CommandHandler { aliases = {"pos"})
public final class PositionCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) { @Override
Position pos = targetPlayer.getPosition(); public void execute(Player sender, Player targetPlayer, List<String> args) {
Position rot = targetPlayer.getRotation(); Position pos = targetPlayer.getPosition();
CommandHandler.sendTranslatedMessage(sender, "commands.position.success", Position rot = targetPlayer.getRotation();
pos.getX(), pos.getY(), pos.getZ(), rot.getX(), rot.getY(), rot.getZ(), targetPlayer.getSceneId()); CommandHandler.sendTranslatedMessage(
} sender,
} "commands.position.success",
pos.getX(),
pos.getY(),
pos.getZ(),
rot.getX(),
rot.getY(),
rot.getZ(),
targetPlayer.getSceneId());
}
}

View File

@ -1,64 +1,64 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List; import emu.grasscutter.game.quest.GameQuest;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "quest", label = "quest",
aliases = {"q"}, aliases = {"q"},
usage = {"(add|finish) [<questId>]"}, usage = {"(add|finish) [<questId>]"},
permission = "player.quest", permission = "player.quest",
permissionTargeted = "player.quest.others") permissionTargeted = "player.quest.others")
public final class QuestCommand implements CommandHandler { public final class QuestCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() != 2) { if (args.size() != 2) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
String cmd = args.get(0).toLowerCase(); String cmd = args.get(0).toLowerCase();
int questId; int questId;
try { try {
questId = Integer.parseInt(args.get(1)); questId = Integer.parseInt(args.get(1));
} catch (Exception e) { } catch (Exception e) {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.invalid_id")); CommandHandler.sendMessage(sender, translate(sender, "commands.quest.invalid_id"));
return; return;
} }
switch (cmd) { switch (cmd) {
case "add" -> { case "add" -> {
GameQuest quest = targetPlayer.getQuestManager().addQuest(questId); GameQuest quest = targetPlayer.getQuestManager().addQuest(questId);
if (quest != null) { if (quest != null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.added", questId)); CommandHandler.sendMessage(sender, translate(sender, "commands.quest.added", questId));
return; return;
} }
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found"));
} }
case "finish" -> { case "finish" -> {
GameQuest quest = targetPlayer.getQuestManager().getQuestById(questId); GameQuest quest = targetPlayer.getQuestManager().getQuestById(questId);
if (quest == null) { if (quest == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found"));
return; return;
} }
quest.finish(); quest.finish();
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.finished", questId)); CommandHandler.sendMessage(sender, translate(sender, "commands.quest.finished", questId));
} }
default -> { default -> {
sendUsageMessage(sender); sendUsageMessage(sender);
} }
} }
} }
} }

View File

@ -1,27 +1,29 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import java.util.List; import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "reload", permission = "server.reload", targetRequirement = Command.TargetRequirement.NONE) label = "reload",
public final class ReloadCommand implements CommandHandler { permission = "server.reload",
targetRequirement = Command.TargetRequirement.NONE)
@Override public final class ReloadCommand implements CommandHandler {
public void execute(Player sender, Player targetPlayer, List<String> args) {
CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start")); @Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Grasscutter.loadConfig(); CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start"));
Grasscutter.loadLanguage();
Grasscutter.getGameServer().getGachaSystem().load(); Grasscutter.loadConfig();
Grasscutter.getGameServer().getDropSystem().load(); Grasscutter.loadLanguage();
Grasscutter.getGameServer().getShopSystem().load(); Grasscutter.getGameServer().getGachaSystem().load();
Grasscutter.getGameServer().getDropSystem().load();
CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done")); Grasscutter.getGameServer().getShopSystem().load();
}
} CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done"));
}
}

View File

@ -1,42 +1,43 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.command.Command;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import java.util.List; import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command( label = "resetConst",
label = "resetConst", aliases = {"resetconstellation"},
aliases = {"resetconstellation"}, usage = "[all]",
usage = "[all]", permission = "player.resetconstellation",
permission = "player.resetconstellation", permissionTargeted = "player.resetconstellation.others")
permissionTargeted = "player.resetconstellation.others") public final class ResetConstCommand implements CommandHandler {
public final class ResetConstCommand implements CommandHandler {
@Override
@Override public void execute(Player sender, Player targetPlayer, List<String> args) {
public void execute(Player sender, Player targetPlayer, List<String> args) { if (args.size() > 0 && args.get(0).equalsIgnoreCase("all")) {
if (args.size() > 0 && args.get(0).equalsIgnoreCase("all")) { targetPlayer.getAvatars().forEach(this::resetConstellation);
targetPlayer.getAvatars().forEach(this::resetConstellation); CommandHandler.sendMessage(sender, translate(sender, "commands.resetConst.reset_all"));
CommandHandler.sendMessage(sender, translate(sender, "commands.resetConst.reset_all")); } else {
} else { EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); if (entity == null) {
if (entity == null) { return;
return; }
}
Avatar avatar = entity.getAvatar();
Avatar avatar = entity.getAvatar(); this.resetConstellation(avatar);
this.resetConstellation(avatar);
CommandHandler.sendMessage(
CommandHandler.sendMessage(sender, translate(sender, "commands.resetConst.success", avatar.getAvatarData().getName())); sender,
} translate(sender, "commands.resetConst.success", avatar.getAvatarData().getName()));
} }
}
private void resetConstellation(Avatar avatar) {
avatar.forceConstellationLevel(-1); private void resetConstellation(Avatar avatar) {
} avatar.forceConstellationLevel(-1);
} }
}

View File

@ -1,20 +1,23 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import java.util.List; import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "resetShopLimit", aliases = {"resetshop"}, permission = "server.resetshop", permissionTargeted = "server.resetshop.others") label = "resetShopLimit",
public final class ResetShopLimitCommand implements CommandHandler { aliases = {"resetshop"},
permission = "server.resetshop",
@Override permissionTargeted = "server.resetshop.others")
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class ResetShopLimitCommand implements CommandHandler {
targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0));
targetPlayer.save(); @Override
CommandHandler.sendMessage(sender, translate(sender, "commands.resetShopLimit.success")); public void execute(Player sender, Player targetPlayer, List<String> args) {
} targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0));
} targetPlayer.save();
CommandHandler.sendMessage(sender, translate(sender, "commands.resetShopLimit.success"));
}
}

View File

@ -1,202 +1,241 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.command.Command;
import emu.grasscutter.game.mail.Mail; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.mail.Mail;
import java.util.HashMap; import emu.grasscutter.game.player.Player;
import java.util.List; import java.util.HashMap;
import java.util.Objects; import java.util.List;
import java.util.Objects;
import static emu.grasscutter.utils.Language.translate;
@SuppressWarnings("ConstantConditions")
@SuppressWarnings("ConstantConditions") @Command(
@Command( label = "sendMail",
label = "sendMail", usage = {"(<userId>|all) [<templateId>]", "help"},
usage = {"(<userId>|all) [<templateId>]", "help"}, permission = "server.sendmail",
permission = "server.sendmail", targetRequirement = Command.TargetRequirement.NONE)
targetRequirement = Command.TargetRequirement.NONE) public final class SendMailCommand implements CommandHandler {
public final class SendMailCommand implements CommandHandler {
// TODO: You should be able to do /sendmail and then just send subsequent messages until you
// TODO: You should be able to do /sendmail and then just send subsequent messages until you finish // finish
// However, due to the current nature of the command system, I don't think this is possible without rewriting // However, due to the current nature of the command system, I don't think this is possible
// the command system (again). For now this will do // without rewriting
// the command system (again). For now this will do
// Key = User that is constructing the mail.
private static final HashMap<Integer, MailBuilder> mailBeingConstructed = new HashMap<Integer, MailBuilder>(); // Key = User that is constructing the mail.
private static final HashMap<Integer, MailBuilder> mailBeingConstructed =
// Yes this is awful and I hate it. new HashMap<Integer, MailBuilder>();
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) { // Yes this is awful and I hate it.
int senderId; @Override
if (sender != null) { public void execute(Player sender, Player targetPlayer, List<String> args) {
senderId = sender.getUid(); int senderId;
} else { if (sender != null) {
senderId = -1; senderId = sender.getUid();
} } else {
senderId = -1;
if (!mailBeingConstructed.containsKey(senderId)) { }
switch (args.size()) {
case 1 -> { if (!mailBeingConstructed.containsKey(senderId)) {
MailBuilder mailBuilder; switch (args.size()) {
switch (args.get(0).toLowerCase()) { case 1 -> {
case "help" -> { MailBuilder mailBuilder;
sendUsageMessage(sender); switch (args.get(0).toLowerCase()) {
return; case "help" -> {
} sendUsageMessage(sender);
case "all" -> mailBuilder = new MailBuilder(true, new Mail()); return;
default -> { }
if (DatabaseHelper.getPlayerByUid(Integer.parseInt(args.get(0))) != null) { case "all" -> mailBuilder = new MailBuilder(true, new Mail());
mailBuilder = new MailBuilder(Integer.parseInt(args.get(0)), new Mail()); default -> {
} else { if (DatabaseHelper.getPlayerByUid(Integer.parseInt(args.get(0))) != null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.user_not_exist", args.get(0))); mailBuilder = new MailBuilder(Integer.parseInt(args.get(0)), new Mail());
return; } else {
} CommandHandler.sendMessage(
} sender, translate(sender, "commands.sendMail.user_not_exist", args.get(0)));
} return;
mailBeingConstructed.put(senderId, mailBuilder); }
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.start_composition")); }
} }
case 2 -> CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.templates")); mailBeingConstructed.put(senderId, mailBuilder);
default -> CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.invalid_arguments")); CommandHandler.sendMessage(
} sender, translate(sender, "commands.sendMail.start_composition"));
} else { }
MailBuilder mailBuilder = mailBeingConstructed.get(senderId); case 2 -> CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.templates"));
if (args.size() >= 1) { default -> CommandHandler.sendMessage(
switch (args.get(0).toLowerCase()) { sender, translate(sender, "commands.sendMail.invalid_arguments"));
case "stop" -> { }
mailBeingConstructed.remove(senderId); } else {
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_cancel")); MailBuilder mailBuilder = mailBeingConstructed.get(senderId);
}
case "finish" -> { if (args.size() >= 1) {
if (mailBuilder.constructionStage == 3) { switch (args.get(0).toLowerCase()) {
if (!mailBuilder.sendToAll) { case "stop" -> {
Grasscutter.getGameServer().getPlayerByUid(mailBuilder.recipient, true).sendMail(mailBuilder.mail); mailBeingConstructed.remove(senderId);
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_done", mailBuilder.recipient)); CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_cancel"));
} else { }
DatabaseHelper.getByGameClass(Player.class).forEach(player -> { case "finish" -> {
var onlinePlayer = Grasscutter.getGameServer().getPlayerByUid(player.getUid(), false); if (mailBuilder.constructionStage == 3) {
Objects.requireNonNullElse(onlinePlayer, player).sendMail(mailBuilder.mail); if (!mailBuilder.sendToAll) {
}); Grasscutter.getGameServer()
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_all_done")); .getPlayerByUid(mailBuilder.recipient, true)
} .sendMail(mailBuilder.mail);
mailBeingConstructed.remove(senderId); CommandHandler.sendMessage(
} else { sender,
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.not_composition_end", getConstructionArgs(mailBuilder.constructionStage, sender))); translate(sender, "commands.sendMail.send_done", mailBuilder.recipient));
} } else {
} DatabaseHelper.getByGameClass(Player.class)
case "help" -> { .forEach(
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.please_use", getConstructionArgs(mailBuilder.constructionStage, sender))); player -> {
} var onlinePlayer =
default -> { Grasscutter.getGameServer().getPlayerByUid(player.getUid(), false);
switch (mailBuilder.constructionStage) { Objects.requireNonNullElse(onlinePlayer, player)
case 0 -> { .sendMail(mailBuilder.mail);
String title = String.join(" ", args.subList(0, args.size())); });
mailBuilder.mail.mailContent.title = title; CommandHandler.sendMessage(
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.set_title", title)); sender, translate(sender, "commands.sendMail.send_all_done"));
mailBuilder.constructionStage++; }
} mailBeingConstructed.remove(senderId);
case 1 -> { } else {
String contents = String.join(" ", args.subList(0, args.size())); CommandHandler.sendMessage(
mailBuilder.mail.mailContent.content = contents; sender,
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.set_contents", contents)); translate(
mailBuilder.constructionStage++; sender,
} "commands.sendMail.not_composition_end",
case 2 -> { getConstructionArgs(mailBuilder.constructionStage, sender)));
String msgSender = String.join(" ", args.subList(0, args.size())); }
mailBuilder.mail.mailContent.sender = msgSender; }
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.set_message_sender", msgSender)); case "help" -> {
mailBuilder.constructionStage++; CommandHandler.sendMessage(
} sender,
case 3 -> { translate(
int item; sender,
int lvl = 1; "commands.sendMail.please_use",
int amount = 1; getConstructionArgs(mailBuilder.constructionStage, sender)));
int refinement = 0; }
switch (args.size()) { default -> {
case 4: // <itemId|itemName> [amount] [level] [refinement] // TODO: this requires Mail support but there's no harm leaving it here for now switch (mailBuilder.constructionStage) {
try { case 0 -> {
refinement = Integer.parseInt(args.get(3)); String title = String.join(" ", args.subList(0, args.size()));
} catch (NumberFormatException ignored) { mailBuilder.mail.mailContent.title = title;
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemRefinement")); CommandHandler.sendMessage(
return; sender, translate(sender, "commands.sendMail.set_title", title));
} // Fallthrough mailBuilder.constructionStage++;
case 3: // <itemId|itemName> [amount] [level] }
try { case 1 -> {
lvl = Integer.parseInt(args.get(2)); String contents = String.join(" ", args.subList(0, args.size()));
} catch (NumberFormatException ignored) { mailBuilder.mail.mailContent.content = contents;
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemLevel")); CommandHandler.sendMessage(
return; sender, translate(sender, "commands.sendMail.set_contents", contents));
} // Fallthrough mailBuilder.constructionStage++;
case 2: // <itemId|itemName> [amount] }
try { case 2 -> {
amount = Integer.parseInt(args.get(1)); String msgSender = String.join(" ", args.subList(0, args.size()));
} catch (NumberFormatException ignored) { mailBuilder.mail.mailContent.sender = msgSender;
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); CommandHandler.sendMessage(
return; sender, translate(sender, "commands.sendMail.set_message_sender", msgSender));
} // Fallthrough mailBuilder.constructionStage++;
case 1: // <itemId|itemName> }
try { case 3 -> {
item = Integer.parseInt(args.get(0)); int item;
} catch (NumberFormatException ignored) { int lvl = 1;
// TODO: Parse from item name using GM Handbook. int amount = 1;
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); int refinement = 0;
return; switch (args.size()) {
} case 4: // <itemId|itemName> [amount] [level] [refinement] // TODO: this requires
break; // Mail support but there's no harm leaving it here for now
default: // *No args* try {
CommandHandler.sendTranslatedMessage(sender, "commands.sendMail.give_usage"); refinement = Integer.parseInt(args.get(3));
return; } catch (NumberFormatException ignored) {
} CommandHandler.sendMessage(
mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl)); sender, translate(sender, "commands.generic.invalid.itemRefinement"));
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send", amount, item, lvl)); return;
} } // Fallthrough
} case 3: // <itemId|itemName> [amount] [level]
} try {
} lvl = Integer.parseInt(args.get(2));
} else { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.invalid_arguments_please_use", getConstructionArgs(mailBuilder.constructionStage, sender))); CommandHandler.sendMessage(
} sender, translate(sender, "commands.generic.invalid.itemLevel"));
} return;
} } // Fallthrough
case 2: // <itemId|itemName> [amount]
private String getConstructionArgs(int stage, Player sender) { try {
return switch (stage) { amount = Integer.parseInt(args.get(1));
case 0 -> translate(sender, "commands.sendMail.title"); } catch (NumberFormatException ignored) {
case 1 -> translate(sender, "commands.sendMail.message"); CommandHandler.sendMessage(
case 2 -> translate(sender, "commands.sendMail.sender"); sender, translate(sender, "commands.generic.invalid.amount"));
case 3 -> translate(sender, "commands.sendMail.arguments"); return;
default -> translate(sender, "commands.sendMail.error", stage); } // Fallthrough
}; case 1: // <itemId|itemName>
} try {
item = Integer.parseInt(args.get(0));
public static class MailBuilder { } catch (NumberFormatException ignored) {
public int recipient; // TODO: Parse from item name using GM Handbook.
public boolean sendToAll; CommandHandler.sendMessage(
public int constructionStage; sender, translate(sender, "commands.generic.invalid.itemId"));
public Mail mail; return;
}
public MailBuilder(int recipient, Mail mail) { break;
this.recipient = recipient; default: // *No args*
this.sendToAll = false; CommandHandler.sendTranslatedMessage(sender, "commands.sendMail.give_usage");
this.constructionStage = 0; return;
this.mail = mail; }
} mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl));
CommandHandler.sendMessage(
public MailBuilder(boolean sendToAll, Mail mail) { sender, translate(sender, "commands.sendMail.send", amount, item, lvl));
if (sendToAll) { }
this.recipient = 0; }
this.sendToAll = true; }
this.constructionStage = 0; }
this.mail = mail; } else {
} else { CommandHandler.sendMessage(
Grasscutter.getLogger().error("Please use MailBuilder(int, mail) when not sending to all"); sender,
Thread.dumpStack(); translate(
} sender,
} "commands.sendMail.invalid_arguments_please_use",
} getConstructionArgs(mailBuilder.constructionStage, sender)));
} }
}
}
private String getConstructionArgs(int stage, Player sender) {
return switch (stage) {
case 0 -> translate(sender, "commands.sendMail.title");
case 1 -> translate(sender, "commands.sendMail.message");
case 2 -> translate(sender, "commands.sendMail.sender");
case 3 -> translate(sender, "commands.sendMail.arguments");
default -> translate(sender, "commands.sendMail.error", stage);
};
}
public static class MailBuilder {
public int recipient;
public boolean sendToAll;
public int constructionStage;
public Mail mail;
public MailBuilder(int recipient, Mail mail) {
this.recipient = recipient;
this.sendToAll = false;
this.constructionStage = 0;
this.mail = mail;
}
public MailBuilder(boolean sendToAll, Mail mail) {
if (sendToAll) {
this.recipient = 0;
this.sendToAll = true;
this.constructionStage = 0;
this.mail = mail;
} else {
Grasscutter.getLogger().error("Please use MailBuilder(int, mail) when not sending to all");
Thread.dumpStack();
}
}
}
}

View File

@ -1,38 +1,37 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.Command.TargetRequirement; import emu.grasscutter.command.Command.TargetRequirement;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.List;
@Command(
@Command( label = "sendMessage",
label = "sendMessage", aliases = {"say", "sendservmsg", "sendservermessage", "b", "broadcast"},
aliases = {"say", "sendservmsg", "sendservermessage", "b", "broadcast"}, usage = {"<message>"},
usage = {"<message>"}, permission = "server.sendmessage",
permission = "server.sendmessage", permissionTargeted = "server.sendmessage.others",
permissionTargeted = "server.sendmessage.others", targetRequirement = TargetRequirement.NONE)
targetRequirement = TargetRequirement.NONE) public final class SendMessageCommand implements CommandHandler {
public final class SendMessageCommand implements CommandHandler {
@Override
@Override public void execute(Player sender, Player targetPlayer, List<String> args) {
public void execute(Player sender, Player targetPlayer, List<String> args) { if (args.size() == 0) {
if (args.size() == 0) { sendUsageMessage(sender);
sendUsageMessage(sender); return;
return; }
}
String message = String.join(" ", args);
String message = String.join(" ", args);
if (targetPlayer == null) {
if (targetPlayer == null) { for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
for (Player p : Grasscutter.getGameServer().getPlayers().values()) { CommandHandler.sendMessage(p, message);
CommandHandler.sendMessage(p, message); }
} } else {
} else { CommandHandler.sendMessage(targetPlayer, message);
CommandHandler.sendMessage(targetPlayer, message); }
} CommandHandler.sendTranslatedMessage(sender, "commands.sendMessage.success");
CommandHandler.sendTranslatedMessage(sender, "commands.sendMessage.success"); }
} }
}

View File

@ -1,88 +1,92 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.World;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import java.util.List;
import java.util.List;
@Command(
@Command( label = "setConst",
label = "setConst", aliases = {"setconstellation"},
aliases = {"setconstellation"}, usage = {"<constellation level> [all]"},
usage = {"<constellation level> [all]"}, permission = "player.setconstellation",
permission = "player.setconstellation", permissionTargeted = "player.setconstellation.others")
permissionTargeted = "player.setconstellation.others") public final class SetConstCommand implements CommandHandler {
public final class SetConstCommand implements CommandHandler { @Override
@Override public void execute(Player sender, Player targetPlayer, List<String> args) {
public void execute(Player sender, Player targetPlayer, List<String> args) { if (args.size() < 1) {
if (args.size() < 1) { sendUsageMessage(sender);
sendUsageMessage(sender); return;
return; }
} try {
try { int constLevel = Integer.parseInt(args.get(0));
int constLevel = Integer.parseInt(args.get(0)); // Check if level is out of range
// Check if level is out of range if (constLevel < -1 || constLevel > 6) {
if (constLevel < -1 || constLevel > 6) { CommandHandler.sendTranslatedMessage(sender, "commands.setConst.range_error");
CommandHandler.sendTranslatedMessage(sender, "commands.setConst.range_error"); return;
return; }
} // If it's either empty or anything else other than "all" just do normal setConstellation
// If it's either empty or anything else other than "all" just do normal setConstellation if (args.size() == 1) {
if (args.size() == 1) { EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); if (entity == null) return;
if (entity == null) return; Avatar avatar = entity.getAvatar();
Avatar avatar = entity.getAvatar(); this.setConstellation(targetPlayer, avatar, constLevel);
this.setConstellation(targetPlayer, avatar, constLevel); CommandHandler.sendTranslatedMessage(
CommandHandler.sendTranslatedMessage(sender, "commands.setConst.success", avatar.getAvatarData().getName(), constLevel); sender, "commands.setConst.success", avatar.getAvatarData().getName(), constLevel);
return; return;
} }
// Check if there's an additional argument which is "all", if it does then go setAllConstellation // Check if there's an additional argument which is "all", if it does then go
if (args.size() > 1 && args.get(1).equalsIgnoreCase("all")) { // setAllConstellation
this.setAllConstellation(targetPlayer, constLevel); if (args.size() > 1 && args.get(1).equalsIgnoreCase("all")) {
CommandHandler.sendTranslatedMessage(sender, "commands.setConst.successall", constLevel); this.setAllConstellation(targetPlayer, constLevel);
} else sendUsageMessage(sender); CommandHandler.sendTranslatedMessage(sender, "commands.setConst.successall", constLevel);
} catch (NumberFormatException ignored) { } else sendUsageMessage(sender);
CommandHandler.sendTranslatedMessage(sender, "commands.setConst.level_error"); } catch (NumberFormatException ignored) {
} CommandHandler.sendTranslatedMessage(sender, "commands.setConst.level_error");
} }
}
private void setConstellation(Player player, Avatar avatar, int constLevel) {
int currentConstLevel = avatar.getCoreProudSkillLevel(); private void setConstellation(Player player, Avatar avatar, int constLevel) {
avatar.forceConstellationLevel(constLevel); int currentConstLevel = avatar.getCoreProudSkillLevel();
avatar.forceConstellationLevel(constLevel);
// force player to reload scene when necessary
if (constLevel < currentConstLevel) { // force player to reload scene when necessary
this.reloadScene(player); if (constLevel < currentConstLevel) {
} this.reloadScene(player);
}
// ensure that all changes are visible to the player
avatar.recalcConstellations(); // ensure that all changes are visible to the player
avatar.recalcStats(true); avatar.recalcConstellations();
avatar.save(); avatar.recalcStats(true);
} avatar.save();
}
private void setAllConstellation(Player player, int constLevel) {
player.getAvatars().forEach(avatar -> { private void setAllConstellation(Player player, int constLevel) {
avatar.forceConstellationLevel(constLevel); player
avatar.recalcConstellations(); .getAvatars()
avatar.recalcStats(true); .forEach(
avatar.save(); avatar -> {
}); avatar.forceConstellationLevel(constLevel);
// Just reload scene once, shorter than having to check for each constLevel < currentConstLevel avatar.recalcConstellations();
this.reloadScene(player); avatar.recalcStats(true);
} avatar.save();
});
private void reloadScene(Player player) { // Just reload scene once, shorter than having to check for each constLevel < currentConstLevel
World world = player.getWorld(); this.reloadScene(player);
Scene scene = player.getScene(); }
Position pos = player.getPosition();
world.transferPlayerToScene(player, 1, pos); private void reloadScene(Player player) {
world.transferPlayerToScene(player, scene.getId(), pos); World world = player.getWorld();
scene.broadcastPacket(new PacketSceneEntityAppearNotify(player)); Scene scene = player.getScene();
} Position pos = player.getPosition();
} world.transferPlayerToScene(player, 1, pos);
world.transferPlayerToScene(player, scene.getId(), pos);
scene.broadcastPacket(new PacketSceneEntityAppearNotify(player));
}
}

View File

@ -1,50 +1,50 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData; import emu.grasscutter.command.Command;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.data.GameData;
import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.Player;
import java.util.List; import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command( label = "setFetterLevel",
label = "setFetterLevel", usage = {"<level>"},
usage = {"<level>"}, aliases = {"setfetterlvl", "setfriendship"},
aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel",
permission = "player.setfetterlevel", permissionTargeted = "player.setfetterlevel.others")
permissionTargeted = "player.setfetterlevel.others") public final class SetFetterLevelCommand implements CommandHandler {
public final class SetFetterLevelCommand implements CommandHandler {
@Override
@Override public void execute(Player sender, Player targetPlayer, List<String> args) {
public void execute(Player sender, Player targetPlayer, List<String> args) { if (args.size() != 1) {
if (args.size() != 1) { sendUsageMessage(sender);
sendUsageMessage(sender); return;
return; }
}
try {
try { int fetterLevel = Integer.parseInt(args.get(0));
int fetterLevel = Integer.parseInt(args.get(0)); if (fetterLevel < 0 || fetterLevel > 10) {
if (fetterLevel < 0 || fetterLevel > 10) { CommandHandler.sendMessage(
CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.range_error")); sender, translate(sender, "commands.setFetterLevel.range_error"));
return; return;
} }
Avatar avatar = targetPlayer.getTeamManager().getCurrentAvatarEntity().getAvatar(); Avatar avatar = targetPlayer.getTeamManager().getCurrentAvatarEntity().getAvatar();
avatar.setFetterLevel(fetterLevel); avatar.setFetterLevel(fetterLevel);
if (fetterLevel != 10) { if (fetterLevel != 10) {
avatar.setFetterExp(GameData.getAvatarFetterLevelDataMap().get(fetterLevel).getExp()); avatar.setFetterExp(GameData.getAvatarFetterLevelDataMap().get(fetterLevel).getExp());
} }
avatar.save(); avatar.save();
targetPlayer.sendPacket(new PacketAvatarFetterDataNotify(avatar)); targetPlayer.sendPacket(new PacketAvatarFetterDataNotify(avatar));
CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.success", fetterLevel)); CommandHandler.sendMessage(
} catch (NumberFormatException ignored) { sender, translate(sender, "commands.setFetterLevel.success", fetterLevel));
CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.level_error")); } catch (NumberFormatException ignored) {
} CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.level_error"));
} }
}
} }

View File

@ -1,250 +1,279 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.tower.TowerLevelRecord; import emu.grasscutter.game.tower.TowerLevelRecord;
import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify; import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify; import emu.grasscutter.server.packet.send.PacketSceneAreaUnlockNotify;
import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify; import emu.grasscutter.server.packet.send.PacketScenePointUnlockNotify;
import java.util.HashMap;
import java.util.HashMap; import java.util.List;
import java.util.List; import java.util.Map;
import java.util.Map;
@Command(
@Command(label = "setProp", aliases = {"prop"}, usage = {"<prop> <value>"}, permission = "player.setprop", permissionTargeted = "player.setprop.others") label = "setProp",
public final class SetPropCommand implements CommandHandler { aliases = {"prop"},
// List of map areas. Unfortunately, there is no readily available source for them in excels or bins. usage = {"<prop> <value>"},
private static final List<Integer> sceneAreas = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 32, 100, 101, 102, 103, 200, 210, 300, 400, 401, 402, 403); permission = "player.setprop",
Map<String, Prop> props; permissionTargeted = "player.setprop.others")
public final class SetPropCommand implements CommandHandler {
public SetPropCommand() { // List of map areas. Unfortunately, there is no readily available source for them in excels or
this.props = new HashMap<>(); // bins.
// Full PlayerProperty enum that won't be advertised but can be used by devs private static final List<Integer> sceneAreas =
for (PlayerProperty prop : PlayerProperty.values()) { List.of(
String name = prop.toString().substring(5); // PROP_EXP -> EXP 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
String key = name.toLowerCase(); // EXP -> exp 28, 29, 32, 100, 101, 102, 103, 200, 210, 300, 400, 401, 402, 403);
this.props.put(key, new Prop(name, prop)); Map<String, Prop> props;
}
// Add special props public SetPropCommand() {
Prop worldlevel = new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL); this.props = new HashMap<>();
this.props.put("worldlevel", worldlevel); // Full PlayerProperty enum that won't be advertised but can be used by devs
this.props.put("wl", worldlevel); for (PlayerProperty prop : PlayerProperty.values()) {
String name = prop.toString().substring(5); // PROP_EXP -> EXP
Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL); String key = name.toLowerCase(); // EXP -> exp
this.props.put("abyss", abyss); this.props.put(key, new Prop(name, prop));
this.props.put("abyssfloor", abyss); }
this.props.put("ut", abyss); // Add special props
this.props.put("tower", abyss); Prop worldlevel =
this.props.put("towerlevel", abyss); new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL);
this.props.put("unlocktower", abyss); this.props.put("worldlevel", worldlevel);
this.props.put("wl", worldlevel);
Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL);
this.props.put("bplevel", bplevel); Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL);
this.props.put("bp", bplevel); this.props.put("abyss", abyss);
this.props.put("battlepass", bplevel); this.props.put("abyssfloor", abyss);
this.props.put("ut", abyss);
Prop godmode = new Prop("GodMode", PseudoProp.GOD_MODE); this.props.put("tower", abyss);
this.props.put("godmode", godmode); this.props.put("towerlevel", abyss);
this.props.put("god", godmode); this.props.put("unlocktower", abyss);
Prop nostamina = new Prop("UnlimitedStamina", PseudoProp.UNLIMITED_STAMINA); Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL);
this.props.put("unlimitedstamina", nostamina); this.props.put("bplevel", bplevel);
this.props.put("us", nostamina); this.props.put("bp", bplevel);
this.props.put("nostamina", nostamina); this.props.put("battlepass", bplevel);
this.props.put("nostam", nostamina);
this.props.put("ns", nostamina); Prop godmode = new Prop("GodMode", PseudoProp.GOD_MODE);
this.props.put("godmode", godmode);
Prop unlimitedenergy = new Prop("UnlimitedEnergy", PseudoProp.UNLIMITED_ENERGY); this.props.put("god", godmode);
this.props.put("unlimitedenergy", unlimitedenergy);
this.props.put("ue", unlimitedenergy); Prop nostamina = new Prop("UnlimitedStamina", PseudoProp.UNLIMITED_STAMINA);
this.props.put("unlimitedstamina", nostamina);
Prop setopenstate = new Prop("SetOpenstate", PseudoProp.SET_OPENSTATE); this.props.put("us", nostamina);
this.props.put("setopenstate", setopenstate); this.props.put("nostamina", nostamina);
this.props.put("so", setopenstate); this.props.put("nostam", nostamina);
this.props.put("ns", nostamina);
Prop unsetopenstate = new Prop("UnsetOpenstate", PseudoProp.UNSET_OPENSTATE);
this.props.put("unsetopenstate", unsetopenstate); Prop unlimitedenergy = new Prop("UnlimitedEnergy", PseudoProp.UNLIMITED_ENERGY);
this.props.put("uo", unsetopenstate); this.props.put("unlimitedenergy", unlimitedenergy);
this.props.put("ue", unlimitedenergy);
Prop unlockmap = new Prop("UnlockMap", PseudoProp.UNLOCK_MAP);
this.props.put("unlockmap", unlockmap); Prop setopenstate = new Prop("SetOpenstate", PseudoProp.SET_OPENSTATE);
this.props.put("um", unlockmap); this.props.put("setopenstate", setopenstate);
} this.props.put("so", setopenstate);
@Override Prop unsetopenstate = new Prop("UnsetOpenstate", PseudoProp.UNSET_OPENSTATE);
public void execute(Player sender, Player targetPlayer, List<String> args) { this.props.put("unsetopenstate", unsetopenstate);
if (args.size() != 2) { this.props.put("uo", unsetopenstate);
sendUsageMessage(sender);
return; Prop unlockmap = new Prop("UnlockMap", PseudoProp.UNLOCK_MAP);
} this.props.put("unlockmap", unlockmap);
String propStr = args.get(0).toLowerCase(); this.props.put("um", unlockmap);
String valueStr = args.get(1).toLowerCase(); }
int value;
@Override
if (!props.containsKey(propStr)) { public void execute(Player sender, Player targetPlayer, List<String> args) {
sendUsageMessage(sender); if (args.size() != 2) {
return; sendUsageMessage(sender);
} return;
try { }
value = switch (valueStr.toLowerCase()) { String propStr = args.get(0).toLowerCase();
case "on", "true" -> 1; String valueStr = args.get(1).toLowerCase();
case "off", "false" -> 0; int value;
case "toggle" -> -1;
default -> Integer.parseInt(valueStr); if (!props.containsKey(propStr)) {
}; sendUsageMessage(sender);
} catch (NumberFormatException ignored) { return;
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); }
return; try {
} value =
switch (valueStr.toLowerCase()) {
boolean success = false; case "on", "true" -> 1;
Prop prop = props.get(propStr); case "off", "false" -> 0;
case "toggle" -> -1;
success = switch (prop.pseudoProp) { default -> Integer.parseInt(valueStr);
case WORLD_LEVEL -> targetPlayer.setWorldLevel(value); };
case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value); } catch (NumberFormatException ignored) {
case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value); CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
case GOD_MODE, UNLIMITED_STAMINA, UNLIMITED_ENERGY -> return;
this.setBool(sender, targetPlayer, prop.pseudoProp, value); }
case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1);
case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0); boolean success = false;
case UNLOCK_MAP -> unlockMap(targetPlayer); Prop prop = props.get(propStr);
default -> targetPlayer.setProperty(prop.prop, value);
}; success =
switch (prop.pseudoProp) {
if (success) { case WORLD_LEVEL -> targetPlayer.setWorldLevel(value);
if (targetPlayer == sender) { case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value);
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", prop.name, valueStr); case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value);
} else { case GOD_MODE, UNLIMITED_STAMINA, UNLIMITED_ENERGY -> this.setBool(
String uidStr = targetPlayer.getAccount().getId(); sender, targetPlayer, prop.pseudoProp, value);
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr); case SET_OPENSTATE -> this.setOpenState(targetPlayer, value, 1);
} case UNSET_OPENSTATE -> this.setOpenState(targetPlayer, value, 0);
} else { case UNLOCK_MAP -> unlockMap(targetPlayer);
if (prop.prop != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages default -> targetPlayer.setProperty(prop.prop, value);
int min = targetPlayer.getPropertyMin(prop.prop); };
int max = targetPlayer.getPropertyMax(prop.prop);
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.value_between", prop.name, min, max); if (success) {
} if (targetPlayer == sender) {
} CommandHandler.sendTranslatedMessage(
} sender, "commands.generic.set_to", prop.name, valueStr);
} else {
private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) { String uidStr = targetPlayer.getAccount().getId();
List<Integer> floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors(); CommandHandler.sendTranslatedMessage(
if (topFloor < 0 || topFloor > floorIds.size()) { sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr);
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.value_between", "Tower Level", 0, floorIds.size()); }
return false; } else {
} if (prop.prop
!= PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages
Map<Integer, TowerLevelRecord> recordMap = targetPlayer.getTowerManager().getRecordMap(); int min = targetPlayer.getPropertyMin(prop.prop);
// Add records for each unlocked floor int max = targetPlayer.getPropertyMax(prop.prop);
for (int floor : floorIds.subList(0, topFloor)) { CommandHandler.sendTranslatedMessage(
if (!recordMap.containsKey(floor)) { sender, "commands.generic.invalid.value_between", prop.name, min, max);
recordMap.put(floor, new TowerLevelRecord(floor)); }
} }
} }
// Remove records for each floor past our target
for (int floor : floorIds.subList(topFloor, floorIds.size())) { private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) {
recordMap.remove(floor); List<Integer> floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors();
} if (topFloor < 0 || topFloor > floorIds.size()) {
// Six stars required on Floor 8 to unlock Floor 9+ CommandHandler.sendTranslatedMessage(
if (topFloor > 8) { sender, "commands.generic.invalid.value_between", "Tower Level", 0, floorIds.size());
recordMap.get(floorIds.get(7)).setLevelStars(0, 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at all return false;
} }
return true;
} Map<Integer, TowerLevelRecord> recordMap = targetPlayer.getTowerManager().getRecordMap();
// Add records for each unlocked floor
private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) { for (int floor : floorIds.subList(0, topFloor)) {
boolean enabled = switch (pseudoProp) { if (!recordMap.containsKey(floor)) {
case GOD_MODE -> targetPlayer.inGodmode(); recordMap.put(floor, new TowerLevelRecord(floor));
case UNLIMITED_STAMINA -> targetPlayer.getUnlimitedStamina(); }
case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().getEnergyUsage(); }
default -> false; // Remove records for each floor past our target
}; for (int floor : floorIds.subList(topFloor, floorIds.size())) {
enabled = switch (value) { recordMap.remove(floor);
case -1 -> !enabled; }
case 0 -> false; // Six stars required on Floor 8 to unlock Floor 9+
default -> true; if (topFloor > 8) {
}; recordMap
.get(floorIds.get(7))
switch (pseudoProp) { .setLevelStars(
case GOD_MODE: 0,
targetPlayer.setGodmode(enabled); 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at
break; // all
case UNLIMITED_STAMINA: }
targetPlayer.setUnlimitedStamina(enabled); return true;
break; }
case UNLIMITED_ENERGY:
targetPlayer.getEnergyManager().setEnergyUsage(!enabled); private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) {
break; boolean enabled =
default: switch (pseudoProp) {
return false; case GOD_MODE -> targetPlayer.inGodmode();
} case UNLIMITED_STAMINA -> targetPlayer.getUnlimitedStamina();
return true; case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().getEnergyUsage();
} default -> false;
};
private boolean setOpenState(Player targetPlayer, int state, int value) { enabled =
targetPlayer.sendPacket(new PacketOpenStateChangeNotify(state, value)); switch (value) {
return true; case -1 -> !enabled;
} case 0 -> false;
default -> true;
private boolean unlockMap(Player targetPlayer) { };
// Unlock.
GameData.getScenePointsPerScene().forEach((sceneId, scenePoints) -> { switch (pseudoProp) {
// Unlock trans points. case GOD_MODE:
targetPlayer.getUnlockedScenePoints(sceneId).addAll(scenePoints); targetPlayer.setGodmode(enabled);
break;
// Unlock map areas. case UNLIMITED_STAMINA:
targetPlayer.getUnlockedSceneAreas(sceneId).addAll(sceneAreas); targetPlayer.setUnlimitedStamina(enabled);
}); break;
case UNLIMITED_ENERGY:
// Send notify. targetPlayer.getEnergyManager().setEnergyUsage(!enabled);
int playerScene = targetPlayer.getSceneId(); break;
targetPlayer.sendPacket(new PacketScenePointUnlockNotify(playerScene, targetPlayer.getUnlockedScenePoints(playerScene))); default:
targetPlayer.sendPacket(new PacketSceneAreaUnlockNotify(playerScene, targetPlayer.getUnlockedSceneAreas(playerScene))); return false;
return true; }
} return true;
}
enum PseudoProp {
NONE, private boolean setOpenState(Player targetPlayer, int state, int value) {
WORLD_LEVEL, targetPlayer.sendPacket(new PacketOpenStateChangeNotify(state, value));
TOWER_LEVEL, return true;
BP_LEVEL, }
GOD_MODE,
UNLIMITED_STAMINA, private boolean unlockMap(Player targetPlayer) {
UNLIMITED_ENERGY, // Unlock.
SET_OPENSTATE, GameData.getScenePointsPerScene()
UNSET_OPENSTATE, .forEach(
UNLOCK_MAP (sceneId, scenePoints) -> {
} // Unlock trans points.
targetPlayer.getUnlockedScenePoints(sceneId).addAll(scenePoints);
static class Prop {
String name; // Unlock map areas.
PlayerProperty prop; targetPlayer.getUnlockedSceneAreas(sceneId).addAll(sceneAreas);
PseudoProp pseudoProp; });
public Prop(PlayerProperty prop) { // Send notify.
this(prop.toString(), prop, PseudoProp.NONE); int playerScene = targetPlayer.getSceneId();
} targetPlayer.sendPacket(
new PacketScenePointUnlockNotify(
public Prop(String name) { playerScene, targetPlayer.getUnlockedScenePoints(playerScene)));
this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE); targetPlayer.sendPacket(
} new PacketSceneAreaUnlockNotify(
playerScene, targetPlayer.getUnlockedSceneAreas(playerScene)));
public Prop(String name, PseudoProp pseudoProp) { return true;
this(name, PlayerProperty.PROP_NONE, pseudoProp); }
}
enum PseudoProp {
public Prop(String name, PlayerProperty prop) { NONE,
this(name, prop, PseudoProp.NONE); WORLD_LEVEL,
} TOWER_LEVEL,
BP_LEVEL,
public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) { GOD_MODE,
this.name = name; UNLIMITED_STAMINA,
this.prop = prop; UNLIMITED_ENERGY,
this.pseudoProp = pseudoProp; SET_OPENSTATE,
} UNSET_OPENSTATE,
} UNLOCK_MAP
} }
static class Prop {
String name;
PlayerProperty prop;
PseudoProp pseudoProp;
public Prop(PlayerProperty prop) {
this(prop.toString(), prop, PseudoProp.NONE);
}
public Prop(String name) {
this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE);
}
public Prop(String name, PseudoProp pseudoProp) {
this(name, PlayerProperty.PROP_NONE, pseudoProp);
}
public Prop(String name, PlayerProperty prop) {
this(name, prop, PseudoProp.NONE);
}
public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) {
this.name = name;
this.prop = prop;
this.pseudoProp = pseudoProp;
}
}
}

View File

@ -1,184 +1,190 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.HashMap;
import java.util.HashMap; import java.util.List;
import java.util.List; import java.util.Map;
import java.util.Map;
@Command(
@Command( label = "setStats",
label = "setStats", aliases = {"stats", "stat"},
aliases = {"stats", "stat"}, usage = {
usage = { "[set] <stat> <value>",
"[set] <stat> <value>", "(lock|freeze) <stat> [<value>]", // Can lock to current value
"(lock|freeze) <stat> [<value>]", // Can lock to current value "(unlock|unfreeze) <stat>"
"(unlock|unfreeze) <stat>"}, },
permission = "player.setstats", permission = "player.setstats",
permissionTargeted = "player.setstats.others") permissionTargeted = "player.setstats.others")
public final class SetStatsCommand implements CommandHandler { public final class SetStatsCommand implements CommandHandler {
private final Map<String, Stat> stats; private final Map<String, Stat> stats;
public SetStatsCommand() { public SetStatsCommand() {
this.stats = new HashMap<>(); this.stats = new HashMap<>();
for (String key : FightProperty.getShortNames()) { for (String key : FightProperty.getShortNames()) {
this.stats.put(key, new Stat(FightProperty.getPropByShortName(key))); this.stats.put(key, new Stat(FightProperty.getPropByShortName(key)));
} }
// Full FightProperty enum that won't be advertised but can be used by devs // Full FightProperty enum that won't be advertised but can be used by devs
// They have a prefix to avoid the "hp" clash // They have a prefix to avoid the "hp" clash
for (FightProperty prop : FightProperty.values()) { for (FightProperty prop : FightProperty.values()) {
String name = prop.toString().substring(10); // FIGHT_PROP_BASE_HP -> _BASE_HP String name = prop.toString().substring(10); // FIGHT_PROP_BASE_HP -> _BASE_HP
String key = name.toLowerCase(); // _BASE_HP -> _base_hp String key = name.toLowerCase(); // _BASE_HP -> _base_hp
name = name.substring(1); // _BASE_HP -> BASE_HP name = name.substring(1); // _BASE_HP -> BASE_HP
this.stats.put(key, new Stat(name, prop)); this.stats.put(key, new Stat(name, prop));
} }
// Compatibility aliases // Compatibility aliases
this.stats.put("mhp", this.stats.get("maxhp")); this.stats.put("mhp", this.stats.get("maxhp"));
this.stats.put("hp", this.stats.get("_cur_hp")); // Overrides FIGHT_PROP_HP this.stats.put("hp", this.stats.get("_cur_hp")); // Overrides FIGHT_PROP_HP
this.stats.put("atk", this.stats.get("_cur_attack")); // Overrides FIGHT_PROP_ATTACK this.stats.put("atk", this.stats.get("_cur_attack")); // Overrides FIGHT_PROP_ATTACK
this.stats.put("def", this.stats.get("_cur_defense")); // Overrides FIGHT_PROP_DEFENSE this.stats.put("def", this.stats.get("_cur_defense")); // Overrides FIGHT_PROP_DEFENSE
this.stats.put("atkb", this.stats.get("_base_attack")); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff. this.stats.put(
this.stats.put("eanemo", this.stats.get("anemo%")); "atkb",
this.stats.put("ecryo", this.stats.get("cryo%")); this.stats.get(
this.stats.put("edendro", this.stats.get("dendro%")); "_base_attack")); // This doesn't seem to get used to recalculate ATK, so it's only
this.stats.put("edend", this.stats.get("dendro%")); // useful for stuff like Bennett's buff.
this.stats.put("eelectro", this.stats.get("electro%")); this.stats.put("eanemo", this.stats.get("anemo%"));
this.stats.put("eelec", this.stats.get("electro%")); this.stats.put("ecryo", this.stats.get("cryo%"));
this.stats.put("ethunder", this.stats.get("electro%")); this.stats.put("edendro", this.stats.get("dendro%"));
this.stats.put("egeo", this.stats.get("geo%")); this.stats.put("edend", this.stats.get("dendro%"));
this.stats.put("ehydro", this.stats.get("hydro%")); this.stats.put("eelectro", this.stats.get("electro%"));
this.stats.put("epyro", this.stats.get("pyro%")); this.stats.put("eelec", this.stats.get("electro%"));
this.stats.put("ephys", this.stats.get("phys%")); this.stats.put("ethunder", this.stats.get("electro%"));
} this.stats.put("egeo", this.stats.get("geo%"));
this.stats.put("ehydro", this.stats.get("hydro%"));
public static float parsePercent(String input) throws NumberFormatException { this.stats.put("epyro", this.stats.get("pyro%"));
if (input.endsWith("%")) { this.stats.put("ephys", this.stats.get("phys%"));
return Float.parseFloat(input.substring(0, input.length() - 1)) / 100f; }
} else {
return Float.parseFloat(input); public static float parsePercent(String input) throws NumberFormatException {
} if (input.endsWith("%")) {
} return Float.parseFloat(input.substring(0, input.length() - 1)) / 100f;
} else {
@Override return Float.parseFloat(input);
public void execute(Player sender, Player targetPlayer, List<String> args) { }
String statStr = null; }
String valueStr;
float value = 0f; @Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 2) { String statStr = null;
sendUsageMessage(sender); String valueStr;
return; float value = 0f;
}
if (args.size() < 2) {
// Get the action and stat sendUsageMessage(sender);
String arg0 = args.remove(0).toLowerCase(); return;
Action action = switch (arg0) { }
default -> {
statStr = arg0; // Get the action and stat
yield Action.ACTION_SET; String arg0 = args.remove(0).toLowerCase();
} // Implicit set command Action action =
case "set" -> Action.ACTION_SET; // Explicit set command switch (arg0) {
case "lock", "freeze" -> Action.ACTION_LOCK; default -> {
case "unlock", "unfreeze" -> Action.ACTION_UNLOCK; statStr = arg0;
}; yield Action.ACTION_SET;
if (statStr == null) { } // Implicit set command
statStr = args.remove(0).toLowerCase(); case "set" -> Action.ACTION_SET; // Explicit set command
} case "lock", "freeze" -> Action.ACTION_LOCK;
if (!stats.containsKey(statStr)) { case "unlock", "unfreeze" -> Action.ACTION_UNLOCK;
sendUsageMessage(sender); // Invalid stat or action };
return; if (statStr == null) {
} statStr = args.remove(0).toLowerCase();
Stat stat = stats.get(statStr); }
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); if (!stats.containsKey(statStr)) {
Avatar avatar = entity.getAvatar(); sendUsageMessage(sender); // Invalid stat or action
return;
// Get the value if the action requires it }
try { Stat stat = stats.get(statStr);
switch (action) { EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
case ACTION_LOCK: Avatar avatar = entity.getAvatar();
if (args.isEmpty()) { // Lock to current value
value = avatar.getFightProperty(stat.prop); // Get the value if the action requires it
break; try {
} // Else fall-through and lock to supplied value switch (action) {
case ACTION_SET: case ACTION_LOCK:
value = parsePercent(args.remove(0)); if (args.isEmpty()) { // Lock to current value
break; value = avatar.getFightProperty(stat.prop);
case ACTION_UNLOCK: break;
break; } // Else fall-through and lock to supplied value
} case ACTION_SET:
} catch (NumberFormatException ignored) { value = parsePercent(args.remove(0));
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.statValue"); break;
return; case ACTION_UNLOCK:
} catch (IndexOutOfBoundsException ignored) { break;
sendUsageMessage(sender); }
return; } catch (NumberFormatException ignored) {
} CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.statValue");
return;
if (!args.isEmpty()) { // Leftover arguments! } catch (IndexOutOfBoundsException ignored) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
switch (action) { if (!args.isEmpty()) { // Leftover arguments!
case ACTION_SET: sendUsageMessage(sender);
entity.setFightProperty(stat.prop, value); return;
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop)); }
break;
case ACTION_LOCK: switch (action) {
avatar.getFightPropOverrides().put(stat.prop.getId(), value); case ACTION_SET:
avatar.recalcStats(); entity.setFightProperty(stat.prop, value);
break; entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop));
case ACTION_UNLOCK: break;
avatar.getFightPropOverrides().remove(stat.prop.getId()); case ACTION_LOCK:
avatar.recalcStats(); avatar.getFightPropOverrides().put(stat.prop.getId(), value);
break; avatar.recalcStats();
} break;
case ACTION_UNLOCK:
// Report action avatar.getFightPropOverrides().remove(stat.prop.getId());
if (FightProperty.isPercentage(stat.prop)) { avatar.recalcStats();
valueStr = String.format("%.1f%%", value * 100f); break;
} else { }
valueStr = String.format("%.0f", value);
} // Report action
if (targetPlayer == sender) { if (FightProperty.isPercentage(stat.prop)) {
CommandHandler.sendTranslatedMessage(sender, action.messageKeySelf, stat.name, valueStr); valueStr = String.format("%.1f%%", value * 100f);
} else { } else {
String uidStr = targetPlayer.getAccount().getId(); valueStr = String.format("%.0f", value);
CommandHandler.sendTranslatedMessage(sender, action.messageKeyOther, stat.name, uidStr, valueStr); }
} if (targetPlayer == sender) {
} CommandHandler.sendTranslatedMessage(sender, action.messageKeySelf, stat.name, valueStr);
} else {
private enum Action { String uidStr = targetPlayer.getAccount().getId();
ACTION_SET("commands.generic.set_to", "commands.generic.set_for_to"), CommandHandler.sendTranslatedMessage(
ACTION_LOCK("commands.setStats.locked_to", "commands.setStats.locked_for_to"), sender, action.messageKeyOther, stat.name, uidStr, valueStr);
ACTION_UNLOCK("commands.setStats.unlocked", "commands.setStats.unlocked_for"); }
public final String messageKeySelf; }
public final String messageKeyOther;
private enum Action {
Action(String messageKeySelf, String messageKeyOther) { ACTION_SET("commands.generic.set_to", "commands.generic.set_for_to"),
this.messageKeySelf = messageKeySelf; ACTION_LOCK("commands.setStats.locked_to", "commands.setStats.locked_for_to"),
this.messageKeyOther = messageKeyOther; ACTION_UNLOCK("commands.setStats.unlocked", "commands.setStats.unlocked_for");
} public final String messageKeySelf;
} public final String messageKeyOther;
private static class Stat { Action(String messageKeySelf, String messageKeyOther) {
String name; this.messageKeySelf = messageKeySelf;
FightProperty prop; this.messageKeyOther = messageKeyOther;
}
public Stat(FightProperty prop) { }
this.name = prop.toString();
this.prop = prop; private static class Stat {
} String name;
FightProperty prop;
public Stat(String name, FightProperty prop) {
this.name = name; public Stat(FightProperty prop) {
this.prop = prop; this.name = prop.toString();
} this.prop = prop;
} }
}
public Stat(String name, FightProperty prop) {
this.name = name;
this.prop = prop;
}
}
}

View File

@ -1,217 +1,217 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.command.CommandHelpers.*;
import emu.grasscutter.command.CommandHandler; import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import emu.grasscutter.data.GameData; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.command.Command;
import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.*; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.Player; import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.props.EntityType; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.entity.*;
import emu.grasscutter.utils.Position; import emu.grasscutter.game.player.Player;
import lombok.Setter; import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.FightProperty;
import java.util.List; import emu.grasscutter.game.world.Scene;
import java.util.Map; import emu.grasscutter.utils.Position;
import java.util.function.BiConsumer; import java.util.List;
import java.util.regex.Pattern; import java.util.Map;
import java.util.function.BiConsumer;
import static emu.grasscutter.command.CommandHelpers.*; import java.util.regex.Pattern;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS; import lombok.Setter;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command( label = "spawn",
label = "spawn", aliases = {"drop", "s"},
aliases = {"drop", "s"}, usage = {
usage = { "<itemId> [x<amount>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>",
"<itemId> [x<amount>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>", "<gadgetId> [x<amount>] [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>",
"<gadgetId> [x<amount>] [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>", "<monsterId> [x<amount>] [lv<level>] [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>"
"<monsterId> [x<amount>] [lv<level>] [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>"}, },
permission = "server.spawn", permission = "server.spawn",
permissionTargeted = "server.spawn.others") permissionTargeted = "server.spawn.others")
public final class SpawnCommand implements CommandHandler { public final class SpawnCommand implements CommandHandler {
private static final Map<Pattern, BiConsumer<SpawnParameters, Integer>> intCommandHandlers = Map.ofEntries( private static final Map<Pattern, BiConsumer<SpawnParameters, Integer>> intCommandHandlers =
Map.entry(lvlRegex, SpawnParameters::setLvl), Map.ofEntries(
Map.entry(amountRegex, SpawnParameters::setAmount), Map.entry(lvlRegex, SpawnParameters::setLvl),
Map.entry(stateRegex, SpawnParameters::setState), Map.entry(amountRegex, SpawnParameters::setAmount),
Map.entry(blockRegex, SpawnParameters::setBlockId), Map.entry(stateRegex, SpawnParameters::setState),
Map.entry(groupRegex, SpawnParameters::setGroupId), Map.entry(blockRegex, SpawnParameters::setBlockId),
Map.entry(configRegex, SpawnParameters::setConfigId), Map.entry(groupRegex, SpawnParameters::setGroupId),
Map.entry(maxHPRegex, SpawnParameters::setMaxHP), Map.entry(configRegex, SpawnParameters::setConfigId),
Map.entry(hpRegex, SpawnParameters::setHp), Map.entry(maxHPRegex, SpawnParameters::setMaxHP),
Map.entry(defRegex, SpawnParameters::setDef), Map.entry(hpRegex, SpawnParameters::setHp),
Map.entry(atkRegex, SpawnParameters::setAtk), Map.entry(defRegex, SpawnParameters::setDef),
Map.entry(aiRegex, SpawnParameters::setAi) Map.entry(atkRegex, SpawnParameters::setAtk),
); Map.entry(aiRegex, SpawnParameters::setAi));
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
SpawnParameters param = new SpawnParameters(); SpawnParameters param = new SpawnParameters();
parseIntParameters(args, param, intCommandHandlers); parseIntParameters(args, param, intCommandHandlers);
// At this point, first remaining argument MUST be the id and the rest the pos // At this point, first remaining argument MUST be the id and the rest the pos
if (args.size() < 1) { if (args.size() < 1) {
sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
switch (args.size()) { switch (args.size()) {
case 4: case 4:
try { try {
float x, y, z; float x, y, z;
x = Float.parseFloat(args.get(1)); x = Float.parseFloat(args.get(1));
y = Float.parseFloat(args.get(2)); y = Float.parseFloat(args.get(2));
z = Float.parseFloat(args.get(3)); z = Float.parseFloat(args.get(3));
param.pos = new Position(x, y, z); param.pos = new Position(x, y, z);
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); CommandHandler.sendMessage(
} // Fallthrough sender, translate(sender, "commands.execution.argument_error"));
case 1: } // Fallthrough
try { case 1:
param.id = Integer.parseInt(args.get(0)); try {
} catch (NumberFormatException ignored) { param.id = Integer.parseInt(args.get(0));
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId")); } catch (NumberFormatException ignored) {
} CommandHandler.sendMessage(
break; sender, translate(sender, "commands.generic.invalid.entityId"));
default: }
sendUsageMessage(sender); break;
return; default:
} sendUsageMessage(sender);
return;
MonsterData monsterData = GameData.getMonsterDataMap().get(param.id); }
GadgetData gadgetData = GameData.getGadgetDataMap().get(param.id);
ItemData itemData = GameData.getItemDataMap().get(param.id); MonsterData monsterData = GameData.getMonsterDataMap().get(param.id);
if (monsterData == null && gadgetData == null && itemData == null) { GadgetData gadgetData = GameData.getGadgetDataMap().get(param.id);
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId")); ItemData itemData = GameData.getItemDataMap().get(param.id);
return; if (monsterData == null && gadgetData == null && itemData == null) {
} CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId"));
return;
param.scene = targetPlayer.getScene(); }
if (param.scene.getEntities().size() + param.amount > GAME_OPTIONS.sceneEntityLimit) { param.scene = targetPlayer.getScene();
param.amount = Math.max(Math.min(GAME_OPTIONS.sceneEntityLimit - param.scene.getEntities().size(), param.amount), 0);
CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.limit_reached", param.amount)); if (param.scene.getEntities().size() + param.amount > GAME_OPTIONS.sceneEntityLimit) {
if (param.amount <= 0) { param.amount =
return; Math.max(
} Math.min(
} GAME_OPTIONS.sceneEntityLimit - param.scene.getEntities().size(), param.amount),
0);
double maxRadius = Math.sqrt(param.amount * 0.2 / Math.PI); CommandHandler.sendMessage(
if (param.pos == null) { sender, translate(sender, "commands.spawn.limit_reached", param.amount));
param.pos = targetPlayer.getPosition(); if (param.amount <= 0) {
} return;
}
for (int i = 0; i < param.amount; i++) { }
Position pos = GetRandomPositionInCircle(param.pos, maxRadius).addY(3);
GameEntity entity = null; double maxRadius = Math.sqrt(param.amount * 0.2 / Math.PI);
if (itemData != null) { if (param.pos == null) {
entity = createItem(itemData, param, pos); param.pos = targetPlayer.getPosition();
} }
if (gadgetData != null) {
pos.addY(-3); for (int i = 0; i < param.amount; i++) {
entity = createGadget(gadgetData, param, pos, targetPlayer); Position pos = GetRandomPositionInCircle(param.pos, maxRadius).addY(3);
} GameEntity entity = null;
if (monsterData != null) { if (itemData != null) {
entity = createMonster(monsterData, param, pos); entity = createItem(itemData, param, pos);
} }
applyCommonParameters(entity, param); if (gadgetData != null) {
pos.addY(-3);
param.scene.addEntity(entity); entity = createGadget(gadgetData, param, pos, targetPlayer);
} }
CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.success", param.amount, param.id)); if (monsterData != null) {
} entity = createMonster(monsterData, param, pos);
}
private EntityItem createItem(ItemData itemData, SpawnParameters param, Position pos) { applyCommonParameters(entity, param);
return new EntityItem(param.scene, null, itemData, pos, 1, true);
} param.scene.addEntity(entity);
}
private EntityMonster createMonster(MonsterData monsterData, SpawnParameters param, Position pos) { CommandHandler.sendMessage(
var entity = new EntityMonster(param.scene, monsterData, pos, param.lvl); sender, translate(sender, "commands.spawn.success", param.amount, param.id));
if (param.ai != -1) { }
entity.setAiId(param.ai);
} private EntityItem createItem(ItemData itemData, SpawnParameters param, Position pos) {
return entity; return new EntityItem(param.scene, null, itemData, pos, 1, true);
} }
private EntityBaseGadget createGadget(GadgetData gadgetData, SpawnParameters param, Position pos, Player targetPlayer) { private EntityMonster createMonster(
EntityBaseGadget entity; MonsterData monsterData, SpawnParameters param, Position pos) {
if (gadgetData.getType() == EntityType.Vehicle) { var entity = new EntityMonster(param.scene, monsterData, pos, param.lvl);
entity = new EntityVehicle(param.scene, targetPlayer, param.id, 0, pos, targetPlayer.getRotation()); if (param.ai != -1) {
} else { entity.setAiId(param.ai);
entity = new EntityGadget(param.scene, param.id, pos, targetPlayer.getRotation()); }
if (param.state != -1) { return entity;
((EntityGadget) entity).setState(param.state); }
}
} private EntityBaseGadget createGadget(
GadgetData gadgetData, SpawnParameters param, Position pos, Player targetPlayer) {
return entity; EntityBaseGadget entity;
} if (gadgetData.getType() == EntityType.Vehicle) {
entity =
private void applyCommonParameters(GameEntity entity, SpawnParameters param) { new EntityVehicle(
if (param.blockId != -1) { param.scene, targetPlayer, param.id, 0, pos, targetPlayer.getRotation());
entity.setBlockId(param.blockId); } else {
} entity = new EntityGadget(param.scene, param.id, pos, targetPlayer.getRotation());
if (param.groupId != -1) { if (param.state != -1) {
entity.setGroupId(param.groupId); ((EntityGadget) entity).setState(param.state);
} }
if (param.configId != -1) { }
entity.setConfigId(param.configId);
} return entity;
if (param.maxHP != -1) { }
entity.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, param.maxHP);
entity.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, param.maxHP); private void applyCommonParameters(GameEntity entity, SpawnParameters param) {
} if (param.blockId != -1) {
if (param.hp != -1) { entity.setBlockId(param.blockId);
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, param.hp == 0 ? Float.MAX_VALUE : param.hp); }
} if (param.groupId != -1) {
if (param.atk != -1) { entity.setGroupId(param.groupId);
entity.setFightProperty(FightProperty.FIGHT_PROP_ATTACK, param.atk); }
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, param.atk); if (param.configId != -1) {
} entity.setConfigId(param.configId);
if (param.def != -1) { }
entity.setFightProperty(FightProperty.FIGHT_PROP_DEFENSE, param.def); if (param.maxHP != -1) {
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, param.def); entity.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, param.maxHP);
} entity.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, param.maxHP);
} }
if (param.hp != -1) {
private Position GetRandomPositionInCircle(Position origin, double radius) { entity.setFightProperty(
Position target = origin.clone(); FightProperty.FIGHT_PROP_CUR_HP, param.hp == 0 ? Float.MAX_VALUE : param.hp);
double angle = Math.random() * 360; }
double r = Math.sqrt(Math.random() * radius * radius); if (param.atk != -1) {
target.addX((float) (r * Math.cos(angle))).addZ((float) (r * Math.sin(angle))); entity.setFightProperty(FightProperty.FIGHT_PROP_ATTACK, param.atk);
return target; entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, param.atk);
} }
if (param.def != -1) {
private static class SpawnParameters { entity.setFightProperty(FightProperty.FIGHT_PROP_DEFENSE, param.def);
@Setter entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, param.def);
public int id; }
@Setter }
public int lvl = 1;
@Setter private Position GetRandomPositionInCircle(Position origin, double radius) {
public int amount = 1; Position target = origin.clone();
@Setter double angle = Math.random() * 360;
public int blockId = -1; double r = Math.sqrt(Math.random() * radius * radius);
@Setter target.addX((float) (r * Math.cos(angle))).addZ((float) (r * Math.sin(angle)));
public int groupId = -1; return target;
@Setter }
public int configId = -1;
@Setter private static class SpawnParameters {
public int state = -1; @Setter public int id;
@Setter @Setter public int lvl = 1;
public int hp = -1; @Setter public int amount = 1;
@Setter @Setter public int blockId = -1;
public int maxHP = -1; @Setter public int groupId = -1;
@Setter @Setter public int configId = -1;
public int atk = -1; @Setter public int state = -1;
@Setter @Setter public int hp = -1;
public int def = -1; @Setter public int maxHP = -1;
@Setter @Setter public int atk = -1;
public int ai = -1; @Setter public int def = -1;
@Setter @Setter public int ai = -1;
public Position pos = null; @Setter public Position pos = null;
public Scene scene = null; public Scene scene = null;
} }
} }

View File

@ -1,24 +1,27 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import java.util.List; import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "stop", aliases = {"shutdown"}, permission = "server.stop", targetRequirement = Command.TargetRequirement.NONE) label = "stop",
public final class StopCommand implements CommandHandler { aliases = {"shutdown"},
permission = "server.stop",
@Override targetRequirement = Command.TargetRequirement.NONE)
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class StopCommand implements CommandHandler {
CommandHandler.sendMessage(null, translate("commands.stop.success"));
for (Player p : Grasscutter.getGameServer().getPlayers().values()) { @Override
CommandHandler.sendMessage(p, translate(p, "commands.stop.success")); public void execute(Player sender, Player targetPlayer, List<String> args) {
} CommandHandler.sendMessage(null, translate("commands.stop.success"));
for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
System.exit(1000); CommandHandler.sendMessage(p, translate(p, "commands.stop.success"));
} }
}
System.exit(1000);
}
}

View File

@ -1,120 +1,129 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarSkillDepotData; import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Language;
import java.util.List;
import java.util.List;
@Command(
@Command( label = "talent",
label = "talent", usage = {"set <talentId> <level>", "(n|e|q|all) <level>", "getid"},
usage = {"set <talentId> <level>", "(n|e|q|all) <level>", "getid"}, permission = "player.settalent",
permission = "player.settalent", permissionTargeted = "player.settalent.others")
permissionTargeted = "player.settalent.others") public final class TalentCommand implements CommandHandler {
public final class TalentCommand implements CommandHandler { private void setTalentLevel(Player sender, Avatar avatar, int skillId, int newLevel) {
private void setTalentLevel(Player sender, Avatar avatar, int skillId, int newLevel) { if (avatar.setSkillLevel(skillId, newLevel)) {
if (avatar.setSkillLevel(skillId, newLevel)) { long nameHash = GameData.getAvatarSkillDataMap().get(skillId).getNameTextMapHash();
long nameHash = GameData.getAvatarSkillDataMap().get(skillId).getNameTextMapHash(); var name = Language.getTextMapKey(nameHash);
var name = Language.getTextMapKey(nameHash); CommandHandler.sendTranslatedMessage(
CommandHandler.sendTranslatedMessage(sender, "commands.talent.set_id", skillId, name, newLevel); sender, "commands.talent.set_id", skillId, name, newLevel);
} else { } else {
CommandHandler.sendTranslatedMessage(sender, "commands.talent.out_of_range"); CommandHandler.sendTranslatedMessage(sender, "commands.talent.out_of_range");
} }
} }
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) { if (args.size() < 1) {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
Avatar avatar = targetPlayer.getTeamManager().getCurrentAvatarEntity().getAvatar(); Avatar avatar = targetPlayer.getTeamManager().getCurrentAvatarEntity().getAvatar();
AvatarSkillDepotData skillDepot = avatar.getSkillDepot(); AvatarSkillDepotData skillDepot = avatar.getSkillDepot();
if (skillDepot == null) { // Avatars without skill depots aren't a suitable target even with manual skillId specified if (skillDepot
CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_skill_id"); == null) { // Avatars without skill depots aren't a suitable target even with manual skillId
return; // specified
} CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_skill_id");
int skillId = 0; return;
int newLevel = -1; }
int skillId = 0;
String cmdSwitch = args.get(0).toLowerCase(); int newLevel = -1;
switch (cmdSwitch) {
default -> { String cmdSwitch = args.get(0).toLowerCase();
sendUsageMessage(sender); switch (cmdSwitch) {
} default -> {
case "set" -> { sendUsageMessage(sender);
if (args.size() < 3) { }
sendUsageMessage(sender); case "set" -> {
return; if (args.size() < 3) {
} sendUsageMessage(sender);
try { return;
skillId = Integer.parseInt(args.get(1)); }
} catch (NumberFormatException ignored) { try {
CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_skill_id"); skillId = Integer.parseInt(args.get(1));
return; } catch (NumberFormatException ignored) {
} CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_skill_id");
try { return;
newLevel = Integer.parseInt(args.get(2)); }
} catch (NumberFormatException ignored) { try {
CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_level"); newLevel = Integer.parseInt(args.get(2));
return; } catch (NumberFormatException ignored) {
} CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_level");
return;
setTalentLevel(sender, avatar, skillId, newLevel); }
}
case "n", "e", "q" -> { setTalentLevel(sender, avatar, skillId, newLevel);
if (args.size() < 2) { }
sendUsageMessage(sender); case "n", "e", "q" -> {
return; if (args.size() < 2) {
} sendUsageMessage(sender);
try { return;
newLevel = Integer.parseInt(args.get(1)); }
} catch (NumberFormatException ignored) { try {
CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_level"); newLevel = Integer.parseInt(args.get(1));
return; } catch (NumberFormatException ignored) {
} CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_level");
return;
skillId = switch (cmdSwitch) { }
default -> skillDepot.getSkills().get(0);
case "e" -> skillDepot.getSkills().get(1); skillId =
case "q" -> skillDepot.getEnergySkill(); switch (cmdSwitch) {
}; default -> skillDepot.getSkills().get(0);
setTalentLevel(sender, avatar, skillId, newLevel); case "e" -> skillDepot.getSkills().get(1);
} case "q" -> skillDepot.getEnergySkill();
case "all" -> { };
if (args.size() < 2) { setTalentLevel(sender, avatar, skillId, newLevel);
sendUsageMessage(sender); }
return; case "all" -> {
} if (args.size() < 2) {
try { sendUsageMessage(sender);
newLevel = Integer.parseInt(args.get(1)); return;
} catch (NumberFormatException ignored) { }
CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_level"); try {
return; newLevel = Integer.parseInt(args.get(1));
} } catch (NumberFormatException ignored) {
// This stops setTalentLevel from outputting 3 "levels out of range" messages CommandHandler.sendTranslatedMessage(sender, "commands.talent.invalid_level");
if (newLevel < 1 || newLevel > 15) { return;
CommandHandler.sendTranslatedMessage(sender, "commands.talent.out_of_range"); }
return; // This stops setTalentLevel from outputting 3 "levels out of range" messages
} if (newLevel < 1 || newLevel > 15) {
int finalNewLevel = newLevel; CommandHandler.sendTranslatedMessage(sender, "commands.talent.out_of_range");
skillDepot.getSkillsAndEnergySkill().forEach(id -> setTalentLevel(sender, avatar, id, finalNewLevel)); return;
} }
case "getid" -> { int finalNewLevel = newLevel;
var map = GameData.getAvatarSkillDataMap(); skillDepot
skillDepot.getSkillsAndEnergySkill().forEach(id -> { .getSkillsAndEnergySkill()
var talent = map.get(id); .forEach(id -> setTalentLevel(sender, avatar, id, finalNewLevel));
if (talent == null) return; }
var talentName = Language.getTextMapKey(talent.getNameTextMapHash()); case "getid" -> {
var talentDesc = Language.getTextMapKey(talent.getDescTextMapHash()); var map = GameData.getAvatarSkillDataMap();
CommandHandler.sendTranslatedMessage(sender, "commands.talent.id_desc", id, talentName, talentDesc); skillDepot
}); .getSkillsAndEnergySkill()
} .forEach(
} id -> {
} var talent = map.get(id);
} if (talent == null) return;
var talentName = Language.getTextMapKey(talent.getNameTextMapHash());
var talentDesc = Language.getTextMapKey(talent.getDescTextMapHash());
CommandHandler.sendTranslatedMessage(
sender, "commands.talent.id_desc", id, talentName, talentDesc);
});
}
}
}
}

View File

@ -1,258 +1,268 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.ArrayList; import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp;
import java.util.HashSet; import java.util.ArrayList;
import java.util.List; import java.util.HashSet;
import java.util.List;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
@Command(
@Command( label = "team",
label = "team", usage = {"add <avatarId,...>", "(remove|set) [index|first|last|index-index,...]"},
usage = {"add <avatarId,...>", "(remove|set) [index|first|last|index-index,...]"}, permission = "player.team",
permission = "player.team", permissionTargeted = "player.team.others")
permissionTargeted = "player.team.others") public final class TeamCommand implements CommandHandler {
public final class TeamCommand implements CommandHandler { private static final int BASE_AVATARID = 10000000;
private static final int BASE_AVATARID = 10000000;
@Override
@Override public void execute(Player sender, Player targetPlayer, List<String> args) {
public void execute(Player sender, Player targetPlayer, List<String> args) { if (args.isEmpty()) {
if (args.isEmpty()) { sendUsageMessage(sender);
sendUsageMessage(sender); return;
return; }
}
switch (args.get(0)) {
switch (args.get(0)) { case "add":
case "add": if (!addCommand(sender, targetPlayer, args)) return;
if (!addCommand(sender, targetPlayer, args)) return; break;
break;
case "remove":
case "remove": if (!removeCommand(sender, targetPlayer, args)) return;
if (!removeCommand(sender, targetPlayer, args)) return; break;
break;
case "set":
case "set": if (!setCommand(sender, targetPlayer, args)) return;
if (!setCommand(sender, targetPlayer, args)) return; break;
break;
default:
default: CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage"); sendUsageMessage(sender);
sendUsageMessage(sender); return;
return; }
}
targetPlayer
targetPlayer.getTeamManager().updateTeamEntities( .getTeamManager()
new PacketChangeMpTeamAvatarRsp(targetPlayer, targetPlayer.getTeamManager().getCurrentTeamInfo())); .updateTeamEntities(
} new PacketChangeMpTeamAvatarRsp(
targetPlayer, targetPlayer.getTeamManager().getCurrentTeamInfo()));
private boolean addCommand(Player sender, Player targetPlayer, List<String> args) { }
if (args.size() < 2) {
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage"); private boolean addCommand(Player sender, Player targetPlayer, List<String> args) {
sendUsageMessage(sender); if (args.size() < 2) {
return false; CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
} sendUsageMessage(sender);
return false;
int index = -1; }
if (args.size() > 2) {
try { int index = -1;
index = Integer.parseInt(args.get(2)) - 1; if (args.size() > 2) {
if (index < 0) index = 0; try {
} catch (Exception e) { index = Integer.parseInt(args.get(2)) - 1;
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_index"); if (index < 0) index = 0;
return false; } catch (Exception e) {
} CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_index");
} return false;
}
var avatarIds = args.get(1).split(","); }
var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars();
var avatarIds = args.get(1).split(",");
if (currentTeamAvatars.size() + avatarIds.length > GAME_OPTIONS.avatarLimits.singlePlayerTeam) { var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars();
CommandHandler.sendTranslatedMessage(sender, "commands.team.add_too_much", GAME_OPTIONS.avatarLimits.singlePlayerTeam);
return false; if (currentTeamAvatars.size() + avatarIds.length > GAME_OPTIONS.avatarLimits.singlePlayerTeam) {
} CommandHandler.sendTranslatedMessage(
sender, "commands.team.add_too_much", GAME_OPTIONS.avatarLimits.singlePlayerTeam);
for (var avatarId : avatarIds) { return false;
int id = Integer.parseInt(avatarId); }
if (!addAvatar(sender, targetPlayer, id, index))
CommandHandler.sendTranslatedMessage(sender, "commands.team.failed_to_add_avatar", avatarId); for (var avatarId : avatarIds) {
if (index > 0) ++index; int id = Integer.parseInt(avatarId);
} if (!addAvatar(sender, targetPlayer, id, index))
return true; CommandHandler.sendTranslatedMessage(
} sender, "commands.team.failed_to_add_avatar", avatarId);
if (index > 0) ++index;
private boolean removeCommand(Player sender, Player targetPlayer, List<String> args) { }
if (args.size() < 2) { return true;
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage"); }
sendUsageMessage(sender);
return false; private boolean removeCommand(Player sender, Player targetPlayer, List<String> args) {
} if (args.size() < 2) {
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars(); sendUsageMessage(sender);
var avatarCount = currentTeamAvatars.size(); return false;
}
var metaIndexList = args.get(1).split(",");
var indexes = new HashSet<Integer>(); var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars();
var ignoreList = new ArrayList<Integer>(); var avatarCount = currentTeamAvatars.size();
for (var metaIndex : metaIndexList) {
// step 1: parse metaIndex to indexes var metaIndexList = args.get(1).split(",");
var subIndexes = transformToIndexes(metaIndex, avatarCount); var indexes = new HashSet<Integer>();
if (subIndexes == null) { var ignoreList = new ArrayList<Integer>();
CommandHandler.sendTranslatedMessage(sender, "commands.team.failed_to_parse_index", metaIndex); for (var metaIndex : metaIndexList) {
continue; // step 1: parse metaIndex to indexes
} var subIndexes = transformToIndexes(metaIndex, avatarCount);
if (subIndexes == null) {
// step 2: get all of the avatar id through indexes CommandHandler.sendTranslatedMessage(
for (var avatarIndex : subIndexes) { sender, "commands.team.failed_to_parse_index", metaIndex);
try { continue;
indexes.add(currentTeamAvatars.get(avatarIndex - 1)); }
} catch (Exception e) {
ignoreList.add(avatarIndex); // step 2: get all of the avatar id through indexes
continue; for (var avatarIndex : subIndexes) {
} try {
} indexes.add(currentTeamAvatars.get(avatarIndex - 1));
} } catch (Exception e) {
ignoreList.add(avatarIndex);
// step 3: check if user remove all of the avatar continue;
if (indexes.size() >= avatarCount) { }
CommandHandler.sendTranslatedMessage(sender, "commands.team.remove_too_much"); }
return false; }
}
// step 3: check if user remove all of the avatar
// step 4: hint user for ignore index if (indexes.size() >= avatarCount) {
if (!ignoreList.isEmpty()) { CommandHandler.sendTranslatedMessage(sender, "commands.team.remove_too_much");
CommandHandler.sendTranslatedMessage(sender, "commands.team.ignore_index", ignoreList); return false;
} }
// step 5: remove // step 4: hint user for ignore index
currentTeamAvatars.removeAll(indexes); if (!ignoreList.isEmpty()) {
return true; CommandHandler.sendTranslatedMessage(sender, "commands.team.ignore_index", ignoreList);
} }
private boolean setCommand(Player sender, Player targetPlayer, List<String> args) { // step 5: remove
if (args.size() < 3) { currentTeamAvatars.removeAll(indexes);
CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage"); return true;
sendUsageMessage(sender); }
return false;
} private boolean setCommand(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 3) {
var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars(); CommandHandler.sendTranslatedMessage(sender, "commands.team.invalid_usage");
sendUsageMessage(sender);
int index; return false;
try { }
index = Integer.parseInt(args.get(1)) - 1;
if (index < 0) index = 0; var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars();
} catch (Exception e) {
CommandHandler.sendTranslatedMessage(sender, "commands.team.failed_to_parse_index", args.get(1)); int index;
return false; try {
} index = Integer.parseInt(args.get(1)) - 1;
if (index < 0) index = 0;
if (index + 1 > currentTeamAvatars.size()) { } catch (Exception e) {
CommandHandler.sendTranslatedMessage(sender, "commands.team.index_out_of_range"); CommandHandler.sendTranslatedMessage(
return false; sender, "commands.team.failed_to_parse_index", args.get(1));
} return false;
}
int avatarId;
try { if (index + 1 > currentTeamAvatars.size()) {
avatarId = Integer.parseInt(args.get(2)); CommandHandler.sendTranslatedMessage(sender, "commands.team.index_out_of_range");
} catch (Exception e) { return false;
CommandHandler.sendTranslatedMessage(sender, "commands.team.failed_parse_avatar_id", args.get(2)); }
return false;
} int avatarId;
if (avatarId < BASE_AVATARID) { try {
avatarId += BASE_AVATARID; avatarId = Integer.parseInt(args.get(2));
} } catch (Exception e) {
CommandHandler.sendTranslatedMessage(
if (currentTeamAvatars.contains(avatarId)) { sender, "commands.team.failed_parse_avatar_id", args.get(2));
CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_already_in_team", avatarId); return false;
return false; }
} if (avatarId < BASE_AVATARID) {
avatarId += BASE_AVATARID;
if (!targetPlayer.getAvatars().hasAvatar(avatarId)) { }
CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_not_found", avatarId);
return false; if (currentTeamAvatars.contains(avatarId)) {
} CommandHandler.sendTranslatedMessage(
sender, "commands.team.avatar_already_in_team", avatarId);
currentTeamAvatars.set(index, avatarId); return false;
return true; }
}
if (!targetPlayer.getAvatars().hasAvatar(avatarId)) {
private boolean addAvatar(Player sender, Player targetPlayer, int avatarId, int index) { CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_not_found", avatarId);
if (avatarId < BASE_AVATARID) { return false;
avatarId += BASE_AVATARID; }
}
var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars(); currentTeamAvatars.set(index, avatarId);
if (currentTeamAvatars.contains(avatarId)) { return true;
CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_already_in_team", avatarId); }
return false;
} private boolean addAvatar(Player sender, Player targetPlayer, int avatarId, int index) {
if (!targetPlayer.getAvatars().hasAvatar(avatarId)) { if (avatarId < BASE_AVATARID) {
CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_not_found", avatarId); avatarId += BASE_AVATARID;
return false; }
} var currentTeamAvatars = targetPlayer.getTeamManager().getCurrentTeamInfo().getAvatars();
if (index < 0) { if (currentTeamAvatars.contains(avatarId)) {
currentTeamAvatars.add(avatarId); CommandHandler.sendTranslatedMessage(
} else { sender, "commands.team.avatar_already_in_team", avatarId);
currentTeamAvatars.add(index, avatarId); return false;
} }
return true; if (!targetPlayer.getAvatars().hasAvatar(avatarId)) {
} CommandHandler.sendTranslatedMessage(sender, "commands.team.avatar_not_found", avatarId);
return false;
private List<Integer> transformToIndexes(String metaIndexes, int listLength) { }
// step 1: check if metaIndexes is a special constants if (index < 0) {
if (metaIndexes.equals("first")) { currentTeamAvatars.add(avatarId);
return List.of(1); } else {
} else if (metaIndexes.equals("last")) { currentTeamAvatars.add(index, avatarId);
return List.of(listLength); }
} return true;
}
// step 2: check if metaIndexes is a range
if (metaIndexes.contains("-")) { private List<Integer> transformToIndexes(String metaIndexes, int listLength) {
var range = metaIndexes.split("-"); // step 1: check if metaIndexes is a special constants
if (range.length < 2) { if (metaIndexes.equals("first")) {
return null; return List.of(1);
} } else if (metaIndexes.equals("last")) {
return List.of(listLength);
int min, max; }
try {
min = switch (range[0]) { // step 2: check if metaIndexes is a range
case "first" -> 1; if (metaIndexes.contains("-")) {
case "last" -> listLength; var range = metaIndexes.split("-");
default -> Integer.parseInt(range[0]); if (range.length < 2) {
}; return null;
}
max = switch (range[1]) {
case "first" -> 1; int min, max;
case "last" -> listLength; try {
default -> Integer.parseInt(range[1]); min =
}; switch (range[0]) {
} catch (Exception e) { case "first" -> 1;
return null; case "last" -> listLength;
} default -> Integer.parseInt(range[0]);
};
if (min > max) {
min ^= max; max =
max ^= min; switch (range[1]) {
min ^= max; case "first" -> 1;
} case "last" -> listLength;
default -> Integer.parseInt(range[1]);
var indexes = new ArrayList<Integer>(); };
for (int i = min; i <= max; ++i) { } catch (Exception e) {
indexes.add(i); return null;
} }
return indexes;
} if (min > max) {
min ^= max;
// step 3: index is a value, simply return max ^= min;
try { min ^= max;
int index = Integer.parseInt(metaIndexes); }
return List.of(index);
} catch (Exception e) { var indexes = new ArrayList<Integer>();
return null; for (int i = min; i <= max; ++i) {
} indexes.add(i);
} }
return indexes;
} }
// step 3: index is a value, simply return
try {
int index = Integer.parseInt(metaIndexes);
return List.of(index);
} catch (Exception e) {
return null;
}
}
}

View File

@ -1,31 +1,36 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List; import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "teleportAll", aliases = {"tpall"}, permission = "player.tpall", permissionTargeted = "player.tpall.others") label = "teleportAll",
public final class TeleportAllCommand implements CommandHandler { aliases = {"tpall"},
permission = "player.tpall",
@Override permissionTargeted = "player.tpall.others")
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class TeleportAllCommand implements CommandHandler {
if (!targetPlayer.getWorld().isMultiplayer()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.teleportAll.error")); @Override
return; public void execute(Player sender, Player targetPlayer, List<String> args) {
} if (!targetPlayer.getWorld().isMultiplayer()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.teleportAll.error"));
for (Player player : targetPlayer.getWorld().getPlayers()) { return;
if (player.equals(targetPlayer)) }
continue;
for (Player player : targetPlayer.getWorld().getPlayers()) {
player.getWorld().transferPlayerToScene(player, targetPlayer.getSceneId(), TeleportType.COMMAND, targetPlayer.getPosition()); if (player.equals(targetPlayer)) continue;
}
player
CommandHandler.sendMessage(sender, translate(sender, "commands.teleportAll.success")); .getWorld()
} .transferPlayerToScene(
} player, targetPlayer.getSceneId(), TeleportType.COMMAND, targetPlayer.getPosition());
}
CommandHandler.sendMessage(sender, translate(sender, "commands.teleportAll.success"));
}
}

View File

@ -1,68 +1,78 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.Command;
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.utils.Position; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
import java.util.List; import emu.grasscutter.utils.Position;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "teleport", aliases = {"tp"}, usage = {"<x> <y> <z> [sceneId]"}, permission = "player.teleport", permissionTargeted = "player.teleport.others") label = "teleport",
public final class TeleportCommand implements CommandHandler { aliases = {"tp"},
usage = {"<x> <y> <z> [sceneId]"},
private float parseRelative(String input, Float current) { // TODO: Maybe this will be useful elsewhere later permission = "player.teleport",
if (input.contains("~")) { // Relative permissionTargeted = "player.teleport.others")
if (!input.equals("~")) { // Relative with offset public final class TeleportCommand implements CommandHandler {
current += Float.parseFloat(input.replace("~", ""));
} // Else no offset, no modification private float parseRelative(
} else { // Absolute String input, Float current) { // TODO: Maybe this will be useful elsewhere later
current = Float.parseFloat(input); if (input.contains("~")) { // Relative
} if (!input.equals("~")) { // Relative with offset
return current; current += Float.parseFloat(input.replace("~", ""));
} } // Else no offset, no modification
} else { // Absolute
@Override current = Float.parseFloat(input);
public void execute(Player sender, Player targetPlayer, List<String> args) { }
Position pos = targetPlayer.getPosition(); return current;
float x = pos.getX(); }
float y = pos.getY();
float z = pos.getZ(); @Override
int sceneId = targetPlayer.getSceneId(); public void execute(Player sender, Player targetPlayer, List<String> args) {
Position pos = targetPlayer.getPosition();
switch (args.size()) { float x = pos.getX();
case 4: float y = pos.getY();
try { float z = pos.getZ();
sceneId = Integer.parseInt(args.get(3)); int sceneId = targetPlayer.getSceneId();
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); switch (args.size()) {
} // Fallthrough case 4:
case 3: try {
try { sceneId = Integer.parseInt(args.get(3));
x = this.parseRelative(args.get(0), x); } catch (NumberFormatException ignored) {
y = this.parseRelative(args.get(1), y); CommandHandler.sendMessage(
z = this.parseRelative(args.get(2), z); sender, translate(sender, "commands.execution.argument_error"));
} catch (NumberFormatException ignored) { } // Fallthrough
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.invalid_position")); case 3:
} try {
break; x = this.parseRelative(args.get(0), x);
default: y = this.parseRelative(args.get(1), y);
this.sendUsageMessage(sender); z = this.parseRelative(args.get(2), z);
return; } catch (NumberFormatException ignored) {
} CommandHandler.sendMessage(
sender, translate(sender, "commands.teleport.invalid_position"));
Position target_pos = new Position(x, y, z); }
boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, TeleportType.COMMAND, target_pos); break;
default:
if (!result) { this.sendUsageMessage(sender);
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.exists_error")); return;
} else { }
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.success",
targetPlayer.getNickname(), x, y, z, sceneId) Position target_pos = new Position(x, y, z);
); boolean result =
} targetPlayer
.getWorld()
} .transferPlayerToScene(targetPlayer, sceneId, TeleportType.COMMAND, target_pos);
}
if (!result) {
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.exists_error"));
} else {
CommandHandler.sendMessage(
sender,
translate(
sender, "commands.teleport.success", targetPlayer.getNickname(), x, y, z, sceneId));
}
}
}

View File

@ -1,41 +1,39 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.List;
@Command(
@Command( label = "unban",
label = "unban", permission = "server.ban",
permission = "server.ban", targetRequirement = Command.TargetRequirement.PLAYER)
targetRequirement = Command.TargetRequirement.PLAYER public final class UnBanCommand implements CommandHandler {
)
public final class UnBanCommand implements CommandHandler { private boolean unBanAccount(Player targetPlayer) {
Account account = targetPlayer.getAccount();
private boolean unBanAccount(Player targetPlayer) {
Account account = targetPlayer.getAccount(); if (account == null) {
return false;
if (account == null) { }
return false;
} account.setBanReason(null);
account.setBanEndTime(0);
account.setBanReason(null); account.setBanStartTime(0);
account.setBanEndTime(0); account.setBanned(false);
account.setBanStartTime(0); account.save();
account.setBanned(false);
account.save(); return true;
}
return true;
} @Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
@Override if (unBanAccount(targetPlayer)) {
public void execute(Player sender, Player targetPlayer, List<String> args) { CommandHandler.sendTranslatedMessage(sender, "commands.unban.success");
if (unBanAccount(targetPlayer)) { } else {
CommandHandler.sendTranslatedMessage(sender, "commands.unban.success"); CommandHandler.sendTranslatedMessage(sender, "commands.unban.failure");
} else { }
CommandHandler.sendTranslatedMessage(sender, "commands.unban.failure"); }
} }
}
}

View File

@ -1,39 +1,43 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData; import emu.grasscutter.command.Command;
import emu.grasscutter.game.player.Player; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.PlayerProgressManager; import emu.grasscutter.data.GameData;
import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.player.PlayerProgressManager;
import java.util.HashMap; import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify;
import java.util.List; import java.util.HashMap;
import java.util.Map; import java.util.List;
import java.util.Map;
import static emu.grasscutter.utils.Language.translate;
@Command(
@Command(label = "unlockall", usage = {""}, permission = "player.unlockall", permissionTargeted = "player.unlockall.others") label = "unlockall",
public final class UnlockAllCommand implements CommandHandler { usage = {""},
permission = "player.unlockall",
@Override permissionTargeted = "player.unlockall.others")
public void execute(Player sender, Player targetPlayer, List<String> args) { public final class UnlockAllCommand implements CommandHandler {
Map<Integer, Integer> changed = new HashMap<>();
@Override
for (var state : GameData.getOpenStateList()) { public void execute(Player sender, Player targetPlayer, List<String> args) {
// Don't unlock blacklisted open states. Map<Integer, Integer> changed = new HashMap<>();
if (PlayerProgressManager.BLACKLIST_OPEN_STATES.contains(state.getId())) {
continue; for (var state : GameData.getOpenStateList()) {
} // Don't unlock blacklisted open states.
if (PlayerProgressManager.BLACKLIST_OPEN_STATES.contains(state.getId())) {
if (targetPlayer.getProgressManager().getOpenState(state.getId()) == 0) { continue;
targetPlayer.getOpenStates().put(state.getId(), 1); }
changed.put(state.getId(), 1);
} if (targetPlayer.getProgressManager().getOpenState(state.getId()) == 0) {
} targetPlayer.getOpenStates().put(state.getId(), 1);
changed.put(state.getId(), 1);
targetPlayer.sendPacket(new PacketOpenStateChangeNotify(changed)); }
}
CommandHandler.sendMessage(sender, translate(sender, "commands.unlockall.success", targetPlayer.getNickname()));
} targetPlayer.sendPacket(new PacketOpenStateChangeNotify(changed));
}
CommandHandler.sendMessage(
sender, translate(sender, "commands.unlockall.success", targetPlayer.getNickname()));
}
}

View File

@ -1,43 +1,52 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ClimateType; import emu.grasscutter.game.props.ClimateType;
import java.util.List;
import java.util.List;
@Command(
@Command(label = "weather", aliases = {"w"}, usage = {"weather [<weatherId>] [<climateType>]"}, permission = "player.weather", permissionTargeted = "player.weather.others") label = "weather",
public final class WeatherCommand implements CommandHandler { aliases = {"w"},
usage = {"weather [<weatherId>] [<climateType>]"},
@Override permission = "player.weather",
public void execute(Player sender, Player targetPlayer, List<String> args) { permissionTargeted = "player.weather.others")
int weatherId = targetPlayer.getWeatherId(); public final class WeatherCommand implements CommandHandler {
ClimateType climate = ClimateType.CLIMATE_NONE; // Sending ClimateType.CLIMATE_NONE to Scene.setWeather will use the default climate for that weather
@Override
if (args.isEmpty()) { public void execute(Player sender, Player targetPlayer, List<String> args) {
climate = targetPlayer.getClimate(); int weatherId = targetPlayer.getWeatherId();
CommandHandler.sendTranslatedMessage(sender, "commands.weather.status", weatherId, climate.getShortName()); ClimateType climate =
return; ClimateType
} .CLIMATE_NONE; // Sending ClimateType.CLIMATE_NONE to Scene.setWeather will use the
// default climate for that weather
for (String arg : args) {
ClimateType c = ClimateType.getTypeByShortName(arg.toLowerCase()); if (args.isEmpty()) {
if (c != ClimateType.CLIMATE_NONE) { climate = targetPlayer.getClimate();
climate = c; CommandHandler.sendTranslatedMessage(
} else { sender, "commands.weather.status", weatherId, climate.getShortName());
try { return;
weatherId = Integer.parseInt(arg); }
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.id"); for (String arg : args) {
sendUsageMessage(sender); ClimateType c = ClimateType.getTypeByShortName(arg.toLowerCase());
return; if (c != ClimateType.CLIMATE_NONE) {
} climate = c;
} } else {
} try {
weatherId = Integer.parseInt(arg);
targetPlayer.setWeather(weatherId, climate); } catch (NumberFormatException ignored) {
climate = targetPlayer.getClimate(); // Might be different to what we set CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.id");
CommandHandler.sendTranslatedMessage(sender, "commands.weather.success", weatherId, climate.getShortName()); sendUsageMessage(sender);
} return;
} }
}
}
targetPlayer.setWeather(weatherId, climate);
climate = targetPlayer.getClimate(); // Might be different to what we set
CommandHandler.sendTranslatedMessage(
sender, "commands.weather.success", weatherId, climate.getShortName());
}
}

View File

@ -1,120 +1,118 @@
package emu.grasscutter.config; package emu.grasscutter.config;
import emu.grasscutter.utils.FileUtils; import static emu.grasscutter.Grasscutter.config;
import java.nio.file.Path; import emu.grasscutter.utils.FileUtils;
import java.util.Locale; import java.nio.file.Path;
import java.util.Locale;
import static emu.grasscutter.Grasscutter.config;
/**
* A data container for the server's configuration.
/** *
* A data container for the server's configuration. * <p>Use `import static emu.grasscutter.Configuration.*;` to import all configuration constants.
* <p> */
* Use `import static emu.grasscutter.Configuration.*;` public final class Configuration extends ConfigContainer {
* to import all configuration constants.
*/ /*
public final class Configuration extends ConfigContainer { * Constants
*/
/*
* Constants // 'c' is short for 'config' and makes code look 'cleaner'.
*/ public static final ConfigContainer c = config;
// 'c' is short for 'config' and makes code look 'cleaner'. public static final Locale LANGUAGE = config.language.language;
public static final ConfigContainer c = config; public static final Locale FALLBACK_LANGUAGE = config.language.fallback;
public static final String DOCUMENT_LANGUAGE = config.language.document;
public static final Locale LANGUAGE = config.language.language; public static final Server SERVER = config.server;
public static final Locale FALLBACK_LANGUAGE = config.language.fallback; public static final Database DATABASE = config.databaseInfo;
public static final String DOCUMENT_LANGUAGE = config.language.document; public static final Account ACCOUNT = config.account;
public static final Server SERVER = config.server; public static final HTTP HTTP_INFO = config.server.http;
public static final Database DATABASE = config.databaseInfo; public static final Game GAME_INFO = config.server.game;
public static final Account ACCOUNT = config.account; public static final Dispatch DISPATCH_INFO = config.server.dispatch;
public static final HTTP HTTP_INFO = config.server.http; public static final DebugMode DEBUG_MODE_INFO = config.server.debugMode;
public static final Game GAME_INFO = config.server.game; public static final Encryption HTTP_ENCRYPTION = config.server.http.encryption;
public static final Dispatch DISPATCH_INFO = config.server.dispatch; public static final Policies HTTP_POLICIES = config.server.http.policies;
public static final DebugMode DEBUG_MODE_INFO = config.server.debugMode; public static final Files HTTP_STATIC_FILES = config.server.http.files;
public static final Encryption HTTP_ENCRYPTION = config.server.http.encryption; public static final GameOptions GAME_OPTIONS = config.server.game.gameOptions;
public static final Policies HTTP_POLICIES = config.server.http.policies; public static final GameOptions.InventoryLimits INVENTORY_LIMITS =
public static final Files HTTP_STATIC_FILES = config.server.http.files; config.server.game.gameOptions.inventoryLimits;
public static final GameOptions GAME_OPTIONS = config.server.game.gameOptions; private static final String DATA_FOLDER = config.folderStructure.data;
public static final GameOptions.InventoryLimits INVENTORY_LIMITS = config.server.game.gameOptions.inventoryLimits; private static final String PLUGINS_FOLDER = config.folderStructure.plugins;
private static final String DATA_FOLDER = config.folderStructure.data; private static final String SCRIPTS_FOLDER = config.folderStructure.scripts;
private static final String PLUGINS_FOLDER = config.folderStructure.plugins; private static final String PACKETS_FOLDER = config.folderStructure.packets;
private static final String SCRIPTS_FOLDER = config.folderStructure.scripts;
private static final String PACKETS_FOLDER = config.folderStructure.packets; /*
* Utilities
/* */
* Utilities @Deprecated(forRemoval = true)
*/ public static String DATA() {
@Deprecated(forRemoval = true) return DATA_FOLDER;
public static String DATA() { }
return DATA_FOLDER;
} @Deprecated(forRemoval = true)
public static String DATA(String path) {
@Deprecated(forRemoval = true) return Path.of(DATA_FOLDER, path).toString();
public static String DATA(String path) { }
return Path.of(DATA_FOLDER, path).toString();
} @Deprecated(forRemoval = true)
public static Path getResourcePath(String path) {
@Deprecated(forRemoval = true) return FileUtils.getResourcePath(path);
public static Path getResourcePath(String path) { }
return FileUtils.getResourcePath(path);
} @Deprecated(forRemoval = true)
public static String RESOURCE(String path) {
@Deprecated(forRemoval = true) return FileUtils.getResourcePath(path).toString();
public static String RESOURCE(String path) { }
return FileUtils.getResourcePath(path).toString();
} @Deprecated(forRemoval = true)
public static String PLUGIN() {
@Deprecated(forRemoval = true) return PLUGINS_FOLDER;
public static String PLUGIN() { }
return PLUGINS_FOLDER;
} public static String PLUGIN(String path) {
return Path.of(PLUGINS_FOLDER, path).toString();
public static String PLUGIN(String path) { }
return Path.of(PLUGINS_FOLDER, path).toString();
} @Deprecated(forRemoval = true)
public static String SCRIPT(String path) {
@Deprecated(forRemoval = true) return Path.of(SCRIPTS_FOLDER, path).toString();
public static String SCRIPT(String path) { }
return Path.of(SCRIPTS_FOLDER, path).toString();
} @Deprecated(forRemoval = true)
public static String PACKET(String path) {
@Deprecated(forRemoval = true) return Path.of(PACKETS_FOLDER, path).toString();
public static String PACKET(String path) { }
return Path.of(PACKETS_FOLDER, path).toString();
} /**
* Fallback method.
/** *
* Fallback method. * @param left Attempt to use.
* * @param right Use if left is undefined.
* @param left Attempt to use. * @return Left or right.
* @param right Use if left is undefined. */
* @return Left or right. public static <T> T lr(T left, T right) {
*/ return left == null ? right : left;
public static <T> T lr(T left, T right) { }
return left == null ? right : left;
} /**
* {@link Configuration#lr(Object, Object)} for {@link String}s.
/** *
* {@link Configuration#lr(Object, Object)} for {@link String}s. * @param left Attempt to use.
* * @param right Use if left is empty.
* @param left Attempt to use. * @return Left or right.
* @param right Use if left is empty. */
* @return Left or right. public static String lr(String left, String right) {
*/ return left.isEmpty() ? right : left;
public static String lr(String left, String right) { }
return left.isEmpty() ? right : left;
} /**
* {@link Configuration#lr(Object, Object)} for {@link Integer}s.
/** *
* {@link Configuration#lr(Object, Object)} for {@link Integer}s. * @param left Attempt to use.
* * @param right Use if left is 0.
* @param left Attempt to use. * @return Left or right.
* @param right Use if left is 0. */
* @return Left or right. public static int lr(int left, int right) {
*/ return left == 0 ? right : left;
public static int lr(int left, int right) { }
return left == 0 ? right : left; }
}
}

View File

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

View File

@ -1,346 +1,475 @@
package emu.grasscutter.data; package emu.grasscutter.data;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.*; import emu.grasscutter.data.binout.*;
import emu.grasscutter.data.excels.*; import emu.grasscutter.data.excels.*;
import emu.grasscutter.game.quest.QuestEncryptionKey; import emu.grasscutter.game.quest.QuestEncryptionKey;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.ints.*;
import lombok.Getter; import java.lang.reflect.Field;
import lombok.experimental.Tolerate; import java.util.*;
import lombok.Getter;
import java.lang.reflect.Field; import lombok.experimental.Tolerate;
import java.util.*;
public class GameData {
public class GameData { protected static final Map<String, AbilityData> abilityDataMap = new HashMap<>();
protected static final Map<String, AbilityData> abilityDataMap = new HashMap<>(); protected static final Int2ObjectMap<ScenePointEntry> scenePointEntryMap =
protected static final Int2ObjectMap<ScenePointEntry> scenePointEntryMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
// BinOutputs // BinOutputs
@Getter @Getter
private static final Int2ObjectMap<HomeworldDefaultSaveData> homeworldDefaultSaveData = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<HomeworldDefaultSaveData> homeworldDefaultSaveData =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<String> abilityHashes = new Int2ObjectOpenHashMap<>();
@Deprecated(forRemoval = true) @Getter private static final Int2ObjectMap<String> abilityHashes = new Int2ObjectOpenHashMap<>();
@Getter
private static final Map<String, AbilityModifierEntry> abilityModifiers = new HashMap<>(); @Deprecated(forRemoval = true)
@Getter @Getter
private static final Map<String, ConfigGadget> gadgetConfigData = new HashMap<>(); private static final Map<String, AbilityModifierEntry> abilityModifiers = new HashMap<>();
@Getter
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>(); @Getter private static final Map<String, ConfigGadget> gadgetConfigData = new HashMap<>();
@Deprecated(forRemoval = true) @Getter private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
@Getter
private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>(); @Deprecated(forRemoval = true)
private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>(); @Getter
private static final Int2ObjectMap<QuestEncryptionKey> questsKeys = new Int2ObjectOpenHashMap<>(); private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>();
private static final Int2ObjectMap<SceneNpcBornData> npcBornData = new Int2ObjectOpenHashMap<>();
private static final Map<String, AbilityEmbryoEntry> abilityEmbryos = new HashMap<>(); private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<QuestEncryptionKey> questsKeys = new Int2ObjectOpenHashMap<>();
// ExcelConfigs private static final Int2ObjectMap<SceneNpcBornData> npcBornData = new Int2ObjectOpenHashMap<>();
@Getter private static final Map<String, AbilityEmbryoEntry> abilityEmbryos = new HashMap<>();
private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>();
private static final Int2ObjectMap<AchievementData> achievementDataMap = new Int2ObjectOpenHashMap<>(); // ExcelConfigs
@Getter @Getter
private static final Int2ObjectMap<AchievementGoalData> achievementGoalDataMap = new Int2ObjectOpenHashMap<>(); private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>();
@Getter
private static final Int2ObjectMap<ActivityData> activityDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AchievementData> achievementDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ActivityShopData> activityShopDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<ActivityWatcherData> activityWatcherDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AchievementGoalData> achievementGoalDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<ActivityData> activityDataMap = new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<AvatarCurveData> avatarCurveDataMap = new Int2ObjectLinkedOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<ActivityShopData> activityShopDataMap =
private static final Int2ObjectMap<AvatarData> avatarDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<AvatarFetterLevelData> avatarFetterLevelDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<ActivityWatcherData> activityWatcherDataMap =
private static final Int2ObjectMap<AvatarFlycloakData> avatarFlycloakDataMap = new Int2ObjectLinkedOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<AvatarLevelData> avatarLevelDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap =
private static final Int2ObjectMap<AvatarSkillData> avatarSkillDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectLinkedOpenHashMap<>();
@Getter
private static final Int2ObjectMap<AvatarSkillDepotData> avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataMap =
private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectLinkedOpenHashMap<>();
@Getter
private static final Int2ObjectMap<BattlePassMissionData> battlePassMissionDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<AvatarCurveData> avatarCurveDataMap =
private static final Int2ObjectMap<BattlePassRewardData> battlePassRewardDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectLinkedOpenHashMap<>();
@Getter
private static final Int2ObjectMap<BlossomRefreshExcelConfigData> blossomRefreshExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<AvatarData> avatarDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BuffData> buffDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<ChapterData> chapterDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarFetterLevelData> avatarFetterLevelDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CityData> cityDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<CodexAnimalData> codexAnimalDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarFlycloakData> avatarFlycloakDataMap =
@Getter new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<CodexMaterialData> codexMaterialDataIdMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<CodexQuestData> codexQuestDataIdMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarLevelData> avatarLevelDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexReliquaryData> codexReliquaryDataIdMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<CodexWeaponData> codexWeaponDataIdMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarSkillData> avatarSkillDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CombineData> combineDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<CookBonusData> cookBonusDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarSkillDepotData> avatarSkillDepotDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CookRecipeData> cookRecipeDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<CompoundData> compoundDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<BattlePassMissionData> battlePassMissionDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DungeonEntryData> dungeonEntryDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<EnvAnimalGatherConfigData> envAnimalGatherConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<BattlePassRewardData> battlePassRewardDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<EquipAffixData> equipAffixDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<BlossomRefreshExcelConfigData>
@Getter blossomRefreshExcelConfigDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ForgeData> forgeDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter private static final Int2ObjectMap<BuffData> buffDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<FurnitureMakeConfigData> furnitureMakeConfigDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<GadgetData> gadgetDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ChapterData> chapterDataMap = new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<GatherData> gatherDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap<CityData> cityDataMap = new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<HomeWorldBgmData> homeWorldBgmDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<CodexAnimalData> codexAnimalDataMap =
private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<InvestigationMonsterData> investigationMonsterDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<CodexMaterialData> codexMaterialDataIdMap =
private static final Int2ObjectMap<ItemData> itemDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<MonsterCurveData> monsterCurveDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<CodexQuestData> codexQuestDataIdMap =
private static final Int2ObjectMap<MonsterData> monsterDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<MonsterDescribeData> monsterDescribeDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<CodexReliquaryData> codexReliquaryDataIdMap =
private static final Int2ObjectMap<MusicGameBasicData> musicGameBasicDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<NpcData> npcDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<CodexWeaponData> codexWeaponDataIdMap =
private static final Int2ObjectMap<OpenStateData> openStateDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<PersonalLineData> personalLineDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<CombineData> combineDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<ProudSkillData> proudSkillDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<CookBonusData> cookBonusDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<QuestData> questDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<ReliquaryAffixData> reliquaryAffixDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<CookRecipeData> cookRecipeDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryMainPropData> reliquaryMainPropDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<ReliquarySetData> reliquarySetDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<CompoundData> compoundDataMap = new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap =
private static final Int2ObjectMap<RewardPreviewData> rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
@Getter
private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); @Getter
@Getter private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<DungeonEntryData> dungeonEntryDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<TowerScheduleData> towerScheduleDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<TriggerExcelConfigData> triggerExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<EnvAnimalGatherConfigData> envAnimalGatherConfigDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeaponCurveData> weaponCurveDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<WeaponLevelData> weaponLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<EquipAffixData> equipAffixDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeaponPromoteData> weaponPromoteDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<WeatherData> weatherDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldAreaData> worldAreaDataMap = new Int2ObjectOpenHashMap<>();
@Getter @Getter
private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ForgeData> forgeDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarPromoteData> avatarPromoteDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>(); @Getter
private static final Int2ObjectMap<ReliquaryLevelData> reliquaryLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FurnitureMakeConfigData> furnitureMakeConfigDataMap =
private static final Int2ObjectMap<ShopGoodsData> shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
// The following are accessed via getMapByResourceDef, and will show as unused
private static final Int2ObjectMap<CodexMaterialData> codexMaterialDataMap = new Int2ObjectOpenHashMap<>(); @Getter
private static final Int2ObjectMap<CodexQuestData> codexQuestDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<GadgetData> gadgetDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexReliquaryData> codexReliquaryDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexWeaponData> codexWeaponDataMap = new Int2ObjectOpenHashMap<>(); @Getter
private static final Int2ObjectMap<GatherData> gatherDataMap = new Int2ObjectOpenHashMap<>();
// Cache
@Getter @Getter
private static final IntList scenePointIdList = new IntArrayList(); private static final Int2ObjectMap<HomeWorldBgmData> homeWorldBgmDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final List<OpenStateData> openStateList = new ArrayList<>();
@Getter @Getter
private static final Map<Integer, List<Integer>> scenePointsPerScene = new HashMap<>(); private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap =
@Getter new Int2ObjectOpenHashMap<>();
private static final Map<String, ScriptSceneData> scriptSceneDataMap = new HashMap<>();
protected static Int2ObjectMap<IntSet> proudSkillGroupLevels = new Int2ObjectOpenHashMap<>(); @Getter
protected static Int2IntMap proudSkillGroupMaxLevels = new Int2IntOpenHashMap(); private static final Int2ObjectMap<InvestigationMonsterData> investigationMonsterDataMap =
protected static Int2ObjectMap<IntSet> avatarSkillLevels = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
private static final Map<Integer, List<Integer>> fetters = new HashMap<>();
private static final Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>(); @Getter private static final Int2ObjectMap<ItemData> itemDataMap = new Int2ObjectOpenHashMap<>();
// Getters with wrong names, remove later @Getter
@Deprecated(forRemoval = true) private static final Int2ObjectMap<MonsterCurveData> monsterCurveDataMap =
public static Int2ObjectMap<CodexReliquaryData> getcodexReliquaryIdMap() { new Int2ObjectOpenHashMap<>();
return codexReliquaryDataIdMap;
} @Getter
private static final Int2ObjectMap<MonsterData> monsterDataMap = new Int2ObjectOpenHashMap<>();
@Deprecated(forRemoval = true)
public static Int2ObjectMap<DungeonEntryData> getDungeonEntryDatatMap() { @Getter
return dungeonEntryDataMap; private static final Int2ObjectMap<MonsterDescribeData> monsterDescribeDataMap =
} new Int2ObjectOpenHashMap<>();
@Deprecated(forRemoval = true) @Getter
@Tolerate private static final Int2ObjectMap<MusicGameBasicData> musicGameBasicDataMap =
public static ArrayList<CodexReliquaryData> getcodexReliquaryArrayList() { new Int2ObjectOpenHashMap<>();
return codexReliquaryArrayList;
} @Getter private static final Int2ObjectMap<NpcData> npcDataMap = new Int2ObjectOpenHashMap<>();
// Getters with different names that stay for now @Getter
public static Int2ObjectMap<MainQuestData> getMainQuestDataMap() { private static final Int2ObjectMap<OpenStateData> openStateDataMap =
return mainQuestData; new Int2ObjectOpenHashMap<>();
}
@Getter
public static Int2ObjectMap<QuestEncryptionKey> getMainQuestEncryptionMap() { private static final Int2ObjectMap<PersonalLineData> personalLineDataMap =
return questsKeys; new Int2ObjectOpenHashMap<>();
}
@Getter
public static Int2ObjectMap<SceneNpcBornData> getSceneNpcBornData() { private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap =
return npcBornData; new Int2ObjectOpenHashMap<>();
}
@Getter
public static Map<String, AbilityEmbryoEntry> getAbilityEmbryoInfo() { private static final Int2ObjectMap<ProudSkillData> proudSkillDataMap =
return abilityEmbryos; new Int2ObjectOpenHashMap<>();
}
@Getter
// Getters that get values rather than containers. If Lombok ever gets syntactic sugar for this, we should adopt that. private static final Int2ObjectMap<QuestData> questDataMap = new Int2ObjectOpenHashMap<>();
public static AbilityData getAbilityData(String abilityName) {
return abilityDataMap.get(abilityName); @Getter
} private static final Int2ObjectMap<ReliquaryAffixData> reliquaryAffixDataMap =
new Int2ObjectOpenHashMap<>();
public static IntSet getAvatarSkillLevels(int avatarSkillId) {
return avatarSkillLevels.get(avatarSkillId); @Getter
} private static final Int2ObjectMap<ReliquaryMainPropData> reliquaryMainPropDataMap =
new Int2ObjectOpenHashMap<>();
public static IntSet getProudSkillGroupLevels(int proudSkillGroupId) {
return proudSkillGroupLevels.get(proudSkillGroupId); @Getter
} private static final Int2ObjectMap<ReliquarySetData> reliquarySetDataMap =
new Int2ObjectOpenHashMap<>();
public static int getProudSkillGroupMaxLevel(int proudSkillGroupId) {
return proudSkillGroupMaxLevels.getOrDefault(proudSkillGroupId, 0); @Getter
} private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
// Multi-keyed getters @Getter
public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) { private static final Int2ObjectMap<RewardPreviewData> rewardPreviewDataMap =
return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel); new Int2ObjectOpenHashMap<>();
}
@Getter
public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) { private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>();
return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel);
} @Getter
private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap =
public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) { new Int2ObjectOpenHashMap<>();
return reliquaryLevelDataMap.get((rankLevel << 8) + level);
} @Getter
private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap =
public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) { new Int2ObjectOpenHashMap<>();
return scenePointEntryMap.get((sceneId << 16) + pointId);
} @Getter
private static final Int2ObjectMap<TowerScheduleData> towerScheduleDataMap =
// Non-nullable value getters new Int2ObjectOpenHashMap<>();
public static int getAvatarLevelExpRequired(int level) {
return Optional.ofNullable(avatarLevelDataMap.get(level)).map(d -> d.getExp()).orElse(0); @Getter
} private static final Int2ObjectMap<TriggerExcelConfigData> triggerExcelConfigDataMap =
new Int2ObjectOpenHashMap<>();
public static int getAvatarFetterLevelExpRequired(int level) {
return Optional.ofNullable(avatarFetterLevelDataMap.get(level)).map(d -> d.getExp()).orElse(0); @Getter
} private static final Int2ObjectMap<WeaponCurveData> weaponCurveDataMap =
new Int2ObjectOpenHashMap<>();
public static int getRelicExpRequired(int rankLevel, int level) {
return Optional.ofNullable(getRelicLevelData(rankLevel, level)).map(d -> d.getExp()).orElse(0); @Getter
} private static final Int2ObjectMap<WeaponLevelData> weaponLevelDataMap =
new Int2ObjectOpenHashMap<>();
// Generic getter @Getter
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) { private static final Int2ObjectMap<WeaponPromoteData> weaponPromoteDataMap =
Int2ObjectMap<?> map = null; new Int2ObjectOpenHashMap<>();
try { @Getter
Field field = GameData.class.getDeclaredField(Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map"); private static final Int2ObjectMap<WeatherData> weatherDataMap = new Int2ObjectOpenHashMap<>();
field.setAccessible(true); @Getter
map = (Int2ObjectMap<?>) field.get(null); private static final Int2ObjectMap<WorldAreaData> worldAreaDataMap =
field.setAccessible(false); new Int2ObjectOpenHashMap<>();
} catch (Exception e) {
Grasscutter.getLogger().error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e); @Getter
} private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap =
new Int2ObjectOpenHashMap<>();
return map;
} private static final Int2ObjectMap<AvatarPromoteData> avatarPromoteDataMap =
new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>();
public static int getWeaponExpRequired(int rankLevel, int level) { private static final Int2ObjectMap<ReliquaryLevelData> reliquaryLevelDataMap =
WeaponLevelData levelData = weaponLevelDataMap.get(level); new Int2ObjectOpenHashMap<>();
if (levelData == null) { private static final Int2ObjectMap<ShopGoodsData> shopGoodsDataMap =
return 0; new Int2ObjectOpenHashMap<>();
} // The following are accessed via getMapByResourceDef, and will show as unused
try { private static final Int2ObjectMap<CodexMaterialData> codexMaterialDataMap =
return levelData.getRequiredExps()[rankLevel - 1]; new Int2ObjectOpenHashMap<>();
} catch (Exception e) { private static final Int2ObjectMap<CodexQuestData> codexQuestDataMap =
return 0; new Int2ObjectOpenHashMap<>();
} private static final Int2ObjectMap<CodexReliquaryData> codexReliquaryDataMap =
} new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexWeaponData> codexWeaponDataMap =
public static Map<Integer, List<Integer>> getFetterDataEntries() { new Int2ObjectOpenHashMap<>();
if (fetters.isEmpty()) {
fetterDataMap.forEach((k, v) -> { // Cache
if (!fetters.containsKey(v.getAvatarId())) { @Getter private static final IntList scenePointIdList = new IntArrayList();
fetters.put(v.getAvatarId(), new ArrayList<>()); @Getter private static final List<OpenStateData> openStateList = new ArrayList<>();
} @Getter private static final Map<Integer, List<Integer>> scenePointsPerScene = new HashMap<>();
fetters.get(v.getAvatarId()).add(k); @Getter private static final Map<String, ScriptSceneData> scriptSceneDataMap = new HashMap<>();
}); protected static Int2ObjectMap<IntSet> proudSkillGroupLevels = new Int2ObjectOpenHashMap<>();
} protected static Int2IntMap proudSkillGroupMaxLevels = new Int2IntOpenHashMap();
protected static Int2ObjectMap<IntSet> avatarSkillLevels = new Int2ObjectOpenHashMap<>();
return fetters; private static final Map<Integer, List<Integer>> fetters = new HashMap<>();
} private static final Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>();
public static Map<Integer, List<ShopGoodsData>> getShopGoodsDataEntries() { // Getters with wrong names, remove later
if (shopGoods.isEmpty()) { @Deprecated(forRemoval = true)
shopGoodsDataMap.forEach((k, v) -> { public static Int2ObjectMap<CodexReliquaryData> getcodexReliquaryIdMap() {
if (!shopGoods.containsKey(v.getShopType())) return codexReliquaryDataIdMap;
shopGoods.put(v.getShopType(), new ArrayList<>()); }
shopGoods.get(v.getShopType()).add(v);
}); @Deprecated(forRemoval = true)
} public static Int2ObjectMap<DungeonEntryData> getDungeonEntryDatatMap() {
return dungeonEntryDataMap;
return shopGoods; }
}
@Deprecated(forRemoval = true)
public static Int2ObjectMap<AchievementData> getAchievementDataMap() { @Tolerate
AchievementData.divideIntoGroups(); public static ArrayList<CodexReliquaryData> getcodexReliquaryArrayList() {
return achievementDataMap; return codexReliquaryArrayList;
} }
}
// Getters with different names that stay for now
public static Int2ObjectMap<MainQuestData> getMainQuestDataMap() {
return mainQuestData;
}
public static Int2ObjectMap<QuestEncryptionKey> getMainQuestEncryptionMap() {
return questsKeys;
}
public static Int2ObjectMap<SceneNpcBornData> getSceneNpcBornData() {
return npcBornData;
}
public static Map<String, AbilityEmbryoEntry> getAbilityEmbryoInfo() {
return abilityEmbryos;
}
// Getters that get values rather than containers. If Lombok ever gets syntactic sugar for this,
// we should adopt that.
public static AbilityData getAbilityData(String abilityName) {
return abilityDataMap.get(abilityName);
}
public static IntSet getAvatarSkillLevels(int avatarSkillId) {
return avatarSkillLevels.get(avatarSkillId);
}
public static IntSet getProudSkillGroupLevels(int proudSkillGroupId) {
return proudSkillGroupLevels.get(proudSkillGroupId);
}
public static int getProudSkillGroupMaxLevel(int proudSkillGroupId) {
return proudSkillGroupMaxLevels.getOrDefault(proudSkillGroupId, 0);
}
// Multi-keyed getters
public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) {
return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel);
}
public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) {
return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel);
}
public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) {
return reliquaryLevelDataMap.get((rankLevel << 8) + level);
}
public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) {
return scenePointEntryMap.get((sceneId << 16) + pointId);
}
// Non-nullable value getters
public static int getAvatarLevelExpRequired(int level) {
return Optional.ofNullable(avatarLevelDataMap.get(level)).map(d -> d.getExp()).orElse(0);
}
public static int getAvatarFetterLevelExpRequired(int level) {
return Optional.ofNullable(avatarFetterLevelDataMap.get(level)).map(d -> d.getExp()).orElse(0);
}
public static int getRelicExpRequired(int rankLevel, int level) {
return Optional.ofNullable(getRelicLevelData(rankLevel, level)).map(d -> d.getExp()).orElse(0);
}
// Generic getter
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
Int2ObjectMap<?> map = null;
try {
Field field =
GameData.class.getDeclaredField(
Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map");
field.setAccessible(true);
map = (Int2ObjectMap<?>) field.get(null);
field.setAccessible(false);
} catch (Exception e) {
Grasscutter.getLogger()
.error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e);
}
return map;
}
public static int getWeaponExpRequired(int rankLevel, int level) {
WeaponLevelData levelData = weaponLevelDataMap.get(level);
if (levelData == null) {
return 0;
}
try {
return levelData.getRequiredExps()[rankLevel - 1];
} catch (Exception e) {
return 0;
}
}
public static Map<Integer, List<Integer>> getFetterDataEntries() {
if (fetters.isEmpty()) {
fetterDataMap.forEach(
(k, v) -> {
if (!fetters.containsKey(v.getAvatarId())) {
fetters.put(v.getAvatarId(), new ArrayList<>());
}
fetters.get(v.getAvatarId()).add(k);
});
}
return fetters;
}
public static Map<Integer, List<ShopGoodsData>> getShopGoodsDataEntries() {
if (shopGoods.isEmpty()) {
shopGoodsDataMap.forEach(
(k, v) -> {
if (!shopGoods.containsKey(v.getShopType()))
shopGoods.put(v.getShopType(), new ArrayList<>());
shopGoods.get(v.getShopType()).add(v);
});
}
return shopGoods;
}
public static Int2ObjectMap<AchievementData> getAchievementDataMap() {
AchievementData.divideIntoGroups();
return achievementDataMap;
}
}

View File

@ -1,78 +1,86 @@
package emu.grasscutter.data; package emu.grasscutter.data;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.ResourceLoader.AvatarConfig; import emu.grasscutter.data.ResourceLoader.AvatarConfig;
import emu.grasscutter.data.excels.ReliquaryAffixData; import emu.grasscutter.data.excels.ReliquaryAffixData;
import emu.grasscutter.data.excels.ReliquaryMainPropData; import emu.grasscutter.data.excels.ReliquaryMainPropData;
import emu.grasscutter.game.managers.blossom.BlossomConfig; import emu.grasscutter.game.managers.blossom.BlossomConfig;
import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.utils.WeightedList; import emu.grasscutter.utils.WeightedList;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter; import java.util.ArrayList;
import lombok.Setter; import java.util.HashMap;
import java.util.List;
import java.util.ArrayList; import java.util.Map;
import java.util.HashMap; import lombok.Getter;
import java.util.List; import lombok.Setter;
import java.util.Map;
public class GameDepot {
public class GameDepot { public static final int[] BLOCK_SIZE = new int[] {50, 500}; // Scales
public static final int[] BLOCK_SIZE = new int[]{50, 500};//Scales
private static final Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicRandomMainPropDepot =
private static final Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicRandomMainPropDepot = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<List<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<List<ReliquaryMainPropData>> relicMainPropDepot =
private static final Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>(); new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot =
@Getter new Int2ObjectOpenHashMap<>();
@Setter
private static Map<String, AvatarConfig> playerAbilities = new HashMap<>(); @Getter @Setter private static Map<String, AvatarConfig> playerAbilities = new HashMap<>();
@Getter
private static final HashMap<SpawnDataEntry.GridBlockId, ArrayList<SpawnDataEntry>> spawnLists = new HashMap<>(); @Getter
@Getter private static final HashMap<SpawnDataEntry.GridBlockId, ArrayList<SpawnDataEntry>> spawnLists =
@Setter new HashMap<>();
private static BlossomConfig blossomConfig;
@Getter @Setter private static BlossomConfig blossomConfig;
public static void load() {
for (ReliquaryMainPropData data : GameData.getReliquaryMainPropDataMap().values()) { public static void load() {
if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) { for (ReliquaryMainPropData data : GameData.getReliquaryMainPropDataMap().values()) {
continue; if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) {
} continue;
List<ReliquaryMainPropData> list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new ArrayList<>()); }
list.add(data); List<ReliquaryMainPropData> list =
WeightedList<ReliquaryMainPropData> weightedList = relicRandomMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>()); relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new ArrayList<>());
weightedList.add(data.getWeight(), data); list.add(data);
} WeightedList<ReliquaryMainPropData> weightedList =
for (ReliquaryAffixData data : GameData.getReliquaryAffixDataMap().values()) { relicRandomMainPropDepot.computeIfAbsent(
if (data.getWeight() <= 0 || data.getDepotId() <= 0) { data.getPropDepotId(), k -> new WeightedList<>());
continue; weightedList.add(data.getWeight(), data);
} }
List<ReliquaryAffixData> list = relicAffixDepot.computeIfAbsent(data.getDepotId(), k -> new ArrayList<>()); for (ReliquaryAffixData data : GameData.getReliquaryAffixDataMap().values()) {
list.add(data); if (data.getWeight() <= 0 || data.getDepotId() <= 0) {
} continue;
// Let the server owner know if theyre missing weights }
if (relicMainPropDepot.size() == 0 || relicAffixDepot.size() == 0) { List<ReliquaryAffixData> list =
Grasscutter.getLogger().error("Relic properties are missing weights! Please check your ReliquaryMainPropExcelConfigData or ReliquaryAffixExcelConfigData files in your ExcelBinOutput folder."); relicAffixDepot.computeIfAbsent(data.getDepotId(), k -> new ArrayList<>());
} list.add(data);
} }
// Let the server owner know if theyre missing weights
public static ReliquaryMainPropData getRandomRelicMainProp(int depot) { if (relicMainPropDepot.size() == 0 || relicAffixDepot.size() == 0) {
WeightedList<ReliquaryMainPropData> depotList = relicRandomMainPropDepot.get(depot); Grasscutter.getLogger()
if (depotList == null) { .error(
return null; "Relic properties are missing weights! Please check your ReliquaryMainPropExcelConfigData or ReliquaryAffixExcelConfigData files in your ExcelBinOutput folder.");
} }
return depotList.next(); }
}
public static ReliquaryMainPropData getRandomRelicMainProp(int depot) {
public static List<ReliquaryMainPropData> getRelicMainPropList(int depot) { WeightedList<ReliquaryMainPropData> depotList = relicRandomMainPropDepot.get(depot);
return relicMainPropDepot.get(depot); if (depotList == null) {
} return null;
}
public static List<ReliquaryAffixData> getRelicAffixList(int depot) { return depotList.next();
return relicAffixDepot.get(depot); }
}
public static List<ReliquaryMainPropData> getRelicMainPropList(int depot) {
public static void addSpawnListById(HashMap<SpawnDataEntry.GridBlockId, ArrayList<SpawnDataEntry>> data) { return relicMainPropDepot.get(depot);
spawnLists.putAll(data); }
}
} public static List<ReliquaryAffixData> getRelicAffixList(int depot) {
return relicAffixDepot.get(depot);
}
public static void addSpawnListById(
HashMap<SpawnDataEntry.GridBlockId, ArrayList<SpawnDataEntry>> data) {
spawnLists.putAll(data);
}
}

View File

@ -1,12 +1,10 @@
package emu.grasscutter.data; package emu.grasscutter.data;
public abstract class GameResource { public abstract class GameResource {
public int getId() { public int getId() {
return 0; return 0;
} }
public void onLoad() { public void onLoad() {}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,40 @@
package emu.grasscutter.data; package emu.grasscutter.data;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface ResourceType { public @interface ResourceType {
/** /** Names of the file that this Resource loads from */
* Names of the file that this Resource loads from String[] name();
*/
String[] name(); /**
* Load priority - dictates which order to load this resource, with "highest" being loaded first
/** */
* Load priority - dictates which order to load this resource, with "highest" being loaded first LoadPriority loadPriority() default LoadPriority.NORMAL;
*/
LoadPriority loadPriority() default LoadPriority.NORMAL; enum LoadPriority {
HIGHEST(4),
enum LoadPriority { HIGH(3),
HIGHEST(4), NORMAL(2),
HIGH(3), LOW(1),
NORMAL(2), LOWEST(0);
LOW(1),
LOWEST(0); private final int value;
private final int value; LoadPriority(int value) {
this.value = value;
LoadPriority(int value) { }
this.value = value;
} public static List<LoadPriority> getInOrder() {
return Stream.of(LoadPriority.values()).sorted((x, y) -> y.value() - x.value()).toList();
public static List<LoadPriority> getInOrder() { }
return Stream.of(LoadPriority.values()).sorted((x, y) -> y.value() - x.value()).toList();
} public int value() {
return value;
public int value() { }
return value; }
} }
}
}

View File

@ -1,23 +1,21 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
public class AbilityEmbryoEntry { public class AbilityEmbryoEntry {
private String name; private String name;
private String[] abilities; private String[] abilities;
public AbilityEmbryoEntry() { public AbilityEmbryoEntry() {}
} public AbilityEmbryoEntry(String avatarName, String[] array) {
this.name = avatarName;
public AbilityEmbryoEntry(String avatarName, String[] array) { this.abilities = array;
this.name = avatarName; }
this.abilities = array;
} public String getName() {
return name;
public String getName() { }
return name;
} public String[] getAbilities() {
return abilities;
public String[] getAbilities() { }
return abilities; }
}
}

View File

@ -1,97 +1,270 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.common.DynamicFloat; import emu.grasscutter.data.common.DynamicFloat;
import java.io.Serializable;
import java.io.Serializable;
public class AbilityModifier implements Serializable {
public class AbilityModifier implements Serializable { private static final long serialVersionUID = -2001232313615923575L;
private static final long serialVersionUID = -2001232313615923575L;
@SerializedName(
@SerializedName(value = "onAdded", alternate = {"KCICDEJLIJD"}) value = "onAdded",
public AbilityModifierAction[] onAdded; alternate = {"KCICDEJLIJD"})
@SerializedName(value = "onThinkInterval", alternate = {"PBDDACFFPOE"}) public AbilityModifierAction[] onAdded;
public AbilityModifierAction[] onThinkInterval;
public AbilityModifierAction[] onRemoved; @SerializedName(
public DynamicFloat duration = DynamicFloat.ZERO; value = "onThinkInterval",
alternate = {"PBDDACFFPOE"})
public static class AbilityModifierAction { public AbilityModifierAction[] onThinkInterval;
@SerializedName("$type")
public Type type; public AbilityModifierAction[] onRemoved;
public String target; public DynamicFloat duration = DynamicFloat.ZERO;
@SerializedName(value = "amount", alternate = "PDLLIFICICJ")
public DynamicFloat amount = DynamicFloat.ZERO; public static class AbilityModifierAction {
public DynamicFloat amountByCasterAttackRatio = DynamicFloat.ZERO; @SerializedName("$type")
public DynamicFloat amountByCasterCurrentHPRatio = DynamicFloat.ZERO; public Type type;
public DynamicFloat amountByCasterMaxHPRatio = DynamicFloat.ZERO;
public DynamicFloat amountByGetDamage = DynamicFloat.ZERO; public String target;
public DynamicFloat amountByTargetCurrentHPRatio = DynamicFloat.ZERO;
public DynamicFloat amountByTargetMaxHPRatio = DynamicFloat.ZERO; @SerializedName(value = "amount", alternate = "PDLLIFICICJ")
@SerializedName(value = "ignoreAbilityProperty", alternate = "HHFGADCJJDI") public DynamicFloat amount = DynamicFloat.ZERO;
public boolean ignoreAbilityProperty;
public String modifierName; public DynamicFloat amountByCasterAttackRatio = DynamicFloat.ZERO;
public enum Type { public DynamicFloat amountByCasterCurrentHPRatio = DynamicFloat.ZERO;
ActCameraRadialBlur, ActCameraShake, AddAvatarSkillInfo, AddChargeBarValue, public DynamicFloat amountByCasterMaxHPRatio = DynamicFloat.ZERO;
AddClimateMeter, AddElementDurability, AddGlobalValue, AddGlobalValueToTarget, public DynamicFloat amountByGetDamage = DynamicFloat.ZERO;
AddRegionalPlayVarValue, ApplyModifier, AttachAbilityStateResistance, AttachBulletAimPoint, public DynamicFloat amountByTargetCurrentHPRatio = DynamicFloat.ZERO;
AttachEffect, AttachEffectFirework, AttachElementTypeResistance, AttachModifier, public DynamicFloat amountByTargetMaxHPRatio = DynamicFloat.ZERO;
AttachUIEffect, AvatarCameraParam, AvatarEnterCameraShot, AvatarEnterFocus,
AvatarEnterViewBias, AvatarExitCameraShot, AvatarExitClimb, AvatarExitFocus, @SerializedName(value = "ignoreAbilityProperty", alternate = "HHFGADCJJDI")
AvatarExitViewBias, AvatarShareCDSkillStart, AvatarSkillStart, BroadcastNeuronStimulate, public boolean ignoreAbilityProperty;
CalcDvalinS04RebornPoint, CallLuaTask, ChangeEnviroWeather, ChangeFollowDampTime,
ChangeGadgetUIInteractHint, ChangePlayMode, ChangeTag, ChangeUGCRayTag, public String modifierName;
ClearEndura, ClearGlobalPos, ClearGlobalValue, ClearLocalGadgets,
ClearLockTarget, ClearPos, ConfigAbilityAction, ControlEmotion, public enum Type {
CopyGlobalValue, CreateGadget, CreateMovingPlatform, CreateTile, ActCameraRadialBlur,
DamageByAttackValue, DebugLog, DestroyTile, DoBlink, ActCameraShake,
DoTileAction, DoWatcherSystemAction, DoWidgetSystemAction, DropSubfield, AddAvatarSkillInfo,
DummyAction, DungeonFogEffects, ElementAttachForActivityGacha, EnableAIStealthy, AddChargeBarValue,
EnableAfterImage, EnableAvatarFlyStateTrail, EnableAvatarMoveOnWater, EnableBulletCollisionPluginTrigger, AddClimateMeter,
EnableGadgetIntee, EnableHeadControl, EnableHitBoxByName, EnableMainInterface, AddElementDurability,
EnablePartControl, EnablePositionSynchronization, EnablePushColliderName, EnableRocketJump, AddGlobalValue,
EnableSceneTransformByName, EnterCameraLock, EntityDoSkill, EquipAffixStart, AddGlobalValueToTarget,
ExecuteGadgetLua, FireAISoundEvent, FireChargeBarEffect, FireEffect, AddRegionalPlayVarValue,
FireEffectFirework, FireEffectForStorm, FireFishingEvent, FireHitEffect, ApplyModifier,
FireSubEmitterEffect, FireUIEffect, FixedMonsterRushMove, ForceAirStateFly, AttachAbilityStateResistance,
ForceEnableShakeOffButton, GenerateElemBall, GetFightProperty, GetInteractIdToGlobalValue, AttachBulletAimPoint,
GetPos, HealHP, HideUIBillBoard, IgnoreMoveColToRockCol, AttachEffect,
KillGadget, KillPlayEntity, KillSelf, KillServerGadget, AttachEffectFirework,
LoseHP, ModifyAvatarSkillCD, ModifyVehicleSkillCD, PlayEmoSync, AttachElementTypeResistance,
Predicated, PushDvalinS01Process, PushInterActionByConfigPath, PushPos, AttachModifier,
Randomed, ReTriggerAISkillInitialCD, RefreshUICombatBarLayout, RegisterAIActionPoint, AttachUIEffect,
ReleaseAIActionPoint, RemoveAvatarSkillInfo, RemoveModifier, RemoveModifierByAbilityStateResistanceID, AvatarCameraParam,
RemoveServerBuff, RemoveUniqueModifier, RemoveVelocityForce, Repeated, AvatarEnterCameraShot,
ResetAIAttackTarget, ResetAIResistTauntLevel, ResetAIThreatBroadcastRange, ResetAnimatorTrigger, AvatarEnterFocus,
ReviveDeadAvatar, ReviveElemEnergy, ReviveStamina, SectorCityManeuver, AvatarEnterViewBias,
SendEffectTrigger, SendEffectTriggerToLineEffect, SendEvtElectricCoreMoveEnterP1, SendEvtElectricCoreMoveInterrupt, AvatarExitCameraShot,
ServerLuaCall, ServerLuaTriggerEvent, ServerMonsterLog, SetAIHitFeeling, AvatarExitClimb,
SetAISkillCDAvailableNow, SetAISkillCDMultiplier, SetAISkillGCD, SetAnimatorBool, AvatarExitFocus,
SetAnimatorFloat, SetAnimatorInt, SetAnimatorTrigger, SetAvatarCanShakeOff, AvatarExitViewBias,
SetAvatarHitBuckets, SetCanDieImmediately, SetChargeBarValue, SetDvalinS01FlyState, AvatarShareCDSkillStart,
SetEmissionScaler, SetEntityScale, SetExtraAbilityEnable, SetExtraAbilityState, AvatarSkillStart,
SetGlobalDir, SetGlobalPos, SetGlobalValue, SetGlobalValueByTargetDistance, BroadcastNeuronStimulate,
SetGlobalValueToOverrideMap, SetKeepInAirVelocityForce, SetMaterialParamFloatByTransform, SetNeuronEnable, CalcDvalinS04RebornPoint,
SetOverrideMapValue, SetPartControlTarget, SetPoseBool, SetPoseFloat, CallLuaTask,
SetPoseInt, SetRandomOverrideMapValue, SetRegionalPlayVarValue, SetSelfAttackTarget, ChangeEnviroWeather,
SetSkillAnchor, SetSpecialCamera, SetSurroundAnchor, SetSystemValueToOverrideMap, ChangeFollowDampTime,
SetTargetNumToGlobalValue, SetUICombatBarAsh, SetUICombatBarSpark, SetVelocityIgnoreAirGY, ChangeGadgetUIInteractHint,
SetWeaponAttachPointRealName, SetWeaponBindState, ShowExtraAbility, ShowProgressBarAction, ChangePlayMode,
ShowReminder, ShowScreenEffect, ShowTextMap, ShowUICombatBar, ChangeTag,
StartDither, SumTargetWeightToSelfGlobalValue, Summon, SyncToStageScript, ChangeUGCRayTag,
TriggerAbility, TriggerAttackEvent, TriggerAttackTargetMapEvent, TriggerAudio, ClearEndura,
TriggerAuxWeaponTrans, TriggerBullet, TriggerCreateGadgetToEquipPart, TriggerDropEquipParts, ClearGlobalPos,
TriggerFaceAnimation, TriggerGadgetInteractive, TriggerHideWeapon, TriggerSetCastShadow, ClearGlobalValue,
TriggerSetPassThrough, TriggerSetRenderersEnable, TriggerSetShadowRamp, TriggerSetVisible, ClearLocalGadgets,
TriggerTaunt, TriggerThrowEquipPart, TriggerUGCGadgetMove, TryFindBlinkPoint, ClearLockTarget,
TryFindBlinkPointByBorn, TryTriggerPlatformStartMove, TurnDirection, TurnDirectionToPos, ClearPos,
UpdateReactionDamage, UseSkillEliteSet, WidgetSkillStart ConfigAbilityAction,
} ControlEmotion,
} CopyGlobalValue,
CreateGadget,
//The following should be implemented into DynamicFloat if older resource formats need to be supported CreateMovingPlatform,
// public static class AbilityModifierValue { CreateTile,
// public boolean isFormula; DamageByAttackValue,
// public boolean isDynamic; DebugLog,
// public String dynamicKey; DestroyTile,
// } DoBlink,
} DoTileAction,
DoWatcherSystemAction,
DoWidgetSystemAction,
DropSubfield,
DummyAction,
DungeonFogEffects,
ElementAttachForActivityGacha,
EnableAIStealthy,
EnableAfterImage,
EnableAvatarFlyStateTrail,
EnableAvatarMoveOnWater,
EnableBulletCollisionPluginTrigger,
EnableGadgetIntee,
EnableHeadControl,
EnableHitBoxByName,
EnableMainInterface,
EnablePartControl,
EnablePositionSynchronization,
EnablePushColliderName,
EnableRocketJump,
EnableSceneTransformByName,
EnterCameraLock,
EntityDoSkill,
EquipAffixStart,
ExecuteGadgetLua,
FireAISoundEvent,
FireChargeBarEffect,
FireEffect,
FireEffectFirework,
FireEffectForStorm,
FireFishingEvent,
FireHitEffect,
FireSubEmitterEffect,
FireUIEffect,
FixedMonsterRushMove,
ForceAirStateFly,
ForceEnableShakeOffButton,
GenerateElemBall,
GetFightProperty,
GetInteractIdToGlobalValue,
GetPos,
HealHP,
HideUIBillBoard,
IgnoreMoveColToRockCol,
KillGadget,
KillPlayEntity,
KillSelf,
KillServerGadget,
LoseHP,
ModifyAvatarSkillCD,
ModifyVehicleSkillCD,
PlayEmoSync,
Predicated,
PushDvalinS01Process,
PushInterActionByConfigPath,
PushPos,
Randomed,
ReTriggerAISkillInitialCD,
RefreshUICombatBarLayout,
RegisterAIActionPoint,
ReleaseAIActionPoint,
RemoveAvatarSkillInfo,
RemoveModifier,
RemoveModifierByAbilityStateResistanceID,
RemoveServerBuff,
RemoveUniqueModifier,
RemoveVelocityForce,
Repeated,
ResetAIAttackTarget,
ResetAIResistTauntLevel,
ResetAIThreatBroadcastRange,
ResetAnimatorTrigger,
ReviveDeadAvatar,
ReviveElemEnergy,
ReviveStamina,
SectorCityManeuver,
SendEffectTrigger,
SendEffectTriggerToLineEffect,
SendEvtElectricCoreMoveEnterP1,
SendEvtElectricCoreMoveInterrupt,
ServerLuaCall,
ServerLuaTriggerEvent,
ServerMonsterLog,
SetAIHitFeeling,
SetAISkillCDAvailableNow,
SetAISkillCDMultiplier,
SetAISkillGCD,
SetAnimatorBool,
SetAnimatorFloat,
SetAnimatorInt,
SetAnimatorTrigger,
SetAvatarCanShakeOff,
SetAvatarHitBuckets,
SetCanDieImmediately,
SetChargeBarValue,
SetDvalinS01FlyState,
SetEmissionScaler,
SetEntityScale,
SetExtraAbilityEnable,
SetExtraAbilityState,
SetGlobalDir,
SetGlobalPos,
SetGlobalValue,
SetGlobalValueByTargetDistance,
SetGlobalValueToOverrideMap,
SetKeepInAirVelocityForce,
SetMaterialParamFloatByTransform,
SetNeuronEnable,
SetOverrideMapValue,
SetPartControlTarget,
SetPoseBool,
SetPoseFloat,
SetPoseInt,
SetRandomOverrideMapValue,
SetRegionalPlayVarValue,
SetSelfAttackTarget,
SetSkillAnchor,
SetSpecialCamera,
SetSurroundAnchor,
SetSystemValueToOverrideMap,
SetTargetNumToGlobalValue,
SetUICombatBarAsh,
SetUICombatBarSpark,
SetVelocityIgnoreAirGY,
SetWeaponAttachPointRealName,
SetWeaponBindState,
ShowExtraAbility,
ShowProgressBarAction,
ShowReminder,
ShowScreenEffect,
ShowTextMap,
ShowUICombatBar,
StartDither,
SumTargetWeightToSelfGlobalValue,
Summon,
SyncToStageScript,
TriggerAbility,
TriggerAttackEvent,
TriggerAttackTargetMapEvent,
TriggerAudio,
TriggerAuxWeaponTrans,
TriggerBullet,
TriggerCreateGadgetToEquipPart,
TriggerDropEquipParts,
TriggerFaceAnimation,
TriggerGadgetInteractive,
TriggerHideWeapon,
TriggerSetCastShadow,
TriggerSetPassThrough,
TriggerSetRenderersEnable,
TriggerSetShadowRamp,
TriggerSetVisible,
TriggerTaunt,
TriggerThrowEquipPart,
TriggerUGCGadgetMove,
TryFindBlinkPoint,
TryFindBlinkPointByBorn,
TryTriggerPlatformStartMove,
TurnDirection,
TurnDirectionToPos,
UpdateReactionDamage,
UseSkillEliteSet,
WidgetSkillStart
}
}
// The following should be implemented into DynamicFloat if older resource formats need to be
// supported
// public static class AbilityModifierValue {
// public boolean isFormula;
// public boolean isDynamic;
// public String dynamicKey;
// }
}

View File

@ -1,37 +1,35 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import java.util.ArrayList;
import java.util.ArrayList; import java.util.List;
import java.util.List;
public class AbilityModifierEntry {
public class AbilityModifierEntry { public List<AbilityModifierAction> onModifierAdded;
public List<AbilityModifierAction> onModifierAdded; public List<AbilityModifierAction> onThinkInterval;
public List<AbilityModifierAction> onThinkInterval; public List<AbilityModifierAction> onRemoved;
public List<AbilityModifierAction> onRemoved; private final String name; // Custom value
private final String name; // Custom value
public AbilityModifierEntry(String name) {
public AbilityModifierEntry(String name) { this.name = name;
this.name = name; this.onModifierAdded = new ArrayList<>();
this.onModifierAdded = new ArrayList<>(); this.onThinkInterval = new ArrayList<>();
this.onThinkInterval = new ArrayList<>(); this.onRemoved = new ArrayList<>();
this.onRemoved = new ArrayList<>(); }
}
public String getName() {
public String getName() { return name;
return name; }
}
public List<AbilityModifierAction> getOnAdded() {
public List<AbilityModifierAction> getOnAdded() { return onModifierAdded;
return onModifierAdded; }
}
public List<AbilityModifierAction> getOnThinkInterval() {
public List<AbilityModifierAction> getOnThinkInterval() { return onThinkInterval;
return onThinkInterval; }
}
public List<AbilityModifierAction> getOnRemoved() {
public List<AbilityModifierAction> getOnRemoved() { return onRemoved;
return onRemoved; }
} }
}

View File

@ -1,15 +1,13 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import lombok.AccessLevel; import javax.annotation.Nullable;
import lombok.Data; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.Data;
import lombok.experimental.FieldDefaults;
import javax.annotation.Nullable;
@Data
@Data @FieldDefaults(level = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE) public class ConfigGadget {
public class ConfigGadget { // There are more values that can be added that might be useful in the json
// There are more values that can be added that might be useful in the json @Nullable ConfigGadgetCombat combat;
@Nullable }
ConfigGadgetCombat combat;
}

View File

@ -1,18 +1,18 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Data; import lombok.Data;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
@Data @Data
@FieldDefaults(level = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE)
public class ConfigGadgetCombatProperty { public class ConfigGadgetCombatProperty {
float HP; float HP;
boolean isLockHP; boolean isLockHP;
boolean isInvincible; boolean isInvincible;
boolean isGhostToAllied; boolean isGhostToAllied;
float attack; float attack;
float defence; float defence;
float weight; float weight;
boolean useCreatorProperty; boolean useCreatorProperty;
} }

View File

@ -1,56 +1,61 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import lombok.AccessLevel; import java.util.List;
import lombok.Data; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.List;
@Data
@Data @FieldDefaults(level = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE) public class HomeworldDefaultSaveData {
public class HomeworldDefaultSaveData {
@SerializedName(value = "KFHBFNPDJBE", alternate = "PKACPHDGGEI")
@SerializedName(value = "KFHBFNPDJBE", alternate = "PKACPHDGGEI") List<HomeBlock> homeBlockLists;
List<HomeBlock> homeBlockLists;
@SerializedName(value = "IJNPADKGNKE", alternate = "MINCKHBNING") @SerializedName(value = "IJNPADKGNKE", alternate = "MINCKHBNING")
Position bornPos; Position bornPos;
@SerializedName("IPIIGEMFLHK")
Position bornRot; @SerializedName("IPIIGEMFLHK")
@SerializedName("HHOLBNPIHEM") Position bornRot;
Position djinPos;
@SerializedName("KNHCJKHCOAN") @SerializedName("HHOLBNPIHEM")
HomeFurniture mainhouse; Position djinPos;
@SerializedName("NIHOJFEKFPG") @SerializedName("KNHCJKHCOAN")
List<HomeFurniture> doorLists; HomeFurniture mainhouse;
@SerializedName("EPGELGEFJFK")
List<HomeFurniture> stairLists; @SerializedName("NIHOJFEKFPG")
List<HomeFurniture> doorLists;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE) @SerializedName("EPGELGEFJFK")
public static class HomeBlock { List<HomeFurniture> stairLists;
@SerializedName(value = "FGIJCELCGFI", alternate = "PGDPDIDJEEL") @Data
int blockId; @FieldDefaults(level = AccessLevel.PRIVATE)
public static class HomeBlock {
@SerializedName("BEAPOFELABD")
List<HomeFurniture> furnitures; @SerializedName(value = "FGIJCELCGFI", alternate = "PGDPDIDJEEL")
int blockId;
@SerializedName("MLIODLGDFHJ")
List<HomeFurniture> persistentFurnitures; @SerializedName("BEAPOFELABD")
} List<HomeFurniture> furnitures;
@Data @SerializedName("MLIODLGDFHJ")
@FieldDefaults(level = AccessLevel.PRIVATE) List<HomeFurniture> persistentFurnitures;
public static class HomeFurniture { }
@SerializedName(value = "ENHNGKJBJAB", alternate = "KMAAJJHPNBA") @Data
int id; @FieldDefaults(level = AccessLevel.PRIVATE)
@SerializedName(value = "NGIEEIOLPPO", alternate = "JFKAHNCPDME") public static class HomeFurniture {
Position pos;
//@SerializedName(value = "HEOCEHKEBFM", alternate = "LKCKOOGFDBM") @SerializedName(value = "ENHNGKJBJAB", alternate = "KMAAJJHPNBA")
Position rot; int id;
}
} @SerializedName(value = "NGIEEIOLPPO", alternate = "JFKAHNCPDME")
Position pos;
// @SerializedName(value = "HEOCEHKEBFM", alternate = "LKCKOOGFDBM")
Position rot;
}
}

View File

@ -1,81 +1,78 @@
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.game.quest.enums.QuestType;
import lombok.Data; import java.util.List;
import java.util.Objects;
import java.util.List; import lombok.Data;
import java.util.Objects;
public class MainQuestData {
public class MainQuestData { private int id;
private int id; private int ICLLDPJFIMA;
private int ICLLDPJFIMA; private int series;
private int series; private QuestType type;
private QuestType type;
private long titleTextMapHash;
private long titleTextMapHash; private int[] suggestTrackMainQuestList;
private int[] suggestTrackMainQuestList; private int[] rewardIdList;
private int[] rewardIdList;
private SubQuestData[] subQuests;
private SubQuestData[] subQuests; private List<TalkData> talks;
private List<TalkData> talks; private long[] preloadLuaList;
private long[] preloadLuaList;
public int getId() {
public int getId() { return id;
return id; }
}
public int getSeries() {
public int getSeries() { return series;
return series; }
}
public QuestType getType() {
public QuestType getType() { return type;
return type; }
}
public long getTitleTextMapHash() {
public long getTitleTextMapHash() { return titleTextMapHash;
return titleTextMapHash; }
}
public int[] getSuggestTrackMainQuestList() {
public int[] getSuggestTrackMainQuestList() { return suggestTrackMainQuestList;
return suggestTrackMainQuestList; }
}
public int[] getRewardIdList() {
public int[] getRewardIdList() { return rewardIdList;
return rewardIdList; }
}
public SubQuestData[] getSubQuests() {
public SubQuestData[] getSubQuests() { return subQuests;
return subQuests; }
}
public List<TalkData> getTalks() {
public List<TalkData> getTalks() { return talks;
return talks; }
}
public void onLoad() {
public void onLoad() { this.talks = talks.stream().filter(Objects::nonNull).toList();
this.talks = talks.stream().filter(Objects::nonNull).toList(); }
}
@Data
@Data public static class SubQuestData {
public static class SubQuestData { private int subId;
private int subId; private int order;
private int order; }
}
@Data
@Entity
@Data public static class TalkData {
@Entity private int id;
public static class TalkData { private String heroTalk;
private int id;
private String heroTalk; public TalkData() {}
public TalkData() { public TalkData(int id, String heroTalk) {
} this.id = id;
this.heroTalk = heroTalk;
public TalkData(int id, String heroTalk) { }
this.id = id; }
this.heroTalk = heroTalk; }
}
}
}

View File

@ -1,72 +1,71 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import emu.grasscutter.data.ResourceLoader.OpenConfigData; import emu.grasscutter.data.ResourceLoader.OpenConfigData;
import java.util.ArrayList;
import java.util.ArrayList; import java.util.List;
import java.util.List;
public class OpenConfigEntry {
public class OpenConfigEntry { private final String name;
private final String name; private String[] addAbilities;
private String[] addAbilities; private int extraTalentIndex;
private int extraTalentIndex; private SkillPointModifier[] skillPointModifiers;
private SkillPointModifier[] skillPointModifiers;
public OpenConfigEntry(String name, OpenConfigData[] data) {
public OpenConfigEntry(String name, OpenConfigData[] data) { this.name = name;
this.name = name;
List<String> abilityList = new ArrayList<>();
List<String> abilityList = new ArrayList<>(); List<SkillPointModifier> modList = new ArrayList<>();
List<SkillPointModifier> modList = new ArrayList<>();
for (OpenConfigData entry : data) {
for (OpenConfigData entry : data) { if (entry.$type.contains("AddAbility")) {
if (entry.$type.contains("AddAbility")) { abilityList.add(entry.abilityName);
abilityList.add(entry.abilityName); } else if (entry.talentIndex > 0) {
} else if (entry.talentIndex > 0) { this.extraTalentIndex = entry.talentIndex;
this.extraTalentIndex = entry.talentIndex; } else if (entry.$type.contains("ModifySkillPoint")) {
} else if (entry.$type.contains("ModifySkillPoint")) { modList.add(new SkillPointModifier(entry.skillID, entry.pointDelta));
modList.add(new SkillPointModifier(entry.skillID, entry.pointDelta)); }
} }
}
if (abilityList.size() > 0) {
if (abilityList.size() > 0) { this.addAbilities = abilityList.toArray(new String[0]);
this.addAbilities = abilityList.toArray(new String[0]); }
}
if (modList.size() > 0) {
if (modList.size() > 0) { this.skillPointModifiers = modList.toArray(new SkillPointModifier[0]);
this.skillPointModifiers = modList.toArray(new SkillPointModifier[0]); }
} }
}
public String getName() {
public String getName() { return name;
return name; }
}
public String[] getAddAbilities() {
public String[] getAddAbilities() { return addAbilities;
return addAbilities; }
}
public int getExtraTalentIndex() {
public int getExtraTalentIndex() { return extraTalentIndex;
return extraTalentIndex; }
}
public SkillPointModifier[] getSkillPointModifiers() {
public SkillPointModifier[] getSkillPointModifiers() { return skillPointModifiers;
return skillPointModifiers; }
}
public static class SkillPointModifier {
public static class SkillPointModifier { private final int skillId;
private final int skillId; private final int delta;
private final int delta;
public SkillPointModifier(int skillId, int delta) {
public SkillPointModifier(int skillId, int delta) { this.skillId = skillId;
this.skillId = skillId; this.delta = delta;
this.delta = delta; }
}
public int getSkillId() {
public int getSkillId() { return skillId;
return skillId; }
}
public int getDelta() {
public int getDelta() { return delta;
return delta; }
} }
} }
}

View File

@ -1,29 +1,24 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import com.github.davidmoten.rtreemulti.RTree; import com.github.davidmoten.rtreemulti.RTree;
import com.github.davidmoten.rtreemulti.geometry.Geometry; import com.github.davidmoten.rtreemulti.geometry.Geometry;
import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneGroup;
import lombok.AccessLevel; import java.util.List;
import lombok.Data; import java.util.Map;
import lombok.experimental.FieldDefaults; import java.util.concurrent.ConcurrentHashMap;
import lombok.AccessLevel;
import java.util.List; import lombok.Data;
import java.util.Map; import lombok.experimental.FieldDefaults;
import java.util.concurrent.ConcurrentHashMap;
@Data
@Data @FieldDefaults(level = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE) public class SceneNpcBornData {
public class SceneNpcBornData { int sceneId;
int sceneId; List<SceneNpcBornEntry> bornPosList;
List<SceneNpcBornEntry> bornPosList;
/** Spatial Index For NPC */
/** transient RTree<SceneNpcBornEntry, Geometry> index;
* Spatial Index For NPC
*/ /** npc groups */
transient RTree<SceneNpcBornEntry, Geometry> index; transient Map<Integer, SceneGroup> groups = new ConcurrentHashMap<>();
}
/**
* npc groups
*/
transient Map<Integer, SceneGroup> groups = new ConcurrentHashMap<>();
}

View File

@ -1,31 +1,42 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import lombok.AccessLevel; import java.util.List;
import lombok.Data; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.Data;
import lombok.experimental.FieldDefaults;
import java.util.List;
@Data
@Data @FieldDefaults(level = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE) public class SceneNpcBornEntry {
public class SceneNpcBornEntry { @SerializedName(
@SerializedName(value = "id", alternate = {"_id", "ID"}) value = "id",
int id; alternate = {"_id", "ID"})
int id;
@SerializedName(value = "configId", alternate = {"_configId"})
int configId; @SerializedName(
value = "configId",
@SerializedName(value = "pos", alternate = {"_pos"}) alternate = {"_configId"})
Position pos; int configId;
@SerializedName(value = "rot", alternate = {"_rot"}) @SerializedName(
Position rot; value = "pos",
alternate = {"_pos"})
@SerializedName(value = "groupId", alternate = {"_groupId"}) Position pos;
int groupId;
@SerializedName(
@SerializedName(value = "suiteIdList", alternate = {"_suiteIdList"}) value = "rot",
List<Integer> suiteIdList; alternate = {"_rot"})
} Position rot;
@SerializedName(
value = "groupId",
alternate = {"_groupId"})
int groupId;
@SerializedName(
value = "suiteIdList",
alternate = {"_suiteIdList"})
List<Integer> suiteIdList;
}

View File

@ -1,26 +1,24 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import emu.grasscutter.data.common.PointData; import emu.grasscutter.data.common.PointData;
import lombok.Getter; import lombok.Getter;
public class ScenePointEntry { public class ScenePointEntry {
@Getter @Getter private final int sceneId;
final private int sceneId; @Getter private final PointData pointData;
@Getter
final private PointData pointData; @Deprecated(forRemoval = true)
public ScenePointEntry(String name, PointData pointData) {
@Deprecated(forRemoval = true) this.sceneId = Integer.parseInt(name.split("_")[0]);
public ScenePointEntry(String name, PointData pointData) { this.pointData = pointData;
this.sceneId = Integer.parseInt(name.split("_")[0]); }
this.pointData = pointData;
} public ScenePointEntry(int sceneId, PointData pointData) {
this.sceneId = sceneId;
public ScenePointEntry(int sceneId, PointData pointData) { this.pointData = pointData;
this.sceneId = sceneId; }
this.pointData = pointData;
} public String getName() {
return this.sceneId + "_" + this.pointData.getId();
public String getName() { }
return this.sceneId + "_" + this.pointData.getId(); }
}
}

View File

@ -1,21 +1,18 @@
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import lombok.Data; import java.util.List;
import java.util.Map;
import java.util.List; import lombok.Data;
import java.util.Map;
@Data
@Data public class ScriptSceneData {
public class ScriptSceneData { Map<String, ScriptObject> scriptObjectList;
Map<String, ScriptObject> scriptObjectList;
@Data
@Data public static class ScriptObject {
public static class ScriptObject { // private SceneGroup groups;
//private SceneGroup groups; @SerializedName("dummy_points")
@SerializedName("dummy_points") private Map<String, List<Float>> dummyPoints;
private Map<String, List<Float>> dummyPoints; }
}
}
}

View File

@ -1,19 +1,19 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
public class CurveInfo { public class CurveInfo {
private String type; private String type;
private String arith; private String arith;
private float value; private float value;
public String getType() { public String getType() {
return type; return type;
} }
public String getArith() { public String getArith() {
return arith; return arith;
} }
public float getValue() { public float getValue() {
return value; return value;
} }
} }

View File

@ -1,97 +1,105 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
import it.unimi.dsi.fastutil.floats.FloatArrayList; import it.unimi.dsi.fastutil.floats.FloatArrayList;
import it.unimi.dsi.fastutil.objects.Object2FloatArrayMap; import it.unimi.dsi.fastutil.objects.Object2FloatArrayMap;
import it.unimi.dsi.fastutil.objects.Object2FloatMap; import it.unimi.dsi.fastutil.objects.Object2FloatMap;
import lombok.val; import java.util.List;
import java.util.Optional;
import java.util.List; import lombok.val;
import java.util.Optional;
public class DynamicFloat {
public class DynamicFloat { public static DynamicFloat ZERO = new DynamicFloat(0f);
public static DynamicFloat ZERO = new DynamicFloat(0f); private List<StackOp> ops;
private List<StackOp> ops; private boolean dynamic = false;
private boolean dynamic = false; private float constant = 0f;
private float constant = 0f;
public DynamicFloat(float constant) { public DynamicFloat(float constant) {
this.constant = constant; this.constant = constant;
} }
public DynamicFloat(String key) { public DynamicFloat(String key) {
this.dynamic = true; this.dynamic = true;
this.ops = List.of(new StackOp(key)); this.ops = List.of(new StackOp(key));
} }
public DynamicFloat(boolean b) { public DynamicFloat(boolean b) {
this.dynamic = true; this.dynamic = true;
this.ops = List.of(new StackOp(String.valueOf(b))); this.ops = List.of(new StackOp(String.valueOf(b)));
} }
public DynamicFloat(List<StackOp> ops) { public DynamicFloat(List<StackOp> ops) {
this.dynamic = true; this.dynamic = true;
this.ops = ops; this.ops = ops;
} }
public String toString(boolean nextBoolean) { public String toString(boolean nextBoolean) {
String key = String.valueOf(nextBoolean); String key = String.valueOf(nextBoolean);
this.ops = List.of(new StackOp(key)); this.ops = List.of(new StackOp(key));
return ops.toString(); return ops.toString();
} }
public float get() { public float get() {
return this.get(new Object2FloatArrayMap<String>()); return this.get(new Object2FloatArrayMap<String>());
} }
public float get(Object2FloatMap<String> props) { public float get(Object2FloatMap<String> props) {
if (!dynamic) if (!dynamic) return constant;
return constant;
val fl = new FloatArrayList();
val fl = new FloatArrayList(); for (var op : this.ops) {
for (var op : this.ops) { switch (op.op) {
switch (op.op) { case CONSTANT -> fl.push(op.fValue);
case CONSTANT -> fl.push(op.fValue); case KEY -> fl.push(props.getOrDefault(op.sValue, 0f));
case KEY -> fl.push(props.getOrDefault(op.sValue, 0f)); case ADD -> fl.push(fl.popFloat() + fl.popFloat());
case ADD -> fl.push(fl.popFloat() + fl.popFloat()); case SUB -> fl.push(
case SUB -> -fl.popFloat() + fl.popFloat()); // [f0, f1, f2] -> [f0, f1-f2] (opposite of RPN order)
fl.push(-fl.popFloat() + fl.popFloat()); // [f0, f1, f2] -> [f0, f1-f2] (opposite of RPN order) case MUL -> fl.push(fl.popFloat() * fl.popFloat());
case MUL -> fl.push(fl.popFloat() * fl.popFloat()); case DIV -> fl.push((1f / fl.popFloat()) * fl.popFloat()); // [f0, f1, f2] -> [f0, f1/f2]
case DIV -> fl.push((1f / fl.popFloat()) * fl.popFloat()); // [f0, f1, f2] -> [f0, f1/f2] case NEXBOOLEAN -> fl.push(props.getOrDefault(Optional.of(op.bValue), 0f));
case NEXBOOLEAN -> fl.push(props.getOrDefault(Optional.of(op.bValue), 0f)); }
} }
}
return fl.popFloat(); // well-formed data will always have only one value left at this point
return fl.popFloat(); // well-formed data will always have only one value left at this point }
}
public static class StackOp {
public static class StackOp { public Op op;
public Op op;
public float fValue;
public float fValue; public String sValue;
public String sValue; public boolean bValue;
public boolean bValue;
public StackOp(String s) { public StackOp(String s) {
switch (s.toUpperCase()) { switch (s.toUpperCase()) {
case "ADD" -> this.op = Op.ADD; case "ADD" -> this.op = Op.ADD;
case "SUB" -> this.op = Op.SUB; case "SUB" -> this.op = Op.SUB;
case "MUL" -> this.op = Op.MUL; case "MUL" -> this.op = Op.MUL;
case "DIV" -> this.op = Op.DIV; case "DIV" -> this.op = Op.DIV;
default -> { default -> {
this.op = Op.KEY; this.op = Op.KEY;
this.sValue = s; this.sValue = s;
} }
} }
} }
public StackOp(boolean b) { public StackOp(boolean b) {
this.op = Op.NEXBOOLEAN; this.op = Op.NEXBOOLEAN;
this.bValue = Boolean.parseBoolean(String.valueOf(b)); this.bValue = Boolean.parseBoolean(String.valueOf(b));
} }
public StackOp(float f) { public StackOp(float f) {
this.op = Op.CONSTANT; this.op = Op.CONSTANT;
this.fValue = f; this.fValue = f;
} }
enum Op {CONSTANT, KEY, ADD, SUB, MUL, DIV, NEXBOOLEAN} enum Op {
} CONSTANT,
} KEY,
ADD,
SUB,
MUL,
DIV,
NEXBOOLEAN
}
}
}

View File

@ -1,25 +1,25 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
public class FightPropData { public class FightPropData {
private String propType; private String propType;
private FightProperty prop; private FightProperty prop;
private float value; private float value;
public String getPropType() { public String getPropType() {
return propType; return propType;
} }
public float getValue() { public float getValue() {
return value; return value;
} }
public FightProperty getProp() { public FightProperty getProp() {
return prop; return prop;
} }
public void onLoad() { public void onLoad() {
this.prop = FightProperty.getPropByName(propType); this.prop = FightProperty.getPropByName(propType);
} }
} }

View File

@ -1,36 +1,39 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
// Used in excels // Used in excels
public class ItemParamData { public class ItemParamData {
@SerializedName(value = "id", alternate = {"itemId"}) @SerializedName(
private int id; value = "id",
alternate = {"itemId"})
@SerializedName(value = "count", alternate = {"itemCount"}) private int id;
private int count;
@SerializedName(
public ItemParamData() { value = "count",
} alternate = {"itemCount"})
private int count;
public ItemParamData(int id, int count) {
this.id = id; public ItemParamData() {}
this.count = count;
} public ItemParamData(int id, int count) {
this.id = id;
public int getId() { this.count = count;
return id; }
}
public int getId() {
public int getItemId() { return id;
return id; }
}
public int getItemId() {
public int getCount() { return id;
return count; }
}
public int getCount() {
public int getItemCount() { return count;
return count; }
}
} public int getItemCount() {
return count;
}
}

View File

@ -1,27 +1,26 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
public class ItemParamStringData { public class ItemParamStringData {
private int id; private int id;
private String count; private String count;
public ItemParamStringData() { public ItemParamStringData() {}
}
public int getId() {
public int getId() { return id;
return id; }
}
public String getCount() {
public String getCount() { return count;
return count; }
}
public ItemParamData toItemParamData() {
public ItemParamData toItemParamData() { if (count.contains(";")) {
if (count.contains(";")) { String[] split = count.split(";");
String[] split = count.split(";"); count = count.split(";")[split.length - 1];
count = count.split(";")[split.length - 1]; } else if (count.contains(".")) {
} else if (count.contains(".")) { return new ItemParamData(id, (int) Math.ceil(Double.parseDouble(count)));
return new ItemParamData(id, (int) Math.ceil(Double.parseDouble(count))); }
} return new ItemParamData(id, Integer.parseInt(count));
return new ItemParamData(id, Integer.parseInt(count)); }
} }
}

View File

@ -1,58 +1,61 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.DailyDungeonData; import emu.grasscutter.data.excels.DailyDungeonData;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
public class PointData { public class PointData {
@Getter @Getter @Setter private int id;
@Setter private String $type;
private int id; @Getter private Position tranPos;
private String $type;
@Getter @SerializedName(
private Position tranPos; value = "dungeonIds",
alternate = {"JHHFPGJNMIN"})
@SerializedName(value = "dungeonIds", alternate = {"JHHFPGJNMIN"}) @Getter
@Getter private int[] dungeonIds;
private int[] dungeonIds;
@SerializedName(
@SerializedName(value = "dungeonRandomList", alternate = {"OIBKFJNBLHO"}) value = "dungeonRandomList",
@Getter alternate = {"OIBKFJNBLHO"})
private int[] dungeonRandomList; @Getter
private int[] dungeonRandomList;
@SerializedName(value = "tranSceneId", alternate = {"JHBICGBAPIH"})
@Getter @SerializedName(
@Setter value = "tranSceneId",
private int tranSceneId; alternate = {"JHBICGBAPIH"})
@Getter
public String getType() { @Setter
return $type; private int tranSceneId;
}
public String getType() {
public void updateDailyDungeon() { return $type;
if (this.dungeonRandomList == null || this.dungeonRandomList.length == 0) { }
return;
} public void updateDailyDungeon() {
if (this.dungeonRandomList == null || this.dungeonRandomList.length == 0) {
IntList newDungeons = new IntArrayList(); return;
int day = Grasscutter.getCurrentDayOfWeek(); }
for (int randomId : this.dungeonRandomList) { IntList newDungeons = new IntArrayList();
DailyDungeonData data = GameData.getDailyDungeonDataMap().get(randomId); int day = Grasscutter.getCurrentDayOfWeek();
if (data != null) { for (int randomId : this.dungeonRandomList) {
for (int d : data.getDungeonsByDay(day)) { DailyDungeonData data = GameData.getDailyDungeonDataMap().get(randomId);
newDungeons.add(d);
} if (data != null) {
} for (int d : data.getDungeonsByDay(day)) {
} newDungeons.add(d);
}
this.dungeonIds = newDungeons.toIntArray(); }
} }
}
this.dungeonIds = newDungeons.toIntArray();
}
}

View File

@ -1,15 +1,14 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
public class PropGrowCurve { public class PropGrowCurve {
private String type; private String type;
private String growCurve; private String growCurve;
public String getType() { public String getType() {
return this.type; return this.type;
} }
public String getGrowCurve() { public String getGrowCurve() {
return this.growCurve; return this.growCurve;
} }
}
}

View File

@ -1,96 +1,99 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import com.github.davidmoten.guavamini.Lists; import com.github.davidmoten.guavamini.Lists;
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 lombok.Getter; import java.util.HashSet;
import java.util.List;
import java.util.HashSet; import java.util.Set;
import java.util.List; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Set; import java.util.stream.Collectors;
import java.util.concurrent.atomic.AtomicBoolean; import lombok.Getter;
import java.util.stream.Collectors;
@Getter
@Getter @ResourceType(name = "AchievementExcelConfigData.json")
@ResourceType(name = "AchievementExcelConfigData.json") public class AchievementData extends GameResource {
public class AchievementData extends GameResource { private static final AtomicBoolean isDivided = new AtomicBoolean();
private static final AtomicBoolean isDivided = new AtomicBoolean(); private int goalId;
private int goalId; private int preStageAchievementId;
private int preStageAchievementId; private final Set<Integer> groupAchievementIdList = new HashSet<>();
private final Set<Integer> groupAchievementIdList = new HashSet<>(); private boolean isParent;
private boolean isParent; private long titleTextMapHash;
private long titleTextMapHash; private long descTextMapHash;
private long descTextMapHash; private int finishRewardId;
private int finishRewardId; private boolean isDeleteWatcherAfterFinish;
private boolean isDeleteWatcherAfterFinish; private int id;
private int id; private BattlePassMissionData.TriggerConfig triggerConfig;
private BattlePassMissionData.TriggerConfig triggerConfig; private int progress;
private int progress; private boolean isDisuse;
private boolean isDisuse;
public static void divideIntoGroups() {
public static void divideIntoGroups() { if (isDivided.get()) {
if (isDivided.get()) { return;
return; }
}
isDivided.set(true);
isDivided.set(true); var map = GameData.getAchievementDataMap();
var map = GameData.getAchievementDataMap(); var achievementDataList = map.values().stream().filter(AchievementData::isUsed).toList();
var achievementDataList = map.values().stream().filter(AchievementData::isUsed).toList(); for (var data : achievementDataList) {
for (var data : achievementDataList) { if (!data.hasPreStageAchievement() || data.hasGroupAchievements()) {
if (!data.hasPreStageAchievement() || data.hasGroupAchievements()) { continue;
continue; }
}
List<Integer> ids = Lists.newArrayList();
List<Integer> ids = Lists.newArrayList(); int parentId = data.getId();
int parentId = data.getId(); while (true) {
while (true) { var next = map.get(parentId + 1);
var next = map.get(parentId + 1); if (next == null || parentId != next.getPreStageAchievementId()) {
if (next == null || parentId != next.getPreStageAchievementId()) { break;
break; }
}
parentId++;
parentId++; }
}
map.get(parentId).isParent = true;
map.get(parentId).isParent = true;
while (true) {
while (true) { ids.add(parentId);
ids.add(parentId); var previous = map.get(--parentId);
var previous = map.get(--parentId); if (previous == null) {
if (previous == null) { break;
break; } else if (!previous.hasPreStageAchievement()) {
} else if (!previous.hasPreStageAchievement()) { ids.add(parentId);
ids.add(parentId); break;
break; }
} }
}
for (int i : ids) {
for (int i : ids) { map.get(i).groupAchievementIdList.addAll(ids);
map.get(i).groupAchievementIdList.addAll(ids); }
} }
}
map.values().stream()
map.values().stream().filter(a -> !a.hasGroupAchievements() && a.isUsed()).forEach(a -> a.isParent = true); .filter(a -> !a.hasGroupAchievements() && a.isUsed())
} .forEach(a -> a.isParent = true);
}
public boolean hasPreStageAchievement() {
return this.preStageAchievementId != 0; public boolean hasPreStageAchievement() {
} return this.preStageAchievementId != 0;
}
public boolean hasGroupAchievements() {
return !this.groupAchievementIdList.isEmpty(); public boolean hasGroupAchievements() {
} return !this.groupAchievementIdList.isEmpty();
}
public boolean isUsed() {
return !this.isDisuse; public boolean isUsed() {
} return !this.isDisuse;
}
public Set<Integer> getGroupAchievementIdList() {
return this.groupAchievementIdList.stream().collect(Collectors.toUnmodifiableSet()); public Set<Integer> getGroupAchievementIdList() {
} return this.groupAchievementIdList.stream().collect(Collectors.toUnmodifiableSet());
}
public Set<Integer> getExcludedGroupAchievementIdList() {
return this.groupAchievementIdList.stream().filter(integer -> integer != this.getId()).collect(Collectors.toUnmodifiableSet()); public Set<Integer> getExcludedGroupAchievementIdList() {
} return this.groupAchievementIdList.stream()
} .filter(integer -> integer != this.getId())
.collect(Collectors.toUnmodifiableSet());
}
}

View File

@ -1,35 +1,37 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
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 lombok.AccessLevel; import java.util.List;
import lombok.Getter; import java.util.Objects;
import lombok.experimental.FieldDefaults; import lombok.AccessLevel;
import lombok.Getter;
import java.util.List; import lombok.experimental.FieldDefaults;
import java.util.Objects;
@ResourceType(
@ResourceType(name = "NewActivityExcelConfigData.json", loadPriority = ResourceType.LoadPriority.LOW) name = "NewActivityExcelConfigData.json",
@Getter loadPriority = ResourceType.LoadPriority.LOW)
@FieldDefaults(level = AccessLevel.PRIVATE) @Getter
public class ActivityData extends GameResource { @FieldDefaults(level = AccessLevel.PRIVATE)
int activityId; public class ActivityData extends GameResource {
String activityType; int activityId;
List<Integer> condGroupId; String activityType;
List<Integer> watcherId; List<Integer> condGroupId;
List<ActivityWatcherData> watcherDataList; List<Integer> watcherId;
List<ActivityWatcherData> watcherDataList;
@Override
public int getId() { @Override
return this.activityId; public int getId() {
} return this.activityId;
}
@Override
public void onLoad() { @Override
this.watcherDataList = watcherId.stream().map(item -> GameData.getActivityWatcherDataMap().get(item.intValue())) public void onLoad() {
.filter(Objects::nonNull) this.watcherDataList =
.toList(); watcherId.stream()
} .map(item -> GameData.getActivityWatcherDataMap().get(item.intValue()))
.filter(Objects::nonNull)
} .toList();
}
}

View File

@ -1,30 +1,24 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.shop.ShopType; import emu.grasscutter.game.shop.ShopType;
import lombok.Getter; import java.util.List;
import lombok.Getter;
import java.util.List;
@ResourceType(name = "ActivityShopOverallExcelConfigData.json")
@ResourceType(name = "ActivityShopOverallExcelConfigData.json") public class ActivityShopData extends GameResource {
public class ActivityShopData extends GameResource { @Getter private int scheduleId;
@Getter @Getter private ShopType shopType;
private int scheduleId; @Getter private List<Integer> sheetList;
@Getter
private ShopType shopType; @Override
@Getter public int getId() {
private List<Integer> sheetList; return getShopTypeId();
}
@Override public int getShopTypeId() {
public int getId() { if (this.shopType == null) this.shopType = ShopType.SHOP_TYPE_NONE;
return getShopTypeId(); return shopType.shopTypeId;
} }
}
public int getShopTypeId() {
if (this.shopType == null)
this.shopType = ShopType.SHOP_TYPE_NONE;
return shopType.shopTypeId;
}
}

View File

@ -1,37 +1,39 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import lombok.AccessLevel; import java.util.List;
import lombok.Getter; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.Getter;
import lombok.experimental.FieldDefaults;
import java.util.List;
@ResourceType(
@ResourceType(name = "NewActivityWatcherConfigData.json", loadPriority = ResourceType.LoadPriority.HIGH) name = "NewActivityWatcherConfigData.json",
@Getter loadPriority = ResourceType.LoadPriority.HIGH)
@FieldDefaults(level = AccessLevel.PRIVATE) @Getter
public class ActivityWatcherData extends GameResource { @FieldDefaults(level = AccessLevel.PRIVATE)
@Getter(onMethod_ = @Override) public class ActivityWatcherData extends GameResource {
int id; @Getter(onMethod_ = @Override)
int rewardID; int id;
int progress;
WatcherTrigger triggerConfig; int rewardID;
int progress;
@Override WatcherTrigger triggerConfig;
public void onLoad() {
triggerConfig.paramList = triggerConfig.paramList.stream().filter(x -> (x != null) && !x.isBlank()).toList(); @Override
triggerConfig.watcherTriggerType = WatcherTriggerType.getTypeByName(triggerConfig.triggerType); public void onLoad() {
} triggerConfig.paramList =
triggerConfig.paramList.stream().filter(x -> (x != null) && !x.isBlank()).toList();
@Getter triggerConfig.watcherTriggerType = WatcherTriggerType.getTypeByName(triggerConfig.triggerType);
@FieldDefaults(level = AccessLevel.PRIVATE) }
public static class WatcherTrigger {
String triggerType; @Getter
List<String> paramList; @FieldDefaults(level = AccessLevel.PRIVATE)
public static class WatcherTrigger {
transient WatcherTriggerType watcherTriggerType; String triggerType;
} List<String> paramList;
} transient WatcherTriggerType watcherTriggerType;
}
}

View File

@ -1,37 +1,38 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
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;
@ResourceType(name = "AvatarCostumeExcelConfigData.json") @ResourceType(name = "AvatarCostumeExcelConfigData.json")
public class AvatarCostumeData extends GameResource { public class AvatarCostumeData extends GameResource {
@SerializedName(value = "skinId", alternate = "costumeId") @SerializedName(value = "skinId", alternate = "costumeId")
private int skinId; private int skinId;
private int itemId;
private int characterId; private int itemId;
private int quality; private int characterId;
private int quality;
@Override
public int getId() { @Override
return this.skinId; public int getId() {
} return this.skinId;
}
public int getItemId() {
return this.itemId; public int getItemId() {
} return this.itemId;
}
public int getCharacterId() {
return characterId; public int getCharacterId() {
} return characterId;
}
public int getQuality() {
return quality; public int getQuality() {
} return quality;
}
@Override
public void onLoad() { @Override
GameData.getAvatarCostumeDataItemIdMap().put(this.getItemId(), this); public void onLoad() {
} GameData.getAvatarCostumeDataItemIdMap().put(this.getItemId(), this);
} }
}

View File

@ -1,36 +1,36 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.CurveInfo; import emu.grasscutter.data.common.CurveInfo;
import java.util.HashMap;
import java.util.HashMap; import java.util.Map;
import java.util.Map; import java.util.stream.Stream;
import java.util.stream.Stream;
@ResourceType(name = "AvatarCurveExcelConfigData.json")
@ResourceType(name = "AvatarCurveExcelConfigData.json") public class AvatarCurveData extends GameResource {
public class AvatarCurveData extends GameResource { private int level;
private int level; private CurveInfo[] curveInfos;
private CurveInfo[] curveInfos;
private Map<String, Float> curveInfoMap;
private Map<String, Float> curveInfoMap;
@Override
@Override public int getId() {
public int getId() { return this.level;
return this.level; }
}
public int getLevel() {
public int getLevel() { return level;
return level; }
}
public Map<String, Float> getCurveInfos() {
public Map<String, Float> getCurveInfos() { return curveInfoMap;
return curveInfoMap; }
}
@Override
@Override public void onLoad() {
public void onLoad() { this.curveInfoMap = new HashMap<>();
this.curveInfoMap = new HashMap<>(); Stream.of(this.curveInfos)
Stream.of(this.curveInfos).forEach(info -> this.curveInfoMap.put(info.getType(), info.getValue())); .forEach(info -> this.curveInfoMap.put(info.getType(), info.getValue()));
} }
} }

View File

@ -1,23 +1,23 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarFettersLevelExcelConfigData.json") @ResourceType(name = "AvatarFettersLevelExcelConfigData.json")
public class AvatarFetterLevelData extends GameResource { public class AvatarFetterLevelData extends GameResource {
private int fetterLevel; private int fetterLevel;
private int needExp; private int needExp;
@Override @Override
public int getId() { public int getId() {
return this.fetterLevel; return this.fetterLevel;
} }
public int getLevel() { public int getLevel() {
return fetterLevel; return fetterLevel;
} }
public int getExp() { public int getExp() {
return needExp; return needExp;
} }
} }

View File

@ -1,24 +1,22 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarFlycloakExcelConfigData.json") @ResourceType(name = "AvatarFlycloakExcelConfigData.json")
public class AvatarFlycloakData extends GameResource { public class AvatarFlycloakData extends GameResource {
private int flycloakId; private int flycloakId;
private long nameTextMapHash; private long nameTextMapHash;
@Override @Override
public int getId() { public int getId() {
return this.flycloakId; return this.flycloakId;
} }
public long getNameTextMapHash() { public long getNameTextMapHash() {
return nameTextMapHash; return nameTextMapHash;
} }
@Override @Override
public void onLoad() { public void onLoad() {}
}
}
}

View File

@ -1,23 +1,23 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarLevelExcelConfigData.json") @ResourceType(name = "AvatarLevelExcelConfigData.json")
public class AvatarLevelData extends GameResource { public class AvatarLevelData extends GameResource {
private int level; private int level;
private int exp; private int exp;
@Override @Override
public int getId() { public int getId() {
return this.level; return this.level;
} }
public int getLevel() { public int getLevel() {
return level; return level;
} }
public int getExp() { public int getExp() {
return exp; return exp;
} }
} }

View File

@ -1,75 +1,74 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData; import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import java.util.ArrayList;
import java.util.ArrayList;
@ResourceType(name = "AvatarPromoteExcelConfigData.json")
@ResourceType(name = "AvatarPromoteExcelConfigData.json") public class AvatarPromoteData extends GameResource {
public class AvatarPromoteData extends GameResource {
private int avatarPromoteId;
private int avatarPromoteId; private int promoteLevel;
private int promoteLevel; private int scoinCost;
private int scoinCost; private ItemParamData[] costItems;
private ItemParamData[] costItems; private int unlockMaxLevel;
private int unlockMaxLevel; private FightPropData[] addProps;
private FightPropData[] addProps; private int requiredPlayerLevel;
private int requiredPlayerLevel;
@Override
@Override public int getId() {
public int getId() { return (avatarPromoteId << 8) + promoteLevel;
return (avatarPromoteId << 8) + promoteLevel; }
}
public int getAvatarPromoteId() {
public int getAvatarPromoteId() { return avatarPromoteId;
return avatarPromoteId; }
}
public int getPromoteLevel() {
public int getPromoteLevel() { return promoteLevel;
return promoteLevel; }
}
public ItemParamData[] getCostItems() {
public ItemParamData[] getCostItems() { return costItems;
return costItems; }
}
public int getCoinCost() {
public int getCoinCost() { return scoinCost;
return scoinCost; }
}
public FightPropData[] getAddProps() {
public FightPropData[] getAddProps() { return addProps;
return addProps; }
}
public int getUnlockMaxLevel() {
public int getUnlockMaxLevel() { return unlockMaxLevel;
return unlockMaxLevel; }
}
public int getRequiredPlayerLevel() {
public int getRequiredPlayerLevel() { return requiredPlayerLevel;
return requiredPlayerLevel; }
}
@Override
@Override public void onLoad() {
public void onLoad() { // Trim item params
// Trim item params ArrayList<ItemParamData> trim = new ArrayList<>(getAddProps().length);
ArrayList<ItemParamData> trim = new ArrayList<>(getAddProps().length); for (ItemParamData itemParam : getCostItems()) {
for (ItemParamData itemParam : getCostItems()) { if (itemParam.getId() == 0) {
if (itemParam.getId() == 0) { continue;
continue; }
} trim.add(itemParam);
trim.add(itemParam); }
} this.costItems = trim.toArray(new ItemParamData[trim.size()]);
this.costItems = trim.toArray(new ItemParamData[trim.size()]); // Trim fight prop data (just in case)
// Trim fight prop data (just in case) ArrayList<FightPropData> parsed = new ArrayList<>(getAddProps().length);
ArrayList<FightPropData> parsed = new ArrayList<>(getAddProps().length); for (FightPropData prop : getAddProps()) {
for (FightPropData prop : getAddProps()) { if (prop.getPropType() != null && prop.getValue() != 0f) {
if (prop.getPropType() != null && prop.getValue() != 0f) { prop.onLoad();
prop.onLoad(); parsed.add(prop);
parsed.add(prop); }
} }
} this.addProps = parsed.toArray(new FightPropData[parsed.size()]);
this.addProps = parsed.toArray(new FightPropData[parsed.size()]); }
} }
}

View File

@ -1,24 +1,25 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.ElementType;
import lombok.Getter; import lombok.Getter;
@ResourceType(name = "AvatarSkillExcelConfigData.json", loadPriority = LoadPriority.HIGHEST) @ResourceType(name = "AvatarSkillExcelConfigData.json", loadPriority = LoadPriority.HIGHEST)
@Getter @Getter
public class AvatarSkillData extends GameResource { public class AvatarSkillData extends GameResource {
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
private int id; private int id;
private float cdTime;
private int costElemVal; private float cdTime;
private int maxChargeNum; private int costElemVal;
private int triggerID; private int maxChargeNum;
private boolean isAttackCameraLock; private int triggerID;
private int proudSkillGroupId; private boolean isAttackCameraLock;
private ElementType costElemType; private int proudSkillGroupId;
private long nameTextMapHash; private ElementType costElemType;
private long descTextMapHash; private long nameTextMapHash;
private String abilityName; private long descTextMapHash;
} private String abilityName;
}

View File

@ -1,86 +1,89 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot; import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceLoader.AvatarConfig; import emu.grasscutter.data.ResourceLoader.AvatarConfig;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.binout.AbilityEmbryoEntry; import emu.grasscutter.data.binout.AbilityEmbryoEntry;
import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter; import java.util.List;
import java.util.Optional;
import java.util.List; import java.util.stream.IntStream;
import java.util.Optional; import lombok.Getter;
import java.util.stream.IntStream;
@ResourceType(name = "AvatarSkillDepotExcelConfigData.json", loadPriority = LoadPriority.HIGH)
@ResourceType(name = "AvatarSkillDepotExcelConfigData.json", loadPriority = LoadPriority.HIGH) @Getter
@Getter public class AvatarSkillDepotData extends GameResource {
public class AvatarSkillDepotData extends GameResource { @Getter(onMethod_ = @Override)
@Getter(onMethod_ = @Override) private int id;
private int id;
private int energySkill; private int energySkill;
private int attackModeSkill; private int attackModeSkill;
private List<Integer> skills; private List<Integer> skills;
private List<Integer> subSkills; private List<Integer> subSkills;
private List<String> extraAbilities; private List<String> extraAbilities;
private List<Integer> talents; private List<Integer> talents;
private List<InherentProudSkillOpens> inherentProudSkillOpens; private List<InherentProudSkillOpens> inherentProudSkillOpens;
private String talentStarName; private String talentStarName;
private String skillDepotAbilityGroup; private String skillDepotAbilityGroup;
// Transient // Transient
private AvatarSkillData energySkillData; private AvatarSkillData energySkillData;
private ElementType elementType; private ElementType elementType;
private IntList abilities; private IntList abilities;
private int talentCostItemId; private int talentCostItemId;
public void setAbilities(AbilityEmbryoEntry info) { public void setAbilities(AbilityEmbryoEntry info) {
this.abilities = new IntArrayList(info.getAbilities().length); this.abilities = new IntArrayList(info.getAbilities().length);
for (String ability : info.getAbilities()) { for (String ability : info.getAbilities()) {
this.abilities.add(Utils.abilityHash(ability)); this.abilities.add(Utils.abilityHash(ability));
} }
} }
@Override @Override
public void onLoad() { public void onLoad() {
// Set energy skill data // Set energy skill data
this.energySkillData = GameData.getAvatarSkillDataMap().get(this.energySkill); this.energySkillData = GameData.getAvatarSkillDataMap().get(this.energySkill);
if (this.energySkillData != null) { if (this.energySkillData != null) {
this.elementType = this.energySkillData.getCostElemType(); this.elementType = this.energySkillData.getCostElemType();
} else { } else {
this.elementType = ElementType.None; this.elementType = ElementType.None;
} }
// Set embryo abilities (if player skill depot) // Set embryo abilities (if player skill depot)
if (getSkillDepotAbilityGroup() != null && getSkillDepotAbilityGroup().length() > 0) { if (getSkillDepotAbilityGroup() != null && getSkillDepotAbilityGroup().length() > 0) {
AvatarConfig config = GameDepot.getPlayerAbilities().get(getSkillDepotAbilityGroup()); AvatarConfig config = GameDepot.getPlayerAbilities().get(getSkillDepotAbilityGroup());
if (config != null) { if (config != null) {
this.setAbilities(new AbilityEmbryoEntry(getSkillDepotAbilityGroup(), config.abilities.stream().map(Object::toString).toArray(String[]::new))); this.setAbilities(
} new AbilityEmbryoEntry(
} getSkillDepotAbilityGroup(),
config.abilities.stream().map(Object::toString).toArray(String[]::new)));
// Get constellation item from GameData }
Optional.ofNullable(this.talents) }
.map(talents -> talents.get(0))
.map(i -> GameData.getAvatarTalentDataMap().get((int) i)) // Get constellation item from GameData
.map(talentData -> talentData.getMainCostItemId()) Optional.ofNullable(this.talents)
.ifPresent(itemId -> this.talentCostItemId = itemId); .map(talents -> talents.get(0))
} .map(i -> GameData.getAvatarTalentDataMap().get((int) i))
.map(talentData -> talentData.getMainCostItemId())
public IntStream getSkillsAndEnergySkill() { .ifPresent(itemId -> this.talentCostItemId = itemId);
return IntStream.concat(this.skills.stream().mapToInt(i -> i), IntStream.of(this.energySkill)) }
.filter(skillId -> skillId > 0);
} public IntStream getSkillsAndEnergySkill() {
return IntStream.concat(this.skills.stream().mapToInt(i -> i), IntStream.of(this.energySkill))
@Getter .filter(skillId -> skillId > 0);
public static class InherentProudSkillOpens { }
private int proudSkillGroupId;
private int needAvatarPromoteLevel; @Getter
} public static class InherentProudSkillOpens {
} private int proudSkillGroupId;
private int needAvatarPromoteLevel;
}
}

View File

@ -1,70 +1,69 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.FightPropData; import emu.grasscutter.data.common.FightPropData;
import java.util.ArrayList;
import java.util.ArrayList;
@ResourceType(name = "AvatarTalentExcelConfigData.json", loadPriority = LoadPriority.HIGHEST)
@ResourceType(name = "AvatarTalentExcelConfigData.json", loadPriority = LoadPriority.HIGHEST) public class AvatarTalentData extends GameResource {
public class AvatarTalentData extends GameResource { private int talentId;
private int talentId; private int prevTalent;
private int prevTalent; private long nameTextMapHash;
private long nameTextMapHash; private String icon;
private String icon; private int mainCostItemId;
private int mainCostItemId; private int mainCostItemCount;
private int mainCostItemCount; private String openConfig;
private String openConfig; private FightPropData[] addProps;
private FightPropData[] addProps; private float[] paramList;
private float[] paramList;
@Override
@Override public int getId() {
public int getId() { return this.talentId;
return this.talentId; }
}
public int PrevTalent() {
public int PrevTalent() { return prevTalent;
return prevTalent; }
}
public long getNameTextMapHash() {
public long getNameTextMapHash() { return nameTextMapHash;
return nameTextMapHash; }
}
public String getIcon() {
public String getIcon() { return icon;
return icon; }
}
public int getMainCostItemId() {
public int getMainCostItemId() { return mainCostItemId;
return mainCostItemId; }
}
public int getMainCostItemCount() {
public int getMainCostItemCount() { return mainCostItemCount;
return mainCostItemCount; }
}
public String getOpenConfig() {
public String getOpenConfig() { return openConfig;
return openConfig; }
}
public FightPropData[] getAddProps() {
public FightPropData[] getAddProps() { return addProps;
return addProps; }
}
public float[] getParamList() {
public float[] getParamList() { return paramList;
return paramList; }
}
@Override
@Override public void onLoad() {
public void onLoad() { ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length);
ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length); for (FightPropData prop : getAddProps()) {
for (FightPropData prop : getAddProps()) { if (prop.getPropType() != null || prop.getValue() == 0f) {
if (prop.getPropType() != null || prop.getValue() == 0f) { prop.onLoad();
prop.onLoad(); parsed.add(prop);
parsed.add(prop); }
} }
} this.addProps = parsed.toArray(new FightPropData[parsed.size()]);
this.addProps = parsed.toArray(new FightPropData[parsed.size()]); }
} }
}

View File

@ -1,69 +1,74 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.BattlePassMissionRefreshType; import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.MissionStatus; import emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.MissionStatus;
import lombok.Getter; import java.util.Arrays;
import java.util.Set;
import java.util.Arrays; import java.util.stream.Collectors;
import java.util.Set; import lombok.Getter;
import java.util.stream.Collectors;
@ResourceType(name = {"BattlePassMissionExcelConfigData.json"})
@ResourceType(name = {"BattlePassMissionExcelConfigData.json"}) @Getter
@Getter public class BattlePassMissionData extends GameResource {
public class BattlePassMissionData extends GameResource { @Getter(onMethod_ = @Override)
@Getter(onMethod_ = @Override) private int id;
private int id;
private int addPoint; private int addPoint;
private int scheduleId; private int scheduleId;
private int progress; private int progress;
private TriggerConfig triggerConfig; private TriggerConfig triggerConfig;
private BattlePassMissionRefreshType refreshType; private BattlePassMissionRefreshType refreshType;
private transient Set<Integer> mainParams; private transient Set<Integer> mainParams;
public WatcherTriggerType getTriggerType() { public WatcherTriggerType getTriggerType() {
return this.getTriggerConfig().getTriggerType(); return this.getTriggerConfig().getTriggerType();
} }
public boolean isCycleRefresh() { public boolean isCycleRefresh() {
return getRefreshType() == null || getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE; return getRefreshType() == null
} || getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE;
public boolean isValidRefreshType() { }
return getRefreshType() == null ||
getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE || public boolean isValidRefreshType() {
getScheduleId() == 2701; return getRefreshType() == null
} || getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE
@Override || getScheduleId() == 2701;
public void onLoad() { }
if (this.getTriggerConfig() != null) {
var params = getTriggerConfig().getParamList()[0]; @Override
if ((params != null) && !params.isEmpty()) { public void onLoad() {
this.mainParams = Arrays.stream(params.split("[:;,]")).map(Integer::parseInt).collect(Collectors.toSet()); if (this.getTriggerConfig() != null) {
} var params = getTriggerConfig().getParamList()[0];
} if ((params != null) && !params.isEmpty()) {
} this.mainParams =
Arrays.stream(params.split("[:;,]")).map(Integer::parseInt).collect(Collectors.toSet());
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() { }
var protoBuilder = emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder(); }
}
protoBuilder
.setMissionId(getId()) public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
.setTotalProgress(this.getProgress()) var protoBuilder =
.setRewardBattlePassPoint(this.getAddPoint()) emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
.setMissionStatus(MissionStatus.MISSION_STATUS_UNFINISHED)
.setMissionType(this.getRefreshType() == null ? 0 : this.getRefreshType().getValue()); protoBuilder
.setMissionId(getId())
return protoBuilder.build(); .setTotalProgress(this.getProgress())
} .setRewardBattlePassPoint(this.getAddPoint())
.setMissionStatus(MissionStatus.MISSION_STATUS_UNFINISHED)
@Getter .setMissionType(this.getRefreshType() == null ? 0 : this.getRefreshType().getValue());
public static class TriggerConfig {
private WatcherTriggerType triggerType; return protoBuilder.build();
private String[] paramList; }
}
} @Getter
public static class TriggerConfig {
private WatcherTriggerType triggerType;
private String[] paramList;
}
}

View File

@ -1,27 +1,25 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import lombok.Getter; import java.util.List;
import lombok.Getter;
import java.util.List;
@ResourceType(name = "BattlePassRewardExcelConfigData.json")
@ResourceType(name = "BattlePassRewardExcelConfigData.json") @Getter
@Getter public class BattlePassRewardData extends GameResource {
public class BattlePassRewardData extends GameResource { private int indexId;
private int indexId; private int level;
private int level; private List<Integer> freeRewardIdList;
private List<Integer> freeRewardIdList; private List<Integer> paidRewardIdList;
private List<Integer> paidRewardIdList;
@Override
@Override public int getId() {
public int getId() { // Reward ID is a combination of index and level.
// Reward ID is a combination of index and level. // We do this to get a unique ID.
// We do this to get a unique ID. return this.indexId * 100 + this.level;
return this.indexId * 100 + this.level; }
}
@Override
@Override public void onLoad() {}
public void onLoad() { }
}
}

View File

@ -1,45 +1,46 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import lombok.Getter; import java.util.List;
import lombok.Getter;
import java.util.List;
@ResourceType(name = "BlossomRefreshExcelConfigData.json")
@ResourceType(name = "BlossomRefreshExcelConfigData.json") @Getter
@Getter public class BlossomRefreshExcelConfigData extends GameResource {
public class BlossomRefreshExcelConfigData extends GameResource { @Getter(onMethod_ = @Override)
@Getter(onMethod_ = @Override) private int id;
private int id; // Map details
// Map details private long nameTextMapHash;
private long nameTextMapHash; private long descTextMapHash;
private long descTextMapHash; private String icon;
private String icon; private String clientShowType; // BLOSSOM_SHOWTYPE_CHALLENGE, BLOSSOM_SHOWTYPE_NPCTALK
private String clientShowType; // BLOSSOM_SHOWTYPE_CHALLENGE, BLOSSOM_SHOWTYPE_NPCTALK
// Refresh details
// Refresh details private String refreshType; // Leyline blossoms, magical ore outcrops
private String refreshType; // Leyline blossoms, magical ore outcrops private int
private int refreshCount; // Number of entries to spawn at refresh (1 for each leyline type for each city, 4 for magical ore for each city) refreshCount; // Number of entries to spawn at refresh (1 for each leyline type for each city,
private String refreshTime; // Server time-of-day to refresh at // 4 for magical ore for each city)
private RefreshCond[] refreshCondVec; // AR requirements etc. private String refreshTime; // Server time-of-day to refresh at
private RefreshCond[] refreshCondVec; // AR requirements etc.
private int cityId;
private int blossomChestId; // 1 for mora, 2 for exp private int cityId;
private Drop[] dropVec; private int blossomChestId; // 1 for mora, 2 for exp
private Drop[] dropVec;
// Unknown details
// @Getter private int reviseLevel; // Unknown details
// @Getter private int campUpdateNeedCount; // Always 1 if specified // @Getter private int reviseLevel;
// @Getter private int campUpdateNeedCount; // Always 1 if specified
@Getter
public static class Drop { @Getter
int dropId; public static class Drop {
int previewReward; int dropId;
} int previewReward;
}
@Getter
public static class RefreshCond { @Getter
String type; public static class RefreshCond {
List<Integer> param; String type;
} List<Integer> param;
} }
}

View File

@ -1,27 +1,28 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.ServerBuffType; import emu.grasscutter.game.props.ServerBuffType;
import lombok.Getter; import lombok.Getter;
@ResourceType(name = "BuffExcelConfigData.json") @ResourceType(name = "BuffExcelConfigData.json")
@Getter @Getter
public class BuffData extends GameResource { public class BuffData extends GameResource {
private int groupId; private int groupId;
private int serverBuffId; private int serverBuffId;
private float time; private float time;
private boolean isPersistent; private boolean isPersistent;
private ServerBuffType serverBuffType; private ServerBuffType serverBuffType;
private String abilityName; private String abilityName;
private String modifierName; private String modifierName;
@Override @Override
public int getId() { public int getId() {
return this.serverBuffId; return this.serverBuffId;
} }
public void onLoad() { public void onLoad() {
this.serverBuffType = this.serverBuffType != null ? this.serverBuffType : ServerBuffType.SERVER_BUFF_NONE; this.serverBuffType =
} this.serverBuffType != null ? this.serverBuffType : ServerBuffType.SERVER_BUFF_NONE;
} }
}

View File

@ -1,32 +1,33 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import lombok.AccessLevel; import java.util.HashMap;
import lombok.Getter; import java.util.Map;
import lombok.Setter; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.Getter;
import lombok.Setter;
import java.util.HashMap; import lombok.experimental.FieldDefaults;
import java.util.Map;
@ResourceType(name = "ChapterExcelConfigData.json")
@ResourceType(name = "ChapterExcelConfigData.json") @Getter
@Getter @Setter // TODO: remove on next API break
@Setter // TODO: remove on next API break @FieldDefaults(level = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE) public class ChapterData extends GameResource {
public class ChapterData extends GameResource { // Why public? TODO: privatise next API break
// Why public? TODO: privatise next API break public static final Map<Integer, ChapterData> beginQuestChapterMap = new HashMap<>();
public static final Map<Integer, ChapterData> beginQuestChapterMap = new HashMap<>(); public static final Map<Integer, ChapterData> endQuestChapterMap = new HashMap<>();
public static final Map<Integer, ChapterData> endQuestChapterMap = new HashMap<>();
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
int id; int id;
int beginQuestId;
int endQuestId; int beginQuestId;
int needPlayerLevel; int endQuestId;
int needPlayerLevel;
@Override
public void onLoad() { @Override
beginQuestChapterMap.put(beginQuestId, this); public void onLoad() {
beginQuestChapterMap.put(endQuestId, this); beginQuestChapterMap.put(beginQuestId, this);
} beginQuestChapterMap.put(endQuestId, this);
} }
}

View File

@ -1,25 +1,24 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import lombok.AccessLevel; import java.util.List;
import lombok.Getter; import lombok.AccessLevel;
import lombok.Setter; import lombok.Getter;
import lombok.experimental.FieldDefaults; import lombok.Setter;
import lombok.experimental.FieldDefaults;
import java.util.List;
@ResourceType(name = "CityConfigData.json", loadPriority = ResourceType.LoadPriority.HIGH)
@ResourceType(name = "CityConfigData.json", loadPriority = ResourceType.LoadPriority.HIGH) @Getter
@Getter @Setter
@Setter @FieldDefaults(level = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE) public class CityData extends GameResource {
public class CityData extends GameResource { int cityId;
int cityId; int sceneId;
int sceneId; List<Integer> areaIdVec;
List<Integer> areaIdVec;
@Override
@Override public int getId() {
public int getId() { return this.cityId;
return this.cityId; }
} }
}

View File

@ -1,23 +1,27 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import lombok.Getter; import lombok.Getter;
@ResourceType(name = {"AnimalCodexExcelConfigData.json"}) @ResourceType(name = {"AnimalCodexExcelConfigData.json"})
@Getter @Getter
public class CodexAnimalData extends GameResource { public class CodexAnimalData extends GameResource {
@Getter(onMethod_ = @Override) @Getter(onMethod_ = @Override)
private int Id; private int Id;
private String type;
private int describeId; private String type;
private int sortOrder; private int describeId;
@SerializedName(value = "countType", alternate = {"OCCLHPBCDGL"}) private int sortOrder;
private CountType countType;
@SerializedName(
public enum CountType { value = "countType",
CODEX_COUNT_TYPE_KILL, alternate = {"OCCLHPBCDGL"})
CODEX_COUNT_TYPE_CAPTURE private CountType countType;
}
} public enum CountType {
CODEX_COUNT_TYPE_KILL,
CODEX_COUNT_TYPE_CAPTURE
}
}

View File

@ -1,41 +1,41 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
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;
@ResourceType(name = {"QuestCodexExcelConfigData.json"}) @ResourceType(name = {"QuestCodexExcelConfigData.json"})
public class CodexQuestData extends GameResource { public class CodexQuestData extends GameResource {
private int Id; private int Id;
private int parentQuestId; private int parentQuestId;
private int chapterId; private int chapterId;
private int sortOrder; private int sortOrder;
private boolean isDisuse; private boolean isDisuse;
public int getParentQuestId() { public int getParentQuestId() {
return parentQuestId; return parentQuestId;
} }
public int getId() { public int getId() {
return Id; return Id;
} }
public int getChapterId() { public int getChapterId() {
return chapterId; return chapterId;
} }
public int getSortOrder() { public int getSortOrder() {
return sortOrder; return sortOrder;
} }
public boolean getIsDisuse() { public boolean getIsDisuse() {
return isDisuse; return isDisuse;
} }
@Override @Override
public void onLoad() { public void onLoad() {
if (!this.getIsDisuse()) { if (!this.getIsDisuse()) {
GameData.getCodexQuestDataIdMap().put(this.getParentQuestId(), this); GameData.getCodexQuestDataIdMap().put(this.getParentQuestId(), this);
} }
} }
} }

View File

@ -1,56 +1,47 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
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 it.unimi.dsi.fastutil.ints.IntCollection; import it.unimi.dsi.fastutil.ints.IntCollection;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import lombok.Getter; import lombok.Getter;
@ResourceType(name = {"ReliquaryCodexExcelConfigData.json"}) @ResourceType(name = {"ReliquaryCodexExcelConfigData.json"})
public class CodexReliquaryData extends GameResource { public class CodexReliquaryData extends GameResource {
@Getter @Getter private int Id;
private int Id; @Getter private int suitId;
@Getter @Getter private int level;
private int suitId; @Getter private int cupId;
@Getter @Getter private int leatherId;
private int level; @Getter private int capId;
@Getter @Getter private int flowerId;
private int cupId; @Getter private int sandId;
@Getter @Getter private int sortOrder;
private int leatherId; private transient IntCollection ids;
@Getter
private int capId; public boolean containsId(int id) {
@Getter return getIds().contains(id);
private int flowerId; }
@Getter
private int sandId; public IntCollection getIds() {
@Getter if (this.ids == null) {
private int sortOrder; int[] idsArr = {cupId, leatherId, capId, flowerId, sandId};
private transient IntCollection ids; this.ids = IntList.of(idsArr);
}
public boolean containsId(int id) { return this.ids;
return getIds().contains(id); }
}
@Override
public IntCollection getIds() { public void onLoad() {
if (this.ids == null) { // Normalize all itemIds to the 0-substat form
int[] idsArr = {cupId, leatherId, capId, flowerId, sandId}; cupId = (cupId / 10) * 10;
this.ids = IntList.of(idsArr); leatherId = (leatherId / 10) * 10;
} capId = (capId / 10) * 10;
return this.ids; flowerId = (flowerId / 10) * 10;
} sandId = (sandId / 10) * 10;
@Override GameData.getCodexReliquaryArrayList().add(this);
public void onLoad() { GameData.getCodexReliquaryDataIdMap().put(getSuitId(), this);
// Normalize all itemIds to the 0-substat form }
cupId = (cupId / 10) * 10; }
leatherId = (leatherId / 10) * 10;
capId = (capId / 10) * 10;
flowerId = (flowerId / 10) * 10;
sandId = (sandId / 10) * 10;
GameData.getCodexReliquaryArrayList().add(this);
GameData.getCodexReliquaryDataIdMap().put(getSuitId(), this);
}
}

View File

@ -1,82 +1,82 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
import java.util.List; import java.util.stream.Collectors;
import java.util.stream.Collectors;
@ResourceType(name = "CombineExcelConfigData.json")
@ResourceType(name = "CombineExcelConfigData.json") public class CombineData extends GameResource {
public class CombineData extends GameResource {
private int combineId;
private int combineId; private int playerLevel;
private int playerLevel; private boolean isDefaultShow;
private boolean isDefaultShow; private int combineType;
private int combineType; private int subCombineType;
private int subCombineType; private int resultItemId;
private int resultItemId; private int resultItemCount;
private int resultItemCount; private int scoinCost;
private int scoinCost; private List<ItemParamData> randomItems;
private List<ItemParamData> randomItems; private List<ItemParamData> materialItems;
private List<ItemParamData> materialItems; private String recipeType;
private String recipeType;
@Override
@Override public int getId() {
public int getId() { return this.combineId;
return this.combineId; }
}
@Override
@Override public void onLoad() {
public void onLoad() { super.onLoad();
super.onLoad(); // clean data
// clean data randomItems =
randomItems = randomItems.stream().filter(item -> item.getId() > 0).collect(Collectors.toList()); randomItems.stream().filter(item -> item.getId() > 0).collect(Collectors.toList());
materialItems = materialItems.stream().filter(item -> item.getId() > 0).collect(Collectors.toList()); materialItems =
} materialItems.stream().filter(item -> item.getId() > 0).collect(Collectors.toList());
}
public int getCombineId() {
return combineId; public int getCombineId() {
} return combineId;
}
public int getPlayerLevel() {
return playerLevel; public int getPlayerLevel() {
} return playerLevel;
}
public boolean isDefaultShow() {
return isDefaultShow; public boolean isDefaultShow() {
} return isDefaultShow;
}
public int getCombineType() {
return combineType; public int getCombineType() {
} return combineType;
}
public int getSubCombineType() {
return subCombineType; public int getSubCombineType() {
} return subCombineType;
}
public int getResultItemId() {
return resultItemId; public int getResultItemId() {
} return resultItemId;
}
public int getResultItemCount() {
return resultItemCount; public int getResultItemCount() {
} return resultItemCount;
}
public int getScoinCost() {
return scoinCost; public int getScoinCost() {
} return scoinCost;
}
public List<ItemParamData> getRandomItems() {
return randomItems; public List<ItemParamData> getRandomItems() {
} return randomItems;
}
public List<ItemParamData> getMaterialItems() {
return materialItems; public List<ItemParamData> getMaterialItems() {
} return materialItems;
}
public String getRecipeType() {
return recipeType; public String getRecipeType() {
} return recipeType;
}
} }

View File

@ -1,22 +1,24 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import lombok.Getter; import java.util.List;
import lombok.Getter;
import java.util.List;
@ResourceType(
@ResourceType(name = {"CompoundExcelConfigData.json"}, loadPriority = ResourceType.LoadPriority.LOW) name = {"CompoundExcelConfigData.json"},
@Getter loadPriority = ResourceType.LoadPriority.LOW)
public class CompoundData extends GameResource { @Getter
@Getter(onMethod_ = @Override) public class CompoundData extends GameResource {
private int id; @Getter(onMethod_ = @Override)
private int groupID; private int id;
private int rankLevel;
private boolean isDefaultUnlocked; private int groupID;
private int costTime; private int rankLevel;
private int queueSize; private boolean isDefaultUnlocked;
private List<ItemParamData> inputVec; private int costTime;
private List<ItemParamData> outputVec; private int queueSize;
} private List<ItemParamData> inputVec;
private List<ItemParamData> outputVec;
}

View File

@ -1,42 +1,43 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority; import emu.grasscutter.data.ResourceType.LoadPriority;
@ResourceType(name = {"CookBonusExcelConfigData.json"}, loadPriority = LoadPriority.LOW) @ResourceType(
public class CookBonusData extends GameResource { name = {"CookBonusExcelConfigData.json"},
private int avatarId; loadPriority = LoadPriority.LOW)
private int recipeId; public class CookBonusData extends GameResource {
private int[] paramVec; private int avatarId;
private int[] complexParamVec; private int recipeId;
private int[] paramVec;
@Override private int[] complexParamVec;
public int getId() {
return this.avatarId; @Override
} public int getId() {
return this.avatarId;
public int getAvatarId() { }
return avatarId;
} public int getAvatarId() {
return avatarId;
public int getRecipeId() { }
return recipeId;
} public int getRecipeId() {
return recipeId;
public int[] getParamVec() { }
return paramVec;
} public int[] getParamVec() {
return paramVec;
public int[] getComplexParamVec() { }
return complexParamVec;
} public int[] getComplexParamVec() {
return complexParamVec;
public int getReplacementItemId() { }
return this.paramVec[0];
} public int getReplacementItemId() {
return this.paramVec[0];
@Override }
public void onLoad() {
} @Override
} public void onLoad() {}
}

Some files were not shown because too many files have changed in this diff Show More