Merge pull request #7 from Grasscutters/development

Development
This commit is contained in:
Akka 2022-05-08 20:54:42 +08:00 committed by GitHub
commit 65861c3c22
105 changed files with 4790 additions and 1707 deletions

View File

@ -31,4 +31,4 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: Grasscutter
path: grasscutter-*-dev.jar
path: grasscutter-*.jar

3
.gitignore vendored
View File

@ -67,4 +67,5 @@ mongod.exe
/*.sh
language/
languages/
gacha_mappings.js
gacha-mapping.js
data/gacha_mappings.js

View File

@ -28,7 +28,7 @@ plugins {
// Eclipse Support
id 'eclipse'
// Intelij Support
// IntelliJ Support
id 'idea'
// Maven
@ -43,7 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
group = 'xyz.grasscutters'
version = '1.0.3-dev'
version = '1.1.0'
sourceCompatibility = 17
targetCompatibility = 17
@ -228,4 +228,4 @@ javadoc {
processResources {
dependsOn "generateProto"
}
}

1072
data/ExpeditionReward.json Normal file

File diff suppressed because it is too large Load Diff

49
plugin-schema.json Normal file
View File

@ -0,0 +1,49 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "JSON schema for a Grasscutter Plugin",
"type": "object",
"additionalProperties": true,
"definitions": {
"plugin-name": {
"type": "string",
"pattern": "^[A-Za-z\\d_.-]+$"
}
},
"required": [ "name", "description", "mainClass" ],
"properties": {
"name": {
"description": "The unique name of plugin.",
"$ref": "#/definitions/plugin-name"
},
"mainClass": {
"description": "The plugin's initial class file.",
"type": "string",
"pattern": "^(?!org\\.bukkit\\.)([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*$"
},
"version": {
"description": "A plugin revision identifier.",
"type": [ "string", "number" ]
},
"description": {
"description": "Human readable plugin summary.",
"type": "string"
},
"author": {
"description": "The plugin author.",
"type": "string"
},
"authors": {
"description": "The plugin contributors.",
"type": "array",
"items": {
"type": "string"
}
},
"website": {
"title": "Website",
"description": "The URL to the plugin's site",
"type": "string",
"format": "uri"
}
}
}

View File

@ -1,4 +1,17 @@
import os
# This can also be replaced with another IP address.
USE_SSL = True
REMOTE_HOST = "127.0.0.1"
REMOTE_PORT = 443
REMOTE_HOST = "localhost"
REMOTE_PORT = 443
if os.getenv('MITM_REMOTE_HOST') != None:
REMOTE_HOST = os.getenv('MITM_REMOTE_HOST')
if os.getenv('MITM_REMOTE_PORT') != None:
REMOTE_PORT = int(os.getenv('MITM_REMOTE_PORT'))
if os.getenv('MITM_USE_SSL') != None:
USE_SSL = bool(os.getenv('MITM_USE_SSL'))
print('MITM Remote Host: ' + REMOTE_HOST)
print('MITM Remote Port: ' + str(REMOTE_PORT))
print('MITM Use SSL ' + str(USE_SSL))

View File

@ -6,7 +6,6 @@ import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.game.mail.Mail;
public final class Config {
public String DatabaseUrl = "mongodb://localhost:27017";
public String DatabaseCollection = "grasscutter";
@ -24,6 +23,7 @@ public final class Config {
public GameServerOptions GameServer = new GameServerOptions();
public DispatchServerOptions DispatchServer = new DispatchServerOptions();
public Locale LocaleLanguage = Locale.getDefault();
public Locale DefaultLanguage = Locale.ENGLISH;
public Boolean OpenStamina = true;
public GameServerOptions getGameServerOptions() {
@ -41,6 +41,8 @@ public final class Config {
public String KeystorePassword = "123456";
public Boolean UseSSL = true;
public Boolean FrontHTTPS = true;
public Boolean CORS = false;
public String[] CORSAllowedOrigins = new String[] { "*" };
public boolean AutomaticallyCreateAccounts = false;
public String[] defaultPermissions = new String[] { "" };
@ -80,6 +82,10 @@ public final class Config {
public boolean WatchGacha = false;
public String ServerNickname = "Server";
public int ServerAvatarId = 10000007;
public int ServerNameCardId = 210001;
public int ServerLevel = 1;
public int ServerWorldLevel = 1;
public String ServerSignature = "Server Signature";
public int[] WelcomeEmotes = {2007, 1002, 4010};
public String WelcomeMotd = "Welcome to Grasscutter emu";
public String WelcomeMailTitle = "Welcome to Grasscutter!";

View File

@ -11,8 +11,6 @@ public final class GameConstants {
public static final int MAX_TEAMS = 4;
public static final int MAIN_CHARACTER_MALE = 10000005;
public static final int MAIN_CHARACTER_FEMALE = 10000007;
public static final String SERVER_AVATAR_NAME = Grasscutter.getConfig().getGameServerOptions().ServerNickname;
public static final int SERVER_AVATAR_ID = Grasscutter.getConfig().getGameServerOptions().ServerAvatarId;
public static final Position START_POSITION = new Position(2747, 194, -1719);
public static final int MAX_FRIENDS = 45;

View File

@ -4,9 +4,7 @@ import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOError;
import java.net.InetSocketAddress;
import java.util.Calendar;
import java.util.Locale;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.plugin.PluginManager;
@ -28,31 +26,32 @@ import com.google.gson.GsonBuilder;
import ch.qos.logback.classic.Logger;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.languages.CNLanguage;
import emu.grasscutter.languages.Language;
import emu.grasscutter.utils.Language;
import emu.grasscutter.server.dispatch.DispatchServer;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.Crypto;
import static emu.grasscutter.utils.Language.translate;
public final class Grasscutter {
private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class);
private static Config config;
private static LineReader consoleLineReader = null;
private static Config config;
private static Language language;
private static CNLanguage cn_language;
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static final File configFile = new File("./config.json");
private static int day; // Current day of week
private static int day; // Current day of week.
private static DispatchServer dispatchServer;
private static GameServer gameServer;
private static PluginManager pluginManager;
public static final Reflections reflector = new Reflections("emu.grasscutter");
static {
// Declare logback configuration.
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
@ -60,46 +59,50 @@ public final class Grasscutter {
// Load server configuration.
Grasscutter.loadConfig();
// Load Language
// Load translation files.
Grasscutter.loadLanguage();
// Check server structure.
Utils.startupCheck();
}
public static void main(String[] args) throws Exception {
Crypto.loadKeys();
public static void main(String[] args) throws Exception {
Crypto.loadKeys(); // Load keys from buffers.
// Parse arguments.
boolean exitEarly = false;
for (String arg : args) {
switch (arg.toLowerCase()) {
case "-handbook" -> {
Tools.createGmHandbook(); return;
Tools.createGmHandbook(); exitEarly = true;
}
case "-gachamap" -> {
Tools.createGachaMapping("./gacha_mappings.js"); return;
Tools.createGachaMapping(Grasscutter.getConfig().DATA_FOLDER + "/gacha_mappings.js"); exitEarly = true;
}
}
}
}
// Exit early if argument sets it.
if(exitEarly) System.exit(0);
// Initialize server.
Grasscutter.getLogger().info(language.Starting_Grasscutter);
Grasscutter.getLogger().info(translate("messages.status.starting"));
// Load all resources.
Grasscutter.updateDayOfWeek();
ResourceLoader.loadAll();
ScriptLoader.init();
// Database
// Initialize database.
DatabaseManager.initialize();
// Create plugin manager instance.
pluginManager = new PluginManager();
// Create server instances.
dispatchServer = new DispatchServer();
gameServer = new GameServer(new InetSocketAddress(getConfig().getGameServerOptions().Ip, getConfig().getGameServerOptions().Port));
gameServer = new GameServer();
// Create a server hook instance with both servers.
new ServerHook(gameServer, dispatchServer);
// Create plugin manager instance.
pluginManager = new PluginManager();
// Start servers.
if (getConfig().RunMode == ServerRunMode.HYBRID) {
@ -110,9 +113,9 @@ public final class Grasscutter {
} else if (getConfig().RunMode == ServerRunMode.GAME_ONLY) {
gameServer.start();
} else {
getLogger().error(language.Invalid_server_run_mode + " " + getConfig().RunMode);
getLogger().error(language.Server_run_mode);
getLogger().error(language.Shutting_down);
getLogger().error(translate("messages.status.run_mode_error", getConfig().RunMode));
getLogger().error(translate("messages.status.run_mode_help"));
getLogger().error(translate("messages.status.shutdown"));
System.exit(1);
}
@ -124,7 +127,7 @@ public final class Grasscutter {
// Open console.
startConsole();
}
}
/**
* Server shutdown event.
@ -145,40 +148,14 @@ public final class Grasscutter {
}
public static void loadLanguage() {
try (FileReader file = new FileReader(String.format("%s%s.json", getConfig().LANGUAGE_FOLDER, Grasscutter.config.LocaleLanguage))) {
language = gson.fromJson(file, Language.class);
} catch (Exception e) {
Grasscutter.language = new Language();
Grasscutter.cn_language = new CNLanguage();
Grasscutter.config.LocaleLanguage = Locale.getDefault();
saveConfig();
try {
File folder = new File("./languages");
if (!folder.exists() && !folder.isDirectory()) {
//noinspection ResultOfMethodCallIgnored
folder.mkdirs();
}
} catch (Exception ee) {
Grasscutter.getLogger().error("Unable to create language folder.");
}
try (FileWriter file = new FileWriter("./languages/" + Locale.US + ".json")) {
file.write(gson.toJson(language));
} catch (Exception ee) {
Grasscutter.getLogger().error("Unable to create language file.");
}
try (FileWriter file = new FileWriter("./languages/" + Locale.SIMPLIFIED_CHINESE + ".json")) {
file.write(gson.toJson(cn_language));
} catch (Exception ee) {
Grasscutter.getLogger().error("无法创建简体中文语言文件。");
}
// try again
try (FileReader file = new FileReader(String.format("%s%s.json", getConfig().LANGUAGE_FOLDER, Grasscutter.config.LocaleLanguage))) {
language = gson.fromJson(file, Language.class);
} catch (Exception ee) {
Grasscutter.getLogger().error("Unable to load " + Grasscutter.config.LocaleLanguage + ".json");
}
var locale = config.LocaleLanguage;
var languageTag = locale.toLanguageTag();
if (languageTag.equals("und")) {
Grasscutter.getLogger().error("Illegal locale language, using 'en-US' instead.");
language = Language.getLanguage("en-US");
} else {
language = Language.getLanguage(languageTag);
}
}
@ -193,11 +170,11 @@ public final class Grasscutter {
public static void startConsole() {
// Console should not start in dispatch only mode.
if (getConfig().RunMode == ServerRunMode.DISPATCH_ONLY) {
getLogger().info(language.Dispatch_mode_not_support_command);
getLogger().info(translate("messages.dispatch.no_commands_error"));
return;
}
getLogger().info(language.Start_done);
getLogger().info(translate("messages.status.done"));
String input = null;
boolean isLastInterrupted = false;
while (true) {
@ -223,7 +200,7 @@ public final class Grasscutter {
try {
CommandMap.getInstance().invoke(null, null, input);
} catch (Exception e) {
Grasscutter.getLogger().error(language.Command_error, e);
Grasscutter.getLogger().error(translate("messages.game.command_error"), e);
}
}
}

View File

@ -9,7 +9,7 @@ public @interface Command {
String usage() default "No usage specified";
String description() default "No description specified";
String description() default "commands.generic.no_description_specified";
String[] aliases() default {};

View File

@ -6,6 +6,7 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
public interface CommandHandler {
/**
* Send a message to the target.
*

View File

@ -8,6 +8,8 @@ import org.reflections.Reflections;
import java.util.*;
import static emu.grasscutter.utils.Language.translate;
@SuppressWarnings({"UnusedReturnValue", "unused"})
public final class CommandMap {
private final Map<String, CommandHandler> commands = new HashMap<>();
@ -77,6 +79,12 @@ public final class CommandMap {
return this;
}
public List<Command> getAnnotationsAsList() { return new LinkedList<>(this.annotations.values()); }
public HashMap<String, Command> getAnnotations() {
return new LinkedHashMap<>(this.annotations);
}
/**
* Returns a list of all registered commands.
*
@ -109,7 +117,7 @@ public final class CommandMap {
public void invoke(Player player, Player targetPlayer, String rawMessage) {
rawMessage = rawMessage.trim();
if (rawMessage.length() == 0) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().No_command_specified);
CommandHandler.sendMessage(player, translate("commands.generic.not_specified"));
return;
}
@ -118,11 +126,12 @@ public final class CommandMap {
List<String> args = new LinkedList<>(Arrays.asList(split));
String label = args.remove(0);
String playerId = (player == null) ? consoleId : player.getAccount().getId();
// Check for special cases - currently only target command
// Check for special cases - currently only target command.
String targetUidStr = null;
if (label.startsWith("@")) { // @[UID]
if (label.startsWith("@")) { // @[UID]
targetUidStr = label.substring(1);
} else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
} else if (label.equalsIgnoreCase("target")) { // target [[@]UID]
if (args.size() > 0) {
targetUidStr = args.get(0);
if (targetUidStr.startsWith("@")) {
@ -133,68 +142,64 @@ public final class CommandMap {
}
}
if (targetUidStr != null) {
if (targetUidStr.equals("")) { // Clears default targetPlayer
if (targetUidStr.equals("")) { // Clears the default targetPlayer.
targetPlayerIds.remove(playerId);
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Target_cleared);
return;
} else { // Sets default targetPlayer to the UID given
CommandHandler.sendMessage(player, translate("commands.execution.clear_target"));
} else { // Sets default targetPlayer to the UID provided.
try {
int uid = Integer.parseInt(targetUidStr);
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid);
if (targetPlayer == null) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Player_not_found_or_offline);
CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error"));
} else {
targetPlayerIds.put(playerId, uid);
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Target_set.replace("{uid}", targetUidStr));
CommandHandler.sendMessage(player, translate("commands.execution.set_target", targetUidStr));
}
} catch (NumberFormatException e) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Invalid_UID);
CommandHandler.sendMessage(player, translate("commands.execution.uid_error"));
}
return;
}
return;
}
// Get command handler.
CommandHandler handler = this.commands.get(label);
if (handler == null) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Unknown_command + label);
CommandHandler.sendMessage(player, translate("commands.generic.unknown_command", label));
return;
}
// If any @UID argument is present, override targetPlayer with it
// If any @UID argument is present, override targetPlayer with it.
for (int i = 0; i < args.size(); i++) {
String arg = args.get(i);
if (!arg.startsWith("@")) {
continue;
} else {
if (arg.startsWith("@")) {
arg = args.remove(i).substring(1);
try {
int uid = Integer.parseInt(arg);
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid);
if (targetPlayer == null) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Player_not_found_or_offline);
CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error"));
return;
}
break;
} catch (NumberFormatException e) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Invalid_UID);
CommandHandler.sendMessage(player, translate("commands.execution.uid_error"));
return;
}
}
}
// If there's still no targetPlayer at this point, use previously-set target
if (targetPlayer == null) {
if (targetPlayerIds.containsKey(playerId)) {
targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.get(playerId)); // We check every time in case the target goes offline after being targeted
if (targetPlayer == null) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Player_not_found_or_offline);
CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error"));
return;
}
} else {
// If there's still no targetPlayer at this point, use local player
if (targetPlayer == null) {
targetPlayer = player;
}
// If there's still no targetPlayer at this point, use executor.
targetPlayer = player;
}
}
@ -205,12 +210,12 @@ public final class CommandMap {
Account account = player.getAccount();
if (player != targetPlayer) { // Additional permission required for targeting another player
if (!permissionNodeTargeted.isEmpty() && !account.hasPermission(permissionNodeTargeted)) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().You_not_permission_run_command);
CommandHandler.sendMessage(player, translate("commands.generic.permission_error"));
return;
}
}
if (!permissionNode.isEmpty() && !account.hasPermission(permissionNode)) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().You_not_permission_run_command);
CommandHandler.sendMessage(player, translate("commands.generic.permission_error"));
return;
}
}
@ -220,10 +225,8 @@ public final class CommandMap {
final Player targetPlayerF = targetPlayer; // Is there a better way to do this?
Runnable runnable = () -> handler.execute(player, targetPlayerF, args);
if(threading) {
Thread command = new Thread(runnable);
command.start();
}
else {
new Thread(runnable).start();
} else {
runnable.run();
}
}

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.database.DatabaseHelper;
@ -8,18 +7,20 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "account", usage = "account <create|delete> <username> [uid]", description = "Modify user accounts")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "account", usage = "account <create|delete> <username> [uid]", description = "commands.account.description")
public final class AccountCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (sender != null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().This_command_can_only_run_from_console);
CommandHandler.sendMessage(sender, translate("commands.generic.console_execute_error"));
return;
}
if (args.size() < 2) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_command_usage);
CommandHandler.sendMessage(null, translate("commands.account.command_usage"));
return;
}
@ -28,7 +29,7 @@ public final class AccountCommand implements CommandHandler {
switch (action) {
default:
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_command_usage);
CommandHandler.sendMessage(null, translate("commands.account.command_usage"));
return;
case "create":
int uid = 0;
@ -36,27 +37,27 @@ public final class AccountCommand implements CommandHandler {
try {
uid = Integer.parseInt(args.get(2));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Invalid_UID);
CommandHandler.sendMessage(null, translate("commands.account.invalid"));
return;
}
}
emu.grasscutter.game.Account account = DatabaseHelper.createAccountWithId(username, uid);
if (account == null) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_exists);
CommandHandler.sendMessage(null, translate("commands.account.exists"));
return;
} else {
account.addPermission("*");
account.save(); // Save account to database.
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_create_UID.replace("{uid}", Integer.toString(account.getPlayerUid())));
CommandHandler.sendMessage(null, translate("commands.account.create", Integer.toString(account.getPlayerUid())));
}
return;
case "delete":
if (DatabaseHelper.deleteAccount(username)) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_delete);
CommandHandler.sendMessage(null, translate("commands.account.delete"));
} else {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Account_not_find);
CommandHandler.sendMessage(null, translate("commands.account.no_account"));
}
}
}

View File

@ -7,14 +7,15 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "broadcast", usage = "broadcast <message>",
description = "Sends a message to all the players", aliases = {"b"}, permission = "server.broadcast")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "broadcast", usage = "broadcast <message>", aliases = {"b"}, permission = "server.broadcast", description = "commands.broadcast.description")
public final class BroadcastCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Broadcast_command_usage);
CommandHandler.sendMessage(sender, translate("commands.broadcast.command_usage"));
return;
}
@ -24,6 +25,6 @@ public final class BroadcastCommand implements CommandHandler {
CommandHandler.sendMessage(p, message);
}
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Broadcast_message_sent);
CommandHandler.sendMessage(sender, translate("commands.broadcast.message_sent"));
}
}

View File

@ -1,43 +1,43 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "changescene", usage = "changescene <scene id>",
description = "Changes your scene", aliases = {"scene"}, permission = "player.changescene")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "changescene", usage = "changescene <scene id>", aliases = {"scene"}, permission = "player.changescene", description = "commands.changescene.description")
public final class ChangeSceneCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() != 1) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Change_screen_usage);
CommandHandler.sendMessage(sender, translate("commands.changescene.usage"));
return;
}
try {
int sceneId = Integer.parseInt(args.get(0));
if (sceneId == targetPlayer.getSceneId()) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Change_screen_you_in_that_screen);
CommandHandler.sendMessage(sender, translate("commands.changescene.already_in_scene"));
return;
}
boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, targetPlayer.getPos());
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Change_screen + sceneId);
CommandHandler.sendMessage(sender, translate("commands.changescene.result", Integer.toString(sceneId)));
if (!result) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Change_screen_not_exist);
CommandHandler.sendMessage(sender, translate("commands.changescene.exists_error"));
}
} catch (Exception e) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments);
CommandHandler.sendMessage(sender, translate("commands.execution.argument_error"));
}
}
}

View File

@ -10,8 +10,10 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "clear", usage = "clear <all|wp|art|mat>", //Merged /clearartifacts and /clearweapons to /clear <args> [uid]
description = "Deletes unequipped unlocked items, including yellow rarity ones from your inventory",
description = "commands.clear.description",
aliases = {"clear"}, permission = "player.clearinv")
public final class ClearCommand implements CommandHandler {
@ -19,11 +21,11 @@ public final class ClearCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() < 1) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_usage);
CommandHandler.sendMessage(sender, translate("commands.clear.command_usage"));
return;
}
Inventory playerInventory = targetPlayer.getInventory();
@ -35,7 +37,7 @@ public final class ClearCommand implements CommandHandler {
.filter(item -> item.getItemType() == ItemType.ITEM_WEAPON)
.filter(item -> !item.isLocked() && !item.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_weapons.replace("{name}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.clear.weapons", targetPlayer.getNickname()));
}
case "art" -> {
toDelete = playerInventory.getItems().values().stream()
@ -43,7 +45,7 @@ public final class ClearCommand implements CommandHandler {
.filter(item -> item.getLevel() == 1 && item.getExp() == 0)
.filter(item -> !item.isLocked() && !item.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.clear.artifacts", targetPlayer.getNickname()));
}
case "mat" -> {
toDelete = playerInventory.getItems().values().stream()
@ -51,7 +53,7 @@ public final class ClearCommand implements CommandHandler {
.filter(item -> item.getLevel() == 1 && item.getExp() == 0)
.filter(item -> !item.isLocked() && !item.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.clear.materials", targetPlayer.getNickname()));
}
case "all" -> {
toDelete = playerInventory.getItems().values().stream()
@ -59,34 +61,44 @@ public final class ClearCommand implements CommandHandler {
.filter(item1 -> item1.getLevel() == 1 && item1.getExp() == 0)
.filter(item1 -> !item1.isLocked() && !item1.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.clear.artifacts", targetPlayer.getNickname()));
playerInventory.removeItems(toDelete);
toDelete = playerInventory.getItems().values().stream()
.filter(item2 -> item2.getItemType() == ItemType.ITEM_MATERIAL)
.filter(item2 -> !item2.isLocked() && !item2.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname()));
playerInventory.removeItems(toDelete);
CommandHandler.sendMessage(sender, translate("commands.clear.materials", targetPlayer.getNickname()));
toDelete = playerInventory.getItems().values().stream()
.filter(item3 -> item3.getItemType() == ItemType.ITEM_WEAPON)
.filter(item3 -> item3.getLevel() == 1 && item3.getExp() == 0)
.filter(item3 -> !item3.isLocked() && !item3.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_artifacts.replace("{name}", targetPlayer.getNickname()));
playerInventory.removeItems(toDelete);
CommandHandler.sendMessage(sender, translate("commands.clear.weapons", targetPlayer.getNickname()));
toDelete = playerInventory.getItems().values().stream()
.filter(item4 -> item4.getItemType() == ItemType.ITEM_FURNITURE)
.filter(item4 -> !item4.isLocked() && !item4.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_furniture.replace("{name}", targetPlayer.getNickname()));
playerInventory.removeItems(toDelete);
CommandHandler.sendMessage(sender, translate("commands.clear.furniture", targetPlayer.getNickname()));
toDelete = playerInventory.getItems().values().stream()
.filter(item5 -> item5.getItemType() == ItemType.ITEM_DISPLAY)
.filter(item5 -> !item5.isLocked() && !item5.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_displays.replace("{name}", targetPlayer.getNickname()));
playerInventory.removeItems(toDelete);
CommandHandler.sendMessage(sender, translate("commands.clear.displays", targetPlayer.getNickname()));
toDelete = playerInventory.getItems().values().stream()
.filter(item6 -> item6.getItemType() == ItemType.ITEM_VIRTUAL)
.filter(item6 -> !item6.isLocked() && !item6.isEquipped())
.toList();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_virtuals.replace("{name}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Clear_everything.replace("{name}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.clear.virtuals", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.clear.everything", targetPlayer.getNickname()));
}
}

View File

@ -7,39 +7,40 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "coop", usage = "coop [host UID]",
description = "Forces someone to join the world of others", permission = "server.coop")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "coop", usage = "coop [host UID]", permission = "server.coop", description = "commands.coop.description")
public final class CoopCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
Player host = sender;
switch (args.size()) {
case 0: // Summon target to self
if (sender == null) { // Console doesn't have a self to summon to
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Coop_usage);
CommandHandler.sendMessage(sender, translate("commands.coop.usage"));
if (sender == null) // Console doesn't have a self to summon to
return;
}
break;
case 1: // Summon target to argument
try {
int hostId = Integer.parseInt(args.get(0));
host = Grasscutter.getGameServer().getPlayerByUid(hostId);
if (host == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Player_is_offline);
CommandHandler.sendMessage(sender, translate("commands.execution.player_offline_error"));
return;
}
break;
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_playerId);
CommandHandler.sendMessage(sender, translate("commands.execution.uid_error"));
return;
}
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Coop_usage);
CommandHandler.sendMessage(sender, translate("commands.coop.usage"));
return;
}
@ -49,6 +50,6 @@ public final class CoopCommand implements CommandHandler {
}
host.getServer().getMultiplayerManager().applyEnterMp(targetPlayer, host.getUid());
targetPlayer.getServer().getMultiplayerManager().applyEnterMpReply(host, targetPlayer.getUid(), true);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Coop_success.replace("{host}", host.getNickname()).replace("{target}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.coop.success", targetPlayer.getNickname(), host.getNickname()));
}
}

View File

@ -11,14 +11,15 @@ import emu.grasscutter.utils.Position;
import java.util.List;
@Command(label = "drop", usage = "drop <itemId|itemName> [amount]",
description = "Drops an item near you", aliases = {"d", "dropitem"}, permission = "server.drop")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "drop", usage = "drop <itemId|itemName> [amount]", aliases = {"d", "dropitem"}, permission = "server.drop", description = "commands.drop.description")
public final class DropCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(null, translate("commands.execution.need_target"));
return;
}
@ -30,25 +31,25 @@ public final class DropCommand implements CommandHandler {
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount"));
return;
} // Slightly cheeky here: no break so it falls through to initialize the first argument too
} // Slightly cheeky here: no break, so it falls through to initialize the first argument too
case 1:
try {
item = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId"));
return;
}
break;
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Drop_usage);
CommandHandler.sendMessage(sender, translate("commands.drop.command_usage"));
return;
}
ItemData itemData = GameData.getItemDataMap().get(item);
if (itemData == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId"));
return;
}
if (itemData.isEquip()) {
@ -62,6 +63,6 @@ public final class DropCommand implements CommandHandler {
EntityItem entity = new EntityItem(targetPlayer.getScene(), targetPlayer, itemData, targetPlayer.getPos().clone().addY(3f), amount);
targetPlayer.getScene().addEntity(entity);
}
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Drop_dropped_of.replace("{amount}", Integer.toString(amount)).replace("{item}", Integer.toString(item)));
CommandHandler.sendMessage(sender, translate("commands.drop.success", Integer.toString(amount), Integer.toString(item)));
}
}

View File

@ -1,43 +1,43 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "enterdungeon", usage = "enterdungeon <dungeon id>",
description = "Enter a dungeon", aliases = {"dungeon"}, permission = "player.enterdungeon")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "enterdungeon", usage = "enterdungeon <dungeon id>", aliases = {"dungeon"}, permission = "player.enterdungeon", description = "commands.enter_dungeon.description")
public final class EnterDungeonCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(null, translate("commands.execution.need_target"));
return;
}
if (args.size() < 1) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_usage);
CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.usage"));
return;
}
try {
int dungeonId = Integer.parseInt(args.get(0));
if (dungeonId == targetPlayer.getSceneId()) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_you_in_that_dungeon);
CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.in_dungeon_error"));
return;
}
boolean result = targetPlayer.getServer().getDungeonManager().enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_changed_to_dungeon + dungeonId);
CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.changed", dungeonId));
if (!result) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_dungeon_not_found);
CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.not_found_error"));
}
} catch (Exception e) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().EnterDungeon_usage);
CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.usage"));
}
}
}

View File

@ -13,14 +13,15 @@ import emu.grasscutter.game.player.Player;
import java.util.*;
@Command(label = "giveall", usage = "giveall [amount]",
description = "Gives all items", aliases = {"givea"}, permission = "player.giveall", threading = true)
import static emu.grasscutter.utils.Language.translate;
@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", threading = true, description = "commands.giveAll.description")
public final class GiveAllCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
int amount = 99999;
@ -32,21 +33,21 @@ public final class GiveAllCommand implements CommandHandler {
try {
amount = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount"));
return;
}
break;
default: // invalid
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveAll_usage);
CommandHandler.sendMessage(sender, translate("commands.giveAll.usage"));
return;
}
this.giveAllItems(targetPlayer, amount);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveAll_done);
CommandHandler.sendMessage(sender, translate("commands.giveAll.success", targetPlayer.getNickname()));
}
public void giveAllItems(Player player, int amount) {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().GiveAll_item);
CommandHandler.sendMessage(player, translate("commands.giveAll.started"));
for (AvatarData avatarData: GameData.getAvatarDataMap().values()) {
//Exclude test avatar

View File

@ -14,17 +14,20 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Command(label = "giveart", usage = "giveart <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", description = "Gives the player a specified artifact", aliases = {"gart"}, permission = "player.giveart")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "giveart", usage = "giveart <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]", aliases = {"gart"}, permission = "player.giveart", description = "commands.giveArtifact.description")
public final class GiveArtifactCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() < 2) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveArtifact_usage);
CommandHandler.sendMessage(sender, translate("commands.giveArtifact.usage"));
return;
}
@ -32,12 +35,12 @@ public final class GiveArtifactCommand implements CommandHandler {
try {
itemId = Integer.parseInt(args.remove(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_artifact_id);
CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error"));
return;
}
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData.getItemType() != ItemType.ITEM_RELIQUARY) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_artifact_id);
CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error"));
return;
}
@ -45,7 +48,7 @@ public final class GiveArtifactCommand implements CommandHandler {
try {
mainPropId = Integer.parseInt(args.remove(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments);
CommandHandler.sendMessage(sender, translate("commands.generic.execution.argument_error"));
return;
}
@ -59,7 +62,7 @@ public final class GiveArtifactCommand implements CommandHandler {
} catch (NumberFormatException ignored) { // Could be a stat,times string so no need to panic
}
ArrayList<Integer> appendPropIdList = new ArrayList<>();
List<Integer> appendPropIdList = new ArrayList<>();
try {
args.forEach(it -> {
String[] arr;
@ -74,7 +77,7 @@ public final class GiveArtifactCommand implements CommandHandler {
appendPropIdList.addAll(Collections.nCopies(n, Integer.parseInt(it)));
});
} catch (Exception ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments);
CommandHandler.sendMessage(sender, translate("commands.execution.argument_error"));
return;
}
@ -85,7 +88,7 @@ public final class GiveArtifactCommand implements CommandHandler {
item.getAppendPropIdList().addAll(appendPropIdList);
targetPlayer.getInventory().addItem(item, ActionReason.SubfieldDrop);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveArtifact_given.replace("{itemId}", Integer.toString(itemId)).replace("target", Integer.toString(targetPlayer.getUid())));
CommandHandler.sendMessage(sender, translate("commands.giveArtifact.success", Integer.toString(itemId), Integer.toString(targetPlayer.getUid())));
}
}

View File

@ -10,14 +10,15 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "givechar", usage = "givechar <avatarId> [level]",
description = "Gives the player a specified character", aliases = {"givec"}, permission = "player.givechar")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "givechar", usage = "givechar <avatarId> [level]", aliases = {"givec"}, permission = "player.givechar", description = "commands.giveChar.description")
public final class GiveCharCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
@ -30,7 +31,7 @@ public final class GiveCharCommand implements CommandHandler {
level = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
// TODO: Parse from avatar name using GM Handbook.
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_avatar_level);
CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarLevel"));
return;
} // Cheeky fall-through to parse first argument too
case 1:
@ -38,24 +39,24 @@ public final class GiveCharCommand implements CommandHandler {
avatarId = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
// TODO: Parse from avatar name using GM Handbook.
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_avatar_id);
CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarId"));
return;
}
break;
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveChar_usage);
CommandHandler.sendMessage(sender, translate("commands.giveChar.usage"));
return;
}
AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId);
if (avatarData == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_avatar_id);
CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarId"));
return;
}
// Check level.
if (level > 90) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_avatar_level);
CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarLevel"));
return;
}
@ -75,6 +76,6 @@ public final class GiveCharCommand implements CommandHandler {
avatar.recalcStats();
targetPlayer.addAvatar(avatar);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().GiveChar_given.replace("{avatarId}", Integer.toString(avatarId)).replace("{level}", Integer.toString(level)).replace("{target}", Integer.toString(targetPlayer.getUid())));
CommandHandler.sendMessage(sender, translate("commands.giveChar.given", Integer.toString(avatarId), Integer.toString(level), Integer.toString(targetPlayer.getUid())));
}
}

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
@ -12,11 +11,13 @@ import emu.grasscutter.game.props.ActionReason;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Command(label = "give", usage = "give <itemId|itemName> [amount] [level]", description = "Gives an item to you or the specified player", aliases = {
"g", "item", "giveitem"}, permission = "player.give")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "give", usage = "give <itemId|itemName> [amount] [level]", aliases = {
"g", "item", "giveitem"}, permission = "player.give", description = "commands.give.description")
public final class GiveCommand implements CommandHandler {
Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals
Pattern refineRegex = Pattern.compile("r(\\d+)");
@ -33,7 +34,7 @@ public final class GiveCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
int item;
@ -67,21 +68,21 @@ public final class GiveCommand implements CommandHandler {
try {
refinement = Integer.parseInt(args.get(3));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_refinement);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemRefinement"));
return;
} // Fallthrough
case 3: // <itemId|itemName> [amount] [level]
try {
lvl = Integer.parseInt(args.get(2));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_level);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemLevel"));
return;
} // Fallthrough
case 2: // <itemId|itemName> [amount]
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount"));
return;
} // Fallthrough
case 1: // <itemId|itemName>
@ -89,30 +90,28 @@ public final class GiveCommand implements CommandHandler {
item = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
// TODO: Parse from item name using GM Handbook.
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId"));
return;
}
break;
default: // *No args*
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_usage);
CommandHandler.sendMessage(sender, translate("commands.give.usage"));
return;
}
ItemData itemData = GameData.getItemDataMap().get(item);
if (itemData == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId"));
return;
}
if (refinement != 0) {
if (itemData.getItemType() == ItemType.ITEM_WEAPON) {
if (refinement < 1 || refinement > 5) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_refinement_must_between_1_and_5);
CommandHandler.sendMessage(sender, translate("commands.give.refinement_must_between_1_and_5"));
return;
}
} else {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_refinement_only_applicable_weapons);
CommandHandler.sendMessage(sender, translate("commands.give.refinement_only_applicable_weapons"));
return;
}
}
@ -120,11 +119,11 @@ public final class GiveCommand implements CommandHandler {
this.item(targetPlayer, itemData, amount, lvl, refinement);
if (!itemData.isEquip()) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_given.replace("{amount}", Integer.toString(amount)).replace("{item}", Integer.toString(item)).replace("{target}", Integer.toString(targetPlayer.getUid())));
CommandHandler.sendMessage(sender, translate("commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid())));
} else if (itemData.getItemType() == ItemType.ITEM_WEAPON) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_given_with_level_and_refinement.replace("{item}", Integer.toString(item)).replace("{lvl}", Integer.toString(lvl)).replace("{refinement}", Integer.toString(refinement)).replace("{amount}", Integer.toString(amount)).replace("{target}", Integer.toString(targetPlayer.getUid())));
CommandHandler.sendMessage(sender, translate("commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid())));
} else {
CommandHandler.sendMessage(sender,Grasscutter.getLanguage().Give_given_level.replace("{item}", Integer.toString(item)).replace("{lvl}", Integer.toString(lvl)).replace("{amount}", Integer.toString(amount)));
CommandHandler.sendMessage(sender, translate("commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount)));
}
}

View File

@ -1,20 +1,20 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "godmode", usage = "godmode [on|off|toggle]",
description = "Prevents you from taking damage. Defaults to toggle.", permission = "player.godmode")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode", description = "commands.godmode.description")
public final class GodModeCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
@ -30,11 +30,11 @@ public final class GodModeCommand implements CommandHandler {
case "toggle":
break; // Already toggled
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Godmode_status);
break;
}
}
targetPlayer.setGodmode(enabled);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Godmode_status.replace("{status}", (enabled ? Grasscutter.getLanguage().Enabled : Grasscutter.getLanguage().Disabled)).replace("{name}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.godmode.success", (enabled ? translate("commands.status.enabled") : translate("commands.status.disabled")), targetPlayer.getNickname()));
}
}

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
@ -10,13 +9,15 @@ import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import java.util.List;
@Command(label = "heal", usage = "heal|h", aliases = {"h"},
description = "Heal all characters in your current team.", permission = "player.heal")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "heal", usage = "heal|h", aliases = {"h"}, permission = "player.heal", description = "commands.heal.description")
public final class HealCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
@ -31,6 +32,6 @@ public final class HealCommand implements CommandHandler {
entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
}
});
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Heal_message);
CommandHandler.sendMessage(sender, translate("commands.heal.success"));
}
}

View File

@ -8,8 +8,9 @@ import emu.grasscutter.game.player.Player;
import java.util.*;
@Command(label = "help", usage = "help [command]",
description = "Sends the help message or shows information about a specified command")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "help", usage = "help [command]", description = "commands.help.description")
public final class HelpCommand implements CommandHandler {
@Override
@ -31,16 +32,16 @@ public final class HelpCommand implements CommandHandler {
} else {
String command = args.get(0);
CommandHandler handler = CommandMap.getInstance().getHandler(command);
StringBuilder builder = new StringBuilder(player == null ? "\n" + Grasscutter.getLanguage().Help + " - " : Grasscutter.getLanguage().Help + " - ").append(command).append(": \n");
StringBuilder builder = new StringBuilder(player == null ? "\n" + translate("commands.status.help") + " - " : translate("commands.status.help") + " - ").append(command).append(": \n");
if (handler == null) {
builder.append(Grasscutter.getLanguage().No_command_found);
builder.append(translate("commands.generic.command_exist_error"));
} else {
Command annotation = handler.getClass().getAnnotation(Command.class);
builder.append(" ").append(annotation.description()).append("\n");
builder.append(Grasscutter.getLanguage().Help_usage).append(annotation.usage());
builder.append(" ").append(translate(annotation.description())).append("\n");
builder.append(translate("commands.help.usage")).append(annotation.usage());
if (annotation.aliases().length >= 1) {
builder.append("\n").append(Grasscutter.getLanguage().Help_aliases);
builder.append("\n").append(translate("commands.help.aliases"));
for (String alias : annotation.aliases()) {
builder.append(alias).append(" ");
}
@ -56,13 +57,13 @@ public final class HelpCommand implements CommandHandler {
void SendAllHelpMessage(Player player, List<Command> annotations) {
if (player == null) {
StringBuilder builder = new StringBuilder("\n" + Grasscutter.getLanguage().Help_available_command + "\n");
StringBuilder builder = new StringBuilder("\n" + translate("commands.help.available_commands") + "\n");
annotations.forEach(annotation -> {
builder.append(annotation.label()).append("\n");
builder.append(" ").append(annotation.description()).append("\n");
builder.append(Grasscutter.getLanguage().Help_usage).append(annotation.usage());
builder.append(" ").append(translate(annotation.description())).append("\n");
builder.append(translate("commands.help.usage")).append(annotation.usage());
if (annotation.aliases().length >= 1) {
builder.append("\n").append(Grasscutter.getLanguage().Help_aliases);
builder.append("\n").append(translate("commands.help.aliases"));
for (String alias : annotation.aliases()) {
builder.append(alias).append(" ");
}
@ -73,13 +74,13 @@ public final class HelpCommand implements CommandHandler {
CommandHandler.sendMessage(null, builder.toString());
} else {
CommandHandler.sendMessage(player, Grasscutter.getLanguage().Help_available_command);
CommandHandler.sendMessage(player, translate("commands.help.available_commands"));
annotations.forEach(annotation -> {
StringBuilder builder = new StringBuilder(annotation.label()).append("\n");
builder.append(" ").append(annotation.description()).append("\n");
builder.append(Grasscutter.getLanguage().Help_usage).append(annotation.usage());
builder.append(" ").append(translate(annotation.description())).append("\n");
builder.append(translate("commands.help.usage")).append(annotation.usage());
if (annotation.aliases().length >= 1) {
builder.append("\n").append(Grasscutter.getLanguage().Help_aliases);
builder.append("\n").append(translate("commands.help.aliases"));
for (String alias : annotation.aliases()) {
builder.append(alias).append(" ");
}

View File

@ -1,27 +1,29 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "kick", usage = "kick",
description = "Kicks the specified player from the server (WIP)", permission = "server.kick")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "kick", usage = "kick", permission = "server.kick", description = "commands.kick.description")
public final class KickCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (sender != null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kick_player_kick_player.replace("{sendUid}", Integer.toString(sender.getAccount().getPlayerUid())).replace("{sendName}", sender.getAccount().getUsername()).replace("kickUid", Integer.toString(targetPlayer.getUid())).replace("{kickName}", targetPlayer.getAccount().getUsername()));
CommandHandler.sendMessage(sender, translate("commands.kick.player_kick_player",
Integer.toString(sender.getAccount().getPlayerUid()), sender.getAccount().getUsername(),
Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername()));
} else {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kick_server_player.replace("{kickUid}", Integer.toString(targetPlayer.getUid())).replace("{kickName}", targetPlayer.getAccount().getUsername()));
CommandHandler.sendMessage(null, translate("commands.kick.server_kick_player", Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername()));
}
targetPlayer.getSession().close();

View File

@ -10,14 +10,15 @@ import emu.grasscutter.game.world.Scene;
import java.util.List;
@Command(label = "killall", usage = "killall [sceneId]",
description = "Kill all entities", permission = "server.killall")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall", description = "commands.kill.description")
public final class KillAllCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
@ -30,14 +31,14 @@ public final class KillAllCommand implements CommandHandler {
scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0)));
break;
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kill_usage);
CommandHandler.sendMessage(sender, translate("commands.kill.usage"));
return;
}
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments);
CommandHandler.sendMessage(sender, translate("commands.execution.argument_error"));
}
if (scene == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kill_scene_not_found_in_player_world);
CommandHandler.sendMessage(sender, translate("commands.kill.scene_not_found_in_player_world"));
return;
}
@ -46,7 +47,7 @@ public final class KillAllCommand implements CommandHandler {
List<GameEntity> toKill = sceneF.getEntities().values().stream()
.filter(entity -> entity instanceof EntityMonster)
.toList();
toKill.stream().forEach(entity -> sceneF.killEntity(entity, 0));
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Kill_kill_monsters_in_scene.replace("{size}", Integer.toString(toKill.size())).replace("{id}", Integer.toString(scene.getId())));
toKill.forEach(entity -> sceneF.killEntity(entity, 0));
CommandHandler.sendMessage(sender, translate("commands.kill.kill_monsters_in_scene", Integer.toString(toKill.size()), Integer.toString(scene.getId())));
}
}

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityAvatar;
@ -12,14 +11,15 @@ import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import java.util.List;
@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"},
description = "Kills the players current character", permission = "player.killcharacter")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", description = "commands.list.description")
public final class KillCharacterCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
@ -32,6 +32,6 @@ public final class KillCharacterCommand implements CommandHandler {
targetPlayer.getScene().removeEntity(entity);
entity.onDeath(0);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().KillCharacter_kill_current_character.replace("{name}", targetPlayer.getNickname()));
CommandHandler.sendMessage(sender, translate("commands.killCharacter.success", targetPlayer.getNickname()));
}
}

View File

@ -8,8 +8,9 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
import java.util.Map;
@Command(label = "list", usage = "list [uid]",
description = "List online players", aliases = {"players"})
import static emu.grasscutter.utils.Language.translate;
@Command(label = "list", usage = "list [uid]", aliases = {"players"}, description = "commands.list.description")
public final class ListCommand implements CommandHandler {
@Override
@ -21,7 +22,7 @@ public final class ListCommand implements CommandHandler {
needUID = args.get(0).equals("uid");
}
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().List_message.replace("{size}", Integer.toString(playersMap.size())));
CommandHandler.sendMessage(sender, translate("commands.list.success", Integer.toString(playersMap.size())));
if (playersMap.size() != 0) {
StringBuilder playerSet = new StringBuilder();

View File

@ -8,19 +8,20 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "permission", usage = "permission <add|remove> <permission>",
description = "Grants or removes a permission for a user", permission = "*")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "permission", usage = "permission <add|remove> <permission>", permission = "*", description = "commands.permission.description")
public final class PermissionCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() != 2) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_usage);
CommandHandler.sendMessage(sender, translate("commands.permission.usage"));
return;
}
@ -29,23 +30,23 @@ public final class PermissionCommand implements CommandHandler {
Account account = targetPlayer.getAccount();
if (account == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Account_not_find);
CommandHandler.sendMessage(sender, translate("commands.permission.account_error"));
return;
}
switch (action) {
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_usage);
CommandHandler.sendMessage(sender, translate("commands.permission.usage"));
break;
case "add":
if (account.addPermission(permission)) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_add);
} else CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_have_permission);
CommandHandler.sendMessage(sender, translate("commands.permission.add"));
} else CommandHandler.sendMessage(sender, translate("commands.permission.has_error"));
break;
case "remove":
if (account.removePermission(permission)) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_remove);
} else CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Permission_not_have_permission);
CommandHandler.sendMessage(sender, translate("commands.permission.remove"));
} else CommandHandler.sendMessage(sender, translate("commands.permission.not_have_error"));
break;
}

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
@ -8,18 +7,21 @@ import emu.grasscutter.utils.Position;
import java.util.List;
@Command(label = "position", usage = "position", aliases = {"pos"},
description = "Get coordinates.")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "position", usage = "position", aliases = {"pos"}, description = "commands.position.description")
public final class PositionCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
Position pos = targetPlayer.getPos();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Position_message.replace("{x}", Float.toString(pos.getX())).replace("{y}", Float.toString(pos.getY())).replace("{z}", Float.toString(pos.getZ())).replace("{id}", Integer.toString(targetPlayer.getSceneId())));
CommandHandler.sendMessage(sender, translate("commands.position.success",
Float.toString(pos.getX()), Float.toString(pos.getY()), Float.toString(pos.getZ()),
Integer.toString(targetPlayer.getSceneId())));
}
}

View File

@ -7,19 +7,22 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "reload", usage = "reload",
description = "Reload server config", permission = "server.reload")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "reload", usage = "reload", permission = "server.reload", description = "commands.reload.description")
public final class ReloadCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Reload_reload_start);
CommandHandler.sendMessage(sender, translate("commands.reload.reload_start"));
Grasscutter.loadConfig();
Grasscutter.loadLanguage();
Grasscutter.getGameServer().getGachaManager().load();
Grasscutter.getGameServer().getDropManager().load();
Grasscutter.getGameServer().getShopManager().load();
Grasscutter.getDispatchServer().loadQueries();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Reload_reload_done);
CommandHandler.sendMessage(sender, translate("commands.reload.reload_done"));
}
}

View File

@ -1,6 +1,5 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar;
@ -9,21 +8,22 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "resetconst", usage = "resetconst [all]",
description = "Resets the constellation level on your current active character, will need to relog after using the command to see any changes.",
aliases = {"resetconstellation"}, permission = "player.resetconstellation")
aliases = {"resetconstellation"}, permission = "player.resetconstellation", description = "commands.resetConst.description")
public final class ResetConstCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() > 0 && args.get(0).equalsIgnoreCase("all")) {
targetPlayer.getAvatars().forEach(this::resetConstellation);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().ResetConst_reset_all);
CommandHandler.sendMessage(sender, translate("commands.resetConst.reset_all"));
} else {
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
if (entity == null) {
@ -33,7 +33,7 @@ public final class ResetConstCommand implements CommandHandler {
Avatar avatar = entity.getAvatar();
this.resetConstellation(avatar);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().ResetConst_reset_all_done.replace("{name}", avatar.getAvatarData().getName()));
CommandHandler.sendMessage(sender, translate("commands.resetConst.success", avatar.getAvatarData().getName()));
}
}

View File

@ -7,18 +7,20 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "resetshop", usage = "resetshop",
description = "Reset target player's shop refresh time.", permission = "server.resetshop")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", description = "commands.status.description")
public final class ResetShopLimitCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0));
targetPlayer.save();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Success);
CommandHandler.sendMessage(sender, translate("commands.status.success"));
}
}

View File

@ -6,7 +6,9 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "restart", usage = "restart - Restarts the current session")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "restart", usage = "restart", description = "commands.restart.description")
public final class RestartCommand implements CommandHandler {
@Override

View File

@ -10,8 +10,10 @@ import emu.grasscutter.game.player.Player;
import java.util.HashMap;
import java.util.List;
@Command(label = "sendmail", usage = "sendmail <userId|all|help> [templateId]",
description = "Sends mail to the specified user. The usage of this command changes based on it's composition state.", permission = "server.sendmail")
import static emu.grasscutter.utils.Language.translate;
@SuppressWarnings("ConstantConditions")
@Command(label = "sendmail", usage = "sendmail <userId|all|help> [templateId]", permission = "server.sendmail", description = "commands.sendMail.description")
public final class SendMailCommand implements CommandHandler {
// TODO: You should be able to do /sendmail and then just send subsequent messages until you finish
@ -37,7 +39,7 @@ public final class SendMailCommand implements CommandHandler {
MailBuilder mailBuilder;
switch (args.get(0).toLowerCase()) {
case "help" -> {
CommandHandler.sendMessage(sender, this.getClass().getAnnotation(Command.class).description() + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage());
CommandHandler.sendMessage(sender, translate(this.getClass().getAnnotation(Command.class).description()) + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage());
return;
}
case "all" -> mailBuilder = new MailBuilder(true, new Mail());
@ -45,16 +47,16 @@ public final class SendMailCommand implements CommandHandler {
if (DatabaseHelper.getPlayerById(Integer.parseInt(args.get(0))) != null) {
mailBuilder = new MailBuilder(Integer.parseInt(args.get(0)), new Mail());
} else {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_user_not_exist.replace("{id}", args.get(0)));
CommandHandler.sendMessage(sender, translate("commands.sendMail.user_not_exist", args.get(0)));
return;
}
}
}
mailBeingConstructed.put(senderId, mailBuilder);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_start_composition);
CommandHandler.sendMessage(sender, translate("commands.sendMail.start_composition"));
}
case 2 -> CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_templates);
default -> CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_invalid_arguments);
case 2 -> CommandHandler.sendMessage(sender, translate("commands.sendMail.templates"));
default -> CommandHandler.sendMessage(sender, translate("commands.sendMail.invalid_arguments"));
}
} else {
MailBuilder mailBuilder = mailBeingConstructed.get(senderId);
@ -63,28 +65,28 @@ public final class SendMailCommand implements CommandHandler {
switch (args.get(0).toLowerCase()) {
case "stop" -> {
mailBeingConstructed.remove(senderId);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_send_cancel);
CommandHandler.sendMessage(sender, translate("commands.sendMail.sendCancel"));
return;
}
case "finish" -> {
if (mailBuilder.constructionStage == 3) {
if (!mailBuilder.sendToAll) {
Grasscutter.getGameServer().getPlayerByUid(mailBuilder.recipient, true).sendMail(mailBuilder.mail);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_send_done.replace("{name}", Integer.toString(mailBuilder.recipient)));
CommandHandler.sendMessage(sender, translate("commands.sendMail.send_done", Integer.toString(mailBuilder.recipient)));
} else {
for (Player player : DatabaseHelper.getAllPlayers()) {
Grasscutter.getGameServer().getPlayerByUid(player.getUid(), true).sendMail(mailBuilder.mail);
}
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_send_all_done);
CommandHandler.sendMessage(sender, translate("commands.sendMail.send_all_done"));
}
mailBeingConstructed.remove(senderId);
} else {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_not_composition_end.replace("{args}", getConstructionArgs(mailBuilder.constructionStage)));
CommandHandler.sendMessage(sender, translate("commands.sendMail.not_composition_end", getConstructionArgs(mailBuilder.constructionStage)));
}
return;
}
case "help" -> {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_please_use.replace("{args}", getConstructionArgs(mailBuilder.constructionStage)));
CommandHandler.sendMessage(sender, translate("commands.sendMail.please_use", getConstructionArgs(mailBuilder.constructionStage)));
return;
}
default -> {
@ -92,19 +94,19 @@ public final class SendMailCommand implements CommandHandler {
case 0 -> {
String title = String.join(" ", args.subList(0, args.size()));
mailBuilder.mail.mailContent.title = title;
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_set_title.replace("{title}", title));
CommandHandler.sendMessage(sender, translate("commands.sendMail.set_title", title));
mailBuilder.constructionStage++;
}
case 1 -> {
String contents = String.join(" ", args.subList(0, args.size()));
mailBuilder.mail.mailContent.content = contents;
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_set_contents.replace("{contents}", contents));
CommandHandler.sendMessage(sender, translate("commands.sendMail.set_contents", contents));
mailBuilder.constructionStage++;
}
case 2 -> {
String msgSender = String.join(" ", args.subList(0, args.size()));
mailBuilder.mail.mailContent.sender = msgSender;
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_set_message_sender.replace("{send}", msgSender));
CommandHandler.sendMessage(sender, translate("commands.sendMail.set_message_sender", msgSender));
mailBuilder.constructionStage++;
}
case 3 -> {
@ -117,21 +119,21 @@ public final class SendMailCommand implements CommandHandler {
try {
refinement = Integer.parseInt(args.get(3));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_refinement);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemRefinement"));
return;
} // Fallthrough
case 3: // <itemId|itemName> [amount] [level]
try {
lvl = Integer.parseInt(args.get(2));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_level);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemLevel"));
return;
} // Fallthrough
case 2: // <itemId|itemName> [amount]
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount"));
return;
} // Fallthrough
case 1: // <itemId|itemName>
@ -139,46 +141,34 @@ public final class SendMailCommand implements CommandHandler {
item = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
// TODO: Parse from item name using GM Handbook.
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_item_id);
CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId"));
return;
}
break;
default: // *No args*
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Give_usage);
CommandHandler.sendMessage(sender, translate("commands.give.usage"));
return;
}
mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl));
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_send.replace("{amount}", Integer.toString(amount)).replace("{item}", Integer.toString(item)).replace("{lvl}", Integer.toString(lvl)));
CommandHandler.sendMessage(sender, translate("commands.sendMail.send", Integer.toString(amount), Integer.toString(item), Integer.toString(lvl)));
}
}
}
}
} else {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SendMail_invalid_arguments_please_use.replace("{args}", getConstructionArgs(mailBuilder.constructionStage)));
CommandHandler.sendMessage(sender, translate("commands.sendMail.invalid_arguments_please_use", getConstructionArgs(mailBuilder.constructionStage)));
}
}
}
private String getConstructionArgs(int stage) {
switch (stage) {
case 0 -> {
return Grasscutter.getLanguage().SendMail_title;
}
case 1 -> {
return Grasscutter.getLanguage().SendMail_message;
}
case 2 -> {
return Grasscutter.getLanguage().SendMail_sender;
}
case 3 -> {
return Grasscutter.getLanguage().SendMail_arguments;
}
default -> {
Thread.dumpStack();
return Grasscutter.getLanguage().SendMail_error.replace("{stage}", Integer.toString(stage));
}
}
return switch(stage) {
case 0 -> translate("commands.sendMail.title");
case 1 -> translate("commands.sendMail.message");
case 2 -> translate("commands.sendMail.sender");
case 3 -> translate("commands.sendMail.arguments");
default -> translate("commands.sendMail.error", Integer.toString(stage));
};
}
public static class MailBuilder {

View File

@ -1,29 +1,30 @@
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "say", usage = "say <message>", description = "Sends a message to a player as the server",
aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "say", usage = "say <message>",
aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage", description = "commands.sendMessage.description")
public final class SendMessageCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() == 0) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().SendMessage_usage);
CommandHandler.sendMessage(null, translate("commands.sendMessage.usage"));
return;
}
String message = String.join(" ", args);
CommandHandler.sendMessage(targetPlayer, message);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SenaMessage_message_sent);
CommandHandler.sendMessage(sender, translate("commands.sendMessage.success"));
}
}

View File

@ -2,7 +2,6 @@ package emu.grasscutter.command.commands;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
@ -10,27 +9,28 @@ import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "setfetterlevel", usage = "setfetterlevel <level>",
description = "Sets your fetter level for your current active character",
aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel")
aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel", description = "commands.setFetterLevel.description")
public final class SetFetterLevelCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() != 1) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetFetterLevel_usage);
CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.usage"));
return;
}
try {
int fetterLevel = Integer.parseInt(args.get(0));
if (fetterLevel < 0 || fetterLevel > 10) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetFetterLevel_fetter_level_must_between_0_and_10);
CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.range_error"));
return;
}
Avatar avatar = targetPlayer.getTeamManager().getCurrentAvatarEntity().getAvatar();
@ -42,9 +42,9 @@ public final class SetFetterLevelCommand implements CommandHandler {
avatar.save();
targetPlayer.sendPacket(new PacketAvatarFetterDataNotify(avatar));
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetFetterLevel_fetter_set_level.replace("{level}", Integer.toString(fetterLevel)));
CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.success", fetterLevel));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetFetterLevel_invalid_fetter_level);
CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.level_error"));
}
}

View File

@ -10,13 +10,14 @@ import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.languages.Language;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Language;
@Command(label = "setstats", usage = "setstats|stats <stat> <value>",
description = "Set fight property for your current active character", aliases = {"stats"}, permission = "player.setstats")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "setstats", usage = "setstats|stats <stat> <value>", aliases = {"stats"}, permission = "player.setstats", description = "commands.setStats.description")
public final class SetStatsCommand implements CommandHandler {
class Stat {
static class Stat {
String name;
FightProperty prop;
boolean percent;
@ -27,44 +28,43 @@ public final class SetStatsCommand implements CommandHandler {
this.percent = percent;
}
}
Map<String, Stat> stats;
Map<String, Stat> stats = new HashMap<>();
public SetStatsCommand() {
Language lang = Grasscutter.getLanguage();
stats = new HashMap<String, Stat>();
// Default stats
stats.put("maxhp", new Stat(lang.Stats_FIGHT_PROP_MAX_HP, FightProperty.FIGHT_PROP_MAX_HP, false));
stats.put("hp", new Stat(lang.Stats_FIGHT_PROP_CUR_HP, FightProperty.FIGHT_PROP_CUR_HP, false));
stats.put("atk", new Stat(lang.Stats_FIGHT_PROP_CUR_ATTACK, FightProperty.FIGHT_PROP_CUR_ATTACK, false));
stats.put("atkb", new Stat(lang.Stats_FIGHT_PROP_BASE_ATTACK, FightProperty.FIGHT_PROP_BASE_ATTACK, false)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff.
stats.put("def", new Stat(lang.Stats_FIGHT_PROP_DEFENSE, FightProperty.FIGHT_PROP_DEFENSE, false));
stats.put("em", new Stat(lang.Stats_FIGHT_PROP_ELEMENT_MASTERY, FightProperty.FIGHT_PROP_ELEMENT_MASTERY, false));
stats.put("er", new Stat(lang.Stats_FIGHT_PROP_CHARGE_EFFICIENCY, FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true));
stats.put("crate", new Stat(lang.Stats_FIGHT_PROP_CRITICAL, FightProperty.FIGHT_PROP_CRITICAL, true));
stats.put("cdmg", new Stat(lang.Stats_FIGHT_PROP_CRITICAL_HURT, FightProperty.FIGHT_PROP_CRITICAL_HURT, true));
stats.put("dmg", new Stat(lang.Stats_FIGHT_PROP_ADD_HURT, FightProperty.FIGHT_PROP_ADD_HURT, true)); // This seems to get reset after attacks
stats.put("eanemo", new Stat(lang.Stats_FIGHT_PROP_WIND_ADD_HURT, FightProperty.FIGHT_PROP_WIND_ADD_HURT, true));
stats.put("ecryo", new Stat(lang.Stats_FIGHT_PROP_ICE_ADD_HURT, FightProperty.FIGHT_PROP_ICE_ADD_HURT, true));
stats.put("edendro", new Stat(lang.Stats_FIGHT_PROP_GRASS_ADD_HURT, FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true));
stats.put("eelectro", new Stat(lang.Stats_FIGHT_PROP_ELEC_ADD_HURT, FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true));
stats.put("egeo", new Stat(lang.Stats_FIGHT_PROP_ROCK_ADD_HURT, FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true));
stats.put("ehydro", new Stat(lang.Stats_FIGHT_PROP_WATER_ADD_HURT, FightProperty.FIGHT_PROP_WATER_ADD_HURT, true));
stats.put("epyro", new Stat(lang.Stats_FIGHT_PROP_FIRE_ADD_HURT, FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true));
stats.put("ephys", new Stat(lang.Stats_FIGHT_PROP_PHYSICAL_ADD_HURT, FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true));
stats.put("resall", new Stat(lang.Stats_FIGHT_PROP_SUB_HURT, FightProperty.FIGHT_PROP_SUB_HURT, true)); // This seems to get reset after attacks
stats.put("resanemo", new Stat(lang.Stats_FIGHT_PROP_WIND_SUB_HURT, FightProperty.FIGHT_PROP_WIND_SUB_HURT, true));
stats.put("rescryo", new Stat(lang.Stats_FIGHT_PROP_ICE_SUB_HURT, FightProperty.FIGHT_PROP_ICE_SUB_HURT, true));
stats.put("resdendro", new Stat(lang.Stats_FIGHT_PROP_GRASS_SUB_HURT, FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true));
stats.put("reselectro", new Stat(lang.Stats_FIGHT_PROP_ELEC_SUB_HURT, FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true));
stats.put("resgeo", new Stat(lang.Stats_FIGHT_PROP_ROCK_SUB_HURT, FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true));
stats.put("reshydro", new Stat(lang.Stats_FIGHT_PROP_WATER_SUB_HURT, FightProperty.FIGHT_PROP_WATER_SUB_HURT, true));
stats.put("respyro", new Stat(lang.Stats_FIGHT_PROP_FIRE_SUB_HURT, FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true));
stats.put("resphys", new Stat(lang.Stats_FIGHT_PROP_PHYSICAL_SUB_HURT, FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true));
stats.put("cdr", new Stat(lang.Stats_FIGHT_PROP_SKILL_CD_MINUS_RATIO, FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true));
stats.put("heal", new Stat(lang.Stats_FIGHT_PROP_HEAL_ADD, FightProperty.FIGHT_PROP_HEAL_ADD, true));
stats.put("heali", new Stat(lang.Stats_FIGHT_PROP_HEALED_ADD, FightProperty.FIGHT_PROP_HEALED_ADD, true));
stats.put("shield", new Stat(lang.Stats_FIGHT_PROP_SHIELD_COST_MINUS_RATIO, FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true));
stats.put("defi", new Stat(lang.Stats_FIGHT_PROP_DEFENCE_IGNORE_RATIO, FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true));
stats.put("maxhp", new Stat(FightProperty.FIGHT_PROP_MAX_HP.toString(), FightProperty.FIGHT_PROP_MAX_HP, false));
stats.put("hp", new Stat(FightProperty.FIGHT_PROP_CUR_HP.toString(), FightProperty.FIGHT_PROP_CUR_HP, false));
stats.put("atk", new Stat(FightProperty.FIGHT_PROP_CUR_ATTACK.toString(), FightProperty.FIGHT_PROP_CUR_ATTACK, false));
stats.put("atkb", new Stat(FightProperty.FIGHT_PROP_BASE_ATTACK.toString(), FightProperty.FIGHT_PROP_BASE_ATTACK, false)); // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff.
stats.put("def", new Stat(FightProperty.FIGHT_PROP_DEFENSE.toString(), FightProperty.FIGHT_PROP_DEFENSE, false));
stats.put("em", new Stat(FightProperty.FIGHT_PROP_ELEMENT_MASTERY.toString(), FightProperty.FIGHT_PROP_ELEMENT_MASTERY, false));
stats.put("er", new Stat(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY.toString(), FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, true));
stats.put("crate", new Stat(FightProperty.FIGHT_PROP_CRITICAL.toString(), FightProperty.FIGHT_PROP_CRITICAL, true));
stats.put("cdmg", new Stat(FightProperty.FIGHT_PROP_CRITICAL_HURT.toString(), FightProperty.FIGHT_PROP_CRITICAL_HURT, true));
stats.put("dmg", new Stat(FightProperty.FIGHT_PROP_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ADD_HURT, true)); // This seems to get reset after attacks
stats.put("eanemo", new Stat(FightProperty.FIGHT_PROP_WIND_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WIND_ADD_HURT, true));
stats.put("ecryo", new Stat(FightProperty.FIGHT_PROP_ICE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ICE_ADD_HURT, true));
stats.put("edendro", new Stat(FightProperty.FIGHT_PROP_GRASS_ADD_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_ADD_HURT, true));
stats.put("eelectro", new Stat(FightProperty.FIGHT_PROP_ELEC_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_ADD_HURT, true));
stats.put("egeo", new Stat(FightProperty.FIGHT_PROP_ROCK_ADD_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_ADD_HURT, true));
stats.put("ehydro", new Stat(FightProperty.FIGHT_PROP_WATER_ADD_HURT.toString(), FightProperty.FIGHT_PROP_WATER_ADD_HURT, true));
stats.put("epyro", new Stat(FightProperty.FIGHT_PROP_FIRE_ADD_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_ADD_HURT, true));
stats.put("ephys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, true));
stats.put("resall", new Stat(FightProperty.FIGHT_PROP_SUB_HURT.toString(), FightProperty.FIGHT_PROP_SUB_HURT, true)); // This seems to get reset after attacks
stats.put("resanemo", new Stat(FightProperty.FIGHT_PROP_WIND_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WIND_SUB_HURT, true));
stats.put("rescryo", new Stat(FightProperty.FIGHT_PROP_ICE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ICE_SUB_HURT, true));
stats.put("resdendro", new Stat(FightProperty.FIGHT_PROP_GRASS_SUB_HURT.toString(), FightProperty.FIGHT_PROP_GRASS_SUB_HURT, true));
stats.put("reselectro", new Stat(FightProperty.FIGHT_PROP_ELEC_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ELEC_SUB_HURT, true));
stats.put("resgeo", new Stat(FightProperty.FIGHT_PROP_ROCK_SUB_HURT.toString(), FightProperty.FIGHT_PROP_ROCK_SUB_HURT, true));
stats.put("reshydro", new Stat(FightProperty.FIGHT_PROP_WATER_SUB_HURT.toString(), FightProperty.FIGHT_PROP_WATER_SUB_HURT, true));
stats.put("respyro", new Stat(FightProperty.FIGHT_PROP_FIRE_SUB_HURT.toString(), FightProperty.FIGHT_PROP_FIRE_SUB_HURT, true));
stats.put("resphys", new Stat(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT.toString(), FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, true));
stats.put("cdr", new Stat(FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SKILL_CD_MINUS_RATIO, true));
stats.put("heal", new Stat(FightProperty.FIGHT_PROP_HEAL_ADD.toString(), FightProperty.FIGHT_PROP_HEAL_ADD, true));
stats.put("heali", new Stat(FightProperty.FIGHT_PROP_HEALED_ADD.toString(), FightProperty.FIGHT_PROP_HEALED_ADD, true));
stats.put("shield", new Stat(FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO.toString(), FightProperty.FIGHT_PROP_SHIELD_COST_MINUS_RATIO, true));
stats.put("defi", new Stat(FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO.toString(), FightProperty.FIGHT_PROP_DEFENCE_IGNORE_RATIO, true));
// Compatibility aliases
stats.put("mhp", stats.get("maxhp"));
stats.put("cr", stats.get("crate"));
@ -175,26 +175,23 @@ public final class SetStatsCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
Language lang = Grasscutter.getLanguage();
String syntax = sender == null ? lang.SetStats_usage_console : lang.SetStats_usage_console;
String usage = syntax + lang.SetStats_help_message;
String syntax = sender == null ? translate("commands.setStats.usage_console") : translate("commands.setStats.ingame");
String usage = syntax + translate("commands.setStats.help_message");
String statStr;
String valueStr;
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, lang.Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
switch (args.size()) {
default:
CommandHandler.sendMessage(sender, usage);
return;
case 2:
statStr = args.get(0).toLowerCase();
valueStr = args.get(1);
break;
};
if (args.size() == 2) {
statStr = args.get(0).toLowerCase();
valueStr = args.get(1);
} else {
CommandHandler.sendMessage(sender, usage);
return;
}
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
@ -206,7 +203,7 @@ public final class SetStatsCommand implements CommandHandler {
value = Float.parseFloat(valueStr);
}
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, lang.SetStats_value_error);
CommandHandler.sendMessage(sender, translate("commands.setStats.value_error"));
return;
}
@ -220,15 +217,14 @@ public final class SetStatsCommand implements CommandHandler {
valueStr = String.format("%.0f", value);
}
if (targetPlayer == sender) {
CommandHandler.sendMessage(sender, lang.SetStats_set_self.replace("{name}", stat.name).replace("{value}", valueStr));
CommandHandler.sendMessage(sender, translate("commands.setStats.set_self", stat.name, valueStr));
} else {
String uidStr = targetPlayer.getAccount().getId();
CommandHandler.sendMessage(sender, lang.SetStats_set_for_uid.replace("{name}", stat.name).replace("{uid}", uidStr).replace("{value}", valueStr));
CommandHandler.sendMessage(sender, translate("commands.setStats.set_self", stat.name, uidStr, valueStr));
}
return;
} else {
CommandHandler.sendMessage(sender, usage);
return;
}
return;
}
}

View File

@ -7,27 +7,28 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "setworldlevel", usage = "setworldlevel <level>",
description = "Sets your world level (Relog to see proper effects)",
aliases = {"setworldlvl"}, permission = "player.setworldlevel")
aliases = {"setworldlvl"}, permission = "player.setworldlevel", description = "commands.setWorldLevel.description")
public final class SetWorldLevelCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() < 1) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetWorldLevel_usage);
CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.usage"));
return;
}
try {
int level = Integer.parseInt(args.get(0));
if (level > 8 || level < 0) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetWorldLevel_world_level_must_between_0_and_8);
CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.value_error"));
return;
}
@ -35,9 +36,9 @@ public final class SetWorldLevelCommand implements CommandHandler {
targetPlayer.getWorld().setWorldLevel(level);
targetPlayer.setWorldLevel(level);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().SetWorldLevel_set_world_level.replace("{level}", Integer.toString(level)));
CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.success", Integer.toString(level)));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().SetWorldLevel_invalid_world_level);
CommandHandler.sendMessage(null, translate("commands.setWorldLevel.invalid_world_level"));
}
}
}

View File

@ -20,14 +20,15 @@ import javax.swing.text.html.parser.Entity;
import java.util.List;
import java.util.Random;
@Command(label = "spawn", usage = "spawn <entityId> [amount] [level(monster only)]",
description = "Spawns an entity near you", permission = "server.spawn")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "spawn", usage = "spawn <entityId> [amount] [level(monster only)]", permission = "server.spawn", description = "commands.spawn.description")
public final class SpawnCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
@ -39,23 +40,23 @@ public final class SpawnCommand implements CommandHandler {
try {
level = Integer.parseInt(args.get(2));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments);
CommandHandler.sendMessage(sender, translate("commands.execution.argument_error"));
} // Fallthrough
case 2:
try {
amount = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_amount);
CommandHandler.sendMessage(sender, translate("commands.generic.error.amount"));
} // Fallthrough
case 1:
try {
id = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_entity_id);
CommandHandler.sendMessage(sender, translate("commands.generic.error.entityId"));
}
break;
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Spawn_usage);
CommandHandler.sendMessage(sender, translate("commands.spawn.usage"));
return;
}
@ -63,7 +64,7 @@ public final class SpawnCommand implements CommandHandler {
GadgetData gadgetData = GameData.getGadgetDataMap().get(id);
ItemData itemData = GameData.getItemDataMap().get(id);
if (monsterData == null && gadgetData == null && itemData == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_entity_id);
CommandHandler.sendMessage(sender, translate("commands.generic.error.entityId"));
return;
}
Scene scene = targetPlayer.getScene();
@ -99,7 +100,7 @@ public final class SpawnCommand implements CommandHandler {
scene.addEntity(entity);
}
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Spawn_message.replace("{amount}", Integer.toString(amount)).replace("{id}", Integer.toString(id)));
CommandHandler.sendMessage(sender, translate("commands.spawn.success", Integer.toString(amount), Integer.toString(id)));
}
private Position GetRandomPositionInCircle(Position origin, double radius){

View File

@ -7,17 +7,18 @@ import emu.grasscutter.game.player.Player;
import java.util.List;
@Command(label = "stop", usage = "stop",
description = "Stops the server", permission = "server.stop")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "stop", usage = "stop", permission = "server.stop", description = "commands.stop.description")
public final class StopCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
CommandHandler.sendMessage(null, Grasscutter.getLanguage().Stop_message);
CommandHandler.sendMessage(null, translate("commands.stop.success"));
for (Player p : Grasscutter.getGameServer().getPlayers().values()) {
CommandHandler.sendMessage(p, Grasscutter.getLanguage().Stop_message);
CommandHandler.sendMessage(p, translate("commands.stop.success"));
}
System.exit(1);
System.exit(1000);
}
}

View File

@ -12,13 +12,14 @@ import emu.grasscutter.server.packet.send.PacketAvatarSkillUpgradeRsp;
import java.util.List;
@Command(label = "talent", usage = "talent <talentID> <value>",
description = "Set talent level for your current active character", permission = "player.settalent")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "talent", usage = "talent <talentID> <value>", permission = "player.settalent", description = "commands.talent.description")
public final class TalentCommand implements CommandHandler {
private void setTalentLevel(Player sender, Player player, Avatar avatar, int talentId, int talentLevel) {
int oldLevel = avatar.getSkillLevelMap().get(talentId);
if (talentLevel < 0 || talentLevel > 15) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_lower_16);
CommandHandler.sendMessage(sender, translate("commands.talent.lower_16"));
return;
}
@ -30,29 +31,29 @@ public final class TalentCommand implements CommandHandler {
player.sendPacket(new PacketAvatarSkillChangeNotify(avatar, talentId, oldLevel, talentLevel));
player.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, talentId, oldLevel, talentLevel));
String successMessage = Grasscutter.getLanguage().Talent_set_id.replace("{id}", Integer.toString(talentId));
String successMessage = "commands.talent.set_id";
AvatarSkillDepotData depot = avatar.getData().getSkillDepot();
if (talentId == depot.getSkills().get(0)) {
successMessage = Grasscutter.getLanguage().Talent_set_atk;
successMessage = "commands.talent.set_atk";
} else if (talentId == depot.getSkills().get(1)) {
successMessage = Grasscutter.getLanguage().Talent_set_e;
successMessage = "commands.talent.set_e";
} else if (talentId == depot.getEnergySkill()) {
successMessage = Grasscutter.getLanguage().Talent_set_q;
successMessage = "commands.talent.set_q";
}
CommandHandler.sendMessage(sender, successMessage.replace("{level}", Integer.toString(talentLevel)));
CommandHandler.sendMessage(sender, translate(successMessage, talentLevel));
}
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (args.size() < 1){
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_1);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_2);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_3);
CommandHandler.sendMessage(sender, translate("commands.talent.usage_1"));
CommandHandler.sendMessage(sender, translate("commands.talent.usage_2"));
CommandHandler.sendMessage(sender, translate("commands.talent.usage_3"));
return;
}
@ -60,66 +61,54 @@ public final class TalentCommand implements CommandHandler {
Avatar avatar = entity.getAvatar();
String cmdSwitch = args.get(0);
switch (cmdSwitch) {
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_1);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_2);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_3);
return;
case "set":
if (args.size() < 3){
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_1);
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_3);
default -> {
CommandHandler.sendMessage(sender, translate("commands.talent.usage_1"));
CommandHandler.sendMessage(sender, translate("commands.talent.usage_2"));
CommandHandler.sendMessage(sender, translate("commands.talent.usage_3"));
return;
}
case "set" -> {
if (args.size() < 3) {
CommandHandler.sendMessage(sender, translate("commands.talent.usage_1"));
CommandHandler.sendMessage(sender, translate("commands.talent.usage_3"));
return;
}
try {
int skillId = Integer.parseInt(args.get(1));
int newLevel = Integer.parseInt(args.get(2));
setTalentLevel(sender, targetPlayer, avatar, skillId, newLevel);
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_invalid_skill_id);
CommandHandler.sendMessage(sender, translate("commands.talent.invalid_skill_id"));
return;
}
break;
case "n":
case "e":
case "q":
if (args.size() < 2){
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_usage_2);
}
case "n", "e", "q" -> {
if (args.size() < 2) {
CommandHandler.sendMessage(sender, translate("commands.talent.usage_2"));
return;
}
AvatarSkillDepotData SkillDepot = avatar.getData().getSkillDepot();
int skillId;
switch (cmdSwitch) {
default:
skillId = SkillDepot.getSkills().get(0);
break;
case "e":
skillId = SkillDepot.getSkills().get(1);
break;
case "q":
skillId = SkillDepot.getEnergySkill();
break;
}
int skillId = switch (cmdSwitch) {
default -> SkillDepot.getSkills().get(0);
case "e" -> SkillDepot.getSkills().get(1);
case "q" -> SkillDepot.getEnergySkill();
};
try {
int newLevel = Integer.parseInt(args.get(1));
setTalentLevel(sender, targetPlayer, avatar, skillId, newLevel);
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_invalid_talent_level);
CommandHandler.sendMessage(sender, translate("commands.talent.invalid_level"));
return;
}
break;
case "getid":
}
case "getid" -> {
int skillIdNorAtk = avatar.getData().getSkillDepot().getSkills().get(0);
int skillIdE = avatar.getData().getSkillDepot().getSkills().get(1);
int skillIdQ = avatar.getData().getSkillDepot().getEnergySkill();
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_normal_attack_id.replace("{id}", Integer.toString(skillIdNorAtk)));
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_e_skill_id.replace("{id}", Integer.toString(skillIdE)));
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Talent_q_skill_id.replace("{id}", Integer.toString(skillIdQ)));
break;
CommandHandler.sendMessage(sender, translate("commands.talent.normal_attack_id", Integer.toString(skillIdNorAtk)));
CommandHandler.sendMessage(sender, translate("commands.talent.e_skill_id", Integer.toString(skillIdE)));
CommandHandler.sendMessage(sender, translate("commands.talent.q_skill_id", Integer.toString(skillIdQ)));
}
}
}
}

View File

@ -8,18 +8,20 @@ import emu.grasscutter.utils.Position;
import java.util.List;
@Command(label = "tpall", usage = "tpall",
description = "Teleports all players in your world to your position", permission = "player.tpall")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "tpall", usage = "tpall", permission = "player.tpall", description = "commands.teleportAll.description")
public final class TeleportAllCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
if (!targetPlayer.getWorld().isMultiplayer()) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().TeleportAll_message);
CommandHandler.sendMessage(sender, translate("commands.teleportAll.error"));
return;
}
@ -30,5 +32,7 @@ public final class TeleportAllCommand implements CommandHandler {
player.getWorld().transferPlayerToScene(player, targetPlayer.getSceneId(), pos);
}
CommandHandler.sendMessage(sender, translate("commands.teleportAll.success"));
}
}

View File

@ -8,8 +8,9 @@ import emu.grasscutter.utils.Position;
import java.util.List;
@Command(label = "teleport", usage = "teleport <x> <y> <z> [scene id]", aliases = {"tp"},
description = "Change the player's position.", permission = "player.teleport")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "teleport", usage = "teleport <x> <y> <z> [scene id]", aliases = {"tp"}, permission = "player.teleport", description = "commands.teleport.description")
public final class TeleportCommand implements CommandHandler {
private float parseRelative(String input, Float current) { // TODO: Maybe this will be useful elsewhere later
@ -26,7 +27,7 @@ public final class TeleportCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
@ -41,7 +42,7 @@ public final class TeleportCommand implements CommandHandler {
try {
sceneId = Integer.parseInt(args.get(3));
}catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Invalid_arguments);
CommandHandler.sendMessage(sender, translate("commands.execution.argument_error"));
} // Fallthrough
case 3:
try {
@ -49,20 +50,23 @@ public final class TeleportCommand implements CommandHandler {
y = parseRelative(args.get(1), y);
z = parseRelative(args.get(2), z);
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Teleport_invalid_position);
CommandHandler.sendMessage(sender, translate("commands.teleport.invalid_position"));
}
break;
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Teleport_usage);
CommandHandler.sendMessage(sender, translate("commands.teleport.usage"));
return;
}
Position target_pos = new Position(x, y, z);
boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, target_pos);
if (!result) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Teleport_invalid_position);
CommandHandler.sendMessage(sender, translate("commands.teleport.invalid_position"));
} else {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Teleport_message.replace("{name}", targetPlayer.getNickname()).replace("{x}", Float.toString(x)).replace("{y}", Float.toString(y)).replace("{z}", Float.toString(z)).replace("{id}", Integer.toString(sceneId)));
CommandHandler.sendMessage(sender, translate("commands.teleport.success",
targetPlayer.getNickname(), Float.toString(x), Float.toString(y),
Float.toString(z), Integer.toString(sceneId))
);
}
}

View File

@ -9,14 +9,15 @@ import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify;
import java.util.List;
@Command(label = "weather", usage = "weather <weatherId> [climateId]",
description = "Changes the weather.", aliases = {"w"}, permission = "player.weather")
import static emu.grasscutter.utils.Language.translate;
@Command(label = "weather", usage = "weather <weatherId> [climateId]", aliases = {"w"}, permission = "player.weather", description = "commands.weather.description")
public final class WeatherCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Target_needed);
CommandHandler.sendMessage(sender, translate("commands.execution.need_target"));
return;
}
@ -27,17 +28,17 @@ public final class WeatherCommand implements CommandHandler {
try {
climateId = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Weather_invalid_id);
CommandHandler.sendMessage(sender, translate("commands.weather.invalid_id"));
}
case 1:
try {
weatherId = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignored) {
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Weather_invalid_id);
CommandHandler.sendMessage(sender, translate("commands.weather.invalid_id"));
}
break;
default:
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Weather_usage);
CommandHandler.sendMessage(sender, translate("commands.weather.usage"));
return;
}
@ -46,7 +47,6 @@ public final class WeatherCommand implements CommandHandler {
targetPlayer.getScene().setWeather(weatherId);
targetPlayer.getScene().setClimate(climate);
targetPlayer.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(targetPlayer));
CommandHandler.sendMessage(sender, Grasscutter.getLanguage().Weather_message.replace("{weatherId}", Integer.toString(weatherId)).replace("{climateId}", Integer.toString(climateId)));
CommandHandler.sendMessage(sender, translate("commands.weather.success", Integer.toString(weatherId), Integer.toString(climateId)));
}
}

View File

@ -7,6 +7,7 @@ import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.gson.Gson;
import emu.grasscutter.utils.Utils;
import org.reflections.Reflections;
@ -120,14 +121,15 @@ public class ResourceLoader {
@SuppressWarnings({"rawtypes", "unchecked"})
protected static void loadFromResource(Class<?> c, String fileName, Int2ObjectMap map) throws Exception {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName)) {
List list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, c).getType());
FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName);
Gson gson = Grasscutter.getGsonFactory();
List list = gson.fromJson(fileReader, List.class);
for (Object o : list) {
GameResource res = (GameResource) o;
res.onLoad();
map.put(res.getId(), res);
}
for (Object o : list) {
Map<String, Object> tempMap = Utils.switchPropertiesUpperLowerCase((Map<String, Object>) o, c);
GameResource res = gson.fromJson(gson.toJson(tempMap), TypeToken.get(c).getType());
res.onLoad();
map.put(res.getId(), res);
}
}

View File

@ -117,6 +117,7 @@ public class EntityMonster extends GameEntity {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) {
getScene().getScriptManager().onMonsterDie();
getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null);
}
if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) {

View File

@ -0,0 +1,44 @@
package emu.grasscutter.game.expedition;
import dev.morphia.annotations.Entity;
@Entity
public class ExpeditionInfo {
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public int getExpId() {
return expId;
}
public void setExpId(int expId) {
this.expId = expId;
}
public int getHourTime() {
return hourTime;
}
public void setHourTime(int hourTime) {
this.hourTime = hourTime;
}
public int getStartTime() {
return startTime;
}
public void setStartTime(int startTime) {
this.startTime = startTime;
}
private int state;
private int expId;
private int hourTime;
private int startTime;
}

View File

@ -0,0 +1,46 @@
package emu.grasscutter.game.expedition;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.io.FileReader;
import java.util.Collection;
import java.util.List;
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;
public ExpeditionManager(GameServer gameServer) {
this.gameServer = gameServer;
this.expeditionRewardData = new Int2ObjectOpenHashMap<>();
this.load();
}
public synchronized void load() {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ExpeditionReward.json")) {
getExpeditionRewardDataList().clear();
List<ExpeditionRewardInfo> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ExpeditionRewardInfo.class).getType());
if(banners.size() > 0) {
for (ExpeditionRewardInfo di : banners) {
getExpeditionRewardDataList().put(di.getExpId(), di.getExpeditionRewardDataList());
}
Grasscutter.getLogger().info("Expedition reward successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load expedition reward. Expedition reward size is 0.");
}
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load expedition reward.", e);
}
}
}

View File

@ -0,0 +1,18 @@
package emu.grasscutter.game.expedition;
public class ExpeditionRewardData {
private int itemId;
private int minCount;
private int maxCount;
public int getItemId() {
return itemId;
}
public int getMinCount() { return minCount; }
public int getMaxCount() {
return maxCount;
}
}

View File

@ -0,0 +1,15 @@
package emu.grasscutter.game.expedition;
import java.util.List;
public class ExpeditionRewardDataList {
public int getHourTime() {
return hourTime;
}
public List<ExpeditionRewardData> getExpeditionRewardData() {
return expeditionRewardData;
}
private int hourTime;
private List<ExpeditionRewardData> expeditionRewardData;
}

View File

@ -0,0 +1,16 @@
package emu.grasscutter.game.expedition;
import java.util.List;
public class ExpeditionRewardInfo {
public int getExpId() {
return expId;
}
public List<ExpeditionRewardDataList> getExpeditionRewardDataList() {
return expeditionRewardDataList;
}
private int expId;
private List<ExpeditionRewardDataList> expeditionRewardDataList;
}

View File

@ -96,7 +96,7 @@ public class GachaBanner {
return toProto("");
}
public GachaInfo toProto(String sessionKey) {
String record = "https://"
String record = "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://"
+ (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ?
Grasscutter.getConfig().getDispatchOptions().Ip :
Grasscutter.getConfig().getDispatchOptions().PublicIp)

View File

@ -1,359 +0,0 @@
package emu.grasscutter.game.managers.MovementManager;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.VectorOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Position;
import org.jetbrains.annotations.NotNull;
import java.lang.Math;
import java.util.*;
public class MovementManager {
public HashMap<String, HashSet<MotionState>> MotionStatesCategorized = new HashMap<>();
private enum Consumption {
None(0),
// consume
CLIMB_START(-500),
CLIMBING(-150),
CLIMB_JUMP(-2500),
DASH(-1800),
SPRINT(-360),
FLY(-60),
SWIM_DASH_START(-200),
SWIM_DASH(-200),
SWIMMING(-80),
// restore
STANDBY(500),
RUN(500),
WALK(500),
STANDBY_MOVE(500),
POWERED_FLY(500);
public final int amount;
Consumption(int amount) {
this.amount = amount;
}
}
private MotionState previousState = MotionState.MOTION_STANDBY;
private MotionState currentState = MotionState.MOTION_STANDBY;
private Position previousCoordinates = new Position(0, 0, 0);
private Position currentCoordinates = new Position(0, 0, 0);
private final Player player;
private float landSpeed = 0;
private Timer movementManagerTickTimer;
private GameSession cachedSession = null;
private GameEntity cachedEntity = null;
private int staminaRecoverDelay = 0;
public MovementManager(Player player) {
previousCoordinates.add(new Position(0,0,0));
this.player = player;
MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList(
MotionState.MOTION_SWIM_MOVE,
MotionState.MOTION_SWIM_IDLE,
MotionState.MOTION_SWIM_DASH,
MotionState.MOTION_SWIM_JUMP
)));
MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList(
MotionState.MOTION_STANDBY,
MotionState.MOTION_STANDBY_MOVE,
MotionState.MOTION_DANGER_STANDBY,
MotionState.MOTION_DANGER_STANDBY_MOVE,
MotionState.MOTION_LADDER_TO_STANDBY,
MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY
)));
MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList(
MotionState.MOTION_CLIMB,
MotionState.MOTION_CLIMB_JUMP,
MotionState.MOTION_STANDBY_TO_CLIMB,
MotionState.MOTION_LADDER_IDLE,
MotionState.MOTION_LADDER_MOVE,
MotionState.MOTION_LADDER_SLIP,
MotionState.MOTION_STANDBY_TO_LADDER
)));
MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList(
MotionState.MOTION_FLY,
MotionState.MOTION_FLY_IDLE,
MotionState.MOTION_FLY_SLOW,
MotionState.MOTION_FLY_FAST,
MotionState.MOTION_POWERED_FLY
)));
MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList(
MotionState.MOTION_DASH,
MotionState.MOTION_DANGER_DASH,
MotionState.MOTION_DASH_BEFORE_SHAKE,
MotionState.MOTION_RUN,
MotionState.MOTION_DANGER_RUN,
MotionState.MOTION_WALK,
MotionState.MOTION_DANGER_WALK
)));
}
public void handle(GameSession session, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo, GameEntity entity) {
if (movementManagerTickTimer == null) {
movementManagerTickTimer = new Timer();
movementManagerTickTimer.scheduleAtFixedRate(new MotionManagerTick(), 0, 200);
}
// cache info for later use in tick
cachedSession = session;
cachedEntity = entity;
MotionInfo motionInfo = moveInfo.getMotionInfo();
moveEntity(entity, moveInfo);
VectorOuterClass.Vector posVector = motionInfo.getPos();
Position newPos = new Position(posVector.getX(),
posVector.getY(), posVector.getZ());;
if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) {
currentCoordinates = newPos;
}
currentState = motionInfo.getState();
Grasscutter.getLogger().debug("" + currentState);
handleFallOnGround(motionInfo);
}
public void resetTimer() {
movementManagerTickTimer.cancel();
movementManagerTickTimer = null;
}
private void moveEntity(GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) {
entity.getPosition().set(moveInfo.getMotionInfo().getPos());
entity.getRotation().set(moveInfo.getMotionInfo().getRot());
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
entity.setMotionState(moveInfo.getMotionInfo().getState());
}
private boolean isPlayerMoving() {
float diffX = currentCoordinates.getX() - previousCoordinates.getX();
float diffY = currentCoordinates.getY() - previousCoordinates.getY();
float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ();
// Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ);
return Math.abs(diffX) > 0.2 || Math.abs(diffY) > 0.1 || Math.abs(diffZ) > 0.2;
}
private int getCurrentStamina() {
return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
}
private int getMaximumStamina() {
return player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
}
// Returns new stamina
public int updateStamina(GameSession session, int amount) {
int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
if (amount == 0) {
return currentStamina;
}
int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA);
int newStamina = currentStamina + amount;
if (newStamina < 0) {
newStamina = 0;
}
if (newStamina > playerMaxStamina) {
newStamina = playerMaxStamina;
}
session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
return newStamina;
}
private void handleFallOnGround(@NotNull MotionInfo motionInfo) {
MotionState state = motionInfo.getState();
// land speed and fall on ground event arrive in different packets
// cache land speed
if (state == MotionState.MOTION_LAND_SPEED) {
landSpeed = motionInfo.getSpeed().getY();
}
if (state == MotionState.MOTION_FALL_ON_GROUND) {
float currentHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float damage = 0;
// Grasscutter.getLogger().debug("LandSpeed: " + landSpeed);
if (landSpeed < -23.5) {
damage = (float)(maxHP * 0.33);
}
if (landSpeed < -25) {
damage = (float)(maxHP * 0.5);
}
if (landSpeed < -26.5) {
damage = (float)(maxHP * 0.66);
}
if (landSpeed < -28) {
damage = (maxHP * 1);
}
float newHP = currentHP - damage;
if (newHP < 0) {
newHP = 0;
}
// Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP);
cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP));
if (newHP == 0) {
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_FALL);
}
landSpeed = 0;
}
}
private void handleDrowning() {
int stamina = getCurrentStamina();
if (stamina < 10) {
boolean isSwimming = MotionStatesCategorized.get("SWIM").contains(currentState);
Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming);
if (isSwimming && currentState != MotionState.MOTION_SWIM_IDLE) {
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN);
}
}
}
public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) {
cachedSession.send(new PacketAvatarLifeStateChangeNotify(
cachedSession.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(),
LifeState.LIFE_DEAD,
dieType
));
cachedSession.send(new PacketLifeStateChangeNotify(
cachedEntity,
LifeState.LIFE_DEAD,
dieType
));
cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0);
cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP));
entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD));
session.getPlayer().getScene().removeEntity(entity);
((EntityAvatar)entity).onDeath(dieType, 0);
}
private class MotionManagerTick extends TimerTask
{
public void run() {
if (Grasscutter.getConfig().OpenStamina) {
boolean moving = isPlayerMoving();
if (moving || (getCurrentStamina() < getMaximumStamina())) {
Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina");
Consumption consumption = Consumption.None;
// TODO: refactor these conditions.
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
if (currentState == MotionState.MOTION_CLIMB) {
// CLIMB
if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) {
consumption = Consumption.CLIMB_START;
} else {
consumption = Consumption.CLIMBING;
}
}
if (currentState == MotionState.MOTION_CLIMB_JUMP) {
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
consumption = Consumption.CLIMB_JUMP;
}
}
if (currentState == MotionState.MOTION_JUMP) {
if (previousState == MotionState.MOTION_CLIMB) {
consumption = Consumption.CLIMB_JUMP;
}
}
} else if (MotionStatesCategorized.get("SWIM").contains((currentState))) {
// SWIM
if (currentState == MotionState.MOTION_SWIM_MOVE) {
consumption = Consumption.SWIMMING;
}
if (currentState == MotionState.MOTION_SWIM_DASH) {
if (previousState != MotionState.MOTION_SWIM_DASH) {
consumption = Consumption.SWIM_DASH_START;
} else {
consumption = Consumption.SWIM_DASH;
}
}
} else if (MotionStatesCategorized.get("RUN").contains(currentState)) {
// RUN, DASH and WALK
// DASH
if (currentState == MotionState.MOTION_DASH) {
if (previousState == MotionState.MOTION_DASH) {
consumption = Consumption.SPRINT;
} else {
// cost more to start dashing
consumption = Consumption.DASH;
}
}
// RUN
if (currentState == MotionState.MOTION_RUN) {
consumption = Consumption.RUN;
}
// WALK
if (currentState == MotionState.MOTION_WALK) {
consumption = Consumption.WALK;
}
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
// FLY
consumption = Consumption.FLY;
// POWERED_FLY, e.g. wind tunnel
if (currentState == MotionState.MOTION_POWERED_FLY) {
consumption = Consumption.POWERED_FLY;
}
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
// STAND
if (currentState == MotionState.MOTION_STANDBY) {
consumption = Consumption.STANDBY;
}
if (currentState == MotionState.MOTION_STANDBY_MOVE) {
consumption = Consumption.STANDBY_MOVE;
}
}
// tick triggered
handleDrowning();
if (cachedSession != null) {
if (consumption.amount < 0) {
staminaRecoverDelay = 0;
}
if (consumption.amount > 0) {
if (staminaRecoverDelay < 5) {
staminaRecoverDelay++;
consumption = Consumption.None;
}
}
int newStamina = updateStamina(cachedSession, consumption.amount);
cachedSession.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t" + consumption + "(" + consumption.amount + ")");
}
}
}
previousState = currentState;
previousCoordinates = new Position(currentCoordinates.getX(),
currentCoordinates.getY(), currentCoordinates.getZ());;
}
}
}

View File

@ -1,7 +1,8 @@
package emu.grasscutter.game.managers.SotSManager;
package emu.grasscutter.game.managers;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
@ -14,6 +15,8 @@ import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotif
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
// Statue of the Seven Manager
public class SotSManager {
@ -21,6 +24,9 @@ public class SotSManager {
// NOTE: Spring volume balance *1 = fight prop HP *100
private final Player player;
private Timer autoRecoverTimer;
public final static int GlobalMaximumSpringVolume = 8500000;
public SotSManager(Player player) {
this.player = player;
@ -46,26 +52,44 @@ public class SotSManager {
public void autoRevive(GameSession session) {
player.getTeamManager().getActiveTeam().forEach(entity -> {
boolean isAlive = entity.isAlive();
float currentHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
// Grasscutter.getLogger().debug("" + entity.getAvatar().getAvatarData().getName() + "\t" + currentHP + "/" + maxHP + "\t" + (isAlive ? "ALIVE":"DEAD"));
float newHP = (float)(maxHP * 0.3);
if (currentHP < newHP) {
updateAvatarCurHP(session, entity, newHP);
}
if (!isAlive) {
float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float newHP = (float)(maxHP * 0.3);
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
}
});
}
public void scheduleAutoRecover(GameSession session) {
// TODO: play audio effects? possibly client side? - client automatically plays.
// delay 2.5 seconds
new Thread(() -> {
try {
Thread.sleep(2500);
autoRecover(session);
} catch (Exception e) {
Grasscutter.getLogger().error(e.getMessage());
}
}).start();
if (autoRecoverTimer == null) {
autoRecoverTimer = new Timer();
autoRecoverTimer.schedule(new AutoRecoverTimerTick(session), 2500);
}
}
public void cancelAutoRecover() {
if (autoRecoverTimer != null) {
autoRecoverTimer.cancel();
autoRecoverTimer = null;
}
}
private class AutoRecoverTimerTick extends TimerTask
{
private GameSession session;
public AutoRecoverTimerTick(GameSession session) {
this.session = session;
}
public void run() {
autoRecover(session);
cancelAutoRecover();
}
}
public void refillSpringVolume() {
@ -124,24 +148,27 @@ public class SotSManager {
float newHP = currentHP + needSV / 100; // convert SV to HP
// TODO: Figure out why client shows current HP instead of added HP.
// Say an avatar had 12000 and now has 14000, it should show "2000".
// The client always show "+14000" which is incorrect.
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP,
newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER,
ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue));
session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
Avatar avatar = entity.getAvatar();
avatar.setCurrentHp(newHP);
session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP));
player.save();
updateAvatarCurHP(session, entity, newHP);
}
});
}
}
private void updateAvatarCurHP(GameSession session, EntityAvatar entity, float newHP) {
// TODO: Figure out why client shows current HP instead of added HP.
// Say an avatar had 12000 and now has 14000, it should show "2000".
// The client always show "+14000" which is incorrect.
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP,
newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER,
ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue));
session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
Avatar avatar = entity.getAvatar();
avatar.setCurrentHp(newHP);
session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP));
player.save();
}
}

View File

@ -0,0 +1,12 @@
package emu.grasscutter.game.managers.StaminaManager;
public interface AfterUpdateStaminaListener {
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update.
*
* @param reason Why updating stamina.
* @param newStamina New Stamina value.
*/
void onAfterUpdateStamina(String reason, int newStamina);
}

View File

@ -0,0 +1,20 @@
package emu.grasscutter.game.managers.StaminaManager;
public interface BeforeUpdateStaminaListener {
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update.
* @param reason Why updating stamina.
* @param newStamina New ABSOLUTE stamina value.
* @return true if you want to cancel this update, otherwise false.
*/
int onBeforeUpdateStamina(String reason, int newStamina);
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
* This gives listeners a chance to intercept this update.
* @param reason Why updating stamina.
* @param consumption ConsumptionType and RELATIVE stamina change amount.
* @return true if you want to cancel this update, otherwise false.
*/
Consumption onBeforeUpdateStamina(String reason, Consumption consumption);
}

View File

@ -0,0 +1,15 @@
package emu.grasscutter.game.managers.StaminaManager;
public class Consumption {
public ConsumptionType consumptionType;
public int amount;
public Consumption(ConsumptionType ct, int a) {
consumptionType = ct;
amount = a;
}
public Consumption(ConsumptionType ct) {
this(ct, ct.amount);
}
}

View File

@ -0,0 +1,30 @@
package emu.grasscutter.game.managers.StaminaManager;
public enum ConsumptionType {
None(0),
// consume
CLIMB_START(-500),
CLIMBING(-150),
CLIMB_JUMP(-2500),
SPRINT(-1800),
DASH(-360),
FLY(-60),
SWIM_DASH_START(-20),
SWIM_DASH(-204),
SWIMMING(-80), // TODO: Slow swimming is handled per movement, not per second. Movement frequency depends on gender/age/height.
FIGHT(0), // See StaminaManager.getFightConsumption()
// restore
STANDBY(500),
RUN(500),
WALK(500),
STANDBY_MOVE(500),
POWERED_FLY(500);
public final int amount;
ConsumptionType(int amount) {
this.amount = amount;
}
}

View File

@ -0,0 +1,73 @@
# Stamina Manager
---
## UpdateStamina
```java
// will use consumption.consumptionType as reason
public int updateStaminaRelative(GameSession session, Consumption consumption);
```
```java
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina)
```
---
## Pause and Resume
```java
public void startSustainedStaminaHandler()
```
```java
public void stopSustainedStaminaHandler()
```
---
## Stamina change listeners and intercepting
### BeforeUpdateStaminaListener
```java
import emu.grasscutter.game.managers.StaminaManager.BeforeUpdateStaminaListener;
// Listener sample: plugin disable CLIMB_JUMP stamina cost.
private class MyClass implements BeforeUpdateStaminaListener {
// Make your class implement the listener, and pass in your class as a listener.
public MyClass() {
getStaminaManager().registerBeforeUpdateStaminaListener("myClass", this);
}
@Override
public boolean onBeforeUpdateStamina(String reason, int newStamina) {
// do not intercept this update
return false;
}
@Override
public boolean onBeforeUpdateStamina(String reason, Consumption consumption) {
// Try to intercept if this update is CLIMB_JUMP
if (consumption.consumptionType == ConsumptionType.CLIMB_JUMP) {
return true;
}
// If it is not CLIMB_JUMP, do not intercept.
return false;
}
}
```
### AfterUpdateStaminaListener
```java
import emu.grasscutter.game.managers.StaminaManager.AfterUpdateStaminaListener;
// Listener sample: plugin listens for changes already made.
private class MyClass implements AfterUpdateStaminaListener {
// Make your class implement the listener, and pass in your class as a listener.
public MyClass() {
registerAfterUpdateStaminaListener("myClass", this);
}
@Override
public void onAfterUpdateStamina(String reason, int newStamina) {
// ...
}
}
```

View File

@ -0,0 +1,472 @@
package emu.grasscutter.game.managers.StaminaManager;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo;
import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Position;
import java.lang.Math;
import java.util.*;
public class StaminaManager {
private final Player player;
private HashMap<String, HashSet<MotionState>> MotionStatesCategorized = new HashMap<>();
public final static int GlobalMaximumStamina = 24000;
private Position currentCoordinates = new Position(0, 0, 0);
private Position previousCoordinates = new Position(0, 0, 0);
private MotionState currentState = MotionState.MOTION_STANDBY;
private MotionState previousState = MotionState.MOTION_STANDBY;
private Timer sustainedStaminaHandlerTimer;
private GameSession cachedSession = null;
private GameEntity cachedEntity = null;
private int staminaRecoverDelay = 0;
private HashMap<String, BeforeUpdateStaminaListener> beforeUpdateStaminaListeners = new HashMap<>();
private HashMap<String, AfterUpdateStaminaListener> afterUpdateStaminaListeners = new HashMap<>();
public StaminaManager(Player player) {
this.player = player;
MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList(
MotionState.MOTION_SWIM_MOVE,
MotionState.MOTION_SWIM_IDLE,
MotionState.MOTION_SWIM_DASH,
MotionState.MOTION_SWIM_JUMP
)));
MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList(
MotionState.MOTION_STANDBY,
MotionState.MOTION_STANDBY_MOVE,
MotionState.MOTION_DANGER_STANDBY,
MotionState.MOTION_DANGER_STANDBY_MOVE,
MotionState.MOTION_LADDER_TO_STANDBY,
MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY
)));
MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList(
MotionState.MOTION_CLIMB,
MotionState.MOTION_CLIMB_JUMP,
MotionState.MOTION_STANDBY_TO_CLIMB,
MotionState.MOTION_LADDER_IDLE,
MotionState.MOTION_LADDER_MOVE,
MotionState.MOTION_LADDER_SLIP,
MotionState.MOTION_STANDBY_TO_LADDER
)));
MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList(
MotionState.MOTION_FLY,
MotionState.MOTION_FLY_IDLE,
MotionState.MOTION_FLY_SLOW,
MotionState.MOTION_FLY_FAST,
MotionState.MOTION_POWERED_FLY
)));
MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList(
MotionState.MOTION_DASH,
MotionState.MOTION_DANGER_DASH,
MotionState.MOTION_DASH_BEFORE_SHAKE,
MotionState.MOTION_RUN,
MotionState.MOTION_DANGER_RUN,
MotionState.MOTION_WALK,
MotionState.MOTION_DANGER_WALK
)));
MotionStatesCategorized.put("FIGHT", new HashSet<>(Arrays.asList(
MotionState.MOTION_FIGHT
)));
MotionStatesCategorized.put("SKIFF", new HashSet<>(Arrays.asList(
MotionState.MOTION_SKIFF_BOARDING,
MotionState.MOTION_SKIFF_NORMAL,
MotionState.MOTION_SKIFF_DASH,
MotionState.MOTION_SKIFF_POWERED_DASH
)));
}
// Listeners
public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) {
if (beforeUpdateStaminaListeners.containsKey(listenerName)) {
return false;
}
beforeUpdateStaminaListeners.put(listenerName, listener);
return true;
}
public boolean unregisterBeforeUpdateStaminaListener(String listenerName) {
if (!beforeUpdateStaminaListeners.containsKey(listenerName)) {
return false;
}
beforeUpdateStaminaListeners.remove(listenerName);
return true;
}
public boolean registerAfterUpdateStaminaListener(String listenerName, AfterUpdateStaminaListener listener) {
if (afterUpdateStaminaListeners.containsKey(listenerName)) {
return false;
}
afterUpdateStaminaListeners.put(listenerName, listener);
return true;
}
public boolean unregisterAfterUpdateStaminaListener(String listenerName) {
if (!afterUpdateStaminaListeners.containsKey(listenerName)) {
return false;
}
afterUpdateStaminaListeners.remove(listenerName);
return true;
}
private boolean isPlayerMoving() {
float diffX = currentCoordinates.getX() - previousCoordinates.getX();
float diffY = currentCoordinates.getY() - previousCoordinates.getY();
float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ();
Grasscutter.getLogger().trace("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates +
", " + diffX + ", " + diffY + ", " + diffZ);
return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3;
}
public int updateStaminaRelative(GameSession session, Consumption consumption) {
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
if (consumption.amount == 0) {
return currentStamina;
}
// notify will update
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.consumptionType.toString(), consumption);
if ((overriddenConsumption.consumptionType != consumption.consumptionType) && (overriddenConsumption.amount != consumption.amount)) {
Grasscutter.getLogger().debug("[StaminaManager] Stamina update relative(" +
consumption.consumptionType.toString() + ", " + consumption.amount + ") overridden to relative(" +
consumption.consumptionType.toString() + ", " + consumption.amount + ") by: " + listener.getKey());
return currentStamina;
}
}
int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
Grasscutter.getLogger().trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" +
(isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.consumptionType + "," +
consumption.amount + ")");
int newStamina = currentStamina + consumption.amount;
if (newStamina < 0) {
newStamina = 0;
} else if (newStamina > playerMaxStamina) {
newStamina = playerMaxStamina;
}
return setStamina(session, consumption.consumptionType.toString(), newStamina);
}
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) {
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
// notify will update
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina);
if (overriddenNewStamina != newStamina) {
Grasscutter.getLogger().debug("[StaminaManager] Stamina update absolute(" +
reason + ", " + newStamina + ") overridden to absolute(" +
reason + ", " + newStamina + ") by: " + listener.getKey());
return currentStamina;
}
}
int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
if (newStamina < 0) {
newStamina = 0;
} else if (newStamina > playerMaxStamina) {
newStamina = playerMaxStamina;
}
return setStamina(session, reason, newStamina);
}
// Returns new stamina and sends PlayerPropNotify
public int setStamina(GameSession session, String reason, int newStamina) {
// set stamina
player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
// notify updated
for (Map.Entry<String, AfterUpdateStaminaListener> listener : afterUpdateStaminaListeners.entrySet()) {
listener.getValue().onAfterUpdateStamina(reason, newStamina);
}
return newStamina;
}
// Kills avatar, removes entity and sends notification.
// TODO: Probably move this to Avatar class? since other components may also need to kill avatar.
public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) {
session.send(new PacketAvatarLifeStateChangeNotify(player.getTeamManager().getCurrentAvatarEntity().getAvatar(),
LifeState.LIFE_DEAD, dieType));
session.send(new PacketLifeStateChangeNotify(entity, LifeState.LIFE_DEAD, dieType));
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD));
player.getScene().removeEntity(entity);
((EntityAvatar) entity).onDeath(dieType, 0);
}
public void startSustainedStaminaHandler() {
if (!player.isPaused() && sustainedStaminaHandlerTimer == null) {
sustainedStaminaHandlerTimer = new Timer();
sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200);
Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started");
}
}
public void stopSustainedStaminaHandler() {
if (sustainedStaminaHandlerTimer != null) {
sustainedStaminaHandlerTimer.cancel();
sustainedStaminaHandlerTimer = null;
Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped");
}
}
// Handlers
// External trigger handler
public void handleEvtDoSkillSuccNotify(GameSession session, EvtDoSkillSuccNotify notify) {
handleImmediateStamina(session, notify);
}
public void handleCombatInvocationsNotify(GameSession session, EntityMoveInfo moveInfo, GameEntity entity) {
// cache info for later use in SustainedStaminaHandler tick
cachedSession = session;
cachedEntity = entity;
MotionInfo motionInfo = moveInfo.getMotionInfo();
MotionState motionState = motionInfo.getState();
boolean isReliable = moveInfo.getIsReliable();
Grasscutter.getLogger().trace("" + motionState + "\t" + (isReliable ? "reliable" : ""));
if (isReliable) {
currentState = motionState;
Vector posVector = motionInfo.getPos();
Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ());
if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) {
currentCoordinates = newPos;
}
}
startSustainedStaminaHandler();
handleImmediateStamina(session, motionInfo, motionState, entity);
}
// Internal handler
private void handleImmediateStamina(GameSession session, MotionInfo motionInfo, MotionState motionState,
GameEntity entity) {
switch (motionState) {
case MOTION_DASH_BEFORE_SHAKE:
if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) {
updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT));
}
break;
case MOTION_CLIMB_JUMP:
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP));
}
break;
case MOTION_SWIM_DASH:
if (previousState != MotionState.MOTION_SWIM_DASH) {
updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START));
}
break;
}
}
private void handleImmediateStamina(GameSession session, EvtDoSkillSuccNotify notify) {
Consumption consumption = getFightConsumption(notify.getSkillId());
updateStaminaRelative(session, consumption);
}
private class SustainedStaminaHandler extends TimerTask {
public void run() {
if (Grasscutter.getConfig().OpenStamina) {
boolean moving = isPlayerMoving();
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
if (moving || (currentStamina < maxStamina)) {
Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " +
(currentStamina >= maxStamina) + ", recalculate stamina");
Consumption consumption = new Consumption(ConsumptionType.None);
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
consumption = getClimbSustainedConsumption();
} else if (MotionStatesCategorized.get("SWIM").contains((currentState))) {
consumption = getSwimSustainedConsumptions();
} else if (MotionStatesCategorized.get("RUN").contains(currentState)) {
consumption = getRunWalkDashSustainedConsumption();
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
consumption = getFlySustainedConsumption();
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
consumption = getStandSustainedConsumption();
}
/*
TODO: Reductions that apply to all motion types:
Elemental Resonance
Wind: -15%
Skills
Diona E: -10% while shield lasts
Barbara E: -12% while lasts
*/
if (cachedSession != null) {
if (consumption.amount < 0) {
staminaRecoverDelay = 0;
}
if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) {
// For POWERED_FLY recover immediately - things like Amber's gliding exam may require this.
if (staminaRecoverDelay < 10) {
// For others recover after 2 seconds (10 ticks) - as official server does.
staminaRecoverDelay++;
consumption.amount = 0;
Grasscutter.getLogger().trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay);
}
}
updateStaminaRelative(cachedSession, consumption);
}
}
}
previousState = currentState;
previousCoordinates = new Position(
currentCoordinates.getX(),
currentCoordinates.getY(),
currentCoordinates.getZ()
);
}
}
private void handleDrowning() {
int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
if (stamina < 10) {
Grasscutter.getLogger().trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" +
player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState);
if (currentState != MotionState.MOTION_SWIM_IDLE) {
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN);
}
}
}
// Consumption Calculators
// Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina
private Consumption getFightConsumption(int skillCasting) {
/* TODO:
Instead of handling here, consider call StaminaManager.updateStamina****() with a Consumption object with
type=FIGHT and a modified amount when handling attacks for more accurate attack start/end time and
other info. Handling it here could be very complicated.
Charged attack
Default:
Polearm: (-2500)
Claymore: (-4000 per second, -800 each tick)
Catalyst: (-5000)
Talent:
Ningguang: When Ningguang is in possession of Star Jades, her Charged Attack does not consume Stamina. (Catalyst * 0)
Klee: When Jumpy Dumpty and Normal Attacks deal DMG, Klee has a 50% chance to obtain an Explosive Spark.
This Explosive Spark is consumed by the next Charged Attack, which costs no Stamina. (Catalyst * 0)
Constellations:
Hu Tao: While in a Paramita Papilio state activated by Guide to Afterlife, Hu Tao's Charge Attacks do not consume Stamina. (Polearm * 0)
Character Specific:
Keqing: (-2500)
Diluc: (Claymore * 0.5)
Talent Moving: (Those are skills too)
Ayaka: (-1000 initial) (-1500 per second) When the Cryo application at the end of Kamisato Art: Senho hits an opponent (+1000)
Mona: (-1000 initial) (-1500 per second)
*/
// TODO: Currently only handling Ayaka and Mona's talent moving initial costs.
Consumption consumption = new Consumption(ConsumptionType.None);
HashMap<Integer, Integer> fightingCost = new HashMap<>() {{
put(10013, -1000); // Kamisato Ayaka
put(10413, -1000); // Mona
}};
if (fightingCost.containsKey(skillCasting)) {
consumption = new Consumption(ConsumptionType.FIGHT, fightingCost.get(skillCasting));
}
return consumption;
}
private Consumption getClimbSustainedConsumption() {
Consumption consumption = new Consumption(ConsumptionType.None);
if (currentState == MotionState.MOTION_CLIMB && isPlayerMoving()) {
consumption = new Consumption(ConsumptionType.CLIMBING);
if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) {
consumption = new Consumption(ConsumptionType.CLIMB_START);
}
}
// TODO: Foods
return consumption;
}
private Consumption getSwimSustainedConsumptions() {
handleDrowning();
Consumption consumption = new Consumption(ConsumptionType.None);
if (currentState == MotionState.MOTION_SWIM_MOVE) {
consumption = new Consumption(ConsumptionType.SWIMMING);
}
if (currentState == MotionState.MOTION_SWIM_DASH) {
consumption = new Consumption(ConsumptionType.SWIM_DASH);
}
return consumption;
}
private Consumption getRunWalkDashSustainedConsumption() {
Consumption consumption = new Consumption(ConsumptionType.None);
if (currentState == MotionState.MOTION_DASH) {
consumption = new Consumption(ConsumptionType.DASH);
// TODO: Foods
}
if (currentState == MotionState.MOTION_RUN) {
consumption = new Consumption(ConsumptionType.RUN);
}
if (currentState == MotionState.MOTION_WALK) {
consumption = new Consumption(ConsumptionType.WALK);
}
return consumption;
}
private Consumption getFlySustainedConsumption() {
// POWERED_FLY, e.g. wind tunnel
if (currentState == MotionState.MOTION_POWERED_FLY) {
return new Consumption(ConsumptionType.POWERED_FLY);
}
Consumption consumption = new Consumption(ConsumptionType.FLY);
// Talent
HashMap<Integer, Float> glidingCostReduction = new HashMap<>() {{
put(212301, 0.8f); // Amber
put(222301, 0.8f); // Venti
}};
float reduction = 1;
for (EntityAvatar entity : cachedSession.getPlayer().getTeamManager().getActiveTeam()) {
for (int skillId : entity.getAvatar().getProudSkillList()) {
if (glidingCostReduction.containsKey(skillId)) {
float potentialLowerReduction = glidingCostReduction.get(skillId);
if (potentialLowerReduction < reduction) {
reduction = potentialLowerReduction;
}
}
}
}
consumption.amount *= reduction;
// TODO: Foods
return consumption;
}
private Consumption getStandSustainedConsumption() {
Consumption consumption = new Consumption(ConsumptionType.None);
if (currentState == MotionState.MOTION_STANDBY) {
consumption = new Consumption(ConsumptionType.STANDBY);
}
if (currentState == MotionState.MOTION_STANDBY_MOVE) {
consumption = new Consumption(ConsumptionType.STANDBY_MOVE);
}
return consumption;
}
}

View File

@ -14,6 +14,7 @@ import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.friends.FriendsList;
import emu.grasscutter.game.friends.PlayerProfile;
import emu.grasscutter.game.gacha.PlayerGachaInfo;
@ -21,8 +22,8 @@ import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.mail.MailHandler;
import emu.grasscutter.game.managers.MovementManager.MovementManager;
import emu.grasscutter.game.managers.SotSManager.SotSManager;
import emu.grasscutter.game.managers.StaminaManager.StaminaManager;
import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.PlayerProperty;
@ -51,6 +52,7 @@ import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.DateHelper;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.MessageHandler;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ -60,9 +62,6 @@ import java.util.concurrent.LinkedBlockingQueue;
@Entity(value = "players", useDiscriminator = false)
public class Player {
@Transient private static int GlobalMaximumSpringVolume = 8500000;
@Transient private static int GlobalMaximumStamina = 24000;
@Id private int id;
@Indexed(options = @IndexOptions(unique = true)) private String accountId;
@ -102,6 +101,7 @@ public class Player {
private ArrayList<AvatarProfileData> shownAvatars;
private Set<Integer> rewardedLevels;
private ArrayList<ShopLimit> shopLimit;
private Map<Long, ExpeditionInfo> expeditionInfo;
private int sceneId;
private int regionId;
@ -129,7 +129,7 @@ public class Player {
@Transient private final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler;
private MapMarksManager mapMarksManager;
@Transient private MovementManager movementManager;
@Transient private StaminaManager staminaManager;
private long springLastUsed;
@ -141,6 +141,7 @@ public class Player {
this.avatars = new AvatarStorage(this);
this.friendsList = new FriendsList(this);
this.mailHandler = new MailHandler(this);
this.towerManager = new TowerManager(this);
this.pos = new Position();
this.rotation = new Position();
this.properties = new HashMap<>();
@ -171,9 +172,10 @@ public class Player {
this.moonCardGetTimes = new HashSet<>();
this.shopLimit = new ArrayList<>();
this.expeditionInfo = new HashMap<>();
this.messageHandler = null;
this.mapMarksManager = new MapMarksManager();
this.movementManager = new MovementManager(this);
this.staminaManager = new StaminaManager(this);
this.sotsManager = new SotSManager(this);
}
@ -186,7 +188,6 @@ public class Player {
this.nickname = "Traveler";
this.signature = "";
this.teamManager = new TeamManager(this);
this.towerManager = new TowerManager(this);
this.birthday = new PlayerBirthday();
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1);
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
@ -202,7 +203,7 @@ public class Player {
this.getRotation().set(0, 307, 0);
this.messageHandler = null;
this.mapMarksManager = new MapMarksManager();
this.movementManager = new MovementManager(this);
this.staminaManager = new StaminaManager(this);
this.sotsManager = new SotSManager(this);
}
@ -674,6 +675,28 @@ public class Player {
session.send(new PacketCardProductRewardNotify(getMoonCardRemainDays()));
}
public Map<Long, ExpeditionInfo> getExpeditionInfo() {
return expeditionInfo;
}
public void addExpeditionInfo(long avaterGuid, int expId, int hourTime, int startTime){
ExpeditionInfo exp = new ExpeditionInfo();
exp.setExpId(expId);
exp.setHourTime(hourTime);
exp.setState(1);
exp.setStartTime(startTime);
expeditionInfo.put(avaterGuid, exp);
}
public void removeExpeditionInfo(long avaterGuid){
expeditionInfo.remove(avaterGuid);
}
public ExpeditionInfo getExpeditionInfo(long avaterGuid){
return expeditionInfo.get(avaterGuid);
}
public List<ShopLimit> getShopLimit() {
return shopLimit;
}
@ -849,11 +872,11 @@ public class Player {
}
public void onPause() {
getStaminaManager().stopSustainedStaminaHandler();
}
public void onUnpause() {
getStaminaManager().startSustainedStaminaHandler();
}
public void sendPacket(BasePacket packet) {
@ -998,7 +1021,7 @@ public class Player {
return mapMarksManager;
}
public MovementManager getMovementManager() { return movementManager; }
public StaminaManager getStaminaManager() { return staminaManager; }
public SotSManager getSotSManager() { return sotsManager; }
@ -1030,6 +1053,22 @@ public class Player {
this.resetSendPlayerLocTime();
}
}
// Expedition
var timeNow = Utils.getCurrentSeconds();
var needNotify = false;
for (Long key : expeditionInfo.keySet()) {
ExpeditionInfo e = expeditionInfo.get(key);
if(e.getState() == 1){
if(timeNow - e.getStartTime() >= e.getHourTime() * 60 * 60){
e.setState(2);
needNotify = true;
}
}
}
if(needNotify){
this.save();
this.sendPacket(new PacketAvatarExpeditionDataNotify(this));
}
}
@ -1057,9 +1096,6 @@ public class Player {
if (this.getProfile().getUid() == 0) {
this.getProfile().syncWithCharacter(this);
}
if (this.getTowerManager() == null) {
this.towerManager = new TowerManager(this);
}
// Check if player object exists in server
// TODO - optimize
@ -1112,8 +1148,11 @@ public class Player {
}
public void onLogout() {
// stop stamina calculation
getStaminaManager().stopSustainedStaminaHandler();
// force to leave the dungeon
if(getScene().getSceneType() == SceneType.SCENE_DUNGEON){
if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) {
this.getServer().getDungeonManager().exitDungeon(this);
}
// Leave world
@ -1172,7 +1211,7 @@ public class Player {
} else if (prop == PlayerProperty.PROP_LAST_CHANGE_AVATAR_TIME) { // 10001
// TODO: implement sanity check
} else if (prop == PlayerProperty.PROP_MAX_SPRING_VOLUME) { // 10002
if (!(value >= 0 && value <= GlobalMaximumSpringVolume)) { return false; }
if (!(value >= 0 && value <= getSotSManager().GlobalMaximumSpringVolume)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_SPRING_VOLUME) { // 10003
int playerMaximumSpringVolume = getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
if (!(value >= 0 && value <= playerMaximumSpringVolume)) { return false; }
@ -1189,7 +1228,7 @@ public class Player {
} else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009
if (!(0 <= value && value <= 1)) { return false; }
} else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010
if (!(value >= 0 && value <= GlobalMaximumStamina)) { return false; }
if (!(value >= 0 && value <= getStaminaManager().GlobalMaximumStamina)) { return false; }
} else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011
int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA);
if (!(value >= 0 && value <= playerMaximumStamina)) { return false; }
@ -1200,7 +1239,7 @@ public class Player {
} else if (prop == PlayerProperty.PROP_PLAYER_EXP) { // 10014
if (!(0 <= value)) { return false; }
} else if (prop == PlayerProperty.PROP_PLAYER_HCOIN) { // 10015
// see 10015
// see PlayerProperty.PROP_PLAYER_HCOIN comments
} else if (prop == PlayerProperty.PROP_PLAYER_SCOIN) { // 10016
// See 10015
} else if (prop == PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE) { // 10017

View File

@ -557,7 +557,7 @@ public class TeamManager {
// return;
// }
// }
player.getMovementManager().resetTimer(); // prevent drowning immediately after respawn
player.getStaminaManager().stopSustainedStaminaHandler(); // prevent drowning immediately after respawn
// Revive all team members
for (EntityAvatar entity : getActiveTeam()) {

View File

@ -4,13 +4,7 @@ import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.data.def.ShopGoodsData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.managers.InventoryManager;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ItemParamOuterClass;
import emu.grasscutter.net.proto.ShopGoodsOuterClass;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@ -54,9 +48,9 @@ public class ShopManager {
public static int getShopNextRefreshTime(ShopInfo shopInfo) {
return switch (shopInfo.getShopRefreshType()) {
case SHOP_REFRESH_DAILY -> Utils.GetNextTimestampOfThisHour(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam());
case SHOP_REFRESH_WEEKLY -> Utils.GetNextTimestampOfThisHourInNextWeek(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam());
case SHOP_REFRESH_MONTHLY -> Utils.GetNextTimestampOfThisHourInNextMonth(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam());
case SHOP_REFRESH_DAILY -> Utils.getNextTimestampOfThisHour(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam());
case SHOP_REFRESH_WEEKLY -> Utils.getNextTimestampOfThisHourInNextWeek(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam());
case SHOP_REFRESH_MONTHLY -> Utils.getNextTimestampOfThisHourInNextMonth(REFRESH_HOUR, TIME_ZONE, shopInfo.getShopRefreshParam());
default -> 0;
};
}

View File

@ -7,6 +7,7 @@ import emu.grasscutter.data.def.TowerLevelData;
import emu.grasscutter.game.dungeons.DungeonSettleListener;
import emu.grasscutter.game.dungeons.TowerDungeonSettleListener;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify;
import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify;
import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp;
@ -15,9 +16,7 @@ import java.util.List;
@Entity
public class TowerManager {
@Transient private final Player player;
@Transient private Player player;
public TowerManager(Player player) {
this.player = player;
@ -77,7 +76,8 @@ public class TowerManager {
player.getScene().setPrevScenePoint(enterPointId);
player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel));
// stop using skill
player.getSession().send(new PacketCanUseSkillNotify(false));
}
public void notifyCurLevelRecordChange(){

View File

@ -105,7 +105,13 @@ public class Scene {
public GameEntity getEntityById(int id) {
return this.entities.get(id);
}
public GameEntity getEntityByConfigId(int configId) {
return this.entities.values().stream()
.filter(x -> x.getConfigId() == configId)
.findFirst()
.orElse(null);
}
/**
* @return the autoCloseTime
*/

View File

@ -1,289 +0,0 @@
package emu.grasscutter.languages;
public final class CNLanguage {
public String An_error_occurred_during_game_update = "游戏更新时发生了错误.";
public String Starting_Grasscutter = "正在开启Grasscutter...";
public String Invalid_server_run_mode = "无效的服务器运行模式. ";
public String Server_run_mode = "服务器运行模式必须为以下几种之一: 'HYBRID'(混合模式), 'DISPATCH_ONLY'(仅dispatch模式), 或 'GAME_ONLY'(仅游戏模式). 无法启动Grasscutter...";
public String Shutting_down = "正在停止....";
public String Start_done = "加载完成!需要指令帮助请输入 \"help\"";
public String Dispatch_mode_not_support_command = "仅dispatch模式无法使用指令。";
public String Command_error = "命令错误:";
public String Error = "出现错误.";
public String Grasscutter_is_free = "Grasscutter是免费软件如果你是花钱买到的你大概被骗了。主页: https://github.com/Grasscutters/Grasscutter";
public String Game_start_port = "游戏服务器已在端口 {port} 上开启。";
public String Client_connect = "来自 {address} 的客户端已连接。";
public String Client_disconnect = "来自 {address} 的客户端已断开。";
public String Client_request = "[Dispatch] 客户端 {ip} 请求: {method} {url}";
public String Not_load_keystore = "[Dispatch] 无法加载证书,正在尝试默认密码...";
public String Use_default_keystore = "[Dispatch] 成功使用默认密码加载证书. 请考虑将config.json中的KeystorePassword项改为123456.";
public String Load_keystore_error = "[Dispatch] 加载证书时出现错误!";
public String Not_find_ssl_cert = "[Dispatch] 未找到SSL证书正在回滚至HTTP模式。";
public String Welcome = "欢迎使用Grasscutter";
public String Potential_unhandled_request = "[Dispatch] 潜在的未处理请求: {method} {url}";
public String Client_login_token = "[Dispatch] 客户端 {ip} 正在尝试使用token登录...";
public String Client_token_login_failed = "[Dispatch] 客户端 {ip} 使用token登录失败。";
public String Client_login_in_token = "[Dispatch] 客户端 {ip} 使用token以 {uid} 的身份登录。";
public String Game_account_cache_error = "游戏账户缓存出现错误。";
public String Wrong_session_key = "会话密钥错误。";
public String Client_exchange_combo_token = "[Dispatch] 客户端 {ip} 成功交换token。";
public String Client_failed_exchange_combo_token = "[Dispatch] 客户端 {ip} 交换token失败。";
public String Dispatch_start_server_port = "[Dispatch] Dispatch服务器已在端口 {port} 上开启。";
public String Client_failed_login_account_create = "[Dispatch] 客户端 {ip} 登录失败: 已创建UID为 {uid} 的账户。";
public String Client_failed_login_account_create_failed = "[Dispatch] 客户端 {ip} 登录失败: 创建账户失败。";
public String Client_failed_login_account_no_found = "[Dispatch] 客户端 {ip} 登录失败: 未找到帐户。";
public String Client_login = "[Dispatch] 客户端 {ip} 以 {uid} 的身份登录。";
public String Username_not_found = "未找到此用户名.";
public String Username_not_found_create_failed = "未找到此用户名, 创建失败。.";
// Command
public String No_command_specified = "未指定命令.";
public String Unknown_command = "未知命令: ";
public String You_not_permission_run_command = "你没有权限运行此命令.";
public String This_command_can_only_run_from_console = "此命令只能在控制台运行.";
public String Run_this_command_in_game = "请在游戏内运行此命令.";
public String Invalid_playerId = "无效的玩家ID.";
public String Player_not_found = "未找到此玩家.";
public String Player_is_offline = "此玩家已离线.";
public String Invalid_amount = "无效的数量.";
public String Invalid_arguments = "无效的命令参数.";
public String Invalid_artifact_id = "无效的圣遗物ID.";
public String Invalid_avatar_id = "无效的角色ID.";
public String Invalid_avatar_level = "无效的角色等级.";
public String Invalid_entity_id = "无效的物品ID.";
public String Invalid_item_id = "无效的物品ID.";
public String Invalid_item_level = "无效的物品等级.";
public String Invalid_item_refinement = "无效的精炼等级.";
public String Invalid_UID = "无效的UID.";
public String Enabled = "启用";
public String Disabled = "禁用";
public String No_command_found = "未找到命令.";
public String Help = "帮助";
public String Player_not_found_or_offline = "此玩家不存在或已离线.";
public String Success = "成功";
public String Target_cleared = "已清除选择目标";
public String Target_set = "接下来的命令将默认以 @{uid} 为目标。输入命令时不必继续携带UID参数。";
public String Target_needed = "此命令需要指定一个目标用户. 输入命令时携带 <@UID> 参数或使用 /target @UID 来指定一个默认目标用户.";
// Help
public String Help_usage = " 用法: ";
public String Help_aliases = " 别名: ";
public String Help_available_command = " 可用命令:";
// Account
public String Modify_user_account = "修改用户帐户";
public String Account_exists = "账户已存在.";
public String Account_create_UID = "UID为 {uid} 的账户已创建.";
public String Account_delete = "已删除账户.";
public String Account_not_find = "账户不存在.";
public String Account_command_usage = "用法: account <create(创建)|delete(删除)> <用户名> [uid]";
// Broadcast
public String Broadcast_command_usage = "用法: broadcast <消息>";
public String Broadcast_message_sent = "消息已发送.";
// ChangeScene
public String Change_screen_usage = "用法: changescene <场景id>";
public String Change_screen_you_in_that_screen = "你已经在此场景中了";
public String Change_screen = "切换到场景 ";
public String Change_screen_not_exist = "此场景不存在。";
// Cleart_or_playerId
public String Clear_weapons = "已清除 {name} 的武器.";
public String Clear_artifacts = "已清除 {name} 的圣遗物 .";
public String Clear_materials = "已清除 {name} 的材料.";
public String Clear_furniture = "已清除 {name} 的摆设.";
public String Clear_displays = "已清除 {name} 的displays.";
public String Clear_virtuals = "已清除 {name} 的virtuals.";
public String Clear_everything = "已清除 {name} 的所有物品.";
// Coop
public String Coop_usage = "用法: coop <房主的UID>";
public String Coop_success = "已将{target}拉进{host}的世界.";
// Drop
public String Drop_usage = "用法: drop <物品ID|物品名> [数量]";
public String Drop_dropped_of = "已在地上丢弃 {amount} 个 {item}.";
// EnterDungeon
public String EnterDungeon_usage = "用法: enterdungeon <副本 id>";
public String EnterDungeon_changed_to_dungeon = "已进入副本 ";
public String EnterDungeon_dungeon_not_found = "副本不存在";
public String EnterDungeon_you_in_that_dungeon = "你已经在此副本中了。";
// GiveAll
public String GiveAll_usage = "用法: giveall [数量]";
public String GiveAll_item = "正在给予所有物品...";
public String GiveAll_done = "完成。";
// GiveArtifact
public String GiveArtifact_usage = "用法: giveart|gart [玩家] <圣遗物Id> <主词条Id> [<副词条Id>[,<被强化次数>]]... [等级]";
public String GiveArtifact_given = "已将 {itemId} 给予 {target}.";
// GiveChar
public String GiveChar_usage = "用法: givechar <p玩家> <角色Id|角色名> [等级]";
public String GiveChar_given = "将等级为 {level} 的 {avatarId} 给予 {target}.";
// Give
public String Give_usage = "用法: give [玩家名] <物品ID|物品名> [数量] [等级] ";
public String Give_refinement_only_applicable_weapons = "精炼只对武器有效。";
public String Give_refinement_must_between_1_and_5 = "精炼等级必须在1和5之间。";
public String Give_given = "已将 {amount} 个 {item} 给与 {target}.";
public String Give_given_with_level_and_refinement = "已将 {amount} 个等级为 {lvl}, 精炼 {refinement} 的 {item} 给予 {target}.";
public String Give_given_level = "已将 {amount} 个等级为 {lvl} 的 {item} 给与 {target}.";
// GodMode
public String Godmode_usage = "用法: godmode [on|off|toggle]";
public String Godmode_status = "设置 {name} 的无敌模式为: {status} ";
// Heal
public String Heal_message = "所有角色已被治疗。";
// Kick
public String Kick_player_kick_player = "玩家 [{sendUid}:{sendName}] 已踢出 [{kickUid}:{kickName}]";
public String Kick_server_player = "正在踢出玩家 [{kickUid}:{kickName}]";
// Kill
public String Kill_usage = "用法: killall [玩家UID] [场景ID]";
public String Kill_scene_not_found_in_player_world = "未在玩家世界中找到此场景";
public String Kill_kill_monsters_in_scene = "已杀死场景 {id} 中的 {size} 只怪物。 ";
// KillCharacter
public String KillCharacter_usage = "用法: /killcharacter [玩家Id]";
public String KillCharacter_kill_current_character = "已干掉 {name} 当前的场上角色.";
// List
public String List_message = "现有 {size} 名玩家在线:";
// Permission
public String Permission_usage = "用法: permission <add|remove> <权限名>";
public String Permission_add = "权限已添加。";
public String Permission_have_permission = "此玩家已拥有此权限!";
public String Permission_remove = "权限已移除。";
public String Permission_not_have_permission = "此玩家未拥有此权限!";
// Position
public String Position_message = "坐标: {x},{y},{z}\n场景: {id}";
// Reload
public String Reload_reload_start = "正在重新加载配置.";
public String Reload_reload_done = "完成.";
// ResetConst
public String ResetConst_reset_all = "重置你所有角色的命座。";
public String ResetConst_reset_all_done = "{name} 的命座已被重置。请重新登录。";
// ResetShopLimit
public String ResetShopLimit_usage = "用法: /resetshop <玩家id>";
// SendMail
public String SendMail_usage = "用法: give [player] <itemId|itemName> [amount]";
public String SendMail_user_not_exist = "不存在id为 '{id}' 的用户。";
public String SendMail_start_composition = "开始编辑邮件的组成部分.\n请使用 `/sendmail <标题>` 以继续.\n你可以在任何时候使用`/sendmail stop` 来停止编辑.";
public String SendMail_templates = "很快就会有邮件模板了.......";
public String SendMail_invalid_arguments = "无效的参数.\n用法 `/sendmail <用户Id|all|help> [模板Id]``";
public String SendMail_send_cancel = "已取消发送邮件。";
public String SendMail_send_done = "已向 {name} 发送邮件!";
public String SendMail_send_all_done = "已向所有玩家发送邮件!";
public String SendMail_not_composition_end = "邮件组成部分编辑尚未结束.\n请使用 `/sendmail {args}` 或 `/sendmail stop` 来停止编辑";
public String SendMail_Please_use = "请使用 `/sendmail {args}`";
public String SendMail_set_title = "邮件标题已设为 '{title}'.\n使用 '/sendmail <邮件正文>' 以继续.";
public String SendMail_set_contents = "邮件的正文如下:\n '{contents}'\n使用 '/sendmail <发送者署名>' 以继续.";
public String SendMail_set_message_sender = "邮件的发送者已设为 '{send}'.\n使用 '/sendmail <物品Id|物品名|finish(结束编辑并发送)> [数量] [等级]";
public String SendMail_send = "已将 {amount} 个 {item} (等级 {lvl}) 作为邮件附件.\n你可以继续添加附件也可以使用 `/sendmail finish` 来停止编辑并发送邮件.";
public String SendMail_invalid_arguments_please_use = "无效的参数 \n 请使用 `/sendmail {args}`";
public String SendMail_title = "<标题>";
public String SendMail_message = "<正文>";
public String SendMail_sender = "<发送者>";
public String SendMail_arguments = "<物品Id|物品名|finish(结束编辑并发送)> [数量] [等级]";
public String SendMail_error = "错误:无效的编写阶段 {stage}. 需要stacktrace请看服务器命令行.";
// SendMessage
public String SendMessage_usage = "用法: sendmessage <玩家名> <消息>";
public String SenaMessage_message_sent = "已发送.";
// SetFetterLevel
public String SetFetterLevel_usage = "用法: setfetterlevel <等级>";
public String SetFetterLevel_fetter_level_must_between_0_and_10 = "设置的好感等级必须位于 0 和 10 之间。";
public String SetFetterLevel_fetter_set_level = "好感等级已设置为 {level}";
public String SetFetterLevel_invalid_fetter_level = "无效的好感等级。";
// SetStats
public String SetStats_usage = "用法: setstats|stats <stat> <value>";
public String SetStats_setstats_help_message = "用法: /setstats|stats <hp(生命值) | mhp(最大生命值) | def(防御力) | atk(攻击) | em(元素精通) | crate(暴击率) | cdmg(暴击伤害)> <数值> 基本属性(整数)";
public String SetStats_stats_help_message = "用法: /stats <er(元素充能) | epyro(火伤) | ecryo(冰伤) | ehydro(水伤) | eanemo(风伤) | egeo(岩伤) | edend(草伤) | eelec(雷伤) | ephys(物伤)> <数值> 元素属性(百分比)";
public String SetStats_set_max_hp = "最大生命值已设为 {int}.";
public String SetStats_set_max_hp_error = "无效的生命数值.";
public String SetStats_set_hp = "生命设置为 {int}.";
public String SetStats_set_hp_error = "无效的生命数值.";
public String SetStats_set_def = "防御力设置为 {int}.";
public String SetStats_set_def_error = "无效的防御力数值.";
public String SetStats_set_atk = "攻击力设置为 {int}.";
public String SetStats_set_atk_error = "无效的攻击力数值.";
public String SetStats_set_em = "元素精通设置为 {int}.";
public String SetStats_set_em_error = "无效的元素精通数值.";
public String SetStats_set_er = "元素充能设置为 {int}%.";
public String SetStats_set_er_error = "无效的元素充能数值.";
public String SetStats_set_cr = "暴击率设置为 {int}%.";
public String SetStats_set_cr_error = "无效的暴击率数值.";
public String SetStats_set_cd = "暴击伤害设置为 {int}%.";
public String SetStats_set_cd_error = "无效的暴击伤害数值.";
public String SetStats_set_pdb = "火伤设置为 {int}%.";
public String SetStats_set_pdb_error = "无效的火伤数值.";
public String SetStats_set_cdb = "冰伤设置为 {int}%.";
public String SetStats_set_cdb_error = "无效的冰伤数值.";
public String SetStats_set_hdb = "水伤设置为 {int}%.";
public String SetStats_set_hdb_error = "无效的水伤数值.";
public String SetStats_set_adb = "风伤设置为 {int}%.";
public String SetStats_set_adb_error = "无效的风伤数值.";
public String SetStats_set_gdb = "岩伤设置为 {int}%.";
public String SetStats_set_gdb_error = "无效的岩伤数值.";
public String SetStats_set_edb = "雷伤设置为 {int}%.";
public String SetStats_set_edb_error = "无效的雷伤数值.";
public String SetStats_set_physdb = "物伤设置为 {int}%.";
public String SetStats_set_physdb_error = "无效的物伤数值.";
public String SetStats_set_ddb = "草伤设置为 {int}%.";
public String SetStats_set_ddb_error = "无效的草伤数值.";
// SetWorldLevel
public String SetWorldLevel_usage = "用法: setworldlevel <level>";
public String SetWorldLevel_world_level_must_between_0_and_8 = "世界等级必须在0-8之间。";
public String SetWorldLevel_set_world_level = "世界等级已设置为 {level}.";
public String SetWorldLevel_invalid_world_level = "无效的世界等级.";
// Spawn
public String Spawn_usage = "用法: spawn <实体ID|实体名> [数量] [等级(仅限怪物)]";
public String Spawn_message = "已生成 {amount} 个 {id}.";
// Stop
public String Stop_message = "正在关闭服务器...";
// Talent
public String Talent_usage_1 = "设置技能等级: /talent set <技能ID> <数值>";
public String Talent_usage_2 = "另一种方式: /talent <n 或 e 或 q> <数值>";
public String Talent_usage_3 = "获取技能ID: /talent getid";
public String Talent_lower_16 = "技能等级应低于16。";
public String Talent_set_atk = "设置普通攻击等级为 {level}.";
public String Talent_set_e = "设置元素战技(e技能)等级为 {level}.";
public String Talent_set_q = "设置元素爆发(q技能)等级为 {level}.";
public String Talent_invalid_skill_id = "无效的技能ID。";
public String Talent_set_this = "技能等级已设为 {level}.";
public String Talent_set_id = "将技能 {id} 的等级设为 {level}.";
public String Talent_invalid_talent_level = "无效的技能等级。";
public String Talent_normal_attack_id = "普通攻击技能ID {id}.";
public String Talent_e_skill_id = "元素战技(e技能)ID {id}.";
public String Talent_q_skill_id = "元素爆发(q技能)ID {id}.";
// TeleportAll
public String TeleportAll_message = "此命令仅在多人游戏下可用。";
// Teleport
public String Teleport_usage_server = "用法: /tp <x> <y> <z> [场景ID]";
public String Teleport_invalid_position = "无效的位置。";
public String Teleport_message = "已将 {name} 传送到场景 {id} ,坐标 {x},{y},{z}";
// Weather
public String Weather_usage = "用法: weather <天气ID> [气候ID]";
public String Weather_message = "已修改天气为 {weatherId} 气候为 {climateId}";
public String Weather_invalid_id = "无效的ID。";
}

View File

@ -1,300 +0,0 @@
package emu.grasscutter.languages;
public final class Language {
public String An_error_occurred_during_game_update = "An error occurred during game update.";
public String Starting_Grasscutter = "Starting Grasscutter...";
public String Invalid_server_run_mode = "Invalid server run mode.";
public String Server_run_mode = "Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter...";
public String Shutting_down = "Shutting down...";
public String Start_done = "Done! For help, type \"help\"";
public String Dispatch_mode_not_support_command = "Commands are not supported in dispatch only mode.";
public String Command_error = "Command error:";
public String Error = "An error occurred.";
public String Grasscutter_is_free = "Grasscutter is FREE software. If you have paid for this, you may have been scammed. Homepage: https://github.com/Grasscutters/Grasscutter";
public String Game_start_port = "Game Server started on port {port}";
public String Client_connect = "Client connected from {address}";
public String Client_disconnect = "Client disconnected from {address}";
public String Client_request = "[Dispatch] Client {ip} {method} request: {url}";
public String Not_load_keystore = "[Dispatch] Unable to load keystore. Trying default keystore password...";
public String Use_default_keystore = "[Dispatch] The default keystore password was loaded successfully. Please consider setting the password to 123456 in config.json.";
public String Load_keystore_error = "[Dispatch] Error while loading keystore!";
public String Not_find_ssl_cert = "[Dispatch] No SSL cert found! Falling back to HTTP server.";
public String Welcome = "Welcome to Grasscutter";
public String Potential_unhandled_request = "[Dispatch] Potential unhandled {method} request: {url}";
public String Client_try_login = "[Dispatch] Client {ip} is trying to log in";
public String Client_login_token = "[Dispatch] Client {ip} is trying to log in via token";
public String Client_token_login_failed = "[Dispatch] Client {ip} failed to log in via token";
public String Client_login_in_token = "[Dispatch] Client {ip} logged in via token as {uid}";
public String Game_account_cache_error = "Game account cache information error";
public String Wrong_session_key = "Wrong session key.";
public String Client_exchange_combo_token = "[Dispatch] Client {ip} succeed to exchange combo token";
public String Client_failed_exchange_combo_token = "[Dispatch] Client {ip} failed to exchange combo token";
public String Dispatch_start_server_port = "[Dispatch] Dispatch server started on port {port}";
public String Client_failed_login_account_create = "[Dispatch] Client {ip} failed to log in: Account {uid} created";
public String Client_failed_login_account_create_failed = "[Dispatch] Client {ip} failed to log in: Account create failed";
public String Client_failed_login_account_no_found = "[Dispatch] Client {ip} failed to log in: Account no found";
public String Client_login = "[Dispatch] Client {ip} logged in as {uid}";
public String Username_not_found = "Username not found.";
public String Username_not_found_create_failed = "Username not found, create failed.";
public String Create_resources_folder = "Creating resources folder...";
public String Place_copy = "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder.";
// Command
public String No_command_specified = "No command specified.";
public String Unknown_command = "Unknown command: ";
public String You_not_permission_run_command = "You do not have permission to run this command.";
public String This_command_can_only_run_from_console = "This command can only be run from the console.";
public String Run_this_command_in_game = "Run this command in-game.";
public String Invalid_amount = "Invalid amount.";
public String Invalid_arguments = "Invalid arguments.";
public String Invalid_artifact_id = "Invalid artifact ID.";
public String Invalid_avatar_id = "Invalid avatar id.";
public String Invalid_avatar_level = "Invalid avatar level.";
public String Invalid_entity_id = "Invalid entity id.";
public String Invalid_item_id = "Invalid item id.";
public String Invalid_item_level = "Invalid item level.";
public String Invalid_item_refinement = "Invalid item refinement level.";
public String Invalid_playerId = "Invalid playerId.";
public String Invalid_UID = "Invalid UID.";
public String Player_not_found = "Player not found.";
public String Player_is_offline = "Player is offline.";
public String Enabled = "enabled";
public String Disabled = "disabled";
public String No_command_found = "No command found.";
public String Help = "Help";
public String Player_not_found_or_offline = "Player not found or offline.";
public String Success = "Success";
public String Target_cleared = "Target cleared.";
public String Target_set = "Subsequent commands will target @{uid} by default.";
public String Target_needed = "This command requires a target UID. Add a <@UID> argument or set a persistent target with /target @UID.";
// Help
public String Help_usage = " Usage: ";
public String Help_aliases = " Aliases: ";
public String Help_available_command = "Available commands:";
// Account
public String Modify_user_account = "Modify user accounts";
public String Account_exists = "Account already exists.";
public String Account_create_UID = "Account created with UID {uid}.";
public String Account_delete = "Account deleted.";
public String Account_not_find = "Account not found.";
public String Account_command_usage = "Usage: account <create|delete> <username> [uid]";
// Broadcast
public String Broadcast_command_usage = "Usage: broadcast <message>";
public String Broadcast_message_sent = "Message sent.";
// ChangeScene
public String Change_screen_usage = "Usage: changescene <scene id>";
public String Change_screen_you_in_that_screen = "You are already in that scene";
public String Change_screen = "Changed to scene ";
public String Change_screen_not_exist = "Scene does not exist";
// Clear
public String Clear_usage = "Usage: clear <all|wp|art|mat>";
public String Clear_weapons = "Cleared weapons for {name} .";
public String Clear_artifacts = "Cleared artifacts for {name} .";
public String Clear_materials = "Cleared materials for {name} .";
public String Clear_furniture = "Cleared furniture for {name} .";
public String Clear_displays = "Cleared displays for {name} .";
public String Clear_virtuals = "Cleared virtuals for {name} .";
public String Clear_everything = "Cleared everything for {name} .";
// Coop
public String Coop_usage = "Usage: coop <host UID>";
public String Coop_success = "Summoned {target} to {host}'s world.";
// Drop
public String Drop_usage = "Usage: drop <itemId|itemName> [amount]";
public String Drop_dropped_of = "Dropped {amount} of {item}.";
// EnterDungeon
public String EnterDungeon_usage = "Usage: enterdungeon <dungeon id>";
public String EnterDungeon_changed_to_dungeon = "Changed to dungeon ";
public String EnterDungeon_dungeon_not_found = "Dungeon does not exist";
public String EnterDungeon_you_in_that_dungeon = "You are already in that dungeon";
// GiveAll
public String GiveAll_usage = "Usage: giveall [amount]";
public String GiveAll_item = "Giving all items...";
public String GiveAll_done = "Giving all items done";
// GiveArtifact
public String GiveArtifact_usage = "Usage: giveart|gart [player] <artifactId> <mainPropId> [<appendPropId>[,<times>]]... [level]";
public String GiveArtifact_given = "Given {itemId} to {target}.";
// GiveChar
public String GiveChar_usage = "Usage: givechar <player> <itemId|itemName> [amount]";
public String GiveChar_given = "Given {avatarId} with level {level} to {target}.";
// Give
public String Give_usage = "Usage: give <player> <itemId|itemName> [amount] [level]";
public String Give_refinement_only_applicable_weapons = "Refinement is only applicable to weapons.";
public String Give_refinement_must_between_1_and_5 = "Refinement must be between 1 and 5.";
public String Give_given = "Given {amount} of {item} to {target}.";
public String Give_given_with_level_and_refinement = "Given {item} with level {lvl}, refinement {refinement} {amount} times to {target}";
public String Give_given_level = "Given {item} with level {lvl} {amount} times to {target}";
// GodMode
public String Godmode_usage = "Usage: godmode [on|off|toggle]";
public String Godmode_status = "Godmode is now {status} for {name}.";
// Heal
public String Heal_message = "All characters have been healed.";
// Kick
public String Kick_player_kick_player = "Player [{sendUid}:{sendName}] has kicked player [{kickUid}:{kickName}]";
public String Kick_server_player = "Kicking player [{kickUid}:{kickName}]";
// Kill
public String Kill_usage = "Usage: killall [playerUid] [sceneId]";
public String Kill_scene_not_found_in_player_world = "Scene not found in player world";
public String Kill_kill_monsters_in_scene = "Killing {size} monsters in scene {id}";
// KillCharacter
public String KillCharacter_usage = "Usage: /killcharacter [playerId]";
public String KillCharacter_kill_current_character = "Killed {name} current character.";
// List
public String List_message = "There are {size} player(s) online:";
// Permission
public String Permission_usage = "Usage: permission <add|remove> <permission>";
public String Permission_add = "Permission added.";
public String Permission_have_permission = "They already have this permission!";
public String Permission_remove = "Permission removed.";
public String Permission_not_have_permission = "They don't have this permission!";
// Position
public String Position_message = "Coord: {x}, {y}, {z}\nScene id: {id}";
// Reload
public String Reload_reload_start = "Reloading config.";
public String Reload_reload_done = "Reload complete.";
// ResetConst
public String ResetConst_reset_all = "Reset all avatars' constellations.";
public String ResetConst_reset_all_done = "Constellations for {name} have been reset. Please relog to see changes.";
// ResetShopLimit
public String ResetShopLimit_usage = "Usage: /resetshop <player id>";
// SendMail
public String SendMail_usage = "Usage: give [player] <itemId|itemName> [amount]";
public String SendMail_user_not_exist = "The user with an id of '{id}' does not exist";
public String SendMail_start_composition = "Starting composition of message.\nPlease use `/sendmail <title>` to continue.\nYou can use `/sendmail stop` at any time";
public String SendMail_templates = "Mail templates coming soon implemented...";
public String SendMail_invalid_arguments = "Invalid arguments.\nUsage `/sendmail <userId|all|help> [templateId]`";
public String SendMail_send_cancel = "Message sending cancelled";
public String SendMail_send_done = "Message sent to user {name}!";
public String SendMail_send_all_done = "Message sent to all users!";
public String SendMail_not_composition_end = "Message composition not at final stage.\nPlease use `/sendmail {args}` or `/sendmail stop` to cancel";
public String SendMail_please_use = "Please use `/sendmail {args}`";
public String SendMail_set_title = "Message title set as '{title}'.\nUse '/sendmail <content>' to continue.";
public String SendMail_set_contents = "Message contents set as '{contents}'.\nUse '/sendmail <sender>' to continue.";
public String SendMail_set_message_sender = "Message sender set as '{send}'.\nUse '/sendmail <itemId|itemName|finish> [amount] [level]' to continue.";
public String SendMail_send = "Attached {amount} of {item} (level {lvl}) to the message.\nContinue adding more items or use `/sendmail finish` to send the message.";
public String SendMail_invalid_arguments_please_use = "Invalid arguments \n Please use `/sendmail {args}`";
public String SendMail_title = "<title>";
public String SendMail_message = "<message>";
public String SendMail_sender = "<sender>";
public String SendMail_arguments = "<itemId|itemName|finish> [amount] [level]";
public String SendMail_error = "ERROR: invalid construction stage {stage}. Check console for stacktrace.";
// SendMessage
public String SendMessage_usage = "Usage: sendmessage <player> <message>";
public String SenaMessage_message_sent = "Message sent.";
// SetFetterLevel
public String SetFetterLevel_usage = "Usage: setfetterlevel <level>";
public String SetFetterLevel_fetter_level_must_between_0_and_10 = "Fetter level must be between 0 and 10.";
public String SetFetterLevel_fetter_set_level = "Fetter level set to {level}";
public String SetFetterLevel_invalid_fetter_level = "Invalid fetter level.";
// SetStats
public String SetStats_usage_console = "Usage: setstats|stats @<UID> <stat> <value>";
public String SetStats_usage_ingame = "Usage: setstats|stats [@UID] <stat> <value>";
public String SetStats_help_message = """
\n\tValues for <stat>: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi
\t(cont.) Elemental DMG Bonus: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys
\t(cont.) Elemental RES: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys
""";
public String SetStats_value_error = "Invalid stat value.";
public String SetStats_set_self = "{name} set to {value}.";
public String SetStats_set_for_uid = "{name} for {uid} set to {value}.";
public String Stats_FIGHT_PROP_MAX_HP = "Max HP";
public String Stats_FIGHT_PROP_CUR_HP = "Current HP";
public String Stats_FIGHT_PROP_CUR_ATTACK = "ATK";
public String Stats_FIGHT_PROP_BASE_ATTACK = "Base ATK";
public String Stats_FIGHT_PROP_DEFENSE = "DEF";
public String Stats_FIGHT_PROP_ELEMENT_MASTERY = "Elemental Mastery";
public String Stats_FIGHT_PROP_CHARGE_EFFICIENCY = "Energy Recharge";
public String Stats_FIGHT_PROP_CRITICAL = "Crit Rate";
public String Stats_FIGHT_PROP_CRITICAL_HURT = "Crit DMG";
public String Stats_FIGHT_PROP_ADD_HURT = "DMG Bonus";
public String Stats_FIGHT_PROP_WIND_ADD_HURT = "Anemo DMG Bonus";
public String Stats_FIGHT_PROP_ICE_ADD_HURT = "Cryo DMG Bonus";
public String Stats_FIGHT_PROP_GRASS_ADD_HURT = "Dendro DMG Bonus";
public String Stats_FIGHT_PROP_ELEC_ADD_HURT = "Electro DMG Bonus";
public String Stats_FIGHT_PROP_ROCK_ADD_HURT = "Geo DMG Bonus";
public String Stats_FIGHT_PROP_WATER_ADD_HURT = "Hydro DMG Bonus";
public String Stats_FIGHT_PROP_FIRE_ADD_HURT = "Pyro DMG Bonus";
public String Stats_FIGHT_PROP_PHYSICAL_ADD_HURT = "Physical DMG Bonus";
public String Stats_FIGHT_PROP_SUB_HURT = "DMG Reduction";
public String Stats_FIGHT_PROP_WIND_SUB_HURT = "Anemo RES";
public String Stats_FIGHT_PROP_ICE_SUB_HURT = "Cryo RES";
public String Stats_FIGHT_PROP_GRASS_SUB_HURT = "Dendro RES";
public String Stats_FIGHT_PROP_ELEC_SUB_HURT = "Electro RES";
public String Stats_FIGHT_PROP_ROCK_SUB_HURT = "Geo RES";
public String Stats_FIGHT_PROP_WATER_SUB_HURT = "Hydro RES";
public String Stats_FIGHT_PROP_FIRE_SUB_HURT = "Pyro RES";
public String Stats_FIGHT_PROP_PHYSICAL_SUB_HURT = "Physical RES";
public String Stats_FIGHT_PROP_SKILL_CD_MINUS_RATIO = "Cooldown Reduction";
public String Stats_FIGHT_PROP_HEAL_ADD = "Healing Bonus";
public String Stats_FIGHT_PROP_HEALED_ADD = "Incoming Healing Bonus";
public String Stats_FIGHT_PROP_SHIELD_COST_MINUS_RATIO = "Shield Strength";
public String Stats_FIGHT_PROP_DEFENCE_IGNORE_RATIO = "DEF Ignore";
// SetWorldLevel
public String SetWorldLevel_usage = "Usage: setworldlevel <level>";
public String SetWorldLevel_world_level_must_between_0_and_8 = "World level must be between 0-8";
public String SetWorldLevel_set_world_level = "World level set to {level}.";
public String SetWorldLevel_invalid_world_level = "Invalid world level.";
// Spawn
public String Spawn_usage = "Usage: spawn <entityId> [amount] [level(monster only)]";
public String Spawn_message = "Spawned {amount} of {id}.";
// Stop
public String Stop_message = "Server shutting down...";
// Talent
public String Talent_usage_1 = "To set talent level: /talent set <talentID> <value>";
public String Talent_usage_2 = "Another way to set talent level: /talent <n or e or q> <value>";
public String Talent_usage_3 = "To get talent ID: /talent getid";
public String Talent_lower_16 = "Invalid talent level. Level should be lower than 16";
public String Talent_set_id = "Set talent {id} to {level}.";
public String Talent_set_atk = "Set talent Normal ATK to {level}.";
public String Talent_set_e = "Set talent E to {level}.";
public String Talent_set_q = "Set talent Q to {level}.";
public String Talent_invalid_skill_id = "Invalid skill ID.";
public String Talent_set_this = "Set this talent to {level}.";
public String Talent_invalid_talent_level = "Invalid talent level.";
public String Talent_normal_attack_id = "Normal Attack ID {id}.";
public String Talent_e_skill_id = "E skill ID {id}.";
public String Talent_q_skill_id = "Q skill ID {id}.";
// TeleportAll
public String TeleportAll_message = "You only can use this command in MP mode.";
// Teleport
public String Teleport_usage = "Usage: /tp <x> <y> <z> [scene id]";
public String Teleport_invalid_position = "Invalid position.";
public String Teleport_message = "Teleported {name} to {x},{y},{z} in scene {id}";
// Weather
public String Weather_usage = "Usage: weather <weatherId> [climateId]";
public String Weather_message = "Changed weather to {weatherId} with climate {climateId}";
public String Weather_invalid_id = "Invalid ID.";
}

View File

@ -4,12 +4,12 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.event.Event;
import emu.grasscutter.server.event.EventHandler;
import emu.grasscutter.server.event.HandlerPriority;
import emu.grasscutter.utils.EventConsumer;
import emu.grasscutter.utils.Utils;
import java.io.File;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
@ -47,12 +47,23 @@ public final class PluginManager {
List<File> plugins = Arrays.stream(files)
.filter(file -> file.getName().endsWith(".jar"))
.toList();
URL[] pluginNames = new URL[plugins.size()];
plugins.forEach(plugin -> {
try {
pluginNames[plugins.indexOf(plugin)] = plugin.toURI().toURL();
} catch (MalformedURLException exception) {
Grasscutter.getLogger().warn("Unable to load plugin.", exception);
}
});
URLClassLoader classLoader = new URLClassLoader(pluginNames);
plugins.forEach(plugin -> {
try {
URL url = plugin.toURI().toURL();
try (URLClassLoader loader = new URLClassLoader(new URL[]{url})) {
URL configFile = loader.findResource("plugin.json");
URL configFile = loader.findResource("plugin.json"); // Find the plugin.json file for each plugin.
InputStreamReader fileReader = new InputStreamReader(configFile.openStream());
PluginConfig pluginConfig = Grasscutter.getGsonFactory().fromJson(fileReader, PluginConfig.class);
@ -68,10 +79,10 @@ public final class PluginManager {
JarEntry entry = entries.nextElement();
if(entry.isDirectory() || !entry.getName().endsWith(".class") || entry.getName().contains("module-info")) continue;
String className = entry.getName().replace(".class", "").replace("/", ".");
loader.loadClass(className);
classLoader.loadClass(className); // Use the same class loader for ALL plugins.
}
Class<?> pluginClass = loader.loadClass(pluginConfig.mainClass);
Class<?> pluginClass = classLoader.loadClass(pluginConfig.mainClass);
Plugin pluginInstance = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
this.loadPlugin(pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig), loader);
@ -156,6 +167,10 @@ public final class PluginManager {
.toList().forEach(handler -> this.invokeHandler(event, handler));
}
public Plugin getPlugin(String name) {
return this.plugins.get(name);
}
/**
* Performs logic checks then invokes the provided event handler.
* @param event The event passed through to the handler.
@ -167,4 +182,4 @@ public final class PluginManager {
(event.isCanceled() && handler.ignoresCanceled())
) handler.getCallback().consume((T) event);
}
}
}

View File

@ -28,6 +28,7 @@ public final class PlayerHook {
/**
* Kicks a player from the server.
* TODO: Refactor to kick using a packet.
*/
public void kick() {
this.player.getSession().close();

View File

@ -1,2 +0,0 @@
# Grasscutter Plugin API
**Warning!** As of now, this is a work in progress and isn't completely documented.

View File

@ -1,19 +1,14 @@
package emu.grasscutter.scripts;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
@ -23,12 +18,8 @@ import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.data.def.WorldLevelData;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.constants.ScriptGadgetState;
import emu.grasscutter.scripts.constants.ScriptRegionShape;
import emu.grasscutter.scripts.data.SceneBlock;
import emu.grasscutter.scripts.data.SceneConfig;
import emu.grasscutter.scripts.data.SceneGadget;
@ -56,7 +47,12 @@ public class SceneScriptManager {
private final Int2ObjectOpenHashMap<Set<SceneTrigger>> triggers;
private final Int2ObjectOpenHashMap<SceneRegion> regions;
private SceneGroup currentGroup;
private AtomicInteger monsterAlive;
private AtomicInteger monsterTideCount;
private int monsterSceneLimit;
private ConcurrentLinkedQueue<Integer> monsterOrders;
public SceneScriptManager(Scene scene) {
this.scene = scene;
this.scriptLib = new ScriptLib(this);
@ -166,7 +162,7 @@ public class SceneScriptManager {
List<SceneBlock> blocks = ScriptLoader.getSerializer().toList(SceneBlock.class, bindings.get("block_rects"));
for (int i = 0; i < blocks.size(); i++) {
SceneBlock block = blocks.get(0);
SceneBlock block = blocks.get(i);
block.id = blockIds.get(i);
loadBlockFromScript(block);
@ -222,7 +218,8 @@ public class SceneScriptManager {
cs.eval(getBindings());
// Set
group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters"));
group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream()
.collect(Collectors.toMap(x -> x.config_id, y -> y));
group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets"));
group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers"));
group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites"));
@ -235,7 +232,7 @@ public class SceneScriptManager {
// Add monsters to suite TODO optimize
Int2ObjectMap<Object> map = new Int2ObjectOpenHashMap<>();
group.monsters.forEach(m -> map.put(m.config_id, m));
group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m));
group.gadgets.forEach(m -> map.put(m.config_id, m));
for (SceneSuite suite : group.suites) {
@ -323,60 +320,92 @@ public class SceneScriptManager {
}
public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) {
spawnMonstersInGroup(group, group.getSuiteByIndex(suiteIndex));
this.currentGroup = group;
this.monsterSceneLimit = 0;
var suite = group.getSuiteByIndex(suiteIndex);
if(suite == null){
return;
}
suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob));
}
public void spawnMonstersInGroup(SceneGroup group) {
spawnMonstersInGroup(group, null);
this.currentGroup = group;
this.monsterSceneLimit = 0;
group.monsters.values().forEach(mob -> spawnMonstersInGroup(group, mob));
}
public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) {
List<SceneMonster> monsters = group.monsters;
if (suite != null) {
monsters = suite.sceneMonsters;
public void spawnMonstersInGroup(SceneGroup group,Integer[] ordersConfigId, int tideCount, int sceneLimit) {
this.currentGroup = group;
this.monsterSceneLimit = sceneLimit;
this.monsterTideCount = new AtomicInteger(tideCount);
this.monsterAlive = new AtomicInteger(0);
this.monsterOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId));
// add the last turn
group.monsters.keySet().stream()
.filter(i -> !this.monsterOrders.contains(i))
.forEach(this.monsterOrders::add);
for (int i = 0; i < sceneLimit; i++) {
spawnMonstersInGroup(group, group.monsters.get(this.monsterOrders.poll()));
}
}
public void spawnMonstersInGroup(SceneGroup group, SceneMonster monster) {
if(monster == null){
return;
}
if(this.monsterSceneLimit > 0){
this.monsterTideCount.decrementAndGet();
this.monsterAlive.incrementAndGet();
}
List<GameEntity> toAdd = new ArrayList<>();
for (SceneMonster monster : monsters) {
MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id);
if (data == null) {
continue;
}
// Calculate level
int level = monster.level;
if (getScene().getDungeonData() != null) {
level = getScene().getDungeonData().getShowLevel();
} else if (getScene().getWorld().getWorldLevel() > 0) {
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel());
if (worldLevelData != null) {
level = worldLevelData.getMonsterLevel();
}
}
// Spawn mob
EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level);
entity.getRotation().set(monster.rot);
entity.setGroupId(group.id);
entity.setConfigId(monster.config_id);
toAdd.add(entity);
MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id);
if (data == null) {
return;
}
if (toAdd.size() > 0) {
getScene().addEntities(toAdd);
for (GameEntity entity : toAdd) {
callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId()));
// Calculate level
int level = monster.level;
if (getScene().getDungeonData() != null) {
level = getScene().getDungeonData().getShowLevel();
} else if (getScene().getWorld().getWorldLevel() > 0) {
WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel());
if (worldLevelData != null) {
level = worldLevelData.getMonsterLevel();
}
}
// Spawn mob
EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level);
entity.getRotation().set(monster.rot);
entity.setGroupId(group.id);
entity.setConfigId(monster.config_id);
getScene().addEntity(entity);
callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId()));
}
public void onMonsterDie(){
if(this.monsterSceneLimit <= 0){
return;
}
if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) {
// maybe not happen
return;
}
if(this.monsterTideCount.get() > 0){
// add more
spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
}else if(this.monsterAlive.get() == 0){
// spawn the last turn of monsters
while(!this.monsterOrders.isEmpty()){
spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll()));
}
}
}
// Events
public void callEvent(int eventType, ScriptArgs params) {
@ -405,4 +434,8 @@ public class SceneScriptManager {
}
}
}
// public LuaValue safetyCall(){
//
// }
}

View File

@ -1,28 +1,24 @@
package emu.grasscutter.scripts;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.game.dungeons.DungeonChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneMonster;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
public class ScriptLib {
public static final Logger logger = LoggerFactory.getLogger(ScriptLib.class);
private final SceneScriptManager sceneScriptManager;
public ScriptLib(SceneScriptManager sceneScriptManager) {
@ -34,6 +30,8 @@ public class ScriptLib {
}
public int SetGadgetStateByConfigId(int configId, int gadgetState) {
logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}",
configId,gadgetState);
Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e.getConfigId() == configId).findFirst();
@ -53,6 +51,8 @@ public class ScriptLib {
}
public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) {
logger.debug("[LUA] Call SetGroupGadgetStateByConfigId with {},{},{}",
groupId,configId,gadgetState);
List<GameEntity> list = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e.getGroupId() == groupId).toList();
@ -71,6 +71,8 @@ public class ScriptLib {
}
public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) {
logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}",
groupId,configId,options);
Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst();
@ -90,6 +92,8 @@ public class ScriptLib {
}
public int DelWorktopOptionByGroupId(int groupId, int configId, int option) {
logger.debug("[LUA] Call DelWorktopOptionByGroupId with {},{},{}",groupId,configId,option);
Optional<GameEntity> entity = getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst();
@ -109,20 +113,24 @@ public class ScriptLib {
}
// Some fields are guessed
public int AutoMonsterTide(int challengeIndex, int groupId, int[] config_ids, int param4, int param5, int param6) {
public int AutoMonsterTide(int challengeIndex, int groupId, Integer[] ordersConfigId, int tideCount, int sceneLimit, int param6) {
logger.debug("[LUA] Call AutoMonsterTide with {},{},{},{},{},{}",
challengeIndex,groupId,ordersConfigId,tideCount,sceneLimit,param6);
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
if (group == null || group.monsters == null) {
return 1;
}
// TODO just spawn all from group for now
this.getSceneScriptManager().spawnMonstersInGroup(group);
this.getSceneScriptManager().spawnMonstersInGroup(group, ordersConfigId, tideCount, sceneLimit);
return 0;
}
public int AddExtraGroupSuite(int groupId, int suite) {
logger.debug("[LUA] Call AddExtraGroupSuite with {},{}",
groupId,suite);
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
if (group == null || group.monsters == null) {
@ -136,8 +144,17 @@ public class ScriptLib {
}
// param3 (probably time limit for timed dungeons)
public int ActiveChallenge(int challengeId, int challengeIndex, int param3, int groupId, int objectiveKills, int param5) {
public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) {
logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}",
challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5);
SceneGroup group = getSceneScriptManager().getGroupById(groupId);
var objective = objectiveKills;
if(group == null){
group = getSceneScriptManager().getGroupById(timeLimitOrGroupId);
objective = groupId;
}
if (group == null || group.monsters == null) {
return 1;
@ -146,7 +163,7 @@ public class ScriptLib {
DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group);
challenge.setChallengeId(challengeId);
challenge.setChallengeIndex(challengeIndex);
challenge.setObjective(objectiveKills);
challenge.setObjective(objective);
getSceneScriptManager().getScene().setChallenge(challenge);
@ -155,26 +172,37 @@ public class ScriptLib {
}
public int GetGroupMonsterCountByGroupId(int groupId) {
logger.debug("[LUA] Call GetGroupMonsterCountByGroupId with {}",
groupId);
return (int) getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityMonster && e.getGroupId() == groupId)
.count();
}
public int GetGroupVariableValue(String var) {
logger.debug("[LUA] Call GetGroupVariableValue with {}",
var);
return getSceneScriptManager().getVariables().getOrDefault(var, 0);
}
public int SetGroupVariableValue(String var, int value) {
logger.debug("[LUA] Call SetGroupVariableValue with {},{}",
var, value);
getSceneScriptManager().getVariables().put(var, value);
return 0;
}
public LuaValue ChangeGroupVariableValue(String var, int value) {
logger.debug("[LUA] Call ChangeGroupVariableValue with {},{}",
var, value);
getSceneScriptManager().getVariables().put(var, getSceneScriptManager().getVariables().get(var) + value);
return LuaValue.ZERO;
}
public int RefreshGroup(LuaTable table) {
logger.debug("[LUA] Call RefreshGroup with {}",
table);
// Kill and Respawn?
int groupId = table.get("group_id").toint();
int suite = table.get("suite").toint();
@ -192,6 +220,8 @@ public class ScriptLib {
}
public int GetRegionEntityCount(LuaTable table) {
logger.debug("[LUA] Call GetRegionEntityCount with {}",
table);
int regionId = table.get("region_eid").toint();
int entityType = table.get("entity_type").toint();
@ -205,21 +235,68 @@ public class ScriptLib {
}
public void PrintContextLog(String msg) {
Grasscutter.getLogger().info("[LUA] " + msg);
logger.info("[LUA] " + msg);
}
public int TowerCountTimeStatus(int var1, int var2){
public int TowerCountTimeStatus(int isDone, int var2){
logger.debug("[LUA] Call TowerCountTimeStatus with {},{}",
isDone,var2);
// TODO record time
return 0;
}
public int GetGroupMonsterCount(int var1){
// Maybe...
return GetGroupMonsterCountByGroupId(var1);
logger.debug("[LUA] Call GetGroupMonsterCount with {}",
var1);
return (int) getSceneScriptManager().getScene().getEntities().values().stream()
.filter(e -> e instanceof EntityMonster)
.count();
}
public int SetMonsterBattleByGroup(int var1, int var2, int var3){
logger.debug("[LUA] Call SetMonsterBattleByGroup with {},{},{}",
var1,var2,var3);
return 0;
}
public int CauseDungeonFail(int var1){
logger.debug("[LUA] Call CauseDungeonFail with {}",
var1);
return 0;
}
// 8-1
public int GetGroupVariableValueByGroup(int var1, String var2, int var3){
logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}",
var1,var2,var3);
//TODO
return getSceneScriptManager().getVariables().getOrDefault(var2, 0);
}
public int SetIsAllowUseSkill(int canUse, int var2){
logger.debug("[LUA] Call SetIsAllowUseSkill with {},{}",
canUse,var2);
getSceneScriptManager().getScene().broadcastPacket(new PacketCanUseSkillNotify(canUse == 1));
return 0;
}
public int KillEntityByConfigId(LuaTable table){
logger.debug("[LUA] Call KillEntityByConfigId with {}",
table);
var configId = table.get("config_id");
if(configId == LuaValue.NIL){
return 1;
}
var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint());
if(entity == null){
return 1;
}
getSceneScriptManager().getScene().killEntity(entity, 0);
return 0;
}
}

View File

@ -1,17 +1,21 @@
package emu.grasscutter.scripts.data;
import java.util.List;
import emu.grasscutter.utils.Position;
import java.util.List;
import java.util.Map;
public class SceneGroup {
public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference
public int id;
public int refresh_id;
public Position pos;
public List<SceneMonster> monsters;
/**
* ConfigId - Monster
*/
public Map<Integer,SceneMonster> monsters;
public List<SceneGadget> gadgets;
public List<SceneTrigger> triggers;
public List<SceneRegion> regions;

View File

@ -2,6 +2,7 @@ package emu.grasscutter.server.dispatch;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode;
@ -9,6 +10,8 @@ import express.http.HttpContextHandler;
import express.http.Request;
import express.http.Response;
import static emu.grasscutter.utils.Language.translate;
public final class DispatchHttpJsonHandler implements HttpContextHandler {
private final String response;
private final String[] missingRoutes = { // TODO: When http requests for theses routes are found please remove it from this list and update the route request type in the DispatchServer
@ -31,8 +34,8 @@ public final class DispatchHttpJsonHandler implements HttpContextHandler {
@Override
public void handle(Request req, Response res) throws IOException {
// Checking for ALL here isn't required as when ALL is enabled enableDevLogging() gets enabled
if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> x == req.baseUrl())) {
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_request.replace("{ip}", req.ip()).replace("{method}", req.method()).replace("{url}", req.baseUrl()) + (Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING ? "(MISSING)" : ""));
if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, req.baseUrl()))) {
Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING ? "(MISSING)" : ""));
}
res.send(response);
}

View File

@ -35,6 +35,8 @@ import java.io.*;
import java.net.URLDecoder;
import java.util.*;
import static emu.grasscutter.utils.Language.translate;
public final class DispatchServer {
public static String query_region_list = "";
public static String query_cur_region = "";
@ -213,21 +215,21 @@ public final class DispatchServer {
sslContextFactory.setKeyStorePassword(Grasscutter.getConfig().getDispatchOptions().KeystorePassword);
} catch (Exception e) {
e.printStackTrace();
Grasscutter.getLogger().warn(Grasscutter.getLanguage().Not_load_keystore);
Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error"));
try {
sslContextFactory.setKeyStorePath(keystoreFile.getPath());
sslContextFactory.setKeyStorePassword("123456");
Grasscutter.getLogger().warn(Grasscutter.getLanguage().Use_default_keystore);
Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.default_password"));
} catch (Exception e2) {
Grasscutter.getLogger().warn(Grasscutter.getLanguage().Load_keystore_error);
Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.general_error"));
e2.printStackTrace();
}
}
serverConnector = new ServerConnector(server, sslContextFactory);
} else {
Grasscutter.getLogger().warn(Grasscutter.getLanguage().Not_find_ssl_cert);
Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error"));
Grasscutter.getConfig().getDispatchOptions().UseSSL = false;
serverConnector = new ServerConnector(server);
@ -245,13 +247,16 @@ public final class DispatchServer {
if(Grasscutter.getConfig().DebugMode == ServerDebugMode.ALL) {
config.enableDevLogging();
}
if (Grasscutter.getConfig().getDispatchOptions().CORS){
if (Grasscutter.getConfig().getDispatchOptions().CORSAllowedOrigins.length > 0) config.enableCorsForOrigin(Grasscutter.getConfig().getDispatchOptions().CORSAllowedOrigins);
else config.enableCorsForAllOrigins();
}
});
httpServer.get("/", (req, res) -> res.send(Grasscutter.getLanguage().Welcome));
httpServer.get("/", (req, res) -> res.send(translate("messages.status.welcome")));
httpServer.raw().error(404, ctx -> {
if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING) {
Grasscutter.getLogger().info(Grasscutter.getLanguage().Potential_unhandled_request.replace("{method}", ctx.method()).replace("{url}", ctx.url()));
Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", ctx.method(), ctx.url()));
}
ctx.contentType("text/html");
ctx.result("<!doctype html><html lang=\"en\"><body><img src=\"https://http.cat/404\" /></body></html>"); // I'm like 70% sure this won't break anything.
@ -309,7 +314,7 @@ public final class DispatchServer {
if (requestData == null) {
return;
}
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_try_login.replace("{ip}", req.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", req.ip()));
res.send(this.getAuthHandler().handleGameLogin(req, requestData));
});
@ -329,7 +334,7 @@ public final class DispatchServer {
return;
}
LoginResultJson responseData = new LoginResultJson();
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_login_token.replace("{ip}", req.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_attempt", req.ip()));
// Login
Account account = DatabaseHelper.getAccountById(requestData.uid);
@ -337,16 +342,16 @@ public final class DispatchServer {
// Test
if (account == null || !account.getSessionKey().equals(requestData.token)) {
responseData.retcode = -111;
responseData.message = Grasscutter.getLanguage().Game_account_cache_error;
responseData.message = translate("messages.dispatch.account.account_cache_error");
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_token_login_failed.replace("{ip}", req.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_error", req.ip()));
} else {
responseData.message = "OK";
responseData.data.account.uid = requestData.uid;
responseData.data.account.token = requestData.token;
responseData.data.account.email = account.getEmail();
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_login_in_token.replace("{ip}", req.ip()).replace("{uid}", responseData.data.account.uid));
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_success", req.ip(), requestData.uid));
}
res.send(responseData);
@ -376,16 +381,16 @@ public final class DispatchServer {
// Test
if (account == null || !account.getSessionKey().equals(loginData.token)) {
responseData.retcode = -201;
responseData.message = Grasscutter.getLanguage().Wrong_session_key;
responseData.message = translate("messages.dispatch.account.session_key_error");
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_failed_exchange_combo_token.replace("{ip}", req.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_error", req.ip()));
} else {
responseData.message = "OK";
responseData.data.open_id = loginData.uid;
responseData.data.combo_id = "157795300";
responseData.data.combo_token = account.generateLoginToken();
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_exchange_combo_token.replace("{ip}", req.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_success", req.ip()));
}
res.send(responseData);
@ -458,7 +463,7 @@ public final class DispatchServer {
httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files
httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port);
Grasscutter.getLogger().info(Grasscutter.getLanguage().Dispatch_start_server_port.replace("{port}", Integer.toString(httpServer.raw().port())));
Grasscutter.getLogger().info(translate("messages.dispatch.port_bind", Integer.toString(httpServer.raw().port())));
}
private Map<String, String> parseQueryString(String qs) {

View File

@ -8,6 +8,8 @@ import emu.grasscutter.server.dispatch.json.LoginResultJson;
import express.http.Request;
import express.http.Response;
import static emu.grasscutter.utils.Language.translate;
public class DefaultAuthenticationHandler implements AuthenticationHandler {
@Override
@ -34,11 +36,9 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler {
// Check if account exists, else create a new one.
if (account == null) {
// Account doesnt exist, so we can either auto create it if the config value is
// set
// Account doesn't exist, so we can either auto create it if the config value is set.
if (Grasscutter.getConfig().getDispatchOptions().AutomaticallyCreateAccounts) {
// 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.createAccountWithId(requestData.account, 0);
for (String permission : Grasscutter.getConfig().getDispatchOptions().defaultPermissions) {
@ -51,19 +51,18 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler {
responseData.data.account.token = account.generateSessionKey();
responseData.data.account.email = account.getEmail();
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_failed_login_account_create.replace("{ip}", req.ip()).replace("{uid}", responseData.data.account.uid));
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", req.ip(), responseData.data.account.uid));
} else {
responseData.retcode = -201;
responseData.message = Grasscutter.getLanguage().Username_not_found_create_failed;
responseData.message = translate("messages.dispatch.account.username_create_error");
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_failed_login_account_no_found.replace("{ip}", req.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", req.ip()));
}
} else {
responseData.retcode = -201;
responseData.message = Grasscutter.getLanguage().Username_not_found;
responseData.message = translate("messages.dispatch.account.username_error");
Grasscutter.getLogger().info(String
.format(Grasscutter.getLanguage().Client_failed_login_account_no_found, req.ip()));
Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_exist_error", req.ip()));
}
} else {
// Account was found, log the player in
@ -72,7 +71,7 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler {
responseData.data.account.token = account.generateSessionKey();
responseData.data.account.email = account.getEmail();
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_login.replace("{ip}", req.ip()).replace("{uid}", responseData.data.account.uid));
Grasscutter.getLogger().info(translate("messages.dispatch.account.login_success", req.ip(), responseData.data.account.uid));
}
return responseData;

View File

@ -8,6 +8,7 @@ import emu.grasscutter.game.Account;
import emu.grasscutter.game.combine.CombineManger;
import emu.grasscutter.game.drop.DropManager;
import emu.grasscutter.game.dungeons.DungeonManager;
import emu.grasscutter.game.expedition.ExpeditionManager;
import emu.grasscutter.game.gacha.GachaManager;
import emu.grasscutter.game.managers.ChatManager;
import emu.grasscutter.game.managers.InventoryManager;
@ -28,6 +29,11 @@ import java.net.InetSocketAddress;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static emu.grasscutter.utils.Language.translate;
public final class GameServer extends KcpServer {
private final InetSocketAddress address;
@ -42,12 +48,20 @@ public final class GameServer extends KcpServer {
private final ShopManager shopManager;
private final MultiplayerManager multiplayerManager;
private final DungeonManager dungeonManager;
private final ExpeditionManager expeditionManager;
private final CommandMap commandMap;
private final TaskMap taskMap;
private final DropManager dropManager;
private final CombineManger combineManger;
public GameServer() {
this(new InetSocketAddress(
Grasscutter.getConfig().getGameServerOptions().Ip,
Grasscutter.getConfig().getGameServerOptions().Port
));
}
public GameServer(InetSocketAddress address) {
super(address);
@ -66,20 +80,8 @@ public final class GameServer extends KcpServer {
this.commandMap = new CommandMap(true);
this.taskMap = new TaskMap(true);
this.dropManager = new DropManager(this);
this.expeditionManager = new ExpeditionManager(this);
this.combineManger = new CombineManger(this);
// Schedule game loop.
Timer gameLoop = new Timer();
gameLoop.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
onTick();
} catch (Exception e) {
Grasscutter.getLogger().error(Grasscutter.getLanguage().An_error_occurred_during_game_update, e);
}
}
}, new Date(), 1000L);
// Hook into shutdown event.
Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown));
@ -124,7 +126,11 @@ public final class GameServer extends KcpServer {
public DungeonManager getDungeonManager() {
return dungeonManager;
}
public ExpeditionManager getExpeditionManager() {
return expeditionManager;
}
public CommandMap getCommandMap() {
return this.commandMap;
}
@ -132,6 +138,7 @@ public final class GameServer extends KcpServer {
public CombineManger getCombineManger(){
return this.combineManger;
}
public TaskMap getTaskMap() {
return this.taskMap;
}
@ -212,10 +219,28 @@ public final class GameServer extends KcpServer {
}
@Override
public synchronized void start() {
// Schedule game loop.
Timer gameLoop = new Timer();
gameLoop.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
onTick();
} catch (Exception e) {
Grasscutter.getLogger().error(translate("messages.game.game_update_error"), e);
}
}
}, new Date(), 1000L);
super.start();
}
@Override
public void onStartFinish() {
Grasscutter.getLogger().info(Grasscutter.getLanguage().Grasscutter_is_free);
Grasscutter.getLogger().info(Grasscutter.getLanguage().Game_start_port.replace("{port}", Integer.toString(address.getPort())));
Grasscutter.getLogger().info(translate("messages.status.free_software"));
Grasscutter.getLogger().info(translate("messages.game.port_bind", Integer.toString(address.getPort())));
ServerStartEvent event = new ServerStartEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); event.call();
}

View File

@ -14,6 +14,7 @@ import emu.grasscutter.server.game.GameSession.SessionState;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@SuppressWarnings("unchecked")
public class GameServerPacketHandler {
private final Int2ObjectMap<PacketHandler> handlers;

View File

@ -22,6 +22,8 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import static emu.grasscutter.utils.Language.translate;
public class GameSession extends KcpChannel {
private GameServer server;
@ -113,21 +115,21 @@ public class GameSession extends KcpChannel {
@Override
protected void onConnect() {
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_connect.replace("{address}", getAddress().getHostString().toLowerCase()));
Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().getHostString().toLowerCase()));
}
@Override
protected synchronized void onDisconnect() { // Synchronize so we dont add character at the same time
Grasscutter.getLogger().info(Grasscutter.getLanguage().Client_disconnect.replace("{address}", getAddress().getHostString().toLowerCase()));
protected synchronized void onDisconnect() { // Synchronize so we don't add character at the same time.
Grasscutter.getLogger().info(translate("messages.game.disconnect", this.getAddress().getHostString().toLowerCase()));
// Set state so no more packets can be handled
this.setState(SessionState.INACTIVE);
// Save after disconnecting
if (this.isLoggedIn()) {
// Save
// Call logout event.
getPlayer().onLogout();
// Remove from gameserver
// Remove from server.
getServer().getPlayers().remove(getPlayer().getUid());
}
}

View File

@ -0,0 +1,15 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAvatarExpeditionAllDataRsp;
@Opcodes(PacketOpcodes.AvatarExpeditionAllDataReq)
public class HandlerAvatarExpeditionAllDataReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
session.send(new PacketAvatarExpeditionAllDataRsp(session.getPlayer()));
}
}

View File

@ -0,0 +1,25 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarExpeditionCallBackReqOuterClass.AvatarExpeditionCallBackReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAvatarExpeditionCallBackRsp;
import emu.grasscutter.server.packet.send.PacketAvatarExpeditionStartRsp;
import emu.grasscutter.utils.Utils;
@Opcodes(PacketOpcodes.AvatarExpeditionCallBackReq)
public class HandlerAvatarExpeditionCallBackReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
AvatarExpeditionCallBackReq req = AvatarExpeditionCallBackReq.parseFrom(payload);
for (int i = 0; i < req.getAvatarGuidCount(); i++) {
session.getPlayer().removeExpeditionInfo(req.getAvatarGuid(i));
}
session.getPlayer().save();
session.send(new PacketAvatarExpeditionCallBackRsp(session.getPlayer()));
}
}

View File

@ -0,0 +1,61 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.drop.DropData;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.expedition.ExpeditionRewardData;
import emu.grasscutter.game.expedition.ExpeditionRewardDataList;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarExpeditionGetRewardReqOuterClass.AvatarExpeditionGetRewardReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAvatarExpeditionCallBackRsp;
import emu.grasscutter.server.packet.send.PacketAvatarExpeditionGetRewardRsp;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@Opcodes(PacketOpcodes.AvatarExpeditionGetRewardReq)
public class HandlerAvatarExpeditionGetRewardReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
AvatarExpeditionGetRewardReq req = AvatarExpeditionGetRewardReq.parseFrom(payload);
ExpeditionInfo expInfo = session.getPlayer().getExpeditionInfo(req.getAvatarGuid());
List<GameItem> items = new LinkedList<>();
if (session.getServer().getExpeditionManager().getExpeditionRewardDataList().containsKey(expInfo.getExpId())) {
for (ExpeditionRewardDataList RewardDataList : session.getServer().getExpeditionManager().getExpeditionRewardDataList().get(expInfo.getExpId())) {
if(RewardDataList.getHourTime() == expInfo.getHourTime()){
if(!RewardDataList.getExpeditionRewardData().isEmpty()){
for (ExpeditionRewardData RewardData :RewardDataList.getExpeditionRewardData()) {
int num = RewardData.getMinCount();
if(RewardData.getMinCount() != RewardData.getMaxCount()){
num = Utils.randomRange(RewardData.getMinCount(), RewardData.getMaxCount());
}
items.add(new GameItem(RewardData.getItemId(), num));
}
}
}
}
}
session.getPlayer().getInventory().addItems(items);
session.getPlayer().sendPacket(new PacketItemAddHintNotify(items, ActionReason.ExpeditionReward));
session.getPlayer().removeExpeditionInfo(req.getAvatarGuid());
session.getPlayer().save();
session.send(new PacketAvatarExpeditionGetRewardRsp(session.getPlayer(), items));
}
}

View File

@ -0,0 +1,23 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarExpeditionStartReqOuterClass.AvatarExpeditionStartReq;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAvatarExpeditionStartRsp;
import emu.grasscutter.utils.Utils;
@Opcodes(PacketOpcodes.AvatarExpeditionStartReq)
public class HandlerAvatarExpeditionStartReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
AvatarExpeditionStartReq req = AvatarExpeditionStartReq.parseFrom(payload);
int startTime = Utils.getCurrentSeconds();
session.getPlayer().addExpeditionInfo(req.getAvatarGuid(), req.getExpId(), req.getHourTime(), startTime);
session.getPlayer().save();
session.send(new PacketAvatarExpeditionStartRsp(session.getPlayer()));
}
}

View File

@ -1,6 +1,8 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify;
@ -8,11 +10,19 @@ import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo;
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
@Opcodes(PacketOpcodes.CombatInvocationsNotify)
public class HandlerCombatInvocationsNotify extends PacketHandler {
private float cachedLandingSpeed = 0;
private long cachedLandingTimeMillisecond = 0;
private boolean monitorLandingEvent = false;
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload);
@ -28,7 +38,34 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData());
GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId());
if (entity != null) {
session.getPlayer().getMovementManager().handle(session, moveInfo, entity);
// Move player
MotionInfo motionInfo = moveInfo.getMotionInfo();
entity.getPosition().set(motionInfo.getPos());
entity.getRotation().set(motionInfo.getRot());
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
MotionState motionState = motionInfo.getState();
entity.setMotionState(motionState);
session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity);
// TODO: handle MOTION_FIGHT landing which has a different damage factor
// Also, for plunge attacks, LAND_SPEED is always -30 and is not useful.
// May need the height when starting plunge attack.
// MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets.
// Cache land speed for later use.
if (motionState == MotionState.MOTION_LAND_SPEED) {
cachedLandingSpeed = motionInfo.getSpeed().getY();
cachedLandingTimeMillisecond = System.currentTimeMillis();
monitorLandingEvent = true;
}
if (monitorLandingEvent) {
if (motionState == MotionState.MOTION_FALL_ON_GROUND) {
monitorLandingEvent = false;
handleFallOnGround(session, entity, motionState);
}
}
}
break;
default:
@ -47,5 +84,48 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
}
}
private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) {
// People have reported that after plunge attack (client sends a FIGHT instead of FALL_ON_GROUND) they will die
// if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping again.
// A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet.
// 200ms seems to be a reasonable delay.
int maxDelay = 200;
long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond;
Grasscutter.getLogger().trace("MOTION_FALL_ON_GROUND received after " + actualDelay + "/" + maxDelay + "ms." + (actualDelay > maxDelay ? " Discard" : ""));
if (actualDelay > maxDelay) {
return;
}
float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float damageFactor = 0;
if (cachedLandingSpeed < -23.5) {
damageFactor = 0.33f;
}
if (cachedLandingSpeed < -25) {
damageFactor = 0.5f;
}
if (cachedLandingSpeed < -26.5) {
damageFactor = 0.66f;
}
if (cachedLandingSpeed < -28) {
damageFactor = 1f;
}
float damage = maxHP * damageFactor;
float newHP = currentHP - damage;
if (newHP < 0) {
newHP = 0;
}
if (damageFactor > 0) {
Grasscutter.getLogger().debug(currentHP + "/" + maxHP + "\tLandingSpeed: " + cachedLandingSpeed +
"\tDamageFactor: " + damageFactor + "\tDamage: " + damage + "\tNewHP: " + newHP);
} else {
Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage");
}
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
if (newHP == 0) {
session.getPlayer().getStaminaManager().killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_FALL);
}
cachedLandingSpeed = 0;
}
}

View File

@ -1,20 +1,11 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.managers.SotSManager.SotSManager;
import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.List;
@Opcodes(PacketOpcodes.EnterTransPointRegionNotify)
public class HandlerEnterTransPointRegionNotify extends PacketHandler {

View File

@ -1,6 +1,5 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
@ -15,12 +14,7 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler {
EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload);
// TODO: Will be used for deducting stamina for charged skills.
int caster = notify.getCasterId();
int skill = notify.getSkillId();
// Grasscutter.getLogger().warn(caster + "\t" + skill);
// session.getPlayer().getScene().broadcastPacket(new PacketEvtAvatarStandUpNotify(notify));
session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, notify);
}
}

View File

@ -0,0 +1,18 @@
package emu.grasscutter.server.packet.recv;
import emu.grasscutter.game.managers.SotSManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameSession;
@Opcodes(PacketOpcodes.ExitTransPointRegionNotify)
public class HandlerExitTransPointRegionNotify extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{
Player player = session.getPlayer();
SotSManager sotsManager = player.getSotSManager();
sotsManager.cancelAutoRecover();
}
}

View File

@ -0,0 +1,33 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarExpeditionAllDataRspOuterClass.AvatarExpeditionAllDataRsp;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
import java.util.*;
public class PacketAvatarExpeditionAllDataRsp extends BasePacket {
public PacketAvatarExpeditionAllDataRsp(Player player) {
super(PacketOpcodes.AvatarExpeditionAllDataRsp);
List<Integer> openExpeditionList = new ArrayList<>(List.of(306,305,304,303,302,301,206,105,204,104,203,103,202,101,102,201,106,205));
Map<Long, AvatarExpeditionInfo> avatarExpeditionInfoList = new HashMap<Long, AvatarExpeditionInfo>();
var expeditionInfo = player.getExpeditionInfo();
for (Long key : player.getExpeditionInfo().keySet()) {
ExpeditionInfo e = expeditionInfo.get(key);
avatarExpeditionInfoList.put(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build());
};
AvatarExpeditionAllDataRsp.Builder proto = AvatarExpeditionAllDataRsp.newBuilder()
.addAllOpenExpeditionList(openExpeditionList)
.setExpeditionCountLimit(5)
.putAllExpeditionInfoMap(avatarExpeditionInfoList);
this.setData(proto.build());
}
}

View File

@ -0,0 +1,23 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarExpeditionCallBackRspOuterClass.AvatarExpeditionCallBackRsp;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
public class PacketAvatarExpeditionCallBackRsp extends BasePacket {
public PacketAvatarExpeditionCallBackRsp(Player player) {
super(PacketOpcodes.AvatarExpeditionCallBackRsp);
AvatarExpeditionCallBackRsp.Builder proto = AvatarExpeditionCallBackRsp.newBuilder();
var expeditionInfo = player.getExpeditionInfo();
for (Long key : player.getExpeditionInfo().keySet()) {
ExpeditionInfo e = expeditionInfo.get(key);
proto.putExpeditionInfoMap(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build());
};
this.setData(proto.build());
}
}

View File

@ -0,0 +1,29 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarExpeditionDataNotifyOuterClass.AvatarExpeditionDataNotify;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
import java.util.*;
public class PacketAvatarExpeditionDataNotify extends BasePacket {
public PacketAvatarExpeditionDataNotify(Player player) {
super(PacketOpcodes.AvatarExpeditionDataNotify);
Map<Long, AvatarExpeditionInfo> avatarExpeditionInfoList = new HashMap<Long, AvatarExpeditionInfo>();
var expeditionInfo = player.getExpeditionInfo();
for (Long key : player.getExpeditionInfo().keySet()) {
ExpeditionInfo e = expeditionInfo.get(key);
avatarExpeditionInfoList.put(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build());
};
AvatarExpeditionDataNotify.Builder proto = AvatarExpeditionDataNotify.newBuilder()
.putAllExpeditionInfoMap(avatarExpeditionInfoList);
this.setData(proto.build());
}
}

View File

@ -0,0 +1,30 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarExpeditionGetRewardRspOuterClass.AvatarExpeditionGetRewardRsp;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
import java.util.Collection;
public class PacketAvatarExpeditionGetRewardRsp extends BasePacket {
public PacketAvatarExpeditionGetRewardRsp(Player player, Collection<GameItem> items) {
super(PacketOpcodes.AvatarExpeditionGetRewardRsp);
AvatarExpeditionGetRewardRsp.Builder proto = AvatarExpeditionGetRewardRsp.newBuilder();
var expeditionInfo = player.getExpeditionInfo();
for (Long key : player.getExpeditionInfo().keySet()) {
ExpeditionInfo e = expeditionInfo.get(key);
proto.putExpeditionInfoMap(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build());
};
for (GameItem item : items) {
proto.addItemList(item.toItemParam());
}
this.setData(proto.build());
}
}

View File

@ -0,0 +1,23 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
import emu.grasscutter.net.proto.AvatarExpeditionStartRspOuterClass.AvatarExpeditionStartRsp;
public class PacketAvatarExpeditionStartRsp extends BasePacket {
public PacketAvatarExpeditionStartRsp(Player player) {
super(PacketOpcodes.AvatarExpeditionStartRsp);
AvatarExpeditionStartRsp.Builder proto = AvatarExpeditionStartRsp.newBuilder();
var expeditionInfo = player.getExpeditionInfo();
for (Long key : player.getExpeditionInfo().keySet()) {
ExpeditionInfo e = expeditionInfo.get(key);
proto.putExpeditionInfoMap(key, AvatarExpeditionInfo.newBuilder().setStateValue(e.getState()).setExpId(e.getExpId()).setHourTime(e.getHourTime()).setStartTime(e.getStartTime()).build());
};
this.setData(proto.build());
}
}

View File

@ -0,0 +1,19 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.CanUseSkillNotifyOuterClass;
public class PacketCanUseSkillNotify extends BasePacket {
public PacketCanUseSkillNotify(boolean canUseSkill) {
super(PacketOpcodes.CanUseSkillNotify);
CanUseSkillNotifyOuterClass.CanUseSkillNotify proto = CanUseSkillNotifyOuterClass.CanUseSkillNotify.newBuilder()
.setIsCanUseSkill(canUseSkill)
.build();
this.setData(proto);
}
}

View File

@ -1,6 +1,7 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.friends.Friendship;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.packet.BasePacket;
@ -18,13 +19,13 @@ public class PacketGetPlayerFriendListRsp extends BasePacket {
FriendBrief serverFriend = FriendBrief.newBuilder()
.setUid(GameConstants.SERVER_CONSOLE_UID)
.setNickname(GameConstants.SERVER_AVATAR_NAME)
.setLevel(1)
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(GameConstants.SERVER_AVATAR_ID))
.setWorldLevel(0)
.setSignature("")
.setNickname(Grasscutter.getConfig().getGameServerOptions().ServerNickname)
.setLevel(Grasscutter.getConfig().getGameServerOptions().ServerLevel)
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(Grasscutter.getConfig().getGameServerOptions().ServerAvatarId))
.setWorldLevel(Grasscutter.getConfig().getGameServerOptions().ServerWorldLevel)
.setSignature(Grasscutter.getConfig().getGameServerOptions().ServerSignature)
.setLastActiveTime((int) (System.currentTimeMillis() / 1000f))
.setNameCardId(210001)
.setNameCardId(Grasscutter.getConfig().getGameServerOptions().ServerNameCardId)
.setOnlineState(FriendOnlineState.FRIEND_ONLINE)
.setParam(1)
.setIsGameSource(true)

View File

@ -1,26 +1,28 @@
package emu.grasscutter.tools;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.data.def.AvatarData;
@ -29,14 +31,77 @@ import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.data.def.SceneData;
import emu.grasscutter.utils.Utils;
import static emu.grasscutter.utils.Language.translate;
public final class Tools {
@SuppressWarnings("deprecation")
public static void createGmHandbook() throws Exception {
ToolsWithLanguageOption.createGmHandbook(getLanguageOption());
}
public static void createGachaMapping(String location) throws Exception {
ToolsWithLanguageOption.createGachaMapping(location, getLanguageOption());
}
public static List<String> getAvailableLanguage() throws Exception {
File textMapFolder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap");
List<String> availableLangList = new ArrayList<String>();
for (String textMapFileName : textMapFolder.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if (name.startsWith("TextMap") && name.endsWith(".json")){
return true;
}
return false;
}
})) {
availableLangList.add(textMapFileName.replace("TextMap","").replace(".json","").toLowerCase());
}
return availableLangList;
}
public static String getLanguageOption() throws Exception {
List<String> availableLangList = getAvailableLanguage();
// Use system out for better format
if (availableLangList.size() == 1) {
return availableLangList.get(0).toUpperCase();
}
String stagedMessage = "";
stagedMessage += "The following languages mappings are available, please select one: [default: EN]\n";
String groupedLangList = ">\t";
int groupedLangCount = 0;
String input = "";
for (String availableLanguage: availableLangList){
groupedLangCount++;
groupedLangList = groupedLangList + "" + availableLanguage + "\t";
if (groupedLangCount == 6) {
stagedMessage += groupedLangList + "\n";
groupedLangCount = 0;
groupedLangList = ">\t";
}
}
if (groupedLangCount > 0) {
stagedMessage += groupedLangList + "\n";
}
stagedMessage += "\nYour choice:[EN] ";
input = Grasscutter.getConsole().readLine(stagedMessage);
if (availableLangList.contains(input.toLowerCase())) {
return input.toUpperCase();
}
Grasscutter.getLogger().info("Invalid option. Will use EN(English) as fallback");
return "EN";
}
}
final class ToolsWithLanguageOption {
@SuppressWarnings("deprecation")
public static void createGmHandbook(String language) throws Exception {
ResourceLoader.loadResources();
Map<Long, String> map;
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMapEN.json")), StandardCharsets.UTF_8)) {
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMap"+language+".json")), StandardCharsets.UTF_8)) {
map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<Long, String>>() {}.getType());
}
@ -48,7 +113,20 @@ public final class Tools {
writer.println("// Grasscutter " + GameConstants.VERSION + " GM Handbook");
writer.println("// Created " + dtf.format(now) + System.lineSeparator() + System.lineSeparator());
CommandMap cmdMap = new CommandMap(true);
List<Command> cmdList = new ArrayList<>(cmdMap.getAnnotationsAsList());
writer.println("// Commands");
for (Command cmd : cmdList) {
String cmdName = cmd.label();
while (cmdName.length() <= 15) {
cmdName = " " + cmdName;
}
writer.println(cmdName + " : " + translate(cmd.description()));
}
writer.println();
list = new ArrayList<>(GameData.getAvatarDataMap().keySet());
Collections.sort(list);
@ -96,11 +174,11 @@ public final class Tools {
}
@SuppressWarnings("deprecation")
public static void createGachaMapping(String location) throws Exception {
public static void createGachaMapping(String location, String language) throws Exception {
ResourceLoader.loadResources();
Map<Long, String> map;
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMapEN.json")), StandardCharsets.UTF_8)) {
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMap"+language+".json")), StandardCharsets.UTF_8)) {
map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<Long, String>>() {}.getType());
}
@ -113,6 +191,9 @@ public final class Tools {
list = new ArrayList<>(GameData.getAvatarDataMap().keySet());
Collections.sort(list);
// if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us"
// since it's the fallback language and there will be no difference in the gacha record page.
// The enduser can still modify the `gacha_mappings.js` directly to enable multilingual for the gacha record system.
writer.println("mappings = {\"en-us\": {");
// Avatars
@ -140,10 +221,10 @@ public final class Tools {
default:
color = "blue";
}
// Got the magic number 4233146695 from manually search in the json file
writer.println(
"\"" + (avatarID % 1000 + 1000) + "\" : [\""
+ map.get(data.getNameTextMapHash()) + "(Avatar)\", \""
+ map.get(data.getNameTextMapHash()) + "(" + map.get(4233146695L)+ ")\", \""
+ color + "\"]");
}
@ -173,13 +254,17 @@ public final class Tools {
default:
continue; // skip unnecessary entries
}
// Got the magic number 4231343903 from manually search in the json file
writer.println(",\"" + data.getId() +
"\" : [\"" + map.get(data.getNameTextMapHash()).replaceAll("\"", "")
+ "(Weapon)\",\""+ color + "\"]");
+ "("+ map.get(4231343903L)+")\",\""+ color + "\"]");
}
writer.println(",\"200\": \"Standard\", \"301\": \"Avatar Event\", \"302\": \"Weapon event\"");
writer.println(",\"200\": \""+map.get(332935371L)+"\", \"301\": \""+ map.get(2272170627L) + "\", \"302\": \""+map.get(2864268523L)+"\"");
writer.println("}\n}");
}
Grasscutter.getLogger().info("Mappings generated!");
Grasscutter.getLogger().info("Mappings generated to " + location + " !");
}
}

View File

@ -0,0 +1,97 @@
package emu.grasscutter.utils;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import emu.grasscutter.Grasscutter;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public final class Language {
private final JsonObject languageData;
private final Map<String, String> cachedTranslations = new HashMap<>();
/**
* Creates a language instance from a code.
* @param langCode The language code.
* @return A language instance.
*/
public static Language getLanguage(String langCode) {
return new Language(langCode + ".json", Grasscutter.getConfig().DefaultLanguage.toLanguageTag());
}
/**
* Returns the translated value from the key while substituting arguments.
* @param key The key of the translated value to return.
* @param args The arguments to substitute.
* @return A translated value with arguments substituted.
*/
public static String translate(String key, Object... args) {
String translated = Grasscutter.getLanguage().get(key);
try {
return translated.formatted(args);
} catch (Exception exception) {
Grasscutter.getLogger().error("Failed to format string: " + key, exception);
return translated;
}
}
/**
* Reads a file and creates a language instance.
* @param fileName The name of the language file.
*/
private Language(String fileName, String fallback) {
@Nullable JsonObject languageData = null;
try {
InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName);
String translationContents = Utils.readFromInputStream(file);
if(translationContents.equals("empty")) {
file = Grasscutter.class.getResourceAsStream("/languages/" + fallback);
translationContents = Utils.readFromInputStream(file);
}
languageData = Grasscutter.getGsonFactory().fromJson(translationContents, JsonObject.class);
} catch (Exception exception) {
Grasscutter.getLogger().warn("Failed to load language file: " + fileName, exception);
}
this.languageData = languageData;
}
/**
* Returns the value (as a string) from a nested key.
* @param key The key to look for.
* @return The value (as a string) from a nested key.
*/
public String get(String key) {
if(this.cachedTranslations.containsKey(key)) {
return this.cachedTranslations.get(key);
}
String[] keys = key.split("\\.");
JsonObject object = this.languageData;
int index = 0;
String result = "This value does not exist. Please report this to the Discord: " + key;
while (true) {
if(index == keys.length) break;
String currentKey = keys[index++];
if(object.has(currentKey)) {
JsonElement element = object.get(currentKey);
if(element.isJsonObject())
object = element.getAsJsonObject();
else {
result = element.getAsString(); break;
}
} else break;
}
this.cachedTranslations.put(key, result); return result;
}
}

View File

@ -1,10 +1,13 @@
package emu.grasscutter.utils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import emu.grasscutter.Config;
@ -15,6 +18,10 @@ import io.netty.buffer.Unpooled;
import org.slf4j.Logger;
import javax.annotation.Nullable;
import static emu.grasscutter.utils.Language.translate;
@SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"})
public final class Utils {
public static final Random random = new Random();
@ -176,15 +183,15 @@ public final class Utils {
// Check for resources folder.
if(!fileExists(resourcesFolder)) {
logger.info(Grasscutter.getLanguage().Create_resources_folder);
logger.info(Grasscutter.getLanguage().Place_copy);
logger.info(translate("messages.status.create_resources"));
logger.info(translate("messages.status.resources_error"));
createFolder(resourcesFolder); exit = true;
}
// Check for BinOutput + ExcelBinOuput.
// Check for BinOutput + ExcelBinOutput.
if(!fileExists(resourcesFolder + "BinOutput") ||
!fileExists(resourcesFolder + "ExcelBinOutput")) {
logger.info(Grasscutter.getLanguage().Place_copy);
logger.info(translate("messages.status.resources_error"));
exit = true;
}
@ -195,7 +202,11 @@ public final class Utils {
if(exit) System.exit(1);
}
public static int GetNextTimestampOfThisHour(int hour, String timeZone, int param) {
/**
* Gets the timestamp of the next hour.
* @return The timestamp in UNIX seconds.
*/
public static int getNextTimestampOfThisHour(int hour, String timeZone, int param) {
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
for (int i = 0; i < param; i ++){
if (zonedDateTime.getHour() < hour) {
@ -204,10 +215,14 @@ public final class Utils {
zonedDateTime = zonedDateTime.plusDays(1).withHour(hour).withMinute(0).withSecond(0);
}
}
return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
}
public static int GetNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) {
/**
* Gets the timestamp of the next hour in a week.
* @return The timestamp in UNIX seconds.
*/
public static int getNextTimestampOfThisHourInNextWeek(int hour, String timeZone, int param) {
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
for (int i = 0; i < param; i++) {
if (zonedDateTime.getDayOfWeek() == DayOfWeek.MONDAY && zonedDateTime.getHour() < hour) {
@ -216,10 +231,14 @@ public final class Utils {
zonedDateTime = zonedDateTime.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).withHour(hour).withMinute(0).withSecond(0);
}
}
return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
}
public static int GetNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) {
/**
* Gets the timestamp of the next hour in a month.
* @return The timestamp in UNIX seconds.
*/
public static int getNextTimestampOfThisHourInNextMonth(int hour, String timeZone, int param) {
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of(timeZone));
for (int i = 0; i < param; i++) {
if (zonedDateTime.getDayOfMonth() == 1 && zonedDateTime.getHour() < hour) {
@ -228,6 +247,63 @@ public final class Utils {
zonedDateTime = zonedDateTime.with(TemporalAdjusters.firstDayOfNextMonth()).withHour(hour).withMinute(0).withSecond(0);
}
}
return (int)zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
return (int) zonedDateTime.toInstant().atZone(ZoneOffset.UTC).toEpochSecond();
}
/**
* Retrieves a string from an input stream.
* @param stream The input stream.
* @return The string.
*/
public static String readFromInputStream(@Nullable InputStream stream) {
if(stream == null) return "empty";
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
String line; while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
} stream.close();
} catch (IOException e) {
Grasscutter.getLogger().warn("Failed to read from input stream.");
} catch (NullPointerException ignored) {
return "empty";
} return stringBuilder.toString();
}
/**
* Switch properties from upper case to lower case?
*/
public static Map<String, Object> switchPropertiesUpperLowerCase(Map<String, Object> objMap, Class<?> cls) {
Map<String, Object> map = new HashMap<>(objMap.size());
for (String key : objMap.keySet()) {
try {
char c = key.charAt(0);
if (c >= 'a' && c <= 'z') {
try {
cls.getDeclaredField(key);
map.put(key, objMap.get(key));
} catch (NoSuchFieldException e) {
String s1 = String.valueOf(c).toUpperCase();
String after = key.length() > 1 ? s1 + key.substring(1) : s1;
cls.getDeclaredField(after);
map.put(after, objMap.get(key));
}
} else if (c >= 'A' && c <= 'Z') {
try {
cls.getDeclaredField(key);
map.put(key, objMap.get(key));
} catch (NoSuchFieldException e) {
String s1 = String.valueOf(c).toLowerCase();
String after = key.length() > 1 ? s1 + key.substring(1) : s1;
cls.getDeclaredField(after);
map.put(after, objMap.get(key));
}
}
} catch (NoSuchFieldException e) {
map.put(key, objMap.get(key));
}
}
return map;
}
}

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