Merge branch 'development' into more-events

# Conflicts:
#	src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java
#	src/main/java/emu/grasscutter/game/entity/EntityAvatar.java
#	src/main/java/emu/grasscutter/game/entity/GameEntity.java
#	src/main/java/emu/grasscutter/game/managers/mapmark/MapMarksManager.java
This commit is contained in:
KingRainbow44 2022-07-22 17:52:58 -04:00
commit 956d4023c7
257 changed files with 17788 additions and 16667 deletions

View File

@ -32,10 +32,10 @@ jobs:
# - run: git merge development # - run: git merge development
- run: git reset --hard development - run: git reset --hard development
- run: git stash pop - run: git stash pop
- run: git add -u - name: Commit any whitespace changes
- run: git commit -m 'Fix whitespace [skip actions]' run: git add -u && git commit -m 'Fix whitespace [skip actions]' || true
- name: Update Languages - name: Update Languages
run: python manage_languages.py -u run: python manage_languages.py -u
- run: git add -u - name: Commit any language changes
- run: git commit -m 'Update languages [skip actions]' run: git add -u && git commit -m 'Update languages [skip actions]' || true
- run: git push --set-upstream --force origin LintRatchet - run: git push --set-upstream --force origin LintRatchet

View File

@ -43,8 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
group = 'xyz.grasscutters' group = 'xyz.grasscutters'
version = '1.2.2-dev' version = '1.2.3-dev'
sourceCompatibility = 17 sourceCompatibility = 17
targetCompatibility = 17 targetCompatibility = 17
@ -61,15 +60,18 @@ repositories {
dependencies { dependencies {
implementation fileTree(dir: 'lib', include: ['*.jar']) implementation fileTree(dir: 'lib', include: ['*.jar'])
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32' implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.36'
implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.9' implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.11'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.9' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.11'
implementation group: 'org.jline', name: 'jline', version: '3.21.0' implementation group: 'org.jline', name: 'jline', version: '3.21.0'
implementation group: 'org.jline', name: 'jline-terminal-jna', version: '3.21.0' implementation group: 'org.jline', name: 'jline-terminal-jna', version: '3.21.0'
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.10.0' implementation group: 'net.java.dev.jna', name: 'jna', version: '5.10.0'
implementation group: 'io.netty', name: 'netty-all', version: '4.1.71.Final' implementation group: 'io.netty', name: 'netty-common', version: '4.1.79.Final'
implementation group: 'io.netty', name: 'netty-handler', version: '4.1.79.Final'
implementation group: 'io.netty', name: 'netty-transport-native-epoll', version: '4.1.79.Final'
implementation group: 'io.netty', name: 'netty-transport-native-kqueue', version: '4.1.79.Final'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0'
implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.18.2' implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.18.2'
@ -79,7 +81,7 @@ dependencies {
implementation group: 'dev.morphia.morphia', name: 'morphia-core', version: '2.2.7' implementation group: 'dev.morphia.morphia', name: 'morphia-core', version: '2.2.7'
implementation group: 'org.greenrobot', name: 'eventbus-java', version: '3.3.1' implementation group: 'org.greenrobot', name: 'eventbus-java', version: '3.3.1'
implementation group: 'org.danilopianini', name: 'java-quadtree', version: '0.1.9' //implementation group: 'org.danilopianini', name: 'java-quadtree', version: '0.1.9'
implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2' implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2'
implementation group: 'org.quartz-scheduler', name: 'quartz-jobs', version: '2.3.2' implementation group: 'org.quartz-scheduler', name: 'quartz-jobs', version: '2.3.2'

BIN
lib/bcrypt-0.8.0.jar Normal file

Binary file not shown.

BIN
lib/bytes-1.3.0.jar Normal file

Binary file not shown.

View File

@ -90,6 +90,7 @@ class JsonHelpers:
class LanguageManager: class LanguageManager:
TRANSLATION_KEY = re.compile(r'[Tt]ranslate.*"(\w+\.[\w\.]+)"') TRANSLATION_KEY = re.compile(r'[Tt]ranslate.*"(\w+\.[\w\.]+)"')
POTENTIAL_KEY = re.compile(r'"(\w+\.[\w\.]+)"') POTENTIAL_KEY = re.compile(r'"(\w+\.[\w\.]+)"')
COMMAND_LABEL = re.compile(r'@Command\s*\([\W\w]*?label\s*=\s*"(\w+)"', re.MULTILINE) # [\W\w] is a cheeky way to match everything including \n
def __init__(self): def __init__(self):
self.load_jsons() self.load_jsons()
@ -122,6 +123,8 @@ class LanguageManager:
used.add(k) used.add(k)
for k in self.POTENTIAL_KEY.findall(data): for k in self.POTENTIAL_KEY.findall(data):
potential.add(k) potential.add(k)
for label in self.COMMAND_LABEL.findall(data):
used.add(f'commands.{label}.description')
return used | (potential & expected_keys) return used | (potential & expected_keys)
def _lint_report_language(self, lang: str, keys: set, flattened: dict, primary_language_flattened: dict) -> None: def _lint_report_language(self, lang: str, keys: set, flattened: dict, primary_language_flattened: dict) -> None:

View File

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

View File

@ -5,14 +5,15 @@ import ch.qos.logback.classic.Logger;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.auth.DefaultAuthentication;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.command.DefaultPermissionHandler; import emu.grasscutter.command.DefaultPermissionHandler;
import emu.grasscutter.command.PermissionHandler; import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.config.ConfigContainer;
import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseManager; import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.net.packet.PacketOpcodesUtils;
import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.PluginManager;
import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.plugin.api.ServerHook;
import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.scripts.ScriptLoader;
@ -26,7 +27,6 @@ import emu.grasscutter.server.http.handlers.GachaHandler;
import emu.grasscutter.server.http.handlers.GenericHandler; import emu.grasscutter.server.http.handlers.GenericHandler;
import emu.grasscutter.server.http.handlers.LogHandler; import emu.grasscutter.server.http.handlers.LogHandler;
import emu.grasscutter.tools.Tools; import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.ConfigContainer;
import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
@ -43,8 +43,8 @@ import javax.annotation.Nullable;
import java.io.*; import java.io.*;
import java.util.Calendar; import java.util.Calendar;
import static emu.grasscutter.Configuration.DATA; import static emu.grasscutter.config.Configuration.DATA;
import static emu.grasscutter.Configuration.SERVER; import static emu.grasscutter.config.Configuration.SERVER;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
public final class Grasscutter { public final class Grasscutter {
@ -98,6 +98,10 @@ public final class Grasscutter {
Tools.createGmHandbook(); Tools.createGmHandbook();
exitEarly = true; exitEarly = true;
} }
case "-dumppacketids" -> {
PacketOpcodesUtils.dumpPacketIds();
exitEarly = true;
}
case "-gachamap" -> { case "-gachamap" -> {
Tools.createGachaMapping(DATA("gacha_mappings.js")); Tools.createGachaMapping(DATA("gacha_mappings.js"));
exitEarly = true; exitEarly = true;
@ -213,7 +217,7 @@ public final class Grasscutter {
*/ */
private static void onShutdown() { private static void onShutdown() {
// Disable all plugins. // Disable all plugins.
if(pluginManager != null) if (pluginManager != null)
pluginManager.disablePlugins(); pluginManager.disablePlugins();
} }

View File

@ -6,6 +6,7 @@ import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.ComboTokenResJson; import emu.grasscutter.server.http.objects.ComboTokenResJson;
import emu.grasscutter.server.http.objects.LoginResultJson; import emu.grasscutter.server.http.objects.LoginResultJson;
import static emu.grasscutter.config.Configuration.ACCOUNT;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
/** /**
@ -13,12 +14,20 @@ import static emu.grasscutter.utils.Language.translate;
* Allows all users to access any account. * Allows all users to access any account.
*/ */
public final class DefaultAuthentication implements AuthenticationSystem { public final class DefaultAuthentication implements AuthenticationSystem {
private final Authenticator<LoginResultJson> passwordAuthenticator = new PasswordAuthenticator(); private Authenticator<LoginResultJson> passwordAuthenticator;
private final Authenticator<LoginResultJson> tokenAuthenticator = new TokenAuthenticator(); private Authenticator<LoginResultJson> tokenAuthenticator = new TokenAuthenticator();
private final Authenticator<ComboTokenResJson> sessionKeyAuthenticator = new SessionKeyAuthenticator(); private Authenticator<ComboTokenResJson> sessionKeyAuthenticator = new SessionKeyAuthenticator();
private final ExternalAuthenticator externalAuthenticator = new ExternalAuthentication(); private ExternalAuthenticator externalAuthenticator = new ExternalAuthentication();
private final OAuthAuthenticator oAuthAuthenticator = new OAuthAuthentication(); private OAuthAuthenticator oAuthAuthenticator = new OAuthAuthentication();
public DefaultAuthentication() {
if (ACCOUNT.EXPERIMENTAL_RealPassword) {
passwordAuthenticator = new ExperimentalPasswordAuthenticator();
} else {
passwordAuthenticator = new PasswordAuthenticator();
}
}
@Override @Override
public void createAccount(String username, String password) { public void createAccount(String username, String password) {
// Unhandled. The default authenticator doesn't store passwords. // Unhandled. The default authenticator doesn't store passwords.

View File

@ -1,31 +1,41 @@
package emu.grasscutter.auth; package emu.grasscutter.auth;
import at.favre.lib.crypto.bcrypt.BCrypt;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest; import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.*; import emu.grasscutter.server.http.objects.*;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import static emu.grasscutter.Configuration.*; import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
/** /**
* A class containing default authenticators. * A class containing default authenticators.
*/ */
public final class DefaultAuthenticators { public final class DefaultAuthenticators {
/** /**
* Handles the authentication request from the username and password form. * Handles the authentication request from the username and password form.
*/ */
public static class PasswordAuthenticator implements Authenticator<LoginResultJson> { public static class PasswordAuthenticator implements Authenticator<LoginResultJson> {
@Override public LoginResultJson authenticate(AuthenticationRequest request) { @Override
public LoginResultJson authenticate(AuthenticationRequest request) {
var response = new LoginResultJson(); var response = new LoginResultJson();
var requestData = request.getPasswordRequest(); var requestData = request.getPasswordRequest();
assert requestData != null; // This should never be null. assert requestData != null; // This should never be null.
int playerCount = Grasscutter.getGameServer().getPlayers().size(); int playerCount = Grasscutter.getGameServer().getPlayers().size();
boolean successfulLogin = false; boolean successfulLogin = false;
String address = request.getRequest().ip(); String address = request.getRequest().ip();
String responseMessage = translate("messages.dispatch.account.username_error"); String responseMessage = translate("messages.dispatch.account.username_error");
String loggerMessage = ""; String loggerMessage = "";
@ -34,12 +44,12 @@ public final class DefaultAuthenticators {
Account account = DatabaseHelper.getAccountByName(requestData.account); Account account = DatabaseHelper.getAccountByName(requestData.account);
if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) { if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
// Check if account exists. // Check if account exists.
if(account == null && ACCOUNT.autoCreate) { if (account == null && ACCOUNT.autoCreate) {
// This account has been created AUTOMATICALLY. There will be no permissions added. // This account has been created AUTOMATICALLY. There will be no permissions added.
account = DatabaseHelper.createAccountWithUid(requestData.account, 0); account = DatabaseHelper.createAccountWithUid(requestData.account, 0);
// Check if the account was created successfully. // Check if the account was created successfully.
if(account == null) { if (account == null) {
responseMessage = translate("messages.dispatch.account.username_create_error"); responseMessage = translate("messages.dispatch.account.username_create_error");
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", address)); Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", address));
} else { } else {
@ -49,9 +59,9 @@ public final class DefaultAuthenticators {
// Log the creation. // Log the creation.
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", address, response.data.account.uid)); Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", address, response.data.account.uid));
} }
} else if(account != null) } else if (account != null)
successfulLogin = true; successfulLogin = true;
else else
loggerMessage = translate("messages.dispatch.account.account_login_exist_error", address); loggerMessage = translate("messages.dispatch.account.account_login_exist_error", address);
} else { } else {
@ -59,9 +69,113 @@ public final class DefaultAuthenticators {
loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address); loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
} }
// Set response data. // Set response data.
if(successfulLogin) { if (successfulLogin) {
response.message = "OK";
response.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.getRequest().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.message = "OK";
response.data.account.uid = account.getId(); response.data.account.uid = account.getId();
response.data.account.token = account.generateSessionKey(); response.data.account.token = account.generateSessionKey();
@ -83,9 +197,10 @@ public final class DefaultAuthenticators {
* Handles the authentication request from the game when using a registry token. * Handles the authentication request from the game when using a registry token.
*/ */
public static class TokenAuthenticator implements Authenticator<LoginResultJson> { public static class TokenAuthenticator implements Authenticator<LoginResultJson> {
@Override public LoginResultJson authenticate(AuthenticationRequest request) { @Override
public LoginResultJson authenticate(AuthenticationRequest request) {
var response = new LoginResultJson(); var response = new LoginResultJson();
var requestData = request.getTokenRequest(); var requestData = request.getTokenRequest();
assert requestData != null; assert requestData != null;
@ -106,7 +221,7 @@ public final class DefaultAuthenticators {
successfulLogin = account != null && account.getSessionKey().equals(requestData.token); successfulLogin = account != null && account.getSessionKey().equals(requestData.token);
// Set response data. // Set response data.
if(successfulLogin) { if (successfulLogin) {
response.message = "OK"; response.message = "OK";
response.data.account.uid = account.getId(); response.data.account.uid = account.getId();
response.data.account.token = account.getSessionKey(); response.data.account.token = account.getSessionKey();
@ -138,13 +253,15 @@ public final class DefaultAuthenticators {
* Handles the authentication request from the game when using a combo token/session key. * Handles the authentication request from the game when using a combo token/session key.
*/ */
public static class SessionKeyAuthenticator implements Authenticator<ComboTokenResJson> { public static class SessionKeyAuthenticator implements Authenticator<ComboTokenResJson> {
@Override public ComboTokenResJson authenticate(AuthenticationRequest request) { @Override
var response = new ComboTokenResJson(); public ComboTokenResJson authenticate(AuthenticationRequest request) {
var response = new ComboTokenResJson();
var requestData = request.getSessionKeyRequest(); var requestData = request.getSessionKeyRequest();
var loginData = request.getSessionKeyData(); var loginData = request.getSessionKeyData();
assert requestData != null; assert loginData != null; assert requestData != null;
assert loginData != null;
boolean successfulLogin; boolean successfulLogin;
String address = request.getRequest().ip(); String address = request.getRequest().ip();
String loggerMessage; String loggerMessage;
@ -158,7 +275,7 @@ public final class DefaultAuthenticators {
successfulLogin = account != null && account.getSessionKey().equals(loginData.token); successfulLogin = account != null && account.getSessionKey().equals(loginData.token);
// Set response data. // Set response data.
if(successfulLogin) { if (successfulLogin) {
response.message = "OK"; response.message = "OK";
response.data.open_id = account.getId(); response.data.open_id = account.getId();
response.data.combo_id = "157795300"; response.data.combo_id = "157795300";
@ -190,17 +307,20 @@ public final class DefaultAuthenticators {
* Handles authentication requests from external sources. * Handles authentication requests from external sources.
*/ */
public static class ExternalAuthentication implements ExternalAuthenticator { public static class ExternalAuthentication implements ExternalAuthenticator {
@Override public void handleLogin(AuthenticationRequest request) { @Override
public void handleLogin(AuthenticationRequest request) {
assert request.getResponse() != null; assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method."); request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@Override public void handleAccountCreation(AuthenticationRequest request) { @Override
public void handleAccountCreation(AuthenticationRequest request) {
assert request.getResponse() != null; assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method."); request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@Override public void handlePasswordReset(AuthenticationRequest request) { @Override
public void handlePasswordReset(AuthenticationRequest request) {
assert request.getResponse() != null; assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method."); request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@ -210,17 +330,20 @@ public final class DefaultAuthenticators {
* Handles authentication requests from OAuth sources. * Handles authentication requests from OAuth sources.
*/ */
public static class OAuthAuthentication implements OAuthAuthenticator { public static class OAuthAuthentication implements OAuthAuthenticator {
@Override public void handleLogin(AuthenticationRequest request) { @Override
public void handleLogin(AuthenticationRequest request) {
assert request.getResponse() != null; assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method."); request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@Override public void handleRedirection(AuthenticationRequest request, ClientType type) { @Override
public void handleRedirection(AuthenticationRequest request, ClientType type) {
assert request.getResponse() != null; assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method."); request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@Override public void handleTokenProcess(AuthenticationRequest request) { @Override
public void handleTokenProcess(AuthenticationRequest request) {
assert request.getResponse() != null; assert request.getResponse() != null;
request.getResponse().send("Authentication is not available with the default authentication method."); request.getResponse().send("Authentication is not available with the default authentication method.");
} }

View File

@ -7,14 +7,12 @@ import java.lang.annotation.RetentionPolicy;
public @interface Command { public @interface Command {
String label() default ""; String label() default "";
String usage() default "commands.generic.no_usage_specified";
String description() default "commands.generic.no_description_specified";
String[] aliases() default {}; String[] aliases() default {};
String[] usage() default {""};
String permission() default ""; String permission() default "";
String permissionTargeted() default ""; String permissionTargeted() default "";
public enum TargetRequirement { public enum TargetRequirement {

View File

@ -2,12 +2,11 @@ package emu.grasscutter.command;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.game.CommandResponseEvent;
import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent; import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent;
import emu.grasscutter.server.event.types.ServerEvent;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
public interface CommandHandler { public interface CommandHandler {
@ -37,6 +36,41 @@ public interface CommandHandler {
sendMessage(player, translate(player, messageKey, 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;
}
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 getDescriptionString(Player player) {
Command annotation = this.getClass().getAnnotation(Command.class);
String key = "commands.%s.description".formatted(annotation.label());
return translate(player, key);
}
/** /**
* Called when a player/console invokes a command. * Called when a player/console invokes a command.
* @param sender The player/console that invoked the command. * @param sender The player/console that invoked the command.

View File

@ -8,9 +8,9 @@ import java.util.*;
@SuppressWarnings({"UnusedReturnValue", "unused"}) @SuppressWarnings({"UnusedReturnValue", "unused"})
public final class CommandMap { public final class CommandMap {
private final Map<String, CommandHandler> commands = new HashMap<>(); private final Map<String, CommandHandler> commands = new TreeMap<>();
private final Map<String, CommandHandler> aliases = new HashMap<>(); private final Map<String, CommandHandler> aliases = new TreeMap<>();
private final Map<String, Command> annotations = new HashMap<>(); private final Map<String, Command> annotations = new TreeMap<>();
private final Map<String, Integer> targetPlayerIds = new HashMap<>(); private final Map<String, Integer> targetPlayerIds = new HashMap<>();
private static final String consoleId = "console"; private static final String consoleId = "console";
@ -35,6 +35,7 @@ public final class CommandMap {
*/ */
public CommandMap registerCommand(String label, CommandHandler command) { public CommandMap registerCommand(String label, CommandHandler command) {
Grasscutter.getLogger().debug("Registered command: " + label); Grasscutter.getLogger().debug("Registered command: " + label);
label = label.toLowerCase();
// Get command data. // Get command data.
Command annotation = command.getClass().getAnnotation(Command.class); Command annotation = command.getClass().getAnnotation(Command.class);
@ -167,7 +168,7 @@ public final class CommandMap {
CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target"); CommandHandler.sendTranslatedMessage(player, "commands.execution.clear_target");
return true; return true;
} }
// Sets default targetPlayer to the UID provided. // Sets default targetPlayer to the UID provided.
try { try {
int uid = Integer.parseInt(targetUid); int uid = Integer.parseInt(targetUid);
@ -203,7 +204,7 @@ public final class CommandMap {
// Parse message. // Parse message.
String[] split = rawMessage.split(" "); String[] split = rawMessage.split(" ");
List<String> args = new LinkedList<>(Arrays.asList(split)); List<String> args = new LinkedList<>(Arrays.asList(split));
String label = args.remove(0); String label = args.remove(0).toLowerCase();
String playerId = (player == null) ? consoleId : player.getAccount().getId(); String playerId = (player == null) ? consoleId : player.getAccount().getId();
// Check for special cases - currently only target command. // Check for special cases - currently only target command.
@ -237,7 +238,7 @@ public final class CommandMap {
Command annotation = this.annotations.get(label); Command annotation = this.annotations.get(label);
// Resolve targetPlayer // Resolve targetPlayer
try{ try {
targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args); targetPlayer = getTargetPlayer(playerId, player, targetPlayer, args);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return; return;

View File

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

View File

@ -13,31 +13,30 @@ import java.util.Random;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "announce", @Command(label = "announce",
usage = "announce|a <\"tpl\" templateId|\"refresh\"|\"revoke\" templateId|content>", usage = {"<content>", "refresh", "(tpl|revoke) <templateId>"},
permission = "server.announce", permission = "server.announce",
aliases = {"a"}, aliases = {"a"},
description = "commands.announce.description",
targetRequirement = Command.TargetRequirement.NONE) targetRequirement = Command.TargetRequirement.NONE)
public final class AnnounceCommand implements CommandHandler { public final class AnnounceCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
var manager = Grasscutter.getGameServer().getAnnouncementManager(); var manager = Grasscutter.getGameServer().getAnnouncementSystem();
if (args.size() < 1) { if (args.size() < 1) {
CommandHandler.sendTranslatedMessage(sender, "commands.announce.command_usage"); sendUsageMessage(sender);
return; return;
} }
switch (args.get(0)){ switch (args.get(0)) {
case "tpl": case "tpl":
if (args.size() < 2) { if (args.size() < 2) {
CommandHandler.sendTranslatedMessage(sender, "commands.announce.command_usage"); sendUsageMessage(sender);
return; return;
} }
var templateId = Integer.parseInt(args.get(1)); var templateId = Integer.parseInt(args.get(1));
var tpl = manager.getAnnounceConfigItemMap().get(templateId); var tpl = manager.getAnnounceConfigItemMap().get(templateId);
if(tpl == null){ if (tpl == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.announce.not_found", templateId)); CommandHandler.sendMessage(sender, translate(sender, "commands.announce.not_found", templateId));
return; return;
} }
@ -53,7 +52,7 @@ public final class AnnounceCommand implements CommandHandler {
case "revoke": case "revoke":
if (args.size() < 2) { if (args.size() < 2) {
CommandHandler.sendTranslatedMessage(sender, "commands.announce.command_usage"); sendUsageMessage(sender);
return; return;
} }

View File

@ -10,8 +10,7 @@ import emu.grasscutter.server.game.GameSession;
@Command( @Command(
label = "ban", label = "ban",
usage = "ban <@player> [time] [reason]", usage = {"[<time> [<reason>]]"},
description = "commands.ban.description",
permission = "server.ban", permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER targetRequirement = Command.TargetRequirement.PLAYER
) )

View File

@ -12,10 +12,11 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
@Command(label = "clear", usage = "clear <all|wp|art|mat> [lv<max level>] [r<max refinement>] [<max rarity>*]", @Command(
description = "commands.clear.description", label = "clear",
aliases = {"clear"}, permission = "player.clearinv", permissionTargeted = "player.clearinv.others") 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 { public final class ClearCommand implements CommandHandler {
private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :( private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :(
private static Pattern refineRegex = Pattern.compile("r(\\d+)"); private static Pattern refineRegex = Pattern.compile("r(\\d+)");
@ -82,7 +83,7 @@ public final class ClearCommand implements CommandHandler {
} }
if (args.size() < 1) { if (args.size() < 1) {
CommandHandler.sendTranslatedMessage(sender, "commands.clear.command_usage"); sendUsageMessage(sender);
return; return;
} }

View File

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

View File

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

View File

@ -23,8 +23,15 @@ import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@Command(label = "give", usage = "give <itemId|avatarId|\"all\"|\"weapons\"|\"mats\"|\"avatars\"> [lv<level>] [r<refinement>] [x<amount>] | give <artifactId> [lv<level>] [x<amount>] [mainPropId] [<appendPropId>[,<times>]]...", aliases = { @Command(
"g", "item", "giveitem"}, permission = "player.give", permissionTargeted = "player.give.others", description = "commands.give.description") label = "give",
aliases = {"g", "item", "giveitem"},
usage = {
"(<itemId>|<avatarId>|all|weapons|mats|avatars) [lv<level>] [r<refinement>] [x<amount>] [c<constellation>]",
"<artifactId> [lv<level>] [x<amount>] [<mainPropId>] [<appendPropId>[,<times>]]..."},
permission = "player.give",
permissionTargeted = "player.give.others",
threading = true)
public final class GiveCommand implements CommandHandler { public final class GiveCommand implements CommandHandler {
private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :( private static Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java doesn't have raw string literals :(
private static Pattern refineRegex = Pattern.compile("r(\\d+)"); private static Pattern refineRegex = Pattern.compile("r(\\d+)");
@ -60,7 +67,7 @@ public final class GiveCommand implements CommandHandler {
public GiveAllType giveAllType = GiveAllType.NONE; public GiveAllType giveAllType = GiveAllType.NONE;
}; };
private static GiveItemParameters parseArgs(Player sender, List<String> args) throws IllegalArgumentException { private GiveItemParameters parseArgs(Player sender, List<String> args) throws IllegalArgumentException {
GiveItemParameters param = new GiveItemParameters(); GiveItemParameters param = new GiveItemParameters();
// Extract any tagged arguments (e.g. "lv90", "x100", "r5") // Extract any tagged arguments (e.g. "lv90", "x100", "r5")
@ -92,7 +99,7 @@ public final class GiveCommand implements CommandHandler {
// At this point, first remaining argument MUST be itemId/avatarId // At this point, first remaining argument MUST be itemId/avatarId
if (args.size() < 1) { if (args.size() < 1) {
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); // Reachable if someone does `/give lv90` or similar sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
String id = args.remove(0); String id = args.remove(0);
@ -162,7 +169,7 @@ public final class GiveCommand implements CommandHandler {
throw e; throw e;
} }
} else { } else {
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); sendUsageMessage(sender);
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
} }
@ -173,7 +180,7 @@ public final class GiveCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) { // *No args* if (args.size() < 1) { // *No args*
CommandHandler.sendTranslatedMessage(sender, "commands.give.usage"); sendUsageMessage(sender);
return; return;
} }
try { try {
@ -257,7 +264,7 @@ public final class GiveCommand implements CommandHandler {
if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) { if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_MALE) {
avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504)); avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(504));
} }
else if(avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) { else if (avatar.getAvatarId() == GameConstants.MAIN_CHARACTER_FEMALE) {
avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704)); avatar.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(704));
} }
@ -352,11 +359,11 @@ public final class GiveCommand implements CommandHandler {
return affixes; 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. // If the given substat text is an integer, we just use that as the append prop ID.
try { try {
return Integer.parseInt(substatText); return Integer.parseInt(substatText);
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
// If the argument was not an integer, we try to determine // If the argument was not an integer, we try to determine
// the append prop ID from the given text + artifact information. // the append prop ID from the given text + artifact information.
// A substat string has the format `substat_tier`, with the // A substat string has the format `substat_tier`, with the
@ -378,8 +385,8 @@ public final class GiveCommand implements CommandHandler {
substatTier -= 1; // 1-indexed to 0-indexed substatTier -= 1; // 1-indexed to 0-indexed
substatTier = Math.min(Math.max(0, substatTier), substats.size() - 1); substatTier = Math.min(Math.max(0, substatTier), substats.size() - 1);
return substats.get(substatTier); 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. // Get the main stat from the arguments.
@ -476,11 +483,11 @@ public final class GiveCommand implements CommandHandler {
10000-10008, 11411, 11506-11508, 12505, 12506, 12508, 12509, 10000-10008, 11411, 11506-11508, 12505, 12506, 12508, 12509,
13503, 13506, 14411, 14503, 14505, 14508, 15504-15506 13503, 13506, 14411, 14503, 14505, 14508, 15504-15506
"""); """);
private static final SparseSet illegalRelicIds = new SparseSet(""" private static final SparseSet illegalRelicIds = new SparseSet("""
20001, 23300-23340, 23383-23385, 78310-78554, 99310-99554 20001, 23300-23340, 23383-23385, 78310-78554, 99310-99554
"""); """);
private static final SparseSet illegalItemIds = new SparseSet(""" private static final SparseSet illegalItemIds = new SparseSet("""
100086, 100087, 100100-101000, 101106-101110, 101306, 101500-104000, 100086, 100087, 100100-101000, 101106-101110, 101306, 101500-104000,
105001, 105004, 106000-107000, 107011, 108000, 109000-110000, 105001, 105004, 106000-107000, 107011, 108000, 109000-110000,

View File

@ -11,7 +11,7 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "heal", usage = "heal|h", aliases = {"h"}, permission = "player.heal", permissionTargeted = "player.heal.others", description = "commands.heal.description") @Command(label = "heal", aliases = {"h"}, permission = "player.heal", permissionTargeted = "player.heal.others")
public final class HealCommand implements CommandHandler { public final class HealCommand implements CommandHandler {
@Override @Override

View File

@ -3,80 +3,81 @@ package emu.grasscutter.command.commands;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import java.util.*; import java.util.*;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "help", usage = "help [command]", description = "commands.help.description", targetRequirement = Command.TargetRequirement.NONE) @Command(label = "help", usage = {"[<command>]"}, targetRequirement = Command.TargetRequirement.NONE)
public final class HelpCommand implements CommandHandler { public final class HelpCommand implements CommandHandler {
private final boolean SHOW_COMMANDS_WITHOUT_PERMISSIONS = false; // TODO: Make this into a server config key
private void createCommand(StringBuilder builder, Player player, Command annotation) { private String createCommand(Player player, CommandHandler command, List<String> args) {
builder.append("\n").append(annotation.label()).append(" - ").append(translate(player, annotation.description())); StringBuilder builder = new StringBuilder(command.getLabel())
builder.append("\n\t").append(translate(player, "commands.help.usage")); .append(" - ")
if (annotation.aliases().length >= 1) { .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")); builder.append("\n\t").append(translate(player, "commands.help.aliases"));
for (String alias : annotation.aliases()) { for (String alias : annotation.aliases()) {
builder.append(alias).append(" "); builder.append(alias).append(" ");
} }
} }
builder.append("\n\t").append(translate(player, "commands.help.tip_need_permission")); builder.append("\n\t").append(translate(player, "commands.help.tip_need_permission"));
if(annotation.permission().isEmpty() || annotation.permission().isBlank()) { if (!annotation.permission().isEmpty()) {
builder.append(translate(player, "commands.help.tip_need_no_permission"));
} else {
builder.append(annotation.permission()); builder.append(annotation.permission());
} else {
builder.append(translate(player, "commands.help.tip_need_no_permission"));
} }
if(!annotation.permissionTargeted().isEmpty() && !annotation.permissionTargeted().isBlank()) { if (!annotation.permissionTargeted().isEmpty()) {
String permissionTargeted = annotation.permissionTargeted(); String permissionTargeted = annotation.permissionTargeted();
builder.append(" ").append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted)); builder.append(" ").append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted));
} }
return builder.toString();
} }
@Override @Override
public void execute(Player player, Player targetPlayer, List<String> args) { public void execute(Player player, Player targetPlayer, List<String> args) {
if (args.size() < 1) { Account account = (player == null) ? null : player.getAccount();
HashMap<String, CommandHandler> handlers = CommandMap.getInstance().getHandlers(); Map<String, CommandHandler> handlers = CommandMap.getInstance().getHandlers();
List<Command> annotations = new ArrayList<>(); List<String> commands = new ArrayList<>();
List<String> commands_no_permission = new ArrayList<>();
if (args.isEmpty()) {
for (String key : handlers.keySet()) { for (String key : handlers.keySet()) {
Command annotation = handlers.get(key).getClass().getAnnotation(Command.class); CommandHandler command = handlers.get(key);
Command annotation = command.getClass().getAnnotation(Command.class);
if (!Arrays.asList(annotation.aliases()).contains(key)) { if (player == null || account.hasPermission(annotation.permission())) {
if (player != null && !Objects.equals(annotation.permission(), "") && !player.getAccount().hasPermission(annotation.permission())) commands.add(createCommand(player, command, args));
continue; } else if (SHOW_COMMANDS_WITHOUT_PERMISSIONS) {
annotations.add(annotation); commands_no_permission.add(createCommand(player, command, args));
} }
} }
CommandHandler.sendTranslatedMessage(player, "commands.help.available_commands");
SendAllHelpMessage(player, annotations);
} else { } else {
String command = args.get(0); String command_str = args.remove(0).toLowerCase();
CommandHandler handler = CommandMap.getInstance().getHandler(command); CommandHandler command = handlers.get(command_str);
StringBuilder builder = new StringBuilder(""); if (command == null) {
if (handler == null) { CommandHandler.sendTranslatedMessage(player, "commands.generic.command_exist_error");
builder.append(translate(player, "commands.generic.command_exist_error")); return;
} else { } else {
Command annotation = handler.getClass().getAnnotation(Command.class); Command annotation = command.getClass().getAnnotation(Command.class);
if (player == null || account.hasPermission(annotation.permission())) {
this.createCommand(builder, player, annotation); commands.add(createCommand(player, command, args));
} else {
if (player != null && !Objects.equals(annotation.permission(), "") && !player.getAccount().hasPermission(annotation.permission())) { commands_no_permission.add(createCommand(player, command, args));
builder.append("\n\t").append(translate(player, "commands.help.warn_player_has_no_permission"));
} }
} }
CommandHandler.sendMessage(player, builder.toString());
} }
} for (String s : commands)
CommandHandler.sendMessage(player, s);
void SendAllHelpMessage(Player player, List<Command> annotations) { for (String s : commands_no_permission)
StringBuilder builder = new StringBuilder(translate(player, "commands.help.available_commands")); CommandHandler.sendMessage(player, s + "\n\t" + translate(player, "commands.help.warn_player_has_no_permission"));
annotations.forEach(annotation -> {
this.createCommand(builder, player, annotation);
builder.append("\n");
});
CommandHandler.sendMessage(player, builder.toString());
} }
} }

View File

@ -6,19 +6,18 @@ import emu.grasscutter.game.player.Player;
import java.util.List; import java.util.List;
import static emu.grasscutter.utils.Language.translate; @Command(label = "kick", aliases = {"restart"}, permissionTargeted = "server.kick")
@Command(label = "kick", usage = "kick", aliases = {"restart"}, permissionTargeted = "server.kick", description = "commands.kick.description")
public final class KickCommand implements CommandHandler { public final class KickCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender != null) { if (sender != null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.kick.player_kick_player", CommandHandler.sendTranslatedMessage(sender, "commands.kick.player_kick_player",
Integer.toString(sender.getUid()), sender.getAccount().getUsername(), Integer.toString(sender.getUid()), sender.getAccount().getUsername(),
Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername())); Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername());
} else { } else {
CommandHandler.sendMessage(null, translate(sender, "commands.kick.server_kick_player", Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername())); CommandHandler.sendTranslatedMessage(sender, "commands.kick.server_kick_player",
Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername());
} }
targetPlayer.getSession().close(); targetPlayer.getSession().close();

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityMonster;
@ -12,7 +11,7 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall", permissionTargeted = "server.killall.others", description = "commands.killall.description") @Command(label = "killall", usage = {"[<sceneId>]"}, permission = "server.killall", permissionTargeted = "server.killall.others")
public final class KillAllCommand implements CommandHandler { public final class KillAllCommand implements CommandHandler {
@Override @Override
@ -26,7 +25,7 @@ public final class KillAllCommand implements CommandHandler {
scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0))); scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0)));
break; break;
default: default:
CommandHandler.sendMessage(sender, translate(sender, "commands.killall.usage")); sendUsageMessage(sender);
return; return;
} }
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {

View File

@ -13,16 +13,11 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", permissionTargeted = "player.killcharacter.others", description = "commands.killCharacter.description") @Command(label = "killCharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", permissionTargeted = "player.killcharacter.others")
public final class KillCharacterCommand implements CommandHandler { public final class KillCharacterCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.isEmpty()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.killCharacter.usage"));
return;
}
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
// Packets // Packets

View File

@ -3,8 +3,6 @@ package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
@ -13,7 +11,7 @@ import java.util.Locale;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "language", usage = "language [language code]", description = "commands.language.description", aliases = {"lang"}, targetRequirement = Command.TargetRequirement.NONE) @Command(label = "language", usage = {"[<language code>]"}, aliases = {"lang"}, targetRequirement = Command.TargetRequirement.NONE)
public final class LanguageCommand implements CommandHandler { public final class LanguageCommand implements CommandHandler {
@Override @Override

View File

@ -10,7 +10,7 @@ import java.util.Map;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "list", usage = "list [uid]", aliases = {"players"}, description = "commands.list.description", targetRequirement = Command.TargetRequirement.NONE) @Command(label = "list", aliases = {"players"}, usage = {"[<UID>]"}, targetRequirement = Command.TargetRequirement.NONE)
public final class ListCommand implements CommandHandler { public final class ListCommand implements CommandHandler {
@Override @Override

View File

@ -11,13 +11,13 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "permission", usage = "permission <add|remove> <permission>", permission = "permission", description = "commands.permission.description", targetRequirement = TargetRequirement.PLAYER) @Command(label = "permission", usage = "(add|remove) <permission>", permission = "permission", targetRequirement = TargetRequirement.PLAYER)
public final class PermissionCommand implements CommandHandler { public final class PermissionCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() != 2) { if (args.size() != 2) {
CommandHandler.sendMessage(sender, translate(sender, "commands.permission.usage")); sendUsageMessage(sender);
return; return;
} }
@ -37,7 +37,7 @@ public final class PermissionCommand implements CommandHandler {
switch (action) { switch (action) {
default: default:
CommandHandler.sendMessage(sender, translate(sender, "commands.permission.usage")); sendUsageMessage(sender);
break; break;
case "add": case "add":
if (account.addPermission(permission)) { if (account.addPermission(permission)) {

View File

@ -7,16 +7,14 @@ import emu.grasscutter.utils.Position;
import java.util.List; import java.util.List;
import static emu.grasscutter.utils.Language.translate; @Command(label = "position", aliases = {"pos"})
@Command(label = "position", usage = "position", aliases = {"pos"}, description = "commands.position.description")
public final class PositionCommand implements CommandHandler { public final class PositionCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
Position pos = targetPlayer.getPos(); Position pos = targetPlayer.getPosition();
CommandHandler.sendMessage(sender, translate(sender, "commands.position.success", CommandHandler.sendTranslatedMessage(sender, "commands.position.success",
Float.toString(pos.getX()), Float.toString(pos.getY()), Float.toString(pos.getZ()), Float.toString(pos.getX()), Float.toString(pos.getY()), Float.toString(pos.getZ()),
Integer.toString(targetPlayer.getSceneId()))); Integer.toString(targetPlayer.getSceneId()));
} }
} }

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
@ -10,13 +9,13 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "quest", usage = "quest <add|finish> [questId]", permission = "player.quest", permissionTargeted = "player.quest.others", description = "commands.quest.description") @Command(label = "quest", usage = {"(add|finish) [<questId>]"}, permission = "player.quest", permissionTargeted = "player.quest.others")
public final class QuestCommand implements CommandHandler { public final class QuestCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() != 2) { if (args.size() != 2) {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.usage")); sendUsageMessage(sender);
return; return;
} }
@ -54,7 +53,7 @@ public final class QuestCommand implements CommandHandler {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.finished", questId)); CommandHandler.sendMessage(sender, translate(sender, "commands.quest.finished", questId));
} }
default -> { default -> {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.usage")); sendUsageMessage(sender);
} }
} }
} }

View File

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

View File

@ -10,8 +10,12 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "resetconst", usage = "resetconst [all]", @Command(
aliases = {"resetconstellation"}, permission = "player.resetconstellation", permissionTargeted = "player.resetconstellation.others", description = "commands.resetConst.description") label = "resetConst",
aliases = {"resetconstellation"},
usage = "[all]",
permission = "player.resetconstellation",
permissionTargeted = "player.resetconstellation.others")
public final class ResetConstCommand implements CommandHandler { public final class ResetConstCommand implements CommandHandler {
@Override @Override

View File

@ -9,16 +9,11 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", permissionTargeted = "server.resetshop.others", description = "commands.resetShopLimit.description") @Command(label = "resetShopLimit", aliases = {"resetshop"}, permission = "server.resetshop", permissionTargeted = "server.resetshop.others")
public final class ResetShopLimitCommand implements CommandHandler { public final class ResetShopLimitCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.isEmpty()) {
CommandHandler.sendMessage(sender, translate(sender, "commands.resetShopLimit.usage"));
return;
}
targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0)); targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0));
targetPlayer.save(); targetPlayer.save();
CommandHandler.sendMessage(sender, translate(sender, "commands.resetShopLimit.success")); CommandHandler.sendMessage(sender, translate(sender, "commands.resetShopLimit.success"));

View File

@ -13,7 +13,11 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@Command(label = "sendmail", usage = "sendmail <userId|all|help> [templateId]", permission = "server.sendmail", description = "commands.sendMail.description", targetRequirement = Command.TargetRequirement.NONE) @Command(
label = "sendMail",
usage = {"(<userId>|all) [<templateId>]", "help"},
permission = "server.sendmail",
targetRequirement = Command.TargetRequirement.NONE)
public final class SendMailCommand implements CommandHandler { public final class SendMailCommand implements CommandHandler {
// TODO: You should be able to do /sendmail and then just send subsequent messages until you finish // TODO: You should be able to do /sendmail and then just send subsequent messages until you finish
@ -27,7 +31,7 @@ public final class SendMailCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
int senderId; int senderId;
if(sender != null) { if (sender != null) {
senderId = sender.getUid(); senderId = sender.getUid();
} else { } else {
senderId = -1; senderId = -1;
@ -39,7 +43,7 @@ public final class SendMailCommand implements CommandHandler {
MailBuilder mailBuilder; MailBuilder mailBuilder;
switch (args.get(0).toLowerCase()) { switch (args.get(0).toLowerCase()) {
case "help" -> { case "help" -> {
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.usage")); sendUsageMessage(sender);
return; return;
} }
case "all" -> mailBuilder = new MailBuilder(true, new Mail()); case "all" -> mailBuilder = new MailBuilder(true, new Mail());
@ -146,7 +150,7 @@ public final class SendMailCommand implements CommandHandler {
} }
break; break;
default: // *No args* default: // *No args*
CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage")); CommandHandler.sendTranslatedMessage(sender, "commands.sendMail.give_usage");
return; return;
} }
mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl)); mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl));
@ -162,7 +166,7 @@ public final class SendMailCommand implements CommandHandler {
} }
private String getConstructionArgs(int stage, Player sender) { private String getConstructionArgs(int stage, Player sender) {
return switch(stage) { return switch (stage) {
case 0 -> translate(sender, "commands.sendMail.title"); case 0 -> translate(sender, "commands.sendMail.title");
case 1 -> translate(sender, "commands.sendMail.message"); case 1 -> translate(sender, "commands.sendMail.message");
case 2 -> translate(sender, "commands.sendMail.sender"); case 2 -> translate(sender, "commands.sendMail.sender");

View File

@ -8,16 +8,19 @@ import emu.grasscutter.game.player.Player;
import java.util.List; import java.util.List;
import static emu.grasscutter.utils.Language.translate; @Command(
label = "sendMessage",
@Command(label = "sendmessage", usage = "sendmessage <message>", aliases = {"say", "sendservmsg", "sendservermessage", "b", "broadcast"},
aliases = {"say", "sendservmsg", "sendservermessage", "b", "broadcast"}, permission = "server.sendmessage", permissionTargeted = "server.sendmessage.others", description = "commands.sendMessage.description", targetRequirement = TargetRequirement.NONE) usage = {"<message>"},
permission = "server.sendmessage",
permissionTargeted = "server.sendmessage.others",
targetRequirement = TargetRequirement.NONE)
public final class SendMessageCommand implements CommandHandler { public final class SendMessageCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() == 0) { if (args.size() == 0) {
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMessage.usage")); sendUsageMessage(sender);
return; return;
} }
@ -30,6 +33,6 @@ public final class SendMessageCommand implements CommandHandler {
} else { } else {
CommandHandler.sendMessage(targetPlayer, message); CommandHandler.sendMessage(targetPlayer, message);
} }
CommandHandler.sendMessage(sender, translate(sender, "commands.sendMessage.success")); CommandHandler.sendTranslatedMessage(sender, "commands.sendMessage.success");
} }
} }

View File

@ -11,14 +11,18 @@ import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "setfetterlevel", usage = "setfetterlevel <level>", @Command(
aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel", permissionTargeted = "player.setfetterlevel.others", description = "commands.setFetterLevel.description") label = "setFetterLevel",
usage = {"<level>"},
aliases = {"setfetterlvl", "setfriendship"},
permission = "player.setfetterlevel",
permissionTargeted = "player.setfetterlevel.others")
public final class SetFetterLevelCommand implements CommandHandler { public final class SetFetterLevelCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() != 1) { if (args.size() != 1) {
CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.usage")); sendUsageMessage(sender);
return; return;
} }

View File

@ -1,205 +1,205 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.tower.TowerLevelRecord; import emu.grasscutter.game.tower.TowerLevelRecord;
@Command(label = "setprop", usage = "setprop|prop <prop> <value>", aliases = {"prop"}, permission = "player.setprop", permissionTargeted = "player.setprop.others", description = "commands.setProp.description") @Command(label = "setProp", aliases = {"prop"}, usage = {"<prop> <value>"}, permission = "player.setprop", permissionTargeted = "player.setprop.others")
public final class SetPropCommand implements CommandHandler { public final class SetPropCommand implements CommandHandler {
static enum PseudoProp { static enum PseudoProp {
NONE, NONE,
WORLD_LEVEL, WORLD_LEVEL,
TOWER_LEVEL, TOWER_LEVEL,
BP_LEVEL, BP_LEVEL,
GOD_MODE, GOD_MODE,
NO_STAMINA, NO_STAMINA,
UNLIMITED_ENERGY UNLIMITED_ENERGY
} }
static class Prop { static class Prop {
String name; String name;
PlayerProperty prop; PlayerProperty prop;
PseudoProp pseudoProp; PseudoProp pseudoProp;
public Prop(PlayerProperty prop) { public Prop(PlayerProperty prop) {
this(prop.toString(), prop, PseudoProp.NONE); this(prop.toString(), prop, PseudoProp.NONE);
} }
public Prop(String name) { public Prop(String name) {
this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE); this(name, PlayerProperty.PROP_NONE, PseudoProp.NONE);
} }
public Prop(String name, PseudoProp pseudoProp) { public Prop(String name, PseudoProp pseudoProp) {
this(name, PlayerProperty.PROP_NONE, pseudoProp); this(name, PlayerProperty.PROP_NONE, pseudoProp);
} }
public Prop(String name, PlayerProperty prop) { public Prop(String name, PlayerProperty prop) {
this(name, prop, PseudoProp.NONE); this(name, prop, PseudoProp.NONE);
} }
public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) { public Prop(String name, PlayerProperty prop, PseudoProp pseudoProp) {
this.name = name; this.name = name;
this.prop = prop; this.prop = prop;
this.pseudoProp = pseudoProp; this.pseudoProp = pseudoProp;
} }
} }
Map<String, Prop> props; Map<String, Prop> props;
public SetPropCommand() { public SetPropCommand() {
this.props = new HashMap<>(); this.props = new HashMap<>();
// Full PlayerProperty enum that won't be advertised but can be used by devs // Full PlayerProperty enum that won't be advertised but can be used by devs
for (PlayerProperty prop : PlayerProperty.values()) { for (PlayerProperty prop : PlayerProperty.values()) {
String name = prop.toString().substring(5); // PROP_EXP -> EXP String name = prop.toString().substring(5); // PROP_EXP -> EXP
String key = name.toLowerCase(); // EXP -> exp String key = name.toLowerCase(); // EXP -> exp
this.props.put(key, new Prop(name, prop)); this.props.put(key, new Prop(name, prop));
} }
// Add special props // Add special props
Prop worldlevel = new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL); Prop worldlevel = new Prop("World Level", PlayerProperty.PROP_PLAYER_WORLD_LEVEL, PseudoProp.WORLD_LEVEL);
this.props.put("worldlevel", worldlevel); this.props.put("worldlevel", worldlevel);
this.props.put("wl", worldlevel); this.props.put("wl", worldlevel);
Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL); Prop abyss = new Prop("Tower Level", PseudoProp.TOWER_LEVEL);
this.props.put("abyss", abyss); this.props.put("abyss", abyss);
this.props.put("abyssfloor", abyss); this.props.put("abyssfloor", abyss);
this.props.put("ut", abyss); this.props.put("ut", abyss);
this.props.put("tower", abyss); this.props.put("tower", abyss);
this.props.put("towerlevel", abyss); this.props.put("towerlevel", abyss);
this.props.put("unlocktower", abyss); this.props.put("unlocktower", abyss);
Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL); Prop bplevel = new Prop("BP Level", PseudoProp.BP_LEVEL);
this.props.put("bplevel", bplevel); this.props.put("bplevel", bplevel);
this.props.put("bp", bplevel); this.props.put("bp", bplevel);
this.props.put("battlepass", bplevel); this.props.put("battlepass", bplevel);
Prop godmode = new Prop("godmode", PseudoProp.GOD_MODE); Prop godmode = new Prop("godmode", PseudoProp.GOD_MODE);
this.props.put("godmode", godmode); this.props.put("godmode", godmode);
this.props.put("god", godmode); this.props.put("god", godmode);
Prop nostamina = new Prop("nostamina", PseudoProp.NO_STAMINA); Prop nostamina = new Prop("nostamina", PseudoProp.NO_STAMINA);
this.props.put("nostamina", nostamina); this.props.put("nostamina", nostamina);
this.props.put("nostam", nostamina); this.props.put("nostam", nostamina);
this.props.put("ns", nostamina); this.props.put("ns", nostamina);
Prop unlimitedenergy = new Prop("unlimitedenergy", PseudoProp.UNLIMITED_ENERGY); Prop unlimitedenergy = new Prop("unlimitedenergy", PseudoProp.UNLIMITED_ENERGY);
this.props.put("unlimitedenergy", unlimitedenergy); this.props.put("unlimitedenergy", unlimitedenergy);
this.props.put("ue", unlimitedenergy); this.props.put("ue", unlimitedenergy);
} }
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() != 2) { if (args.size() != 2) {
CommandHandler.sendTranslatedMessage(sender, "commands.setProp.usage"); sendUsageMessage(sender);
return; return;
} }
String propStr = args.get(0).toLowerCase(); String propStr = args.get(0).toLowerCase();
String valueStr = args.get(1).toLowerCase(); String valueStr = args.get(1).toLowerCase();
int value; int value;
if (!props.containsKey(propStr)) { if (!props.containsKey(propStr)) {
CommandHandler.sendTranslatedMessage(sender, "commands.setProp.usage"); sendUsageMessage(sender);
return; return;
} }
try { try {
value = switch(valueStr.toLowerCase()) { value = switch (valueStr.toLowerCase()) {
case "on", "true" -> 1; case "on", "true" -> 1;
case "off", "false" -> 0; case "off", "false" -> 0;
case "toggle" -> -1; case "toggle" -> -1;
default -> Integer.parseInt(valueStr); default -> Integer.parseInt(valueStr);
}; };
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error"); CommandHandler.sendTranslatedMessage(sender, "commands.execution.argument_error");
return; return;
} }
boolean success = false; boolean success = false;
Prop prop = props.get(propStr); Prop prop = props.get(propStr);
success = switch (prop.pseudoProp) { success = switch (prop.pseudoProp) {
case WORLD_LEVEL -> targetPlayer.setWorldLevel(value); case WORLD_LEVEL -> targetPlayer.setWorldLevel(value);
case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value); case BP_LEVEL -> targetPlayer.getBattlePassManager().setLevel(value);
case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value); case TOWER_LEVEL -> this.setTowerLevel(sender, targetPlayer, value);
case GOD_MODE, NO_STAMINA, UNLIMITED_ENERGY -> this.setBool(sender, targetPlayer, prop.pseudoProp, value); case GOD_MODE, NO_STAMINA, UNLIMITED_ENERGY -> this.setBool(sender, targetPlayer, prop.pseudoProp, value);
default -> targetPlayer.setProperty(prop.prop, value); default -> targetPlayer.setProperty(prop.prop, value);
}; };
if (success) { if (success) {
if (targetPlayer == sender) { if (targetPlayer == sender) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", prop.name, valueStr); CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", prop.name, valueStr);
} else { } else {
String uidStr = targetPlayer.getAccount().getId(); String uidStr = targetPlayer.getAccount().getId();
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr); CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", prop.name, uidStr, valueStr);
} }
} else { } else {
if (prop.prop != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages if (prop.prop != PlayerProperty.PROP_NONE) { // PseudoProps need to do their own error messages
String min = Integer.toString(targetPlayer.getPropertyMin(prop.prop)); String min = Integer.toString(targetPlayer.getPropertyMin(prop.prop));
String max = Integer.toString(targetPlayer.getPropertyMax(prop.prop)); String max = Integer.toString(targetPlayer.getPropertyMax(prop.prop));
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.value_between", prop.name, min, max); CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.value_between", prop.name, min, max);
} }
} }
} }
private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) { private boolean setTowerLevel(Player sender, Player targetPlayer, int topFloor) {
List<Integer> floorIds = targetPlayer.getServer().getTowerScheduleManager().getAllFloors(); List<Integer> floorIds = targetPlayer.getServer().getTowerSystem().getAllFloors();
if (topFloor < 0 || topFloor > floorIds.size()) { if (topFloor < 0 || topFloor > floorIds.size()) {
String min = Integer.toString(0); String min = Integer.toString(0);
String max = Integer.toString(floorIds.size()); String max = Integer.toString(floorIds.size());
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.value_between", "Tower Level", min, max); CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.value_between", "Tower Level", min, max);
return false; return false;
} }
Map<Integer, TowerLevelRecord> recordMap = targetPlayer.getTowerManager().getRecordMap(); Map<Integer, TowerLevelRecord> recordMap = targetPlayer.getTowerManager().getRecordMap();
// Add records for each unlocked floor // Add records for each unlocked floor
for (int floor : floorIds.subList(0, topFloor)) { for (int floor : floorIds.subList(0, topFloor)) {
if (!recordMap.containsKey(floor)) { if (!recordMap.containsKey(floor)) {
recordMap.put(floor, new TowerLevelRecord(floor)); recordMap.put(floor, new TowerLevelRecord(floor));
} }
} }
// Remove records for each floor past our target // Remove records for each floor past our target
for (int floor : floorIds.subList(topFloor, floorIds.size())) { for (int floor : floorIds.subList(topFloor, floorIds.size())) {
if (recordMap.containsKey(floor)) { if (recordMap.containsKey(floor)) {
recordMap.remove(floor); recordMap.remove(floor);
} }
} }
// Six stars required on Floor 8 to unlock Floor 9+ // Six stars required on Floor 8 to unlock Floor 9+
if (topFloor > 8) { if (topFloor > 8) {
recordMap.get(floorIds.get(7)).setLevelStars(0, 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at all recordMap.get(floorIds.get(7)).setLevelStars(0, 6); // levelIds seem to start at 1 for Floor 1 Chamber 1, so this doesn't get shown at all
} }
return true; return true;
} }
private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) { private boolean setBool(Player sender, Player targetPlayer, PseudoProp pseudoProp, int value) {
boolean enabled = switch (pseudoProp) { boolean enabled = switch (pseudoProp) {
case GOD_MODE -> targetPlayer.inGodmode(); case GOD_MODE -> targetPlayer.inGodmode();
case NO_STAMINA -> targetPlayer.getUnlimitedStamina(); case NO_STAMINA -> targetPlayer.getUnlimitedStamina();
case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().getEnergyUsage(); case UNLIMITED_ENERGY -> !targetPlayer.getEnergyManager().getEnergyUsage();
default -> false; default -> false;
}; };
enabled = switch (value) { enabled = switch (value) {
case -1 -> !enabled; case -1 -> !enabled;
case 0 -> false; case 0 -> false;
default -> true; default -> true;
}; };
switch (pseudoProp) { switch (pseudoProp) {
case GOD_MODE: case GOD_MODE:
targetPlayer.setGodmode(enabled); targetPlayer.setGodmode(enabled);
break; break;
case NO_STAMINA: case NO_STAMINA:
targetPlayer.setUnlimitedStamina(enabled); targetPlayer.setUnlimitedStamina(enabled);
break; break;
case UNLIMITED_ENERGY: case UNLIMITED_ENERGY:
targetPlayer.getEnergyManager().setEnergyUsage(!enabled); targetPlayer.getEnergyManager().setEnergyUsage(!enabled);
break; break;
default: default:
return false; return false;
} }
return true; return true;
} }
} }

View File

@ -11,7 +11,7 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
@Command(label = "setstats", usage = "setstats|stats <stat> <value>", aliases = {"stats"}, permission = "player.setstats", permissionTargeted = "player.setstats.others", description = "commands.setStats.description") @Command(label = "setStats", aliases = {"stats", "stat"}, usage = {"<stat> <value>"}, permission = "player.setstats", permissionTargeted = "player.setstats.others")
public final class SetStatsCommand implements CommandHandler { public final class SetStatsCommand implements CommandHandler {
static class Stat { static class Stat {
String name; String name;
@ -71,7 +71,7 @@ public final class SetStatsCommand implements CommandHandler {
statStr = args.get(0).toLowerCase(); statStr = args.get(0).toLowerCase();
valueStr = args.get(1); valueStr = args.get(1);
} else { } else {
CommandHandler.sendTranslatedMessage(sender, "commands.setStats.usage"); sendUsageMessage(sender);
return; return;
} }
@ -105,7 +105,7 @@ public final class SetStatsCommand implements CommandHandler {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", stat.name, uidStr, valueStr); CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", stat.name, uidStr, valueStr);
} }
} else { } else {
CommandHandler.sendTranslatedMessage(sender, "commands.setStats.usage"); sendUsageMessage(sender);
} }
return; return;
} }

View File

@ -1,29 +1,28 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.GadgetData; import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.*; import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import javax.swing.text.html.parser.Entity;
import java.util.List; import java.util.List;
import java.util.Random;
import static emu.grasscutter.Configuration.*; import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "spawn", usage = "spawn <entityId> [amount] [level(monster only)] [<x> <y> <z>(monster only, optional)]", aliases = {"drop"}, permission = "server.spawn", permissionTargeted = "server.spawn.others", description = "commands.spawn.description") @Command(
label = "spawn",
usage = {"spawn <entityId> [amount] [level(monster only)] [<x> <y> <z>(monster only)]"},
aliases = {"drop"},
permission = "server.spawn",
permissionTargeted = "server.spawn.others")
public final class SpawnCommand implements CommandHandler { public final class SpawnCommand implements CommandHandler {
@Override @Override
@ -31,7 +30,7 @@ public final class SpawnCommand implements CommandHandler {
int id = 0; // This is just to shut up the linter, it's not a real default int id = 0; // This is just to shut up the linter, it's not a real default
int amount = 1; int amount = 1;
int level = 1; int level = 1;
float x = 0, y = 0, z = 0; float x = 0, y = 0, z = 0;
switch (args.size()) { switch (args.size()) {
case 6: case 6:
try { try {
@ -61,10 +60,10 @@ public final class SpawnCommand implements CommandHandler {
} }
break; break;
default: default:
CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.usage")); sendUsageMessage(sender);
return; return;
} }
MonsterData monsterData = GameData.getMonsterDataMap().get(id); MonsterData monsterData = GameData.getMonsterDataMap().get(id);
GadgetData gadgetData = GameData.getGadgetDataMap().get(id); GadgetData gadgetData = GameData.getGadgetDataMap().get(id);
ItemData itemData = GameData.getItemDataMap().get(id); ItemData itemData = GameData.getItemDataMap().get(id);
@ -72,21 +71,21 @@ public final class SpawnCommand implements CommandHandler {
CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId")); CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId"));
return; return;
} }
Scene scene = targetPlayer.getScene(); Scene scene = targetPlayer.getScene();
if (scene.getEntities().size() + amount > GAME_OPTIONS.sceneEntityLimit) { if (scene.getEntities().size() + amount > GAME_OPTIONS.sceneEntityLimit) {
amount = Math.max(Math.min(GAME_OPTIONS.sceneEntityLimit - scene.getEntities().size(), amount), 0); amount = Math.max(Math.min(GAME_OPTIONS.sceneEntityLimit - scene.getEntities().size(), amount), 0);
CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.limit_reached", amount)); CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.limit_reached", amount));
if (amount <= 0) { if (amount <= 0) {
return; return;
} }
} }
double maxRadius = Math.sqrt(amount * 0.2 / Math.PI); double maxRadius = Math.sqrt(amount * 0.2 / Math.PI);
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
Position pos = GetRandomPositionInCircle(targetPlayer.getPos(), maxRadius).addY(3); Position pos = GetRandomPositionInCircle(targetPlayer.getPosition(), maxRadius).addY(3);
if(x != 0 && y != 0 && z != 0) { if (x != 0 && y != 0 && z != 0) {
pos = GetRandomPositionInCircle(new Position(x, y, z), maxRadius).addY(3); pos = GetRandomPositionInCircle(new Position(x, y, z), maxRadius).addY(3);
} }
GameEntity entity = null; GameEntity entity = null;
@ -120,7 +119,7 @@ public final class SpawnCommand implements CommandHandler {
CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.success", Integer.toString(amount), Integer.toString(id))); CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.success", Integer.toString(amount), Integer.toString(id)));
} }
private Position GetRandomPositionInCircle(Position origin, double radius){ private Position GetRandomPositionInCircle(Position origin, double radius) {
Position target = origin.clone(); Position target = origin.clone();
double angle = Math.random() * 360; double angle = Math.random() * 360;
double r = Math.sqrt(Math.random() * radius * radius); double r = Math.sqrt(Math.random() * radius * radius);

View File

@ -9,7 +9,7 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "stop", usage = "stop", permission = "server.stop", description = "commands.stop.description", targetRequirement = Command.TargetRequirement.NONE) @Command(label = "stop", permission = "server.stop", targetRequirement = Command.TargetRequirement.NONE)
public final class StopCommand implements CommandHandler { public final class StopCommand implements CommandHandler {
@Override @Override

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.excels.AvatarSkillDepotData; import emu.grasscutter.data.excels.AvatarSkillDepotData;
@ -14,7 +13,11 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "talent", usage = "talent <talentId> <value>", permission = "player.settalent", permissionTargeted = "player.settalent.others", description = "commands.talent.description") @Command(
label = "talent",
usage = {"set <talentId> <level>", "(n|e|q) <level>", "getid"},
permission = "player.settalent",
permissionTargeted = "player.settalent.others")
public final class TalentCommand implements CommandHandler { public final class TalentCommand implements CommandHandler {
private void setTalentLevel(Player sender, Player player, Avatar avatar, int talentId, int talentLevel) { private void setTalentLevel(Player sender, Player player, Avatar avatar, int talentId, int talentLevel) {
int oldLevel = avatar.getSkillLevelMap().get(talentId); int oldLevel = avatar.getSkillLevelMap().get(talentId);
@ -46,9 +49,7 @@ public final class TalentCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1){ if (args.size() < 1){
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_1")); sendUsageMessage(sender);
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_2"));
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_3"));
return; return;
} }
@ -57,15 +58,13 @@ public final class TalentCommand implements CommandHandler {
String cmdSwitch = args.get(0); String cmdSwitch = args.get(0);
switch (cmdSwitch) { switch (cmdSwitch) {
default -> { default -> {
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_1")); sendUsageMessage(sender);
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_2"));
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_3"));
return; return;
} }
case "set" -> { case "set" -> {
if (args.size() < 3) { if (args.size() < 3) {
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_1")); sendUsageMessage(sender);
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_3")); sendUsageMessage(sender);
return; return;
} }
try { try {
@ -79,7 +78,7 @@ public final class TalentCommand implements CommandHandler {
} }
case "n", "e", "q" -> { case "n", "e", "q" -> {
if (args.size() < 2) { if (args.size() < 2) {
CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_2")); sendUsageMessage(sender);
return; return;
} }
AvatarSkillDepotData SkillDepot = avatar.getData().getSkillDepot(); AvatarSkillDepotData SkillDepot = avatar.getData().getSkillDepot();

View File

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

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands; package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
@ -11,7 +10,7 @@ import java.util.List;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@Command(label = "teleport", usage = "teleport <x> <y> <z> [sceneId]", aliases = {"tp"}, permission = "player.teleport", permissionTargeted = "player.teleport.others", description = "commands.teleport.description") @Command(label = "teleport", aliases = {"tp"}, usage = {"<x> <y> <z> [sceneId]"}, permission = "player.teleport", permissionTargeted = "player.teleport.others")
public final class TeleportCommand implements CommandHandler { public final class TeleportCommand implements CommandHandler {
private float parseRelative(String input, Float current) { // TODO: Maybe this will be useful elsewhere later private float parseRelative(String input, Float current) { // TODO: Maybe this will be useful elsewhere later
@ -27,7 +26,7 @@ public final class TeleportCommand implements CommandHandler {
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
Position pos = targetPlayer.getPos(); Position pos = targetPlayer.getPosition();
float x = pos.getX(); float x = pos.getX();
float y = pos.getY(); float y = pos.getY();
float z = pos.getZ(); float z = pos.getZ();
@ -50,7 +49,7 @@ public final class TeleportCommand implements CommandHandler {
} }
break; break;
default: default:
CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.usage")); sendUsageMessage(sender);
return; return;
} }

View File

@ -9,8 +9,6 @@ import emu.grasscutter.game.player.Player;
@Command( @Command(
label = "unban", label = "unban",
usage = "unban <@player>",
description = "commands.unban.description",
permission = "server.ban", permission = "server.ban",
targetRequirement = Command.TargetRequirement.PLAYER targetRequirement = Command.TargetRequirement.PLAYER
) )

View File

@ -0,0 +1,35 @@
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.OpenState;
import emu.grasscutter.server.packet.send.PacketOpenStateChangeNotify;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static emu.grasscutter.utils.Language.translate;
@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 (OpenState state : OpenState.values()) {
if (state == OpenState.OPEN_STATE_NONE || state == OpenState.OPEN_STATE_LIMIT_REGION_GLOBAL) continue;
if (targetPlayer.getOpenStateManager().getOpenStateMap().getOrDefault(state.getValue(), 0) == 0) {
targetPlayer.getOpenStateManager().getOpenStateMap().put(state.getValue(), 1);
changed.put(state.getValue(), 1);
}
}
targetPlayer.sendPacket(new PacketOpenStateChangeNotify(changed));
CommandHandler.sendMessage(sender, translate(sender, "commands.unlockall.success", targetPlayer.getNickname()));
}
}

View File

@ -4,11 +4,10 @@ import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ClimateType; import emu.grasscutter.game.props.ClimateType;
import emu.grasscutter.game.world.Scene;
import java.util.List; import java.util.List;
@Command(label = "weather", usage = "weather [weatherId] [climateType]", aliases = {"w"}, permission = "player.weather", permissionTargeted = "player.weather.others", description = "commands.weather.description") @Command(label = "weather", aliases = {"w"}, usage = {"weather [<weatherId>] [<climateType>]"}, permission = "player.weather", permissionTargeted = "player.weather.others")
public final class WeatherCommand implements CommandHandler { public final class WeatherCommand implements CommandHandler {
@Override @Override
@ -31,7 +30,7 @@ public final class WeatherCommand implements CommandHandler {
weatherId = Integer.parseInt(arg); weatherId = Integer.parseInt(arg);
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.id"); CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.id");
CommandHandler.sendTranslatedMessage(sender, "commands.weather.usage"); sendUsageMessage(sender);
return; return;
} }
} }

View File

@ -1,4 +1,4 @@
package emu.grasscutter.utils; package emu.grasscutter.config;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
@ -28,7 +28,7 @@ public class ConfigContainer {
try { // Check if the server is using a legacy config. try { // Check if the server is using a legacy config.
JsonObject configObject = Grasscutter.getGsonFactory() JsonObject configObject = Grasscutter.getGsonFactory()
.fromJson(new FileReader(Grasscutter.configFile), JsonObject.class); .fromJson(new FileReader(Grasscutter.configFile), JsonObject.class);
if(!configObject.has("version")) { if (!configObject.has("version")) {
Grasscutter.getLogger().info("Updating legacy .."); Grasscutter.getLogger().info("Updating legacy ..");
Grasscutter.saveConfig(null); Grasscutter.saveConfig(null);
} }
@ -37,7 +37,7 @@ public class ConfigContainer {
var existing = config.version; var existing = config.version;
var latest = version(); var latest = version();
if(existing == latest) if (existing == latest)
return; return;
// Create a new configuration instance. // Create a new configuration instance.
@ -93,9 +93,8 @@ public class ConfigContainer {
} }
public static class Server { public static class Server {
public ServerDebugMode debugLevel = ServerDebugMode.NONE; public Set<Integer> debugWhitelist = Set.of();
public Set<Integer> DebugWhitelist = Set.of(); public Set<Integer> debugBlacklist = Set.of();
public Set<Integer> DebugBlacklist = Set.of();
public ServerRunMode runMode = ServerRunMode.HYBRID; public ServerRunMode runMode = ServerRunMode.HYBRID;
public HTTP http = new HTTP(); public HTTP http = new HTTP();
@ -112,6 +111,7 @@ public class ConfigContainer {
public static class Account { public static class Account {
public boolean autoCreate = false; public boolean autoCreate = false;
public boolean EXPERIMENTAL_RealPassword = false;
public String[] defaultPermissions = {}; public String[] defaultPermissions = {};
public int maxPlayer = -1; public int maxPlayer = -1;
} }
@ -120,10 +120,10 @@ public class ConfigContainer {
public static class HTTP { public static class HTTP {
public String bindAddress = "0.0.0.0"; public String bindAddress = "0.0.0.0";
public int bindPort = 443;
/* This is the address used in URLs. */ /* This is the address used in URLs. */
public String accessAddress = "127.0.0.1"; public String accessAddress = "127.0.0.1";
public int bindPort = 443;
/* This is the port used in URLs. */ /* This is the port used in URLs. */
public int accessPort = 0; public int accessPort = 0;
@ -134,16 +134,23 @@ public class ConfigContainer {
public static class Game { public static class Game {
public String bindAddress = "0.0.0.0"; public String bindAddress = "0.0.0.0";
public int bindPort = 22102;
/* This is the address used in the default region. */ /* This is the address used in the default region. */
public String accessAddress = "127.0.0.1"; public String accessAddress = "127.0.0.1";
public int bindPort = 22102;
/* This is the port used in the default region. */ /* This is the port used in the default region. */
public int accessPort = 0; public int accessPort = 0;
/* Entities within a certain range will be loaded for the player */ /* Entities within a certain range will be loaded for the player */
public int loadEntitiesForPlayerRange = 100; public int loadEntitiesForPlayerRange = 100;
public boolean enableScriptInBigWorld = false; public boolean enableScriptInBigWorld = false;
public boolean enableConsole = true; public boolean enableConsole = true;
/* Kcp internal work interval (milliseconds) */
public int kcpInterval = 20;
/* Controls whether packets should be logged in console or not */
public ServerDebugMode logPackets = ServerDebugMode.NONE;
public GameOptions gameOptions = new GameOptions(); public GameOptions gameOptions = new GameOptions();
public JoinOptions joinOptions = new JoinOptions(); public JoinOptions joinOptions = new JoinOptions();
public ConsoleAccount serverAccount = new ConsoleAccount(); public ConsoleAccount serverAccount = new ConsoleAccount();
@ -155,6 +162,8 @@ public class ConfigContainer {
public Region[] regions = {}; public Region[] regions = {};
public String defaultName = "Grasscutter"; public String defaultName = "Grasscutter";
public ServerDebugMode logRequests = ServerDebugMode.NONE;
} }
public static class Encryption { public static class Encryption {

View File

@ -1,9 +1,7 @@
package emu.grasscutter; package emu.grasscutter.config;
import emu.grasscutter.utils.ConfigContainer;
import emu.grasscutter.utils.ConfigContainer.*;
import java.util.Locale; import java.util.Locale;
import java.nio.file.Paths; import java.nio.file.Paths;
import static emu.grasscutter.Grasscutter.config; import static emu.grasscutter.Grasscutter.config;
@ -11,19 +9,19 @@ import static emu.grasscutter.Grasscutter.config;
/** /**
* A data container for the server's configuration. * A data container for the server's configuration.
* *
* Use `import static emu.grasscutter.Configuration.*;` * Use `import static emu.grasscutter.Configuration.*;`
* to import all configuration constants. * to import all configuration constants.
*/ */
public final class Configuration extends ConfigContainer { public final class Configuration extends ConfigContainer {
/* /*
* Constants * Constants
*/ */
// 'c' is short for 'config' and makes code look 'cleaner'. // 'c' is short for 'config' and makes code look 'cleaner'.
public static final ConfigContainer c = config; public static final ConfigContainer c = config;
public static final Locale LANGUAGE = config.language.language; public static final Locale LANGUAGE = config.language.language;
public static final Locale FALLBACK_LANGUAGE = config.language.fallback; public static final Locale FALLBACK_LANGUAGE = config.language.fallback;
public static final String DOCUMENT_LANGUAGE = config.language.document; public static final String DOCUMENT_LANGUAGE = config.language.document;
@ -32,22 +30,22 @@ public final class Configuration extends ConfigContainer {
private static final String PLUGINS_FOLDER = config.folderStructure.plugins; private static final String PLUGINS_FOLDER = config.folderStructure.plugins;
private static final String SCRIPTS_FOLDER = config.folderStructure.scripts; private static final String SCRIPTS_FOLDER = config.folderStructure.scripts;
private static final String PACKETS_FOLDER = config.folderStructure.packets; private static final String PACKETS_FOLDER = config.folderStructure.packets;
public static final Server SERVER = config.server; public static final Server SERVER = config.server;
public static final Database DATABASE = config.databaseInfo; public static final Database DATABASE = config.databaseInfo;
public static final Account ACCOUNT = config.account; public static final Account ACCOUNT = config.account;
public static final HTTP HTTP_INFO = config.server.http; public static final HTTP HTTP_INFO = config.server.http;
public static final Game GAME_INFO = config.server.game; public static final Game GAME_INFO = config.server.game;
public static final Dispatch DISPATCH_INFO = config.server.dispatch; public static final Dispatch DISPATCH_INFO = config.server.dispatch;
public static final Encryption HTTP_ENCRYPTION = config.server.http.encryption; public static final Encryption HTTP_ENCRYPTION = config.server.http.encryption;
public static final Policies HTTP_POLICIES = config.server.http.policies; public static final Policies HTTP_POLICIES = config.server.http.policies;
public static final Files HTTP_STATIC_FILES = config.server.http.files; public static final Files HTTP_STATIC_FILES = config.server.http.files;
public static final GameOptions GAME_OPTIONS = config.server.game.gameOptions; public static final GameOptions GAME_OPTIONS = config.server.game.gameOptions;
public static final GameOptions.InventoryLimits INVENTORY_LIMITS = config.server.game.gameOptions.inventoryLimits; public static final GameOptions.InventoryLimits INVENTORY_LIMITS = config.server.game.gameOptions.inventoryLimits;
/* /*
* Utilities * Utilities
*/ */
@ -58,11 +56,11 @@ public final class Configuration extends ConfigContainer {
public static String DATA(String path) { public static String DATA(String path) {
return Paths.get(DATA_FOLDER, path).toString(); return Paths.get(DATA_FOLDER, path).toString();
} }
public static String RESOURCE(String path) { public static String RESOURCE(String path) {
return Paths.get(RESOURCES_FOLDER, path).toString(); return Paths.get(RESOURCES_FOLDER, path).toString();
} }
public static String PLUGIN() { public static String PLUGIN() {
return PLUGINS_FOLDER; return PLUGINS_FOLDER;
} }

View File

@ -6,14 +6,16 @@ import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import static emu.grasscutter.config.Configuration.DATA;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import static emu.grasscutter.Configuration.DATA;
public class DataLoader { public class DataLoader {
/** /**
@ -28,6 +30,24 @@ public class DataLoader {
return load(resourcePath, true); return load(resourcePath, true);
} }
/**
* Creates an input stream reader for a data file. If the file isn't found within the /data directory then it will fallback to the default within the jar resources
*
* @param resourcePath The path to the data file to be loaded.
* @return InputStreamReader of the data file.
* @throws IOException
* @throws FileNotFoundException
* @see #load(String, boolean)
*/
public static InputStreamReader loadReader(String resourcePath) throws IOException, FileNotFoundException {
try {
InputStream is = load(resourcePath, true);
return new InputStreamReader(is);
} catch (FileNotFoundException exception) {
throw exception;
}
}
/** /**
* Load a data file by its name. * Load a data file by its name.
* *
@ -49,7 +69,7 @@ public class DataLoader {
return null; return null;
} }
public static void CheckAllFiles() { public static void checkAllFiles() {
try { try {
List<Path> filenames = FileUtils.getPathsFromResource("/defaults/data/"); List<Path> filenames = FileUtils.getPathsFromResource("/defaults/data/");
@ -58,16 +78,16 @@ public class DataLoader {
} else for (Path file : filenames) { } else for (Path file : filenames) {
String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1]; String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1];
CheckAndCopyData(relativePath); checkAndCopyData(relativePath);
} }
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e); Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e);
} }
GenerateGachaMappings(); generateGachaMappings();
} }
private static void CheckAndCopyData(String name) { private static void checkAndCopyData(String name) {
String filePath = Utils.toFilePath(DATA(name)); String filePath = Utils.toFilePath(DATA(name));
if (!Utils.fileExists(filePath)) { if (!Utils.fileExists(filePath)) {
@ -93,7 +113,7 @@ public class DataLoader {
} }
} }
private static void GenerateGachaMappings() { private static void generateGachaMappings() {
if (!Utils.fileExists(GachaHandler.gachaMappings)) { if (!Utils.fileExists(GachaHandler.gachaMappings)) {
try { try {
Grasscutter.getLogger().info("Creating default '" + GachaHandler.gachaMappings + "' data"); Grasscutter.getLogger().info("Creating default '" + GachaHandler.gachaMappings + "' data");

View File

@ -76,6 +76,7 @@ public class GameData {
private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>(); private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>();
private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldAreaData> worldAreaDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<WorldAreaData> worldAreaDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>();
@ -90,6 +91,7 @@ public class GameData {
private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<TowerFloorData> towerFloorDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<TowerLevelData> towerLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<TowerScheduleData> towerScheduleDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<TowerScheduleData> towerScheduleDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<BuffData> buffDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ForgeData> forgeDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ForgeData> forgeDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<HomeWorldLevelData> homeWorldLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<FurnitureMakeConfigData> furnitureMakeConfigDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FurnitureMakeConfigData> furnitureMakeConfigDataMap = new Int2ObjectOpenHashMap<>();
@ -448,4 +450,8 @@ public class GameData {
public static Int2ObjectMap<CookBonusData> getCookBonusDataMap() { public static Int2ObjectMap<CookBonusData> getCookBonusDataMap() {
return cookBonusDataMap; return cookBonusDataMap;
} }
public static Int2ObjectMap<BuffData> getBuffDataMap() {
return buffDataMap;
}
} }

View File

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

View File

@ -9,8 +9,8 @@ import java.util.Map.Entry;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.google.gson.Gson;
import emu.grasscutter.data.binout.*; import emu.grasscutter.data.binout.*;
import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.scripts.SceneIndexManager; import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@ -28,464 +28,468 @@ import emu.grasscutter.data.common.PointData;
import emu.grasscutter.data.common.ScenePointConfig; import emu.grasscutter.data.common.ScenePointConfig;
import emu.grasscutter.game.world.SpawnDataEntry.*; import emu.grasscutter.game.world.SpawnDataEntry.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import static emu.grasscutter.Configuration.*; import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
public class ResourceLoader { public class ResourceLoader {
private static final List<String> loadedResources = new ArrayList<>(); private static final List<String> loadedResources = new ArrayList<>();
public static List<Class<?>> getResourceDefClasses() { public static List<Class<?>> getResourceDefClasses() {
Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName()); Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName());
Set<?> classes = reflections.getSubTypesOf(GameResource.class); Set<?> classes = reflections.getSubTypesOf(GameResource.class);
List<Class<?>> classList = new ArrayList<>(classes.size()); List<Class<?>> classList = new ArrayList<>(classes.size());
classes.forEach(o -> { classes.forEach(o -> {
Class<?> c = (Class<?>) o; Class<?> c = (Class<?>) o;
if (c.getAnnotation(ResourceType.class) != null) { if (c.getAnnotation(ResourceType.class) != null) {
classList.add(c); classList.add(c);
} }
}); });
classList.sort((a, b) -> b.getAnnotation(ResourceType.class).loadPriority().value() - a.getAnnotation(ResourceType.class).loadPriority().value()); classList.sort((a, b) -> b.getAnnotation(ResourceType.class).loadPriority().value() - a.getAnnotation(ResourceType.class).loadPriority().value());
return classList; return classList;
} }
public static void loadAll() { public static void loadAll() {
Grasscutter.getLogger().info(translate("messages.status.resources.loading")); Grasscutter.getLogger().info(translate("messages.status.resources.loading"));
// Load ability lists // Load ability lists
loadAbilityEmbryos(); loadAbilityEmbryos();
loadOpenConfig(); loadOpenConfig();
loadAbilityModifiers(); loadAbilityModifiers();
// Load resources // Load resources
loadResources(); loadResources();
// Process into depots // Process into depots
GameDepot.load(); GameDepot.load();
// Load spawn data and quests // Load spawn data and quests
loadSpawnData(); loadSpawnData();
loadQuests(); loadQuests();
// Load scene points - must be done AFTER resources are loaded // Load scene points - must be done AFTER resources are loaded
loadScenePoints(); loadScenePoints();
// Load default home layout // Load default home layout
loadHomeworldDefaultSaveData(); loadHomeworldDefaultSaveData();
loadNpcBornData(); loadNpcBornData();
Grasscutter.getLogger().info(translate("messages.status.resources.finish")); Grasscutter.getLogger().info(translate("messages.status.resources.finish"));
} }
public static void loadResources() { public static void loadResources() {
loadResources(false); loadResources(false);
} }
public static void loadResources(boolean doReload) { public static void loadResources(boolean doReload) {
for (Class<?> resourceDefinition : getResourceDefClasses()) { for (Class<?> resourceDefinition : getResourceDefClasses()) {
ResourceType type = resourceDefinition.getAnnotation(ResourceType.class); ResourceType type = resourceDefinition.getAnnotation(ResourceType.class);
if (type == null) { if (type == null) {
continue; continue;
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
Int2ObjectMap map = GameData.getMapByResourceDef(resourceDefinition); Int2ObjectMap map = GameData.getMapByResourceDef(resourceDefinition);
if (map == null) { if (map == null) {
continue; continue;
} }
try { try {
loadFromResource(resourceDefinition, type, map, doReload); loadFromResource(resourceDefinition, type, map, doReload);
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Error loading resource file: " + Arrays.toString(type.name()), e); Grasscutter.getLogger().error("Error loading resource file: " + Arrays.toString(type.name()), e);
} }
} }
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
protected static void loadFromResource(Class<?> c, ResourceType type, Int2ObjectMap map, boolean doReload) throws Exception { protected static void loadFromResource(Class<?> c, ResourceType type, Int2ObjectMap map, boolean doReload) throws Exception {
if(!loadedResources.contains(c.getSimpleName()) || doReload) { if (!loadedResources.contains(c.getSimpleName()) || doReload) {
for (String name : type.name()) { for (String name : type.name()) {
loadFromResource(c, name, map); loadFromResource(c, name, map);
} }
loadedResources.add(c.getSimpleName()); loadedResources.add(c.getSimpleName());
Grasscutter.getLogger().debug("Loaded " + map.size() + " " + c.getSimpleName() + "s."); Grasscutter.getLogger().debug("Loaded " + map.size() + " " + c.getSimpleName() + "s.");
} }
} }
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
protected static void loadFromResource(Class<?> c, String fileName, Int2ObjectMap map) throws Exception { protected static void loadFromResource(Class<?> c, String fileName, Int2ObjectMap map) throws Exception {
try (FileReader fileReader = new FileReader(RESOURCE("ExcelBinOutput/" + fileName))) { try (FileReader fileReader = new FileReader(RESOURCE("ExcelBinOutput/" + fileName))) {
List list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, c).getType()); List list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, c).getType());
for (Object o : list) { for (Object o : list) {
GameResource res = (GameResource) o; GameResource res = (GameResource) o;
res.onLoad(); res.onLoad();
map.put(res.getId(), res); map.put(res.getId(), res);
} }
} }
} }
private static void loadScenePoints() { private static void loadScenePoints() {
Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)"); Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)");
File folder = new File(RESOURCE("BinOutput/Scene/Point")); File folder = new File(RESOURCE("BinOutput/Scene/Point"));
if (!folder.isDirectory() || !folder.exists() || folder.listFiles() == null) { if (!folder.isDirectory() || !folder.exists() || folder.listFiles() == null) {
Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!"); Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!");
return; return;
} }
List<ScenePointEntry> scenePointList = new ArrayList<>(); List<ScenePointEntry> scenePointList = new ArrayList<>();
for (File file : Objects.requireNonNull(folder.listFiles())) { for (File file : Objects.requireNonNull(folder.listFiles())) {
ScenePointConfig config; Integer sceneId; ScenePointConfig config; Integer sceneId;
Matcher matcher = pattern.matcher(file.getName()); Matcher matcher = pattern.matcher(file.getName());
if (matcher.find()) { if (matcher.find()) {
sceneId = Integer.parseInt(matcher.group(1)); sceneId = Integer.parseInt(matcher.group(1));
} else { } else {
continue; continue;
} }
try (FileReader fileReader = new FileReader(file)) { try (FileReader fileReader = new FileReader(file)) {
config = Grasscutter.getGsonFactory().fromJson(fileReader, ScenePointConfig.class); config = Grasscutter.getGsonFactory().fromJson(fileReader, ScenePointConfig.class);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
continue; continue;
} }
if (config.points == null) { if (config.points == null) {
continue; continue;
} }
for (Map.Entry<String, JsonElement> entry : config.points.entrySet()) { for (Map.Entry<String, JsonElement> entry : config.points.entrySet()) {
PointData pointData = Grasscutter.getGsonFactory().fromJson(entry.getValue(), PointData.class); PointData pointData = Grasscutter.getGsonFactory().fromJson(entry.getValue(), PointData.class);
pointData.setId(Integer.parseInt(entry.getKey())); pointData.setId(Integer.parseInt(entry.getKey()));
ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData); ScenePointEntry sl = new ScenePointEntry(sceneId + "_" + entry.getKey(), pointData);
scenePointList.add(sl); scenePointList.add(sl);
GameData.getScenePointIdList().add(pointData.getId()); GameData.getScenePointIdList().add(pointData.getId());
pointData.updateDailyDungeon(); pointData.updateDailyDungeon();
} }
for (ScenePointEntry entry : scenePointList) { for (ScenePointEntry entry : scenePointList) {
GameData.getScenePointEntries().put(entry.getName(), entry); GameData.getScenePointEntries().put(entry.getName(), entry);
} }
} }
} }
private static void loadAbilityEmbryos() { private static void loadAbilityEmbryos() {
List<AbilityEmbryoEntry> embryoList = null; List<AbilityEmbryoEntry> embryoList = null;
// Read from cached file if exists // Read from cached file if exists
try (InputStream embryoCache = DataLoader.load("AbilityEmbryos.json", false)) { try (InputStream embryoCache = DataLoader.load("AbilityEmbryos.json", false)) {
embryoList = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(embryoCache), TypeToken.getParameterized(Collection.class, AbilityEmbryoEntry.class).getType()); embryoList = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(embryoCache), TypeToken.getParameterized(Collection.class, AbilityEmbryoEntry.class).getType());
} catch (Exception ignored) {} } catch (Exception ignored) {}
if(embryoList == null) { if (embryoList == null) {
// Load from BinOutput // Load from BinOutput
Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)"); Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)");
embryoList = new LinkedList<>(); embryoList = new LinkedList<>();
File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Avatar/"))); File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Avatar/")));
File[] files = folder.listFiles(); File[] files = folder.listFiles();
if(files == null) { if (files == null) {
Grasscutter.getLogger().error("Error loading ability embryos: no files found in " + folder.getAbsolutePath()); Grasscutter.getLogger().error("Error loading ability embryos: no files found in " + folder.getAbsolutePath());
return; return;
} }
for (File file : files) { for (File file : files) {
AvatarConfig config; AvatarConfig config;
String avatarName; String avatarName;
Matcher matcher = pattern.matcher(file.getName()); Matcher matcher = pattern.matcher(file.getName());
if (matcher.find()) { if (matcher.find()) {
avatarName = matcher.group(0); avatarName = matcher.group(0);
} else { } else {
continue; continue;
} }
try (FileReader fileReader = new FileReader(file)) { try (FileReader fileReader = new FileReader(file)) {
config = Grasscutter.getGsonFactory().fromJson(fileReader, AvatarConfig.class); config = Grasscutter.getGsonFactory().fromJson(fileReader, AvatarConfig.class);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
continue; continue;
} }
if (config.abilities == null) { if (config.abilities == null) {
continue; continue;
} }
int s = config.abilities.size(); int s = config.abilities.size();
AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s])); AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s]));
embryoList.add(al); embryoList.add(al);
} }
File playerElementsFile = new File(Utils.toFilePath(RESOURCE("BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json"))); File playerElementsFile = new File(Utils.toFilePath(RESOURCE("BinOutput/AbilityGroup/AbilityGroup_Other_PlayerElementAbility.json")));
if (playerElementsFile.exists()) { if (playerElementsFile.exists()) {
try (FileReader fileReader = new FileReader(playerElementsFile)) { try (FileReader fileReader = new FileReader(playerElementsFile)) {
GameDepot.setPlayerAbilities(Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<String, AvatarConfig>>(){}.getType())); GameDepot.setPlayerAbilities(Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<String, AvatarConfig>>(){}.getType()));
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
if (embryoList == null || embryoList.isEmpty()) { if (embryoList == null || embryoList.isEmpty()) {
Grasscutter.getLogger().error("No embryos loaded!"); Grasscutter.getLogger().error("No embryos loaded!");
return; return;
} }
for (AbilityEmbryoEntry entry : embryoList) { for (AbilityEmbryoEntry entry : embryoList) {
GameData.getAbilityEmbryoInfo().put(entry.getName(), entry); GameData.getAbilityEmbryoInfo().put(entry.getName(), entry);
} }
} }
private static void loadAbilityModifiers() { private static void loadAbilityModifiers() {
// Load from BinOutput // Load from BinOutput
File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Ability/Temp/AvatarAbilities/"))); File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Ability/Temp/AvatarAbilities/")));
File[] files = folder.listFiles(); File[] files = folder.listFiles();
if (files == null) { if (files == null) {
Grasscutter.getLogger().error("Error loading ability modifiers: no files found in " + folder.getAbsolutePath()); Grasscutter.getLogger().error("Error loading ability modifiers: no files found in " + folder.getAbsolutePath());
return; return;
} }
for (File file : files) { for (File file : files) {
List<AbilityConfigData> abilityConfigList; List<AbilityConfigData> abilityConfigList;
try (FileReader fileReader = new FileReader(file)) { try (FileReader fileReader = new FileReader(file)) {
abilityConfigList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityConfigData.class).getType()); abilityConfigList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityConfigData.class).getType());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
continue; continue;
} }
for (AbilityConfigData data : abilityConfigList) { for (AbilityConfigData data : abilityConfigList) {
if (data.Default.modifiers == null || data.Default.modifiers.size() == 0) { if (data.Default.modifiers == null || data.Default.modifiers.size() == 0) {
continue; continue;
} }
AbilityModifierEntry modifierEntry = new AbilityModifierEntry(data.Default.abilityName); AbilityModifierEntry modifierEntry = new AbilityModifierEntry(data.Default.abilityName);
for (Entry<String, AbilityModifier> entry : data.Default.modifiers.entrySet()) { for (Entry<String, AbilityModifier> entry : data.Default.modifiers.entrySet()) {
AbilityModifier modifier = entry.getValue(); AbilityModifier modifier = entry.getValue();
// Stare. // Stare.
if (modifier.onAdded != null) { if (modifier.onAdded != null) {
for (AbilityModifierAction action : modifier.onAdded) { for (AbilityModifierAction action : modifier.onAdded) {
if (action.$type.contains("HealHP")) { if (action.$type.contains("HealHP")) {
action.type = AbilityModifierActionType.HealHP; action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnAdded().add(action); modifierEntry.getOnAdded().add(action);
} }
} }
} }
if (modifier.onThinkInterval != null) { if (modifier.onThinkInterval != null) {
for (AbilityModifierAction action : modifier.onThinkInterval) { for (AbilityModifierAction action : modifier.onThinkInterval) {
if (action.$type.contains("HealHP")) { if (action.$type.contains("HealHP")) {
action.type = AbilityModifierActionType.HealHP; action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnThinkInterval().add(action); modifierEntry.getOnThinkInterval().add(action);
} }
} }
} }
if (modifier.onRemoved != null) { if (modifier.onRemoved != null) {
for (AbilityModifierAction action : modifier.onRemoved) { for (AbilityModifierAction action : modifier.onRemoved) {
if (action.$type.contains("HealHP")) { if (action.$type.contains("HealHP")) {
action.type = AbilityModifierActionType.HealHP; action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnRemoved().add(action); modifierEntry.getOnRemoved().add(action);
} }
} }
} }
} }
GameData.getAbilityModifiers().put(modifierEntry.getName(), modifierEntry); GameData.getAbilityModifiers().put(modifierEntry.getName(), modifierEntry);
} }
} }
} }
private static void loadSpawnData() { private static void loadSpawnData() {
String[] spawnDataNames = {"Spawns.json", "GadgetSpawns.json"}; String[] spawnDataNames = {"Spawns.json", "GadgetSpawns.json"};
Int2ObjectMap<SpawnGroupEntry> spawnEntryMap = new Int2ObjectOpenHashMap<>(); ArrayList<SpawnGroupEntry> spawnEntryMap = new ArrayList<>();
for (String name : spawnDataNames) { for (String name : spawnDataNames) {
// Load spawn entries from file // Load spawn entries from file
try (InputStream spawnDataEntries = DataLoader.load(name)) { try (InputStreamReader reader = DataLoader.loadReader(name)) {
Type type = TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType(); Type type = TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType();
List<SpawnGroupEntry> list = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(spawnDataEntries), type); List<SpawnGroupEntry> list = Grasscutter.getGsonFactory().fromJson(reader, type);
// Add spawns to group if it already exists in our spawn group map // Add spawns to group if it already exists in our spawn group map
for (SpawnGroupEntry group : list) { spawnEntryMap.addAll(list);
if (spawnEntryMap.containsKey(group.getGroupId())) { } catch (Exception ignored) {}
spawnEntryMap.get(group.getGroupId()).getSpawns().addAll(group.getSpawns()); }
} else {
spawnEntryMap.put(group.getGroupId(), group); if (spawnEntryMap.isEmpty()) {
} Grasscutter.getLogger().error("No spawn data loaded!");
} return;
} catch (Exception ignored) {} }
}
HashMap<GridBlockId, ArrayList<SpawnDataEntry>> areaSort = new HashMap<>();
if (spawnEntryMap.isEmpty()) { //key = sceneId,x,z , value = ArrayList<SpawnDataEntry>
Grasscutter.getLogger().error("No spawn data loaded!"); for (SpawnGroupEntry entry : spawnEntryMap) {
return; entry.getSpawns().forEach(
} s -> {
s.setGroup(entry);
for (SpawnGroupEntry entry : spawnEntryMap.values()) { GridBlockId point = s.getBlockId();
entry.getSpawns().forEach(s -> s.setGroup(entry)); if (!areaSort.containsKey(point)) {
GameDepot.getSpawnListById(entry.getSceneId()).insert(entry, entry.getPos().getX(), entry.getPos().getZ()); areaSort.put(point, new ArrayList<>());
} }
} areaSort.get(point).add(s);
}
private static void loadOpenConfig() { );
// Read from cached file if exists }
List<OpenConfigEntry> list = null; GameDepot.addSpawnListById(areaSort);
}
try(InputStream openConfigCache = DataLoader.load("OpenConfig.json", false)) {
list = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(openConfigCache), TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType()); private static void loadOpenConfig() {
} catch (Exception ignored) {} // Read from cached file if exists
List<OpenConfigEntry> list = null;
if (list == null) {
Map<String, OpenConfigEntry> map = new TreeMap<>(); try (InputStream openConfigCache = DataLoader.load("OpenConfig.json", false)) {
java.lang.reflect.Type type = new TypeToken<Map<String, OpenConfigData[]>>() {}.getType(); list = Grasscutter.getGsonFactory().fromJson(new InputStreamReader(openConfigCache), TypeToken.getParameterized(Collection.class, SpawnGroupEntry.class).getType());
String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"}; } catch (Exception ignored) {}
for (String name : folderNames) { if (list == null) {
File folder = new File(Utils.toFilePath(RESOURCE(name))); Map<String, OpenConfigEntry> map = new TreeMap<>();
File[] files = folder.listFiles(); java.lang.reflect.Type type = new TypeToken<Map<String, OpenConfigData[]>>() {}.getType();
if(files == null) { String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"};
Grasscutter.getLogger().error("Error loading open config: no files found in " + folder.getAbsolutePath()); return;
} for (String name : folderNames) {
File folder = new File(Utils.toFilePath(RESOURCE(name)));
for (File file : files) { File[] files = folder.listFiles();
if (!file.getName().endsWith(".json")) { if (files == null) {
continue; Grasscutter.getLogger().error("Error loading open config: no files found in " + folder.getAbsolutePath()); return;
} }
Map<String, OpenConfigData[]> config; for (File file : files) {
if (!file.getName().endsWith(".json")) {
try (FileReader fileReader = new FileReader(file)) { continue;
config = Grasscutter.getGsonFactory().fromJson(fileReader, type); }
} catch (Exception e) {
e.printStackTrace(); Map<String, OpenConfigData[]> config;
continue;
} try (FileReader fileReader = new FileReader(file)) {
config = Grasscutter.getGsonFactory().fromJson(fileReader, type);
for (Entry<String, OpenConfigData[]> e : config.entrySet()) { } catch (Exception e) {
OpenConfigEntry entry = new OpenConfigEntry(e.getKey(), e.getValue()); e.printStackTrace();
map.put(entry.getName(), entry); continue;
} }
}
} for (Entry<String, OpenConfigData[]> e : config.entrySet()) {
OpenConfigEntry entry = new OpenConfigEntry(e.getKey(), e.getValue());
list = new ArrayList<>(map.values()); map.put(entry.getName(), entry);
} }
}
if (list == null || list.isEmpty()) { }
Grasscutter.getLogger().error("No openconfig entries loaded!");
return; list = new ArrayList<>(map.values());
} }
for (OpenConfigEntry entry : list) { if (list == null || list.isEmpty()) {
GameData.getOpenConfigEntries().put(entry.getName(), entry); Grasscutter.getLogger().error("No openconfig entries loaded!");
} return;
} }
private static void loadQuests() { for (OpenConfigEntry entry : list) {
File folder = new File(RESOURCE("BinOutput/Quest/")); GameData.getOpenConfigEntries().put(entry.getName(), entry);
}
if (!folder.exists()) { }
return;
} private static void loadQuests() {
File folder = new File(RESOURCE("BinOutput/Quest/"));
for (File file : folder.listFiles()) {
MainQuestData mainQuest = null; if (!folder.exists()) {
return;
try (FileReader fileReader = new FileReader(file)) { }
mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, MainQuestData.class);
} catch (Exception e) { for (File file : folder.listFiles()) {
e.printStackTrace(); MainQuestData mainQuest = null;
continue;
} try (FileReader fileReader = new FileReader(file)) {
mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, MainQuestData.class);
GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); } catch (Exception e) {
} e.printStackTrace();
continue;
Grasscutter.getLogger().debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas."); }
}
GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
@SneakyThrows }
private static void loadHomeworldDefaultSaveData(){
var folder = Files.list(Path.of(RESOURCE("BinOutput/HomeworldDefaultSave"))).toList(); Grasscutter.getLogger().debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas.");
var pattern = Pattern.compile("scene(.*)_home_config.json"); }
for(var file : folder){ @SneakyThrows
var matcher = pattern.matcher(file.getFileName().toString()); private static void loadHomeworldDefaultSaveData() {
if(!matcher.find()){ var folder = Files.list(Path.of(RESOURCE("BinOutput/HomeworldDefaultSave"))).toList();
continue; var pattern = Pattern.compile("scene(.*)_home_config.json");
}
var sceneId = matcher.group(1); for (var file : folder) {
var matcher = pattern.matcher(file.getFileName().toString());
var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), HomeworldDefaultSaveData.class); if (!matcher.find()) {
continue;
GameData.getHomeworldDefaultSaveData().put(Integer.parseInt(sceneId), data); }
} var sceneId = matcher.group(1);
Grasscutter.getLogger().debug("Loaded " + GameData.getHomeworldDefaultSaveData().size() + " HomeworldDefaultSaveDatas."); var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), HomeworldDefaultSaveData.class);
}
GameData.getHomeworldDefaultSaveData().put(Integer.parseInt(sceneId), data);
@SneakyThrows }
private static void loadNpcBornData(){
var folder = Files.list(Path.of(RESOURCE("BinOutput/Scene/SceneNpcBorn"))).toList(); Grasscutter.getLogger().debug("Loaded " + GameData.getHomeworldDefaultSaveData().size() + " HomeworldDefaultSaveDatas.");
}
for(var file : folder){
if(file.toFile().isDirectory()){ @SneakyThrows
continue; private static void loadNpcBornData() {
} var folder = Files.list(Path.of(RESOURCE("BinOutput/Scene/SceneNpcBorn"))).toList();
var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), SceneNpcBornData.class); for (var file : folder) {
if(data.getBornPosList() == null || data.getBornPosList().size() == 0){ if (file.toFile().isDirectory()) {
continue; continue;
} }
data.setIndex(SceneIndexManager.buildIndex(3, data.getBornPosList(), item -> item.getPos().toPoint())); var data = Grasscutter.getGsonFactory().fromJson(Files.readString(file), SceneNpcBornData.class);
GameData.getSceneNpcBornData().put(data.getSceneId(), data); if (data.getBornPosList() == null || data.getBornPosList().size() == 0) {
} continue;
}
Grasscutter.getLogger().debug("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas.");
} data.setIndex(SceneIndexManager.buildIndex(3, data.getBornPosList(), item -> item.getPos().toPoint()));
GameData.getSceneNpcBornData().put(data.getSceneId(), data);
// BinOutput configs }
public static class AvatarConfig { Grasscutter.getLogger().debug("Loaded " + GameData.getSceneNpcBornData().size() + " SceneNpcBornDatas.");
@SerializedName(value="abilities", alternate={"targetAbilities"}) }
public ArrayList<AvatarConfigAbility> abilities;
} // BinOutput configs
public static class AvatarConfigAbility { public static class AvatarConfig {
public String abilityName; @SerializedName(value="abilities", alternate={"targetAbilities"})
public String toString() { public ArrayList<AvatarConfigAbility> abilities;
return abilityName; }
}
} public static class AvatarConfigAbility {
public String abilityName;
private static class OpenConfig { public String toString() {
public OpenConfigData[] data; return abilityName;
} }
}
public static class OpenConfigData {
public String $type; private static class OpenConfig {
public String abilityName; public OpenConfigData[] data;
}
public static class OpenConfigData {
public String $type;
public String abilityName;
@SerializedName(value="talentIndex", alternate={"OJOFFKLNAHN"}) @SerializedName(value="talentIndex", alternate={"OJOFFKLNAHN"})
public int talentIndex; public int talentIndex;
@SerializedName(value="skillID", alternate={"overtime"}) @SerializedName(value="skillID", alternate={"overtime"})
public int skillID; public int skillID;
@SerializedName(value="pointDelta", alternate={"IGEBKIHPOIF"}) @SerializedName(value="pointDelta", alternate={"IGEBKIHPOIF"})
public int pointDelta; public int pointDelta;
} }
} }

View File

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

View File

@ -1,24 +1,19 @@
package emu.grasscutter.data.common; package emu.grasscutter.data.common;
import java.util.List; import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseData { public class ItemUseData {
private String useOp; private ItemUseOp useOp;
private List<String> useParam; private String[] useParam;
public String getUseOp() { public ItemUseOp getUseOp() {
if (useOp == null) {
useOp = ItemUseOp.ITEM_USE_NONE;
}
return useOp; return useOp;
} }
public void setUseOp(String useOp) { public String[] getUseParam() {
this.useOp = useOp;
}
public List<String> getUseParam() {
return useParam; return useParam;
} }
public void setUseParam(List<String> useParam) {
this.useParam = useParam;
}
} }

View File

@ -17,13 +17,8 @@ public class AvatarSkillData extends GameResource {
private boolean isAttackCameraLock; private boolean isAttackCameraLock;
private int proudSkillGroupId; private int proudSkillGroupId;
private ElementType costElemType; private ElementType costElemType;
private List<Float> lockWeightParams;
private long nameTextMapHash; private long nameTextMapHash;
private String abilityName; private String abilityName;
private String lockShape;
private String globalValueKey;
@Override @Override
public int getId(){ public int getId(){
@ -58,10 +53,6 @@ public class AvatarSkillData extends GameResource {
return costElemType; return costElemType;
} }
public List<Float> getLockWeightParams() {
return lockWeightParams;
}
public long getNameTextMapHash() { public long getNameTextMapHash() {
return nameTextMapHash; return nameTextMapHash;
} }
@ -69,14 +60,6 @@ public class AvatarSkillData extends GameResource {
public String getAbilityName() { public String getAbilityName() {
return abilityName; return abilityName;
} }
public String getLockShape() {
return lockShape;
}
public String getGlobalValueKey() {
return globalValueKey;
}
@Override @Override
public void onLoad() { public void onLoad() {

View File

@ -0,0 +1,25 @@
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;
@Override
public int getId() {
return this.serverBuffId;
}
public void onLoad() {
this.serverBuffType = this.serverBuffType != null ? this.serverBuffType : ServerBuffType.SERVER_BUFF_NONE;
}
}

View File

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

View File

@ -23,9 +23,9 @@ public class ForgeData extends GameResource {
private List<ItemParamData> materialItems; private List<ItemParamData> materialItems;
@Override @Override
public int getId() { public int getId() {
return this.id; return this.id;
} }
public int getPlayerLevel() { public int getPlayerLevel() {
return playerLevel; return playerLevel;

View File

@ -13,10 +13,8 @@ public class GadgetData extends GameResource {
private boolean isInteractive; private boolean isInteractive;
private String[] tags; private String[] tags;
private String itemJsonName; private String itemJsonName;
private String inteeIconName;
private long nameTextMapHash; private long nameTextMapHash;
private int campID; private int campID;
private String LODPatternName;
@Override @Override
public int getId() { public int getId() {
@ -43,10 +41,6 @@ public class GadgetData extends GameResource {
return itemJsonName; return itemJsonName;
} }
public String getInteeIconName() {
return inteeIconName;
}
public long getNameTextMapHash() { public long getNameTextMapHash() {
return nameTextMapHash; return nameTextMapHash;
} }
@ -55,8 +49,6 @@ public class GadgetData extends GameResource {
return campID; return campID;
} }
public String getLODPatternName() { return LODPatternName; }
@Override @Override
public void onLoad() { public void onLoad() {

View File

@ -1,5 +1,6 @@
package emu.grasscutter.data.excels; package emu.grasscutter.data.excels;
import java.util.Arrays;
import java.util.List; import java.util.List;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -8,6 +9,7 @@ import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemUseData; import emu.grasscutter.data.common.ItemUseData;
import emu.grasscutter.game.inventory.*; import emu.grasscutter.game.inventory.*;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.ItemUseTarget;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Getter; import lombok.Getter;
@ -17,103 +19,75 @@ import lombok.Getter;
"ReliquaryExcelConfigData.json", "ReliquaryExcelConfigData.json",
"HomeWorldFurnitureExcelConfigData.json" "HomeWorldFurnitureExcelConfigData.json"
}) })
@Getter
public class ItemData extends GameResource { public class ItemData extends GameResource {
// Main
private int id; private int id;
@Getter private int stackLimit = 1; private int stackLimit = 1;
@Getter private int maxUseCount; private int maxUseCount;
@Getter private int rankLevel; private int rankLevel;
@Getter private String effectName; private String effectName;
@Getter private int[] satiationParams; private int rank;
@Getter private int rank; private int weight;
@Getter private int weight; private int gadgetId;
@Getter private int gadgetId;
@Getter private int[] destroyReturnMaterial; private int[] destroyReturnMaterial;
@Getter private int[] destroyReturnMaterialCount; private int[] destroyReturnMaterialCount;
@Getter private List<ItemUseData> itemUse;
// Food // Enums
@Getter private String foodQuality; private ItemType itemType = ItemType.ITEM_NONE;
@Getter private String useTarget; private MaterialType materialType = MaterialType.MATERIAL_NONE;
private String[] iseParam; private EquipType equipType = EquipType.EQUIP_NONE;
// String enums
private String itemType;
private String materialType;
private String equipType;
private String effectType; private String effectType;
private String destroyRule; private String destroyRule;
// Post load enum forms of above // Food
private transient MaterialType materialEnumType; private String foodQuality;
private transient ItemType itemEnumType; private int[] satiationParams;
private transient EquipType equipEnumType;
// Usable item
private ItemUseTarget useTarget;
private List<ItemUseData> itemUse;
// Relic // Relic
@Getter private int mainPropDepotId; private int mainPropDepotId;
@Getter private int appendPropDepotId; private int appendPropDepotId;
@Getter private int appendPropNum; private int appendPropNum;
@Getter private int setId; private int setId;
private int[] addPropLevels; private int[] addPropLevels;
@Getter private int baseConvExp; private int baseConvExp;
@Getter private int maxLevel; private int maxLevel;
// Weapon // Weapon
@Getter private int weaponPromoteId; private int weaponPromoteId;
@Getter private int weaponBaseExp; private int weaponBaseExp;
@Getter private int storyId; private int storyId;
@Getter private int avatarPromoteId; private int avatarPromoteId;
@Getter private int awakenMaterial; private int awakenMaterial;
@Getter private int[] awakenCosts; private int[] awakenCosts;
@Getter private int[] skillAffix; private int[] skillAffix;
private WeaponProperty[] weaponProp; private WeaponProperty[] weaponProp;
// Hash // Hash
@Getter private String icon; private long nameTextMapHash;
@Getter private long nameTextMapHash;
@Getter private IntSet addPropLevelSet;
// Furniture // Furniture
@Getter private int comfort; private int comfort;
@Getter private List<Integer> furnType; private List<Integer> furnType;
@Getter private List<Integer> furnitureGadgetID; private List<Integer> furnitureGadgetID;
@SerializedName("JFDLJGDFIGL") @SerializedName("JFDLJGDFIGL")
@Getter private int roomSceneId; private int roomSceneId;
// Custom
private transient IntSet addPropLevelSet;
@Override @Override
public int getId(){ public int getId(){
return this.id; return this.id;
} }
public String getMaterialTypeString(){ public WeaponProperty[] getWeaponProperties() {
return this.materialType; return this.weaponProp;
}
public String[] getUseParam(){
return this.iseParam;
}
public String getItemTypeString(){
return this.itemType;
}
public WeaponProperty[] getWeaponProperties() {
return weaponProp;
}
public ItemType getItemType() {
return this.itemEnumType;
}
public MaterialType getMaterialType() {
return this.materialEnumType;
}
public EquipType getEquipType() {
return this.equipEnumType;
} }
public boolean canAddRelicProp(int level) { public boolean canAddRelicProp(int level) {
@ -121,48 +95,37 @@ public class ItemData extends GameResource {
} }
public boolean isEquip() { public boolean isEquip() {
return this.itemEnumType == ItemType.ITEM_RELIQUARY || this.itemEnumType == ItemType.ITEM_WEAPON; return this.itemType == ItemType.ITEM_RELIQUARY || this.itemType == ItemType.ITEM_WEAPON;
} }
@Override @Override
public void onLoad() { public void onLoad() {
this.itemEnumType = ItemType.getTypeByName(getItemTypeString()); if (this.itemType == ItemType.ITEM_RELIQUARY) {
this.materialEnumType = MaterialType.getTypeByName(getMaterialTypeString());
if (this.itemEnumType == ItemType.ITEM_RELIQUARY) {
this.equipEnumType = EquipType.getTypeByName(this.equipType);
if (this.addPropLevels != null && this.addPropLevels.length > 0) { if (this.addPropLevels != null && this.addPropLevels.length > 0) {
this.addPropLevelSet = new IntOpenHashSet(this.addPropLevels); this.addPropLevelSet = new IntOpenHashSet(this.addPropLevels);
} }
} else if (this.itemEnumType == ItemType.ITEM_WEAPON) { } else if (this.itemType == ItemType.ITEM_WEAPON) {
this.equipEnumType = EquipType.EQUIP_WEAPON; this.equipType = EquipType.EQUIP_WEAPON;
} else { } else {
this.equipEnumType = EquipType.EQUIP_NONE; this.equipType = EquipType.EQUIP_NONE;
} }
if (this.getWeaponProperties() != null) { if (this.weaponProp != null) {
for (WeaponProperty weaponProperty : this.getWeaponProperties()) { this.weaponProp = Arrays.stream(this.weaponProp).filter(prop -> prop.getPropType() != null).toArray(WeaponProperty[]::new);
weaponProperty.onLoad();
}
} }
if(this.getFurnType() != null){ if (this.getFurnType() != null) {
this.furnType = this.furnType.stream().filter(x -> x > 0).toList(); this.furnType = this.furnType.stream().filter(x -> x > 0).toList();
} }
if(this.getFurnitureGadgetID() != null){ if (this.getFurnitureGadgetID() != null) {
this.furnitureGadgetID = this.furnitureGadgetID.stream().filter(x -> x > 0).toList(); this.furnitureGadgetID = this.furnitureGadgetID.stream().filter(x -> x > 0).toList();
} }
} }
@Getter
public static class WeaponProperty { public static class WeaponProperty {
@Getter private FightProperty fightProp; private FightProperty propType;
@Getter private String propType; private float initValue;
@Getter private float initValue; private String type;
@Getter private String type;
public void onLoad() {
this.fightProp = FightProperty.getPropByName(propType);
}
} }
} }

View File

@ -10,7 +10,6 @@ public class MonsterDescribeData extends GameResource {
private long nameTextMapHash; private long nameTextMapHash;
private int titleID; private int titleID;
private int specialNameLabID; private int specialNameLabID;
private String icon;
@Override @Override
public int getId() { public int getId() {
@ -29,10 +28,6 @@ public class MonsterDescribeData extends GameResource {
return specialNameLabID; return specialNameLabID;
} }
public String getIcon() {
return icon;
}
@Override @Override
public void onLoad() { public void onLoad() {

View File

@ -5,63 +5,65 @@ import java.util.List;
import emu.grasscutter.data.GameResource; import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType; import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ResourceType(name = "ReliquaryLevelExcelConfigData.json") @ResourceType(name = "ReliquaryLevelExcelConfigData.json")
public class ReliquaryLevelData extends GameResource { public class ReliquaryLevelData extends GameResource {
private int id; private int id;
private Int2ObjectMap<Float> propMap; private Int2FloatMap propMap;
private int rank; private int rank;
private int level; private int level;
private int exp; private int exp;
private List<RelicLevelProperty> addProps; private List<RelicLevelProperty> addProps;
@Override @Override
public int getId() { public int getId() {
return this.id; return this.id;
} }
public int getRank() { public int getRank() {
return rank; return rank;
} }
public int getLevel() { public int getLevel() {
return level; return level;
} }
public int getExp() { public int getExp() {
return exp; return exp;
} }
public float getPropValue(FightProperty prop) { public float getPropValue(FightProperty prop) {
return getPropValue(prop.getId()); return getPropValue(prop.getId());
} }
public float getPropValue(int id) { public float getPropValue(int id) {
return propMap.get(id); return propMap.getOrDefault(id, 0f);
} }
@Override @Override
public void onLoad() { public void onLoad() {
this.id = (rank << 8) + this.getLevel(); this.id = (rank << 8) + this.getLevel();
this.propMap = new Int2ObjectOpenHashMap<>(); this.propMap = new Int2FloatOpenHashMap();
for (RelicLevelProperty p : addProps) { for (RelicLevelProperty p : addProps) {
this.propMap.put(FightProperty.getPropByName(p.getPropType()).getId(), (Float) p.getValue()); this.propMap.put(FightProperty.getPropByName(p.getPropType()).getId(), p.getValue());
} }
} }
public class RelicLevelProperty { public class RelicLevelProperty {
private String propType; private String propType;
private float value; private float value;
public String getPropType() { public String getPropType() {
return propType; return propType;
} }
public float getValue() { public float getValue() {
return value; return value;
} }
} }
} }

View File

@ -1,5 +1,7 @@
package emu.grasscutter.database; package emu.grasscutter.database;
import static emu.grasscutter.config.Configuration.*;
import com.mongodb.MongoCommandException; import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients; import com.mongodb.client.MongoClients;
@ -26,101 +28,99 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.GameQuest;
import static emu.grasscutter.Configuration.*;
public final class DatabaseManager { public final class DatabaseManager {
private static Datastore gameDatastore; private static Datastore gameDatastore;
private static Datastore dispatchDatastore; private static Datastore dispatchDatastore;
private static final Class<?>[] mappedClasses = new Class<?>[] { private static final Class<?>[] mappedClasses = new Class<?>[] {
DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class,
GachaRecord.class, Mail.class, GameMainQuest.class, GameHome.class, BattlePassManager.class, GachaRecord.class, Mail.class, GameMainQuest.class, GameHome.class, BattlePassManager.class,
PlayerActivityData.class, MusicGameBeatmap.class PlayerActivityData.class, MusicGameBeatmap.class
}; };
public static Datastore getGameDatastore() { public static Datastore getGameDatastore() {
return gameDatastore; return gameDatastore;
} }
public static MongoDatabase getGameDatabase() { public static MongoDatabase getGameDatabase() {
return getGameDatastore().getDatabase(); return getGameDatastore().getDatabase();
} }
// Yes. I very dislike this method. However, this will be good for now. // Yes. I very dislike this method. However, this will be good for now.
// TODO: Add dispatch routes for player account management // TODO: Add dispatch routes for player account management
public static Datastore getAccountDatastore() { public static Datastore getAccountDatastore() {
if(SERVER.runMode == ServerRunMode.GAME_ONLY) { if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
return dispatchDatastore; return dispatchDatastore;
} else { } else {
return gameDatastore; 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.
gameDatastore.getMapper().map(mappedClasses);
// Ensure indexes
try {
gameDatastore.ensureIndexes();
} catch (MongoCommandException exception) {
Grasscutter.getLogger().info("Mongo index error: ", exception);
// Duplicate index error
if (exception.getCode() == 85) {
// Drop all indexes and re add them
MongoIterable<String> collections = gameDatastore.getDatabase().listCollectionNames();
for (String name : collections) {
gameDatastore.getDatabase().getCollection(name).dropIndexes();
}
// Add back indexes
gameDatastore.ensureIndexes();
}
}
if(SERVER.runMode == ServerRunMode.GAME_ONLY) { public static void initialize() {
MongoClient dispatchMongoClient = MongoClients.create(DATABASE.server.connectionUri); // Initialize
dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, DATABASE.server.collection); MongoClient gameMongoClient = MongoClients.create(DATABASE.game.connectionUri);
// Ensure indexes for dispatch server // Set mapper options.
try { MapperOptions mapperOptions = MapperOptions.builder()
dispatchDatastore.ensureIndexes(); .storeEmpties(true).storeNulls(false).build();
} catch (MongoCommandException e) { // Create data store.
Grasscutter.getLogger().info("Mongo index error: ", e); gameDatastore = Morphia.createDatastore(gameMongoClient, DATABASE.game.collection, mapperOptions);
// Duplicate index error // Map classes.
if (e.getCode() == 85) { gameDatastore.getMapper().map(mappedClasses);
// Drop all indexes and re add them
MongoIterable<String> collections = dispatchDatastore.getDatabase().listCollectionNames();
for (String name : collections) {
dispatchDatastore.getDatabase().getCollection(name).dropIndexes();
}
// Add back indexes
dispatchDatastore.ensureIndexes();
}
}
}
}
public static synchronized int getNextId(Class<?> c) { // Ensure indexes
DatabaseCounter counter = getGameDatastore().find(DatabaseCounter.class).filter(Filters.eq("_id", c.getSimpleName())).first(); try {
if (counter == null) { gameDatastore.ensureIndexes();
counter = new DatabaseCounter(c.getSimpleName()); } catch (MongoCommandException exception) {
} Grasscutter.getLogger().info("Mongo index error: ", exception);
try { // Duplicate index error
return counter.getNextId(); if (exception.getCode() == 85) {
} finally { // Drop all indexes and re add them
getGameDatastore().save(counter); MongoIterable<String> collections = gameDatastore.getDatabase().listCollectionNames();
} for (String name : collections) {
} gameDatastore.getDatabase().getCollection(name).dropIndexes();
}
// Add back indexes
gameDatastore.ensureIndexes();
}
}
public static synchronized int getNextId(Object o) { if (SERVER.runMode == ServerRunMode.GAME_ONLY) {
return getNextId(o.getClass()); MongoClient dispatchMongoClient = MongoClients.create(DATABASE.server.connectionUri);
} dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, DATABASE.server.collection);
}
// Ensure indexes for dispatch server
try {
dispatchDatastore.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 = dispatchDatastore.getDatabase().listCollectionNames();
for (String name : collections) {
dispatchDatastore.getDatabase().getCollection(name).dropIndexes();
}
// Add back indexes
dispatchDatastore.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

@ -5,102 +5,102 @@ import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import static emu.grasscutter.config.Configuration.*;
import java.util.*; import java.util.*;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.bson.Document; import org.bson.Document;
import static emu.grasscutter.Configuration.*;
@Entity(value = "accounts", useDiscriminator = false) @Entity(value = "accounts", useDiscriminator = false)
public class Account { public class Account {
@Id private String id; @Id private String id;
@Indexed(options = @IndexOptions(unique = true)) @Indexed(options = @IndexOptions(unique = true))
@Collation(locale = "simple", caseLevel = true) @Collation(locale = "simple", caseLevel = true)
private String username; private String username;
private String password; // Unused for now private String password; // Unused for now
private int reservedPlayerId; private int reservedPlayerId;
private String email; private String email;
private String token; private String token;
private String sessionKey; // Session token for dispatch server private String sessionKey; // Session token for dispatch server
private List<String> permissions; private List<String> permissions;
private Locale locale; private Locale locale;
private String banReason; private String banReason;
private int banEndTime; private int banEndTime;
private int banStartTime; private int banStartTime;
private boolean isBanned; private boolean isBanned;
@Deprecated @Deprecated
public Account() { public Account() {
this.permissions = new ArrayList<>(); this.permissions = new ArrayList<>();
this.locale = LANGUAGE; this.locale = LANGUAGE;
} }
public String getId() { public String getId() {
return id; return id;
} }
public void setId(String id) { public void setId(String id) {
this.id = id; this.id = id;
} }
public String getUsername() { public String getUsername() {
return username; return username;
} }
public void setUsername(String username) { public void setUsername(String username) {
this.username = username; this.username = username;
} }
public String getPassword() { public String getPassword() {
return password; return password;
} }
public void setPassword(String password) { public void setPassword(String password) {
this.password = password; this.password = password;
} }
public String getToken() { public String getToken() {
return token; return token;
} }
public void setToken(String token) { public void setToken(String token) {
this.token = token; this.token = token;
} }
public int getReservedPlayerUid() { public int getReservedPlayerUid() {
return this.reservedPlayerId; return this.reservedPlayerId;
} }
public void setReservedPlayerUid(int playerId) { public void setReservedPlayerUid(int playerId) {
this.reservedPlayerId = playerId; this.reservedPlayerId = playerId;
} }
public String getEmail() {
if(email != null && !email.isEmpty()) {
return email;
} else {
return "";
}
}
public void setEmail(String email) { public String getEmail() {
this.email = email; if (email != null && !email.isEmpty()) {
} return email;
} else {
return "";
}
}
public String getSessionKey() { public void setEmail(String email) {
return this.sessionKey; this.email = email;
} }
public String generateSessionKey() { public String getSessionKey() {
this.sessionKey = Utils.bytesToHex(Crypto.createSessionKey(32)); return this.sessionKey;
this.save(); }
return this.sessionKey;
} public String generateSessionKey() {
this.sessionKey = Utils.bytesToHex(Crypto.createSessionKey(32));
this.save();
return this.sessionKey;
}
public Locale getLocale() { public Locale getLocale() {
return locale; return locale;
@ -110,126 +110,127 @@ public class Account {
this.locale = locale; this.locale = locale;
} }
public String getBanReason() { public String getBanReason() {
return banReason; return banReason;
} }
public void setBanReason(String banReason) { public void setBanReason(String banReason) {
this.banReason = banReason; this.banReason = banReason;
} }
public int getBanEndTime() { public int getBanEndTime() {
return banEndTime; return banEndTime;
} }
public void setBanEndTime(int banEndTime) { public void setBanEndTime(int banEndTime) {
this.banEndTime = banEndTime; this.banEndTime = banEndTime;
} }
public int getBanStartTime() { public int getBanStartTime() {
return banStartTime; return banStartTime;
} }
public void setBanStartTime(int banStartTime) { public void setBanStartTime(int banStartTime) {
this.banStartTime = banStartTime; this.banStartTime = banStartTime;
} }
public boolean isBanned() { public boolean isBanned() {
if (banEndTime > 0 && banEndTime < System.currentTimeMillis() / 1000) { if (banEndTime > 0 && banEndTime < System.currentTimeMillis() / 1000) {
this.isBanned = false; this.isBanned = false;
this.banEndTime = 0; this.banEndTime = 0;
this.banStartTime = 0; this.banStartTime = 0;
this.banReason = null; this.banReason = null;
save(); save();
} }
return isBanned; return isBanned;
} }
public void setBanned(boolean isBanned) { public void setBanned(boolean isBanned) {
this.isBanned = isBanned; this.isBanned = isBanned;
} }
/** /**
* The collection of a player's permissions. * The collection of a player's permissions.
*/ */
public List<String> getPermissions() { public List<String> getPermissions() {
return this.permissions; return this.permissions;
} }
public boolean addPermission(String permission) {
if(this.permissions.contains(permission)) return false;
this.permissions.add(permission); return true;
}
public static boolean permissionMatchesWildcard(String wildcard, String[] permissionParts) { public boolean addPermission(String permission) {
String[] wildcardParts = wildcard.split("\\."); if (this.permissions.contains(permission)) return false;
if (permissionParts.length < wildcardParts.length) { // A longer wildcard can never match a shorter permission this.permissions.add(permission); return true;
return false; }
}
for (int i=0; i<wildcardParts.length; i++) {
switch (wildcardParts[i]) {
case "**": // Recursing match
return true;
case "*": // Match only one layer
if (i >= (permissionParts.length-1)) {
return true;
}
break;
default: // This layer isn't a wildcard, it needs to match exactly
if (!wildcardParts[i].equals(permissionParts[i])) {
return false;
}
}
}
// At this point the wildcard will have matched every layer, but if it is shorter then the permission then this is not a match at this point (no **).
return (wildcardParts.length == permissionParts.length);
}
public boolean hasPermission(String permission) { public static boolean permissionMatchesWildcard(String wildcard, String[] permissionParts) {
if(this.permissions.contains("*") && this.permissions.size() == 1) return true; String[] wildcardParts = wildcard.split("\\.");
if (permissionParts.length < wildcardParts.length) { // A longer wildcard can never match a shorter permission
return false;
}
for (int i=0; i<wildcardParts.length; i++) {
switch (wildcardParts[i]) {
case "**": // Recursing match
return true;
case "*": // Match only one layer
if (i >= (permissionParts.length-1)) {
return true;
}
break;
default: // This layer isn't a wildcard, it needs to match exactly
if (!wildcardParts[i].equals(permissionParts[i])) {
return false;
}
}
}
// At this point the wildcard will have matched every layer, but if it is shorter then the permission then this is not a match at this point (no **).
return (wildcardParts.length == permissionParts.length);
}
// Add default permissions if it doesn't exist public boolean hasPermission(String permission) {
List<String> permissions = Stream.of(this.permissions, Arrays.asList(ACCOUNT.defaultPermissions)) if (permission.isEmpty()) return true;
.flatMap(Collection::stream) if (this.permissions.contains("*") && this.permissions.size() == 1) return true;
.distinct().toList();
if (permissions.contains(permission)) return true; // Add default permissions if it doesn't exist
List<String> permissions = Stream.of(this.permissions, Arrays.asList(ACCOUNT.defaultPermissions))
.flatMap(Collection::stream)
.distinct().toList();
String[] permissionParts = permission.split("\\."); if (permissions.contains(permission)) return true;
for (String p : permissions) {
if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) return false;
if (permissionMatchesWildcard(p, permissionParts)) return true;
}
return permissions.contains("*"); String[] permissionParts = permission.split("\\.");
} for (String p : permissions) {
if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) return false;
if (permissionMatchesWildcard(p, permissionParts)) return true;
}
public boolean removePermission(String permission) { return permissions.contains("*");
return this.permissions.remove(permission); }
}
// TODO make unique public boolean removePermission(String permission) {
public String generateLoginToken() { return this.permissions.remove(permission);
this.token = Utils.bytesToHex(Crypto.createSessionKey(32)); }
this.save();
return this.token;
}
public void save() {
DatabaseHelper.saveAccount(this);
}
@PreLoad // TODO make unique
public void onLoad(Document document) { public String generateLoginToken() {
// Grant the superuser permissions to accounts created before the permissions update this.token = Utils.bytesToHex(Crypto.createSessionKey(32));
if (!document.containsKey("permissions")) { this.save();
this.addPermission("*"); return this.token;
} }
public void save() {
DatabaseHelper.saveAccount(this);
}
@PreLoad
public void onLoad(Document document) {
// Grant the superuser permissions to accounts created before the permissions update
if (!document.containsKey("permissions")) {
this.addPermission("*");
}
// Set account default language as server default language // Set account default language as server default language
if (!document.containsKey("locale")) { if (!document.containsKey("locale")) {
this.locale = LANGUAGE; this.locale = LANGUAGE;
} }
} }
} }

View File

@ -1,29 +1,15 @@
package emu.grasscutter.game.ability; package emu.grasscutter.game.ability;
import java.util.*;
import java.util.Optional;
import java.util.Map.Entry;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityModifierEntry; import emu.grasscutter.data.binout.AbilityModifierEntry;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityClientGadget;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.entity.gadget.GadgetGatherObject; import emu.grasscutter.game.entity.gadget.GadgetGatherObject;
import emu.grasscutter.game.entity.gadget.GadgetGatherPoint; import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall;
import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead; import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange; import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange;
@ -31,169 +17,161 @@ import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityM
import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina; import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina;
import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry; import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry;
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
import emu.grasscutter.game.props.FightProperty;
public class AbilityManager { public class AbilityManager extends BasePlayerManager {
private Player player;
HealAbilityManager healAbilityManager; HealAbilityManager healAbilityManager;
public AbilityManager(Player player) {
this.player = player;
this.healAbilityManager = new HealAbilityManager(player);
}
public Player getPlayer() {
return this.player;
}
public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception { public AbilityManager(Player player) {
super(player);
this.healAbilityManager = new HealAbilityManager(player);
}
public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception {
healAbilityManager.healHandler(invoke); healAbilityManager.healHandler(invoke);
//Grasscutter.getLogger().info(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue() + "): " + Utils.bytesToHex(invoke.toByteArray())); //Grasscutter.getLogger().info(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue() + "): " + Utils.bytesToHex(invoke.toByteArray()));
switch (invoke.getArgumentType()) { switch (invoke.getArgumentType()) {
case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM: case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM:
handleOverrideParam(invoke); handleOverrideParam(invoke);
break; break;
case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP: case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP:
handleReinitOverrideMap(invoke); handleReinitOverrideMap(invoke);
break; break;
case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE: case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE:
handleModifierChange(invoke); handleModifierChange(invoke);
break; break;
case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA: case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA:
handleMixinCostStamina(invoke); handleMixinCostStamina(invoke);
break; break;
case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL: case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL:
handleGenerateElemBall(invoke); handleGenerateElemBall(invoke);
break; break;
default: default:
break; break;
}
}
private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityScalarValueEntry entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData());
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityMetaReInitOverrideMap map = AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData());
for (AbilityScalarValueEntry entry : map.getOverrideMapList()) {
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
}
private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
// Sanity checks
GameEntity target = player.getScene().getEntityById(invoke.getEntityId());
if (target == null) {
return;
}
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
// Destroying rocks
if (target instanceof EntityGadget targetGadget && targetGadget.getContent() instanceof GadgetGatherObject gatherObject) {
if (data.getAction() == ModifierAction.REMOVED) {
gatherObject.dropItems(this.getPlayer());
return;
}
} }
// Sanity checks
AbilityInvokeEntryHead head = invoke.getHead();
if (head == null) {
return;
}
GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId());
if (sourceEntity == null) {
return;
}
// This is not how it works but we will keep it for now since healing abilities dont work properly anyways
if (data.getAction() == ModifierAction.ADDED && data.getParentAbilityName() != null) {
// Handle add modifier here
String modifierString = data.getParentAbilityName().getStr();
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnAdded().size() > 0) {
for (AbilityModifierAction action : modifier.getOnAdded()) {
invokeAction(action, target, sourceEntity);
}
}
// Add to meta modifier list
target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString);
} else if (data.getAction() == ModifierAction.REMOVED) {
// Handle remove modifier
String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId());
if (modifierString != null) {
// Get modifier and call on remove event
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnRemoved().size() > 0) {
for (AbilityModifierAction action : modifier.getOnRemoved()) {
invokeAction(action, target, sourceEntity);
}
}
// Remove from meta modifiers
target.getMetaModifiers().remove(head.getInstancedModifierId());
}
}
}
private void handleMixinCostStamina(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
AbilityMixinCostStamina costStamina = AbilityMixinCostStamina.parseFrom((invoke.getAbilityData()));
getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim());
}
private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { }
this.player.getEnergyManager().handleGenerateElemBall(invoke);
} private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = player.getScene().getEntityById(invoke.getEntityId());
private void invokeAction(AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) {
switch (action.type) { if (entity == null) {
case HealHP -> { return;
} }
case LoseHP -> {
if (action.amountByTargetCurrentHPRatio == null) { AbilityScalarValueEntry entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData());
return;
} entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
float damageAmount = 0;
private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception {
if (action.amount.isDynamic && action.amount.dynamicKey != null) { GameEntity entity = player.getScene().getEntityById(invoke.getEntityId());
damageAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
} if (entity == null) {
return;
if (damageAmount > 0) { }
target.damage(damageAmount);
} AbilityMetaReInitOverrideMap map = AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData());
}
} for (AbilityScalarValueEntry entry : map.getOverrideMapList()) {
} entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
}
private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
// Sanity checks
GameEntity target = player.getScene().getEntityById(invoke.getEntityId());
if (target == null) {
return;
}
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
// Destroying rocks
if (target instanceof EntityGadget targetGadget && targetGadget.getContent() instanceof GadgetGatherObject gatherObject) {
if (data.getAction() == ModifierAction.REMOVED) {
gatherObject.dropItems(this.getPlayer());
return;
}
}
// Sanity checks
AbilityInvokeEntryHead head = invoke.getHead();
if (head == null) {
return;
}
GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId());
if (sourceEntity == null) {
return;
}
// This is not how it works but we will keep it for now since healing abilities dont work properly anyways
if (data.getAction() == ModifierAction.ADDED && data.getParentAbilityName() != null) {
// Handle add modifier here
String modifierString = data.getParentAbilityName().getStr();
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnAdded().size() > 0) {
for (AbilityModifierAction action : modifier.getOnAdded()) {
invokeAction(action, target, sourceEntity);
}
}
// Add to meta modifier list
target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString);
} else if (data.getAction() == ModifierAction.REMOVED) {
// Handle remove modifier
String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId());
if (modifierString != null) {
// Get modifier and call on remove event
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnRemoved().size() > 0) {
for (AbilityModifierAction action : modifier.getOnRemoved()) {
invokeAction(action, target, sourceEntity);
}
}
// Remove from meta modifiers
target.getMetaModifiers().remove(head.getInstancedModifierId());
}
}
}
private void handleMixinCostStamina(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
AbilityMixinCostStamina costStamina = AbilityMixinCostStamina.parseFrom((invoke.getAbilityData()));
getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim());
}
private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
this.player.getEnergyManager().handleGenerateElemBall(invoke);
}
private void invokeAction(AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) {
switch (action.type) {
case HealHP -> {
}
case LoseHP -> {
if (action.amountByTargetCurrentHPRatio == null) {
return;
}
float damageAmount = 0;
if (action.amount.isDynamic && action.amount.dynamicKey != null) {
damageAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
}
if (damageAmount > 0) {
target.damage(damageAmount);
}
}
}
}
} }

View File

@ -5,6 +5,7 @@ import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActivityType; import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
@ -15,13 +16,13 @@ import org.reflections.Reflections;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@Getter @Getter
public class ActivityManager { public class ActivityManager extends BasePlayerManager {
private static final Map<Integer, ActivityConfigItem> activityConfigItemMap; private static final Map<Integer, ActivityConfigItem> activityConfigItemMap;
private final Player player;
private final Map<Integer, PlayerActivityData> playerActivityDataMap; private final Map<Integer, PlayerActivityData> playerActivityDataMap;
static { static {
@ -44,24 +45,24 @@ public class ActivityManager {
activityWatcherTypeMap.put(typeName.value(), ConstructorAccess.get(item)); activityWatcherTypeMap.put(typeName.value(), ConstructorAccess.get(item));
}); });
try(InputStream is = DataLoader.load("ActivityConfig.json"); InputStreamReader isr = new InputStreamReader(is)) { try (Reader reader = DataLoader.loadReader("ActivityConfig.json")) {
List<ActivityConfigItem> activities = Grasscutter.getGsonFactory().fromJson( List<ActivityConfigItem> activities = Grasscutter.getGsonFactory().fromJson(
isr, reader,
TypeToken.getParameterized(List.class, ActivityConfigItem.class).getType()); TypeToken.getParameterized(List.class, ActivityConfigItem.class).getType());
activities.forEach(item -> { activities.forEach(item -> {
var activityData = GameData.getActivityDataMap().get(item.getActivityId()); var activityData = GameData.getActivityDataMap().get(item.getActivityId());
if(activityData == null){ if (activityData == null) {
Grasscutter.getLogger().warn("activity {} not exist.", item.getActivityId()); Grasscutter.getLogger().warn("activity {} not exist.", item.getActivityId());
return; return;
} }
var activityHandlerType = activityHandlerTypeMap.get(ActivityType.getTypeByName(activityData.getActivityType())); var activityHandlerType = activityHandlerTypeMap.get(ActivityType.getTypeByName(activityData.getActivityType()));
ActivityHandler activityHandler; ActivityHandler activityHandler;
if(activityHandlerType != null) { if (activityHandlerType != null) {
activityHandler = (ActivityHandler) activityHandlerType.newInstance(); activityHandler = (ActivityHandler) activityHandlerType.newInstance();
}else{ }else {
activityHandler = new DefaultActivityHandler(); activityHandler = new DefaultActivityHandler();
} }
activityHandler.setActivityConfigItem(item); activityHandler.setActivityConfigItem(item);
@ -78,14 +79,14 @@ public class ActivityManager {
} }
public ActivityManager(Player player){ public ActivityManager(Player player) {
this.player = player; super(player);
playerActivityDataMap = new ConcurrentHashMap<>(); playerActivityDataMap = new ConcurrentHashMap<>();
// load data for player // load data for player
activityConfigItemMap.values().forEach(item -> { activityConfigItemMap.values().forEach(item -> {
var data = PlayerActivityData.getByPlayer(player, item.getActivityId()); var data = PlayerActivityData.getByPlayer(player, item.getActivityId());
if(data == null){ if (data == null) {
data = item.getActivityHandler().initPlayerActivityData(player); data = item.getActivityHandler().initPlayerActivityData(player);
data.save(); data.save();
} }
@ -115,34 +116,34 @@ public class ActivityManager {
params)); params));
} }
public ActivityInfoOuterClass.ActivityInfo getInfoProtoByActivityId(int activityId){ public ActivityInfoOuterClass.ActivityInfo getInfoProtoByActivityId(int activityId) {
var activityHandler = activityConfigItemMap.get(activityId).getActivityHandler(); var activityHandler = activityConfigItemMap.get(activityId).getActivityHandler();
var activityData = playerActivityDataMap.get(activityId); var activityData = playerActivityDataMap.get(activityId);
return activityHandler.toProto(activityData); return activityHandler.toProto(activityData);
} }
public Optional<ActivityHandler> getActivityHandler(ActivityType type){ public Optional<ActivityHandler> getActivityHandler(ActivityType type) {
return activityConfigItemMap.values().stream() return activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler) .map(ActivityConfigItem::getActivityHandler)
.filter(x -> type == x.getClass().getAnnotation(GameActivity.class).value()) .filter(x -> type == x.getClass().getAnnotation(GameActivity.class).value())
.findFirst(); .findFirst();
} }
public <T extends ActivityHandler> Optional<T> getActivityHandlerAs(ActivityType type, Class<T> clazz){ public <T extends ActivityHandler> Optional<T> getActivityHandlerAs(ActivityType type, Class<T> clazz) {
return getActivityHandler(type).map(x -> (T)x); return getActivityHandler(type).map(x -> (T)x);
} }
public Optional<Integer> getActivityIdByActivityType(ActivityType type){ public Optional<Integer> getActivityIdByActivityType(ActivityType type) {
return getActivityHandler(type) return getActivityHandler(type)
.map(ActivityHandler::getActivityConfigItem) .map(ActivityHandler::getActivityConfigItem)
.map(ActivityConfigItem::getActivityId); .map(ActivityConfigItem::getActivityId);
} }
public Optional<PlayerActivityData> getPlayerActivityDataByActivityType(ActivityType type){ public Optional<PlayerActivityData> getPlayerActivityDataByActivityType(ActivityType type) {
return getActivityIdByActivityType(type) return getActivityIdByActivityType(type)
.map(playerActivityDataMap::get); .map(playerActivityDataMap::get);
} }
public Optional<ActivityInfoOuterClass.ActivityInfo> getInfoProtoByActivityType(ActivityType type){ public Optional<ActivityInfoOuterClass.ActivityInfo> getInfoProtoByActivityType(ActivityType type) {
return getActivityIdByActivityType(type) return getActivityIdByActivityType(type)
.map(this::getInfoProtoByActivityId); .map(this::getInfoProtoByActivityId);
} }

View File

@ -29,21 +29,20 @@ public class MusicGameActivityHandler extends ActivityHandler {
.putAllMusicGameRecordMap( .putAllMusicGameRecordMap(
musicGamePlayerData.getMusicGameRecord().values().stream() musicGamePlayerData.getMusicGameRecord().values().stream()
.collect(Collectors.toMap(MusicGamePlayerData.MusicGameRecord::getMusicId, MusicGamePlayerData.MusicGameRecord::toProto))) .collect(Collectors.toMap(MusicGamePlayerData.MusicGameRecord::getMusicId, MusicGamePlayerData.MusicGameRecord::toProto)))
.addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream() .addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream()
.map(MusicGamePlayerData.CustomBeatmapRecord::toPersonalBriefProto) .map(MusicGamePlayerData.CustomBeatmapRecord::toPersonalBriefProto)
.map(MusicBriefInfoOuterClass.MusicBriefInfo.Builder::build) .map(MusicBriefInfoOuterClass.MusicBriefInfo.Builder::build)
.toList()) .toList())
.addAllPersonCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream() .addAllOthersCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream()
.map(MusicGamePlayerData.CustomBeatmapRecord::toOthersBriefProto) .map(MusicGamePlayerData.CustomBeatmapRecord::toOthersBriefProto)
.map(MusicBriefInfoOuterClass.MusicBriefInfo.Builder::build) .map(MusicBriefInfoOuterClass.MusicBriefInfo.Builder::build)
.toList()) .toList())
.build()); .build());
} }
public MusicGamePlayerData getMusicGamePlayerData(PlayerActivityData playerActivityData){ public MusicGamePlayerData getMusicGamePlayerData(PlayerActivityData playerActivityData) {
if(playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()){ if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData); onInitPlayerActivityData(playerActivityData);
playerActivityData.save(); playerActivityData.save();
} }
@ -52,7 +51,7 @@ public class MusicGameActivityHandler extends ActivityHandler {
MusicGamePlayerData.class); MusicGamePlayerData.class);
} }
public boolean setMusicGameRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord){ public boolean setMusicGameRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData); var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId()); var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId());
@ -64,7 +63,7 @@ public class MusicGameActivityHandler extends ActivityHandler {
return newRecord.getMaxScore() > saveRecord.getMaxScore(); return newRecord.getMaxScore() > saveRecord.getMaxScore();
} }
public void setMusicGameCustomBeatmapRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord){ public void setMusicGameCustomBeatmapRecord(PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData); var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord); musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord);

View File

@ -21,7 +21,7 @@ public class MusicGamePlayerData {
Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord; Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord;
Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord; Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord;
public static MusicGamePlayerData create(){ public static MusicGamePlayerData create() {
return MusicGamePlayerData.of() return MusicGamePlayerData.of()
.musicGameRecord(GameData.getMusicGameBasicDataMap().values().stream() .musicGameRecord(GameData.getMusicGameBasicDataMap().values().stream()
.collect(Collectors.toMap(MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create))) .collect(Collectors.toMap(MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create)))
@ -38,13 +38,13 @@ public class MusicGamePlayerData {
int maxCombo; int maxCombo;
int maxScore; int maxScore;
public static MusicGameRecord create(MusicGameBasicData musicGameBasicData){ public static MusicGameRecord create(MusicGameBasicData musicGameBasicData) {
return MusicGameRecord.of() return MusicGameRecord.of()
.musicId(musicGameBasicData.getId()) .musicId(musicGameBasicData.getId())
.build(); .build();
} }
public MusicGameRecordOuterClass.MusicGameRecord toProto(){ public MusicGameRecordOuterClass.MusicGameRecord toProto() {
return MusicGameRecordOuterClass.MusicGameRecord.newBuilder() return MusicGameRecordOuterClass.MusicGameRecord.newBuilder()
.setIsUnlock(true) .setIsUnlock(true)
.setMaxCombo(maxCombo) .setMaxCombo(maxCombo)
@ -61,7 +61,7 @@ public class MusicGamePlayerData {
int score; int score;
boolean settle; boolean settle;
public MusicBriefInfoOuterClass.MusicBriefInfo.Builder toPersonalBriefProto(){ public MusicBriefInfoOuterClass.MusicBriefInfo.Builder toPersonalBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId); var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return MusicBriefInfoOuterClass.MusicBriefInfo.newBuilder() return MusicBriefInfoOuterClass.MusicBriefInfo.newBuilder()
@ -71,11 +71,10 @@ public class MusicGamePlayerData {
.setMaxScore(musicGameBeatmap.getMaxScore()) .setMaxScore(musicGameBeatmap.getMaxScore())
.setPosition(musicGameBeatmap.getSavePosition()) .setPosition(musicGameBeatmap.getSavePosition())
.setMusicNoteCount(musicGameBeatmap.getMusicNoteCount()) .setMusicNoteCount(musicGameBeatmap.getMusicNoteCount())
.setMusicShareId(musicShareId) .setMusicShareId(musicShareId);
;
} }
public MusicBriefInfoOuterClass.MusicBriefInfo.Builder toOthersBriefProto(){ public MusicBriefInfoOuterClass.MusicBriefInfo.Builder toOthersBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId); var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return musicGameBeatmap.toBriefProto() return musicGameBeatmap.toBriefProto()

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +0,0 @@
package emu.grasscutter.game.avatar;
import dev.morphia.annotations.Entity;
@Entity
public class AvatarProfileData {
private int avatarId;
private int level;
public AvatarProfileData(Avatar avatar) {
this.update(avatar);
}
public int getAvatarId() {
return avatarId;
}
public int getLevel() {
return level;
}
public void update(Avatar avatar) {
this.avatarId = avatar.getAvatarId();
this.level = avatar.getLevel();
}
}

View File

@ -9,6 +9,7 @@ import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeNotify; import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFlycloakChangeNotify; import emu.grasscutter.server.packet.send.PacketAvatarFlycloakChangeNotify;
@ -17,161 +18,156 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
public class AvatarStorage implements Iterable<Avatar> { public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar> {
private final Player player; private final Int2ObjectMap<Avatar> avatars;
private final Int2ObjectMap<Avatar> avatars; private final Long2ObjectMap<Avatar> avatarsGuid;
private final Long2ObjectMap<Avatar> avatarsGuid;
public AvatarStorage(Player player) {
this.player = player;
this.avatars = new Int2ObjectOpenHashMap<>();
this.avatarsGuid = new Long2ObjectOpenHashMap<>();
}
public Player getPlayer() {
return player;
}
public Int2ObjectMap<Avatar> getAvatars() { public AvatarStorage(Player player) {
return avatars; super(player);
} this.avatars = new Int2ObjectOpenHashMap<>();
this.avatarsGuid = new Long2ObjectOpenHashMap<>();
public int getAvatarCount() { }
return this.avatars.size();
}
public Avatar getAvatarById(int id) {
return getAvatars().get(id);
}
public Avatar getAvatarByGuid(long id) {
return avatarsGuid.get(id);
}
public boolean hasAvatar(int id) {
return getAvatars().containsKey(id);
}
public boolean addAvatar(Avatar avatar) {
if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
return false;
}
// Set owner first
avatar.setOwner(getPlayer());
// Put into maps public Int2ObjectMap<Avatar> getAvatars() {
this.avatars.put(avatar.getAvatarId(), avatar); return avatars;
this.avatarsGuid.put(avatar.getGuid(), avatar); }
avatar.save(); public int getAvatarCount() {
return this.avatars.size();
}
return true; public Avatar getAvatarById(int id) {
} return getAvatars().get(id);
}
public void addStartingWeapon(Avatar avatar) {
// Make sure avatar owner is this player
if (avatar.getPlayer() != this.getPlayer()) {
return;
}
// Create weapon
GameItem weapon = new GameItem(avatar.getAvatarData().getInitialWeapon());
if (weapon.getItemData() != null) {
this.getPlayer().getInventory().addItem(weapon);
avatar.equipItem(weapon, true);
}
}
public boolean wearFlycloak(long avatarGuid, int flycloakId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null || !getPlayer().getFlyCloakList().contains(flycloakId)) { public Avatar getAvatarByGuid(long id) {
return false; return avatarsGuid.get(id);
} }
avatar.setFlyCloak(flycloakId);
avatar.save();
// Update
getPlayer().sendPacket(new PacketAvatarFlycloakChangeNotify(avatar));
return true; public boolean hasAvatar(int id) {
} return getAvatars().containsKey(id);
}
public boolean changeCostume(long avatarGuid, int costumeId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null) { public boolean addAvatar(Avatar avatar) {
return false; if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
} return false;
}
if (costumeId != 0 && !getPlayer().getCostumeList().contains(costumeId)) {
return false;
}
// TODO make sure avatar can wear costume
avatar.setCostume(costumeId);
avatar.save();
// Update entity // Set owner first
EntityAvatar entity = avatar.getAsEntity(); avatar.setOwner(getPlayer());
if (entity == null) {
entity = new EntityAvatar(avatar);
getPlayer().sendPacket(new PacketAvatarChangeCostumeNotify(entity));
} else {
getPlayer().getScene().broadcastPacket(new PacketAvatarChangeCostumeNotify(entity));
}
// Done
return true;
}
public void loadFromDatabase() {
List<Avatar> avatars = DatabaseHelper.getAvatars(getPlayer());
for (Avatar avatar : avatars) {
// Should never happen
if (avatar.getObjectId() == null) {
continue;
}
AvatarData avatarData = GameData.getAvatarDataMap().get(avatar.getAvatarId());
AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (avatarData == null || skillDepot == null) {
continue;
}
// Set ownerships
avatar.setAvatarData(avatarData);
avatar.setSkillDepot(skillDepot);
avatar.setOwner(getPlayer());
// Force recalc of const boosted skills
avatar.recalcConstellations();
// Add to avatar storage
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
}
}
public void postLoad() {
for (Avatar avatar : this) {
// Weapon check
if (avatar.getWeapon() == null) {
this.addStartingWeapon(avatar);
}
// Recalc stats
avatar.recalcStats();
}
}
@Override // Put into maps
public Iterator<Avatar> iterator() { this.avatars.put(avatar.getAvatarId(), avatar);
return getAvatars().values().iterator(); this.avatarsGuid.put(avatar.getGuid(), avatar);
}
avatar.save();
return true;
}
public void addStartingWeapon(Avatar avatar) {
// Make sure avatar owner is this player
if (avatar.getPlayer() != this.getPlayer()) {
return;
}
// Create weapon
GameItem weapon = new GameItem(avatar.getAvatarData().getInitialWeapon());
if (weapon.getItemData() != null) {
this.getPlayer().getInventory().addItem(weapon);
avatar.equipItem(weapon, true);
}
}
public boolean wearFlycloak(long avatarGuid, int flycloakId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null || !getPlayer().getFlyCloakList().contains(flycloakId)) {
return false;
}
avatar.setFlyCloak(flycloakId);
avatar.save();
// Update
getPlayer().sendPacket(new PacketAvatarFlycloakChangeNotify(avatar));
return true;
}
public boolean changeCostume(long avatarGuid, int costumeId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null) {
return false;
}
if (costumeId != 0 && !getPlayer().getCostumeList().contains(costumeId)) {
return false;
}
// TODO make sure avatar can wear costume
avatar.setCostume(costumeId);
avatar.save();
// Update entity
EntityAvatar entity = avatar.getAsEntity();
if (entity == null) {
entity = new EntityAvatar(avatar);
getPlayer().sendPacket(new PacketAvatarChangeCostumeNotify(entity));
} else {
getPlayer().getScene().broadcastPacket(new PacketAvatarChangeCostumeNotify(entity));
}
// Done
return true;
}
public void loadFromDatabase() {
List<Avatar> avatars = DatabaseHelper.getAvatars(getPlayer());
for (Avatar avatar : avatars) {
// Should never happen
if (avatar.getObjectId() == null) {
continue;
}
AvatarData avatarData = GameData.getAvatarDataMap().get(avatar.getAvatarId());
AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (avatarData == null || skillDepot == null) {
continue;
}
// Set ownerships
avatar.setAvatarData(avatarData);
avatar.setSkillDepot(skillDepot);
avatar.setOwner(getPlayer());
// Force recalc of const boosted skills
avatar.recalcConstellations();
// Add to avatar storage
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
}
}
public void postLoad() {
for (Avatar avatar : this) {
// Weapon check
if (avatar.getWeapon() == null) {
this.addStartingWeapon(avatar);
}
// Recalc stats
avatar.recalcStats();
}
}
@Override
public Iterator<Avatar> iterator() {
return getAvatars().values().iterator();
}
} }

View File

@ -15,7 +15,6 @@ import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id; import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed; import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
@ -26,9 +25,11 @@ import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType; import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.BasePlayerDataManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType; import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus; import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle; import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus; import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
@ -41,324 +42,324 @@ import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import lombok.Getter; import lombok.Getter;
@Entity(value = "battlepass", useDiscriminator = false) @Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager { public class BattlePassManager extends BasePlayerDataManager {
@Id @Getter private ObjectId id; @Id @Getter private ObjectId id;
@Transient @Getter private Player player;
@Indexed private int ownerUid;
@Indexed private int ownerUid;
@Getter private int point; @Getter private int point;
@Getter private int cyclePoints; // Weekly maximum cap @Getter private int cyclePoints; // Weekly maximum cap
@Getter private int level; @Getter private int level;
@Getter private boolean viewed; @Getter private boolean viewed;
private boolean paid; private boolean paid;
private Map<Integer, BattlePassMission> missions; private Map<Integer, BattlePassMission> missions;
private Map<Integer, BattlePassReward> takenRewards; private Map<Integer, BattlePassReward> takenRewards;
@Deprecated // Morphia only @Deprecated // Morphia only
public BattlePassManager() {} public BattlePassManager() {}
public BattlePassManager(Player player) { public BattlePassManager(Player player) {
this.setPlayer(player); super(player);
this.ownerUid = player.getUid();
} }
public void setPlayer(Player player) { public void setPlayer(Player player) {
this.player = player; this.player = player;
this.ownerUid = player.getUid(); this.ownerUid = player.getUid();
} }
public void updateViewed() { public void updateViewed() {
this.viewed = true; this.viewed = true;
} }
public boolean setLevel(int level) { public boolean setLevel(int level) {
if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) { if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
this.level = level; this.level = level;
this.point = 0; this.point = 0;
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player)); this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
return true; return true;
} }
return false; return false;
} }
public void addPoints(int points){ public void addPoints(int points) {
this.addPointsDirectly(points, false); this.addPointsDirectly(points, false);
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player)); this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save(); this.save();
} }
public void addPointsDirectly(int points, boolean isWeekly) { public void addPointsDirectly(int points, boolean isWeekly) {
int amount = points; int amount = points;
if (isWeekly) { if (isWeekly) {
amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints); amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
} }
if (amount <= 0) { if (amount <= 0) {
return; return;
} }
this.point += amount; this.point += amount;
this.cyclePoints += amount; this.cyclePoints += amount;
if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL && this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) { 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); int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
// Make sure player cant go above max BP level // Make sure player cant go above max BP level
levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups); levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);
// Set new points after level up // Set new points after level up
this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL); this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
this.level += levelups; 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 Map<Integer, BattlePassMission> getMissions() {
public BattlePassMission loadMissionById(int id) { if (this.missions == null) this.missions = new HashMap<>();
return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i)); return this.missions;
} }
public boolean hasMission(int id) {
return getMissions().containsKey(id);
}
public boolean isPaid() { // Will return a new empty mission if the mission id is not found
// ToDo: Change this when we actually support unlocking "paid" BP. public BattlePassMission loadMissionById(int id) {
return true; return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
} }
public Map<Integer, BattlePassReward> getTakenRewards() { public boolean hasMission(int id) {
if (this.takenRewards == null) this.takenRewards = new HashMap<>(); return getMissions().containsKey(id);
return this.takenRewards; }
}
// Mission triggers
public void triggerMission(WatcherTriggerType triggerType) {
getPlayer().getServer().getBattlePassMissionManager().triggerMission(getPlayer(), triggerType);
}
public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
getPlayer().getServer().getBattlePassMissionManager().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) { public boolean isPaid() {
// Sanity checks. // ToDo: Change this when we actually support unlocking "paid" BP.
if (rewardItemData.getItemUse().size() < 1) { return true;
return; }
}
// Get possible item choices. public Map<Integer, BattlePassReward> getTakenRewards() {
String[] choices = rewardItemData.getItemUse().get(0).getUseParam().get(0).split(","); if (this.takenRewards == null) this.takenRewards = new HashMap<>();
if (choices.length < index) { return this.takenRewards;
return; }
}
// Get data for the selected item. // Mission triggers
// This depends on the type of chest. public void triggerMission(WatcherTriggerType triggerType) {
int chosenId = Integer.parseInt(choices[index - 1]); getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
}
// For ITEM_USE_ADD_SELECT_ITEM chests, we can directly add the item specified in the chest's data. public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
if (rewardItemData.getItemUse().get(0).getUseOp().equals("ITEM_USE_ADD_SELECT_ITEM")) { getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType, param, progress);
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().equals("ITEM_USE_GRANT_SELECT_REWARD")) {
RewardData selectedReward = GameData.getRewardDataMap().get(chosenId);
for (var r : selectedReward.getRewardItemList()) { // Handlers
GameItem rewardItem = new GameItem(GameData.getItemDataMap().get(r.getItemId()), r.getItemCount()); public void takeMissionPoint(List<Integer> missionIdList) {
rewardItems.add(rewardItem); // Obvious exploit check
} if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
} return;
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<>(); List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());
for (var option : rewardList) {
var tag = option.getTag();
int index = option.getOptionIdx();
// Make sure we have reward data. for (int id : missionIdList) {
RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId()); // Skip if we dont have this mission
if (reward == null) { if (!this.hasMission(id)) {
continue; 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. BattlePassMission mission = this.loadMissionById(id);
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. if (mission.getData() == null) {
BattlePassReward bpReward = new BattlePassReward(tag.getLevel(), tag.getRewardId(), tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID); this.getMissions().remove(mission.getId());
this.getTakenRewards().put(bpReward.getRewardId(), bpReward); continue;
} }
// 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()) { // Take reward
if (mission.getData().getRefreshType() == null || mission.getData().getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_DAILY) { if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED); this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
mission.setProgress(0); mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);
resetMissions.add(mission); updatedMissions.add(mission);
} }
} }
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions)); if (updatedMissions.size() > 0) {
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer())); // Save to db
} this.save();
public void resetWeeklyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) { // Packet
if (mission.getData().getRefreshType() == BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE) { getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED); getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
mission.setProgress(0); }
}
resetMissions.add(mission); private void takeRewardsFromSelectChest(ItemData rewardItemData, int index, ItemParamData entry, List<GameItem> rewardItems) {
} // Sanity checks.
} if (rewardItemData.getItemUse().size() < 1) {
return;
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions)); // Get possible item choices.
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer())); String[] choices = rewardItemData.getItemUse().get(0).getUseParam()[0].split(",");
} if (choices.length < index) {
return;
// }
public BattlePassSchedule getScheduleProto() {
var currentDate = LocalDate.now(); // Get data for the selected item.
var nextSundayDate = (currentDate.getDayOfWeek() == DayOfWeek.SUNDAY) // This depends on the type of chest.
? currentDate int chosenId = Integer.parseInt(choices[index - 1]);
: LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
var nextSundayTime = LocalDateTime.of(nextSundayDate.getYear(), nextSundayDate.getMonthValue(), nextSundayDate.getDayOfMonth(), 23, 59, 59); // 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) {
BattlePassSchedule.Builder schedule = BattlePassSchedule.newBuilder() 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().equals("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) .setScheduleId(2700)
.setLevel(this.getLevel()) .setLevel(this.getLevel())
.setPoint(this.getPoint()) .setPoint(this.getPoint())
@ -367,21 +368,21 @@ public class BattlePassManager {
.setIsViewed(this.isViewed()) .setIsViewed(this.isViewed())
.setUnlockStatus(this.isPaid() ? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID : BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE) .setUnlockStatus(this.isPaid() ? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID : BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setJPFMGBEBBBJ(2) // Not bought on Playstation. .setJPFMGBEBBBJ(2) // Not bought on Playstation.
.setCurCyclePoints(this.getCyclePoints()) .setCurCyclePoints(this.getCyclePoints())
.setCurCycle(BattlePassCycle.newBuilder() .setCurCycle(BattlePassCycle.newBuilder()
.setBeginTime(0) .setBeginTime(0)
.setEndTime((int)nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond()) .setEndTime((int)nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
.setCycleIdx(3) .setCycleIdx(3)
); );
for (BattlePassReward reward : getTakenRewards().values()) { for (BattlePassReward reward : getTakenRewards().values()) {
schedule.addRewardTakenList(reward.toProto()); schedule.addRewardTakenList(reward.toProto());
} }
return schedule.build(); return schedule.build();
} }
public void save() { public void save() {
DatabaseHelper.saveBattlePass(this); DatabaseHelper.saveBattlePass(this);
} }
} }

View File

@ -1,78 +0,0 @@
package emu.grasscutter.game.battlepass;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
public class BattlePassMissionManager {
private final GameServer server;
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 BattlePassMissionManager(GameServer server) {
this.server = 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

@ -0,0 +1,79 @@
package emu.grasscutter.game.battlepass;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
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;
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

@ -10,8 +10,10 @@ import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.net.proto.RetcodeOuterClass; import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify; import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify;
import emu.grasscutter.server.packet.send.PacketCombineRsp; import emu.grasscutter.server.packet.send.PacketCombineRsp;
@ -29,65 +31,60 @@ import java.util.List;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
public class CombineManger { public class CombineManger extends BaseGameSystem {
private final GameServer gameServer; private final static Int2ObjectMap<List<Integer>> reliquaryDecomposeData = new Int2ObjectOpenHashMap<>();
private final static Int2ObjectMap<List<Integer>> reliquaryDecomposeData = new Int2ObjectOpenHashMap<>();
public GameServer getGameServer() { public CombineManger(GameServer server) {
return gameServer; super(server);
}
public CombineManger(GameServer gameServer) {
this.gameServer = gameServer;
} }
public static void initialize() { public static void initialize() {
// Read the data we need for strongbox. // Read the data we need for strongbox.
try (Reader fileReader = new InputStreamReader(DataLoader.load("ReliquaryDecompose.json"))) { try (Reader fileReader = DataLoader.loadReader("ReliquaryDecompose.json")) {
List<ReliquaryDecomposeEntry> decomposeEntries = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ReliquaryDecomposeEntry.class).getType()); List<ReliquaryDecomposeEntry> decomposeEntries = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ReliquaryDecomposeEntry.class).getType());
for (ReliquaryDecomposeEntry entry : decomposeEntries) { for (ReliquaryDecomposeEntry entry : decomposeEntries) {
reliquaryDecomposeData.put(entry.getConfigId(), entry.getItems()); reliquaryDecomposeData.put(entry.getConfigId(), entry.getItems());
} }
Grasscutter.getLogger().debug("Loaded {} reliquary decompose entries.", reliquaryDecomposeData.size()); Grasscutter.getLogger().debug("Loaded {} reliquary decompose entries.", reliquaryDecomposeData.size());
} }
catch (Exception ex) { catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load reliquary decompose data.", ex); Grasscutter.getLogger().error("Unable to load reliquary decompose data.", ex);
} }
} }
public boolean unlockCombineDiagram(Player player, GameItem diagramItem) { public boolean unlockCombineDiagram(Player player, GameItem diagramItem) {
// Make sure this is actually a diagram. // Make sure this is actually a diagram.
if (!diagramItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_COMBINE")) { if (diagramItem.getItemData().getItemUse().get(0).getUseOp() != ItemUseOp.ITEM_USE_UNLOCK_COMBINE) {
return false; return false;
} }
// Determine the combine item we should unlock. // Determine the combine item we should unlock.
int combineId = Integer.parseInt(diagramItem.getItemData().getItemUse().get(0).getUseParam().get(0)); int combineId = Integer.parseInt(diagramItem.getItemData().getItemUse().get(0).getUseParam()[0]);
// Remove the diagram from the player's inventory. // Remove the diagram from the player's inventory.
// We need to do this here, before sending CombineFormulaDataNotify, or the the combine UI won't correctly // We need to do this here, before sending CombineFormulaDataNotify, or the the combine UI won't correctly
// update when unlocking the diagram. // update when unlocking the diagram.
player.getInventory().removeItem(diagramItem, 1); player.getInventory().removeItem(diagramItem, 1);
// Tell the client that this diagram is now unlocked and add the unlocked item to the player. // Tell the client that this diagram is now unlocked and add the unlocked item to the player.
player.getUnlockedCombines().add(combineId); player.getUnlockedCombines().add(combineId);
player.sendPacket(new PacketCombineFormulaDataNotify(combineId)); player.sendPacket(new PacketCombineFormulaDataNotify(combineId));
return true; return true;
} }
public CombineResult combineItem(Player player, int cid, int count){ public CombineResult combineItem(Player player, int cid, int count) {
// check config exist // check config exist
if(!GameData.getCombineDataMap().containsKey(cid)){ if (!GameData.getCombineDataMap().containsKey(cid)) {
player.getWorld().getHost().sendPacket(new PacketCombineRsp()); player.getWorld().getHost().sendPacket(new PacketCombineRsp());
return null; return null;
} }
CombineData combineData = GameData.getCombineDataMap().get(cid); CombineData combineData = GameData.getCombineDataMap().get(cid);
if(combineData.getPlayerLevel() > player.getLevel()){ if (combineData.getPlayerLevel() > player.getLevel()) {
return null; return null;
} }
@ -124,7 +121,7 @@ public class CombineManger {
player.sendPacket(new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR)); player.sendPacket(new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return; return;
} }
// Check if the number of input items matches the output count. // Check if the number of input items matches the output count.
if (input.size() != count * 3) { if (input.size() != count * 3) {
player.sendPacket(new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR)); player.sendPacket(new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
@ -148,7 +145,7 @@ public class CombineManger {
List<Long> resultItems = new ArrayList<>(); List<Long> resultItems = new ArrayList<>();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int itemId = Utils.drawRandomListElement(possibleDrops); int itemId = Utils.drawRandomListElement(possibleDrops);
GameItem newReliquary = new GameItem(itemId, 1); GameItem newReliquary = new GameItem(itemId, 1);
player.getInventory().addItem(newReliquary); player.getInventory().addItem(newReliquary);
resultItems.add(newReliquary.getGuid()); resultItems.add(newReliquary.getGuid());

View File

@ -12,6 +12,7 @@ import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
@ -23,30 +24,24 @@ import java.io.Reader;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
public class DropManager { public class DropSystem extends BaseGameSystem {
public GameServer getGameServer() { private final Int2ObjectMap<List<DropData>> dropData;
return gameServer;
}
private final GameServer gameServer; public DropSystem(GameServer server) {
super(server);
this.dropData = new Int2ObjectOpenHashMap<>();
this.load();
}
public Int2ObjectMap<List<DropData>> getDropData() { public Int2ObjectMap<List<DropData>> getDropData() {
return dropData; return dropData;
} }
private final Int2ObjectMap<List<DropData>> dropData;
public DropManager(GameServer gameServer) {
this.gameServer = gameServer;
this.dropData = new Int2ObjectOpenHashMap<>();
this.load();
}
public synchronized void load() { public synchronized void load() {
try (Reader fileReader = new InputStreamReader(DataLoader.load("Drop.json"))) { try (Reader fileReader = DataLoader.loadReader("Drop.json")) {
getDropData().clear(); getDropData().clear();
List<DropInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DropInfo.class).getType()); List<DropInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DropInfo.class).getType());
if(banners.size() > 0) { if (banners.size() > 0) {
for (DropInfo di : banners) { for (DropInfo di : banners) {
getDropData().put(di.getMonsterId(), di.getDropDataList()); getDropData().put(di.getMonsterId(), di.getDropDataList());
} }

View File

@ -1,117 +0,0 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
import emu.grasscutter.utils.Position;
import java.util.List;
public class DungeonManager {
private final GameServer server;
private static final BasicDungeonSettleListener basicDungeonSettleObserver = new BasicDungeonSettleListener();
public DungeonManager(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
if (entry == null) {
// Error
player.sendPacket(new PacketDungeonEntryInfoRsp());
return;
}
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger().info("{}({}) is trying to enter dungeon {}" ,player.getNickname(),player.getUid(),dungeonId);
int sceneId = data.getSceneId();
player.getScene().setPrevScene(sceneId);
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
}
player.getScene().setPrevScenePoint(pointId);
player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId));
return true;
}
/**
* used in tower dungeons handoff
*/
public boolean handoffDungeon(Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger().info("{}({}) is trying to enter tower dungeon {}" ,player.getNickname(),player.getUid(),dungeonId);
if(player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)){
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
}
return true;
}
public void exitDungeon(Player player) {
Scene scene = player.getScene();
if (scene==null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
return;
}
// Get previous scene
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position
DungeonData dungeonData = scene.getDungeonData();
Position prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) {
prevPos.set(entry.getPointData().getTranPos());
}
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
// Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
}
public void updateDailyDungeons() {
for (ScenePointEntry entry : GameData.getScenePointEntries().values()) {
entry.getPointData().updateDailyDungeon();
}
}
}

View File

@ -0,0 +1,114 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
import emu.grasscutter.utils.Position;
import java.util.List;
public class DungeonSystem extends BaseGameSystem {
private static final BasicDungeonSettleListener basicDungeonSettleObserver = new BasicDungeonSettleListener();
public DungeonSystem(GameServer server) {
super(server);
}
public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
if (entry == null) {
// Error
player.sendPacket(new PacketDungeonEntryInfoRsp());
return;
}
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger().info("{}({}) is trying to enter dungeon {}" ,player.getNickname(),player.getUid(),dungeonId);
int sceneId = data.getSceneId();
player.getScene().setPrevScene(sceneId);
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
}
player.getScene().setPrevScenePoint(pointId);
player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId));
return true;
}
/**
* used in tower dungeons handoff
*/
public boolean handoffDungeon(Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger().info("{}({}) is trying to enter tower dungeon {}" ,player.getNickname(),player.getUid(),dungeonId);
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
}
return true;
}
public void exitDungeon(Player player) {
Scene scene = player.getScene();
if (scene==null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
return;
}
// Get previous scene
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position
DungeonData dungeonData = scene.getDungeonData();
Position prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) {
prevPos.set(entry.getPointData().getTranPos());
}
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
// Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
}
public void updateDailyDungeons() {
for (ScenePointEntry entry : GameData.getScenePointEntries().values()) {
entry.getPointData().updateDailyDungeon();
}
}
}

View File

@ -36,180 +36,180 @@ import com.google.gson.reflect.TypeToken;
public class DungeonChallenge extends WorldChallenge { public class DungeonChallenge extends WorldChallenge {
/** /**
* has more challenge * has more challenge
*/ */
private boolean stage; private boolean stage;
private IntSet rewardedPlayers; private IntSet rewardedPlayers;
private final static Int2ObjectMap<List<DungeonDropEntry>> dungeonDropData = new Int2ObjectOpenHashMap<>(); private final static Int2ObjectMap<List<DungeonDropEntry>> dungeonDropData = new Int2ObjectOpenHashMap<>();
public static void initialize() { public static void initialize() {
// Read the data we need for dungeon rewards drops. // Read the data we need for dungeon rewards drops.
try (Reader fileReader = new InputStreamReader(DataLoader.load("DungeonDrop.json"))) { try (Reader fileReader = DataLoader.loadReader("DungeonDrop.json")) {
List<DungeonDrop> dungeonDropList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DungeonDrop.class).getType()); List<DungeonDrop> dungeonDropList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DungeonDrop.class).getType());
for (DungeonDrop entry : dungeonDropList) { for (DungeonDrop entry : dungeonDropList) {
dungeonDropData.put(entry.getDungeonId(), entry.getDrops()); dungeonDropData.put(entry.getDungeonId(), entry.getDrops());
} }
Grasscutter.getLogger().debug("Loaded {} dungeon drop data entries.", dungeonDropData.size()); Grasscutter.getLogger().debug("Loaded {} dungeon drop data entries.", dungeonDropData.size());
} }
catch (Exception ex) { catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load dungeon drop data.", ex); Grasscutter.getLogger().error("Unable to load dungeon drop data.", ex);
} }
} }
public DungeonChallenge(Scene scene, SceneGroup group, public DungeonChallenge(Scene scene, SceneGroup group,
int challengeId, int challengeIndex, int challengeId, int challengeIndex,
List<Integer> paramList, List<Integer> paramList,
int timeLimit, int goal, int timeLimit, int goal,
List<ChallengeTrigger> challengeTriggers) { List<ChallengeTrigger> challengeTriggers) {
super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers); super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers);
this.setRewardedPlayers(new IntOpenHashSet()); this.setRewardedPlayers(new IntOpenHashSet());
} }
public boolean isStage() { public boolean isStage() {
return stage; return stage;
} }
public void setStage(boolean stage) { public void setStage(boolean stage) {
this.stage = stage; this.stage = stage;
} }
public IntSet getRewardedPlayers() { public IntSet getRewardedPlayers() {
return rewardedPlayers; return rewardedPlayers;
} }
public void setRewardedPlayers(IntSet rewardedPlayers) { public void setRewardedPlayers(IntSet rewardedPlayers) {
this.rewardedPlayers = rewardedPlayers; this.rewardedPlayers = rewardedPlayers;
} }
@Override @Override
public void done() { public void done() {
super.done(); super.done();
if (this.isSuccess()) { if (this.isSuccess()) {
// Settle // Settle
settle(); settle();
} }
} }
private void settle() { private void settle() {
if(!stage){ if (!stage) {
getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene()));
getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE,
new ScriptArgs(this.isSuccess() ? 1 : 0)); new ScriptArgs(this.isSuccess() ? 1 : 0));
// Battle pass trigger // Battle pass trigger
this.getScene().getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON)); this.getScene().getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON));
} }
} }
private List<GameItem> rollRewards(boolean useCondensed) { private List<GameItem> rollRewards(boolean useCondensed) {
List<GameItem> rewards = new ArrayList<>(); List<GameItem> rewards = new ArrayList<>();
int dungeonId = this.getScene().getDungeonData().getId(); int dungeonId = this.getScene().getDungeonData().getId();
// If we have specific drop data for this dungeon, we use it. // If we have specific drop data for this dungeon, we use it.
if (dungeonDropData.containsKey(dungeonId)) { if (dungeonDropData.containsKey(dungeonId)) {
List<DungeonDropEntry> dropEntries = dungeonDropData.get(dungeonId); List<DungeonDropEntry> dropEntries = dungeonDropData.get(dungeonId);
// Roll for each drop group. // Roll for each drop group.
for (var entry : dropEntries) { for (var entry : dropEntries) {
// Determine the number of drops we get for this entry. // Determine the number of drops we get for this entry.
int start = entry.getCounts().get(0); int start = entry.getCounts().get(0);
int end = entry.getCounts().get(entry.getCounts().size() - 1); int end = entry.getCounts().get(entry.getCounts().size() - 1);
var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList()); var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList());
int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities()); int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
if (useCondensed) { if (useCondensed) {
amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities()); amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
} }
// Double rewards in multiplay mode, if specified. // Double rewards in multiplay mode, if specified.
if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) { if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) {
amount *= 2; amount *= 2;
} }
// Roll items for this group. // Roll items for this group.
// Here, we have to handle stacking, or the client will not display results correctly. // Here, we have to handle stacking, or the client will not display results correctly.
// For now, we use the following logic: If the possible drop item are a list of multiple items, // For now, we use the following logic: If the possible drop item are a list of multiple items,
// we roll them separately. If not, we stack them. This should work out in practice, at least // we roll them separately. If not, we stack them. This should work out in practice, at least
// for the currently existing set of dungeons. // for the currently existing set of dungeons.
if (entry.getItems().size() == 1) { if (entry.getItems().size() == 1) {
rewards.add(new GameItem(entry.getItems().get(0), amount)); rewards.add(new GameItem(entry.getItems().get(0), amount));
} }
else { else {
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
// int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size()); // int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size());
// int itemId = entry.getItems().get(itemIndex); // int itemId = entry.getItems().get(itemIndex);
int itemId = Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities()); int itemId = Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities());
rewards.add(new GameItem(itemId, 1)); rewards.add(new GameItem(itemId, 1));
} }
} }
} }
} }
// Otherwise, we fall back to the preview data. // Otherwise, we fall back to the preview data.
else { else {
Grasscutter.getLogger().info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId); Grasscutter.getLogger().info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) { for (ItemParamData param : getScene().getDungeonData().getRewardPreview().getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1))); rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
} }
} }
return rewards; return rewards;
} }
public void getStatueDrops(Player player, GadgetInteractReq request) { public void getStatueDrops(Player player, GadgetInteractReq request) {
DungeonData dungeonData = getScene().getDungeonData(); DungeonData dungeonData = getScene().getDungeonData();
int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20; int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) { if (!isSuccess() || dungeonData == null || dungeonData.getRewardPreview() == null || dungeonData.getRewardPreview().getPreviewItems().length == 0) {
return; return;
} }
// Already rewarded // Already rewarded
if (getRewardedPlayers().contains(player.getUid())) { if (getRewardedPlayers().contains(player.getUid())) {
return; return;
} }
// Get rewards. // Get rewards.
List<GameItem> rewards = new ArrayList<>(); List<GameItem> rewards = new ArrayList<>();
if (request.getIsUseCondenseResin()) { if (request.getIsUseCondenseResin()) {
// Check if condensed resin is usable here. // Check if condensed resin is usable here.
// For this, we use the following logic for now: // For this, we use the following logic for now:
// The normal resin cost of the dungeon has to be 20. // The normal resin cost of the dungeon has to be 20.
if (resinCost != 20) { if (resinCost != 20) {
return; return;
} }
// Make sure the player has condensed resin. // Make sure the player has condensed resin.
GameItem condensedResin = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(220007); GameItem condensedResin = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(220007);
if (condensedResin == null || condensedResin.getCount() <= 0) { if (condensedResin == null || condensedResin.getCount() <= 0) {
return; return;
} }
// Deduct. // Deduct.
player.getInventory().removeItem(condensedResin, 1); player.getInventory().removeItem(condensedResin, 1);
// Roll rewards. // Roll rewards.
rewards.addAll(this.rollRewards(true)); rewards.addAll(this.rollRewards(true));
} }
else { else {
// If the player used regular resin, try to deduct. // If the player used regular resin, try to deduct.
// Stop if insufficient resin. // Stop if insufficient resin.
boolean success = player.getResinManager().useResin(resinCost); boolean success = player.getResinManager().useResin(resinCost);
if (!success) { if (!success) {
return; return;
} }
// Roll rewards. // Roll rewards.
rewards.addAll(this.rollRewards(false)); rewards.addAll(this.rollRewards(false));
} }
// Add rewards to player and send notification. // Add rewards to player and send notification.
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop); player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards)); player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
getRewardedPlayers().add(player.getUid()); getRewardedPlayers().add(player.getUid());
} }
} }

View File

@ -30,7 +30,6 @@ import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.event.player.PlayerMoveEvent;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
@ -64,13 +63,13 @@ public class EntityAvatar extends GameEntity {
this.avatar.setCurrentEnergy(); this.avatar.setCurrentEnergy();
} }
public Player getPlayer() { public Player getPlayer() {
return avatar.getPlayer(); return avatar.getPlayer();
} }
@Override @Override
public Position getPosition() { public Position getPosition() {
return getPlayer().getPos(); return getPlayer().getPosition();
} }
@Override @Override
@ -107,12 +106,12 @@ public class EntityAvatar extends GameEntity {
return 0; return 0;
} }
@Override @Override
public void onDeath(int killerId) { public void onDeath(int killerId) {
this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER; this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER;
this.killedBy = killerId; this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE); clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
} }
public void onDeath(PlayerDieType dieType, int killerId) { public void onDeath(PlayerDieType dieType, int killerId) {
this.killedType = dieType; this.killedType = dieType;
@ -126,7 +125,7 @@ public class EntityAvatar extends GameEntity {
if (healed > 0f) { if (healed > 0f) {
getScene().broadcastPacket( getScene().broadcastPacket(
new PacketEntityFightPropChangeReasonNotify(this, FightProperty.FIGHT_PROP_CUR_HP, healed, PropChangeReason.PROP_CHANGE_REASON_ABILITY, ChangeHpReason.CHANGE_HP_REASON_CHANGE_HP_ADD_ABILITY) new PacketEntityFightPropChangeReasonNotify(this, FightProperty.FIGHT_PROP_CUR_HP, healed, PropChangeReason.PROP_CHANGE_REASON_ABILITY, ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY)
); );
} }
@ -138,14 +137,14 @@ public class EntityAvatar extends GameEntity {
FightProperty curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); FightProperty curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
FightProperty maxEnergyProp = this.getAvatar().getSkillDepot().getElementType().getMaxEnergyProp(); FightProperty maxEnergyProp = this.getAvatar().getSkillDepot().getElementType().getMaxEnergyProp();
// Get max energy. // Get max energy.
float maxEnergy = this.avatar.getFightProperty(maxEnergyProp); float maxEnergy = this.avatar.getFightProperty(maxEnergyProp);
// Set energy to zero. // Set energy to zero.
this.avatar.setCurrentEnergy(curEnergyProp, 0); this.avatar.setCurrentEnergy(curEnergyProp, 0);
// Send packets. // Send packets.
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp)); this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) { if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) {
this.getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -maxEnergy, reason)); this.getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -maxEnergy, reason));
@ -166,10 +165,10 @@ public class EntityAvatar extends GameEntity {
// Get energy recharge. // Get energy recharge.
float energyRecharge = this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY); float energyRecharge = this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY);
// Scale amount by energy recharge, if the amount is not flat. // Scale amount by energy recharge, if the amount is not flat.
if (!isFlat) { if (!isFlat) {
amount *= energyRecharge; amount *= energyRecharge;
} }
// Determine the new energy value. // Determine the new energy value.
float newEnergy = Math.min(curEnergy + amount, maxEnergy); float newEnergy = Math.min(curEnergy + amount, maxEnergy);

View File

@ -3,6 +3,7 @@ package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.GadgetData; import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.gadget.*; import emu.grasscutter.game.entity.gadget.*;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.LifeState;
@ -15,6 +16,7 @@ import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityIn
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair; import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
@ -22,6 +24,7 @@ import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGadget; import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.scripts.data.ScriptArgs;
@ -35,186 +38,195 @@ import lombok.ToString;
@ToString(callSuper = true) @ToString(callSuper = true)
public class EntityGadget extends EntityBaseGadget { public class EntityGadget extends EntityBaseGadget {
private final GadgetData data; private final GadgetData data;
private final Position pos; private final Position pos;
private final Position rot; private final Position rot;
private int gadgetId; private int gadgetId;
private int state; private int state;
private int pointType; private int pointType;
private GadgetContent content; private GadgetContent content;
private Int2FloatOpenHashMap fightProp; private Int2FloatOpenHashMap fightProp;
private SceneGadget metaGadget; private SceneGadget metaGadget;
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) { public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) {
super(scene); super(scene);
this.data = GameData.getGadgetDataMap().get(gadgetId); this.data = GameData.getGadgetDataMap().get(gadgetId);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET); this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.gadgetId = gadgetId; this.gadgetId = gadgetId;
this.pos = pos.clone(); this.pos = pos.clone();
this.rot = rot != null ? rot.clone() : new Position(); this.rot = rot != null ? rot.clone() : new Position();
} }
public EntityGadget(Scene scene, int gadgetId, Position pos) { public EntityGadget(Scene scene, int gadgetId, Position pos) {
this(scene, gadgetId, pos, new Position()); this(scene, gadgetId, pos, new Position());
} }
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) { public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) {
this(scene, gadgetId, pos, rot); this(scene, gadgetId, pos, rot);
this.content = content; this.content = content;
} }
public GadgetData getGadgetData() { public GadgetData getGadgetData() {
return data; return data;
} }
@Override @Override
public Position getPosition() { public Position getPosition() {
return this.pos; return this.pos;
} }
@Override @Override
public Position getRotation() { public Position getRotation() {
return this.rot; return this.rot;
} }
public int getGadgetId() { public int getGadgetId() {
return gadgetId; return gadgetId;
} }
public void setGadgetId(int gadgetId) { public void setGadgetId(int gadgetId) {
this.gadgetId = gadgetId; this.gadgetId = gadgetId;
} }
public int getState() { public int getState() {
return state; return state;
} }
public void setState(int state) { public void setState(int state) {
this.state = state; this.state = state;
} }
public void updateState(int state){ public void updateState(int state) {
this.setState(state); this.setState(state);
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state)); this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(state, this.getConfigId())); getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(state, this.getConfigId()));
} }
public int getPointType() { public int getPointType() {
return pointType; return pointType;
} }
public void setPointType(int pointType) { public void setPointType(int pointType) {
this.pointType = pointType; this.pointType = pointType;
} }
public GadgetContent getContent() { public GadgetContent getContent() {
return content; return content;
} }
@Deprecated // Dont use! @Deprecated // Dont use!
public void setContent(GadgetContent content) { public void setContent(GadgetContent content) {
this.content = this.content == null ? content : this.content; this.content = this.content == null ? content : this.content;
} }
public SceneGadget getMetaGadget() { public SceneGadget getMetaGadget() {
return metaGadget; return metaGadget;
} }
public void setMetaGadget(SceneGadget metaGadget) { public void setMetaGadget(SceneGadget metaGadget) {
this.metaGadget = metaGadget; this.metaGadget = metaGadget;
} }
// TODO refactor // TODO refactor
public void buildContent() { public void buildContent() {
if (getContent() != null || getGadgetData() == null || getGadgetData().getType() == null) { if (getContent() != null || getGadgetData() == null || getGadgetData().getType() == null) {
return; return;
} }
EntityType type = getGadgetData().getType(); EntityType type = getGadgetData().getType();
GadgetContent content = switch (type) { GadgetContent content = switch (type) {
case GatherPoint -> new GadgetGatherPoint(this); case GatherPoint -> new GadgetGatherPoint(this);
case GatherObject -> new GadgetGatherObject(this); case GatherObject -> new GadgetGatherObject(this);
case Worktop -> new GadgetWorktop(this); case Worktop -> new GadgetWorktop(this);
case RewardStatue -> new GadgetRewardStatue(this); case RewardStatue -> new GadgetRewardStatue(this);
case Chest -> new GadgetChest(this); case Chest -> new GadgetChest(this);
case Gadget -> new GadgetObject(this); case Gadget -> new GadgetObject(this);
default -> null; default -> null;
}; };
this.content = content; this.content = content;
} }
@Override @Override
public Int2FloatOpenHashMap getFightProperties() { public Int2FloatOpenHashMap getFightProperties() {
if (this.fightProp == null) this.fightProp = new Int2FloatOpenHashMap(); if (this.fightProp == null) this.fightProp = new Int2FloatOpenHashMap();
return this.fightProp; return this.fightProp;
} }
@Override @Override
public void onCreate() { public void onInteract(Player player, GadgetInteractReq interactReq) {
// Lua event if (this.getContent() == null) {
getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId())); return;
} }
@Override boolean shouldDelete = this.getContent().onInteract(player, interactReq);
public void onDeath(int killerId) {
if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
if (getScene().getChallenge() != null) {
getScene().getChallenge().onGadgetDeath(this);
}
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId()));
}
@Override if (shouldDelete) {
public SceneEntityInfo toProto() { this.getScene().killEntity(this);
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder() }
.setAbilityInfo(AbilitySyncStateInfo.newBuilder()) }
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() @Override
.setEntityId(getId()) public void onCreate() {
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET) // Lua event
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) getScene().getScriptManager().callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId()));
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) }
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder() @Override
.setType(PlayerProperty.PROP_LEVEL.getId()) public void onDeath(int killerId) {
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1)) if (this.getSpawnEntry() != null) {
.build(); this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
entityInfo.addPropList(pair); }
if (getScene().getChallenge() != null) {
getScene().getChallenge().onGadgetDeath(this);
}
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId()));
}
// We do not use the getter to null check because the getter will create a fight prop map if it is null @Override
if (this.fightProp != null) { public SceneEntityInfo toProto() {
this.addAllFightPropsToEntityInfo(entityInfo); EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
} .setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder() SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setGadgetId(this.getGadgetId()) .setEntityId(getId())
.setGroupId(this.getGroupId()) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setConfigId(this.getConfigId()) .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.setGadgetState(this.getState()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setIsEnableInteract(true) .setEntityClientData(EntityClientData.newBuilder())
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); .setEntityAuthorityInfo(authority)
.setLifeState(1);
if (this.getContent() != null) { PropPair pair = PropPair.newBuilder()
this.getContent().onBuildProto(gadgetInfo); .setType(PlayerProperty.PROP_LEVEL.getId())
} .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
entityInfo.setGadget(gadgetInfo); // We do not use the getter to null check because the getter will create a fight prop map if it is null
if (this.fightProp != null) {
this.addAllFightPropsToEntityInfo(entityInfo);
}
return entityInfo.build(); SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
} .setGadgetId(this.getGadgetId())
public void die() { .setGroupId(this.getGroupId())
getScene().broadcastPacket(new PacketLifeStateChangeNotify(this, LifeState.LIFE_DEAD)); .setConfigId(this.getConfigId())
this.onDeath(0); .setGadgetState(this.getState())
} .setIsEnableInteract(true)
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId());
if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo);
}
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
} }

View File

@ -3,6 +3,7 @@ package emu.grasscutter.game.entity;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
@ -12,6 +13,8 @@ import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityIn
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetBornTypeOuterClass.GadgetBornType; import emu.grasscutter.net.proto.GadgetBornTypeOuterClass.GadgetBornType;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
@ -19,120 +22,145 @@ import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityItem extends EntityBaseGadget { public class EntityItem extends EntityBaseGadget {
private final Position pos; private final Position pos;
private final Position rot; private final Position rot;
private final GameItem item;
private final long guid;
private final boolean share; private final GameItem item;
private final long guid;
public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count) { private final boolean share;
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.pos = new Position(pos);
this.rot = new Position();
this.guid = player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid();
this.item = new GameItem(itemData, count);
this.share = true;
}
// In official game, some drop items are shared to all players, and some other items are independent to all players public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count) {
// For example, if you killed a monster in MP mode, all players could get drops but rarity and number of them are different super(scene);
// but if you broke regional mine, when someone picked up the drop then it disappeared this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count, boolean share) { this.pos = new Position(pos);
super(scene); this.rot = new Position();
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET); this.guid = player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid();
this.pos = new Position(pos); this.item = new GameItem(itemData, count);
this.rot = new Position(); this.share = true;
this.guid = player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid(); }
this.item = new GameItem(itemData, count);
this.share = share;
}
@Override
public int getId() {
return this.id;
}
private GameItem getItem() {
return this.item;
}
public ItemData getItemData() { // In official game, some drop items are shared to all players, and some other items are independent to all players
return this.getItem().getItemData(); // For example, if you killed a monster in MP mode, all players could get drops but rarity and number of them are different
} // but if you broke regional mine, when someone picked up the drop then it disappeared
public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count, boolean share) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.pos = new Position(pos);
this.rot = new Position();
this.guid = player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid();
this.item = new GameItem(itemData, count);
this.share = share;
}
public long getGuid() { @Override
return guid; public int getId() {
} return this.id;
}
public int getCount() { private GameItem getItem() {
return this.getItem().getCount(); return this.item;
} }
@Override
public int getGadgetId() {
return this.getItemData().getGadgetId();
}
@Override public ItemData getItemData() {
public Position getPosition() { return this.getItem().getItemData();
return this.pos; }
}
@Override public long getGuid() {
public Position getRotation() { return guid;
return this.rot; }
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
return null;
}
public boolean isShare() { public int getCount() {
return share; return this.getItem().getCount();
} }
@Override @Override
public SceneEntityInfo toProto() { public int getGadgetId() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder() return this.getItemData().getGadgetId();
.setAbilityInfo(AbilitySyncStateInfo.newBuilder()) }
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getItemData().getGadgetId())
.setTrifleItem(this.getItem().toProto())
.setBornType(GadgetBornType.GADGET_BORN_TYPE_IN_AIR)
.setAuthorityPeerId(this.getWorld().getHostPeerId())
.setIsEnableInteract(true);
entityInfo.setGadget(gadgetInfo); @Override
public Position getPosition() {
return entityInfo.build(); return this.pos;
} }
@Override
public Position getRotation() {
return this.rot;
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
return null;
}
public boolean isShare() {
return share;
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
// check drop owner to avoid someone picked up item in others' world
if (!this.isShare()) {
int dropOwner = (int) (this.getGuid() >> 32);
if (dropOwner != player.getUid()) {
return;
}
}
this.getScene().removeEntity(this);
GameItem item = new GameItem(this.getItemData(), this.getCount());
// Add to inventory
boolean success = player.getInventory().addItem(item, ActionReason.SubfieldDrop);
if (success) {
if (!this.isShare()) { // not shared drop
player.sendPacket(new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
} else {
this.getScene().broadcastPacket(new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
}
}
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getItemData().getGadgetId())
.setTrifleItem(this.getItem().toProto())
.setBornType(GadgetBornType.GADGET_BORN_TYPE_IN_AIR)
.setAuthorityPeerId(this.getWorld().getHostPeerId())
.setIsEnableInteract(true);
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
} }

View File

@ -1,21 +1,29 @@
package emu.grasscutter.game.entity; package emu.grasscutter.game.entity;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.common.PropGrowCurve; import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.excels.EnvAnimalGatherConfigData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.MonsterCurveData; import emu.grasscutter.data.excels.MonsterCurveData;
import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair; import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MonsterBornTypeOuterClass.MonsterBornType; import emu.grasscutter.net.proto.MonsterBornTypeOuterClass.MonsterBornType;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
@ -31,244 +39,257 @@ import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityMonster extends GameEntity { public class EntityMonster extends GameEntity {
private final MonsterData monsterData; private final MonsterData monsterData;
private final Int2FloatOpenHashMap fightProp; private final Int2FloatOpenHashMap fightProp;
private final Position pos;
private final Position rot;
private final Position bornPos;
private final int level;
private int weaponEntityId;
private int poseId;
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
super(scene);
this.id = getWorld().getNextEntityId(EntityIdType.MONSTER);
this.monsterData = monsterData;
this.fightProp = new Int2FloatOpenHashMap();
this.pos = new Position(pos);
this.rot = new Position();
this.bornPos = getPosition().clone();
this.level = level;
// Monster weapon
if (getMonsterWeaponId() > 0) {
this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON);
}
this.recalcStats();
}
@Override
public int getId() {
return this.id;
}
public MonsterData getMonsterData() { private final Position pos;
return monsterData; private final Position rot;
} private final Position bornPos;
private final int level;
public int getMonsterWeaponId() { private int weaponEntityId;
return getMonsterData().getWeaponId(); private int poseId;
}
private int getMonsterId() {
return this.getMonsterData().getId();
}
public int getLevel() { public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
return level; super(scene);
} this.id = getWorld().getNextEntityId(EntityIdType.MONSTER);
this.monsterData = monsterData;
this.fightProp = new Int2FloatOpenHashMap();
this.pos = new Position(pos);
this.rot = new Position();
this.bornPos = getPosition().clone();
this.level = level;
@Override // Monster weapon
public Position getPosition() { if (getMonsterWeaponId() > 0) {
return this.pos; this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON);
} }
@Override this.recalcStats();
public Position getRotation() { }
return this.rot;
}
public Position getBornPos() {
return bornPos;
}
@Override @Override
public Int2FloatOpenHashMap getFightProperties() { public int getId() {
return fightProp; return this.id;
} }
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
public int getPoseId() { public MonsterData getMonsterData() {
return poseId; return monsterData;
} }
public void setPoseId(int poseId) { public int getMonsterWeaponId() {
this.poseId = poseId; return getMonsterData().getWeaponId();
} }
@Override
public void onCreate() {
// Lua event
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId()));
}
@Override private int getMonsterId() {
public void damage(float amount, int killerId) { return this.getMonsterData().getId();
// Get HP before damage. }
float hpBeforeDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Apply damage. public int getLevel() {
super.damage(amount, killerId); return level;
}
// Get HP after damage. @Override
float hpAfterDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); public Position getPosition() {
return this.pos;
}
// Invoke energy drop logic. @Override
for (Player player : this.getScene().getPlayers()) { public Position getRotation() {
player.getEnergyManager().handleMonsterEnergyDrop(this, hpBeforeDamage, hpAfterDamage); return this.rot;
} }
}
@Override public Position getBornPos() {
public void onDeath(int killerId) { return bornPos;
if (this.getSpawnEntry() != null) { }
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
// first set the challenge data
if (getScene().getChallenge() != null) {
getScene().getChallenge().onMonsterDeath(this);
}
if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) {
if(getScene().getScriptManager().getScriptMonsterSpawnService() != null){
getScene().getScriptManager().getScriptMonsterSpawnService().onMonsterDead(this);
}
// prevent spawn monster after success
if(getScene().getChallenge() != null && getScene().getChallenge().inProgress()){
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
}else if(getScene().getChallenge() == null){
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
}
}
// Battle Pass trigger
getScene().getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
}
public void recalcStats() {
// Monster data
MonsterData data = this.getMonsterData();
// Get hp percent, set to 100% if none @Override
float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); public Int2FloatOpenHashMap getFightProperties() {
return fightProp;
// Clear properties }
this.getFightProperties().clear();
// Base stats
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, data.getBaseHp());
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, data.getBaseAttack());
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, data.getBaseDefense());
this.setFightProperty(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, data.getPhysicalSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_FIRE_SUB_HURT, .1f);
this.setFightProperty(FightProperty.FIGHT_PROP_ELEC_SUB_HURT, data.getElecSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_WATER_SUB_HURT, data.getWaterSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_GRASS_SUB_HURT, data.getGrassSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_WIND_SUB_HURT, data.getWindSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_ROCK_SUB_HURT, .1f);
this.setFightProperty(FightProperty.FIGHT_PROP_ICE_SUB_HURT, data.getIceSubHurt());
// Level curve
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel());
if (curve != null) {
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
this.setFightProperty(prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
}
}
// Set % stats
this.setFightProperty(
FightProperty.FIGHT_PROP_MAX_HP,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_HP) * (1f + getFightProperty(FightProperty.FIGHT_PROP_HP_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_HP)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_ATTACK,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK) * (1f + getFightProperty(FightProperty.FIGHT_PROP_ATTACK_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_ATTACK)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_DEFENSE,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE) * (1f + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE)
);
// Set current hp
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
}
@Override @Override
public SceneEntityInfo toProto() { public boolean isAlive() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder() return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
.setAbilityInfo(AbilitySyncStateInfo.newBuilder()) }
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(this.getBornPos().toProto()))
.setBornPos(this.getBornPos().toProto())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
.setMotionInfo(this.getMotionInfo())
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(this.getLifeState().getValue());
for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) {
if (entry.getIntKey() == 0) {
continue;
}
FightPropPair fightProp = FightPropPair.newBuilder().setPropType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build();
entityInfo.addFightPropList(fightProp);
}
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel()))
.build();
entityInfo.addPropList(pair);
SceneMonsterInfo.Builder monsterInfo = SceneMonsterInfo.newBuilder()
.setMonsterId(getMonsterId())
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.addAllAffixList(getMonsterData().getAffix())
.setAuthorityPeerId(getWorld().getHostPeerId())
.setPoseId(this.getPoseId())
.setBlockId(3001)
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT)
.setSpecialNameId(40);
if (getMonsterData().getDescribeData() != null) {
monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleID());
}
if (this.getMonsterWeaponId() > 0) {
SceneWeaponInfo weaponInfo = SceneWeaponInfo.newBuilder()
.setEntityId(this.weaponEntityId)
.setGadgetId(this.getMonsterWeaponId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.build();
monsterInfo.addWeaponList(weaponInfo);
}
entityInfo.setMonster(monsterInfo);
return entityInfo.build(); public int getPoseId() {
} return poseId;
}
public void setPoseId(int poseId) {
this.poseId = poseId;
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
EnvAnimalGatherConfigData gatherData = GameData.getEnvAnimalGatherConfigDataMap().get(this.getMonsterData().getId());
if (gatherData == null) {
return;
}
player.getInventory().addItem(gatherData.getGatherItem(), ActionReason.SubfieldDrop);
this.getScene().killEntity(this);
}
@Override
public void onCreate() {
// Lua event
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId()));
}
@Override
public void damage(float amount, int killerId) {
// Get HP before damage.
float hpBeforeDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Apply damage.
super.damage(amount, killerId);
// Get HP after damage.
float hpAfterDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
// Invoke energy drop logic.
for (Player player : this.getScene().getPlayers()) {
player.getEnergyManager().handleMonsterEnergyDrop(this, hpBeforeDamage, hpAfterDamage);
}
}
@Override
public void onDeath(int killerId) {
if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
// first set the challenge data
if (getScene().getChallenge() != null) {
getScene().getChallenge().onMonsterDeath(this);
}
if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) {
if (getScene().getScriptManager().getScriptMonsterSpawnService() != null) {
getScene().getScriptManager().getScriptMonsterSpawnService().onMonsterDead(this);
}
// prevent spawn monster after success
if (getScene().getChallenge() != null && getScene().getChallenge().inProgress()) {
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
}else if (getScene().getChallenge() == null) {
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
}
}
// Battle Pass trigger
getScene().getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
}
public void recalcStats() {
// Monster data
MonsterData data = this.getMonsterData();
// Get hp percent, set to 100% if none
float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
// Clear properties
this.getFightProperties().clear();
// Base stats
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, data.getBaseHp());
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, data.getBaseAttack());
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, data.getBaseDefense());
this.setFightProperty(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, data.getPhysicalSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_FIRE_SUB_HURT, .1f);
this.setFightProperty(FightProperty.FIGHT_PROP_ELEC_SUB_HURT, data.getElecSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_WATER_SUB_HURT, data.getWaterSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_GRASS_SUB_HURT, data.getGrassSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_WIND_SUB_HURT, data.getWindSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_ROCK_SUB_HURT, .1f);
this.setFightProperty(FightProperty.FIGHT_PROP_ICE_SUB_HURT, data.getIceSubHurt());
// Level curve
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel());
if (curve != null) {
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
this.setFightProperty(prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
}
}
// Set % stats
this.setFightProperty(
FightProperty.FIGHT_PROP_MAX_HP,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_HP) * (1f + getFightProperty(FightProperty.FIGHT_PROP_HP_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_HP)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_ATTACK,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK) * (1f + getFightProperty(FightProperty.FIGHT_PROP_ATTACK_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_ATTACK)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_DEFENSE,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE) * (1f + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE)
);
// Set current hp
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(this.getBornPos().toProto()))
.setBornPos(this.getBornPos().toProto())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
.setMotionInfo(this.getMotionInfo())
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(this.getLifeState().getValue());
for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) {
if (entry.getIntKey() == 0) {
continue;
}
FightPropPair fightProp = FightPropPair.newBuilder().setPropType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build();
entityInfo.addFightPropList(fightProp);
}
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel()))
.build();
entityInfo.addPropList(pair);
SceneMonsterInfo.Builder monsterInfo = SceneMonsterInfo.newBuilder()
.setMonsterId(getMonsterId())
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.addAllAffixList(getMonsterData().getAffix())
.setAuthorityPeerId(getWorld().getHostPeerId())
.setPoseId(this.getPoseId())
.setBlockId(3001)
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT)
.setSpecialNameId(40);
if (getMonsterData().getDescribeData() != null) {
monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleID());
}
if (this.getMonsterWeaponId() > 0) {
SceneWeaponInfo weaponInfo = SceneWeaponInfo.newBuilder()
.setEntityId(this.weaponEntityId)
.setGadgetId(this.getMonsterWeaponId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.build();
monsterInfo.addWeaponList(weaponInfo);
}
entityInfo.setMonster(monsterInfo);
return entityInfo.build();
}
} }

View File

@ -10,6 +10,7 @@ import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.Animat
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.FightPropPairOuterClass.*; import emu.grasscutter.net.proto.FightPropPairOuterClass.*;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
@ -30,104 +31,104 @@ import java.util.ArrayList;
public class EntityVehicle extends EntityBaseGadget { public class EntityVehicle extends EntityBaseGadget {
private final Player owner; private final Player owner;
private final Int2FloatOpenHashMap fightProp; private final Int2FloatOpenHashMap fightProp;
private final Position pos; private final Position pos;
private final Position rot; private final Position rot;
private final int pointId; private final int pointId;
private final int gadgetId; private final int gadgetId;
private float curStamina; private float curStamina;
private List<VehicleMember> vehicleMembers; private List<VehicleMember> vehicleMembers;
public EntityVehicle(Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) { public EntityVehicle(Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) {
super(scene); super(scene);
this.owner = player; this.owner = player;
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET); this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.fightProp = new Int2FloatOpenHashMap(); this.fightProp = new Int2FloatOpenHashMap();
this.pos = new Position(pos); this.pos = new Position(pos);
this.rot = new Position(rot); this.rot = new Position(rot);
this.gadgetId = gadgetId; this.gadgetId = gadgetId;
this.pointId = pointId; this.pointId = pointId;
this.curStamina = 240; this.curStamina = 240;
this.vehicleMembers = new ArrayList<VehicleMember>(); this.vehicleMembers = new ArrayList<VehicleMember>();
} }
@Override @Override
public int getGadgetId() { return gadgetId; } public int getGadgetId() { return gadgetId; }
public Player getOwner() { public Player getOwner() {
return owner; return owner;
} }
public float getCurStamina() { return curStamina; } public float getCurStamina() { return curStamina; }
public void setCurStamina(float stamina) { this.curStamina = stamina; } public void setCurStamina(float stamina) { this.curStamina = stamina; }
public int getPointId() { return pointId; } public int getPointId() { return pointId; }
public List<VehicleMember> getVehicleMembers() { return vehicleMembers; } public List<VehicleMember> getVehicleMembers() { return vehicleMembers; }
@Override @Override
public Int2FloatOpenHashMap getFightProperties() { public Int2FloatOpenHashMap getFightProperties() {
return fightProp; return fightProp;
} }
@Override @Override
public Position getPosition() { return this.pos; } public Position getPosition() { return this.pos; }
@Override @Override
public Position getRotation() { public Position getRotation() {
return this.rot; return this.rot;
} }
@Override @Override
public SceneEntityInfo toProto() { public SceneEntityInfo toProto() {
VehicleInfo vehicle = VehicleInfo.newBuilder() VehicleInfo vehicle = VehicleInfo.newBuilder()
.setOwnerUid(this.owner.getUid()) .setOwnerUid(this.owner.getUid())
.setCurStamina(getCurStamina()) .setCurStamina(getCurStamina())
.build(); .build();
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder() EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder()) .setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(getPosition().toProto())) .setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto()) .setBornPos(getPosition().toProto())
.build(); .build();
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder() SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId()) .setGadgetId(this.getGadgetId())
.setAuthorityPeerId(this.getOwner().getPeerId()) .setAuthorityPeerId(this.getOwner().getPeerId())
.setIsEnableInteract(true) .setIsEnableInteract(true)
.setVehicleInfo(vehicle); .setVehicleInfo(vehicle);
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId()) .setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder())) .setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setGadget(gadgetInfo) .setGadget(gadgetInfo)
.setEntityAuthorityInfo(authority) .setEntityAuthorityInfo(authority)
.setLifeState(1); .setLifeState(1);
PropPair pair = PropPair.newBuilder() PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId()) .setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47)) .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47))
.build(); .build();
for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) { for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) {
if (entry.getIntKey() == 0) { if (entry.getIntKey() == 0) {
continue; continue;
} }
FightPropPair fightProp = FightPropPair.newBuilder().setPropType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build(); FightPropPair fightProp = FightPropPair.newBuilder().setPropType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build();
entityInfo.addFightPropList(fightProp); entityInfo.addFightPropList(fightProp);
} }
entityInfo.addPropList(pair); entityInfo.addPropList(pair);
return entityInfo.build(); return entityInfo.build();
} }
} }

View File

@ -3,12 +3,14 @@ package emu.grasscutter.game.entity;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.World; import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair; import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
@ -80,153 +82,153 @@ public abstract class GameEntity {
return this.metaModifiers; return this.metaModifiers;
} }
public abstract Int2FloatOpenHashMap getFightProperties(); public abstract Int2FloatOpenHashMap getFightProperties();
public abstract Position getPosition(); public abstract Position getPosition();
public abstract Position getRotation(); public abstract Position getRotation();
public MotionState getMotionState() { public MotionState getMotionState() {
return moveState; return moveState;
} }
public void setMotionState(MotionState moveState) { public void setMotionState(MotionState moveState) {
this.moveState = moveState; this.moveState = moveState;
} }
public int getLastMoveSceneTimeMs() { public int getLastMoveSceneTimeMs() {
return lastMoveSceneTimeMs; return lastMoveSceneTimeMs;
} }
public void setLastMoveSceneTimeMs(int lastMoveSceneTimeMs) { public void setLastMoveSceneTimeMs(int lastMoveSceneTimeMs) {
this.lastMoveSceneTimeMs = lastMoveSceneTimeMs; this.lastMoveSceneTimeMs = lastMoveSceneTimeMs;
} }
public int getLastMoveReliableSeq() { public int getLastMoveReliableSeq() {
return lastMoveReliableSeq; return lastMoveReliableSeq;
} }
public void setLastMoveReliableSeq(int lastMoveReliableSeq) { public void setLastMoveReliableSeq(int lastMoveReliableSeq) {
this.lastMoveReliableSeq = lastMoveReliableSeq; this.lastMoveReliableSeq = lastMoveReliableSeq;
} }
public void setFightProperty(FightProperty prop, float value) { public void setFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), value); this.getFightProperties().put(prop.getId(), value);
} }
private void setFightProperty(int id, float value) { private void setFightProperty(int id, float value) {
this.getFightProperties().put(id, value); this.getFightProperties().put(id, value);
} }
public void addFightProperty(FightProperty prop, float value) { public void addFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), getFightProperty(prop) + value); this.getFightProperties().put(prop.getId(), getFightProperty(prop) + value);
} }
public float getFightProperty(FightProperty prop) { public float getFightProperty(FightProperty prop) {
return getFightProperties().getOrDefault(prop.getId(), 0f); return getFightProperties().getOrDefault(prop.getId(), 0f);
} }
public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) { public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) {
for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) { for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) {
if (entry.getIntKey() == 0) { if (entry.getIntKey() == 0) {
continue; continue;
} }
FightPropPair fightProp = FightPropPair.newBuilder().setPropType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build(); FightPropPair fightProp = FightPropPair.newBuilder().setPropType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build();
entityInfo.addFightPropList(fightProp); entityInfo.addFightPropList(fightProp);
} }
} }
public int getBlockId() { public int getBlockId() {
return blockId; return blockId;
} }
public void setBlockId(int blockId) { public void setBlockId(int blockId) {
this.blockId = blockId; this.blockId = blockId;
} }
public int getConfigId() { public int getConfigId() {
return configId; return configId;
} }
public void setConfigId(int configId) { public void setConfigId(int configId) {
this.configId = configId; this.configId = configId;
} }
public int getGroupId() { public int getGroupId() {
return groupId; return groupId;
} }
public void setGroupId(int groupId) { public void setGroupId(int groupId) {
this.groupId = groupId; this.groupId = groupId;
} }
protected MotionInfo getMotionInfo() { protected MotionInfo getMotionInfo() {
MotionInfo proto = MotionInfo.newBuilder() MotionInfo proto = MotionInfo.newBuilder()
.setPos(getPosition().toProto()) .setPos(getPosition().toProto())
.setRot(getRotation().toProto()) .setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()) .setSpeed(Vector.newBuilder())
.setState(this.getMotionState()) .setState(this.getMotionState())
.build(); .build();
return proto; return proto;
} }
public SpawnDataEntry getSpawnEntry() { public SpawnDataEntry getSpawnEntry() {
return spawnEntry; return spawnEntry;
} }
public void setSpawnEntry(SpawnDataEntry spawnEntry) { public void setSpawnEntry(SpawnDataEntry spawnEntry) {
this.spawnEntry = spawnEntry; this.spawnEntry = spawnEntry;
} }
public float heal(float amount) { public float heal(float amount) {
if (this.getFightProperties() == null) { if (this.getFightProperties() == null) {
return 0f; return 0f;
} }
float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); float maxHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
if (curHp >= maxHp) { if (curHp >= maxHp) {
return 0f; return 0f;
} }
float healed = Math.min(maxHp - curHp, amount); float healed = Math.min(maxHp - curHp, amount);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed); this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed);
getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
return healed; return healed;
} }
public void damage(float amount) { public void damage(float amount) {
damage(amount, 0); damage(amount, 0);
} }
public void damage(float amount, int killerId) { public void damage(float amount, int killerId) {
// Sanity check // Sanity check
if (getFightProperties() == null) { if (getFightProperties() == null) {
return; return;
} }
// Lose hp // Lose hp
addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -amount); addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -amount);
// Check if dead // Check if dead
boolean isDead = false; boolean isDead = false;
if (getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) { if (getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
isDead = true; isDead = true;
} }
// Packets // Packets
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
// Check if dead // Check if dead
if (isDead) { if (isDead) {
getScene().killEntity(this, killerId); getScene().killEntity(this, killerId);
} }
} }
/** /**
* Move this entity to a new position. * Move this entity to a new position.
@ -239,20 +241,29 @@ public abstract class GameEntity {
this.getRotation().set(rotation); this.getRotation().set(rotation);
} }
/**
* Called when a player interacts with this entity
* @param player Player that is interacting with this entity
* @param interactReq Interact request protobuf data
*/
public void onInteract(Player player, GadgetInteractReq interactReq) {
}
/** /**
* Called when this entity is added to the world * Called when this entity is added to the world
*/ */
public void onCreate() { public void onCreate() {
} }
/** /**
* Called when this entity dies * Called when this entity dies
* @param killerId Entity id of the entity that killed this entity * @param killerId Entity id of the entity that killed this entity
*/ */
public void onDeath(int killerId) { public void onDeath(int killerId) {
} }
public abstract SceneEntityInfo toProto(); public abstract SceneEntityInfo toProto();
} }

View File

@ -3,6 +3,7 @@ package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo; import emu.grasscutter.net.proto.BossChestInfoOuterClass.BossChestInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType; import emu.grasscutter.net.proto.InterOpTypeOuterClass.InterOpType;
@ -11,54 +12,54 @@ import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo; import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.scripts.constants.ScriptGadgetState; import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp; import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
public class GadgetChest extends GadgetContent { public class GadgetChest extends GadgetContent {
public GadgetChest(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) { public GadgetChest(EntityGadget gadget) {
var chestInteractHandlerMap = getGadget().getScene().getWorld().getServer().getWorldDataManager().getChestInteractHandlerMap(); super(gadget);
var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName()); }
if(handler == null){
Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", getGadget().getGadgetData().getJsonName());
return false;
}
if(req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()){ public boolean onInteract(Player player, GadgetInteractReq req) {
player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START)); var chestInteractHandlerMap = getGadget().getScene().getWorld().getServer().getWorldDataSystem().getChestInteractHandlerMap();
return false; var handler = chestInteractHandlerMap.get(getGadget().getGadgetData().getJsonName());
}else{ if (handler == null) {
var success = handler.onInteract(this, player); Grasscutter.getLogger().warn("Could not found the handler of this type of Chests {}", getGadget().getGadgetData().getJsonName());
if (!success){ return false;
return false; }
}
getGadget().updateState(ScriptGadgetState.ChestOpened); if (req.getOpType() == InterOpType.INTER_OP_TYPE_START && handler.isTwoStep()) {
player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST)); player.sendPacket(new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_CHEST, InterOpType.INTER_OP_TYPE_START));
// let the chest disappear return false;
getGadget().die(); }else {
return true; var success = handler.onInteract(this, player);
} if (!success) {
} return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) { getGadget().updateState(ScriptGadgetState.ChestOpened);
if(getGadget().getMetaGadget() == null){ player.sendPacket(new PacketGadgetInteractRsp(this.getGadget(), InteractTypeOuterClass.InteractType.INTERACT_TYPE_OPEN_CHEST));
return;
}
var bossChest = getGadget().getMetaGadget().boss_chest; return true;
if(bossChest != null){ }
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList(); }
gadgetInfo.setBossChest(BossChestInfo.newBuilder() public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
.setMonsterConfigId(bossChest.monster_config_id) if (getGadget().getMetaGadget() == null) {
.setResin(bossChest.resin) return;
.addAllQualifyUidList(players) }
.addAllRemainUidList(players)
.build());
}
} var bossChest = getGadget().getMetaGadget().boss_chest;
if (bossChest != null) {
var players = getGadget().getScene().getPlayers().stream().map(Player::getUid).toList();
gadgetInfo.setBossChest(BossChestInfo.newBuilder()
.setMonsterConfigId(bossChest.monster_config_id)
.setResin(bossChest.resin)
.addAllQualifyUidList(players)
.addAllRemainUidList(players)
.build());
}
}
} }

View File

@ -19,11 +19,11 @@ public class BossChestInteractHandler implements ChestInteractHandler{
@Override @Override
public boolean onInteract(GadgetChest chest, Player player) { public boolean onInteract(GadgetChest chest, Player player) {
var worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataManager(); var worldDataManager = chest.getGadget().getScene().getWorld().getServer().getWorldDataSystem();
var monster = chest.getGadget().getMetaGadget().group.monsters.get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id); var monster = chest.getGadget().getMetaGadget().group.monsters.get(chest.getGadget().getMetaGadget().boss_chest.monster_config_id);
var reward = worldDataManager.getRewardByBossId(monster.monster_id); var reward = worldDataManager.getRewardByBossId(monster.monster_id);
if(reward == null){ if (reward == null) {
Grasscutter.getLogger().warn("Could not found the reward of boss monster {}", monster.monster_id); Grasscutter.getLogger().warn("Could not found the reward of boss monster {}", monster.monster_id);
return false; return false;
} }

View File

@ -3,40 +3,37 @@ package emu.grasscutter.game.expedition;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import static emu.grasscutter.config.Configuration.*;
import java.io.FileReader; import java.io.FileReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import static emu.grasscutter.Configuration.*; public class ExpeditionSystem extends BaseGameSystem {
public class ExpeditionManager {
public GameServer getGameServer() {
return gameServer;
}
private final GameServer gameServer;
public Int2ObjectMap<List<ExpeditionRewardDataList>> getExpeditionRewardDataList() { return expeditionRewardData; }
private final Int2ObjectMap<List<ExpeditionRewardDataList>> expeditionRewardData; private final Int2ObjectMap<List<ExpeditionRewardDataList>> expeditionRewardData;
public ExpeditionManager(GameServer gameServer) { public ExpeditionSystem(GameServer server) {
this.gameServer = gameServer; super(server);
this.expeditionRewardData = new Int2ObjectOpenHashMap<>(); this.expeditionRewardData = new Int2ObjectOpenHashMap<>();
this.load(); this.load();
} }
public Int2ObjectMap<List<ExpeditionRewardDataList>> getExpeditionRewardDataList() {
return expeditionRewardData;
}
public synchronized void load() { public synchronized void load() {
try (Reader fileReader = new InputStreamReader(DataLoader.load("ExpeditionReward.json"))) { try (Reader fileReader = DataLoader.loadReader("ExpeditionReward.json")) {
getExpeditionRewardDataList().clear(); getExpeditionRewardDataList().clear();
List<ExpeditionRewardInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ExpeditionRewardInfo.class).getType()); List<ExpeditionRewardInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ExpeditionRewardInfo.class).getType());
if(banners.size() > 0) { if (banners.size() > 0) {
for (ExpeditionRewardInfo di : banners) { for (ExpeditionRewardInfo di : banners) {
getExpeditionRewardDataList().put(di.getExpId(), di.getExpeditionRewardDataList()); getExpeditionRewardDataList().put(di.getExpId(), di.getExpeditionRewardDataList());
} }

View File

@ -3,6 +3,7 @@ package emu.grasscutter.game.friends;
import java.util.List; import java.util.List;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType; import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType;
import emu.grasscutter.server.packet.send.PacketAskAddFriendNotify; import emu.grasscutter.server.packet.send.PacketAskAddFriendNotify;
@ -13,250 +14,244 @@ import emu.grasscutter.server.packet.send.PacketDeleteFriendRsp;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class FriendsList { public class FriendsList extends BasePlayerManager {
private final Player player; private final Int2ObjectMap<Friendship> friends;
private final Int2ObjectMap<Friendship> pendingFriends;
private final Int2ObjectMap<Friendship> friends;
private final Int2ObjectMap<Friendship> pendingFriends;
private boolean loaded = false;
public FriendsList(Player player) {
this.player = player;
this.friends = new Int2ObjectOpenHashMap<Friendship>();
this.pendingFriends = new Int2ObjectOpenHashMap<Friendship>();
}
public Player getPlayer() {
return player;
}
public boolean hasLoaded() {
return loaded;
}
public synchronized Int2ObjectMap<Friendship> getFriends() {
return friends;
}
public synchronized Int2ObjectMap<Friendship> getPendingFriends() {
return this.pendingFriends;
}
public synchronized boolean isFriendsWith(int uid) {
return this.getFriends().containsKey(uid);
}
private synchronized Friendship getFriendshipById(int id) {
Friendship friendship = this.getFriends().get(id);
if (friendship == null) {
friendship = this.getPendingFriendById(id);
}
return friendship;
}
private synchronized Friendship getFriendById(int id) {
return this.getFriends().get(id);
}
private synchronized Friendship getPendingFriendById(int id) {
return this.getPendingFriends().get(id);
}
public void addFriend(Friendship friendship) {
getFriends().put(friendship.getFriendId(), friendship);
}
public void addPendingFriend(Friendship friendship) {
getPendingFriends().put(friendship.getFriendId(), friendship);
}
public synchronized void handleFriendRequest(int targetUid, DealAddFriendResultType result) {
// Check if player has sent friend request
Friendship myFriendship = this.getPendingFriendById(targetUid);
if (myFriendship == null) {
return;
}
// Make sure asker cant do anything
if (myFriendship.getAskerId() == this.getPlayer().getUid()) {
return;
}
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true); private boolean loaded = false;
if (target == null) {
return; // Should never happen
}
// Get target's friendship public FriendsList(Player player) {
Friendship theirFriendship = null; super(player);
if (target.isOnline()) { this.friends = new Int2ObjectOpenHashMap<Friendship>();
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid()); this.pendingFriends = new Int2ObjectOpenHashMap<Friendship>();
} else { }
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
}
if (theirFriendship == null) { public boolean hasLoaded() {
// They dont have us on their friends list anymore, rip return loaded;
this.getPendingFriends().remove(myFriendship.getOwnerId()); }
myFriendship.delete();
return;
}
// Handle public synchronized Int2ObjectMap<Friendship> getFriends() {
if (result == DealAddFriendResultType.DEAL_ADD_FRIEND_RESULT_TYPE_ACCEPT) { // Request accepted return friends;
myFriendship.setIsFriend(true); }
theirFriendship.setIsFriend(true);
this.getPendingFriends().remove(myFriendship.getOwnerId());
this.addFriend(myFriendship);
if (target.isOnline()) {
target.getFriendsList().getPendingFriends().remove(this.getPlayer().getUid());
target.getFriendsList().addFriend(theirFriendship);
}
myFriendship.save();
theirFriendship.save();
} else { // Request declined
// Delete from my pending friends
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
// Delete from target uid
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
}
theirFriendship.delete();
}
// Packet
this.getPlayer().sendPacket(new PacketDealAddFriendRsp(targetUid, result));
}
public synchronized void deleteFriend(int targetUid) {
Friendship myFriendship = this.getFriendById(targetUid);
if (myFriendship == null) {
return;
}
this.getFriends().remove(targetUid);
myFriendship.delete();
Friendship theirFriendship = null;
Player friend = myFriendship.getFriendProfile().getPlayer();
if (friend != null) {
// Friend online
theirFriendship = friend.getFriendsList().getFriendById(this.getPlayer().getUid());
if (theirFriendship != null) {
friend.getFriendsList().getFriends().remove(theirFriendship.getFriendId());
theirFriendship.delete();
friend.sendPacket(new PacketDeleteFriendNotify(theirFriendship.getFriendId()));
}
} else {
// Friend offline
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
if (theirFriendship != null) {
theirFriendship.delete();
}
}
// Packet
this.getPlayer().sendPacket(new PacketDeleteFriendRsp(targetUid));
}
public synchronized void sendFriendRequest(int targetUid) {
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null || target == this.getPlayer()) { public synchronized Int2ObjectMap<Friendship> getPendingFriends() {
return; return this.pendingFriends;
} }
// Check if friend already exists
if (this.getPendingFriends().containsKey(targetUid) || this.getFriends().containsKey(targetUid)) {
return;
}
// Create friendships
Friendship myFriendship = new Friendship(getPlayer(), target, getPlayer());
Friendship theirFriendship = new Friendship(target, getPlayer(), getPlayer());
// Add pending lists
this.addPendingFriend(myFriendship);
if (target.isOnline() && target.getFriendsList().hasLoaded()) { public synchronized boolean isFriendsWith(int uid) {
target.getFriendsList().addPendingFriend(theirFriendship); return this.getFriends().containsKey(uid);
target.sendPacket(new PacketAskAddFriendNotify(theirFriendship)); }
}
// Save
myFriendship.save();
theirFriendship.save();
// Packets
this.getPlayer().sendPacket(new PacketAskAddFriendRsp(targetUid));
}
/** Gets total amount of potential friends
* */
public int getFullFriendCount() {
return this.getPendingFriends().size() + this.getFriends().size();
}
public synchronized void loadFromDatabase() { private synchronized Friendship getFriendshipById(int id) {
if (this.hasLoaded()) { Friendship friendship = this.getFriends().get(id);
return; if (friendship == null) {
} friendship = this.getPendingFriendById(id);
}
// Get friendships from the db return friendship;
List<Friendship> friendships = DatabaseHelper.getFriends(player); }
friendships.forEach(this::loadFriendFromDatabase);
// Set loaded flag
this.loaded = true;
}
private void loadFriendFromDatabase(Friendship friendship) {
// Set friendship owner
friendship.setOwner(getPlayer());
// Check if friend is online private synchronized Friendship getFriendById(int id) {
Player friend = getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getUid()); return this.getFriends().get(id);
if (friend != null) { }
// Set friend to online mode
friendship.setFriendProfile(friend); private synchronized Friendship getPendingFriendById(int id) {
return this.getPendingFriends().get(id);
// Update our status on friend's client if theyre online }
if (friend.getFriendsList().hasLoaded()) {
Friendship theirFriendship = friend.getFriendsList().getFriendshipById(getPlayer().getUid()); public void addFriend(Friendship friendship) {
if (theirFriendship != null) { getFriends().put(friendship.getFriendId(), friendship);
// Update friend profile }
theirFriendship.setFriendProfile(getPlayer());
} else { public void addPendingFriend(Friendship friendship) {
// They dont have us on their friends list anymore, rip getPendingFriends().put(friendship.getFriendId(), friendship);
friendship.delete(); }
return;
} public synchronized void handleFriendRequest(int targetUid, DealAddFriendResultType result) {
} // Check if player has sent friend request
} Friendship myFriendship = this.getPendingFriendById(targetUid);
if (myFriendship == null) {
// Finally, load to our friends list return;
if (friendship.isFriend()) { }
getFriends().put(friendship.getFriendId(), friendship);
} else { // Make sure asker cant do anything
getPendingFriends().put(friendship.getFriendId(), friendship); if (myFriendship.getAskerId() == this.getPlayer().getUid()) {
// TODO - Hacky fix to force client to see a notification for a friendship return;
if (getPendingFriends().size() == 1) { }
getPlayer().getSession().send(new PacketAskAddFriendNotify(friendship));
} Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
} if (target == null) {
} return; // Should never happen
}
public void save() {
// Update all our friends // Get target's friendship
List<Friendship> friendships = DatabaseHelper.getReverseFriends(getPlayer()); Friendship theirFriendship = null;
for (Friendship friend : friendships) { if (target.isOnline()) {
friend.setFriendProfile(this.getPlayer()); theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
friend.save(); } else {
} theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
} }
if (theirFriendship == null) {
// They dont have us on their friends list anymore, rip
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
return;
}
// Handle
if (result == DealAddFriendResultType.DEAL_ADD_FRIEND_RESULT_TYPE_ACCEPT) { // Request accepted
myFriendship.setIsFriend(true);
theirFriendship.setIsFriend(true);
this.getPendingFriends().remove(myFriendship.getOwnerId());
this.addFriend(myFriendship);
if (target.isOnline()) {
target.getFriendsList().getPendingFriends().remove(this.getPlayer().getUid());
target.getFriendsList().addFriend(theirFriendship);
}
myFriendship.save();
theirFriendship.save();
} else { // Request declined
// Delete from my pending friends
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
// Delete from target uid
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
}
theirFriendship.delete();
}
// Packet
this.getPlayer().sendPacket(new PacketDealAddFriendRsp(targetUid, result));
}
public synchronized void deleteFriend(int targetUid) {
Friendship myFriendship = this.getFriendById(targetUid);
if (myFriendship == null) {
return;
}
this.getFriends().remove(targetUid);
myFriendship.delete();
Friendship theirFriendship = null;
Player friend = myFriendship.getFriendProfile().getPlayer();
if (friend != null) {
// Friend online
theirFriendship = friend.getFriendsList().getFriendById(this.getPlayer().getUid());
if (theirFriendship != null) {
friend.getFriendsList().getFriends().remove(theirFriendship.getFriendId());
theirFriendship.delete();
friend.sendPacket(new PacketDeleteFriendNotify(theirFriendship.getFriendId()));
}
} else {
// Friend offline
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
if (theirFriendship != null) {
theirFriendship.delete();
}
}
// Packet
this.getPlayer().sendPacket(new PacketDeleteFriendRsp(targetUid));
}
public synchronized void sendFriendRequest(int targetUid) {
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null || target == this.getPlayer()) {
return;
}
// Check if friend already exists
if (this.getPendingFriends().containsKey(targetUid) || this.getFriends().containsKey(targetUid)) {
return;
}
// Create friendships
Friendship myFriendship = new Friendship(getPlayer(), target, getPlayer());
Friendship theirFriendship = new Friendship(target, getPlayer(), getPlayer());
// Add pending lists
this.addPendingFriend(myFriendship);
if (target.isOnline() && target.getFriendsList().hasLoaded()) {
target.getFriendsList().addPendingFriend(theirFriendship);
target.sendPacket(new PacketAskAddFriendNotify(theirFriendship));
}
// Save
myFriendship.save();
theirFriendship.save();
// Packets
this.getPlayer().sendPacket(new PacketAskAddFriendRsp(targetUid));
}
/** Gets total amount of potential friends
* */
public int getFullFriendCount() {
return this.getPendingFriends().size() + this.getFriends().size();
}
public synchronized void loadFromDatabase() {
if (this.hasLoaded()) {
return;
}
// Get friendships from the db
List<Friendship> friendships = DatabaseHelper.getFriends(player);
friendships.forEach(this::loadFriendFromDatabase);
// Set loaded flag
this.loaded = true;
}
private void loadFriendFromDatabase(Friendship friendship) {
// Set friendship owner
friendship.setOwner(getPlayer());
// Check if friend is online
Player friend = getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getUid());
if (friend != null) {
// Set friend to online mode
friendship.setFriendProfile(friend);
// Update our status on friend's client if theyre online
if (friend.getFriendsList().hasLoaded()) {
Friendship theirFriendship = friend.getFriendsList().getFriendshipById(getPlayer().getUid());
if (theirFriendship != null) {
// Update friend profile
theirFriendship.setFriendProfile(getPlayer());
} else {
// They dont have us on their friends list anymore, rip
friendship.delete();
return;
}
}
}
// Finally, load to our friends list
if (friendship.isFriend()) {
getFriends().put(friendship.getFriendId(), friendship);
} else {
getPendingFriends().put(friendship.getFriendId(), friendship);
// TODO - Hacky fix to force client to see a notification for a friendship
if (getPendingFriends().size() == 1) {
getPlayer().getSession().send(new PacketAskAddFriendNotify(friendship));
}
}
}
public void save() {
// Update all our friends
List<Friendship> friendships = DatabaseHelper.getReverseFriends(getPlayer());
for (Friendship friend : friendships) {
friend.setFriendProfile(this.getPlayer());
friend.save();
}
}
} }

View File

@ -1,5 +1,7 @@
package emu.grasscutter.game.gacha; package emu.grasscutter.game.gacha;
import static emu.grasscutter.config.Configuration.*;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo; import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
@ -7,167 +9,165 @@ import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import lombok.Getter; import lombok.Getter;
import static emu.grasscutter.Configuration.*;
public class GachaBanner { public class GachaBanner {
@Getter private int gachaType; @Getter private int gachaType;
@Getter private int scheduleId; @Getter private int scheduleId;
@Getter private String prefabPath; @Getter private String prefabPath;
@Getter private String previewPrefabPath; @Getter private String previewPrefabPath;
@Getter private String titlePath; @Getter private String titlePath;
private int costItemId = 0; private int costItemId = 0;
private int costItemAmount = 1; private int costItemAmount = 1;
private int costItemId10 = 0; private int costItemId10 = 0;
private int costItemAmount10 = 10; private int costItemAmount10 = 10;
@Getter private int beginTime; @Getter private int beginTime;
@Getter private int endTime; @Getter private int endTime;
@Getter private int sortId; @Getter private int sortId;
@Getter private int gachaTimesLimit = Integer.MAX_VALUE; @Getter private int gachaTimesLimit = Integer.MAX_VALUE;
private int[] rateUpItems4 = {}; private int[] rateUpItems4 = {};
private int[] rateUpItems5 = {}; private int[] rateUpItems5 = {};
@Getter private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; @Getter private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
@Getter private int[] fallbackItems4Pool1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; @Getter private int[] fallbackItems4Pool1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064};
@Getter private int[] fallbackItems4Pool2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; @Getter private int[] fallbackItems4Pool2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
@Getter private int[] fallbackItems5Pool1 = {1003, 1016, 1042, 1035, 1041}; @Getter private int[] fallbackItems5Pool1 = {1003, 1016, 1042, 1035, 1041};
@Getter private int[] fallbackItems5Pool2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; @Getter private int[] fallbackItems5Pool2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
@Getter private boolean removeC6FromPool = false; @Getter private boolean removeC6FromPool = false;
@Getter private boolean autoStripRateUpFromFallback = true; @Getter private boolean autoStripRateUpFromFallback = true;
private int[][] weights4 = {{1,510}, {8,510}, {10,10000}}; private int[][] weights4 = {{1,510}, {8,510}, {10,10000}};
private int[][] weights5 = {{1,75}, {73,150}, {90,10000}}; private int[][] weights5 = {{1,75}, {73,150}, {90,10000}};
private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}}; private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}};
private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}}; private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}};
private int eventChance4 = 50; // Chance to win a featured event item private int eventChance4 = 50; // Chance to win a featured event item
private int eventChance5 = 50; // Chance to win a featured event item private int eventChance5 = 50; // Chance to win a featured event item
@Getter private BannerType bannerType = BannerType.STANDARD; @Getter private BannerType bannerType = BannerType.STANDARD;
// Kinda wanna deprecate these but they're in people's configs // Kinda wanna deprecate these but they're in people's configs
private int[] rateUpItems1 = {}; private int[] rateUpItems1 = {};
private int[] rateUpItems2 = {}; private int[] rateUpItems2 = {};
private int eventChance = -1; private int eventChance = -1;
private int costItem = 0; private int costItem = 0;
@Getter private int wishMaxProgress = 2; @Getter private int wishMaxProgress = 2;
public ItemParamData getCost(int numRolls) { public ItemParamData getCost(int numRolls) {
return switch (numRolls) { return switch (numRolls) {
case 10 -> new ItemParamData((costItemId10 > 0) ? costItemId10 : getCostItem(), costItemAmount10); case 10 -> new ItemParamData((costItemId10 > 0) ? costItemId10 : getCostItem(), costItemAmount10);
default -> new ItemParamData(getCostItem(), costItemAmount * numRolls); default -> new ItemParamData(getCostItem(), costItemAmount * numRolls);
}; };
} }
public int getCostItem() { public int getCostItem() {
return (costItem > 0) ? costItem : costItemId; return (costItem > 0) ? costItem : costItemId;
} }
public int[] getRateUpItems4() { public int[] getRateUpItems4() {
return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4; return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4;
} }
public int[] getRateUpItems5() { public int[] getRateUpItems5() {
return (rateUpItems1.length > 0) ? rateUpItems1 : rateUpItems5; return (rateUpItems1.length > 0) ? rateUpItems1 : rateUpItems5;
} }
public boolean hasEpitomized() { public boolean hasEpitomized() {
return bannerType.equals(BannerType.WEAPON); return bannerType.equals(BannerType.WEAPON);
} }
public int getWeight(int rarity, int pity) { public int getWeight(int rarity, int pity) {
return switch(rarity) { return switch (rarity) {
case 4 -> Utils.lerp(pity, weights4); case 4 -> Utils.lerp(pity, weights4);
default -> Utils.lerp(pity, weights5); default -> Utils.lerp(pity, weights5);
}; };
} }
public int getPoolBalanceWeight(int rarity, int pity) { public int getPoolBalanceWeight(int rarity, int pity) {
return switch(rarity) { return switch (rarity) {
case 4 -> Utils.lerp(pity, poolBalanceWeights4); case 4 -> Utils.lerp(pity, poolBalanceWeights4);
default -> Utils.lerp(pity, poolBalanceWeights5); default -> Utils.lerp(pity, poolBalanceWeights5);
}; };
} }
public int getEventChance(int rarity) { public int getEventChance(int rarity) {
return switch(rarity) { return switch (rarity) {
case 4 -> eventChance4; case 4 -> eventChance4;
default -> (eventChance > -1) ? eventChance : eventChance5; default -> (eventChance > -1) ? eventChance : eventChance5;
}; };
} }
public GachaInfo toProto(Player player) {
// TODO: use other Nonce/key insteadof session key to ensure the overall security for the player
String sessionKey = player.getAccount().getSessionKey();
String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/gacha?s=" + sessionKey + "&gachaType=" + gachaType;
String details = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/gacha/details?s=" + sessionKey + "&scheduleId=" + scheduleId;
// Grasscutter.getLogger().info("record = " + record); public GachaInfo toProto(Player player) {
ItemParamData costItem1 = this.getCost(1); // TODO: use other Nonce/key insteadof session key to ensure the overall security for the player
ItemParamData costItem10 = this.getCost(10); String sessionKey = player.getAccount().getSessionKey();
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(this);
int leftGachaTimes = switch(gachaTimesLimit) {
case Integer.MAX_VALUE -> Integer.MAX_VALUE;
default -> Math.max(gachaTimesLimit - gachaInfo.getTotalPulls(), 0);
};
GachaInfo.Builder info = GachaInfo.newBuilder()
.setGachaType(this.getGachaType())
.setScheduleId(this.getScheduleId())
.setBeginTime(this.getBeginTime())
.setEndTime(this.getEndTime())
.setCostItemId(costItem1.getId())
.setCostItemNum(costItem1.getCount())
.setTenCostItemId(costItem10.getId())
.setTenCostItemNum(costItem10.getCount())
.setGachaPrefabPath(this.getPrefabPath())
.setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
.setGachaProbUrl(details)
.setGachaProbUrlOversea(details)
.setGachaRecordUrl(record)
.setGachaRecordUrlOversea(record)
.setLeftGachaTimes(leftGachaTimes)
.setGachaTimesLimit(gachaTimesLimit)
.setGachaSortId(this.getSortId());
if(hasEpitomized()) { String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
info.setWishItemId(gachaInfo.getWishItemId()) + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
.setWishProgress(gachaInfo.getFailedChosenItemPulls()) + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
.setWishMaxProgress(this.getWishMaxProgress()); + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType;
} String details = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://"
+ lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":"
+ lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort)
+ "/gacha/details?s=" + sessionKey + "&scheduleId=" + scheduleId;
if (this.getTitlePath() != null) { // Grasscutter.getLogger().info("record = " + record);
info.setTitleTextmap(this.getTitlePath()); ItemParamData costItem1 = this.getCost(1);
} ItemParamData costItem10 = this.getCost(10);
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(this);
if (this.getRateUpItems5().length > 0) { int leftGachaTimes = switch (gachaTimesLimit) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1); case Integer.MAX_VALUE -> Integer.MAX_VALUE;
default -> Math.max(gachaTimesLimit - gachaInfo.getTotalPulls(), 0);
for (int id : getRateUpItems5()) { };
upInfo.addItemIdList(id); GachaInfo.Builder info = GachaInfo.newBuilder()
info.addDisplayUp5ItemList(id); .setGachaType(this.getGachaType())
} .setScheduleId(this.getScheduleId())
.setBeginTime(this.getBeginTime())
info.addGachaUpInfoList(upInfo); .setEndTime(this.getEndTime())
} .setCostItemId(costItem1.getId())
.setCostItemNum(costItem1.getCount())
if (this.getRateUpItems4().length > 0) { .setTenCostItemId(costItem10.getId())
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2); .setTenCostItemNum(costItem10.getCount())
.setGachaPrefabPath(this.getPrefabPath())
for (int id : getRateUpItems4()) { .setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
upInfo.addItemIdList(id); .setGachaProbUrl(details)
if (info.getDisplayUp4ItemListCount() == 0) { .setGachaProbUrlOversea(details)
info.addDisplayUp4ItemList(id); .setGachaRecordUrl(record)
} .setGachaRecordUrlOversea(record)
} .setLeftGachaTimes(leftGachaTimes)
.setGachaTimesLimit(gachaTimesLimit)
info.addGachaUpInfoList(upInfo); .setGachaSortId(this.getSortId());
}
if (hasEpitomized()) {
return info.build(); info.setWishItemId(gachaInfo.getWishItemId())
} .setWishProgress(gachaInfo.getFailedChosenItemPulls())
.setWishMaxProgress(this.getWishMaxProgress());
public enum BannerType { }
STANDARD, EVENT, WEAPON;
} if (this.getTitlePath() != null) {
info.setTitleTextmap(this.getTitlePath());
}
if (this.getRateUpItems5().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1);
for (int id : getRateUpItems5()) {
upInfo.addItemIdList(id);
info.addDisplayUp5ItemList(id);
}
info.addGachaUpInfoList(upInfo);
}
if (this.getRateUpItems4().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2);
for (int id : getRateUpItems4()) {
upInfo.addItemIdList(id);
if (info.getDisplayUp4ItemListCount() == 0) {
info.addDisplayUp4ItemList(id);
}
}
info.addGachaUpInfoList(upInfo);
}
return info.build();
}
public enum BannerType {
STANDARD, EVENT, WEAPON;
}
} }

View File

@ -1,451 +0,0 @@
package emu.grasscutter.game.gacha;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import com.google.gson.reflect.TypeToken;
import com.sun.nio.file.SensitivityWatchEventModifier;
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.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.server.packet.send.PacketGachaWishRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.greenrobot.eventbus.Subscribe;
import static emu.grasscutter.Configuration.*;
public class GachaManager {
private final GameServer server;
private final Int2ObjectMap<GachaBanner> gachaBanners;
private WatchService watchService;
private static final int starglitterId = 221;
private static final int stardustId = 222;
private int[] fallbackItems4Pool2Default = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
private int[] fallbackItems5Pool2Default = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
public GachaManager(GameServer server) {
this.server = server;
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
this.startWatcher(server);
}
public GameServer getServer() {
return server;
}
public Int2ObjectMap<GachaBanner> getGachaBanners() {
return gachaBanners;
}
public int randomRange(int min, int max) { // Both are inclusive
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
}
public int getRandom(int[] array) {
return array[randomRange(0, array.length - 1)];
}
public synchronized void load() {
try (Reader fileReader = new InputStreamReader(DataLoader.load("Banners.json"))) {
getGachaBanners().clear();
List<GachaBanner> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType());
if(banners.size() > 0) {
for (GachaBanner banner : banners) {
getGachaBanners().put(banner.getScheduleId(), banner);
}
Grasscutter.getLogger().debug("Banners successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private class BannerPools {
public int[] rateUpItems4;
public int[] rateUpItems5;
public int[] fallbackItems4Pool1;
public int[] fallbackItems4Pool2;
public int[] fallbackItems5Pool1;
public int[] fallbackItems5Pool2;
public BannerPools(GachaBanner banner) {
rateUpItems4 = banner.getRateUpItems4();
rateUpItems5 = banner.getRateUpItems5();
fallbackItems4Pool1 = banner.getFallbackItems4Pool1();
fallbackItems4Pool2 = banner.getFallbackItems4Pool2();
fallbackItems5Pool1 = banner.getFallbackItems5Pool1();
fallbackItems5Pool2 = banner.getFallbackItems5Pool2();
if (banner.isAutoStripRateUpFromFallback()) {
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5);
}
}
public void removeFromAllPools(int[] itemIds) {
rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds);
rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds);
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds);
}
}
private synchronized int checkPlayerAvatarConstellationLevel(Player player, int itemId) { // Maybe this would be useful in the Player class?
ItemData itemData = GameData.getItemDataMap().get(itemId);
if ((itemData == null) || (itemData.getMaterialType() != MaterialType.MATERIAL_AVATAR)){
return -2; // Not an Avatar
}
Avatar avatar = player.getAvatars().getAvatarById((itemId % 1000) + 10000000);
if (avatar == null) {
return -1; // Doesn't have
}
// Constellation
int constLevel = avatar.getCoreProudSkillLevel();
GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId + 100);
constLevel += (constItem == null)? 0 : constItem.getCount();
return constLevel;
}
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
IntList temp = new IntArrayList();
for (int itemId : itemPool) {
if (checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
temp.add(itemId);
}
}
return temp.toIntArray();
}
private synchronized int drawRoulette(int[] weights, int cutoff) {
// This follows the logic laid out in issue #183
// Simple weighted selection with an upper bound for the roll that cuts off trailing entries
// All weights must be >= 0
int total = 0;
for (int weight : weights) {
if (weight < 0) {
throw new IllegalArgumentException("Weights must be non-negative!");
}
total += weight;
}
int roll = ThreadLocalRandom.current().nextInt((total < cutoff)? total : cutoff);
int subTotal = 0;
for (int i=0; i<weights.length; i++) {
subTotal += weights[i];
if (roll < subTotal) {
return i;
}
}
// throw new IllegalStateException();
return 0; // This should only be reachable if total==0
}
private synchronized int doFallbackRarePull(int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
if (fallback1.length < 1) {
if (fallback2.length < 1) {
return getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default);
} else {
return getRandom(fallback2);
}
} else if (fallback2.length < 1) {
return getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1));
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2));
int chosenPool = switch ((pityPool1 >= pityPool2)? 1 : 0) { // Larger weight must come first for the hard cutoff to function correctly
case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000);
default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000);
};
return switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
}
}
private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
int itemId = 0;
boolean epitomized = (banner.hasEpitomized()) && (rarity == 5) && (gachaInfo.getWishItemId() != 0);
boolean pityEpitomized = (gachaInfo.getFailedChosenItemPulls() >= banner.getWishMaxProgress()); // Maximum fate points reached
boolean pityFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1); // Lost previous coinflip
boolean rollFeatured = (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
boolean pullFeatured = pityFeatured || rollFeatured;
if (epitomized && pityEpitomized) { // Auto pick item when epitomized points reached
gachaInfo.setFailedFeaturedItemPulls(rarity, 0); // Epitomized item will always be a featured one
itemId = gachaInfo.getWishItemId();
} else {
if (pullFeatured && (featured.length > 0)) {
gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
itemId = getRandom(featured);
} else {
gachaInfo.addFailedFeaturedItemPulls(rarity, 1); // This could be moved into doFallbackRarePull but having it here makes it clearer
itemId = doFallbackRarePull(fallback1, fallback2, rarity, banner, gachaInfo);
}
}
if (epitomized) {
if(itemId == gachaInfo.getWishItemId()) { // Reset epitomized points when got wished item
gachaInfo.setFailedChosenItemPulls(0);
} else { // Add epitomized points if not get wished item
gachaInfo.addFailedChosenItemPulls(1);
}
}
return itemId;
}
private synchronized int doPull(GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) {
// Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity)
gachaInfo.incPityAll();
int[] weights = {banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000};
int levelWon = 5 - drawRoulette(weights, 10000);
return switch (levelWon) {
case 5:
gachaInfo.setPity5(0);
yield doRarePull(pools.rateUpItems5, pools.fallbackItems5Pool1, pools.fallbackItems5Pool2, 5, banner, gachaInfo);
case 4:
gachaInfo.setPity4(0);
yield doRarePull(pools.rateUpItems4, pools.fallbackItems4Pool1, pools.fallbackItems4Pool2, 4, banner, gachaInfo);
default:
yield getRandom(banner.getFallbackItems3());
};
}
public synchronized void doPulls(Player player, int scheduleId, int times) {
// Sanity check
if (times != 10 && times != 1) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_INVALID_TIMES));
return;
}
Inventory inventory = player.getInventory();
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_ITEM_EXCEED_LIMIT));
return;
}
// Get banner
GachaBanner banner = this.getGachaBanners().get(scheduleId);
if (banner == null) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Check against total limit
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
int gachaTimesLimit = banner.getGachaTimesLimit();
if (gachaTimesLimit != Integer.MAX_VALUE && (gachaInfo.getTotalPulls() + times) > gachaTimesLimit) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_TIMES_LIMIT));
return;
}
// Spend currency
ItemParamData cost = banner.getCost(times);
if (cost.getCount() > 0 && !inventory.payItem(cost)) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_COST_ITEM_NOT_ENOUGH));
return;
}
// Add to character
gachaInfo.addTotalPulls(times);
BannerPools pools = new BannerPools(banner);
List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0;
if (banner.isRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla)
pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player);
pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player);
pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player);
pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player);
pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player);
pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player);
}
for (int i = 0; i < times; i++) {
// Roll
int itemId = doPull(banner, gachaInfo, pools);
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) {
continue; // Maybe we should bail out if an item fails instead of rolling the rest?
}
// Write gacha record
GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), banner.getGachaType());
DatabaseHelper.saveGachaRecord(gachaRecord);
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
int addStardust = 0, addStarglitter = 0;
boolean isTransferItem = false;
// Const check
int constellation = checkPlayerAvatarConstellationLevel(player, itemId);
switch (constellation) {
case -2: // Is weapon
switch (itemData.getRankLevel()) {
case 5 -> addStarglitter = 10;
case 4 -> addStarglitter = 2;
default -> addStardust = 15;
}
break;
case -1: // New character
gachaItem.setIsGachaItemNew(true);
break;
default:
if (constellation >= 6) { // C6, give consolation starglitter
addStarglitter = (itemData.getRankLevel()==5)? 25 : 5;
} else { // C0-C5, give constellation item
if (banner.isRemoveC6FromPool() && constellation == 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull
pools.removeFromAllPools(new int[] {itemId});
}
addStarglitter = (itemData.getRankLevel()==5)? 10 : 2;
int constItemId = itemId + 100;
GameItem constItem = inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId);
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null));
inventory.addItem(constItemId, 1);
}
isTransferItem = true;
break;
}
// Create item
GameItem item = new GameItem(itemData);
gachaItem.setGachaItem(item.toItemParam());
inventory.addItem(item);
stardust += addStardust;
starglitter += addStarglitter;
if (addStardust > 0) {
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
}
if (addStarglitter > 0) {
ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
}
gachaItem.addTokenItemList(starglitterParam);
}
list.add(gachaItem.build());
}
// Add stardust/starglitter
if (stardust > 0) {
inventory.addItem(stardustId, stardust);
}
if (starglitter > 0) {
inventory.addItem(starglitterId, starglitter);
}
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo));
// Battle Pass trigger
player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_GACHA_NUM, 0, times);
}
private synchronized void startWatcher(GameServer server) {
if(this.watchService == null) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
Path path = new File(DATA()).toPath();
path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
e.printStackTrace();
}
} else {
Grasscutter.getLogger().error("Cannot reinitialise watcher ");
}
}
@Subscribe
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
if(GAME_OPTIONS.watchGachaConfig) {
try {
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
final Path changed = (Path) event.context();
if (changed.endsWith("Banners.json")) {
Grasscutter.getLogger().info("Change detected with banners.json. Reloading gacha config");
this.load();
}
}
boolean valid = watchKey.reset();
if (!valid) {
Grasscutter.getLogger().error("Unable to reset Gacha Manager Watch Key. Auto-reload of banners.json will no longer work.");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private synchronized GetGachaInfoRsp createProto(Player player) {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
long currentTime = System.currentTimeMillis() / 1000L;
for (GachaBanner banner : getGachaBanners().values()) {
if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime) || (banner.getBannerType() == BannerType.STANDARD))
{
proto.addGachaInfoList(banner.toProto(player));
}
}
return proto.build();
}
public GetGachaInfoRsp toProto(Player player) {
return createProto(player);
}
}

View File

@ -0,0 +1,447 @@
package emu.grasscutter.game.gacha;
import static emu.grasscutter.config.Configuration.*;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import com.google.gson.reflect.TypeToken;
import com.sun.nio.file.SensitivityWatchEventModifier;
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.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.server.packet.send.PacketGachaWishRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.greenrobot.eventbus.Subscribe;
public class GachaSystem extends BaseGameSystem {
private final Int2ObjectMap<GachaBanner> gachaBanners;
private WatchService watchService;
private static final int starglitterId = 221;
private static final int stardustId = 222;
private int[] fallbackItems4Pool2Default = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
private int[] fallbackItems5Pool2Default = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
public GachaSystem(GameServer server) {
super(server);
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
this.startWatcher(server);
}
public Int2ObjectMap<GachaBanner> getGachaBanners() {
return gachaBanners;
}
public int randomRange(int min, int max) { // Both are inclusive
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
}
public int getRandom(int[] array) {
return array[randomRange(0, array.length - 1)];
}
public synchronized void load() {
try (Reader fileReader = DataLoader.loadReader("Banners.json")) {
getGachaBanners().clear();
List<GachaBanner> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType());
if (banners.size() > 0) {
for (GachaBanner banner : banners) {
getGachaBanners().put(banner.getScheduleId(), banner);
}
Grasscutter.getLogger().debug("Banners successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private class BannerPools {
public int[] rateUpItems4;
public int[] rateUpItems5;
public int[] fallbackItems4Pool1;
public int[] fallbackItems4Pool2;
public int[] fallbackItems5Pool1;
public int[] fallbackItems5Pool2;
public BannerPools(GachaBanner banner) {
rateUpItems4 = banner.getRateUpItems4();
rateUpItems5 = banner.getRateUpItems5();
fallbackItems4Pool1 = banner.getFallbackItems4Pool1();
fallbackItems4Pool2 = banner.getFallbackItems4Pool2();
fallbackItems5Pool1 = banner.getFallbackItems5Pool1();
fallbackItems5Pool2 = banner.getFallbackItems5Pool2();
if (banner.isAutoStripRateUpFromFallback()) {
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5);
}
}
public void removeFromAllPools(int[] itemIds) {
rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds);
rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds);
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds);
}
}
private synchronized int checkPlayerAvatarConstellationLevel(Player player, int itemId) { // Maybe this would be useful in the Player class?
ItemData itemData = GameData.getItemDataMap().get(itemId);
if ((itemData == null) || (itemData.getMaterialType() != MaterialType.MATERIAL_AVATAR)) {
return -2; // Not an Avatar
}
Avatar avatar = player.getAvatars().getAvatarById((itemId % 1000) + 10000000);
if (avatar == null) {
return -1; // Doesn't have
}
// Constellation
int constLevel = avatar.getCoreProudSkillLevel();
GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId + 100);
constLevel += (constItem == null)? 0 : constItem.getCount();
return constLevel;
}
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
IntList temp = new IntArrayList();
for (int itemId : itemPool) {
if (checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
temp.add(itemId);
}
}
return temp.toIntArray();
}
private synchronized int drawRoulette(int[] weights, int cutoff) {
// This follows the logic laid out in issue #183
// Simple weighted selection with an upper bound for the roll that cuts off trailing entries
// All weights must be >= 0
int total = 0;
for (int weight : weights) {
if (weight < 0) {
throw new IllegalArgumentException("Weights must be non-negative!");
}
total += weight;
}
int roll = ThreadLocalRandom.current().nextInt((total < cutoff)? total : cutoff);
int subTotal = 0;
for (int i=0; i<weights.length; i++) {
subTotal += weights[i];
if (roll < subTotal) {
return i;
}
}
// throw new IllegalStateException();
return 0; // This should only be reachable if total==0
}
private synchronized int doFallbackRarePull(int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
if (fallback1.length < 1) {
if (fallback2.length < 1) {
return getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default);
} else {
return getRandom(fallback2);
}
} else if (fallback2.length < 1) {
return getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1));
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2));
int chosenPool = switch ((pityPool1 >= pityPool2)? 1 : 0) { // Larger weight must come first for the hard cutoff to function correctly
case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000);
default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000);
};
return switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
}
}
private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) {
int itemId = 0;
boolean epitomized = (banner.hasEpitomized()) && (rarity == 5) && (gachaInfo.getWishItemId() != 0);
boolean pityEpitomized = (gachaInfo.getFailedChosenItemPulls() >= banner.getWishMaxProgress()); // Maximum fate points reached
boolean pityFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1); // Lost previous coinflip
boolean rollFeatured = (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
boolean pullFeatured = pityFeatured || rollFeatured;
if (epitomized && pityEpitomized) { // Auto pick item when epitomized points reached
gachaInfo.setFailedFeaturedItemPulls(rarity, 0); // Epitomized item will always be a featured one
itemId = gachaInfo.getWishItemId();
} else {
if (pullFeatured && (featured.length > 0)) {
gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
itemId = getRandom(featured);
} else {
gachaInfo.addFailedFeaturedItemPulls(rarity, 1); // This could be moved into doFallbackRarePull but having it here makes it clearer
itemId = doFallbackRarePull(fallback1, fallback2, rarity, banner, gachaInfo);
}
}
if (epitomized) {
if (itemId == gachaInfo.getWishItemId()) { // Reset epitomized points when got wished item
gachaInfo.setFailedChosenItemPulls(0);
} else { // Add epitomized points if not get wished item
gachaInfo.addFailedChosenItemPulls(1);
}
}
return itemId;
}
private synchronized int doPull(GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) {
// Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity)
gachaInfo.incPityAll();
int[] weights = {banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000};
int levelWon = 5 - drawRoulette(weights, 10000);
return switch (levelWon) {
case 5:
gachaInfo.setPity5(0);
yield doRarePull(pools.rateUpItems5, pools.fallbackItems5Pool1, pools.fallbackItems5Pool2, 5, banner, gachaInfo);
case 4:
gachaInfo.setPity4(0);
yield doRarePull(pools.rateUpItems4, pools.fallbackItems4Pool1, pools.fallbackItems4Pool2, 4, banner, gachaInfo);
default:
yield getRandom(banner.getFallbackItems3());
};
}
public synchronized void doPulls(Player player, int scheduleId, int times) {
// Sanity check
if (times != 10 && times != 1) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_INVALID_TIMES));
return;
}
Inventory inventory = player.getInventory();
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_ITEM_EXCEED_LIMIT));
return;
}
// Get banner
GachaBanner banner = this.getGachaBanners().get(scheduleId);
if (banner == null) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Check against total limit
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
int gachaTimesLimit = banner.getGachaTimesLimit();
if (gachaTimesLimit != Integer.MAX_VALUE && (gachaInfo.getTotalPulls() + times) > gachaTimesLimit) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_TIMES_LIMIT));
return;
}
// Spend currency
ItemParamData cost = banner.getCost(times);
if (cost.getCount() > 0 && !inventory.payItem(cost)) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_COST_ITEM_NOT_ENOUGH));
return;
}
// Add to character
gachaInfo.addTotalPulls(times);
BannerPools pools = new BannerPools(banner);
List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0;
if (banner.isRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla)
pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player);
pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player);
pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player);
pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player);
pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player);
pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player);
}
for (int i = 0; i < times; i++) {
// Roll
int itemId = doPull(banner, gachaInfo, pools);
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) {
continue; // Maybe we should bail out if an item fails instead of rolling the rest?
}
// Write gacha record
GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), banner.getGachaType());
DatabaseHelper.saveGachaRecord(gachaRecord);
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
int addStardust = 0, addStarglitter = 0;
boolean isTransferItem = false;
// Const check
int constellation = checkPlayerAvatarConstellationLevel(player, itemId);
switch (constellation) {
case -2: // Is weapon
switch (itemData.getRankLevel()) {
case 5 -> addStarglitter = 10;
case 4 -> addStarglitter = 2;
default -> addStardust = 15;
}
break;
case -1: // New character
gachaItem.setIsGachaItemNew(true);
break;
default:
if (constellation >= 6) { // C6, give consolation starglitter
addStarglitter = (itemData.getRankLevel()==5)? 25 : 5;
} else { // C0-C5, give constellation item
if (banner.isRemoveC6FromPool() && constellation == 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull
pools.removeFromAllPools(new int[] {itemId});
}
addStarglitter = (itemData.getRankLevel()==5)? 10 : 2;
int constItemId = itemId + 100;
GameItem constItem = inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId);
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null));
inventory.addItem(constItemId, 1);
}
isTransferItem = true;
break;
}
// Create item
GameItem item = new GameItem(itemData);
gachaItem.setGachaItem(item.toItemParam());
inventory.addItem(item);
stardust += addStardust;
starglitter += addStarglitter;
if (addStardust > 0) {
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
}
if (addStarglitter > 0) {
ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
}
gachaItem.addTokenItemList(starglitterParam);
}
list.add(gachaItem.build());
}
// Add stardust/starglitter
if (stardust > 0) {
inventory.addItem(stardustId, stardust);
}
if (starglitter > 0) {
inventory.addItem(starglitterId, starglitter);
}
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo));
// Battle Pass trigger
player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_GACHA_NUM, 0, times);
}
private synchronized void startWatcher(GameServer server) {
if (this.watchService == null) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
Path path = new File(DATA()).toPath();
path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
e.printStackTrace();
}
} else {
Grasscutter.getLogger().error("Cannot reinitialise watcher ");
}
}
@Subscribe
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
if (GAME_OPTIONS.watchGachaConfig) {
try {
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
final Path changed = (Path) event.context();
if (changed.endsWith("Banners.json")) {
Grasscutter.getLogger().info("Change detected with banners.json. Reloading gacha config");
this.load();
}
}
boolean valid = watchKey.reset();
if (!valid) {
Grasscutter.getLogger().error("Unable to reset Gacha Manager Watch Key. Auto-reload of banners.json will no longer work.");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private synchronized GetGachaInfoRsp createProto(Player player) {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
long currentTime = System.currentTimeMillis() / 1000L;
for (GachaBanner banner : getGachaBanners().values()) {
if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime) || (banner.getBannerType() == BannerType.STANDARD))
{
proto.addGachaInfoList(banner.toProto(player));
}
}
return proto.build();
}
public GetGachaInfoRsp toProto(Player player) {
return createProto(player);
}
}

View File

@ -1,5 +1,7 @@
package emu.grasscutter.game.inventory; package emu.grasscutter.game.inventory;
import static emu.grasscutter.config.Configuration.*;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
@ -15,6 +17,7 @@ import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
@ -29,194 +32,200 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import static emu.grasscutter.Configuration.*; public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
private final Long2ObjectMap<GameItem> store;
private final Int2ObjectMap<InventoryTab> inventoryTypes;
public class Inventory implements Iterable<GameItem> { public Inventory(Player player) {
private final Player player; super(player);
private final Long2ObjectMap<GameItem> store;
private final Int2ObjectMap<InventoryTab> inventoryTypes;
public Inventory(Player player) {
this.player = player;
this.store = new Long2ObjectOpenHashMap<>();
this.inventoryTypes = new Int2ObjectOpenHashMap<>();
this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(INVENTORY_LIMITS.weapons));
this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(INVENTORY_LIMITS.relics));
this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(INVENTORY_LIMITS.materials));
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(INVENTORY_LIMITS.furniture));
}
public Player getPlayer() { this.store = new Long2ObjectOpenHashMap<>();
return player; this.inventoryTypes = new Int2ObjectOpenHashMap<>();
}
public AvatarStorage getAvatarStorage() {
return this.getPlayer().getAvatars();
}
public Long2ObjectMap<GameItem> getItems() { this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(INVENTORY_LIMITS.weapons));
return store; this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(INVENTORY_LIMITS.relics));
} this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(INVENTORY_LIMITS.materials));
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(INVENTORY_LIMITS.furniture));
public Int2ObjectMap<InventoryTab> getInventoryTypes() { }
return inventoryTypes;
}
public InventoryTab getInventoryTab(ItemType type) {
return getInventoryTypes().get(type.getValue());
}
public void createInventoryTab(ItemType type, InventoryTab tab) {
this.getInventoryTypes().put(type.getValue(), tab);
}
public GameItem getItemByGuid(long id) {
return this.getItems().get(id);
}
public boolean addItem(int itemId) {
return addItem(itemId, 1);
}
public boolean addItem(int itemId, int count) {
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) {
return false;
}
GameItem item = new GameItem(itemData, count);
return addItem(item);
}
public boolean addItem(GameItem item) { public AvatarStorage getAvatarStorage() {
GameItem result = putItem(item); return this.getPlayer().getAvatars();
}
if (result != null) {
getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM, result.getItemId(), result.getCount());
getPlayer().sendPacket(new PacketStoreItemChangeNotify(result));
return true;
}
return false;
}
public boolean addItem(GameItem item, ActionReason reason) {
boolean result = addItem(item);
if (result && reason != null) {
getPlayer().sendPacket(new PacketItemAddHintNotify(item, reason));
}
return result;
}
public boolean addItem(GameItem item, ActionReason reason, boolean forceNotify) { public Long2ObjectMap<GameItem> getItems() {
boolean result = addItem(item); return store;
}
if (reason != null && (forceNotify || result)) { public Int2ObjectMap<InventoryTab> getInventoryTypes() {
getPlayer().sendPacket(new PacketItemAddHintNotify(item, reason)); return inventoryTypes;
} }
return result; public InventoryTab getInventoryTab(ItemType type) {
} return getInventoryTypes().get(type.getValue());
}
public void addItems(Collection<GameItem> items) {
this.addItems(items, null); public void createInventoryTab(ItemType type, InventoryTab tab) {
} this.getInventoryTypes().put(type.getValue(), tab);
}
public void addItems(Collection<GameItem> items, ActionReason reason) {
List<GameItem> changedItems = new LinkedList<>(); public GameItem getItemByGuid(long id) {
return this.getItems().get(id);
for (GameItem item : items) { }
GameItem result = putItem(item);
public boolean addItem(int itemId) {
if (result != null) { return addItem(itemId, 1);
getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM, result.getItemId(), result.getCount()); }
changedItems.add(result);
} public boolean addItem(int itemId, int count) {
} return addItem(itemId, count, null);
}
if (changedItems.size() == 0) {
return; public boolean addItem(int itemId, int count, ActionReason reason) {
} ItemData itemData = GameData.getItemDataMap().get(itemId);
if (reason != null) { if (itemData == null) {
getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason)); return false;
} }
getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems)); GameItem item = new GameItem(itemData, count);
}
return addItem(item, reason);
public void addItemParams(Collection<ItemParam> items) { }
addItems(items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(), null);
} public boolean addItem(GameItem item) {
GameItem result = putItem(item);
public void addItemParamDatas(Collection<ItemParamData> items) {
addItemParamDatas(items, null); if (result != null) {
} getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM, result.getItemId(), result.getCount());
getPlayer().sendPacket(new PacketStoreItemChangeNotify(result));
public void addItemParamDatas(Collection<ItemParamData> items, ActionReason reason) { return true;
addItems(items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(), reason); }
}
return false;
private synchronized GameItem putItem(GameItem item) { }
// Dont add items that dont have a valid item definition.
if (item.getItemData() == null) { public boolean addItem(GameItem item, ActionReason reason) {
return null; boolean result = addItem(item);
}
if (result && reason != null) {
// Add item to inventory store getPlayer().sendPacket(new PacketItemAddHintNotify(item, reason));
ItemType type = item.getItemData().getItemType(); }
InventoryTab tab = getInventoryTab(type);
return result;
// Add }
switch (type) {
case ITEM_WEAPON: public boolean addItem(GameItem item, ActionReason reason, boolean forceNotify) {
case ITEM_RELIQUARY: boolean result = addItem(item);
if (tab.getSize() >= tab.getMaxCapacity()) {
return null; if (reason != null && (forceNotify || result)) {
} getPlayer().sendPacket(new PacketItemAddHintNotify(item, reason));
// Duplicates cause problems }
item.setCount(Math.max(item.getCount(), 1));
// Adds to inventory return result;
}
public boolean addItem(ItemParamData itemParam) {
return addItem(itemParam, null);
}
public boolean addItem(ItemParamData itemParam, ActionReason reason) {
if (itemParam == null) return false;
return addItem(itemParam.getId(), itemParam.getCount(), reason);
}
public void addItems(Collection<GameItem> items) {
this.addItems(items, null);
}
public void addItems(Collection<GameItem> items, ActionReason reason) {
List<GameItem> changedItems = new LinkedList<>();
for (GameItem item : items) {
GameItem result = putItem(item);
if (result != null) {
getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_OBTAIN_MATERIAL_NUM, result.getItemId(), result.getCount());
changedItems.add(result);
}
}
if (changedItems.size() == 0) {
return;
}
if (reason != null) {
getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason));
}
getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
}
public void addItemParams(Collection<ItemParam> items) {
addItems(items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(), null);
}
public void addItemParamDatas(Collection<ItemParamData> items) {
addItemParamDatas(items, null);
}
public void addItemParamDatas(Collection<ItemParamData> items, ActionReason reason) {
addItems(items.stream().map(param -> new GameItem(param.getItemId(), param.getCount())).toList(), reason);
}
private synchronized GameItem putItem(GameItem item) {
// Dont add items that dont have a valid item definition.
if (item.getItemData() == null) {
return null;
}
// Add item to inventory store
ItemType type = item.getItemData().getItemType();
InventoryTab tab = getInventoryTab(type);
// Add
switch (type) {
case ITEM_WEAPON:
case ITEM_RELIQUARY:
if (tab.getSize() >= tab.getMaxCapacity()) {
return null;
}
// Duplicates cause problems
item.setCount(Math.max(item.getCount(), 1));
// Adds to inventory
this.putItem(item, tab); this.putItem(item, tab);
// Set ownership and save to db // Set ownership and save to db
item.save(); item.save();
return item; return item;
case ITEM_VIRTUAL: case ITEM_VIRTUAL:
// Handle // Handle
this.addVirtualItem(item.getItemId(), item.getCount()); this.addVirtualItem(item.getItemId(), item.getCount());
return item; return item;
default: default:
switch (item.getItemData().getMaterialType()) { switch (item.getItemData().getMaterialType()) {
case MATERIAL_ADSORBATE: case MATERIAL_ADSORBATE:
this.player.getEnergyManager().handlePickupElemBall(item); this.player.getEnergyManager().handlePickupElemBall(item);
return null; return null;
case MATERIAL_AVATAR: case MATERIAL_AVATAR:
// Get avatar id // Get avatar id
int avatarId = (item.getItemId() % 1000) + 10000000; int avatarId = (item.getItemId() % 1000) + 10000000;
// Dont let people give themselves extra main characters // Dont let people give themselves extra main characters
if (avatarId == GameConstants.MAIN_CHARACTER_MALE || avatarId == GameConstants.MAIN_CHARACTER_FEMALE) { if (avatarId == GameConstants.MAIN_CHARACTER_MALE || avatarId == GameConstants.MAIN_CHARACTER_FEMALE) {
return null; return null;
} }
// Add avatar // Add avatar
AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId); AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId);
if (avatarData != null && !this.player.getAvatars().hasAvatar(avatarId)) { if (avatarData != null && !this.player.getAvatars().hasAvatar(avatarId)) {
this.player.addAvatar(new Avatar(avatarData)); this.player.addAvatar(new Avatar(avatarData));
} }
return null; return null;
case MATERIAL_FLYCLOAK: case MATERIAL_FLYCLOAK:
AvatarFlycloakData flycloakData = GameData.getAvatarFlycloakDataMap().get(item.getItemId()); AvatarFlycloakData flycloakData = GameData.getAvatarFlycloakDataMap().get(item.getItemId());
if (flycloakData != null && !this.player.getFlyCloakList().contains(item.getItemId())) { if (flycloakData != null && !this.player.getFlyCloakList().contains(item.getItemId())) {
this.player.addFlycloak(item.getItemId()); this.player.addFlycloak(item.getItemId());
} }
return null; return null;
case MATERIAL_COSTUME: case MATERIAL_COSTUME:
AvatarCostumeData costumeData = GameData.getAvatarCostumeDataItemIdMap().get(item.getItemId()); AvatarCostumeData costumeData = GameData.getAvatarCostumeDataItemIdMap().get(item.getItemId());
if (costumeData != null && !this.player.getCostumeList().contains(costumeData.getId())) { if (costumeData != null && !this.player.getCostumeList().contains(costumeData.getId())) {
this.player.addCostume(costumeData.getId()); this.player.addCostume(costumeData.getId());
} }
@ -224,51 +233,51 @@ public class Inventory implements Iterable<GameItem> {
case MATERIAL_NAMECARD: case MATERIAL_NAMECARD:
if (!this.player.getNameCardList().contains(item.getItemId())) { if (!this.player.getNameCardList().contains(item.getItemId())) {
this.player.addNameCard(item.getItemId()); this.player.addNameCard(item.getItemId());
} }
return null; return null;
default: default:
if (tab == null) { if (tab == null) {
return null; return null;
} }
GameItem existingItem = tab.getItemById(item.getItemId()); GameItem existingItem = tab.getItemById(item.getItemId());
if (existingItem == null) { if (existingItem == null) {
// Item type didnt exist before, we will add it to main inventory map if there is enough space // Item type didnt exist before, we will add it to main inventory map if there is enough space
if (tab.getSize() >= tab.getMaxCapacity()) { if (tab.getSize() >= tab.getMaxCapacity()) {
return null; return null;
} }
this.putItem(item, tab); this.putItem(item, tab);
// Set ownership and save to db // Set ownership and save to db
item.save(); item.save();
return item; return item;
} else { } else {
// Add count // Add count
existingItem.setCount(Math.min(existingItem.getCount() + item.getCount(), item.getItemData().getStackLimit())); existingItem.setCount(Math.min(existingItem.getCount() + item.getCount(), item.getItemData().getStackLimit()));
existingItem.save(); existingItem.save();
return existingItem; return existingItem;
} }
} }
} }
} }
private synchronized void putItem(GameItem item, InventoryTab tab) { private synchronized void putItem(GameItem item, InventoryTab tab) {
this.player.getCodex().checkAddedItem(item); this.player.getCodex().checkAddedItem(item);
// Set owner and guid FIRST! // Set owner and guid FIRST!
item.setOwner(this.player); item.setOwner(this.player);
// Put in item store // Put in item store
getItems().put(item.getGuid(), item); getItems().put(item.getGuid(), item);
if (tab != null) { if (tab != null) {
tab.onAddItem(item); tab.onAddItem(item);
} }
} }
private void addVirtualItem(int itemId, int count) { private void addVirtualItem(int itemId, int count) {
switch (itemId) { switch (itemId) {
case 101 -> // Character exp case 101 -> // Character exp
this.player.getServer().getInventoryManager().upgradeAvatar(this.player, this.player.getTeamManager().getCurrentAvatarEntity().getAvatar(), count); this.player.getServer().getInventorySystem().upgradeAvatar(this.player, this.player.getTeamManager().getCurrentAvatarEntity().getAvatar(), count);
case 102 -> // Adventure exp case 102 -> // Adventure exp
this.player.addExpDirectly(count); this.player.addExpDirectly(count);
case 105 -> // Companionship exp case 105 -> // Companionship exp
this.player.getServer().getInventoryManager().upgradeAvatarFetterLevel(this.player, this.player.getTeamManager().getCurrentAvatarEntity().getAvatar(), count); this.player.getServer().getInventorySystem().upgradeAvatarFetterLevel(this.player, this.player.getTeamManager().getCurrentAvatarEntity().getAvatar(), count);
case 106 -> // Resin case 106 -> // Resin
this.player.getResinManager().addResin(count); this.player.getResinManager().addResin(count);
case 107 -> // Legendary Key case 107 -> // Legendary Key
@ -281,216 +290,216 @@ public class Inventory implements Iterable<GameItem> {
this.player.setCrystals(this.player.getCrystals() + count); this.player.setCrystals(this.player.getCrystals() + count);
case 204 -> // Home Coin case 204 -> // Home Coin
this.player.setHomeCoin(this.player.getHomeCoin() + count); this.player.setHomeCoin(this.player.getHomeCoin() + count);
} }
} }
private int getVirtualItemCount(int itemId) { private int getVirtualItemCount(int itemId) {
switch (itemId) { switch (itemId) {
case 201: // Primogem case 201: // Primogem
return this.player.getPrimogems(); return this.player.getPrimogems();
case 202: // Mora case 202: // Mora
return this.player.getMora(); return this.player.getMora();
case 203: // Genesis Crystals case 203: // Genesis Crystals
return this.player.getCrystals(); return this.player.getCrystals();
case 106: // Resin case 106: // Resin
return this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN); return this.player.getProperty(PlayerProperty.PROP_PLAYER_RESIN);
case 107: // Legendary Key case 107: // Legendary Key
return this.player.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY); return this.player.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY);
case 204: // Home Coin case 204: // Home Coin
return this.player.getHomeCoin(); return this.player.getHomeCoin();
default: default:
GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S
return (item == null) ? 0 : item.getCount(); return (item == null) ? 0 : item.getCount();
} }
} }
public boolean payItem(int id, int count) { public boolean payItem(int id, int count) {
return payItem(new ItemParamData(id, count)); return payItem(new ItemParamData(id, count));
} }
public boolean payItem(ItemParamData costItem) { public boolean payItem(ItemParamData costItem) {
return payItems(new ItemParamData[] {costItem}, 1, null); return payItems(new ItemParamData[] {costItem}, 1, null);
} }
public boolean payItems(ItemParamData[] costItems) { public boolean payItems(ItemParamData[] costItems) {
return payItems(costItems, 1, null); return payItems(costItems, 1, null);
} }
public boolean payItems(ItemParamData[] costItems, int quantity) { public boolean payItems(ItemParamData[] costItems, int quantity) {
return payItems(costItems, quantity, null); return payItems(costItems, quantity, null);
} }
public synchronized boolean payItems(ItemParamData[] costItems, int quantity, ActionReason reason) { public synchronized boolean payItems(ItemParamData[] costItems, int quantity, ActionReason reason) {
// Make sure player has requisite items // Make sure player has requisite items
for (ItemParamData cost : costItems) { for (ItemParamData cost : costItems) {
if (getVirtualItemCount(cost.getId()) < (cost.getCount() * quantity)) { if (getVirtualItemCount(cost.getId()) < (cost.getCount() * quantity)) {
return false; return false;
} }
} }
// All costs are satisfied, now remove them all // All costs are satisfied, now remove them all
for (ItemParamData cost : costItems) { for (ItemParamData cost : costItems) {
switch (cost.getId()) { switch (cost.getId()) {
case 201 -> // Primogem case 201 -> // Primogem
player.setPrimogems(player.getPrimogems() - (cost.getCount() * quantity)); player.setPrimogems(player.getPrimogems() - (cost.getCount() * quantity));
case 202 -> // Mora case 202 -> // Mora
player.setMora(player.getMora() - (cost.getCount() * quantity)); player.setMora(player.getMora() - (cost.getCount() * quantity));
case 203 -> // Genesis Crystals case 203 -> // Genesis Crystals
player.setCrystals(player.getCrystals() - (cost.getCount() * quantity)); player.setCrystals(player.getCrystals() - (cost.getCount() * quantity));
case 106 -> // Resin case 106 -> // Resin
player.getResinManager().useResin(cost.getCount() * quantity); player.getResinManager().useResin(cost.getCount() * quantity);
case 107 -> // LegendaryKey case 107 -> // LegendaryKey
player.useLegendaryKey(cost.getCount() * quantity); player.useLegendaryKey(cost.getCount() * quantity);
case 204 -> // Home Coin case 204 -> // Home Coin
player.setHomeCoin(player.getHomeCoin() - (cost.getCount() * quantity)); player.setHomeCoin(player.getHomeCoin() - (cost.getCount() * quantity));
default -> default ->
removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount() * quantity); removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount() * quantity);
} }
} }
if (reason != null) { // Do we need these? if (reason != null) { // Do we need these?
// getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason)); // getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason));
} }
// getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems)); // getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
return true; return true;
} }
public void removeItems(List<GameItem> items) {
// TODO Bulk delete
for (GameItem item : items) {
this.removeItem(item, item.getCount());
}
}
public boolean removeItem(long guid) {
return removeItem(guid, 1);
}
public synchronized boolean removeItem(long guid, int count) {
GameItem item = this.getItemByGuid(guid);
if (item == null) {
return false;
}
return removeItem(item, count);
}
public synchronized boolean removeItem(GameItem item) {
return removeItem(item, item.getCount());
}
public synchronized boolean removeItem(GameItem item, int count) {
// Sanity check
if (count <= 0 || item == null) {
return false;
}
if (item.getItemData().isEquip()) { public void removeItems(List<GameItem> items) {
item.setCount(0); // TODO Bulk delete
} else { for (GameItem item : items) {
item.setCount(item.getCount() - count); this.removeItem(item, item.getCount());
} }
}
if (item.getCount() <= 0) {
// Remove from inventory tab too
InventoryTab tab = null;
if (item.getItemData() != null) {
tab = getInventoryTab(item.getItemData().getItemType());
}
// Remove if less than 0
deleteItem(item, tab);
//
getPlayer().sendPacket(new PacketStoreItemDelNotify(item));
} else {
getPlayer().sendPacket(new PacketStoreItemChangeNotify(item));
}
// Battle pass trigger
int removeCount = Math.min(count, item.getCount());
getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_COST_MATERIAL, item.getItemId(), removeCount);
// Update in db
item.save();
// Returns true on success
return true;
}
private void deleteItem(GameItem item, InventoryTab tab) {
getItems().remove(item.getGuid());
if (tab != null) {
tab.onRemoveItem(item);
}
}
public boolean equipItem(long avatarGuid, long equipGuid) {
Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(avatarGuid);
GameItem item = this.getItemByGuid(equipGuid);
if (avatar != null && item != null) {
return avatar.equipItem(item, true);
}
return false;
}
public boolean unequipItem(long avatarGuid, int slot) {
Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(avatarGuid);
EquipType equipType = EquipType.getTypeByValue(slot);
if (avatar != null && equipType != EquipType.EQUIP_WEAPON) {
if (avatar.unequipItem(equipType)) {
getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(avatar, equipType));
avatar.recalcStats();
return true;
}
}
return false;
}
public void loadFromDatabase() {
List<GameItem> items = DatabaseHelper.getInventoryItems(getPlayer());
for (GameItem item : items) {
// Should never happen
if (item.getObjectId() == null) {
continue;
}
ItemData itemData = GameData.getItemDataMap().get(item.getItemId());
if (itemData == null) {
continue;
}
item.setItemData(itemData);
InventoryTab tab = null;
if (item.getItemData() != null) {
tab = getInventoryTab(item.getItemData().getItemType());
}
putItem(item, tab);
// Equip to a character if possible
if (item.isEquipped()) {
Avatar avatar = getPlayer().getAvatars().getAvatarById(item.getEquipCharacter());
boolean hasEquipped = false;
if (avatar != null) {
hasEquipped = avatar.equipItem(item, false);
}
if (!hasEquipped) {
item.setEquipCharacter(0);
item.save();
}
}
}
}
@Override public boolean removeItem(long guid) {
public Iterator<GameItem> iterator() { return removeItem(guid, 1);
return this.getItems().values().iterator(); }
}
public synchronized boolean removeItem(long guid, int count) {
GameItem item = this.getItemByGuid(guid);
if (item == null) {
return false;
}
return removeItem(item, count);
}
public synchronized boolean removeItem(GameItem item) {
return removeItem(item, item.getCount());
}
public synchronized boolean removeItem(GameItem item, int count) {
// Sanity check
if (count <= 0 || item == null) {
return false;
}
if (item.getItemData().isEquip()) {
item.setCount(0);
} else {
item.setCount(item.getCount() - count);
}
if (item.getCount() <= 0) {
// Remove from inventory tab too
InventoryTab tab = null;
if (item.getItemData() != null) {
tab = getInventoryTab(item.getItemData().getItemType());
}
// Remove if less than 0
deleteItem(item, tab);
//
getPlayer().sendPacket(new PacketStoreItemDelNotify(item));
} else {
getPlayer().sendPacket(new PacketStoreItemChangeNotify(item));
}
// Battle pass trigger
int removeCount = Math.min(count, item.getCount());
getPlayer().getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_COST_MATERIAL, item.getItemId(), removeCount);
// Update in db
item.save();
// Returns true on success
return true;
}
private void deleteItem(GameItem item, InventoryTab tab) {
getItems().remove(item.getGuid());
if (tab != null) {
tab.onRemoveItem(item);
}
}
public boolean equipItem(long avatarGuid, long equipGuid) {
Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(avatarGuid);
GameItem item = this.getItemByGuid(equipGuid);
if (avatar != null && item != null) {
return avatar.equipItem(item, true);
}
return false;
}
public boolean unequipItem(long avatarGuid, int slot) {
Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(avatarGuid);
EquipType equipType = EquipType.getTypeByValue(slot);
if (avatar != null && equipType != EquipType.EQUIP_WEAPON) {
if (avatar.unequipItem(equipType)) {
getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(avatar, equipType));
avatar.recalcStats();
return true;
}
}
return false;
}
public void loadFromDatabase() {
List<GameItem> items = DatabaseHelper.getInventoryItems(getPlayer());
for (GameItem item : items) {
// Should never happen
if (item.getObjectId() == null) {
continue;
}
ItemData itemData = GameData.getItemDataMap().get(item.getItemId());
if (itemData == null) {
continue;
}
item.setItemData(itemData);
InventoryTab tab = null;
if (item.getItemData() != null) {
tab = getInventoryTab(item.getItemData().getItemType());
}
putItem(item, tab);
// Equip to a character if possible
if (item.isEquipped()) {
Avatar avatar = getPlayer().getAvatars().getAvatarById(item.getEquipCharacter());
boolean hasEquipped = false;
if (avatar != null) {
hasEquipped = avatar.equipItem(item, false);
}
if (!hasEquipped) {
item.setEquipCharacter(0);
item.save();
}
}
}
}
@Override
public Iterator<GameItem> iterator() {
return this.getItems().values().iterator();
}
} }

View File

@ -8,35 +8,45 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum MaterialType { public enum MaterialType {
MATERIAL_NONE (0), MATERIAL_NONE (0),
MATERIAL_FOOD (1), MATERIAL_FOOD (1),
MATERIAL_QUEST (2), MATERIAL_QUEST (2),
MATERIAL_EXCHANGE (4), MATERIAL_EXCHANGE (4),
MATERIAL_CONSUME (5), MATERIAL_CONSUME (5),
MATERIAL_EXP_FRUIT (6), MATERIAL_EXP_FRUIT (6),
MATERIAL_AVATAR (7), MATERIAL_AVATAR (7),
MATERIAL_ADSORBATE (8), MATERIAL_ADSORBATE (8),
MATERIAL_CRICKET (9), MATERIAL_CRICKET (9),
MATERIAL_ELEM_CRYSTAL (10), MATERIAL_ELEM_CRYSTAL (10),
MATERIAL_WEAPON_EXP_STONE (11), MATERIAL_WEAPON_EXP_STONE (11),
MATERIAL_CHEST (12), MATERIAL_CHEST (12),
MATERIAL_RELIQUARY_MATERIAL (13), MATERIAL_RELIQUARY_MATERIAL (13),
MATERIAL_AVATAR_MATERIAL (14), MATERIAL_AVATAR_MATERIAL (14),
MATERIAL_NOTICE_ADD_HP (15), MATERIAL_NOTICE_ADD_HP (15),
MATERIAL_SEA_LAMP (16), MATERIAL_SEA_LAMP (16),
MATERIAL_SELECTABLE_CHEST (17), MATERIAL_SELECTABLE_CHEST (17),
MATERIAL_FLYCLOAK (18), MATERIAL_FLYCLOAK (18),
MATERIAL_NAMECARD (19), MATERIAL_NAMECARD (19),
MATERIAL_TALENT (20), MATERIAL_TALENT (20),
MATERIAL_WIDGET (21), MATERIAL_WIDGET (21),
MATERIAL_CHEST_BATCH_USE (22), MATERIAL_CHEST_BATCH_USE (22),
MATERIAL_FAKE_ABSORBATE (23), MATERIAL_FAKE_ABSORBATE (23),
MATERIAL_CONSUME_BATCH_USE (24), MATERIAL_CONSUME_BATCH_USE (24),
MATERIAL_WOOD (25), MATERIAL_WOOD (25),
MATERIAL_FURNITURE_FORMULA (27), MATERIAL_FURNITURE_FORMULA (27),
MATERIAL_CHANNELLER_SLAB_BUFF (28), MATERIAL_CHANNELLER_SLAB_BUFF (28),
MATERIAL_FURNITURE_SUITE_FORMULA (29), MATERIAL_FURNITURE_SUITE_FORMULA (29),
MATERIAL_COSTUME (30); MATERIAL_COSTUME (30),
MATERIAL_HOME_SEED (31),
MATERIAL_FISH_BAIT (32),
MATERIAL_FISH_ROD (33),
MATERIAL_SUMO_BUFF (34),
MATERIAL_FIREWORKS (35),
MATERIAL_BGM (36),
MATERIAL_SPICE_FOOD (37),
MATERIAL_ACTIVITY_ROBOT (38),
MATERIAL_ACTIVITY_GEAR (39),
MATERIAL_ACTIVITY_JIGSAW (40);
private final int value; private final int value;
private static final Int2ObjectMap<MaterialType> map = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<MaterialType> map = new Int2ObjectOpenHashMap<>();

View File

@ -1,105 +1,101 @@
package emu.grasscutter.game.mail; package emu.grasscutter.game.mail;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.player.PlayerReceiveMailEvent; import emu.grasscutter.server.event.player.PlayerReceiveMailEvent;
import emu.grasscutter.server.packet.send.PacketDelMailRsp; import emu.grasscutter.server.packet.send.PacketDelMailRsp;
import emu.grasscutter.server.packet.send.PacketMailChangeNotify; import emu.grasscutter.server.packet.send.PacketMailChangeNotify;
public class MailHandler { public class MailHandler extends BasePlayerManager {
private final Player player; private final List<Mail> mail;
private final List<Mail> mail;
public MailHandler(Player player) {
this.player = player;
this.mail = new ArrayList<>();
}
public Player getPlayer() { public MailHandler(Player player) {
return player; super(player);
}
public List<Mail> getMail() { this.mail = new ArrayList<>();
return mail; }
}
// ---------------------MAIL------------------------
public void sendMail(Mail message) { public List<Mail> getMail() {
// Call mail receive event. return mail;
PlayerReceiveMailEvent event = new PlayerReceiveMailEvent(this.getPlayer(), message); event.call(); }
if(event.isCanceled()) return; message = event.getMessage();
message.setOwnerUid(this.getPlayer().getUid());
message.save();
this.mail.add(message);
Grasscutter.getLogger().debug("Mail sent to user [" + this.getPlayer().getUid() + ":" + this.getPlayer().getNickname() + "]!");
if (this.getPlayer().isOnline()) {
this.getPlayer().sendPacket(new PacketMailChangeNotify(this.getPlayer(), message));
} // TODO: setup a way for the mail notification to show up when someone receives mail when they were offline
}
public boolean deleteMail(int mailId) { // ---------------------MAIL------------------------
Mail message = getMailById(mailId);
if (message != null) { public void sendMail(Mail message) {
this.getMail().remove(mailId); // Call mail receive event.
message.expireTime = 0; PlayerReceiveMailEvent event = new PlayerReceiveMailEvent(this.getPlayer(), message); event.call();
message.save(); if (event.isCanceled()) return; message = event.getMessage();
return true;
}
return false; message.setOwnerUid(this.getPlayer().getUid());
} message.save();
public void deleteMail(List<Integer> mailList) {
List<Integer> sortedMailList = new ArrayList<>();
sortedMailList.addAll(mailList);
Collections.sort(sortedMailList, Collections.reverseOrder());
List<Integer> deleted = new ArrayList<>();
for (int id : sortedMailList) {
if (this.deleteMail(id)) {
deleted.add(id);
}
}
player.getSession().send(new PacketDelMailRsp(player, deleted));
player.getSession().send(new PacketMailChangeNotify(player, null, deleted));
}
public Mail getMailById(int index) { return this.mail.get(index); } this.mail.add(message);
public int getMailIndex(Mail message) {
return this.mail.indexOf(message);
}
public boolean replaceMailByIndex(int index, Mail message) { Grasscutter.getLogger().debug("Mail sent to user [" + this.getPlayer().getUid() + ":" + this.getPlayer().getNickname() + "]!");
if(getMailById(index) != null) {
this.mail.set(index, message);
message.save();
return true;
} else {
return false;
}
}
public void loadFromDatabase() { if (this.getPlayer().isOnline()) {
List<Mail> mailList = DatabaseHelper.getAllMail(this.getPlayer()); this.getPlayer().sendPacket(new PacketMailChangeNotify(this.getPlayer(), message));
} // TODO: setup a way for the mail notification to show up when someone receives mail when they were offline
for (Mail mail : mailList) { }
this.getMail().add(mail);
} public boolean deleteMail(int mailId) {
} Mail message = getMailById(mailId);
if (message != null) {
this.getMail().remove(mailId);
message.expireTime = 0;
message.save();
return true;
}
return false;
}
public void deleteMail(List<Integer> mailList) {
List<Integer> sortedMailList = new ArrayList<>();
sortedMailList.addAll(mailList);
Collections.sort(sortedMailList, Collections.reverseOrder());
List<Integer> deleted = new ArrayList<>();
for (int id : sortedMailList) {
if (this.deleteMail(id)) {
deleted.add(id);
}
}
player.getSession().send(new PacketDelMailRsp(player, deleted));
player.getSession().send(new PacketMailChangeNotify(player, null, deleted));
}
public Mail getMailById(int index) { return this.mail.get(index); }
public int getMailIndex(Mail message) {
return this.mail.indexOf(message);
}
public boolean replaceMailByIndex(int index, Mail message) {
if (getMailById(index) != null) {
this.mail.set(index, message);
message.save();
return true;
} else {
return false;
}
}
public void loadFromDatabase() {
List<Mail> mailList = DatabaseHelper.getAllMail(this.getPlayer());
for (Mail mail : mailList) {
this.getMail().add(mail);
}
}
} }

View File

@ -1,15 +0,0 @@
package emu.grasscutter.game.managers;
import emu.grasscutter.server.game.GameServer;
public class AccountManager {
private final GameServer server;
public AccountManager(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
}

View File

@ -10,8 +10,10 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.net.proto.CookRecipeDataOuterClass; import emu.grasscutter.net.proto.CookRecipeDataOuterClass;
import emu.grasscutter.net.proto.PlayerCookArgsReqOuterClass.PlayerCookArgsReq; import emu.grasscutter.net.proto.PlayerCookArgsReqOuterClass.PlayerCookArgsReq;
import emu.grasscutter.net.proto.PlayerCookReqOuterClass.PlayerCookReq; import emu.grasscutter.net.proto.PlayerCookReqOuterClass.PlayerCookReq;
@ -22,14 +24,12 @@ import emu.grasscutter.server.packet.send.PacketPlayerCookArgsRsp;
import emu.grasscutter.server.packet.send.PacketPlayerCookRsp; import emu.grasscutter.server.packet.send.PacketPlayerCookRsp;
import io.netty.util.internal.ThreadLocalRandom; import io.netty.util.internal.ThreadLocalRandom;
public class CookingManager { public class CookingManager extends BasePlayerManager {
private static final int MANUAL_PERFECT_COOK_QUALITY = 3; private static final int MANUAL_PERFECT_COOK_QUALITY = 3;
private static Set<Integer> defaultUnlockedRecipies; private static Set<Integer> defaultUnlockedRecipies;
private final Player player;
public CookingManager(Player player) { public CookingManager(Player player) {
this.player = player; super(player);
} }
public static void initialize() { public static void initialize() {
@ -48,12 +48,12 @@ public class CookingManager {
********************/ ********************/
public synchronized boolean unlockRecipe(GameItem recipeItem) { public synchronized boolean unlockRecipe(GameItem recipeItem) {
// Make sure this is actually a cooking recipe. // Make sure this is actually a cooking recipe.
if (!recipeItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_COOK_RECIPE")) { if (recipeItem.getItemData().getItemUse().get(0).getUseOp() != ItemUseOp.ITEM_USE_UNLOCK_COOK_RECIPE) {
return false; return false;
} }
// Determine the recipe we should unlock. // Determine the recipe we should unlock.
int recipeId = Integer.parseInt(recipeItem.getItemData().getItemUse().get(0).getUseParam().get(0)); int recipeId = Integer.parseInt(recipeItem.getItemData().getItemUse().get(0).getUseParam()[0]);
// Remove the item from the player's inventory. // Remove the item from the player's inventory.
// We need to do this here, before sending CookRecipeDataNotify, or the the UI won't correctly update. // We need to do this here, before sending CookRecipeDataNotify, or the the UI won't correctly update.
@ -103,9 +103,9 @@ public class CookingManager {
} }
// Get result item information. // Get result item information.
int qualityIndex = int qualityIndex =
quality == 0 quality == 0
? 2 ? 2
: quality - 1; : quality - 1;
ItemParamData resultParam = recipeData.getQualityOutputVec().get(qualityIndex); ItemParamData resultParam = recipeData.getQualityOutputVec().get(qualityIndex);

View File

@ -4,7 +4,9 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.home.FurnitureMakeSlotItem; import emu.grasscutter.game.home.FurnitureMakeSlotItem;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.net.proto.ItemParamOuterClass; import emu.grasscutter.net.proto.ItemParamOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode; import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*; import emu.grasscutter.server.packet.send.*;
@ -13,42 +15,42 @@ import emu.grasscutter.utils.Utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class FurnitureManager { public class FurnitureManager extends BasePlayerManager {
private final Player player;
public FurnitureManager(Player player) { public FurnitureManager(Player player) {
this.player = player; super(player);
} }
public void onLogin(){ public void onLogin() {
notifyUnlockFurniture(); notifyUnlockFurniture();
notifyUnlockFurnitureSuite(); notifyUnlockFurnitureSuite();
} }
public void notifyUnlockFurniture(){ public void notifyUnlockFurniture() {
player.getSession().send(new PacketUnlockedFurnitureFormulaDataNotify(player.getUnlockedFurniture())); player.getSession().send(new PacketUnlockedFurnitureFormulaDataNotify(player.getUnlockedFurniture()));
} }
public void notifyUnlockFurnitureSuite(){ public void notifyUnlockFurnitureSuite() {
player.getSession().send(new PacketUnlockedFurnitureSuiteDataNotify(player.getUnlockedFurnitureSuite())); player.getSession().send(new PacketUnlockedFurnitureSuiteDataNotify(player.getUnlockedFurnitureSuite()));
} }
public synchronized boolean unlockFurnitureOrSuite(GameItem useItem){ public synchronized boolean unlockFurnitureOrSuite(GameItem useItem) {
ItemUseOp itemUseOp = useItem.getItemData().getItemUse().get(0).getUseOp();
// Check // Check
if (!List.of("ITEM_USE_UNLOCK_FURNITURE_FORMULA", "ITEM_USE_UNLOCK_FURNITURE_SUITE") if (itemUseOp != ItemUseOp.ITEM_USE_UNLOCK_FURNITURE_SUITE && itemUseOp != ItemUseOp.ITEM_USE_UNLOCK_FURNITURE_FORMULA) {
.contains(useItem.getItemData().getItemUse().get(0).getUseOp())) {
return false; return false;
} }
int furnitureIdOrSuiteId = Integer.parseInt(useItem.getItemData().getItemUse().get(0).getUseParam().get(0)); int furnitureIdOrSuiteId = Integer.parseInt(useItem.getItemData().getItemUse().get(0).getUseParam()[0]);
// Remove first // Remove first
player.getInventory().removeItem(useItem, 1); player.getInventory().removeItem(useItem, 1);
if("ITEM_USE_UNLOCK_FURNITURE_FORMULA".equals(useItem.getItemData().getItemUse().get(0).getUseOp())){ if (useItem.getItemData().getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_UNLOCK_FURNITURE_FORMULA) {
player.getUnlockedFurniture().add(furnitureIdOrSuiteId); player.getUnlockedFurniture().add(furnitureIdOrSuiteId);
notifyUnlockFurniture(); notifyUnlockFurniture();
}else{ }else {
player.getUnlockedFurnitureSuite().add(furnitureIdOrSuiteId); player.getUnlockedFurnitureSuite().add(furnitureIdOrSuiteId);
notifyUnlockFurnitureSuite(); notifyUnlockFurnitureSuite();
} }
@ -57,19 +59,19 @@ public class FurnitureManager {
public void startMake(int makeId, int avatarId) { public void startMake(int makeId, int avatarId) {
var makeData = GameData.getFurnitureMakeConfigDataMap().get(makeId); var makeData = GameData.getFurnitureMakeConfigDataMap().get(makeId);
if(makeData == null){ if (makeData == null) {
player.getSession().send(new PacketFurnitureMakeStartRsp(Retcode.RET_FURNITURE_MAKE_CONFIG_ERROR_VALUE, null)); player.getSession().send(new PacketFurnitureMakeStartRsp(Retcode.RET_FURNITURE_MAKE_CONFIG_ERROR_VALUE, null));
return; return;
} }
// check slot count // check slot count
if (player.getHome().getLevelData().getFurnitureMakeSlotCount() <= player.getHome().getFurnitureMakeSlotItemList().size()){ if (player.getHome().getLevelData().getFurnitureMakeSlotCount() <= player.getHome().getFurnitureMakeSlotItemList().size()) {
player.getSession().send(new PacketFurnitureMakeStartRsp(Retcode.RET_FURNITURE_MAKE_SLOT_FULL_VALUE, null)); player.getSession().send(new PacketFurnitureMakeStartRsp(Retcode.RET_FURNITURE_MAKE_SLOT_FULL_VALUE, null));
return; return;
} }
// pay items first // pay items first
if(!player.getInventory().payItems(makeData.getMaterialItems().toArray(new ItemParamData[0]))){ if (!player.getInventory().payItems(makeData.getMaterialItems().toArray(new ItemParamData[0]))) {
player.getSession().send(new PacketFurnitureMakeStartRsp(Retcode.RET_HOME_FURNITURE_COUNT_NOT_ENOUGH_VALUE, null)); player.getSession().send(new PacketFurnitureMakeStartRsp(Retcode.RET_HOME_FURNITURE_COUNT_NOT_ENOUGH_VALUE, null));
return; return;
} }
@ -93,7 +95,7 @@ public class FurnitureManager {
} }
public void queryStatus() { public void queryStatus() {
if (player.getHome().getFurnitureMakeSlotItemList() == null){ if (player.getHome().getFurnitureMakeSlotItemList() == null) {
player.getHome().setFurnitureMakeSlotItemList(new ArrayList<>()); player.getHome().setFurnitureMakeSlotItemList(new ArrayList<>());
} }
@ -103,7 +105,7 @@ public class FurnitureManager {
public void take(int index, int makeId, boolean isFastFinish) { public void take(int index, int makeId, boolean isFastFinish) {
var makeData = GameData.getFurnitureMakeConfigDataMap().get(makeId); var makeData = GameData.getFurnitureMakeConfigDataMap().get(makeId);
if(makeData == null){ if (makeData == null) {
player.getSession().send(new PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_CONFIG_ERROR_VALUE, makeId, null, null)); player.getSession().send(new PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_CONFIG_ERROR_VALUE, makeId, null, null));
return; return;
} }
@ -112,19 +114,19 @@ public class FurnitureManager {
.filter(x -> x.getIndex() == index && x.getMakeId() == makeId) .filter(x -> x.getIndex() == index && x.getMakeId() == makeId)
.findFirst(); .findFirst();
if(slotItem.isEmpty()){ if (slotItem.isEmpty()) {
player.getSession().send(new PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_NO_MAKE_DATA_VALUE, makeId, null, null)); player.getSession().send(new PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_NO_MAKE_DATA_VALUE, makeId, null, null));
return; return;
} }
// pay the speedup item // pay the speedup item
if(isFastFinish && !player.getInventory().payItem(107013,1)){ if (isFastFinish && !player.getInventory().payItem(107013,1)) {
player.getSession().send(new PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_UNFINISH_VALUE, makeId, null, null)); player.getSession().send(new PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_UNFINISH_VALUE, makeId, null, null));
return; return;
} }
// check if player can take // check if player can take
// if(slotItem.get().getBeginTime() + slotItem.get().getDurTime() >= Utils.getCurrentSeconds() && !isFastFinish){ // if (slotItem.get().getBeginTime() + slotItem.get().getDurTime() >= Utils.getCurrentSeconds() && !isFastFinish) {
// player.getSession().send(new PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_UNFINISH_VALUE, makeId, null, null)); // player.getSession().send(new PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_UNFINISH_VALUE, makeId, null, null));
// return; // return;
// } // }

View File

@ -1,44 +0,0 @@
package emu.grasscutter.game.managers;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.EnvAnimalGatherConfigData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.EntityVehicle;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
public record InsectCaptureManager(Player player) {
public void arrestSmallCreature(GameEntity entity) {
//System.out.println("arrestSmallCreature!");
EnvAnimalGatherConfigData gather;
int thingId;
if (entity instanceof EntityMonster monster) {
thingId = monster.getMonsterData().getId();
gather = GameData.getEnvAnimalGatherConfigDataMap().get(thingId);
} else if (entity instanceof EntityVehicle gadget) {
thingId = gadget.getGadgetId();
gather = GameData.getEnvAnimalGatherConfigDataMap().get(thingId);
} else {
return;
}
if (gather == null) {
Grasscutter.getLogger().warn("monster/gather(id={}) couldn't be caught.", thingId);
return;
}
String type = gather.getEntityType();
if ((type.equals("Monster") && entity instanceof EntityMonster) || (type.equals("Gadget") && entity instanceof EntityVehicle)) {
EnvAnimalGatherConfigData.GatherItem gatherItem = gather.gatherItem();
ItemData data = GameData.getItemDataMap().get(gatherItem.getId());
GameItem item = new GameItem(data, gatherItem.getCount());
player.getInventory().addItem(item, ActionReason.SubfieldDrop);
entity.getScene().removeEntity(entity, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
} else {
Grasscutter.getLogger().warn("monster/gather(id={}) has a wrong type.", thingId);
}
}
}

View File

@ -1,961 +0,0 @@
package emu.grasscutter.game.managers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.OpenConfigEntry;
import emu.grasscutter.data.binout.OpenConfigEntry.SkillPointModifier;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.AvatarPromoteData;
import emu.grasscutter.data.excels.AvatarSkillData;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.data.excels.AvatarTalentData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.ProudSkillData;
import emu.grasscutter.data.excels.WeaponPromoteData;
import emu.grasscutter.data.excels.AvatarSkillDepotData.InherentProudSkillOpens;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.shop.ShopChestBatchUseTable;
import emu.grasscutter.game.shop.ShopChestTable;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.MaterialInfoOuterClass.MaterialInfo;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
public class InventoryManager {
private final GameServer server;
private final static int RELIC_MATERIAL_1 = 105002; // Sanctifying Unction
private final static int RELIC_MATERIAL_2 = 105003; // Sanctifying Essence
private final static int RELIC_MATERIAL_EXP_1 = 2500; // Sanctifying Unction
private final static int RELIC_MATERIAL_EXP_2 = 10000; // Sanctifying Essence
private final static int WEAPON_ORE_1 = 104011; // Enhancement Ore
private final static int WEAPON_ORE_2 = 104012; // Fine Enhancement Ore
private final static int WEAPON_ORE_3 = 104013; // Mystic Enhancement Ore
private final static int WEAPON_ORE_EXP_1 = 400; // Enhancement Ore
private final static int WEAPON_ORE_EXP_2 = 2000; // Fine Enhancement Ore
private final static int WEAPON_ORE_EXP_3 = 10000; // Mystic Enhancement Ore
private final static int AVATAR_BOOK_1 = 104001; // Wanderer's Advice
private final static int AVATAR_BOOK_2 = 104002; // Adventurer's Experience
private final static int AVATAR_BOOK_3 = 104003; // Hero's Wit
private final static int AVATAR_BOOK_EXP_1 = 1000; // Wanderer's Advice
private final static int AVATAR_BOOK_EXP_2 = 5000; // Adventurer's Experience
private final static int AVATAR_BOOK_EXP_3 = 20000; // Hero's Wit
public InventoryManager(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
public void lockEquip(Player player, long targetEquipGuid, boolean isLocked) {
GameItem equip = player.getInventory().getItemByGuid(targetEquipGuid);
if (equip == null || !equip.getItemData().isEquip()) {
return;
}
equip.setLocked(isLocked);
equip.save();
player.sendPacket(new PacketStoreItemChangeNotify(equip));
player.sendPacket(new PacketSetEquipLockStateRsp(equip));
}
public void upgradeRelic(Player player, long targetGuid, List<Long> foodRelicList, List<ItemParam> list) {
GameItem relic = player.getInventory().getItemByGuid(targetGuid);
if (relic == null || relic.getItemType() != ItemType.ITEM_RELIQUARY) {
return;
}
int moraCost = 0;
int expGain = 0;
List<GameItem> foodRelics = new ArrayList<GameItem>();
for (long guid : foodRelicList) {
// Add to delete queue
GameItem food = player.getInventory().getItemByGuid(guid);
if (food == null || !food.isDestroyable()) {
continue;
}
// Calculate mora cost
moraCost += food.getItemData().getBaseConvExp();
expGain += food.getItemData().getBaseConvExp();
// Feeding artifact with exp already
if (food.getTotalExp() > 0) {
expGain += (food.getTotalExp() * 4) / 5;
}
foodRelics.add(food);
}
List<ItemParamData> payList = new ArrayList<ItemParamData>();
for (ItemParam itemParam : list) {
int amount = itemParam.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order
int gain = amount * switch(itemParam.getItemId()) {
case RELIC_MATERIAL_1 -> RELIC_MATERIAL_EXP_1;
case RELIC_MATERIAL_2 -> RELIC_MATERIAL_EXP_2;
default -> 0;
};
expGain += gain;
moraCost += gain;
payList.add(new ItemParamData(itemParam.getItemId(), itemParam.getCount()));
}
// Make sure exp gain is valid
if (expGain <= 0) {
return;
}
// Confirm payment of materials and mora (assume food relics are payable afterwards)
payList.add(new ItemParamData(202, moraCost));
if (!player.getInventory().payItems(payList.toArray(new ItemParamData[0]))) {
return;
}
// Consume food relics
player.getInventory().removeItems(foodRelics);
// Implement random rate boost
int rate = 1;
int boost = Utils.randomRange(1, 100);
if (boost == 100) {
rate = 5;
} else if (boost <= 9) {
rate = 2;
}
expGain *= rate;
// Now we upgrade
int level = relic.getLevel();
int oldLevel = level;
int exp = relic.getExp();
int totalExp = relic.getTotalExp();
int reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
int upgrades = 0;
List<Integer> oldAppendPropIdList = new ArrayList<>(relic.getAppendPropIdList());
while (expGain > 0 && reqExp > 0 && level < relic.getItemData().getMaxLevel()) {
// Do calculations
int toGain = Math.min(expGain, reqExp - exp);
exp += toGain;
totalExp += toGain;
expGain -= toGain;
// Level up
if (exp >= reqExp) {
// Exp
exp = 0;
level += 1;
// On relic levelup
if (relic.getItemData().getAddPropLevelSet() != null && relic.getItemData().getAddPropLevelSet().contains(level)) {
upgrades += 1;
}
// Set req exp
reqExp = GameData.getRelicExpRequired(relic.getItemData().getRankLevel(), level);
}
}
relic.addAppendProps(upgrades);
// Save
relic.setLevel(level);
relic.setExp(exp);
relic.setTotalExp(totalExp);
relic.save();
// Avatar
if (oldLevel != level) {
Avatar avatar = relic.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(relic.getEquipCharacter()) : null;
if (avatar != null) {
avatar.recalcStats();
}
}
// Packet
player.sendPacket(new PacketStoreItemChangeNotify(relic));
player.sendPacket(new PacketReliquaryUpgradeRsp(relic, rate, oldLevel, oldAppendPropIdList));
}
public List<ItemParam> calcWeaponUpgradeReturnItems(Player player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) {
GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
// Sanity checks
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
return null;
}
WeaponPromoteData promoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
if (promoteData == null) {
return null;
}
// Get exp gain
int expGain = 0;
for (long guid : foodWeaponGuidList) {
GameItem food = player.getInventory().getItemByGuid(guid);
if (food == null) {
continue;
}
expGain += food.getItemData().getWeaponBaseExp();
if (food.getTotalExp() > 0) {
expGain += (food.getTotalExp() * 4) / 5;
}
}
for (ItemParam param : itemParamList) {
expGain += param.getCount() * switch(param.getItemId()) {
case WEAPON_ORE_1 -> WEAPON_ORE_EXP_1;
case WEAPON_ORE_2 -> WEAPON_ORE_EXP_2;
case WEAPON_ORE_3 -> WEAPON_ORE_EXP_3;
default -> 0;
};
}
// Try
int maxLevel = promoteData.getUnlockMaxLevel();
int level = weapon.getLevel();
int exp = weapon.getExp();
int reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
// Do calculations
int toGain = Math.min(expGain, reqExp - exp);
exp += toGain;
expGain -= toGain;
// Level up
if (exp >= reqExp) {
// Exp
exp = 0;
level += 1;
// Set req exp
reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
}
}
return getLeftoverOres(expGain);
}
public void upgradeWeapon(Player player, long targetGuid, List<Long> foodWeaponGuidList, List<ItemParam> itemParamList) {
GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
// Sanity checks
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
return;
}
WeaponPromoteData promoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
if (promoteData == null) {
return;
}
// Get exp gain
int expGain = 0, expGainFree = 0;
List<GameItem> foodWeapons = new ArrayList<GameItem>();
for (long guid : foodWeaponGuidList) {
GameItem food = player.getInventory().getItemByGuid(guid);
if (food == null || !food.isDestroyable()) {
continue;
}
expGain += food.getItemData().getWeaponBaseExp();
if (food.getTotalExp() > 0) {
expGainFree += (food.getTotalExp() * 4) / 5; // No tax :D
}
foodWeapons.add(food);
}
List<ItemParamData> payList = new ArrayList<ItemParamData>();
for (ItemParam param : itemParamList) {
int amount = param.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order
int gain = amount * switch(param.getItemId()) {
case WEAPON_ORE_1 -> WEAPON_ORE_EXP_1;
case WEAPON_ORE_2 -> WEAPON_ORE_EXP_2;
case WEAPON_ORE_3 -> WEAPON_ORE_EXP_3;
default -> 0;
};
expGain += gain;
payList.add(new ItemParamData(param.getItemId(), amount));
}
// Make sure exp gain is valid
int moraCost = expGain / 10;
expGain += expGainFree;
if (expGain <= 0) {
return;
}
// Confirm payment of materials and mora (assume food weapons are payable afterwards)
payList.add(new ItemParamData(202, moraCost));
if (!player.getInventory().payItems(payList.toArray(new ItemParamData[0]))) {
return;
}
player.getInventory().removeItems(foodWeapons);
// Level up
int maxLevel = promoteData.getUnlockMaxLevel();
int level = weapon.getLevel();
int oldLevel = level;
int exp = weapon.getExp();
int totalExp = weapon.getTotalExp();
int reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
// Do calculations
int toGain = Math.min(expGain, reqExp - exp);
exp += toGain;
totalExp += toGain;
expGain -= toGain;
// Level up
if (exp >= reqExp) {
// Exp
exp = 0;
level += 1;
// Set req exp
reqExp = GameData.getWeaponExpRequired(weapon.getItemData().getRankLevel(), level);
}
}
List<ItemParam> leftovers = getLeftoverOres(expGain);
player.getInventory().addItemParams(leftovers);
weapon.setLevel(level);
weapon.setExp(exp);
weapon.setTotalExp(totalExp);
weapon.save();
// Avatar
if (oldLevel != level) {
Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
if (avatar != null) {
avatar.recalcStats();
}
}
// Packets
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
player.sendPacket(new PacketWeaponUpgradeRsp(weapon, oldLevel, leftovers));
}
private List<ItemParam> getLeftoverOres(int leftover) {
List<ItemParam> leftoverOreList = new ArrayList<>(3);
if (leftover < WEAPON_ORE_EXP_1) {
return leftoverOreList;
}
// Get leftovers
int ore3 = leftover / WEAPON_ORE_EXP_3;
leftover = leftover % WEAPON_ORE_EXP_3;
int ore2 = leftover / WEAPON_ORE_EXP_2;
leftover = leftover % WEAPON_ORE_EXP_2;
int ore1 = leftover / WEAPON_ORE_EXP_1;
if (ore3 > 0) {
leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_3).setCount(ore3).build());
} if (ore2 > 0) {
leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_2).setCount(ore2).build());
} if (ore1 > 0) {
leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_1).setCount(ore1).build());
}
return leftoverOreList;
}
public void refineWeapon(Player player, long targetGuid, long feedGuid) {
GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
GameItem feed = player.getInventory().getItemByGuid(feedGuid);
// Sanity checks
if (weapon == null || feed == null || !feed.isDestroyable()) {
return;
}
if (weapon.getItemData().getAwakenMaterial() == 0) {
if (weapon.getItemType() != ItemType.ITEM_WEAPON || weapon.getItemId() != feed.getItemId()) {
return;
}
} else {
if (weapon.getItemType() != ItemType.ITEM_WEAPON || weapon.getItemData().getAwakenMaterial() != feed.getItemId()) {
return;
}
}
if (weapon.getRefinement() >= 4 || weapon.getAffixes() == null || weapon.getAffixes().size() == 0) {
return;
}
// Calculate
int oldRefineLevel = weapon.getRefinement();
int targetRefineLevel = Math.min(oldRefineLevel + feed.getRefinement() + 1, 4);
int moraCost = 0;
try {
moraCost = weapon.getItemData().getAwakenCosts()[weapon.getRefinement()];
} catch (Exception e) {
return;
}
// Mora check
if (player.getMora() >= moraCost) {
player.setMora(player.getMora() - moraCost);
} else {
return;
}
// Consume weapon
player.getInventory().removeItem(feed, 1);
// Get
weapon.setRefinement(targetRefineLevel);
weapon.save();
// Avatar
Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
if (avatar != null) {
avatar.recalcStats();
}
// Packets
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
player.sendPacket(new PacketWeaponAwakenRsp(avatar, weapon, feed, oldRefineLevel));
}
public void promoteWeapon(Player player, long targetGuid) {
GameItem weapon = player.getInventory().getItemByGuid(targetGuid);
if (weapon == null || weapon.getItemType() != ItemType.ITEM_WEAPON) {
return;
}
int nextPromoteLevel = weapon.getPromoteLevel() + 1;
WeaponPromoteData currentPromoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
WeaponPromoteData nextPromoteData = GameData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), nextPromoteLevel);
if (currentPromoteData == null || nextPromoteData == null) {
return;
}
// Level check
if (weapon.getLevel() != currentPromoteData.getUnlockMaxLevel()) {
return;
}
// Pay materials and mora if possible
ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null?
if (nextPromoteData.getCoinCost() > 0) {
costs = Arrays.copyOf(costs, costs.length + 1);
costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost());
}
if (!player.getInventory().payItems(costs)) {
return;
}
int oldPromoteLevel = weapon.getPromoteLevel();
weapon.setPromoteLevel(nextPromoteLevel);
weapon.save();
// Avatar
Avatar avatar = weapon.getEquipCharacter() > 0 ? player.getAvatars().getAvatarById(weapon.getEquipCharacter()) : null;
if (avatar != null) {
avatar.recalcStats();
}
// Packets
player.sendPacket(new PacketStoreItemChangeNotify(weapon));
player.sendPacket(new PacketWeaponPromoteRsp(weapon, oldPromoteLevel));
}
public void promoteAvatar(Player player, long guid) {
Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
// Sanity checks
if (avatar == null) {
return;
}
int nextPromoteLevel = avatar.getPromoteLevel() + 1;
AvatarPromoteData currentPromoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
AvatarPromoteData nextPromoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), nextPromoteLevel);
if (currentPromoteData == null || nextPromoteData == null) {
return;
}
// Level check
if (avatar.getLevel() != currentPromoteData.getUnlockMaxLevel()) {
return;
}
// Pay materials and mora if possible
ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null?
if (nextPromoteData.getCoinCost() > 0) {
costs = Arrays.copyOf(costs, costs.length + 1);
costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost());
}
if (!player.getInventory().payItems(costs)) {
return;
}
// Update promote level
avatar.setPromoteLevel(nextPromoteLevel);
// Update proud skills
AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) {
for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) {
if (openData.getProudSkillGroupId() == 0) {
continue;
}
if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) {
int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1;
if (GameData.getProudSkillDataMap().containsKey(proudSkillId)) {
avatar.getProudSkillList().add(proudSkillId);
player.sendPacket(new PacketProudSkillChangeNotify(avatar));
}
}
}
}
// Packets
player.sendPacket(new PacketAvatarPropNotify(avatar));
player.sendPacket(new PacketAvatarPromoteRsp(avatar));
// TODO Send entity prop update packet to world
avatar.recalcStats(true);
avatar.save();
}
public void upgradeAvatar(Player player, long guid, int itemId, int count) {
Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
// Sanity checks
if (avatar == null) {
return;
}
AvatarPromoteData promoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
if (promoteData == null) {
return;
}
// Calc exp
int expGain = switch(itemId) {
case AVATAR_BOOK_1 -> AVATAR_BOOK_EXP_1 * count;
case AVATAR_BOOK_2 -> AVATAR_BOOK_EXP_2 * count;
case AVATAR_BOOK_3 -> AVATAR_BOOK_EXP_3 * count;
default -> 0;
};
// Sanity check
if (expGain <= 0) {
return;
}
// Payment check
int moraCost = expGain / 5;
ItemParamData[] costItems = new ItemParamData[] {new ItemParamData(itemId, count), new ItemParamData(202, moraCost)};
if (!player.getInventory().payItems(costItems)) {
return;
}
// Level up
upgradeAvatar(player, avatar, promoteData, expGain);
}
public void upgradeAvatar(Player player, Avatar avatar, int expGain) {
AvatarPromoteData promoteData = GameData.getAvatarPromoteData(avatar.getAvatarData().getAvatarPromoteId(), avatar.getPromoteLevel());
if (promoteData == null) {
return;
}
upgradeAvatar(player, avatar, promoteData, expGain);
}
public void upgradeAvatar(Player player, Avatar avatar, AvatarPromoteData promoteData, int expGain) {
int maxLevel = promoteData.getUnlockMaxLevel();
int level = avatar.getLevel();
int oldLevel = level;
int exp = avatar.getExp();
int reqExp = GameData.getAvatarLevelExpRequired(level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
// Do calculations
int toGain = Math.min(expGain, reqExp - exp);
exp += toGain;
expGain -= toGain;
// Level up
if (exp >= reqExp) {
// Exp
exp = 0;
level += 1;
// Set req exp
reqExp = GameData.getAvatarLevelExpRequired(level);
}
}
// Old map for packet
Map<Integer, Float> oldPropMap = avatar.getFightProperties();
if (oldLevel != level) {
// Deep copy if level has changed
oldPropMap = avatar.getFightProperties().int2FloatEntrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
// Done
avatar.setLevel(level);
avatar.setExp(exp);
avatar.recalcStats();
avatar.save();
// TODO Send entity prop update packet to world
// Packets
player.sendPacket(new PacketAvatarPropNotify(avatar));
player.sendPacket(new PacketAvatarUpgradeRsp(avatar, oldLevel, oldPropMap));
}
public void upgradeAvatarFetterLevel(Player player, Avatar avatar, int expGain) {
// May work. Not test.
int maxLevel = 10; // Keep it until I think of a more "elegant" way
int level = avatar.getFetterLevel();
int exp = avatar.getFetterExp();
int reqExp = GameData.getAvatarFetterLevelExpRequired(level);
while (expGain > 0 && reqExp > 0 && level < maxLevel) {
int toGain = Math.min(expGain, reqExp - exp);
exp += toGain;
expGain -= toGain;
if (exp >= reqExp) {
exp = 0;
level += 1;
reqExp = GameData.getAvatarFetterLevelExpRequired(level);
}
}
avatar.setFetterLevel(level);
avatar.setFetterExp(exp);
avatar.save();
player.sendPacket(new PacketAvatarPropNotify(avatar));
player.sendPacket(new PacketAvatarFetterDataNotify(avatar));
}
public void upgradeAvatarSkill(Player player, long guid, int skillId) {
// Sanity checks
Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
if (avatar == null) {
return;
}
// Make sure avatar has skill
if (!avatar.getSkillLevelMap().containsKey(skillId)) {
return;
}
AvatarSkillData skillData = GameData.getAvatarSkillDataMap().get(skillId);
if (skillData == null) {
return;
}
// Get data for next skill level
int currentLevel = avatar.getSkillLevelMap().get(skillId);
int nextLevel = currentLevel + 1;
int proudSkillId = (skillData.getProudSkillGroupId() * 100) + nextLevel;
// Capped at level 10 talent
if (nextLevel > 10) {
return;
}
// Proud skill data
ProudSkillData proudSkill = GameData.getProudSkillDataMap().get(proudSkillId);
if (proudSkill == null) {
return;
}
// Make sure break level is correct
if (avatar.getPromoteLevel() < proudSkill.getBreakLevel()) {
return;
}
// Pay materials and mora if possible
List<ItemParamData> costs = new ArrayList<ItemParamData>(proudSkill.getCostItems()); // Can this be null?
if (proudSkill.getCoinCost() > 0) {
costs.add(new ItemParamData(202, proudSkill.getCoinCost()));
}
if (!player.getInventory().payItems(costs.toArray(new ItemParamData[0]))) {
return;
}
// Upgrade skill
avatar.getSkillLevelMap().put(skillId, nextLevel);
avatar.save();
// Packet
player.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillId, currentLevel, nextLevel));
player.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillId, currentLevel, nextLevel));
}
public void unlockAvatarConstellation(Player player, long guid) {
// Sanity checks
Avatar avatar = player.getAvatars().getAvatarByGuid(guid);
if (avatar == null) {
return;
}
// Get talent
int currentTalentLevel = avatar.getCoreProudSkillLevel();
int nextTalentId = ((avatar.getAvatarId() % 10000000) * 10) + currentTalentLevel + 1;
if (avatar.getAvatarId() == 10000006) {
// Lisa is special in that her talentId starts with 4 instead of 6.
nextTalentId = 40 + currentTalentLevel + 1;
}
AvatarTalentData talentData = GameData.getAvatarTalentDataMap().get(nextTalentId);
if (talentData == null) {
return;
}
// Pay constellation item if possible
if (!player.getInventory().payItem(talentData.getMainCostItemId(), 1)) {
return;
}
// Apply + recalc
avatar.getTalentIdList().add(talentData.getId());
avatar.setCoreProudSkillLevel(currentTalentLevel + 1);
// Packet
player.sendPacket(new PacketAvatarUnlockTalentNotify(avatar, nextTalentId));
player.sendPacket(new PacketUnlockAvatarTalentRsp(avatar, nextTalentId));
// Proud skill bonus map (Extra skills)
OpenConfigEntry entry = GameData.getOpenConfigEntries().get(talentData.getOpenConfig());
if (entry != null) {
if (entry.getExtraTalentIndex() > 0) {
// Check if new constellation adds +3 to a skill level
avatar.recalcConstellations();
// Packet
player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex()));
} else if (entry.getSkillPointModifiers() != null) {
// Check if new constellation adds skill charges
avatar.recalcConstellations();
// Packet
for (SkillPointModifier mod : entry.getSkillPointModifiers()) {
player.sendPacket(
new PacketAvatarSkillMaxChargeCountNotify(avatar, mod.getSkillId(), avatar.getSkillExtraChargeMap().getOrDefault(mod.getSkillId(), 0))
);
}
}
}
// Recalc + save avatar
avatar.recalcStats(true);
avatar.save();
}
public void destroyMaterial(Player player, List<MaterialInfo> list) {
// Return materials
Int2IntOpenHashMap returnMaterialMap = new Int2IntOpenHashMap();
for (MaterialInfo info : list) {
// Sanity check
if (info.getCount() <= 0) {
continue;
}
GameItem item = player.getInventory().getItemByGuid(info.getGuid());
if (item == null || !item.isDestroyable()) {
continue;
}
// Remove
int removeAmount = Math.min(info.getCount(), item.getCount());
player.getInventory().removeItem(item, removeAmount);
// Delete material return items
if (item.getItemData().getDestroyReturnMaterial().length > 0) {
for (int i = 0; i < item.getItemData().getDestroyReturnMaterial().length; i++) {
returnMaterialMap.addTo(item.getItemData().getDestroyReturnMaterial()[i], item.getItemData().getDestroyReturnMaterialCount()[i]);
}
}
}
// Give back items
if (returnMaterialMap.size() > 0) {
for (Int2IntMap.Entry e : returnMaterialMap.int2IntEntrySet()) {
player.getInventory().addItem(new GameItem(e.getIntKey(), e.getIntValue()));
}
}
// Packets
player.sendPacket(new PacketDestroyMaterialRsp(returnMaterialMap));
}
public GameItem useItem(Player player, long targetGuid, long itemGuid, int count, int optionId) {
Avatar target = player.getAvatars().getAvatarByGuid(targetGuid);
GameItem useItem = player.getInventory().getItemByGuid(itemGuid);
if (useItem == null) {
return null;
}
int used = 0;
boolean useSuccess = false;
// Use
switch (useItem.getItemData().getMaterialType()) {
case MATERIAL_FOOD:
if (useItem.getItemData().getUseTarget().equals("ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR")) {
if (target == null) {
break;
}
used = player.getTeamManager().reviveAvatar(target) ? 1 : 0;
}
break;
case MATERIAL_NOTICE_ADD_HP:
if (useItem.getItemData().getUseTarget().equals("ITEM_USE_TARGET_SPECIFY_ALIVE_AVATAR")) {
if (target == null) {
break;
}
int[] SatiationParams = useItem.getItemData().getSatiationParams();
used = player.getTeamManager().healAvatar(target, SatiationParams[0], SatiationParams[1]) ? 1 : 0;
}
break;
case MATERIAL_CONSUME:
// Make sure we have usage data for this material.
if (useItem.getItemData().getItemUse() == null) {
break;
}
// Handle forging blueprints.
if (useItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_FORGE")) {
// Unlock.
useSuccess = player.getForgingManager().unlockForgingBlueprint(useItem);
}
// Handle combine diagrams.
if (useItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_COMBINE")) {
// Unlock.
useSuccess = player.getServer().getCombineManger().unlockCombineDiagram(player, useItem);
}
// Handle cooking recipies.
if (useItem.getItemData().getItemUse().get(0).getUseOp().equals("ITEM_USE_UNLOCK_COOK_RECIPE")) {
// Unlock.
useSuccess = player.getCookingManager().unlockRecipe(useItem);
}
break;
case MATERIAL_FURNITURE_FORMULA:
case MATERIAL_FURNITURE_SUITE_FORMULA:
if (useItem.getItemData().getItemUse() == null) {
break;
}
useSuccess = player.getFurnitureManager().unlockFurnitureOrSuite(useItem);
break;
case MATERIAL_CONSUME_BATCH_USE:
// Make sure we have usage data for this material.
if (useItem.getItemData().getItemUse() == null) {
break;
}
// Handle fragile/transient resin.
if (useItem.getItemId() == 107009 || useItem.getItemId() == 107012){
// Add resin to the inventory.
ItemData resinItemData = GameData.getItemDataMap().get(106);
player.getInventory().addItem(new GameItem(resinItemData, 60 * count), ActionReason.PlayerUseItem);
// Set used amount.
used = count;
}
break;
case MATERIAL_CHEST:
List<ShopChestTable> shopChestTableList = player.getServer().getShopManager().getShopChestData();
List<GameItem> rewardItemList = new ArrayList<>();
for (ShopChestTable shopChestTable : shopChestTableList) {
if (shopChestTable.getItemId() != useItem.getItemId()) {
continue;
}
if (shopChestTable.getContainsItem() == null) {
break;
}
for (ItemParamData itemParamData : shopChestTable.getContainsItem()) {
ItemData itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
continue;
}
rewardItemList.add(new GameItem(itemData, itemParamData.getCount()));
}
if (!rewardItemList.isEmpty()) {
player.getInventory().addItems(rewardItemList, ActionReason.Shop);
}
used = 1;
break;
}
break;
case MATERIAL_CHEST_BATCH_USE:
if (optionId < 1) {
break;
}
List<ShopChestBatchUseTable> shopChestBatchUseTableList = player.getServer().getShopManager().getShopChestBatchUseData();
for (ShopChestBatchUseTable shopChestBatchUseTable : shopChestBatchUseTableList) {
if (shopChestBatchUseTable.getItemId() != useItem.getItemId()) {
continue;
}
if (shopChestBatchUseTable.getOptionItem() == null || optionId > shopChestBatchUseTable.getOptionItem().size()) {
break;
}
int optionItemId = shopChestBatchUseTable.getOptionItem().get(optionId - 1);
ItemData itemData = GameData.getItemDataMap().get(optionItemId);
if (itemData == null) {
break;
}
player.getInventory().addItem(new GameItem(itemData, count), ActionReason.Shop);
used = count;
break;
}
break;
default:
break;
}
// Welkin
if (useItem.getItemId() == 1202) {
player.rechargeMoonCard();
used = 1;
}
// If we used at least one item, or one of the methods called here reports using the item successfully,
// we return the item to make UseItemRsp a success.
if (used > 0) {
player.getInventory().removeItem(useItem, used);
return useItem;
}
if (useSuccess) {
return useItem;
}
return null;
}
}

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