Format code [skip actions]

This commit is contained in:
github-actions 2023-05-11 02:23:43 +00:00
parent f51fd55cb5
commit f9906c4492
730 changed files with 29212 additions and 29159 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,354 +1,354 @@
package emu.grasscutter.command;
import static emu.grasscutter.config.Configuration.SERVER;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.*;
import org.reflections.Reflections;
@SuppressWarnings({"UnusedReturnValue", "unused"})
public final class CommandMap {
private static final int INVALID_UID = Integer.MIN_VALUE;
private static final String consoleId = "console";
private final Map<String, CommandHandler> commands = new TreeMap<>();
private final Map<String, CommandHandler> aliases = new TreeMap<>();
private final Map<String, Command> annotations = new TreeMap<>();
private final Object2IntMap<String> targetPlayerIds = new Object2IntOpenHashMap<>();
public CommandMap() {
this(false);
}
public CommandMap(boolean scan) {
if (scan) this.scan();
}
public static CommandMap getInstance() {
return Grasscutter.getCommandMap();
}
private static int getUidFromString(String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException ignored) {
var account = DatabaseHelper.getAccountByName(input);
if (account == null) return INVALID_UID;
var player = DatabaseHelper.getPlayerByAccount(account);
if (player == null) return INVALID_UID;
// We will be immediately fetching the player again after this,
// but offline vs online Player safety is more important than saving a lookup
return player.getUid();
}
}
/**
* Register a command handler.
*
* @param label The command label.
* @param command The command handler.
* @return Instance chaining.
*/
public CommandMap registerCommand(String label, CommandHandler command) {
Grasscutter.getLogger().debug("Registered command: " + label);
label = label.toLowerCase();
// Get command data.
Command annotation = command.getClass().getAnnotation(Command.class);
this.annotations.put(label, annotation);
this.commands.put(label, command);
// Register aliases.
for (String alias : annotation.aliases()) {
this.aliases.put(alias, command);
this.annotations.put(alias, annotation);
}
return this;
}
/**
* Removes a registered command handler.
*
* @param label The command label.
* @return Instance chaining.
*/
public CommandMap unregisterCommand(String label) {
Grasscutter.getLogger().debug("Unregistered command: " + label);
CommandHandler handler = this.commands.get(label);
if (handler == null) return this;
Command annotation = handler.getClass().getAnnotation(Command.class);
this.annotations.remove(label);
this.commands.remove(label);
// Unregister aliases.
for (String alias : annotation.aliases()) {
this.aliases.remove(alias);
this.annotations.remove(alias);
}
return this;
}
public List<Command> getAnnotationsAsList() {
return new ArrayList<>(this.annotations.values());
}
public Map<String, Command> getAnnotations() {
return new LinkedHashMap<>(this.annotations);
}
/**
* Returns a list of all registered commands.
*
* @return All command handlers as a list.
*/
public List<CommandHandler> getHandlersAsList() {
return new ArrayList<>(this.commands.values());
}
public Map<String, CommandHandler> getHandlers() {
return this.commands;
}
/**
* Returns a handler by label/alias.
*
* @param label The command label.
* @return The command handler.
*/
public CommandHandler getHandler(String label) {
CommandHandler handler = this.commands.get(label);
if (handler == null) {
// Try getting by alias
handler = this.aliases.get(label);
}
return handler;
}
private Player getTargetPlayer(
String playerId, Player player, Player targetPlayer, List<String> args) {
// Top priority: If any @UID argument is present, override targetPlayer with it.
for (int i = 0; i < args.size(); i++) {
String arg = args.get(i);
if (arg.startsWith("@")) {
arg = args.remove(i).substring(1);
if (arg.equals("")) {
// 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.
return null;
}
int uid = getUidFromString(arg);
if (uid == INVALID_UID) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
throw new IllegalArgumentException();
}
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
throw new IllegalArgumentException();
}
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.
if (targetPlayer != null) {
return targetPlayer;
}
// Next priority: Use previously-set target. (see /target [[@]UID])
if (targetPlayerIds.containsKey(playerId)) {
targetPlayer =
Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.getInt(playerId), true);
// We check every time in case the target is deleted after being targeted
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
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;
}
private boolean setPlayerTarget(String playerId, Player player, String targetUid) {
if (targetUid.equals("")) { // Clears the default targetPlayer.
targetPlayerIds.removeInt(playerId);
CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
return true;
}
// Sets default targetPlayer to the UID provided.
int uid = getUidFromString(targetUid);
if (uid == INVALID_UID) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
return false;
}
Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
return false;
}
targetPlayerIds.put(playerId, uid);
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);
return true;
}
/**
* Invoke a command handler with the given arguments.
*
* @param player The player invoking the command or null for the server console.
* @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})]
if (SERVER.logCommands) {
if (player != null) {
Grasscutter.getLogger()
.info(
"Command used by ["
+ player.getAccount().getUsername()
+ " (Player UID: "
+ player.getUid()
+ ")]: "
+ rawMessage);
} else {
Grasscutter.getLogger().info("Command used by server console: " + rawMessage);
}
}
rawMessage = rawMessage.trim();
if (rawMessage.length() == 0) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.not_specified");
return;
}
// Parse message.
String[] split = rawMessage.split(" ");
String label = split[0].toLowerCase();
List<String> args = new ArrayList<>(Arrays.asList(split).subList(1, split.length));
String playerId = (player == null) ? consoleId : player.getAccount().getId();
// Check for special cases - currently only target command.
if (label.startsWith("@")) { // @[UID]
this.setPlayerTarget(playerId, player, label.substring(1));
return;
} else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
if (args.size() > 0) {
String targetUidStr = args.get(0);
if (targetUidStr.startsWith("@")) {
targetUidStr = targetUidStr.substring(1);
}
this.setPlayerTarget(playerId, player, targetUidStr);
return;
} else {
this.setPlayerTarget(playerId, player, "");
return;
}
}
// Get command handler.
CommandHandler handler = this.getHandler(label);
// Check if the handler is null.
if (handler == null) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label);
return;
}
// Get the command's annotation.
Command annotation = this.annotations.get(label);
// Resolve targetPlayer
try {
targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args);
} catch (IllegalArgumentException e) {
return;
}
// Check for permissions.
if (!Grasscutter.getPermissionHandler()
.checkPermission(
player,
targetPlayer,
annotation.permission(),
this.annotations.get(label).permissionTargeted())) {
return;
}
// Check if command has unfulfilled constraints on targetPlayer
Command.TargetRequirement targetRequirement = annotation.targetRequirement();
if (targetRequirement != Command.TargetRequirement.NONE) {
if (targetPlayer == null) {
handler.sendUsageMessage(player);
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target");
return;
}
if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) {
handler.sendUsageMessage(player);
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online");
return;
}
if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) {
handler.sendUsageMessage(player);
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline");
return;
}
}
// Copy player and handler to final properties.
final Player targetPlayerF = targetPlayer; // Is there a better way to do this?
final CommandHandler handlerF = handler; // Is there a better way to do this?
// Invoke execute method for handler.
Runnable runnable = () -> handlerF.execute(player, targetPlayerF, args);
if (annotation.threading()) {
new Thread(runnable).start();
} 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);
}
});
}
}
package emu.grasscutter.command;
import static emu.grasscutter.config.Configuration.SERVER;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.*;
import org.reflections.Reflections;
@SuppressWarnings({"UnusedReturnValue", "unused"})
public final class CommandMap {
private static final int INVALID_UID = Integer.MIN_VALUE;
private static final String consoleId = "console";
private final Map<String, CommandHandler> commands = new TreeMap<>();
private final Map<String, CommandHandler> aliases = new TreeMap<>();
private final Map<String, Command> annotations = new TreeMap<>();
private final Object2IntMap<String> targetPlayerIds = new Object2IntOpenHashMap<>();
public CommandMap() {
this(false);
}
public CommandMap(boolean scan) {
if (scan) this.scan();
}
public static CommandMap getInstance() {
return Grasscutter.getCommandMap();
}
private static int getUidFromString(String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException ignored) {
var account = DatabaseHelper.getAccountByName(input);
if (account == null) return INVALID_UID;
var player = DatabaseHelper.getPlayerByAccount(account);
if (player == null) return INVALID_UID;
// We will be immediately fetching the player again after this,
// but offline vs online Player safety is more important than saving a lookup
return player.getUid();
}
}
/**
* Register a command handler.
*
* @param label The command label.
* @param command The command handler.
* @return Instance chaining.
*/
public CommandMap registerCommand(String label, CommandHandler command) {
Grasscutter.getLogger().debug("Registered command: " + label);
label = label.toLowerCase();
// Get command data.
Command annotation = command.getClass().getAnnotation(Command.class);
this.annotations.put(label, annotation);
this.commands.put(label, command);
// Register aliases.
for (String alias : annotation.aliases()) {
this.aliases.put(alias, command);
this.annotations.put(alias, annotation);
}
return this;
}
/**
* Removes a registered command handler.
*
* @param label The command label.
* @return Instance chaining.
*/
public CommandMap unregisterCommand(String label) {
Grasscutter.getLogger().debug("Unregistered command: " + label);
CommandHandler handler = this.commands.get(label);
if (handler == null) return this;
Command annotation = handler.getClass().getAnnotation(Command.class);
this.annotations.remove(label);
this.commands.remove(label);
// Unregister aliases.
for (String alias : annotation.aliases()) {
this.aliases.remove(alias);
this.annotations.remove(alias);
}
return this;
}
public List<Command> getAnnotationsAsList() {
return new ArrayList<>(this.annotations.values());
}
public Map<String, Command> getAnnotations() {
return new LinkedHashMap<>(this.annotations);
}
/**
* Returns a list of all registered commands.
*
* @return All command handlers as a list.
*/
public List<CommandHandler> getHandlersAsList() {
return new ArrayList<>(this.commands.values());
}
public Map<String, CommandHandler> getHandlers() {
return this.commands;
}
/**
* Returns a handler by label/alias.
*
* @param label The command label.
* @return The command handler.
*/
public CommandHandler getHandler(String label) {
CommandHandler handler = this.commands.get(label);
if (handler == null) {
// Try getting by alias
handler = this.aliases.get(label);
}
return handler;
}
private Player getTargetPlayer(
String playerId, Player player, Player targetPlayer, List<String> args) {
// Top priority: If any @UID argument is present, override targetPlayer with it.
for (int i = 0; i < args.size(); i++) {
String arg = args.get(i);
if (arg.startsWith("@")) {
arg = args.remove(i).substring(1);
if (arg.equals("")) {
// 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.
return null;
}
int uid = getUidFromString(arg);
if (uid == INVALID_UID) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
throw new IllegalArgumentException();
}
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
throw new IllegalArgumentException();
}
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.
if (targetPlayer != null) {
return targetPlayer;
}
// Next priority: Use previously-set target. (see /target [[@]UID])
if (targetPlayerIds.containsKey(playerId)) {
targetPlayer =
Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.getInt(playerId), true);
// We check every time in case the target is deleted after being targeted
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
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;
}
private boolean setPlayerTarget(String playerId, Player player, String targetUid) {
if (targetUid.equals("")) { // Clears the default targetPlayer.
targetPlayerIds.removeInt(playerId);
CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
return true;
}
// Sets default targetPlayer to the UID provided.
int uid = getUidFromString(targetUid);
if (uid == INVALID_UID) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid");
return false;
}
Player targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid, true);
if (targetPlayer == null) {
CommandHandler.sendTranslatedMessage(player, "commands.execution.player_exist_error");
return false;
}
targetPlayerIds.put(playerId, uid);
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);
return true;
}
/**
* Invoke a command handler with the given arguments.
*
* @param player The player invoking the command or null for the server console.
* @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})]
if (SERVER.logCommands) {
if (player != null) {
Grasscutter.getLogger()
.info(
"Command used by ["
+ player.getAccount().getUsername()
+ " (Player UID: "
+ player.getUid()
+ ")]: "
+ rawMessage);
} else {
Grasscutter.getLogger().info("Command used by server console: " + rawMessage);
}
}
rawMessage = rawMessage.trim();
if (rawMessage.length() == 0) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.not_specified");
return;
}
// Parse message.
String[] split = rawMessage.split(" ");
String label = split[0].toLowerCase();
List<String> args = new ArrayList<>(Arrays.asList(split).subList(1, split.length));
String playerId = (player == null) ? consoleId : player.getAccount().getId();
// Check for special cases - currently only target command.
if (label.startsWith("@")) { // @[UID]
this.setPlayerTarget(playerId, player, label.substring(1));
return;
} else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
if (args.size() > 0) {
String targetUidStr = args.get(0);
if (targetUidStr.startsWith("@")) {
targetUidStr = targetUidStr.substring(1);
}
this.setPlayerTarget(playerId, player, targetUidStr);
return;
} else {
this.setPlayerTarget(playerId, player, "");
return;
}
}
// Get command handler.
CommandHandler handler = this.getHandler(label);
// Check if the handler is null.
if (handler == null) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label);
return;
}
// Get the command's annotation.
Command annotation = this.annotations.get(label);
// Resolve targetPlayer
try {
targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args);
} catch (IllegalArgumentException e) {
return;
}
// Check for permissions.
if (!Grasscutter.getPermissionHandler()
.checkPermission(
player,
targetPlayer,
annotation.permission(),
this.annotations.get(label).permissionTargeted())) {
return;
}
// Check if command has unfulfilled constraints on targetPlayer
Command.TargetRequirement targetRequirement = annotation.targetRequirement();
if (targetRequirement != Command.TargetRequirement.NONE) {
if (targetPlayer == null) {
handler.sendUsageMessage(player);
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target");
return;
}
if ((targetRequirement == Command.TargetRequirement.ONLINE) && !targetPlayer.isOnline()) {
handler.sendUsageMessage(player);
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_online");
return;
}
if ((targetRequirement == Command.TargetRequirement.OFFLINE) && targetPlayer.isOnline()) {
handler.sendUsageMessage(player);
CommandHandler.sendTranslatedMessage(player, "commands.execution.need_target_offline");
return;
}
}
// Copy player and handler to final properties.
final Player targetPlayerF = targetPlayer; // Is there a better way to do this?
final CommandHandler handlerF = handler; // Is there a better way to do this?
// Invoke execute method for handler.
Runnable runnable = () -> handlerF.execute(player, targetPlayerF, args);
if (annotation.threading()) {
new Thread(runnable).start();
} 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,33 +1,33 @@
package emu.grasscutter.command;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
public class DefaultPermissionHandler implements PermissionHandler {
@Override
public boolean EnablePermissionCommand() {
return true;
}
@Override
public boolean checkPermission(
Player player, Player targetPlayer, String permissionNode, String permissionNodeTargeted) {
if (player == null) {
return true;
}
Account account = player.getAccount();
if (player != targetPlayer) { // Additional permission required for targeting another player
if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
return false;
}
}
if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
return false;
}
return true;
}
}
package emu.grasscutter.command;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
public class DefaultPermissionHandler implements PermissionHandler {
@Override
public boolean EnablePermissionCommand() {
return true;
}
@Override
public boolean checkPermission(
Player player, Player targetPlayer, String permissionNode, String permissionNodeTargeted) {
if (player == null) {
return true;
}
Account account = player.getAccount();
if (player != targetPlayer) { // Additional permission required for targeting another player
if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
return false;
}
}
if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.permission_error");
return false;
}
return true;
}
}

View File

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

View File

@ -47,7 +47,7 @@ public final class AccountCommand implements CommandHandler {
if (Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) {
if (args.size() < 3) {
CommandHandler.sendMessage(
sender, "EXPERIMENTAL_RealPassword requires a password argument");
sender, "EXPERIMENTAL_RealPassword requires a password argument");
CommandHandler.sendMessage(sender, "Usage: account create <username> <password> [uid]");
return;
}
@ -60,10 +60,10 @@ public final class AccountCommand implements CommandHandler {
CommandHandler.sendMessage(sender, translate(sender, "commands.account.invalid"));
if (Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) {
CommandHandler.sendMessage(
sender,
"EXPERIMENTAL_RealPassword requires argument 2 to be a password, not a uid");
sender,
"EXPERIMENTAL_RealPassword requires argument 2 to be a password, not a uid");
CommandHandler.sendMessage(
sender, "Usage: account create <username> <password> [uid]");
sender, "Usage: account create <username> <password> [uid]");
}
return;
}
@ -90,7 +90,7 @@ public final class AccountCommand implements CommandHandler {
account.save(); // Save account to database.
CommandHandler.sendMessage(
sender, translate(sender, "commands.account.create", account.getReservedPlayerUid()));
sender, translate(sender, "commands.account.create", account.getReservedPlayerUid()));
}
}
case "delete" -> {
@ -106,7 +106,7 @@ public final class AccountCommand implements CommandHandler {
case "resetpass" -> {
if (!Configuration.ACCOUNT.EXPERIMENTAL_RealPassword) {
CommandHandler.sendMessage(
sender, "resetpass requires EXPERIMENTAL_RealPassword to be true.");
sender, "resetpass requires EXPERIMENTAL_RealPassword to be true.");
return;
}
if (args.size() != 3) {
@ -128,23 +128,27 @@ public final class AccountCommand implements CommandHandler {
}
case "list" -> {
CommandHandler.sendMessage(sender, "Note: This command might take a while to complete.");
CommandHandler.sendMessage(sender,
"Accounts: \n" + DatabaseManager.getAccountDatastore()
.find(Account.class).stream()
.map(acc -> "%s: %s (%s)".formatted(
acc.getId(), acc.getUsername(),
acc.getReservedPlayerUid() == 0 ?
this.getPlayerUid(acc) :
acc.getReservedPlayerUid()))
.collect(Collectors.joining("\n"))
);
CommandHandler.sendMessage(
sender,
"Accounts: \n"
+ DatabaseManager.getAccountDatastore().find(Account.class).stream()
.map(
acc ->
"%s: %s (%s)"
.formatted(
acc.getId(),
acc.getUsername(),
acc.getReservedPlayerUid() == 0
? this.getPlayerUid(acc)
: acc.getReservedPlayerUid()))
.collect(Collectors.joining("\n")));
}
}
}
/**
* Returns the UID of the player associated with the given account.
* If the player is not found, returns "no UID".
* Returns the UID of the player associated with the given account. If the player is not found,
* returns "no UID".
*
* @param account The account to get the UID of.
* @return The UID of the player associated with the given account.

View File

@ -1,78 +1,78 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketServerAnnounceNotify;
import java.util.Collections;
import java.util.List;
import java.util.Random;
@Command(
label = "announce",
usage = {"<content>", "refresh", "(tpl|revoke) <templateId>"},
permission = "server.announce",
aliases = {"a"},
targetRequirement = Command.TargetRequirement.NONE)
public final class AnnounceCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
var manager = Grasscutter.getGameServer().getAnnouncementSystem();
if (args.size() < 1) {
sendUsageMessage(sender);
return;
}
switch (args.get(0)) {
case "tpl":
if (args.size() < 2) {
sendUsageMessage(sender);
return;
}
var templateId = Integer.parseInt(args.get(1));
var tpl = manager.getAnnounceConfigItemMap().get(templateId);
if (tpl == null) {
CommandHandler.sendMessage(
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()));
break;
case "refresh":
var num = manager.refresh();
CommandHandler.sendMessage(
sender, translate(sender, "commands.announce.refresh_success", num));
break;
case "revoke":
if (args.size() < 2) {
sendUsageMessage(sender);
return;
}
var templateId1 = Integer.parseInt(args.get(1));
manager.revoke(templateId1);
CommandHandler.sendMessage(
sender, translate(sender, "commands.announce.revoke_done", templateId1));
break;
default:
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));
}
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketServerAnnounceNotify;
import java.util.Collections;
import java.util.List;
import java.util.Random;
@Command(
label = "announce",
usage = {"<content>", "refresh", "(tpl|revoke) <templateId>"},
permission = "server.announce",
aliases = {"a"},
targetRequirement = Command.TargetRequirement.NONE)
public final class AnnounceCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
var manager = Grasscutter.getGameServer().getAnnouncementSystem();
if (args.size() < 1) {
sendUsageMessage(sender);
return;
}
switch (args.get(0)) {
case "tpl":
if (args.size() < 2) {
sendUsageMessage(sender);
return;
}
var templateId = Integer.parseInt(args.get(1));
var tpl = manager.getAnnounceConfigItemMap().get(templateId);
if (tpl == null) {
CommandHandler.sendMessage(
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()));
break;
case "refresh":
var num = manager.refresh();
CommandHandler.sendMessage(
sender, translate(sender, "commands.announce.refresh_success", num));
break;
case "revoke":
if (args.size() < 2) {
sendUsageMessage(sender);
return;
}
var templateId1 = Integer.parseInt(args.get(1));
manager.revoke(templateId1);
CommandHandler.sendMessage(
sender, translate(sender, "commands.announce.revoke_done", templateId1));
break;
default:
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,62 +1,62 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameSession;
import java.util.List;
@Command(
label = "ban",
usage = {"[<time> [<reason>]]"},
permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER)
public final class BanCommand implements CommandHandler {
private boolean banAccount(Player targetPlayer, int time, String reason) {
Account account = targetPlayer.getAccount();
if (account == null) {
return false;
}
account.setBanReason(reason);
account.setBanEndTime(time);
account.setBanStartTime((int) System.currentTimeMillis() / 1000);
account.setBanned(true);
account.save();
GameSession session = targetPlayer.getSession();
if (session != null) {
session.close();
}
return true;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
int time = 2051190000;
String reason = "Reason not specified.";
switch (args.size()) {
case 2:
reason = args.get(1); // Fall-through
case 1:
try {
time = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.invalid_time");
return;
} // Fall-through, unimportant
default:
break;
}
if (banAccount(targetPlayer, time, reason)) {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.success");
} else {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.failure");
}
}
}
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameSession;
import java.util.List;
@Command(
label = "ban",
usage = {"[<time> [<reason>]]"},
permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER)
public final class BanCommand implements CommandHandler {
private boolean banAccount(Player targetPlayer, int time, String reason) {
Account account = targetPlayer.getAccount();
if (account == null) {
return false;
}
account.setBanReason(reason);
account.setBanEndTime(time);
account.setBanStartTime((int) System.currentTimeMillis() / 1000);
account.setBanned(true);
account.save();
GameSession session = targetPlayer.getSession();
if (session != null) {
session.close();
}
return true;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
int time = 2051190000;
String reason = "Reason not specified.";
switch (args.size()) {
case 2:
reason = args.get(1); // Fall-through
case 1:
try {
time = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.invalid_time");
return;
} // Fall-through, unimportant
default:
break;
}
if (banAccount(targetPlayer, time, reason)) {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.success");
} else {
CommandHandler.sendTranslatedMessage(sender, "commands.ban.failure");
}
}
}

View File

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

View File

@ -1,57 +1,57 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "coop",
usage = {"[<host UID>]"},
permission = "server.coop",
permissionTargeted = "server.coop.others")
public final class CoopCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Player host = sender;
switch (args.size()) {
case 0: // Summon target to self
if (sender == null) { // Console doesn't have a self to summon to
sendUsageMessage(sender);
return;
}
break;
case 1: // Summon target to argument
try {
int hostId = Integer.parseInt(args.get(0));
host = Grasscutter.getGameServer().getPlayerByUid(hostId);
if (host == null) {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.player_offline_error");
return;
}
break;
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.uid");
return;
}
default:
sendUsageMessage(sender);
return;
}
// There's no target==host check but this just places them in multiplayer in their own world
// which seems fine.
if (targetPlayer.isInMultiplayer()) {
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());
}
}
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "coop",
usage = {"[<host UID>]"},
permission = "server.coop",
permissionTargeted = "server.coop.others")
public final class CoopCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Player host = sender;
switch (args.size()) {
case 0: // Summon target to self
if (sender == null) { // Console doesn't have a self to summon to
sendUsageMessage(sender);
return;
}
break;
case 1: // Summon target to argument
try {
int hostId = Integer.parseInt(args.get(0));
host = Grasscutter.getGameServer().getPlayerByUid(hostId);
if (host == null) {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.player_offline_error");
return;
}
break;
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.uid");
return;
}
default:
sendUsageMessage(sender);
return;
}
// There's no target==host check but this just places them in multiplayer in their own world
// which seems fine.
if (targetPlayer.isInMultiplayer()) {
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,50 +1,50 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "enter_dungeon",
aliases = {"enterdungeon", "dungeon"},
usage = {"<dungeonId>"},
permission = "player.enterdungeon",
permissionTargeted = "player.enterdungeon.others")
public final class EnterDungeonCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
sendUsageMessage(sender);
return;
}
try {
int dungeonId = Integer.parseInt(args.get(0));
if (dungeonId == targetPlayer.getSceneId()) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.enter_dungeon.in_dungeon_error"));
return;
}
boolean result =
targetPlayer
.getServer()
.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);
}
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "enter_dungeon",
aliases = {"enterdungeon", "dungeon"},
usage = {"<dungeonId>"},
permission = "player.enterdungeon",
permissionTargeted = "player.enterdungeon.others")
public final class EnterDungeonCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
sendUsageMessage(sender);
return;
}
try {
int dungeonId = Integer.parseInt(args.get(0));
if (dungeonId == targetPlayer.getSceneId()) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.enter_dungeon.in_dungeon_error"));
return;
}
boolean result =
targetPlayer
.getServer()
.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,11 +1,14 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.GameConstants.*;
import static emu.grasscutter.command.CommandHelpers.*;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.excels.avatar.AvatarData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.avatar.AvatarData;
import emu.grasscutter.data.excels.reliquary.ReliquaryAffixData;
import emu.grasscutter.data.excels.reliquary.ReliquaryMainPropData;
import emu.grasscutter.game.avatar.Avatar;
@ -14,45 +17,50 @@ import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.utils.SparseSet;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
import static emu.grasscutter.GameConstants.*;
import static emu.grasscutter.command.CommandHelpers.*;
import lombok.Setter;
@Command(
label = "give",
aliases = {"g", "item", "giveitem"},
usage = {
"(<itemId>|<avatarId>|all|weapons|mats|avatars) [lv<level>] [r<refinement>] [x<amount>] [c<constellation>] [sl<skilllevel>]",
"<artifactId> [lv<level>] [x<amount>] [<mainPropId>] [<appendPropId>[,<times>]]..."},
permission = "player.give",
permissionTargeted = "player.give.others",
threading = true)
label = "give",
aliases = {"g", "item", "giveitem"},
usage = {
"(<itemId>|<avatarId>|all|weapons|mats|avatars) [lv<level>] [r<refinement>] [x<amount>] [c<constellation>] [sl<skilllevel>]",
"<artifactId> [lv<level>] [x<amount>] [<mainPropId>] [<appendPropId>[,<times>]]..."
},
permission = "player.give",
permissionTargeted = "player.give.others",
threading = true)
public final class GiveCommand implements CommandHandler {
private static final Map<Pattern, BiConsumer<GiveItemParameters, Integer>> intCommandHandlers = Map.ofEntries(
Map.entry(lvlRegex, GiveItemParameters::setLvl),
Map.entry(refineRegex, GiveItemParameters::setRefinement),
Map.entry(amountRegex, GiveItemParameters::setAmount),
Map.entry(constellationRegex, GiveItemParameters::setConstellation),
Map.entry(skillLevelRegex, GiveItemParameters::setSkillLevel)
);
private static final Map<Pattern, BiConsumer<GiveItemParameters, Integer>> intCommandHandlers =
Map.ofEntries(
Map.entry(lvlRegex, GiveItemParameters::setLvl),
Map.entry(refineRegex, GiveItemParameters::setRefinement),
Map.entry(amountRegex, GiveItemParameters::setAmount),
Map.entry(constellationRegex, GiveItemParameters::setConstellation),
Map.entry(skillLevelRegex, GiveItemParameters::setSkillLevel));
private static Avatar makeAvatar(GiveItemParameters param) {
return makeAvatar(param.avatarData, param.lvl, Avatar.getMinPromoteLevel(param.lvl), param.constellation, param.skillLevel);
return makeAvatar(
param.avatarData,
param.lvl,
Avatar.getMinPromoteLevel(param.lvl),
param.constellation,
param.skillLevel);
}
private static Avatar makeAvatar(AvatarData avatarData, int level, int promoteLevel, int constellation, int skillLevel) {
private static Avatar makeAvatar(
AvatarData avatarData, int level, int promoteLevel, int constellation, int skillLevel) {
Avatar avatar = new Avatar(avatarData);
avatar.setLevel(level);
avatar.setPromoteLevel(promoteLevel);
avatar.getSkillDepot().getSkillsAndEnergySkill().forEach(id -> avatar.setSkillLevel(id, skillLevel));
avatar
.getSkillDepot()
.getSkillsAndEnergySkill()
.forEach(id -> avatar.setSkillLevel(id, skillLevel));
avatar.forceConstellationLevel(constellation);
avatar.recalcStats(true);
avatar.save();
@ -62,12 +70,16 @@ public final class GiveCommand implements CommandHandler {
private static void giveAllAvatars(Player player, GiveItemParameters param) {
int promoteLevel = Avatar.getMinPromoteLevel(param.lvl);
if (param.constellation < 0 || param.constellation > 6)
param.constellation = 6; // constellation's default is -1 so if no parameters set for constellations it'll automatically be 6
param.constellation =
6; // constellation's default is -1 so if no parameters set for constellations it'll
// automatically be 6
for (AvatarData avatarData : GameData.getAvatarDataMap().values()) {
int id = avatarData.getId();
if (id < 10000002 || id >= 11000000) continue; // Exclude test avatars
// Don't try to add each avatar to the current team
player.addAvatar(makeAvatar(avatarData, param.lvl, promoteLevel, param.constellation, param.skillLevel), false);
player.addAvatar(
makeAvatar(avatarData, param.lvl, promoteLevel, param.constellation, param.skillLevel),
false);
}
}
@ -76,8 +88,7 @@ public final class GiveCommand implements CommandHandler {
int totalExp = 0;
if (param.data.getItemType() == ItemType.ITEM_WEAPON) {
int rankLevel = param.data.getRankLevel();
for (int i = 1; i < param.lvl; i++)
totalExp += GameData.getWeaponExpRequired(rankLevel, i);
for (int i = 1; i < param.lvl; i++) totalExp += GameData.getWeaponExpRequired(rankLevel, i);
}
List<GameItem> items = new ArrayList<>(param.amount);
@ -87,7 +98,7 @@ public final class GiveCommand implements CommandHandler {
if (item.getItemType() == ItemType.ITEM_WEAPON) {
item.setPromoteLevel(promoteLevel);
item.setTotalExp(totalExp);
item.setRefinement(param.refinement - 1); // Actual refinement data is 0..4 not 1..5
item.setRefinement(param.refinement - 1); // Actual refinement data is 0..4 not 1..5
}
items.add(item);
}
@ -98,8 +109,7 @@ public final class GiveCommand implements CommandHandler {
param.lvl = Math.min(param.lvl, param.data.getMaxLevel());
int rank = param.data.getRankLevel();
int totalExp = 0;
for (int i = 1; i < param.lvl; i++)
totalExp += GameData.getRelicExpRequired(rank, i);
for (int i = 1; i < param.lvl; i++) totalExp += GameData.getRelicExpRequired(rank, i);
List<GameItem> items = new ArrayList<>(param.amount);
for (int i = 0; i < param.amount; i++) {
@ -108,8 +118,8 @@ public final class GiveCommand implements CommandHandler {
item.setLevel(param.lvl);
item.setTotalExp(totalExp);
int numAffixes = param.data.getAppendPropNum() + (param.lvl - 1) / 4;
if (param.mainPropId > 0) // Keep random mainProp if we didn't specify one
item.setMainPropId(param.mainPropId);
if (param.mainPropId > 0) // Keep random mainProp if we didn't specify one
item.setMainPropId(param.mainPropId);
if (param.appendPropIdList != null) {
item.getAppendPropIdList().clear();
item.getAppendPropIdList().addAll(param.appendPropIdList);
@ -121,15 +131,17 @@ public final class GiveCommand implements CommandHandler {
return items;
}
private static int getArtifactMainProp(ItemData itemData, FightProperty prop) throws IllegalArgumentException {
private static int getArtifactMainProp(ItemData itemData, FightProperty prop)
throws IllegalArgumentException {
if (prop != FightProperty.FIGHT_PROP_NONE)
for (ReliquaryMainPropData data : GameDepot.getRelicMainPropList(itemData.getMainPropDepotId()))
if (data.getWeight() > 0 && data.getFightProp() == prop)
return data.getId();
for (ReliquaryMainPropData data :
GameDepot.getRelicMainPropList(itemData.getMainPropDepotId()))
if (data.getWeight() > 0 && data.getFightProp() == prop) return data.getId();
throw new IllegalArgumentException();
}
private static List<Integer> getArtifactAffixes(ItemData itemData, FightProperty prop) throws IllegalArgumentException {
private static List<Integer> getArtifactAffixes(ItemData itemData, FightProperty prop)
throws IllegalArgumentException {
if (prop == FightProperty.FIGHT_PROP_NONE) {
throw new IllegalArgumentException();
}
@ -142,7 +154,8 @@ public final class GiveCommand implements CommandHandler {
return affixes;
}
private static int getAppendPropId(String substatText, ItemData itemData) throws IllegalArgumentException {
private static int getAppendPropId(String substatText, ItemData itemData)
throws IllegalArgumentException {
// If the given substat text is an integer, we just use that as the append prop ID.
try {
return Integer.parseInt(substatText);
@ -159,19 +172,21 @@ public final class GiveCommand implements CommandHandler {
substatTier = Integer.parseInt(substatArgs[1]);
}
List<Integer> substats = getArtifactAffixes(itemData, FightProperty.getPropByShortName(substatType));
List<Integer> substats =
getArtifactAffixes(itemData, FightProperty.getPropByShortName(substatType));
if (substats.isEmpty()) {
throw new IllegalArgumentException();
}
substatTier -= 1; // 1-indexed to 0-indexed
substatTier -= 1; // 1-indexed to 0-indexed
substatTier = Math.min(Math.max(0, substatTier), substats.size() - 1);
return substats.get(substatTier);
}
}
private static void parseRelicArgs(GiveItemParameters param, List<String> args) throws IllegalArgumentException {
private static void parseRelicArgs(GiveItemParameters param, List<String> args)
throws IllegalArgumentException {
// Get the main stat from the arguments.
// If the given argument is an integer, we use that.
// If not, we check if the argument string is in the main prop map.
@ -181,7 +196,8 @@ public final class GiveCommand implements CommandHandler {
param.mainPropId = Integer.parseInt(mainPropIdString);
} catch (NumberFormatException ignored) {
// This can in turn throw an exception which we don't want to catch here.
param.mainPropId = getArtifactMainProp(param.data, FightProperty.getPropByShortName(mainPropIdString));
param.mainPropId =
getArtifactMainProp(param.data, FightProperty.getPropByShortName(mainPropIdString));
}
// Get substats.
@ -219,7 +235,7 @@ public final class GiveCommand implements CommandHandler {
List<GameItem> itemList = new ArrayList<>();
for (ItemData itemdata : GameData.getItemDataMap().values()) {
int id = itemdata.getId();
if (id < 100_000) continue; // Nothing meaningful below this
if (id < 100_000) continue; // Nothing meaningful below this
if (ILLEGAL_ITEMS.contains(id)) continue;
if (itemdata.isEquip()) continue;
@ -239,7 +255,7 @@ public final class GiveCommand implements CommandHandler {
List<GameItem> itemList = new ArrayList<>();
for (ItemData itemdata : GameData.getItemDataMap().values()) {
int id = itemdata.getId();
if (id < 11100 || id > 16000) continue; // All extant weapons are within this range
if (id < 11100 || id > 16000) continue; // All extant weapons are within this range
if (ILLEGAL_WEAPONS.contains(id)) continue;
if (!itemdata.isEquip()) continue;
if (itemdata.getItemType() != ItemType.ITEM_WEAPON) continue;
@ -262,7 +278,8 @@ public final class GiveCommand implements CommandHandler {
giveAllWeapons(player, param);
}
private GiveItemParameters parseArgs(Player sender, List<String> args) throws IllegalArgumentException {
private GiveItemParameters parseArgs(Player sender, List<String> args)
throws IllegalArgumentException {
GiveItemParameters param = new GiveItemParameters();
// Extract any tagged arguments (e.g. "lv90", "x100", "r5")
@ -270,7 +287,7 @@ public final class GiveCommand implements CommandHandler {
// At this point, first remaining argument MUST be itemId/avatarId
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();
}
String id = args.remove(0);
@ -304,7 +321,9 @@ public final class GiveCommand implements CommandHandler {
param.avatarData = GameData.getAvatarDataMap().get(param.id - 1000 + 10_000_000);
isRelic = ((param.data != null) && (param.data.getItemType() == ItemType.ITEM_RELIQUARY));
if (!isRelic && !args.isEmpty() && (param.amount == 1)) { // A concession for the people that truly hate [x<amount>].
if (!isRelic
&& !args.isEmpty()
&& (param.amount == 1)) { // A concession for the people that truly hate [x<amount>].
try {
param.amount = Integer.parseInt(args.remove(0));
} catch (NumberFormatException e) {
@ -382,7 +401,8 @@ public final class GiveCommand implements CommandHandler {
if (param.avatarData != null) {
Avatar avatar = makeAvatar(param);
targetPlayer.addAvatar(avatar);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_avatar", param.id, param.lvl, targetPlayer.getUid());
CommandHandler.sendTranslatedMessage(
sender, "commands.give.given_avatar", param.id, param.lvl, targetPlayer.getUid());
return;
}
// If it's not an avatar, it needs to be a valid item
@ -393,16 +413,34 @@ public final class GiveCommand implements CommandHandler {
switch (param.data.getItemType()) {
case ITEM_WEAPON:
targetPlayer.getInventory().addItems(makeUnstackableItems(param), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_with_level_and_refinement", param.id, param.lvl, param.refinement, param.amount, targetPlayer.getUid());
targetPlayer
.getInventory()
.addItems(makeUnstackableItems(param), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(
sender,
"commands.give.given_with_level_and_refinement",
param.id,
param.lvl,
param.refinement,
param.amount,
targetPlayer.getUid());
return;
case ITEM_RELIQUARY:
targetPlayer.getInventory().addItems(makeArtifacts(param), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given_level", param.id, param.lvl, param.amount, targetPlayer.getUid());
CommandHandler.sendTranslatedMessage(
sender,
"commands.give.given_level",
param.id,
param.lvl,
param.amount,
targetPlayer.getUid());
return;
default:
targetPlayer.getInventory().addItem(new GameItem(param.data, param.amount), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(sender, "commands.give.given", param.amount, param.id, targetPlayer.getUid());
targetPlayer
.getInventory()
.addItem(new GameItem(param.data, param.amount), ActionReason.SubfieldDrop);
CommandHandler.sendTranslatedMessage(
sender, "commands.give.given", param.amount, param.id, targetPlayer.getUid());
}
} catch (IllegalArgumentException ignored) {
}
@ -418,16 +456,11 @@ public final class GiveCommand implements CommandHandler {
private static class GiveItemParameters {
public int id;
@Setter
public int lvl = 0;
@Setter
public int amount = 1;
@Setter
public int refinement = 1;
@Setter
public int constellation = -1;
@Setter
public int skillLevel = 1;
@Setter public int lvl = 0;
@Setter public int amount = 1;
@Setter public int refinement = 1;
@Setter public int constellation = -1;
@Setter public int skillLevel = 1;
public int mainPropId = -1;
public List<Integer> appendPropIdList;
public ItemData data;

View File

@ -1,44 +1,44 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import java.util.List;
@Command(
label = "heal",
aliases = {"h"},
permission = "player.heal",
permissionTargeted = "player.heal.others")
public final class HealCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
targetPlayer
.getTeamManager()
.getActiveTeam()
.forEach(
entity -> {
boolean isAlive = entity.isAlive();
entity.setFightProperty(
FightProperty.FIGHT_PROP_CUR_HP,
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"));
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import java.util.List;
@Command(
label = "heal",
aliases = {"h"},
permission = "player.heal",
permissionTargeted = "player.heal.others")
public final class HealCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
targetPlayer
.getTeamManager()
.getActiveTeam()
.forEach(
entity -> {
boolean isAlive = entity.isAlive();
entity.setFightProperty(
FightProperty.FIGHT_PROP_CUR_HP,
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,92 +1,92 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import java.util.ArrayList;
import java.util.List;
@Command(
label = "help",
usage = {"[<command>]"},
targetRequirement = Command.TargetRequirement.NONE)
public final class HelpCommand implements CommandHandler {
private final boolean SHOW_COMMANDS_WITHOUT_PERMISSIONS =
false; // TODO: Make this into a server config key
private String createCommand(Player player, CommandHandler command, List<String> args) {
StringBuilder builder =
new StringBuilder(command.getLabel())
.append(" - ")
.append(command.getDescriptionString(player))
.append("\n\t")
.append(command.getUsageString(player, args.toArray(new String[0])));
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(alias).append(" ");
}
}
builder.append("\n\t").append(translate(player, "commands.help.tip_need_permission"));
if (!annotation.permission().isEmpty()) {
builder.append(annotation.permission());
} else {
builder.append(translate(player, "commands.help.tip_need_no_permission"));
}
if (!annotation.permissionTargeted().isEmpty()) {
String permissionTargeted = annotation.permissionTargeted();
builder
.append(" ")
.append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted));
}
return builder.toString();
}
@Override
public void execute(Player player, Player targetPlayer, List<String> args) {
Account account = (player == null) ? null : player.getAccount();
var commandMap = CommandMap.getInstance();
List<String> commands = new ArrayList<>();
List<String> commands_no_permission = new ArrayList<>();
if (args.isEmpty()) {
commandMap
.getHandlers()
.forEach(
(key, command) -> {
Command annotation = command.getClass().getAnnotation(Command.class);
if (player == null || account.hasPermission(annotation.permission())) {
commands.add(createCommand(player, command, args));
} else if (SHOW_COMMANDS_WITHOUT_PERMISSIONS) {
commands_no_permission.add(createCommand(player, command, args));
}
});
CommandHandler.sendTranslatedMessage(player, "commands.help.available_commands");
} else {
String command_str = args.remove(0).toLowerCase();
CommandHandler command = commandMap.getHandler(command_str);
if (command == null) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.command_exist_error");
CommandHandler.sendMessage(player, "Command: " + command_str);
return;
} else {
Command annotation = command.getClass().getAnnotation(Command.class);
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));
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import java.util.ArrayList;
import java.util.List;
@Command(
label = "help",
usage = {"[<command>]"},
targetRequirement = Command.TargetRequirement.NONE)
public final class HelpCommand implements CommandHandler {
private final boolean SHOW_COMMANDS_WITHOUT_PERMISSIONS =
false; // TODO: Make this into a server config key
private String createCommand(Player player, CommandHandler command, List<String> args) {
StringBuilder builder =
new StringBuilder(command.getLabel())
.append(" - ")
.append(command.getDescriptionString(player))
.append("\n\t")
.append(command.getUsageString(player, args.toArray(new String[0])));
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(alias).append(" ");
}
}
builder.append("\n\t").append(translate(player, "commands.help.tip_need_permission"));
if (!annotation.permission().isEmpty()) {
builder.append(annotation.permission());
} else {
builder.append(translate(player, "commands.help.tip_need_no_permission"));
}
if (!annotation.permissionTargeted().isEmpty()) {
String permissionTargeted = annotation.permissionTargeted();
builder
.append(" ")
.append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted));
}
return builder.toString();
}
@Override
public void execute(Player player, Player targetPlayer, List<String> args) {
Account account = (player == null) ? null : player.getAccount();
var commandMap = CommandMap.getInstance();
List<String> commands = new ArrayList<>();
List<String> commands_no_permission = new ArrayList<>();
if (args.isEmpty()) {
commandMap
.getHandlers()
.forEach(
(key, command) -> {
Command annotation = command.getClass().getAnnotation(Command.class);
if (player == null || account.hasPermission(annotation.permission())) {
commands.add(createCommand(player, command, args));
} else if (SHOW_COMMANDS_WITHOUT_PERMISSIONS) {
commands_no_permission.add(createCommand(player, command, args));
}
});
CommandHandler.sendTranslatedMessage(player, "commands.help.available_commands");
} else {
String command_str = args.remove(0).toLowerCase();
CommandHandler command = commandMap.getHandler(command_str);
if (command == null) {
CommandHandler.sendTranslatedMessage(player, "commands.generic.command_exist_error");
CommandHandler.sendMessage(player, "Command: " + command_str);
return;
} else {
Command annotation = command.getClass().getAnnotation(Command.class);
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,34 +1,34 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "kick",
aliases = {"restart"},
permissionTargeted = "server.kick")
public final class KickCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender != null) {
CommandHandler.sendTranslatedMessage(
sender,
"commands.kick.player_kick_player",
sender.getUid(),
sender.getAccount().getUsername(),
targetPlayer.getUid(),
targetPlayer.getAccount().getUsername());
} else {
CommandHandler.sendTranslatedMessage(
sender,
"commands.kick.server_kick_player",
targetPlayer.getUid(),
targetPlayer.getAccount().getUsername());
}
targetPlayer.getSession().close();
}
}
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "kick",
aliases = {"restart"},
permissionTargeted = "server.kick")
public final class KickCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender != null) {
CommandHandler.sendTranslatedMessage(
sender,
"commands.kick.player_kick_player",
sender.getUid(),
sender.getAccount().getUsername(),
targetPlayer.getUid(),
targetPlayer.getAccount().getUsername());
} else {
CommandHandler.sendTranslatedMessage(
sender,
"commands.kick.server_kick_player",
targetPlayer.getUid(),
targetPlayer.getAccount().getUsername());
}
targetPlayer.getSession().close();
}
}

View File

@ -1,54 +1,54 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import java.util.List;
@Command(
label = "killall",
usage = {"[<sceneId>]"},
permission = "server.killall",
permissionTargeted = "server.killall.others")
public final class KillAllCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Scene scene = targetPlayer.getScene();
try {
switch (args.size()) {
case 0: // *No args*
break;
case 1: // [sceneId]
scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0)));
break;
default:
sendUsageMessage(sender);
return;
}
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error"));
}
if (scene == null) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.killall.scene_not_found_in_player_world"));
return;
}
// Separate into list to avoid concurrency issue
final Scene sceneF = scene;
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()));
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import java.util.List;
@Command(
label = "killall",
usage = {"[<sceneId>]"},
permission = "server.killall",
permissionTargeted = "server.killall.others")
public final class KillAllCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Scene scene = targetPlayer.getScene();
try {
switch (args.size()) {
case 0: // *No args*
break;
case 1: // [sceneId]
scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0)));
break;
default:
sendUsageMessage(sender);
return;
}
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error"));
}
if (scene == null) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.killall.scene_not_found_in_player_world"));
return;
}
// Separate into list to avoid concurrency issue
final Scene sceneF = scene;
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,41 +1,41 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import java.util.List;
@Command(
label = "killCharacter",
aliases = {"suicide", "kill"},
permission = "player.killcharacter",
permissionTargeted = "player.killcharacter.others")
public final class KillCharacterCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
// Packets
entity
.getWorld()
.broadcastPacket(
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()));
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import java.util.List;
@Command(
label = "killCharacter",
aliases = {"suicide", "kill"},
permission = "player.killcharacter",
permissionTargeted = "player.killcharacter.others")
public final class KillCharacterCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
// Packets
entity
.getWorld()
.broadcastPacket(
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,58 +1,58 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Utils;
import java.util.List;
import java.util.Locale;
@Command(
label = "language",
usage = {"[<language code>]"},
aliases = {"lang"},
targetRequirement = Command.TargetRequirement.NONE)
public final class LanguageCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.isEmpty()) {
String curLangCode = null;
if (sender != null) {
curLangCode = Utils.getLanguageCode(sender.getAccount().getLocale());
} else {
curLangCode = Grasscutter.getLanguage().getLanguageCode();
}
CommandHandler.sendMessage(
sender, translate(sender, "commands.language.current_language", curLangCode));
return;
}
String langCode = args.get(0);
var languageInst = Grasscutter.getLanguage(langCode);
var actualLangCode = languageInst.getLanguageCode();
var locale = Locale.forLanguageTag(actualLangCode);
if (sender != null) {
var account = sender.getAccount();
account.setLocale(locale);
account.save();
} else {
Grasscutter.setLanguage(languageInst);
var config = Grasscutter.getConfig();
config.language.language = locale;
Grasscutter.saveConfig(config);
}
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));
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Utils;
import java.util.List;
import java.util.Locale;
@Command(
label = "language",
usage = {"[<language code>]"},
aliases = {"lang"},
targetRequirement = Command.TargetRequirement.NONE)
public final class LanguageCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.isEmpty()) {
String curLangCode = null;
if (sender != null) {
curLangCode = Utils.getLanguageCode(sender.getAccount().getLocale());
} else {
curLangCode = Grasscutter.getLanguage().getLanguageCode();
}
CommandHandler.sendMessage(
sender, translate(sender, "commands.language.current_language", curLangCode));
return;
}
String langCode = args.get(0);
var languageInst = Grasscutter.getLanguage(langCode);
var actualLangCode = languageInst.getLanguageCode();
var locale = Locale.forLanguageTag(actualLangCode);
if (sender != null) {
var account = sender.getAccount();
account.setLocale(locale);
account.save();
} else {
Grasscutter.setLanguage(languageInst);
var config = Grasscutter.getConfig();
config.language.language = locale;
Grasscutter.saveConfig(config);
}
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,56 +1,56 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.Map;
@Command(
label = "list",
aliases = {"players"},
usage = {"[<UID>]"},
targetRequirement = Command.TargetRequirement.NONE)
public final class ListCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Map<Integer, Player> playersMap = Grasscutter.getGameServer().getPlayers();
boolean needUID = false;
if (args.size() > 0) {
needUID = args.get(0).equals("uid");
}
CommandHandler.sendMessage(
sender, translate(sender, "commands.list.success", playersMap.size()));
if (playersMap.size() != 0) {
StringBuilder playerSet = new StringBuilder();
boolean finalNeedUID = needUID;
playersMap
.values()
.forEach(
player -> {
playerSet.append(player.getNickname());
if (finalNeedUID) {
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();
CommandHandler.sendMessage(sender, players.substring(0, players.length() - 2));
}
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.Map;
@Command(
label = "list",
aliases = {"players"},
usage = {"[<UID>]"},
targetRequirement = Command.TargetRequirement.NONE)
public final class ListCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Map<Integer, Player> playersMap = Grasscutter.getGameServer().getPlayers();
boolean needUID = false;
if (args.size() > 0) {
needUID = args.get(0).equals("uid");
}
CommandHandler.sendMessage(
sender, translate(sender, "commands.list.success", playersMap.size()));
if (playersMap.size() != 0) {
StringBuilder playerSet = new StringBuilder();
boolean finalNeedUID = needUID;
playersMap
.values()
.forEach(
player -> {
playerSet.append(player.getNickname());
if (finalNeedUID) {
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();
CommandHandler.sendMessage(sender, players.substring(0, players.length() - 2));
}
}
}

View File

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

View File

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

View File

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

View File

@ -1,23 +1,23 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "resetShopLimit",
aliases = {"resetshop"},
permission = "server.resetshop",
permissionTargeted = "server.resetshop.others")
public final class ResetShopLimitCommand implements CommandHandler {
@Override
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"));
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "resetShopLimit",
aliases = {"resetshop"},
permission = "server.resetshop",
permissionTargeted = "server.resetshop.others")
public final class ResetShopLimitCommand implements CommandHandler {
@Override
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,241 +1,241 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.player.Player;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@SuppressWarnings("ConstantConditions")
@Command(
label = "sendMail",
usage = {"(<userId>|all) [<templateId>]", "help"},
permission = "server.sendmail",
targetRequirement = Command.TargetRequirement.NONE)
public final class SendMailCommand implements CommandHandler {
// TODO: You should be able to do /sendmail and then just send subsequent messages until you
// finish
// However, due to the current nature of the command system, I don't think this is possible
// 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>();
// Yes this is awful and I hate it.
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
int senderId;
if (sender != null) {
senderId = sender.getUid();
} else {
senderId = -1;
}
if (!mailBeingConstructed.containsKey(senderId)) {
switch (args.size()) {
case 1 -> {
MailBuilder mailBuilder;
switch (args.get(0).toLowerCase()) {
case "help" -> {
sendUsageMessage(sender);
return;
}
case "all" -> mailBuilder = new MailBuilder(true, new Mail());
default -> {
if (DatabaseHelper.getPlayerByUid(Integer.parseInt(args.get(0))) != null) {
mailBuilder = new MailBuilder(Integer.parseInt(args.get(0)), new Mail());
} 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"));
default -> CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.invalid_arguments"));
}
} else {
MailBuilder mailBuilder = mailBeingConstructed.get(senderId);
if (args.size() >= 1) {
switch (args.get(0).toLowerCase()) {
case "stop" -> {
mailBeingConstructed.remove(senderId);
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_cancel"));
}
case "finish" -> {
if (mailBuilder.constructionStage == 3) {
if (!mailBuilder.sendToAll) {
Grasscutter.getGameServer()
.getPlayerByUid(mailBuilder.recipient, true)
.sendMail(mailBuilder.mail);
CommandHandler.sendMessage(
sender,
translate(sender, "commands.sendMail.send_done", mailBuilder.recipient));
} else {
DatabaseHelper.getByGameClass(Player.class)
.forEach(
player -> {
var onlinePlayer =
Grasscutter.getGameServer().getPlayerByUid(player.getUid(), false);
Objects.requireNonNullElse(onlinePlayer, player)
.sendMail(mailBuilder.mail);
});
CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.send_all_done"));
}
mailBeingConstructed.remove(senderId);
} else {
CommandHandler.sendMessage(
sender,
translate(
sender,
"commands.sendMail.not_composition_end",
getConstructionArgs(mailBuilder.constructionStage, sender)));
}
}
case "help" -> {
CommandHandler.sendMessage(
sender,
translate(
sender,
"commands.sendMail.please_use",
getConstructionArgs(mailBuilder.constructionStage, sender)));
}
default -> {
switch (mailBuilder.constructionStage) {
case 0 -> {
String title = String.join(" ", args.subList(0, args.size()));
mailBuilder.mail.mailContent.title = title;
CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.set_title", title));
mailBuilder.constructionStage++;
}
case 1 -> {
String contents = String.join(" ", args.subList(0, args.size()));
mailBuilder.mail.mailContent.content = contents;
CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.set_contents", contents));
mailBuilder.constructionStage++;
}
case 2 -> {
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));
mailBuilder.constructionStage++;
}
case 3 -> {
int item;
int lvl = 1;
int amount = 1;
int refinement = 0;
switch (args.size()) {
case 4: // <itemId|itemName> [amount] [level] [refinement] // TODO: this requires
// Mail support but there's no harm leaving it here for now
try {
refinement = Integer.parseInt(args.get(3));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.generic.invalid.itemRefinement"));
return;
} // Fallthrough
case 3: // <itemId|itemName> [amount] [level]
try {
lvl = Integer.parseInt(args.get(2));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.generic.invalid.itemLevel"));
return;
} // Fallthrough
case 2: // <itemId|itemName> [amount]
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.generic.invalid.amount"));
return;
} // Fallthrough
case 1: // <itemId|itemName>
try {
item = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
// TODO: Parse from item name using GM Handbook.
CommandHandler.sendMessage(
sender, translate(sender, "commands.generic.invalid.itemId"));
return;
}
break;
default: // *No args*
CommandHandler.sendTranslatedMessage(sender, "commands.sendMail.give_usage");
return;
}
mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl));
CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.send", amount, item, lvl));
}
}
}
}
} else {
CommandHandler.sendMessage(
sender,
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();
}
}
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.player.Player;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@SuppressWarnings("ConstantConditions")
@Command(
label = "sendMail",
usage = {"(<userId>|all) [<templateId>]", "help"},
permission = "server.sendmail",
targetRequirement = Command.TargetRequirement.NONE)
public final class SendMailCommand implements CommandHandler {
// TODO: You should be able to do /sendmail and then just send subsequent messages until you
// finish
// However, due to the current nature of the command system, I don't think this is possible
// 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>();
// Yes this is awful and I hate it.
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
int senderId;
if (sender != null) {
senderId = sender.getUid();
} else {
senderId = -1;
}
if (!mailBeingConstructed.containsKey(senderId)) {
switch (args.size()) {
case 1 -> {
MailBuilder mailBuilder;
switch (args.get(0).toLowerCase()) {
case "help" -> {
sendUsageMessage(sender);
return;
}
case "all" -> mailBuilder = new MailBuilder(true, new Mail());
default -> {
if (DatabaseHelper.getPlayerByUid(Integer.parseInt(args.get(0))) != null) {
mailBuilder = new MailBuilder(Integer.parseInt(args.get(0)), new Mail());
} 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"));
default -> CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.invalid_arguments"));
}
} else {
MailBuilder mailBuilder = mailBeingConstructed.get(senderId);
if (args.size() >= 1) {
switch (args.get(0).toLowerCase()) {
case "stop" -> {
mailBeingConstructed.remove(senderId);
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_cancel"));
}
case "finish" -> {
if (mailBuilder.constructionStage == 3) {
if (!mailBuilder.sendToAll) {
Grasscutter.getGameServer()
.getPlayerByUid(mailBuilder.recipient, true)
.sendMail(mailBuilder.mail);
CommandHandler.sendMessage(
sender,
translate(sender, "commands.sendMail.send_done", mailBuilder.recipient));
} else {
DatabaseHelper.getByGameClass(Player.class)
.forEach(
player -> {
var onlinePlayer =
Grasscutter.getGameServer().getPlayerByUid(player.getUid(), false);
Objects.requireNonNullElse(onlinePlayer, player)
.sendMail(mailBuilder.mail);
});
CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.send_all_done"));
}
mailBeingConstructed.remove(senderId);
} else {
CommandHandler.sendMessage(
sender,
translate(
sender,
"commands.sendMail.not_composition_end",
getConstructionArgs(mailBuilder.constructionStage, sender)));
}
}
case "help" -> {
CommandHandler.sendMessage(
sender,
translate(
sender,
"commands.sendMail.please_use",
getConstructionArgs(mailBuilder.constructionStage, sender)));
}
default -> {
switch (mailBuilder.constructionStage) {
case 0 -> {
String title = String.join(" ", args.subList(0, args.size()));
mailBuilder.mail.mailContent.title = title;
CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.set_title", title));
mailBuilder.constructionStage++;
}
case 1 -> {
String contents = String.join(" ", args.subList(0, args.size()));
mailBuilder.mail.mailContent.content = contents;
CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.set_contents", contents));
mailBuilder.constructionStage++;
}
case 2 -> {
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));
mailBuilder.constructionStage++;
}
case 3 -> {
int item;
int lvl = 1;
int amount = 1;
int refinement = 0;
switch (args.size()) {
case 4: // <itemId|itemName> [amount] [level] [refinement] // TODO: this requires
// Mail support but there's no harm leaving it here for now
try {
refinement = Integer.parseInt(args.get(3));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.generic.invalid.itemRefinement"));
return;
} // Fallthrough
case 3: // <itemId|itemName> [amount] [level]
try {
lvl = Integer.parseInt(args.get(2));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.generic.invalid.itemLevel"));
return;
} // Fallthrough
case 2: // <itemId|itemName> [amount]
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.generic.invalid.amount"));
return;
} // Fallthrough
case 1: // <itemId|itemName>
try {
item = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
// TODO: Parse from item name using GM Handbook.
CommandHandler.sendMessage(
sender, translate(sender, "commands.generic.invalid.itemId"));
return;
}
break;
default: // *No args*
CommandHandler.sendTranslatedMessage(sender, "commands.sendMail.give_usage");
return;
}
mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl));
CommandHandler.sendMessage(
sender, translate(sender, "commands.sendMail.send", amount, item, lvl));
}
}
}
}
} else {
CommandHandler.sendMessage(
sender,
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,37 +1,37 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.Command.TargetRequirement;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "sendMessage",
aliases = {"say", "sendservmsg", "sendservermessage", "b", "broadcast"},
usage = {"<message>"},
permission = "server.sendmessage",
permissionTargeted = "server.sendmessage.others",
targetRequirement = TargetRequirement.NONE)
public final class SendMessageCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() == 0) {
sendUsageMessage(sender);
return;
}
String message = String.join(" ", args);
if (targetPlayer == null) {
for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
CommandHandler.sendMessage(p, message);
}
} else {
CommandHandler.sendMessage(targetPlayer, message);
}
CommandHandler.sendTranslatedMessage(sender, "commands.sendMessage.success");
}
}
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.Command.TargetRequirement;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "sendMessage",
aliases = {"say", "sendservmsg", "sendservermessage", "b", "broadcast"},
usage = {"<message>"},
permission = "server.sendmessage",
permissionTargeted = "server.sendmessage.others",
targetRequirement = TargetRequirement.NONE)
public final class SendMessageCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() == 0) {
sendUsageMessage(sender);
return;
}
String message = String.join(" ", args);
if (targetPlayer == null) {
for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
CommandHandler.sendMessage(p, message);
}
} else {
CommandHandler.sendMessage(targetPlayer, message);
}
CommandHandler.sendTranslatedMessage(sender, "commands.sendMessage.success");
}
}

View File

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

View File

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

View File

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

View File

@ -1,78 +1,78 @@
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
import emu.grasscutter.utils.Position;
import java.util.List;
@Command(
label = "teleport",
aliases = {"tp"},
usage = {"<x> <y> <z> [sceneId]"},
permission = "player.teleport",
permissionTargeted = "player.teleport.others")
public final class TeleportCommand implements CommandHandler {
private float parseRelative(
String input, Float current) { // TODO: Maybe this will be useful elsewhere later
if (input.contains("~")) { // Relative
if (!input.equals("~")) { // Relative with offset
current += Float.parseFloat(input.replace("~", ""));
} // Else no offset, no modification
} else { // Absolute
current = Float.parseFloat(input);
}
return current;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Position pos = targetPlayer.getPosition();
float x = pos.getX();
float y = pos.getY();
float z = pos.getZ();
int sceneId = targetPlayer.getSceneId();
switch (args.size()) {
case 4:
try {
sceneId = Integer.parseInt(args.get(3));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.execution.argument_error"));
} // Fallthrough
case 3:
try {
x = this.parseRelative(args.get(0), x);
y = this.parseRelative(args.get(1), y);
z = this.parseRelative(args.get(2), z);
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.teleport.invalid_position"));
}
break;
default:
this.sendUsageMessage(sender);
return;
}
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));
}
}
}
package emu.grasscutter.command.commands;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
import emu.grasscutter.utils.Position;
import java.util.List;
@Command(
label = "teleport",
aliases = {"tp"},
usage = {"<x> <y> <z> [sceneId]"},
permission = "player.teleport",
permissionTargeted = "player.teleport.others")
public final class TeleportCommand implements CommandHandler {
private float parseRelative(
String input, Float current) { // TODO: Maybe this will be useful elsewhere later
if (input.contains("~")) { // Relative
if (!input.equals("~")) { // Relative with offset
current += Float.parseFloat(input.replace("~", ""));
} // Else no offset, no modification
} else { // Absolute
current = Float.parseFloat(input);
}
return current;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Position pos = targetPlayer.getPosition();
float x = pos.getX();
float y = pos.getY();
float z = pos.getZ();
int sceneId = targetPlayer.getSceneId();
switch (args.size()) {
case 4:
try {
sceneId = Integer.parseInt(args.get(3));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.execution.argument_error"));
} // Fallthrough
case 3:
try {
x = this.parseRelative(args.get(0), x);
y = this.parseRelative(args.get(1), y);
z = this.parseRelative(args.get(2), z);
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(
sender, translate(sender, "commands.teleport.invalid_position"));
}
break;
default:
this.sendUsageMessage(sender);
return;
}
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,39 +1,39 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "unban",
permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER)
public final class UnBanCommand implements CommandHandler {
private boolean unBanAccount(Player targetPlayer) {
Account account = targetPlayer.getAccount();
if (account == null) {
return false;
}
account.setBanReason(null);
account.setBanEndTime(0);
account.setBanStartTime(0);
account.setBanned(false);
account.save();
return true;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (unBanAccount(targetPlayer)) {
CommandHandler.sendTranslatedMessage(sender, "commands.unban.success");
} else {
CommandHandler.sendTranslatedMessage(sender, "commands.unban.failure");
}
}
}
package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(
label = "unban",
permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER)
public final class UnBanCommand implements CommandHandler {
private boolean unBanAccount(Player targetPlayer) {
Account account = targetPlayer.getAccount();
if (account == null) {
return false;
}
account.setBanReason(null);
account.setBanEndTime(0);
account.setBanStartTime(0);
account.setBanned(false);
account.save();
return true;
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (unBanAccount(targetPlayer)) {
CommandHandler.sendTranslatedMessage(sender, "commands.unban.success");
} else {
CommandHandler.sendTranslatedMessage(sender, "commands.unban.failure");
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,24 @@
package emu.grasscutter.data.common;
import java.util.List;
public class OpenCondData {
private String condType;
private List<Integer> paramList;
public String getCondType() {
return condType;
}
public void setCondType(String condType) {
condType = condType;
}
public List<Integer> getParamList() {
return paramList;
}
public void setParamList(List<Integer> paramList) {
paramList = paramList;
}
}
package emu.grasscutter.data.common;
import java.util.List;
public class OpenCondData {
private String condType;
private List<Integer> paramList;
public String getCondType() {
return condType;
}
public void setCondType(String condType) {
condType = condType;
}
public List<Integer> getParamList() {
return paramList;
}
public void setParamList(List<Integer> paramList) {
paramList = paramList;
}
}

View File

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

View File

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

View File

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

View File

@ -1,46 +1,46 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
import lombok.Getter;
@ResourceType(name = "BlossomRefreshExcelConfigData.json")
@Getter
public class BlossomRefreshExcelConfigData extends GameResource {
@Getter(onMethod_ = @Override)
private int id;
// Map details
private long nameTextMapHash;
private long descTextMapHash;
private String icon;
private String clientShowType; // BLOSSOM_SHOWTYPE_CHALLENGE, BLOSSOM_SHOWTYPE_NPCTALK
// Refresh details
private String refreshType; // Leyline blossoms, magical ore outcrops
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)
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 Drop[] dropVec;
// Unknown details
// @Getter private int reviseLevel;
// @Getter private int campUpdateNeedCount; // Always 1 if specified
@Getter
public static class Drop {
int dropId;
int previewReward;
}
@Getter
public static class RefreshCond {
String type;
List<Integer> param;
}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
import lombok.Getter;
@ResourceType(name = "BlossomRefreshExcelConfigData.json")
@Getter
public class BlossomRefreshExcelConfigData extends GameResource {
@Getter(onMethod_ = @Override)
private int id;
// Map details
private long nameTextMapHash;
private long descTextMapHash;
private String icon;
private String clientShowType; // BLOSSOM_SHOWTYPE_CHALLENGE, BLOSSOM_SHOWTYPE_NPCTALK
// Refresh details
private String refreshType; // Leyline blossoms, magical ore outcrops
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)
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 Drop[] dropVec;
// Unknown details
// @Getter private int reviseLevel;
// @Getter private int campUpdateNeedCount; // Always 1 if specified
@Getter
public static class Drop {
int dropId;
int previewReward;
}
@Getter
public static class RefreshCond {
String type;
List<Integer> param;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,36 +1,36 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
@ResourceType(
name = "EnvAnimalGatherExcelConfigData.json",
loadPriority = ResourceType.LoadPriority.LOW)
public class EnvAnimalGatherConfigData extends GameResource {
private int animalId;
private String entityType;
private List<ItemParamData> gatherItemId;
private String excludeWeathers;
private int aliveTime;
private int escapeTime;
private int escapeRadius;
@Override
public int getId() {
return animalId;
}
public int getAnimalId() {
return animalId;
}
public String getEntityType() {
return entityType;
}
public ItemParamData getGatherItem() {
return gatherItemId.size() > 0 ? gatherItemId.get(0) : null;
}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
@ResourceType(
name = "EnvAnimalGatherExcelConfigData.json",
loadPriority = ResourceType.LoadPriority.LOW)
public class EnvAnimalGatherConfigData extends GameResource {
private int animalId;
private String entityType;
private List<ItemParamData> gatherItemId;
private String excludeWeathers;
private int aliveTime;
private int escapeTime;
private int escapeRadius;
@Override
public int getId() {
return animalId;
}
public int getAnimalId() {
return animalId;
}
public String getEntityType() {
return entityType;
}
public ItemParamData getGatherItem() {
return gatherItemId.size() > 0 ? gatherItemId.get(0) : null;
}
}

View File

@ -1,59 +1,59 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData;
import java.util.ArrayList;
@ResourceType(name = "EquipAffixExcelConfigData.json")
public class EquipAffixData extends GameResource {
private int affixId;
private int id;
private int level;
private long nameTextMapHash;
private String openConfig;
private FightPropData[] addProps;
private float[] paramList;
@Override
public int getId() {
return affixId;
}
public int getMainId() {
return id;
}
public int getLevel() {
return level;
}
public long getNameTextMapHash() {
return nameTextMapHash;
}
public String getOpenConfig() {
return openConfig;
}
public FightPropData[] getAddProps() {
return addProps;
}
public float[] getParamList() {
return paramList;
}
@Override
public void onLoad() {
ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null && prop.getValue() != 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.addProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData;
import java.util.ArrayList;
@ResourceType(name = "EquipAffixExcelConfigData.json")
public class EquipAffixData extends GameResource {
private int affixId;
private int id;
private int level;
private long nameTextMapHash;
private String openConfig;
private FightPropData[] addProps;
private float[] paramList;
@Override
public int getId() {
return affixId;
}
public int getMainId() {
return id;
}
public int getLevel() {
return level;
}
public long getNameTextMapHash() {
return nameTextMapHash;
}
public String getOpenConfig() {
return openConfig;
}
public FightPropData[] getAddProps() {
return addProps;
}
public float[] getParamList() {
return paramList;
}
@Override
public void onLoad() {
ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null && prop.getValue() != 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.addProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}

View File

@ -1,23 +1,23 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
@ResourceType(name = "FetterCharacterCardExcelConfigData.json", loadPriority = LoadPriority.HIGHEST)
public class FetterCharacterCardData extends GameResource {
private int avatarId;
private int rewardId;
@Override
public int getId() {
return avatarId;
}
public int getRewardId() {
return rewardId;
}
@Override
public void onLoad() {}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
@ResourceType(name = "FetterCharacterCardExcelConfigData.json", loadPriority = LoadPriority.HIGHEST)
public class FetterCharacterCardData extends GameResource {
private int avatarId;
private int rewardId;
@Override
public int getId() {
return avatarId;
}
public int getRewardId() {
return rewardId;
}
@Override
public void onLoad() {}
}

View File

@ -1,38 +1,38 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.OpenCondData;
import java.util.List;
@ResourceType(
name = {
"FetterInfoExcelConfigData.json",
"FettersExcelConfigData.json",
"FetterStoryExcelConfigData.json",
"PhotographExpressionExcelConfigData.json",
"PhotographPosenameExcelConfigData.json"
},
loadPriority = LoadPriority.HIGHEST)
public class FetterData extends GameResource {
private int avatarId;
private int fetterId;
private List<OpenCondData> openCond;
@Override
public int getId() {
return fetterId;
}
public int getAvatarId() {
return avatarId;
}
public List<OpenCondData> getOpenConds() {
return openCond;
}
@Override
public void onLoad() {}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.OpenCondData;
import java.util.List;
@ResourceType(
name = {
"FetterInfoExcelConfigData.json",
"FettersExcelConfigData.json",
"FetterStoryExcelConfigData.json",
"PhotographExpressionExcelConfigData.json",
"PhotographPosenameExcelConfigData.json"
},
loadPriority = LoadPriority.HIGHEST)
public class FetterData extends GameResource {
private int avatarId;
private int fetterId;
private List<OpenCondData> openCond;
@Override
public int getId() {
return fetterId;
}
public int getAvatarId() {
return avatarId;
}
public List<OpenCondData> getOpenConds() {
return openCond;
}
@Override
public void onLoad() {}
}

View File

@ -1,29 +1,29 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
import lombok.Getter;
@ResourceType(
name = {"ForgeExcelConfigData.json"},
loadPriority = LoadPriority.HIGHEST)
@Getter
public class ForgeData extends GameResource {
@Getter(onMethod_ = @Override)
private int id;
private int playerLevel;
private int forgeType;
private int showItemId;
private int resultItemId;
private int resultItemCount;
private int forgeTime;
private int queueNum;
private int scoinCost;
private int priority;
private int forgePoint;
private List<ItemParamData> materialItems;
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
import lombok.Getter;
@ResourceType(
name = {"ForgeExcelConfigData.json"},
loadPriority = LoadPriority.HIGHEST)
@Getter
public class ForgeData extends GameResource {
@Getter(onMethod_ = @Override)
private int id;
private int playerLevel;
private int forgeType;
private int showItemId;
private int resultItemId;
private int resultItemCount;
private int forgeTime;
private int queueNum;
private int scoinCost;
private int priority;
private int forgePoint;
private List<ItemParamData> materialItems;
}

View File

@ -1,34 +1,34 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
@ResourceType(name = {"FurnitureMakeExcelConfigData.json"})
public class FurnitureMakeConfigData extends GameResource {
int configID;
int furnitureItemID;
int count;
int exp;
List<ItemParamData> materialItems;
int makeTime;
int maxAccelerateTime;
int quickFetchMaterialNum;
@Override
public int getId() {
return configID;
}
@Override
public void onLoad() {
this.materialItems = materialItems.stream().filter(x -> x.getId() > 0).toList();
}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
@ResourceType(name = {"FurnitureMakeExcelConfigData.json"})
public class FurnitureMakeConfigData extends GameResource {
int configID;
int furnitureItemID;
int count;
int exp;
List<ItemParamData> materialItems;
int makeTime;
int maxAccelerateTime;
int quickFetchMaterialNum;
@Override
public int getId() {
return configID;
}
@Override
public void onLoad() {
this.materialItems = materialItems.stream().filter(x -> x.getId() > 0).toList();
}
}

View File

@ -1,47 +1,47 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "GatherExcelConfigData.json")
public class GatherData extends GameResource {
private int pointType;
private int id;
private int gadgetId;
private int itemId;
private int cd; // Probably hours
private boolean isForbidGuest;
private boolean initDisableInteract;
@Override
public int getId() {
return this.pointType;
}
public int getGatherId() {
return id;
}
public int getGadgetId() {
return gadgetId;
}
public int getItemId() {
return itemId;
}
public int getCd() {
return cd;
}
public boolean isForbidGuest() {
return isForbidGuest;
}
public boolean initDisableInteract() {
return initDisableInteract;
}
@Override
public void onLoad() {}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "GatherExcelConfigData.json")
public class GatherData extends GameResource {
private int pointType;
private int id;
private int gadgetId;
private int itemId;
private int cd; // Probably hours
private boolean isForbidGuest;
private boolean initDisableInteract;
@Override
public int getId() {
return this.pointType;
}
public int getGatherId() {
return id;
}
public int getGadgetId() {
return gadgetId;
}
public int getItemId() {
return itemId;
}
public int getCd() {
return cd;
}
public boolean isForbidGuest() {
return isForbidGuest;
}
public boolean initDisableInteract() {
return initDisableInteract;
}
@Override
public void onLoad() {}
}

View File

@ -1,31 +1,31 @@
package emu.grasscutter.data.excels;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
@ResourceType(name = {"HomeWorldBgmExcelConfigData.json"})
public class HomeWorldBgmData extends GameResource {
@SerializedName(value = "homeBgmId", alternate = "MJJENLEBKEF")
private int homeBgmId;
private boolean isDefaultUnlock;
private boolean NBIDHGOOCKD;
private boolean JJMNJMCCOKP;
private int cityId;
private int sortOrder;
private String GEGHMJBJMGB;
@SerializedName(value = "bgmNameTextMapHash", alternate = "LMLNBMJFFML")
private long bgmNameTextMapHash;
@Override
public int getId() {
return this.homeBgmId;
}
}
package emu.grasscutter.data.excels;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
@ResourceType(name = {"HomeWorldBgmExcelConfigData.json"})
public class HomeWorldBgmData extends GameResource {
@SerializedName(value = "homeBgmId", alternate = "MJJENLEBKEF")
private int homeBgmId;
private boolean isDefaultUnlock;
private boolean NBIDHGOOCKD;
private boolean JJMNJMCCOKP;
private int cityId;
private int sortOrder;
private String GEGHMJBJMGB;
@SerializedName(value = "bgmNameTextMapHash", alternate = "LMLNBMJFFML")
private long bgmNameTextMapHash;
@Override
public int getId() {
return this.homeBgmId;
}
}

View File

@ -1,31 +1,31 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
@ResourceType(name = {"HomeworldLevelExcelConfigData.json"})
public class HomeWorldLevelData extends GameResource {
int level;
int exp;
int homeCoinStoreLimit;
int homeFetterExpStoreLimit;
int rewardId;
int furnitureMakeSlotCount;
int outdoorUnlockBlockCount;
int freeUnlockModuleCount;
int deployNpcCount;
int limitShopGoodsCount;
List<String> levelFuncs;
@Override
public int getId() {
return level;
}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
@ResourceType(name = {"HomeworldLevelExcelConfigData.json"})
public class HomeWorldLevelData extends GameResource {
int level;
int exp;
int homeCoinStoreLimit;
int homeFetterExpStoreLimit;
int rewardId;
int furnitureMakeSlotCount;
int outdoorUnlockBlockCount;
int freeUnlockModuleCount;
int deployNpcCount;
int limitShopGoodsCount;
List<String> levelFuncs;
@Override
public int getId() {
return level;
}
}

View File

@ -1,33 +1,33 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@ResourceType(
name = "InvestigationMonsterConfigData.json",
loadPriority = ResourceType.LoadPriority.LOW)
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class InvestigationMonsterData extends GameResource {
@Getter(onMethod_ = @Override)
int id;
int cityId;
List<Integer> monsterIdList;
List<Integer> groupIdList;
int rewardPreviewId;
String mapMarkCreateType;
String monsterCategory;
CityData cityData;
@Override
public void onLoad() {
this.cityData = GameData.getCityDataMap().get(cityId);
}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@ResourceType(
name = "InvestigationMonsterConfigData.json",
loadPriority = ResourceType.LoadPriority.LOW)
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class InvestigationMonsterData extends GameResource {
@Getter(onMethod_ = @Override)
int id;
int cityId;
List<Integer> monsterIdList;
List<Integer> groupIdList;
int rewardPreviewId;
String mapMarkCreateType;
String monsterCategory;
CityData cityData;
@Override
public void onLoad() {
this.cityData = GameData.getCityDataMap().get(cityId);
}
}

View File

@ -1,18 +1,18 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@ResourceType(name = "MusicGameBasicConfigData.json")
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class MusicGameBasicData extends GameResource {
@Getter(onMethod_ = @Override)
int id;
int musicID;
int musicLevel;
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@ResourceType(name = "MusicGameBasicConfigData.json")
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class MusicGameBasicData extends GameResource {
@Getter(onMethod_ = @Override)
int id;
int musicID;
int musicLevel;
}

View File

@ -1,23 +1,23 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@ResourceType(name = "PersonalLineExcelConfigData.json")
@Getter
@Setter // TODO: remove setters next API break
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PersonalLineData extends GameResource {
@Getter(onMethod_ = @Override)
int id;
int avatarID;
List<Integer> preQuestId;
int startQuestId;
int chapterId;
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@ResourceType(name = "PersonalLineExcelConfigData.json")
@Getter
@Setter // TODO: remove setters next API break
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PersonalLineData extends GameResource {
@Getter(onMethod_ = @Override)
int id;
int avatarID;
List<Integer> preQuestId;
int startQuestId;
int chapterId;
}

View File

@ -1,57 +1,57 @@
package emu.grasscutter.data.excels;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.common.ItemParamData;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
@ResourceType(name = "ProudSkillExcelConfigData.json")
public class ProudSkillData extends GameResource {
private int proudSkillId;
@Getter private int proudSkillGroupId;
@Getter private int level;
@Getter private int coinCost;
@Getter private int breakLevel;
@Getter private int proudSkillType;
@Getter private String openConfig;
@Getter private List<ItemParamData> costItems;
@Getter private List<String> filterConds;
@Getter private List<String> lifeEffectParams;
@Getter private FightPropData[] addProps;
@Getter private float[] paramList;
@Getter private long[] paramDescList;
@Getter private long nameTextMapHash;
@Transient private Iterable<ItemParamData> totalCostItems;
@Override
public int getId() {
return proudSkillId;
}
public Iterable<ItemParamData> getTotalCostItems() {
if (this.totalCostItems == null) {
ArrayList<ItemParamData> total =
(this.costItems != null) ? new ArrayList<>(this.costItems) : new ArrayList<>(1);
if (this.coinCost > 0) total.add(new ItemParamData(202, this.coinCost));
this.totalCostItems = total;
}
return this.totalCostItems;
}
@Override
public void onLoad() {
// Fight props
ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null && prop.getValue() != 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.addProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}
package emu.grasscutter.data.excels;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.common.ItemParamData;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
@ResourceType(name = "ProudSkillExcelConfigData.json")
public class ProudSkillData extends GameResource {
private int proudSkillId;
@Getter private int proudSkillGroupId;
@Getter private int level;
@Getter private int coinCost;
@Getter private int breakLevel;
@Getter private int proudSkillType;
@Getter private String openConfig;
@Getter private List<ItemParamData> costItems;
@Getter private List<String> filterConds;
@Getter private List<String> lifeEffectParams;
@Getter private FightPropData[] addProps;
@Getter private float[] paramList;
@Getter private long[] paramDescList;
@Getter private long nameTextMapHash;
@Transient private Iterable<ItemParamData> totalCostItems;
@Override
public int getId() {
return proudSkillId;
}
public Iterable<ItemParamData> getTotalCostItems() {
if (this.totalCostItems == null) {
ArrayList<ItemParamData> total =
(this.costItems != null) ? new ArrayList<>(this.costItems) : new ArrayList<>(1);
if (this.coinCost > 0) total.add(new ItemParamData(202, this.coinCost));
this.totalCostItems = total;
}
return this.totalCostItems;
}
@Override
public void onLoad() {
// Fight props
ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null && prop.getValue() != 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.addProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}

View File

@ -1,26 +1,26 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
@ResourceType(name = "RewardExcelConfigData.json")
public class RewardData extends GameResource {
public int rewardId;
public List<ItemParamData> rewardItemList;
@Override
public int getId() {
return rewardId;
}
public List<ItemParamData> getRewardItemList() {
return rewardItemList;
}
@Override
public void onLoad() {
rewardItemList = rewardItemList.stream().filter(i -> i.getId() > 0).toList();
}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
@ResourceType(name = "RewardExcelConfigData.json")
public class RewardData extends GameResource {
public int rewardId;
public List<ItemParamData> rewardItemList;
@Override
public int getId() {
return rewardId;
}
public List<ItemParamData> getRewardItemList() {
return rewardItemList;
}
@Override
public void onLoad() {
rewardItemList = rewardItemList.stream().filter(i -> i.getId() > 0).toList();
}
}

View File

@ -1,35 +1,35 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.common.ItemParamStringData;
import java.util.Arrays;
import lombok.Getter;
@ResourceType(name = "RewardPreviewExcelConfigData.json", loadPriority = LoadPriority.HIGH)
public class RewardPreviewData extends GameResource {
@Getter(onMethod_ = @Override)
private int id;
private ItemParamStringData[] previewItems;
private ItemParamData[] previewItemsArray;
public ItemParamData[] getPreviewItems() {
return previewItemsArray;
}
@Override
public void onLoad() {
if (this.previewItems != null && this.previewItems.length > 0) {
this.previewItemsArray =
Arrays.stream(this.previewItems)
.filter(d -> d.getId() > 0 && d.getCount() != null && !d.getCount().isEmpty())
.map(ItemParamStringData::toItemParamData)
.toArray(size -> new ItemParamData[size]);
} else {
this.previewItemsArray = new ItemParamData[0];
}
}
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.common.ItemParamStringData;
import java.util.Arrays;
import lombok.Getter;
@ResourceType(name = "RewardPreviewExcelConfigData.json", loadPriority = LoadPriority.HIGH)
public class RewardPreviewData extends GameResource {
@Getter(onMethod_ = @Override)
private int id;
private ItemParamStringData[] previewItems;
private ItemParamData[] previewItemsArray;
public ItemParamData[] getPreviewItems() {
return previewItemsArray;
}
@Override
public void onLoad() {
if (this.previewItems != null && this.previewItems.length > 0) {
this.previewItemsArray =
Arrays.stream(this.previewItems)
.filter(d -> d.getId() > 0 && d.getCount() != null && !d.getCount().isEmpty())
.map(ItemParamStringData::toItemParamData)
.toArray(size -> new ItemParamData[size]);
} else {
this.previewItemsArray = new ItemParamData[0];
}
}
}

View File

@ -1,112 +1,112 @@
package emu.grasscutter.data.excels;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.shop.ShopInfo;
import java.util.List;
@ResourceType(name = "ShopGoodsExcelConfigData.json")
public class ShopGoodsData extends GameResource {
private int goodsId;
private int shopType;
private int itemId;
private int itemCount;
private int costScoin;
private int costHcoin;
private int costMcoin;
private List<ItemParamData> costItems;
private int minPlayerLevel;
private int maxPlayerLevel;
private int buyLimit;
@SerializedName(
value = "subTabId",
alternate = {"secondarySheetId"})
private int subTabId;
private String refreshType;
private transient ShopInfo.ShopRefreshType refreshTypeEnum;
private int refreshParam;
@Override
public void onLoad() {
if (this.refreshType == null) this.refreshTypeEnum = ShopInfo.ShopRefreshType.NONE;
else {
this.refreshTypeEnum =
switch (this.refreshType) {
case "SHOP_REFRESH_DAILY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_DAILY;
case "SHOP_REFRESH_WEEKLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_WEEKLY;
case "SHOP_REFRESH_MONTHLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_MONTHLY;
default -> ShopInfo.ShopRefreshType.NONE;
};
}
}
@Override
public int getId() {
return getGoodsId();
}
public int getGoodsId() {
return goodsId;
}
public int getShopType() {
return shopType;
}
public int getItemId() {
return itemId;
}
public int getItemCount() {
return itemCount;
}
public int getCostScoin() {
return costScoin;
}
public int getCostHcoin() {
return costHcoin;
}
public int getCostMcoin() {
return costMcoin;
}
public List<ItemParamData> getCostItems() {
return costItems;
}
public int getMinPlayerLevel() {
return minPlayerLevel;
}
public int getMaxPlayerLevel() {
return maxPlayerLevel;
}
public int getBuyLimit() {
return buyLimit;
}
public int getSubTabId() {
return subTabId;
}
public ShopInfo.ShopRefreshType getRefreshType() {
return refreshTypeEnum;
}
public int getRefreshParam() {
return refreshParam;
}
}
package emu.grasscutter.data.excels;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.shop.ShopInfo;
import java.util.List;
@ResourceType(name = "ShopGoodsExcelConfigData.json")
public class ShopGoodsData extends GameResource {
private int goodsId;
private int shopType;
private int itemId;
private int itemCount;
private int costScoin;
private int costHcoin;
private int costMcoin;
private List<ItemParamData> costItems;
private int minPlayerLevel;
private int maxPlayerLevel;
private int buyLimit;
@SerializedName(
value = "subTabId",
alternate = {"secondarySheetId"})
private int subTabId;
private String refreshType;
private transient ShopInfo.ShopRefreshType refreshTypeEnum;
private int refreshParam;
@Override
public void onLoad() {
if (this.refreshType == null) this.refreshTypeEnum = ShopInfo.ShopRefreshType.NONE;
else {
this.refreshTypeEnum =
switch (this.refreshType) {
case "SHOP_REFRESH_DAILY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_DAILY;
case "SHOP_REFRESH_WEEKLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_WEEKLY;
case "SHOP_REFRESH_MONTHLY" -> ShopInfo.ShopRefreshType.SHOP_REFRESH_MONTHLY;
default -> ShopInfo.ShopRefreshType.NONE;
};
}
}
@Override
public int getId() {
return getGoodsId();
}
public int getGoodsId() {
return goodsId;
}
public int getShopType() {
return shopType;
}
public int getItemId() {
return itemId;
}
public int getItemCount() {
return itemCount;
}
public int getCostScoin() {
return costScoin;
}
public int getCostHcoin() {
return costHcoin;
}
public int getCostMcoin() {
return costMcoin;
}
public List<ItemParamData> getCostItems() {
return costItems;
}
public int getMinPlayerLevel() {
return minPlayerLevel;
}
public int getMaxPlayerLevel() {
return maxPlayerLevel;
}
public int getBuyLimit() {
return buyLimit;
}
public int getSubTabId() {
return subTabId;
}
public ShopInfo.ShopRefreshType getRefreshType() {
return refreshTypeEnum;
}
public int getRefreshParam() {
return refreshParam;
}
}

View File

@ -1,14 +1,14 @@
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.Getter;
@ResourceType(name = "TriggerExcelConfigData.json")
@Getter
public class TriggerExcelConfigData extends GameResource {
@Getter private int id;
private int sceneId;
private int groupId;
private String triggerName;
}
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.Getter;
@ResourceType(name = "TriggerExcelConfigData.json")
@Getter
public class TriggerExcelConfigData extends GameResource {
@Getter private int id;
private int sceneId;
private int groupId;
private String triggerName;
}

View File

@ -1,22 +1,22 @@
package emu.grasscutter.database;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
@Entity(value = "counters", useDiscriminator = false)
public class DatabaseCounter {
@Id private String id;
private int count;
public DatabaseCounter() {}
public DatabaseCounter(String id) {
this.id = id;
this.count = 10000;
}
public int getNextId() {
int id = ++count;
return id;
}
}
package emu.grasscutter.database;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
@Entity(value = "counters", useDiscriminator = false)
public class DatabaseCounter {
@Id private String id;
private int count;
public DatabaseCounter() {}
public DatabaseCounter(String id) {
this.id = id;
this.count = 10000;
}
public int getNextId() {
int id = ++count;
return id;
}
}

View File

@ -1,126 +1,126 @@
package emu.grasscutter.database;
import static emu.grasscutter.config.Configuration.DATABASE;
import static emu.grasscutter.config.Configuration.SERVER;
import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import dev.morphia.Datastore;
import dev.morphia.Morphia;
import dev.morphia.annotations.Entity;
import dev.morphia.mapping.Mapper;
import dev.morphia.mapping.MapperOptions;
import dev.morphia.query.experimental.filters.Filters;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.game.Account;
import org.reflections.Reflections;
public final class DatabaseManager {
private static Datastore gameDatastore;
private static Datastore dispatchDatastore;
public static Datastore getGameDatastore() {
return gameDatastore;
}
public static MongoDatabase getGameDatabase() {
return getGameDatastore().getDatabase();
}
// Yes. I very dislike this method. However, this will be good for now.
// TODO: Add dispatch routes for player account management
public static Datastore getAccountDatastore() {
if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
return dispatchDatastore;
} else {
return gameDatastore;
}
}
public static void initialize() {
// Initialize
MongoClient gameMongoClient = MongoClients.create(DATABASE.game.connectionUri);
// Set mapper options.
MapperOptions mapperOptions =
MapperOptions.builder().storeEmpties(true).storeNulls(false).build();
// Create data store.
gameDatastore =
Morphia.createDatastore(gameMongoClient, DATABASE.game.collection, mapperOptions);
// Map classes.
Class<?>[] entities =
new Reflections(Grasscutter.class.getPackageName())
.getTypesAnnotatedWith(Entity.class).stream()
.filter(
cls -> {
Entity e = cls.getAnnotation(Entity.class);
return e != null && !e.value().equals(Mapper.IGNORED_FIELDNAME);
})
.toArray(Class<?>[]::new);
gameDatastore.getMapper().map(entities);
// Ensure indexes for the game datastore
ensureIndexes(gameDatastore);
if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
MongoClient dispatchMongoClient = MongoClients.create(DATABASE.server.connectionUri);
dispatchDatastore =
Morphia.createDatastore(dispatchMongoClient, DATABASE.server.collection, mapperOptions);
dispatchDatastore.getMapper().map(new Class<?>[] {DatabaseCounter.class, Account.class});
// Ensure indexes for dispatch datastore
ensureIndexes(dispatchDatastore);
}
}
/**
* Ensures the database indexes exist and rebuilds them if there is an error with them
*
* @param datastore The datastore to ensure indexes on
*/
private static void ensureIndexes(Datastore datastore) {
try {
datastore.ensureIndexes();
} catch (MongoCommandException e) {
Grasscutter.getLogger().info("Mongo index error: ", e);
// Duplicate index error
if (e.getCode() == 85) {
// Drop all indexes and re add them
MongoIterable<String> collections = datastore.getDatabase().listCollectionNames();
for (String name : collections) {
datastore.getDatabase().getCollection(name).dropIndexes();
}
// Add back indexes
datastore.ensureIndexes();
}
}
}
public static synchronized int getNextId(Class<?> c) {
DatabaseCounter counter =
getGameDatastore()
.find(DatabaseCounter.class)
.filter(Filters.eq("_id", c.getSimpleName()))
.first();
if (counter == null) {
counter = new DatabaseCounter(c.getSimpleName());
}
try {
return counter.getNextId();
} finally {
getGameDatastore().save(counter);
}
}
public static synchronized int getNextId(Object o) {
return getNextId(o.getClass());
}
}
package emu.grasscutter.database;
import static emu.grasscutter.config.Configuration.DATABASE;
import static emu.grasscutter.config.Configuration.SERVER;
import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import dev.morphia.Datastore;
import dev.morphia.Morphia;
import dev.morphia.annotations.Entity;
import dev.morphia.mapping.Mapper;
import dev.morphia.mapping.MapperOptions;
import dev.morphia.query.experimental.filters.Filters;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.game.Account;
import org.reflections.Reflections;
public final class DatabaseManager {
private static Datastore gameDatastore;
private static Datastore dispatchDatastore;
public static Datastore getGameDatastore() {
return gameDatastore;
}
public static MongoDatabase getGameDatabase() {
return getGameDatastore().getDatabase();
}
// Yes. I very dislike this method. However, this will be good for now.
// TODO: Add dispatch routes for player account management
public static Datastore getAccountDatastore() {
if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
return dispatchDatastore;
} else {
return gameDatastore;
}
}
public static void initialize() {
// Initialize
MongoClient gameMongoClient = MongoClients.create(DATABASE.game.connectionUri);
// Set mapper options.
MapperOptions mapperOptions =
MapperOptions.builder().storeEmpties(true).storeNulls(false).build();
// Create data store.
gameDatastore =
Morphia.createDatastore(gameMongoClient, DATABASE.game.collection, mapperOptions);
// Map classes.
Class<?>[] entities =
new Reflections(Grasscutter.class.getPackageName())
.getTypesAnnotatedWith(Entity.class).stream()
.filter(
cls -> {
Entity e = cls.getAnnotation(Entity.class);
return e != null && !e.value().equals(Mapper.IGNORED_FIELDNAME);
})
.toArray(Class<?>[]::new);
gameDatastore.getMapper().map(entities);
// Ensure indexes for the game datastore
ensureIndexes(gameDatastore);
if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
MongoClient dispatchMongoClient = MongoClients.create(DATABASE.server.connectionUri);
dispatchDatastore =
Morphia.createDatastore(dispatchMongoClient, DATABASE.server.collection, mapperOptions);
dispatchDatastore.getMapper().map(new Class<?>[] {DatabaseCounter.class, Account.class});
// Ensure indexes for dispatch datastore
ensureIndexes(dispatchDatastore);
}
}
/**
* Ensures the database indexes exist and rebuilds them if there is an error with them
*
* @param datastore The datastore to ensure indexes on
*/
private static void ensureIndexes(Datastore datastore) {
try {
datastore.ensureIndexes();
} catch (MongoCommandException e) {
Grasscutter.getLogger().info("Mongo index error: ", e);
// Duplicate index error
if (e.getCode() == 85) {
// Drop all indexes and re add them
MongoIterable<String> collections = datastore.getDatabase().listCollectionNames();
for (String name : collections) {
datastore.getDatabase().getCollection(name).dropIndexes();
}
// Add back indexes
datastore.ensureIndexes();
}
}
}
public static synchronized int getNextId(Class<?> c) {
DatabaseCounter counter =
getGameDatastore()
.find(DatabaseCounter.class)
.filter(Filters.eq("_id", c.getSimpleName()))
.first();
if (counter == null) {
counter = new DatabaseCounter(c.getSimpleName());
}
try {
return counter.getNextId();
} finally {
getGameDatastore().save(counter);
}
}
public static synchronized int getNextId(Object o) {
return getNextId(o.getClass());
}
}

View File

@ -1,31 +1,31 @@
package emu.grasscutter.game;
import emu.grasscutter.game.player.Player;
public class CoopRequest {
private final Player requester;
private final long requestTime;
private final long expireTime;
public CoopRequest(Player requester) {
this.requester = requester;
this.requestTime = System.currentTimeMillis();
this.expireTime = this.requestTime + 10000;
}
public Player getRequester() {
return requester;
}
public long getRequestTime() {
return requestTime;
}
public long getExpireTime() {
return expireTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > getExpireTime();
}
}
package emu.grasscutter.game;
import emu.grasscutter.game.player.Player;
public class CoopRequest {
private final Player requester;
private final long requestTime;
private final long expireTime;
public CoopRequest(Player requester) {
this.requester = requester;
this.requestTime = System.currentTimeMillis();
this.expireTime = this.requestTime + 10000;
}
public Player getRequester() {
return requester;
}
public long getRequestTime() {
return requestTime;
}
public long getExpireTime() {
return expireTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > getExpireTime();
}
}

View File

@ -1,47 +1,47 @@
package emu.grasscutter.game.achievement;
import lombok.Getter;
@Getter
public class AchievementControlReturns {
private final Return ret;
private final int changedAchievementStatusNum;
private AchievementControlReturns(Return ret) {
this(ret, 0);
}
private AchievementControlReturns(Return ret, int changedAchievementStatusNum) {
this.ret = ret;
this.changedAchievementStatusNum = changedAchievementStatusNum;
}
public static AchievementControlReturns success(int changedAchievementStatusNum) {
return new AchievementControlReturns(Return.SUCCESS, changedAchievementStatusNum);
}
public static AchievementControlReturns achievementNotFound() {
return new AchievementControlReturns(Return.ACHIEVEMENT_NOT_FOUND);
}
public static AchievementControlReturns alreadyAchieved() {
return new AchievementControlReturns(Return.ALREADY_ACHIEVED);
}
public static AchievementControlReturns notYetAchieved() {
return new AchievementControlReturns(Return.NOT_YET_ACHIEVED);
}
public enum Return {
SUCCESS("commands.achievement.success."),
ACHIEVEMENT_NOT_FOUND("commands.achievement.fail.achievement_not_found"),
ALREADY_ACHIEVED("commands.achievement.fail.already_achieved"),
NOT_YET_ACHIEVED("commands.achievement.fail.not_yet_achieved");
@Getter private final String key;
Return(String key) {
this.key = key;
}
}
}
package emu.grasscutter.game.achievement;
import lombok.Getter;
@Getter
public class AchievementControlReturns {
private final Return ret;
private final int changedAchievementStatusNum;
private AchievementControlReturns(Return ret) {
this(ret, 0);
}
private AchievementControlReturns(Return ret, int changedAchievementStatusNum) {
this.ret = ret;
this.changedAchievementStatusNum = changedAchievementStatusNum;
}
public static AchievementControlReturns success(int changedAchievementStatusNum) {
return new AchievementControlReturns(Return.SUCCESS, changedAchievementStatusNum);
}
public static AchievementControlReturns achievementNotFound() {
return new AchievementControlReturns(Return.ACHIEVEMENT_NOT_FOUND);
}
public static AchievementControlReturns alreadyAchieved() {
return new AchievementControlReturns(Return.ALREADY_ACHIEVED);
}
public static AchievementControlReturns notYetAchieved() {
return new AchievementControlReturns(Return.NOT_YET_ACHIEVED);
}
public enum Return {
SUCCESS("commands.achievement.success."),
ACHIEVEMENT_NOT_FOUND("commands.achievement.fail.achievement_not_found"),
ALREADY_ACHIEVED("commands.achievement.fail.already_achieved"),
NOT_YET_ACHIEVED("commands.achievement.fail.not_yet_achieved");
@Getter private final String key;
Return(String key) {
this.key = key;
}
}
}

View File

@ -1,13 +1,13 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ActivityWatcherType {
WatcherTriggerType value();
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ActivityWatcherType {
WatcherTriggerType value();
}

View File

@ -1,15 +1,15 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
@GameActivity(ActivityType.NONE)
public class DefaultActivityHandler extends ActivityHandler {
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {}
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {}
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
@GameActivity(ActivityType.NONE)
public class DefaultActivityHandler extends ActivityHandler {
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {}
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {}
}

View File

@ -1,11 +1,11 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_NONE)
public class DefaultWatcher extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
return false;
}
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_NONE)
public class DefaultWatcher extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
return false;
}
}

View File

@ -1,13 +1,13 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GameActivity {
ActivityType value();
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GameActivity {
ActivityType value();
}

View File

@ -1,105 +1,105 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityHandler;
import emu.grasscutter.game.activity.GameActivity;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.net.proto.MusicGameActivityDetailInfoOuterClass;
import emu.grasscutter.utils.JsonUtils;
import java.util.stream.Collectors;
@GameActivity(ActivityType.NEW_ACTIVITY_MUSIC_GAME)
public class MusicGameActivityHandler extends ActivityHandler {
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
var musicGamePlayerData = MusicGamePlayerData.create();
playerActivityData.setDetail(musicGamePlayerData);
}
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
MusicGamePlayerData musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
activityInfo.setMusicGameInfo(
MusicGameActivityDetailInfoOuterClass.MusicGameActivityDetailInfo.newBuilder()
.putAllMusicGameRecordMap(
musicGamePlayerData.getMusicGameRecord().values().stream()
.collect(
Collectors.toMap(
MusicGamePlayerData.MusicGameRecord::getMusicId,
MusicGamePlayerData.MusicGameRecord::toProto)))
//
// .addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toPersonalBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
//
//
// .addAllOthersCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toOthersBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
.build());
}
public MusicGamePlayerData getMusicGamePlayerData(PlayerActivityData playerActivityData) {
if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData);
playerActivityData.save();
}
return JsonUtils.decode(playerActivityData.getDetail(), MusicGamePlayerData.class);
}
public boolean setMusicGameRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId());
saveRecord.setMaxCombo(Math.max(newRecord.getMaxCombo(), saveRecord.getMaxCombo()));
saveRecord.setMaxScore(Math.max(newRecord.getMaxScore(), saveRecord.getMaxScore()));
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
return newRecord.getMaxScore() > saveRecord.getMaxScore();
}
public void setMusicGameCustomBeatmapRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord);
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void addPersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData
.getPersonalCustomBeatmapRecord()
.put(
musicGameBeatmap.getMusicShareId(),
MusicGamePlayerData.CustomBeatmapRecord.of()
.musicShareId(musicGameBeatmap.getMusicShareId())
.build());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void removePersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getPersonalCustomBeatmapRecord().remove(musicGameBeatmap.getMusicShareId());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityHandler;
import emu.grasscutter.game.activity.GameActivity;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.net.proto.MusicGameActivityDetailInfoOuterClass;
import emu.grasscutter.utils.JsonUtils;
import java.util.stream.Collectors;
@GameActivity(ActivityType.NEW_ACTIVITY_MUSIC_GAME)
public class MusicGameActivityHandler extends ActivityHandler {
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
var musicGamePlayerData = MusicGamePlayerData.create();
playerActivityData.setDetail(musicGamePlayerData);
}
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
MusicGamePlayerData musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
activityInfo.setMusicGameInfo(
MusicGameActivityDetailInfoOuterClass.MusicGameActivityDetailInfo.newBuilder()
.putAllMusicGameRecordMap(
musicGamePlayerData.getMusicGameRecord().values().stream()
.collect(
Collectors.toMap(
MusicGamePlayerData.MusicGameRecord::getMusicId,
MusicGamePlayerData.MusicGameRecord::toProto)))
//
// .addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toPersonalBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
//
//
// .addAllOthersCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toOthersBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
.build());
}
public MusicGamePlayerData getMusicGamePlayerData(PlayerActivityData playerActivityData) {
if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData);
playerActivityData.save();
}
return JsonUtils.decode(playerActivityData.getDetail(), MusicGamePlayerData.class);
}
public boolean setMusicGameRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId());
saveRecord.setMaxCombo(Math.max(newRecord.getMaxCombo(), saveRecord.getMaxCombo()));
saveRecord.setMaxScore(Math.max(newRecord.getMaxScore(), saveRecord.getMaxScore()));
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
return newRecord.getMaxScore() > saveRecord.getMaxScore();
}
public void setMusicGameCustomBeatmapRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord);
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void addPersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData
.getPersonalCustomBeatmapRecord()
.put(
musicGameBeatmap.getMusicShareId(),
MusicGamePlayerData.CustomBeatmapRecord.of()
.musicShareId(musicGameBeatmap.getMusicShareId())
.build());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void removePersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getPersonalCustomBeatmapRecord().remove(musicGameBeatmap.getMusicShareId());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
}

View File

@ -1,97 +1,97 @@
package emu.grasscutter.game.activity.musicgame;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import emu.grasscutter.net.proto.UgcMusicNoteOuterClass;
import emu.grasscutter.net.proto.UgcMusicRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicTrackOuterClass;
import java.util.List;
import java.util.Random;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity("music_game_beatmaps")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGameBeatmap {
@Id long musicShareId;
int authorUid;
int musicId;
int musicNoteCount;
int savePosition;
int maxScore;
int createTime;
List<List<BeatmapNote>> beatmap;
public static MusicGameBeatmap getByShareId(long musicShareId) {
return DatabaseHelper.getMusicGameBeatmap(musicShareId);
}
public static List<List<BeatmapNote>> parse(
List<UgcMusicTrackOuterClass.UgcMusicTrack> beatmapItemListList) {
return beatmapItemListList.stream()
.map(item -> item.getMusicNoteListList().stream().map(BeatmapNote::parse).toList())
.toList();
}
public void save() {
if (musicShareId == 0) {
musicShareId = new Random().nextLong(100000000000000L, 999999999999999L);
}
DatabaseHelper.saveMusicGameBeatmap(this);
}
public UgcMusicRecordOuterClass.UgcMusicRecord toProto() {
return UgcMusicRecordOuterClass.UgcMusicRecord.newBuilder()
.setMusicId(musicId)
.addAllMusicTrackList(beatmap.stream().map(this::musicBeatmapListToProto).toList())
.build();
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toBriefProto() {
var player = DatabaseHelper.getPlayerByUid(authorUid);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
.setMusicId(musicId)
// .setMusicNoteCount(musicNoteCount)
.setUgcGuid(musicShareId)
.setMaxScore(maxScore)
// .setShareTime(createTime)
.setCreatorNickname(player.getNickname())
.setVersion(1);
}
private UgcMusicTrackOuterClass.UgcMusicTrack musicBeatmapListToProto(
List<BeatmapNote> beatmapNoteList) {
return UgcMusicTrackOuterClass.UgcMusicTrack.newBuilder()
.addAllMusicNoteList(beatmapNoteList.stream().map(BeatmapNote::toProto).toList())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
@Entity
public static class BeatmapNote {
int startTime;
int endTime;
public static BeatmapNote parse(UgcMusicNoteOuterClass.UgcMusicNote note) {
return BeatmapNote.of().startTime(note.getStartTime()).endTime(note.getEndTime()).build();
}
public UgcMusicNoteOuterClass.UgcMusicNote toProto() {
return UgcMusicNoteOuterClass.UgcMusicNote.newBuilder()
.setStartTime(startTime)
.setEndTime(endTime)
.build();
}
}
}
package emu.grasscutter.game.activity.musicgame;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import emu.grasscutter.net.proto.UgcMusicNoteOuterClass;
import emu.grasscutter.net.proto.UgcMusicRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicTrackOuterClass;
import java.util.List;
import java.util.Random;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity("music_game_beatmaps")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGameBeatmap {
@Id long musicShareId;
int authorUid;
int musicId;
int musicNoteCount;
int savePosition;
int maxScore;
int createTime;
List<List<BeatmapNote>> beatmap;
public static MusicGameBeatmap getByShareId(long musicShareId) {
return DatabaseHelper.getMusicGameBeatmap(musicShareId);
}
public static List<List<BeatmapNote>> parse(
List<UgcMusicTrackOuterClass.UgcMusicTrack> beatmapItemListList) {
return beatmapItemListList.stream()
.map(item -> item.getMusicNoteListList().stream().map(BeatmapNote::parse).toList())
.toList();
}
public void save() {
if (musicShareId == 0) {
musicShareId = new Random().nextLong(100000000000000L, 999999999999999L);
}
DatabaseHelper.saveMusicGameBeatmap(this);
}
public UgcMusicRecordOuterClass.UgcMusicRecord toProto() {
return UgcMusicRecordOuterClass.UgcMusicRecord.newBuilder()
.setMusicId(musicId)
.addAllMusicTrackList(beatmap.stream().map(this::musicBeatmapListToProto).toList())
.build();
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toBriefProto() {
var player = DatabaseHelper.getPlayerByUid(authorUid);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
.setMusicId(musicId)
// .setMusicNoteCount(musicNoteCount)
.setUgcGuid(musicShareId)
.setMaxScore(maxScore)
// .setShareTime(createTime)
.setCreatorNickname(player.getNickname())
.setVersion(1);
}
private UgcMusicTrackOuterClass.UgcMusicTrack musicBeatmapListToProto(
List<BeatmapNote> beatmapNoteList) {
return UgcMusicTrackOuterClass.UgcMusicTrack.newBuilder()
.addAllMusicNoteList(beatmapNoteList.stream().map(BeatmapNote::toProto).toList())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
@Entity
public static class BeatmapNote {
int startTime;
int endTime;
public static BeatmapNote parse(UgcMusicNoteOuterClass.UgcMusicNote note) {
return BeatmapNote.of().startTime(note.getStartTime()).endTime(note.getEndTime()).build();
}
public UgcMusicNoteOuterClass.UgcMusicNote toProto() {
return UgcMusicNoteOuterClass.UgcMusicNote.newBuilder()
.setStartTime(startTime)
.setEndTime(endTime)
.build();
}
}
}

View File

@ -1,86 +1,86 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.MusicGameBasicData;
import emu.grasscutter.net.proto.MusicGameRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGamePlayerData {
Map<Integer, MusicGameRecord> musicGameRecord;
Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord;
Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord;
public static MusicGamePlayerData create() {
return MusicGamePlayerData.of()
.musicGameRecord(
GameData.getMusicGameBasicDataMap().values().stream()
.collect(
Collectors.toMap(
MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create)))
.personalCustomBeatmapRecord(new HashMap<>())
.othersCustomBeatmapRecord(new HashMap<>())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class MusicGameRecord {
int musicId;
int maxCombo;
int maxScore;
public static MusicGameRecord create(MusicGameBasicData musicGameBasicData) {
return MusicGameRecord.of().musicId(musicGameBasicData.getId()).build();
}
public MusicGameRecordOuterClass.MusicGameRecord toProto() {
return MusicGameRecordOuterClass.MusicGameRecord.newBuilder()
.setIsUnlock(true)
.setMaxCombo(maxCombo)
.setMaxScore(maxScore)
.build();
}
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class CustomBeatmapRecord {
long musicShareId;
int score;
boolean settle;
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toPersonalBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
// .setCanShare(true)
// .setCreateTime(musicGameBeatmap.getCreateTime())
.setMusicId(musicGameBeatmap.getMusicId())
.setMaxScore(musicGameBeatmap.getMaxScore())
// .setPosition(musicGameBeatmap.getSavePosition())
// .setMusicNoteCount(musicGameBeatmap.getMusicNoteCount())
.setUgcGuid(musicShareId);
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toOthersBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return musicGameBeatmap.toBriefProto()
// .setScore(score)
// .setSettle(settle)
;
}
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.MusicGameBasicData;
import emu.grasscutter.net.proto.MusicGameRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGamePlayerData {
Map<Integer, MusicGameRecord> musicGameRecord;
Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord;
Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord;
public static MusicGamePlayerData create() {
return MusicGamePlayerData.of()
.musicGameRecord(
GameData.getMusicGameBasicDataMap().values().stream()
.collect(
Collectors.toMap(
MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create)))
.personalCustomBeatmapRecord(new HashMap<>())
.othersCustomBeatmapRecord(new HashMap<>())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class MusicGameRecord {
int musicId;
int maxCombo;
int maxScore;
public static MusicGameRecord create(MusicGameBasicData musicGameBasicData) {
return MusicGameRecord.of().musicId(musicGameBasicData.getId()).build();
}
public MusicGameRecordOuterClass.MusicGameRecord toProto() {
return MusicGameRecordOuterClass.MusicGameRecord.newBuilder()
.setIsUnlock(true)
.setMaxCombo(maxCombo)
.setMaxScore(maxScore)
.build();
}
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class CustomBeatmapRecord {
long musicShareId;
int score;
boolean settle;
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toPersonalBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
// .setCanShare(true)
// .setCreateTime(musicGameBeatmap.getCreateTime())
.setMusicId(musicGameBeatmap.getMusicId())
.setMaxScore(musicGameBeatmap.getMaxScore())
// .setPosition(musicGameBeatmap.getSavePosition())
// .setMusicNoteCount(musicGameBeatmap.getMusicNoteCount())
.setUgcGuid(musicShareId);
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toOthersBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return musicGameBeatmap.toBriefProto()
// .setScore(score)
// .setSettle(settle)
;
}
}
}

View File

@ -1,23 +1,23 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.ActivityWatcherType;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE)
public class MusicGameScoreTrigger extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
if (param.length != 2) {
return false;
}
var paramList = getActivityWatcherData().getTriggerConfig().getParamList();
if (!paramList.get(0).equals(param[0])) {
return false;
}
var score = Integer.parseInt(param[1]);
var target = Integer.parseInt(paramList.get(1));
return score >= target;
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.ActivityWatcherType;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE)
public class MusicGameScoreTrigger extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
if (param.length != 2) {
return false;
}
var paramList = getActivityWatcherData().getTriggerConfig().getParamList();
if (!paramList.get(0).equals(param[0])) {
return false;
}
var score = Integer.parseInt(param[1]);
var target = Integer.parseInt(paramList.get(1));
return score >= target;
}
}

View File

@ -1,415 +1,415 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.BattlePassRewardData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.BasePlayerDataManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager extends BasePlayerDataManager {
@Id @Getter private ObjectId id;
@Indexed private int ownerUid;
@Getter private int point;
@Getter private int cyclePoints; // Weekly maximum cap
@Getter private int level;
@Getter private boolean viewed;
private boolean paid;
private Map<Integer, BattlePassMission> missions;
private Map<Integer, BattlePassReward> takenRewards;
@Deprecated // Morphia only
public BattlePassManager() {}
public BattlePassManager(Player player) {
super(player);
this.ownerUid = player.getUid();
}
public void setPlayer(Player player) {
this.player = player;
this.ownerUid = player.getUid();
}
public void updateViewed() {
this.viewed = true;
}
public boolean setLevel(int level) {
if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
this.level = level;
this.point = 0;
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
return true;
}
return false;
}
public void addPoints(int points) {
this.addPointsDirectly(points, false);
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save();
}
public void addPointsDirectly(int points, boolean isWeekly) {
int amount = points;
if (isWeekly) {
amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
}
if (amount <= 0) {
return;
}
this.point += amount;
this.cyclePoints += amount;
if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL
&& this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
// Make sure player cant go above max BP level
levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);
// Set new points after level up
this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
this.level += levelups;
}
}
public Map<Integer, BattlePassMission> getMissions() {
if (this.missions == null) this.missions = new HashMap<>();
return this.missions;
}
// Will return a new empty mission if the mission id is not found
public BattlePassMission loadMissionById(int id) {
return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
}
public boolean hasMission(int id) {
return getMissions().containsKey(id);
}
public boolean isPaid() {
// ToDo: Change this when we actually support unlocking "paid" BP.
return true;
}
public Map<Integer, BattlePassReward> getTakenRewards() {
if (this.takenRewards == null) this.takenRewards = new HashMap<>();
return this.takenRewards;
}
// Mission triggers
public void triggerMission(WatcherTriggerType triggerType) {
getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
}
public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
getPlayer()
.getServer()
.getBattlePassSystem()
.triggerMission(getPlayer(), triggerType, param, progress);
}
// Handlers
public void takeMissionPoint(List<Integer> missionIdList) {
// Obvious exploit check
if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
return;
}
List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());
for (int id : missionIdList) {
// Skip if we dont have this mission
if (!this.hasMission(id)) {
continue;
}
BattlePassMission mission = this.loadMissionById(id);
if (mission.getData() == null) {
this.getMissions().remove(mission.getId());
continue;
}
// Take reward
if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);
updatedMissions.add(mission);
}
}
if (updatedMissions.size() > 0) {
// Save to db
this.save();
// Packet
getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
}
private void takeRewardsFromSelectChest(
ItemData rewardItemData, int index, ItemParamData entry, List<GameItem> rewardItems) {
// Sanity checks.
if (rewardItemData.getItemUse().size() < 1) {
return;
}
// Get possible item choices.
String[] choices = rewardItemData.getItemUse().get(0).getUseParam()[0].split(",");
if (choices.length < index) {
return;
}
// Get data for the selected item.
// This depends on the type of chest.
int chosenId = Integer.parseInt(choices[index - 1]);
// For ITEM_USE_ADD_SELECT_ITEM chests, we can directly add the item specified in the chest's
// data.
if (rewardItemData.getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_ADD_SELECT_ITEM) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(chosenId), entry.getItemCount());
rewardItems.add(rewardItem);
}
// For ITEM_USE_GRANT_SELECT_REWARD chests, we have to again look up reward data.
else if (rewardItemData.getItemUse().get(0).getUseOp()
== ItemUseOp.ITEM_USE_GRANT_SELECT_REWARD) {
RewardData selectedReward = GameData.getRewardDataMap().get(chosenId);
for (var r : selectedReward.getRewardItemList()) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(r.getItemId()), r.getItemCount());
rewardItems.add(rewardItem);
}
} else {
Grasscutter.getLogger().error("Invalid chest type for BP reward.");
}
}
public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
List<BattlePassRewardTakeOption> rewardList = new ArrayList<>();
for (BattlePassRewardTakeOption option : takeOptionList) {
// Duplicate check
if (option.getTag().getRewardId() == 0
|| getTakenRewards().containsKey(option.getTag().getRewardId())) {
continue;
}
// Level check
if (option.getTag().getLevel() > this.getLevel()) {
continue;
}
BattlePassRewardData rewardData =
GameData.getBattlePassRewardDataMap()
.get(GameConstants.BATTLE_PASS_CURRENT_INDEX * 100 + option.getTag().getLevel());
// Sanity check with excel data
if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else if (this.isPaid()
&& rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else {
Grasscutter.getLogger().info("Not in rewards list: {}", option.getTag().getRewardId());
}
}
// Get rewards
List<GameItem> rewardItems = null;
if (rewardList.size() > 0) {
rewardItems = new ArrayList<>();
for (var option : rewardList) {
var tag = option.getTag();
int index = option.getOptionIdx();
// Make sure we have reward data.
RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
if (reward == null) {
continue;
}
// Add reward items.
for (var entry : reward.getRewardItemList()) {
ItemData rewardItemData = GameData.getItemDataMap().get(entry.getItemId());
// Some rewards are chests where the user can select the item they want.
if (rewardItemData.getMaterialType() == MaterialType.MATERIAL_SELECTABLE_CHEST) {
this.takeRewardsFromSelectChest(rewardItemData, index, entry, rewardItems);
}
// All other rewards directly give us the right item.
else {
GameItem rewardItem = new GameItem(rewardItemData, entry.getItemCount());
rewardItems.add(rewardItem);
}
}
// Construct the reward and set as taken.
BattlePassReward bpReward =
new BattlePassReward(
tag.getLevel(),
tag.getRewardId(),
tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID);
this.getTakenRewards().put(bpReward.getRewardId(), bpReward);
}
// Save to db
this.save();
// Add items and send battle pass schedule packet
getPlayer().getInventory().addItems(rewardItems);
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
}
public int buyLevels(int buyLevel) {
int boughtLevels = Math.min(buyLevel, GameConstants.BATTLE_PASS_MAX_LEVEL - buyLevel);
if (boughtLevels > 0) {
int price = GameConstants.BATTLE_PASS_LEVEL_PRICE * boughtLevels;
if (getPlayer().getPrimogems() < price) {
return 0;
}
this.level += boughtLevels;
this.save();
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
return boughtLevels;
}
public void resetDailyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType() == null
|| mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_DAILY) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
public void resetWeeklyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
//
public BattlePassSchedule getScheduleProto() {
var currentDate = LocalDate.now();
var nextSundayDate =
(currentDate.getDayOfWeek() == DayOfWeek.SUNDAY)
? currentDate
: LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
var nextSundayTime =
LocalDateTime.of(
nextSundayDate.getYear(),
nextSundayDate.getMonthValue(),
nextSundayDate.getDayOfMonth(),
23,
59,
59);
BattlePassSchedule.Builder schedule =
BattlePassSchedule.newBuilder()
.setScheduleId(2700)
.setLevel(this.getLevel())
.setPoint(this.getPoint())
.setBeginTime(0)
.setEndTime(2059483200)
.setIsViewed(this.isViewed())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setPaidPlatformFlags(2) // Not bought on Playstation.
.setCurCyclePoints(this.getCyclePoints())
.setCurCycle(
BattlePassCycle.newBuilder()
.setBeginTime(0)
.setEndTime((int) nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
.setCycleIdx(3));
for (BattlePassReward reward : getTakenRewards().values()) {
schedule.addRewardTakenList(reward.toProto());
}
return schedule.build();
}
public void save() {
DatabaseHelper.saveBattlePass(this);
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.BattlePassRewardData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.BasePlayerDataManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager extends BasePlayerDataManager {
@Id @Getter private ObjectId id;
@Indexed private int ownerUid;
@Getter private int point;
@Getter private int cyclePoints; // Weekly maximum cap
@Getter private int level;
@Getter private boolean viewed;
private boolean paid;
private Map<Integer, BattlePassMission> missions;
private Map<Integer, BattlePassReward> takenRewards;
@Deprecated // Morphia only
public BattlePassManager() {}
public BattlePassManager(Player player) {
super(player);
this.ownerUid = player.getUid();
}
public void setPlayer(Player player) {
this.player = player;
this.ownerUid = player.getUid();
}
public void updateViewed() {
this.viewed = true;
}
public boolean setLevel(int level) {
if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
this.level = level;
this.point = 0;
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
return true;
}
return false;
}
public void addPoints(int points) {
this.addPointsDirectly(points, false);
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save();
}
public void addPointsDirectly(int points, boolean isWeekly) {
int amount = points;
if (isWeekly) {
amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
}
if (amount <= 0) {
return;
}
this.point += amount;
this.cyclePoints += amount;
if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL
&& this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
// Make sure player cant go above max BP level
levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);
// Set new points after level up
this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
this.level += levelups;
}
}
public Map<Integer, BattlePassMission> getMissions() {
if (this.missions == null) this.missions = new HashMap<>();
return this.missions;
}
// Will return a new empty mission if the mission id is not found
public BattlePassMission loadMissionById(int id) {
return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
}
public boolean hasMission(int id) {
return getMissions().containsKey(id);
}
public boolean isPaid() {
// ToDo: Change this when we actually support unlocking "paid" BP.
return true;
}
public Map<Integer, BattlePassReward> getTakenRewards() {
if (this.takenRewards == null) this.takenRewards = new HashMap<>();
return this.takenRewards;
}
// Mission triggers
public void triggerMission(WatcherTriggerType triggerType) {
getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
}
public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
getPlayer()
.getServer()
.getBattlePassSystem()
.triggerMission(getPlayer(), triggerType, param, progress);
}
// Handlers
public void takeMissionPoint(List<Integer> missionIdList) {
// Obvious exploit check
if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
return;
}
List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());
for (int id : missionIdList) {
// Skip if we dont have this mission
if (!this.hasMission(id)) {
continue;
}
BattlePassMission mission = this.loadMissionById(id);
if (mission.getData() == null) {
this.getMissions().remove(mission.getId());
continue;
}
// Take reward
if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);
updatedMissions.add(mission);
}
}
if (updatedMissions.size() > 0) {
// Save to db
this.save();
// Packet
getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
}
private void takeRewardsFromSelectChest(
ItemData rewardItemData, int index, ItemParamData entry, List<GameItem> rewardItems) {
// Sanity checks.
if (rewardItemData.getItemUse().size() < 1) {
return;
}
// Get possible item choices.
String[] choices = rewardItemData.getItemUse().get(0).getUseParam()[0].split(",");
if (choices.length < index) {
return;
}
// Get data for the selected item.
// This depends on the type of chest.
int chosenId = Integer.parseInt(choices[index - 1]);
// For ITEM_USE_ADD_SELECT_ITEM chests, we can directly add the item specified in the chest's
// data.
if (rewardItemData.getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_ADD_SELECT_ITEM) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(chosenId), entry.getItemCount());
rewardItems.add(rewardItem);
}
// For ITEM_USE_GRANT_SELECT_REWARD chests, we have to again look up reward data.
else if (rewardItemData.getItemUse().get(0).getUseOp()
== ItemUseOp.ITEM_USE_GRANT_SELECT_REWARD) {
RewardData selectedReward = GameData.getRewardDataMap().get(chosenId);
for (var r : selectedReward.getRewardItemList()) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(r.getItemId()), r.getItemCount());
rewardItems.add(rewardItem);
}
} else {
Grasscutter.getLogger().error("Invalid chest type for BP reward.");
}
}
public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
List<BattlePassRewardTakeOption> rewardList = new ArrayList<>();
for (BattlePassRewardTakeOption option : takeOptionList) {
// Duplicate check
if (option.getTag().getRewardId() == 0
|| getTakenRewards().containsKey(option.getTag().getRewardId())) {
continue;
}
// Level check
if (option.getTag().getLevel() > this.getLevel()) {
continue;
}
BattlePassRewardData rewardData =
GameData.getBattlePassRewardDataMap()
.get(GameConstants.BATTLE_PASS_CURRENT_INDEX * 100 + option.getTag().getLevel());
// Sanity check with excel data
if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else if (this.isPaid()
&& rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else {
Grasscutter.getLogger().info("Not in rewards list: {}", option.getTag().getRewardId());
}
}
// Get rewards
List<GameItem> rewardItems = null;
if (rewardList.size() > 0) {
rewardItems = new ArrayList<>();
for (var option : rewardList) {
var tag = option.getTag();
int index = option.getOptionIdx();
// Make sure we have reward data.
RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
if (reward == null) {
continue;
}
// Add reward items.
for (var entry : reward.getRewardItemList()) {
ItemData rewardItemData = GameData.getItemDataMap().get(entry.getItemId());
// Some rewards are chests where the user can select the item they want.
if (rewardItemData.getMaterialType() == MaterialType.MATERIAL_SELECTABLE_CHEST) {
this.takeRewardsFromSelectChest(rewardItemData, index, entry, rewardItems);
}
// All other rewards directly give us the right item.
else {
GameItem rewardItem = new GameItem(rewardItemData, entry.getItemCount());
rewardItems.add(rewardItem);
}
}
// Construct the reward and set as taken.
BattlePassReward bpReward =
new BattlePassReward(
tag.getLevel(),
tag.getRewardId(),
tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID);
this.getTakenRewards().put(bpReward.getRewardId(), bpReward);
}
// Save to db
this.save();
// Add items and send battle pass schedule packet
getPlayer().getInventory().addItems(rewardItems);
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
}
public int buyLevels(int buyLevel) {
int boughtLevels = Math.min(buyLevel, GameConstants.BATTLE_PASS_MAX_LEVEL - buyLevel);
if (boughtLevels > 0) {
int price = GameConstants.BATTLE_PASS_LEVEL_PRICE * boughtLevels;
if (getPlayer().getPrimogems() < price) {
return 0;
}
this.level += boughtLevels;
this.save();
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
return boughtLevels;
}
public void resetDailyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType() == null
|| mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_DAILY) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
public void resetWeeklyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
//
public BattlePassSchedule getScheduleProto() {
var currentDate = LocalDate.now();
var nextSundayDate =
(currentDate.getDayOfWeek() == DayOfWeek.SUNDAY)
? currentDate
: LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
var nextSundayTime =
LocalDateTime.of(
nextSundayDate.getYear(),
nextSundayDate.getMonthValue(),
nextSundayDate.getDayOfMonth(),
23,
59,
59);
BattlePassSchedule.Builder schedule =
BattlePassSchedule.newBuilder()
.setScheduleId(2700)
.setLevel(this.getLevel())
.setPoint(this.getPoint())
.setBeginTime(0)
.setEndTime(2059483200)
.setIsViewed(this.isViewed())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setPaidPlatformFlags(2) // Not bought on Playstation.
.setCurCyclePoints(this.getCyclePoints())
.setCurCycle(
BattlePassCycle.newBuilder()
.setBeginTime(0)
.setEndTime((int) nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
.setCycleIdx(3));
for (BattlePassReward reward : getTakenRewards().values()) {
schedule.addRewardTakenList(reward.toProto());
}
return schedule.build();
}
public void save() {
DatabaseHelper.saveBattlePass(this);
}
}

View File

@ -1,75 +1,75 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.props.BattlePassMissionStatus;
@Entity
public class BattlePassMission {
private int id;
private int progress;
private BattlePassMissionStatus status;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassMission() {}
public BattlePassMission(int id) {
this.id = id;
}
public int getId() {
return id;
}
public BattlePassMissionData getData() {
if (this.data == null) {
this.data = GameData.getBattlePassMissionDataMap().get(getId());
}
return this.data;
}
public int getProgress() {
return progress;
}
public void setProgress(int value) {
this.progress = value;
}
public void addProgress(int addProgress, int maxProgress) {
this.progress = Math.min(addProgress + this.progress, maxProgress);
}
public BattlePassMissionStatus getStatus() {
if (status == null) status = BattlePassMissionStatus.MISSION_STATUS_UNFINISHED;
return status;
}
public void setStatus(BattlePassMissionStatus status) {
this.status = status;
}
public boolean isFinshed() {
return getStatus().getValue() >= 2;
}
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
var protoBuilder =
emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
protoBuilder
.setMissionId(getId())
.setCurProgress(getProgress())
.setTotalProgress(getData().getProgress())
.setRewardBattlePassPoint(getData().getAddPoint())
.setMissionStatus(getStatus().getMissionStatus())
.setMissionType(
getData().getRefreshType() == null ? 0 : getData().getRefreshType().getValue());
return protoBuilder.build();
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.props.BattlePassMissionStatus;
@Entity
public class BattlePassMission {
private int id;
private int progress;
private BattlePassMissionStatus status;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassMission() {}
public BattlePassMission(int id) {
this.id = id;
}
public int getId() {
return id;
}
public BattlePassMissionData getData() {
if (this.data == null) {
this.data = GameData.getBattlePassMissionDataMap().get(getId());
}
return this.data;
}
public int getProgress() {
return progress;
}
public void setProgress(int value) {
this.progress = value;
}
public void addProgress(int addProgress, int maxProgress) {
this.progress = Math.min(addProgress + this.progress, maxProgress);
}
public BattlePassMissionStatus getStatus() {
if (status == null) status = BattlePassMissionStatus.MISSION_STATUS_UNFINISHED;
return status;
}
public void setStatus(BattlePassMissionStatus status) {
this.status = status;
}
public boolean isFinshed() {
return getStatus().getValue() >= 2;
}
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
var protoBuilder =
emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
protoBuilder
.setMissionId(getId())
.setCurProgress(getProgress())
.setTotalProgress(getData().getProgress())
.setRewardBattlePassPoint(getData().getAddPoint())
.setMissionStatus(getStatus().getMissionStatus())
.setMissionType(
getData().getRefreshType() == null ? 0 : getData().getRefreshType().getValue());
return protoBuilder.build();
}
}

View File

@ -1,51 +1,51 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.net.proto.BattlePassRewardTagOuterClass.BattlePassRewardTag;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
@Entity
public class BattlePassReward {
private int level;
private int rewardId;
private boolean paid;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassReward() {}
public BattlePassReward(int level, int rewardId, boolean paid) {
this.level = level;
this.rewardId = rewardId;
this.paid = paid;
}
public int getLevel() {
return level;
}
public int getRewardId() {
return rewardId;
}
public boolean isPaid() {
return paid;
}
public BattlePassRewardTag toProto() {
var protoBuilder = BattlePassRewardTag.newBuilder();
protoBuilder
.setLevel(this.getLevel())
.setRewardId(this.getRewardId())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE);
return protoBuilder.build();
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.net.proto.BattlePassRewardTagOuterClass.BattlePassRewardTag;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
@Entity
public class BattlePassReward {
private int level;
private int rewardId;
private boolean paid;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassReward() {}
public BattlePassReward(int level, int rewardId, boolean paid) {
this.level = level;
this.rewardId = rewardId;
this.paid = paid;
}
public int getLevel() {
return level;
}
public int getRewardId() {
return rewardId;
}
public boolean isPaid() {
return paid;
}
public BattlePassRewardTag toProto() {
var protoBuilder = BattlePassRewardTag.newBuilder();
protoBuilder
.setLevel(this.getLevel())
.setRewardId(this.getRewardId())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE);
return protoBuilder.build();
}
}

View File

@ -1,80 +1,80 @@
package emu.grasscutter.game.battlepass;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BattlePassSystem extends BaseGameSystem {
private final Map<WatcherTriggerType, List<BattlePassMissionData>> cachedTriggers;
// BP Mission manager for the server, contains cached triggers so we dont have to load it for each
// player
public BattlePassSystem(GameServer server) {
super(server);
this.cachedTriggers = new HashMap<>();
for (BattlePassMissionData missionData : GameData.getBattlePassMissionDataMap().values()) {
if (missionData.isValidRefreshType()) {
List<BattlePassMissionData> triggerList =
getTriggers().computeIfAbsent(missionData.getTriggerType(), e -> new ArrayList<>());
triggerList.add(missionData);
}
}
}
public GameServer getServer() {
return server;
}
private Map<WatcherTriggerType, List<BattlePassMissionData>> getTriggers() {
return cachedTriggers;
}
public void triggerMission(Player player, WatcherTriggerType triggerType) {
triggerMission(player, triggerType, 0, 1);
}
public void triggerMission(
Player player, WatcherTriggerType triggerType, int param, int progress) {
List<BattlePassMissionData> triggerList = getTriggers().get(triggerType);
if (triggerList == null || triggerList.isEmpty()) return;
for (BattlePassMissionData data : triggerList) {
// Skip params check if param == 0
if (param != 0) {
if (!data.getMainParams().contains(param)) {
continue;
}
}
// Get mission from player, if it doesnt exist, then we make one
BattlePassMission mission = player.getBattlePassManager().loadMissionById(data.getId());
if (mission.isFinshed()) continue;
// Add progress
mission.addProgress(progress, data.getProgress());
if (mission.getProgress() >= data.getProgress()) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_FINISHED);
}
// Save to db
player.getBattlePassManager().save();
// Packet
player.sendPacket(new PacketBattlePassMissionUpdateNotify(mission));
}
}
}
package emu.grasscutter.game.battlepass;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BattlePassSystem extends BaseGameSystem {
private final Map<WatcherTriggerType, List<BattlePassMissionData>> cachedTriggers;
// BP Mission manager for the server, contains cached triggers so we dont have to load it for each
// player
public BattlePassSystem(GameServer server) {
super(server);
this.cachedTriggers = new HashMap<>();
for (BattlePassMissionData missionData : GameData.getBattlePassMissionDataMap().values()) {
if (missionData.isValidRefreshType()) {
List<BattlePassMissionData> triggerList =
getTriggers().computeIfAbsent(missionData.getTriggerType(), e -> new ArrayList<>());
triggerList.add(missionData);
}
}
}
public GameServer getServer() {
return server;
}
private Map<WatcherTriggerType, List<BattlePassMissionData>> getTriggers() {
return cachedTriggers;
}
public void triggerMission(Player player, WatcherTriggerType triggerType) {
triggerMission(player, triggerType, 0, 1);
}
public void triggerMission(
Player player, WatcherTriggerType triggerType, int param, int progress) {
List<BattlePassMissionData> triggerList = getTriggers().get(triggerType);
if (triggerList == null || triggerList.isEmpty()) return;
for (BattlePassMissionData data : triggerList) {
// Skip params check if param == 0
if (param != 0) {
if (!data.getMainParams().contains(param)) {
continue;
}
}
// Get mission from player, if it doesnt exist, then we make one
BattlePassMission mission = player.getBattlePassManager().loadMissionById(data.getId());
if (mission.isFinshed()) continue;
// Add progress
mission.addProgress(progress, data.getProgress());
if (mission.getProgress() >= data.getProgress()) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_FINISHED);
}
// Save to db
player.getBattlePassManager().save();
// Packet
player.sendPacket(new PacketBattlePassMissionUpdateNotify(mission));
}
}
}

View File

@ -1,215 +1,215 @@
package emu.grasscutter.game.chat;
import static emu.grasscutter.config.Configuration.GAME_INFO;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
import emu.grasscutter.server.packet.send.PacketPullPrivateChatRsp;
import emu.grasscutter.server.packet.send.PacketPullRecentChatRsp;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class ChatSystem implements ChatSystemHandler {
static final String PREFIXES = "[/!]";
static final Pattern RE_PREFIXES = Pattern.compile(PREFIXES);
static final Pattern RE_COMMANDS = Pattern.compile("\n" + PREFIXES);
// We store the chat history for ongoing sessions in the form
// user id -> chat partner id -> [messages]
private final Map<Integer, Map<Integer, List<ChatInfo>>> history = new HashMap<>();
private final GameServer server;
public ChatSystem(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
private boolean tryInvokeCommand(Player sender, Player target, String rawMessage) {
if (!RE_PREFIXES.matcher(rawMessage.substring(0, 1)).matches()) return false;
for (String line : rawMessage.substring(1).split("\n[/!]"))
CommandMap.getInstance().invoke(sender, target, line);
return true;
}
/********************
* Chat history handling
********************/
private void putInHistory(int uid, int partnerId, ChatInfo info) {
this.history
.computeIfAbsent(uid, x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>())
.add(info);
}
public void clearHistoryOnLogout(Player player) {
this.history.remove(player.getUid());
}
public void handlePullPrivateChatReq(Player player, int partnerId) {
var chatHistory =
this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>());
player.sendPacket(new PacketPullPrivateChatRsp(chatHistory));
}
public void handlePullRecentChatReq(Player player) {
// If this user has no chat history yet, create it by sending the server welcome messages.
if (!this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.containsKey(GameConstants.SERVER_CONSOLE_UID)) {
this.sendServerWelcomeMessages(player);
}
// For now, we send the list three messages from the server for the recent chat history.
// This matches the previous behavior, but ultimately, we should probably keep track of the last
// chat partner
// for every given player and return the last messages exchanged with that partner.
int historyLength =
this.history.get(player.getUid()).get(GameConstants.SERVER_CONSOLE_UID).size();
var messages =
this.history
.get(player.getUid())
.get(GameConstants.SERVER_CONSOLE_UID)
.subList(Math.max(historyLength - 3, 0), historyLength);
player.sendPacket(new PacketPullRecentChatRsp(messages));
}
/********************
* Sending messages
********************/
public void sendPrivateMessageFromServer(int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, message);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessageFromServer(int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, emote);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessage(Player player, int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), targetUid, message);
// Send and put in history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
// Check if command
boolean isCommand = tryInvokeCommand(player, target, message);
if ((target != null) && (!isCommand)) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendPrivateMessage(Player player, int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), emote);
// Send and put is history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
if (target != null) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendTeamMessage(Player player, int channel, String message) {
// Sanity checks
if (message == null || message.length() == 0) {
return;
}
// Check if command
if (tryInvokeCommand(player, null, message)) {
return;
}
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message));
}
public void sendTeamMessage(Player player, int channel, int icon) {
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon));
}
/********************
* Welcome messages
********************/
private void sendServerWelcomeMessages(Player player) {
var joinOptions = GAME_INFO.joinOptions;
if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) {
this.sendPrivateMessageFromServer(
player.getUid(),
joinOptions.welcomeEmotes[Utils.randomRange(0, joinOptions.welcomeEmotes.length - 1)]);
}
if (joinOptions.welcomeMessage != null && joinOptions.welcomeMessage.length() > 0) {
this.sendPrivateMessageFromServer(player.getUid(), joinOptions.welcomeMessage);
}
}
}
package emu.grasscutter.game.chat;
import static emu.grasscutter.config.Configuration.GAME_INFO;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
import emu.grasscutter.server.packet.send.PacketPullPrivateChatRsp;
import emu.grasscutter.server.packet.send.PacketPullRecentChatRsp;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class ChatSystem implements ChatSystemHandler {
static final String PREFIXES = "[/!]";
static final Pattern RE_PREFIXES = Pattern.compile(PREFIXES);
static final Pattern RE_COMMANDS = Pattern.compile("\n" + PREFIXES);
// We store the chat history for ongoing sessions in the form
// user id -> chat partner id -> [messages]
private final Map<Integer, Map<Integer, List<ChatInfo>>> history = new HashMap<>();
private final GameServer server;
public ChatSystem(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
private boolean tryInvokeCommand(Player sender, Player target, String rawMessage) {
if (!RE_PREFIXES.matcher(rawMessage.substring(0, 1)).matches()) return false;
for (String line : rawMessage.substring(1).split("\n[/!]"))
CommandMap.getInstance().invoke(sender, target, line);
return true;
}
/********************
* Chat history handling
********************/
private void putInHistory(int uid, int partnerId, ChatInfo info) {
this.history
.computeIfAbsent(uid, x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>())
.add(info);
}
public void clearHistoryOnLogout(Player player) {
this.history.remove(player.getUid());
}
public void handlePullPrivateChatReq(Player player, int partnerId) {
var chatHistory =
this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>());
player.sendPacket(new PacketPullPrivateChatRsp(chatHistory));
}
public void handlePullRecentChatReq(Player player) {
// If this user has no chat history yet, create it by sending the server welcome messages.
if (!this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.containsKey(GameConstants.SERVER_CONSOLE_UID)) {
this.sendServerWelcomeMessages(player);
}
// For now, we send the list three messages from the server for the recent chat history.
// This matches the previous behavior, but ultimately, we should probably keep track of the last
// chat partner
// for every given player and return the last messages exchanged with that partner.
int historyLength =
this.history.get(player.getUid()).get(GameConstants.SERVER_CONSOLE_UID).size();
var messages =
this.history
.get(player.getUid())
.get(GameConstants.SERVER_CONSOLE_UID)
.subList(Math.max(historyLength - 3, 0), historyLength);
player.sendPacket(new PacketPullRecentChatRsp(messages));
}
/********************
* Sending messages
********************/
public void sendPrivateMessageFromServer(int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, message);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessageFromServer(int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, emote);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessage(Player player, int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), targetUid, message);
// Send and put in history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
// Check if command
boolean isCommand = tryInvokeCommand(player, target, message);
if ((target != null) && (!isCommand)) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendPrivateMessage(Player player, int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), emote);
// Send and put is history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
if (target != null) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendTeamMessage(Player player, int channel, String message) {
// Sanity checks
if (message == null || message.length() == 0) {
return;
}
// Check if command
if (tryInvokeCommand(player, null, message)) {
return;
}
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message));
}
public void sendTeamMessage(Player player, int channel, int icon) {
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon));
}
/********************
* Welcome messages
********************/
private void sendServerWelcomeMessages(Player player) {
var joinOptions = GAME_INFO.joinOptions;
if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) {
this.sendPrivateMessageFromServer(
player.getUid(),
joinOptions.welcomeEmotes[Utils.randomRange(0, joinOptions.welcomeEmotes.length - 1)]);
}
if (joinOptions.welcomeMessage != null && joinOptions.welcomeMessage.length() > 0) {
this.sendPrivateMessageFromServer(player.getUid(), joinOptions.welcomeMessage);
}
}
}

View File

@ -1,26 +1,26 @@
package emu.grasscutter.game.chat;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
public interface ChatSystemHandler {
GameServer getServer();
void sendPrivateMessage(Player player, int targetUid, String message);
void sendPrivateMessage(Player player, int targetUid, int emote);
void sendTeamMessage(Player player, int channel, String message);
void sendTeamMessage(Player player, int channel, int icon);
void sendPrivateMessageFromServer(int targetUid, String message);
void sendPrivateMessageFromServer(int targetUid, int emote);
void handlePullPrivateChatReq(Player player, int targetUid);
void clearHistoryOnLogout(Player player);
void handlePullRecentChatReq(Player player);
}
package emu.grasscutter.game.chat;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
public interface ChatSystemHandler {
GameServer getServer();
void sendPrivateMessage(Player player, int targetUid, String message);
void sendPrivateMessage(Player player, int targetUid, int emote);
void sendTeamMessage(Player player, int channel, String message);
void sendTeamMessage(Player player, int channel, int icon);
void sendPrivateMessageFromServer(int targetUid, String message);
void sendPrivateMessageFromServer(int targetUid, int emote);
void handlePullPrivateChatReq(Player player, int targetUid);
void clearHistoryOnLogout(Player player);
void handlePullRecentChatReq(Player player);
}

View File

@ -1,143 +1,143 @@
package emu.grasscutter.game.combine;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.CombineData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify;
import emu.grasscutter.server.packet.send.PacketCombineRsp;
import emu.grasscutter.server.packet.send.PacketReliquaryDecomposeRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
public class CombineManger extends BaseGameSystem {
private static final Int2ObjectMap<List<Integer>> reliquaryDecomposeData =
new Int2ObjectOpenHashMap<>();
public CombineManger(GameServer server) {
super(server);
}
public static void initialize() {
// Read the data we need for strongbox.
try {
DataLoader.loadList("ReliquaryDecompose.json", ReliquaryDecomposeEntry.class)
.forEach(
entry -> {
reliquaryDecomposeData.put(entry.getConfigId(), entry.getItems());
});
Grasscutter.getLogger()
.debug("Loaded {} reliquary decompose entries.", reliquaryDecomposeData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load reliquary decompose data.", ex);
}
}
public boolean unlockCombineDiagram(Player player, int combineId) {
if (!player.getUnlockedCombines().add(combineId)) {
return false; // Already unlocked
}
// Tell the client that this diagram is now unlocked and add the unlocked item to the player.
player.sendPacket(new PacketCombineFormulaDataNotify(combineId));
return true;
}
public CombineResult combineItem(Player player, int cid, int count) {
// check config exist
if (!GameData.getCombineDataMap().containsKey(cid)) {
player.getWorld().getHost().sendPacket(new PacketCombineRsp());
return null;
}
CombineData combineData = GameData.getCombineDataMap().get(cid);
if (combineData.getPlayerLevel() > player.getLevel()) {
return null;
}
// consume items
List<ItemParamData> material = new ArrayList<>(combineData.getMaterialItems());
material.add(new ItemParamData(202, combineData.getScoinCost()));
boolean success = player.getInventory().payItems(material, count, ActionReason.Combine);
// abort if not enough material
if (!success) {
player.sendPacket(
new PacketCombineRsp(RetcodeOuterClass.Retcode.RET_ITEM_COMBINE_COUNT_NOT_ENOUGH_VALUE));
}
// make the result
player
.getInventory()
.addItem(combineData.getResultItemId(), combineData.getResultItemCount() * count);
CombineResult result = new CombineResult();
result.setMaterial(List.of());
result.setResult(
List.of(
new ItemParamData(
combineData.getResultItemId(), combineData.getResultItemCount() * count)));
// TODO lucky characters
result.setExtra(List.of());
result.setBack(List.of());
return result;
}
public synchronized void decomposeReliquaries(
Player player, int configId, int count, List<Long> input) {
// Check if the configId is legal.
List<Integer> possibleDrops = reliquaryDecomposeData.get(configId);
if (possibleDrops == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if the number of input items matches the output count.
if (input.size() != count * 3) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if all the input reliquaries actually are in the player's inventory.
for (long guid : input) {
if (player.getInventory().getItemByGuid(guid) == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
}
// Delete the input reliquaries.
for (long guid : input) {
player.getInventory().removeItem(guid);
}
// Generate outoput reliquaries.
List<Long> resultItems = new ArrayList<>();
for (int i = 0; i < count; i++) {
int itemId = Utils.drawRandomListElement(possibleDrops);
GameItem newReliquary = new GameItem(itemId, 1);
player.getInventory().addItem(newReliquary);
resultItems.add(newReliquary.getGuid());
}
// Send packet.
player.sendPacket(new PacketReliquaryDecomposeRsp(resultItems));
}
}
package emu.grasscutter.game.combine;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.CombineData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify;
import emu.grasscutter.server.packet.send.PacketCombineRsp;
import emu.grasscutter.server.packet.send.PacketReliquaryDecomposeRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
public class CombineManger extends BaseGameSystem {
private static final Int2ObjectMap<List<Integer>> reliquaryDecomposeData =
new Int2ObjectOpenHashMap<>();
public CombineManger(GameServer server) {
super(server);
}
public static void initialize() {
// Read the data we need for strongbox.
try {
DataLoader.loadList("ReliquaryDecompose.json", ReliquaryDecomposeEntry.class)
.forEach(
entry -> {
reliquaryDecomposeData.put(entry.getConfigId(), entry.getItems());
});
Grasscutter.getLogger()
.debug("Loaded {} reliquary decompose entries.", reliquaryDecomposeData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load reliquary decompose data.", ex);
}
}
public boolean unlockCombineDiagram(Player player, int combineId) {
if (!player.getUnlockedCombines().add(combineId)) {
return false; // Already unlocked
}
// Tell the client that this diagram is now unlocked and add the unlocked item to the player.
player.sendPacket(new PacketCombineFormulaDataNotify(combineId));
return true;
}
public CombineResult combineItem(Player player, int cid, int count) {
// check config exist
if (!GameData.getCombineDataMap().containsKey(cid)) {
player.getWorld().getHost().sendPacket(new PacketCombineRsp());
return null;
}
CombineData combineData = GameData.getCombineDataMap().get(cid);
if (combineData.getPlayerLevel() > player.getLevel()) {
return null;
}
// consume items
List<ItemParamData> material = new ArrayList<>(combineData.getMaterialItems());
material.add(new ItemParamData(202, combineData.getScoinCost()));
boolean success = player.getInventory().payItems(material, count, ActionReason.Combine);
// abort if not enough material
if (!success) {
player.sendPacket(
new PacketCombineRsp(RetcodeOuterClass.Retcode.RET_ITEM_COMBINE_COUNT_NOT_ENOUGH_VALUE));
}
// make the result
player
.getInventory()
.addItem(combineData.getResultItemId(), combineData.getResultItemCount() * count);
CombineResult result = new CombineResult();
result.setMaterial(List.of());
result.setResult(
List.of(
new ItemParamData(
combineData.getResultItemId(), combineData.getResultItemCount() * count)));
// TODO lucky characters
result.setExtra(List.of());
result.setBack(List.of());
return result;
}
public synchronized void decomposeReliquaries(
Player player, int configId, int count, List<Long> input) {
// Check if the configId is legal.
List<Integer> possibleDrops = reliquaryDecomposeData.get(configId);
if (possibleDrops == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if the number of input items matches the output count.
if (input.size() != count * 3) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if all the input reliquaries actually are in the player's inventory.
for (long guid : input) {
if (player.getInventory().getItemByGuid(guid) == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
}
// Delete the input reliquaries.
for (long guid : input) {
player.getInventory().removeItem(guid);
}
// Generate outoput reliquaries.
List<Long> resultItems = new ArrayList<>();
for (int i = 0; i < count; i++) {
int itemId = Utils.drawRandomListElement(possibleDrops);
GameItem newReliquary = new GameItem(itemId, 1);
player.getInventory().addItem(newReliquary);
resultItems.add(newReliquary.getGuid());
}
// Send packet.
player.sendPacket(new PacketReliquaryDecomposeRsp(resultItems));
}
}

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