diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..8457bf7ac --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: "Build" +on: + push: + branches: + - "stable" +jobs: + Build-Server-Jar: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: '8' + - name: Run Gradle + run: .\gradlew.bat && .\gradlew jar + - name: Upload build + uses: actions/upload-artifact@v3 + with: + name: Grasscutter + path: grasscutter.jar + diff --git a/.gitignore b/.gitignore index 1cd17e099..9df6d0071 100644 --- a/.gitignore +++ b/.gitignore @@ -47,12 +47,13 @@ tmp/ # Grasscutter resources/* +logs/* data/AbilityEmbryos.json data/OpenConfig.json proto/auto/ proto/protoc.exe GM Handbook.txt -config.json -mitmdump.exe -grasscutter.jar +config.json +mitmdump.exe +grasscutter.jar mongod.exe diff --git a/README.md b/README.md index 19f2a1e3e..853c37ae3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ A WIP server reimplementation for *some anime game* 2.3-2.6 * Friends list * Co-op *partially* work # Quick setup guide +### Note +* If you update from an older version, delete `config.json` for regeneration + ### Prerequisites * JDK-8u202 ([mirror link](https://mirrors.huaweicloud.com/java/jdk/8u202-b08/) since Oracle required an account to download old builds) * Mongodb (recommended 4.0+) @@ -26,7 +29,7 @@ A WIP server reimplementation for *some anime game* 2.3-2.6 ### Connecting with the client ½. Create an account using *server console command* below 1. Run a proxy daemon: (choose either one) - - mitmdump: `mitmdump -s proxy.py --ssl-insecure` + - mitmdump: `mitmdump -s proxy.py -k` - Fiddler Classic: Run Fiddler Classic, turn on `Decrypt https traffic` in setting and change the default port there (Tools -> Options -> Connections) to anything other than `8888`, and load [this script](https://github.lunatic.moe/fiddlerscript). - [Hosts file](https://github.com/Melledy/Grasscutter/wiki/Running#traffic-route-map) 2. Trust CA certificate: @@ -37,32 +40,33 @@ A WIP server reimplementation for *some anime game* 2.3-2.6 * or you can use `run.cmd` to start Server & Proxy daemon with one click # Grasscutter commands -### Server console commands +There is a dummy user named "Server" in every player's friends list that you can message to use commands. Commands also work in other chat rooms, such as private/team chats. `account create [username] {playerid}` - Creates an account with the specified username and the in-game uid for that account. The playerid parameter is optional and will be auto generated if not set. -### In-Game commands -There is a dummy user named "Server" in every player's friends list that you can message to use commands. Commands also work in other chat rooms, such as private/team chats. +`spawn [monster id] [level] [amount]` -`!spawn [monster id] [level] [amount]` +`give [item id] [amount]` -`!give [item id] [amount]` +`givechar [avatar id] [level]` -`!givechar [avatar id] [level]` +`drop [item id] [amount]` -`!drop [item id] [amount]` +`killall` -`!killall` +`setworldlevel [level]` - Relog to see effects properly -`!setworldlevel [level]` - Relog to see effects properly +`godmode` - Prevents you from taking damage -`!godmode` - Prevents you from taking damage +`resetconst` - Resets the constellation level on your current active character, will need to relog after using the command to see any changes. -`!resetconst` - Resets the constellation level on your current active character, will need to relog after using the command to see any changes. +`setstats [stats] [amount]` - Changes the current character's specified stat. -`!sethp [hp]` +`clearartifacts` - Deletes all unequipped and unlocked level 0 artifacts, **including yellow rarity ones** from your inventory -`!clearartifacts` - Deletes all unequipped and unlocked level 0 artifacts, **including yellow rarity ones** from your inventory +`pos` - Gets your current coordinate. + +`weather [weather id] [climate id]` - Changes the current weather. *More commands will be updated in the [wiki](https://github.com/Melledy/Grasscutter/wiki/).* diff --git a/data/Banners.json b/data/Banners.json index 959a8b436..a4f724ac9 100644 --- a/data/Banners.json +++ b/data/Banners.json @@ -19,7 +19,7 @@ "bannerType": "EVENT", "prefabPath": "GachaShowPanel_A079", "previewPrefabPath": "UI_Tab_GachaShowPanel_A079", - "titlePath": "UI_GACHA_SHOW_PANEL_A079_TITLE", + "titlePath": "UI_GACHA_SHOW_PANEL_A048_TITLE", "costItem": 223, "beginTime": 0, "endTime": 1924992000, @@ -34,7 +34,7 @@ "bannerType": "WEAPON", "prefabPath": "GachaShowPanel_A080", "previewPrefabPath": "UI_Tab_GachaShowPanel_A080", - "titlePath": "UI_GACHA_SHOW_PANEL_A080_TITLE", + "titlePath": "UI_GACHA_SHOW_PANEL_A021_TITLE", "costItem": 223, "beginTime": 0, "endTime": 1924992000, diff --git a/proxy.py b/proxy.py index 2d2282af9..11625cc20 100644 --- a/proxy.py +++ b/proxy.py @@ -16,12 +16,14 @@ # - mitmdump from mitmproxy # # @author MlgmXyysd -# @version 1.0 +# @version 1.1 # ## from mitmproxy import http +from proxy_config import USE_SSL from proxy_config import REMOTE_HOST +from proxy_config import REMOTE_PORT class MlgmXyysd_Genshin_Impact_Proxy: @@ -55,12 +57,19 @@ class MlgmXyysd_Genshin_Impact_Proxy: "minor-api.mihoyo.com", "public-data-api.mihoyo.com", "uspider.yuanshen.com", - "sdk-static.mihoyo.com" + "sdk-static.mihoyo.com", + "abtest-api-data-sg.hoyoverse.com", + "log-upload-os.hoyoverse.com" ] def request(self, flow: http.HTTPFlow) -> None: if flow.request.host in self.LIST_DOMAINS: + if USE_SSL: + flow.request.scheme = "https" + else: + flow.request.scheme = "http" flow.request.host = REMOTE_HOST + flow.request.port = REMOTE_PORT addons = [ MlgmXyysd_Genshin_Impact_Proxy() diff --git a/proxy_config.py b/proxy_config.py index f048ef88c..5025a974e 100644 --- a/proxy_config.py +++ b/proxy_config.py @@ -1,2 +1,4 @@ # This can also be replaced with another IP address. -REMOTE_HOST = "localhost" \ No newline at end of file +USE_SSL = True +REMOTE_HOST = "127.0.0.1" +REMOTE_PORT = 443 \ No newline at end of file diff --git a/run.cmd b/run.cmd deleted file mode 100644 index 36a20d811..000000000 --- a/run.cmd +++ /dev/null @@ -1,115 +0,0 @@ -@rem -@rem Copyright (C) 2002-2022 MlgmXyysd All Rights Reserved. -@rem - -@if "%DEBUG%" == "" echo off -pushd %~dp0 -title Grasscutter -call :LOG [INFO] Grasscutter -call :LOG [INFO] Initializing... - -@rem This will not work if your java or mitmproxy is in a different location, plugin as necessary -@rem this just saves you from changing your PATH -set JAVA_PATH=C:\Program Files\Java\jdk1.8.0_202\ -set MITMPROXY_PATH=%~dp0 -set PROXY_SCRIPT=proxy -@rem TODO: MongoDB integration -set SERVER_PATH=%~dp0 - -@rem mitmproxy not found, server only -if not exist "%MITMPROXY_PATH%mitmdump.exe" ( - call :LOG [WARN] mitmproxy not found, server only mode. - goto :SERVER -) -@rem proxy script not found, server only -if not exist "%PROXY_SCRIPT%.py" ( - if not exist "%PROXY_SCRIPT%.pyc" ( - call :LOG [WARN] Missing proxy script or compiled proxy script, server only mode. - goto :SERVER - ) else set PROXY_SCRIPT=%PROXY_SCRIPT%.pyc -) else set PROXY_SCRIPT=%PROXY_SCRIPT%.py - -:PROXY -@rem UAC Administrator privileges ->nul 2>&1 reg query "HKU\S-1-5-19" || ( - call :LOG [WARN] Currently running with non Administrator privileges, raising... - echo set UAC = CreateObject^("Shell.Application"^) > "%temp%\UAC.vbs" - echo UAC.ShellExecute "%~f0", "%1", "", "runas", 1 >> "%temp%\UAC.vbs" - "%temp%\UAC.vbs" - del /f /q "%temp%\UAC.vbs" >nul 2>nul - exit /b -) - -set PROXY=true -@rem Store original proxy settings -for /f "tokens=2*" %%a in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable 2^>nul') do set "ORIG_PROXY_ENABLE=%%b" -for /f "tokens=2*" %%a in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer 2^>nul') do set "ORIG_PROXY_SERVER=%%b" - -call :LOG [INFO] Starting proxy daemon... -@rem TODO: External proxy when ORIG_PROXY_ENABLE == 0x1 -start /min "" "%MITMPROXY_PATH%mitmdump.exe" -s %PROXY_SCRIPT% --ssl-insecure - -@rem CA certificate for possible HTTPS scheme -call :LOG [INFO] Waiting for CA certificate generation... -set CA_CERT_FILE="%USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer" - -set /A TIMEOUT_COUNT=0 - -:CERT_CA_CHECK -if not exist %CA_CERT_FILE% ( - timeout /T 1 >nul 2>nul - set /A TIMEOUT_COUNT+=1 - goto CERT_CA_CHECK -) -:EXTRA_TIMEOUT -if %TIMEOUT_COUNT% LEQ 2 ( - timeout /T 1 >nul 2>nul - set /A TIMEOUT_COUNT+=1 - goto EXTRA_TIMEOUT -) -call :LOG [INFO] Adding CA certificate to store... -certutil -addstore root %CA_CERT_FILE% >nul 2>nul - -call :LOG [INFO] Setting up network proxy... -reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f >nul 2>nul -reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer /d "127.0.0.1:8080" /f >nul 2>nul - -:SERVER -if not exist "%JAVA_PATH%bin\java.exe" ( - call :LOG [ERROR] Java not found. - goto :EXIT -) -if not exist "%SERVER_PATH%grasscutter.jar" ( - call :LOG [ERROR] Server jar not found. - goto :EXIT -) -call :LOG [INFO] Starting server... -"%JAVA_PATH%bin\java.exe" -jar "%SERVER_PATH%grasscutter.jar" -call :LOG [INFO] Server stopped - -:EXIT -if "%PROXY%" == "" ( - call :LOG [INFO] Proxy not started, no need to clean up. -) else ( - call :LOG [INFO] Restoring network settings... - - reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable /t REG_DWORD /d "%ORIG_PROXY_ENABLE%" /f >nul 2>nul - reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer /d "%ORIG_PROXY_SERVER%" /f >nul 2>nul - - call :LOG [INFO] Shutting down proxy daemon... - taskkill /t /f /im mitmdump.exe >nul 2>nul - - call :LOG [INFO] Removing CA certificate... - - for /F "tokens=2" %%s in ('certutil -dump %CA_CERT_FILE% ^| findstr ^"^sha1^"') do ( - set SERIAL=%%s - ) - - certutil -delstore root %SERIAL% >nul 2>nul -) - -call :LOG [INFO] See you again :) -goto :EOF - -:LOG -echo [%time:~0,8%] %* \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index dddbbfa9e..4a6f71680 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -1,48 +1,66 @@ package emu.grasscutter; +import java.util.ArrayList; + public final class Config { - public String DispatchServerIp = "127.0.0.1"; - public String DispatchServerPublicIp = ""; - public int DispatchServerPort = 443; - public String DispatchServerKeystorePath = "./keystore.p12"; - public String DispatchServerKeystorePassword = ""; - public Boolean UseSSL = true; - - public String GameServerName = "Test"; - public String GameServerIp = "127.0.0.1"; - public String GameServerPublicIp = ""; - public int GameServerPort = 22102; - - public int UploadLogPort = 80; - + public String DatabaseUrl = "mongodb://localhost:27017"; public String DatabaseCollection = "grasscutter"; - + public String RESOURCE_FOLDER = "./resources/"; public String DATA_FOLDER = "./data/"; public String PACKETS_FOLDER = "./packets/"; public String DUMPS_FOLDER = "./dumps/"; public String KEY_FOLDER = "./keys/"; - public boolean LOG_PACKETS = false; - public GameRates Game = new GameRates(); - public ServerOptions ServerOptions = new ServerOptions(); - - public GameRates getGameRates() { - return Game; + public String RunMode = "HYBRID"; // HYBRID, DISPATCH_ONLY, GAME_ONLY + public GameServerOptions GameServer = new GameServerOptions(); + public DispatchServerOptions DispatchServer = new DispatchServerOptions(); + + public GameServerOptions getGameServerOptions() { + return GameServer; + } + + public DispatchServerOptions getDispatchOptions() { return DispatchServer; } + + public static class DispatchServerOptions { + public String Ip = "0.0.0.0"; + public String PublicIp = "127.0.0.1"; + public int Port = 443; + public int PublicPort = 0; + public String KeystorePath = "./keystore.p12"; + public String KeystorePassword = ""; + public Boolean UseSSL = true; + public Boolean FrontHTTPS = true; + + public boolean AutomaticallyCreateAccounts = false; + + public RegionInfo[] GameServers = {}; + + public RegionInfo[] getGameServers() { + return GameServers; + } + + public static class RegionInfo { + public String Name = "os_usa"; + public String Title = "Test"; + public String Ip = "127.0.0.1"; + public int Port = 22102; + } } - public ServerOptions getServerOptions() { - return ServerOptions; - } - - public static class GameRates { - public float ADVENTURE_EXP_RATE = 1.0f; - public float MORA_RATE = 1.0f; - public float DOMAIN_DROP_RATE = 1.0f; - } - - public static class ServerOptions { + public static class GameServerOptions { + public String Name = "Test"; + public String Ip = "0.0.0.0"; + public String PublicIp = "127.0.0.1"; + public int Port = 22102; + public int PublicPort = 0; + + public String DispatchServerDatabaseUrl = "mongodb://localhost:27017"; + public String DispatchServerDatabaseCollection = "grasscutter"; + + public boolean LOG_PACKETS = false; + public int InventoryLimitWeapon = 2000; public int InventoryLimitRelic = 2000; public int InventoryLimitMaterial = 2000; @@ -54,6 +72,15 @@ public final class Config { public boolean WatchGacha = false; public int[] WelcomeEmotes = {2007, 1002, 4010}; public String WelcomeMotd = "Welcome to Grasscutter emu"; - public boolean AutomaticallyCreateAccounts = false; + + public GameRates Game = new GameRates(); + + public GameRates getGameRates() { return Game; } + + public static class GameRates { + public float ADVENTURE_EXP_RATE = 1.0f; + public float MORA_RATE = 1.0f; + public float DOMAIN_DROP_RATE = 1.0f; + } } } diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index a66c71254..ad1107e8e 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -73,11 +73,26 @@ public final class Grasscutter { DatabaseManager.initialize(); // Start servers. - dispatchServer = new DispatchServer(); - dispatchServer.start(); - - gameServer = new GameServer(new InetSocketAddress(getConfig().GameServerIp, getConfig().GameServerPort)); - gameServer.start(); + if(getConfig().RunMode.equalsIgnoreCase("HYBRID")) { + dispatchServer = new DispatchServer(); + dispatchServer.start(); + + gameServer = new GameServer(new InetSocketAddress(getConfig().getGameServerOptions().Ip, getConfig().getGameServerOptions().Port)); + gameServer.start(); + } else if(getConfig().RunMode.equalsIgnoreCase("DISPATCH_ONLY")) { + dispatchServer = new DispatchServer(); + dispatchServer.start(); + } else if(getConfig().RunMode.equalsIgnoreCase("GAME_ONLY")) { + gameServer = new GameServer(new InetSocketAddress(getConfig().getGameServerOptions().Ip, getConfig().getGameServerOptions().Port)); + gameServer.start(); + } else { + getLogger().error("Invalid server run mode. " + getConfig().RunMode); + getLogger().error("Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter..."); + getLogger().error("Shutting down..."); + System.exit(1); + } + + // Open console. startConsole(); @@ -86,8 +101,10 @@ public final class Grasscutter { public static void loadConfig() { try (FileReader file = new FileReader(configFile)) { config = gson.fromJson(file, Config.class); + saveConfig(); } catch (Exception e) { - Grasscutter.config = new Config(); saveConfig(); + Grasscutter.config = new Config(); + saveConfig(); } } @@ -104,9 +121,14 @@ public final class Grasscutter { try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) { while ((input = br.readLine()) != null) { try { + if(getConfig().RunMode.equalsIgnoreCase("DISPATCH_ONLY")) { + getLogger().error("Commands are not supported in dispatch only mode"); + return; + } CommandMap.getInstance().invoke(null, input); } catch (Exception e) { - Grasscutter.getLogger().error("Command error: " + e.getMessage()); + Grasscutter.getLogger().error("Command error: "); + e.printStackTrace(); } } } catch (Exception e) { diff --git a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java index 2c21cba79..26570e0e8 100644 --- a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java @@ -46,7 +46,7 @@ public final class AccountCommand implements CommandHandler { CommandHandler.sendMessage(null, "Account already exists."); return; } else { - CommandHandler.sendMessage(null, "Account created with UID " + account.getPlayerId() + "."); + CommandHandler.sendMessage(null, "Account created with UID " + account.getPlayerUid() + "."); account.addPermission("*"); // Grant the player superuser permissions. account.save(); // Save account to database. } diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java index a5afe7bf7..eb2e998f2 100644 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java @@ -23,11 +23,17 @@ public final class ChangeSceneCommand implements CommandHandler { try { int sceneId = Integer.parseInt(args.get(0)); + + if (sceneId == sender.getSceneId()) { + CommandHandler.sendMessage(sender, "You are already in that scene"); + return; + } + boolean result = sender.getWorld().transferPlayerToScene(sender, sceneId, sender.getPos()); - CommandHandler.sendMessage(sender, "Changed to scene " + sceneId); + if (!result) { - CommandHandler.sendMessage(sender, "Scene does not exist or you are already in it"); + CommandHandler.sendMessage(sender, "Scene does not exist"); } } catch (Exception e) { CommandHandler.sendMessage(sender, "Usage: changescene "); diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index 7d15a4759..a533130bc 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -95,18 +95,19 @@ public final class GiveCommand implements CommandHandler { } private void item(GenshinPlayer player, ItemData itemData, int amount) { - GenshinItem genshinItem = new GenshinItem(itemData); if (itemData.isEquip()) { List items = new LinkedList<>(); for (int i = 0; i < amount; i++) { - items.add(genshinItem); + items.add(new GenshinItem(itemData)); } player.getInventory().addItems(items); player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop)); } else { + GenshinItem genshinItem = new GenshinItem(itemData); genshinItem.setCount(amount); player.getInventory().addItem(genshinItem); player.sendPacket(new PacketItemAddHintNotify(genshinItem, ActionReason.SubfieldDrop)); } } } + diff --git a/src/main/java/emu/grasscutter/command/commands/HealCommand.java b/src/main/java/emu/grasscutter/command/commands/HealCommand.java new file mode 100644 index 000000000..511e52812 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/HealCommand.java @@ -0,0 +1,36 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; +import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; +import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; + +import java.util.List; + +@Command(label = "heal", usage = "heal|h", + description = "Heal all characters in your current team.", aliases = {"h"}, permission = "player.heal") +public class HealCommand implements CommandHandler { + @Override + public void execute(GenshinPlayer sender, List args) { + if (sender == null) { + CommandHandler.sendMessage(null, "Run this command in-game."); + return; + } + sender.getTeamManager().getActiveTeam().forEach(entity -> { + boolean isAlive = entity.isAlive(); + entity.setFightProperty( + FightProperty.FIGHT_PROP_CUR_HP, + entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) + ); + entity.getWorld().broadcastPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); + if (!isAlive) { + entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); + } + }); + CommandHandler.sendMessage(sender, "All characters are healed."); + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/KickCommand.java b/src/main/java/emu/grasscutter/command/commands/KickCommand.java index 188ce1877..61e9d759b 100644 --- a/src/main/java/emu/grasscutter/command/commands/KickCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KickCommand.java @@ -22,7 +22,7 @@ public final class KickCommand implements CommandHandler { } if (sender != null) { - CommandHandler.sendMessage(sender, String.format("Player [%s:%s] has kicked player [%s:%s]", sender.getAccount().getPlayerId(), sender.getAccount().getUsername(), target, targetPlayer.getAccount().getUsername())); + CommandHandler.sendMessage(sender, String.format("Player [%s:%s] has kicked player [%s:%s]", sender.getAccount().getPlayerUid(), sender.getAccount().getUsername(), target, targetPlayer.getAccount().getUsername())); } CommandHandler.sendMessage(sender, String.format("Kicking player [%s:%s]", target, targetPlayer.getAccount().getUsername())); diff --git a/src/main/java/emu/grasscutter/command/commands/ListCommand.java b/src/main/java/emu/grasscutter/command/commands/ListCommand.java new file mode 100644 index 000000000..6afca4a6d --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/ListCommand.java @@ -0,0 +1,33 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.GenshinPlayer; + +import java.util.List; +import java.util.Map; + +@Command(label = "list", description = "List online players") +public class ListCommand implements CommandHandler { + + @Override + public void execute(GenshinPlayer sender, List args) { + Map playersMap = Grasscutter.getGameServer().getPlayers(); + + CommandHandler.sendMessage(sender, String.format("There are %s player(s) online:", playersMap.size())); + + if (playersMap.size() != 0) { + StringBuilder playerSet = new StringBuilder(); + + for (Map.Entry entry : playersMap.entrySet()) { + playerSet.append(entry.getValue().getNickname()); + playerSet.append(", "); + } + + String players = playerSet.toString(); + + CommandHandler.sendMessage(sender, players.substring(0, players.length() - 2)); + } + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index afc1a3e14..e3efdb0d5 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -10,7 +10,7 @@ import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import java.util.List; @Command(label = "setstats", usage = "setstats|stats ", - aliases = {"stats"}) + description = "Set fight property for your current active character", aliases = {"stats"}, permission = "player.setstats") public final class SetStatsCommand implements CommandHandler { @Override @@ -20,6 +20,11 @@ public final class SetStatsCommand implements CommandHandler { return; } + if (args.size() < 2){ + CommandHandler.sendMessage(sender, "Usage: setstats|stats "); + return; + } + String stat = args.get(0); switch (stat) { default: diff --git a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java new file mode 100644 index 000000000..2ced6f1c3 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java @@ -0,0 +1,107 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.avatar.GenshinAvatar; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.server.packet.send.PacketAvatarSkillChangeNotify; +import emu.grasscutter.server.packet.send.PacketAvatarSkillUpgradeRsp; + +import java.util.List; + +@Command(label = "talent", usage = "talent ", + description = "Set talent level for your current active character", permission = "player.settalent") +public class TalentCommand implements CommandHandler { + + @Override + public void execute(GenshinPlayer sender, List args) { + if (sender == null) { + CommandHandler.sendMessage(null, "Run this command in-game."); + return; + } + + if (args.size() < 0 || args.size() < 1){ + CommandHandler.sendMessage(sender, "To set talent level: /talent set "); + CommandHandler.sendMessage(sender, "To get talent ID: /talent getid"); + return; + } + + String cmdSwitch = args.get(0); + switch (cmdSwitch) { + default: + CommandHandler.sendMessage(sender, "To set talent level: /talent set "); + CommandHandler.sendMessage(sender, "To get talent ID: /talent getid"); + return; + case "set": + try { + int skillId = Integer.parseInt(args.get(1)); + int nextLevel = Integer.parseInt(args.get(2)); + EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity(); + GenshinAvatar avatar = entity.getAvatar(); + int skillIdNorAtk = avatar.getData().getSkillDepot().getSkills().get(0); + int skillIdE = avatar.getData().getSkillDepot().getSkills().get(1); + int skillIdQ = avatar.getData().getSkillDepot().getEnergySkill(); + int currentLevelNorAtk = avatar.getSkillLevelMap().get(skillIdNorAtk); + int currentLevelE = avatar.getSkillLevelMap().get(skillIdE); + int currentLevelQ = avatar.getSkillLevelMap().get(skillIdQ); + if (args.size() < 2){ + CommandHandler.sendMessage(sender, "To set talent level: /talent set "); + CommandHandler.sendMessage(sender, "To get talent ID: /talent getid"); + return; + } + if (nextLevel > 16){ + CommandHandler.sendMessage(sender, "Invalid talent level. Level should be lower than 16"); + return; + } + if (skillId == skillIdNorAtk){ + // Upgrade skill + avatar.getSkillLevelMap().put(skillIdNorAtk, nextLevel); + avatar.save(); + + // Packet + sender.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillIdNorAtk, currentLevelNorAtk, nextLevel)); + sender.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillIdNorAtk, currentLevelNorAtk, nextLevel)); + CommandHandler.sendMessage(sender, "Set talent Normal ATK to " + nextLevel + "."); + } + if (skillId == skillIdE){ + // Upgrade skill + avatar.getSkillLevelMap().put(skillIdE, nextLevel); + avatar.save(); + + // Packet + sender.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillIdE, currentLevelE, nextLevel)); + sender.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillIdE, currentLevelE, nextLevel)); + CommandHandler.sendMessage(sender, "Set talent E to " + nextLevel + "."); + } + if (skillId == skillIdQ){ + // Upgrade skill + avatar.getSkillLevelMap().put(skillIdQ, nextLevel); + avatar.save(); + + // Packet + sender.sendPacket(new PacketAvatarSkillChangeNotify(avatar, skillIdQ, currentLevelQ, nextLevel)); + sender.sendPacket(new PacketAvatarSkillUpgradeRsp(avatar, skillIdQ, currentLevelQ, nextLevel)); + CommandHandler.sendMessage(sender, "Set talent Q to " + nextLevel + "."); + } + + } catch (NumberFormatException ignored) { + CommandHandler.sendMessage(sender, "Invalid skill ID."); + return; + } + + break; + case "getid": + EntityAvatar entity = sender.getTeamManager().getCurrentAvatarEntity(); + GenshinAvatar avatar = entity.getAvatar(); + 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, "Normal Attack ID " + skillIdNorAtk + "."); + CommandHandler.sendMessage(sender, "E skill ID " + skillIdE + "."); + CommandHandler.sendMessage(sender, "Q skill ID " + skillIdQ + "."); + break; + } + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java index 919432ed1..bb9ae4a42 100644 --- a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java @@ -8,7 +8,7 @@ import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify; import java.util.List; -@Command(label = "weather", usage = "weather ", +@Command(label = "weather", usage = "weather [climateId]", description = "Changes the weather.", aliases = {"w"}, permission = "player.weather") public final class WeatherCommand implements CommandHandler { @@ -20,20 +20,22 @@ public final class WeatherCommand implements CommandHandler { } if (args.size() < 1) { - CommandHandler.sendMessage(sender, "Usage: weather "); + CommandHandler.sendMessage(sender, "Usage: weather [climateId]"); return; } try { int weatherId = Integer.parseInt(args.get(0)); + int climateId = args.size() > 1 ? Integer.parseInt(args.get(1)) : 1; - ClimateType climate = ClimateType.getTypeByValue(weatherId); + ClimateType climate = ClimateType.getTypeByValue(climateId); + sender.getScene().setWeather(weatherId); sender.getScene().setClimate(climate); sender.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(sender)); - CommandHandler.sendMessage(sender, "Changed weather to " + weatherId); + CommandHandler.sendMessage(sender, "Changed weather to " + weatherId + " with climate " + climateId); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, "Invalid weather ID."); + CommandHandler.sendMessage(sender, "Invalid ID."); } } } diff --git a/src/main/java/emu/grasscutter/data/GenshinData.java b/src/main/java/emu/grasscutter/data/GenshinData.java index 7dc5a5f7c..7e14b1b46 100644 --- a/src/main/java/emu/grasscutter/data/GenshinData.java +++ b/src/main/java/emu/grasscutter/data/GenshinData.java @@ -1,7 +1,9 @@ package emu.grasscutter.data; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import emu.grasscutter.Grasscutter; @@ -54,6 +56,10 @@ public class GenshinData { private static final Int2ObjectMap avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); + private static final Int2ObjectMap fetterDataMap = new Int2ObjectOpenHashMap<>(); + + // Cache + private static Map> fetters = new HashMap<>(); public static Int2ObjectMap getMapByResourceDef(Class resourceDefinition) { Int2ObjectMap map = null; @@ -221,4 +227,17 @@ public class GenshinData { public static Int2ObjectMap getSceneDataMap() { return sceneDataMap; } + + public static Map> getFetterDataEntries() { + if (fetters.isEmpty()) { + fetterDataMap.forEach((k, v) -> { + if (!fetters.containsKey(v.getAvatarId())) { + fetters.put(v.getAvatarId(), new ArrayList<>()); + } + fetters.get(v.getAvatarId()).add(k); + }); + } + + return fetters; + } } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index e597257ce..bb95d70e9 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -128,7 +128,13 @@ public class ResourceLoader { private static void loadScenePoints() { Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)"); - File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutPut/Scene/Point"); + File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Scene/Point"); + + if (!folder.isDirectory() || !folder.exists() || folder.listFiles() == null) { + Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!"); + return; + } + List scenePointList = new ArrayList<>(); for (File file : folder.listFiles()) { ScenePointConfig config = null; diff --git a/src/main/java/emu/grasscutter/data/def/AvatarData.java b/src/main/java/emu/grasscutter/data/def/AvatarData.java index dcc503994..8097e04a7 100644 --- a/src/main/java/emu/grasscutter/data/def/AvatarData.java +++ b/src/main/java/emu/grasscutter/data/def/AvatarData.java @@ -55,6 +55,8 @@ public class AvatarData extends GenshinResource { private float[] defenseGrowthCurve; private AvatarSkillDepotData skillDepot; private IntList abilities; + + private List fetters; @Override public int getId(){ @@ -193,9 +195,16 @@ public class AvatarData extends GenshinResource { return abilities; } + public List getFetters() { + return fetters; + } + @Override public void onLoad() { this.skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.SkillDepotId); + + // Get fetters from GenshinData + this.fetters = GenshinData.getFetterDataEntries().get(this.Id); int size = GenshinData.getAvatarCurveDataMap().size(); this.hpGrowthCurve = new float[size]; diff --git a/src/main/java/emu/grasscutter/data/def/FetterData.java b/src/main/java/emu/grasscutter/data/def/FetterData.java new file mode 100644 index 000000000..d17c940d1 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/FetterData.java @@ -0,0 +1,24 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GenshinResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.data.ResourceType.LoadPriority; + +@ResourceType(name = {"FetterInfoExcelConfigData.json", "FettersExcelConfigData.json", "FetterStoryExcelConfigData.json"}, loadPriority = LoadPriority.HIGHEST) +public class FetterData extends GenshinResource { + private int AvatarId; + private int FetterId; + + @Override + public int getId() { + return FetterId; + } + + public int getAvatarId() { + return AvatarId; + } + + @Override + public void onLoad() { + } +} diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index b70365225..fc24b70e6 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -66,7 +66,7 @@ public final class DatabaseHelper { } public static void saveAccount(Account account) { - DatabaseManager.getDatastore().save(account); + DatabaseManager.getAccountDatastore().save(account); } public static Account getAccountByName(String username) { diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 004f6ba80..9d64f9258 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -1,5 +1,6 @@ package emu.grasscutter.database; +import com.mongodb.MongoClientURI; import com.mongodb.MongoCommandException; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -18,7 +19,12 @@ import emu.grasscutter.game.friends.Friendship; import emu.grasscutter.game.inventory.GenshinItem; public final class DatabaseManager { + + private static MongoClient mongoClient; + private static MongoClient dispatchMongoClient; + private static Datastore datastore; + private static Datastore dispatchDatastore; private static final Class[] mappedClasses = new Class[] { DatabaseCounter.class, Account.class, GenshinPlayer.class, GenshinAvatar.class, GenshinItem.class, Friendship.class @@ -31,6 +37,16 @@ public final class DatabaseManager { public static MongoDatabase getDatabase() { return getDatastore().getDatabase(); } + + // Yes. I very dislike this method. However, this will be good for now. + // TODO: Add dispatch routes for player account management + public static Datastore getAccountDatastore() { + if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("GAME_ONLY")) { + return dispatchDatastore; + } else { + return datastore; + } + } public static void initialize() { // Initialize @@ -60,6 +76,28 @@ public final class DatabaseManager { datastore.ensureIndexes(); } } + + if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("GAME_ONLY")) { + dispatchMongoClient = MongoClients.create(Grasscutter.getConfig().getGameServerOptions().DispatchServerDatabaseUrl); + dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, Grasscutter.getConfig().getGameServerOptions().DispatchServerDatabaseCollection); + + // Ensure indexes for dispatch server + try { + dispatchDatastore.ensureIndexes(); + } catch (MongoCommandException e) { + Grasscutter.getLogger().info("Mongo index error: ", e); + // Duplicate index error + if (e.getCode() == 85) { + // Drop all indexes and re add them + MongoIterable collections = dispatchDatastore.getDatabase().listCollectionNames(); + for (String name : collections) { + dispatchDatastore.getDatabase().getCollection(name).dropIndexes(); + } + // Add back indexes + dispatchDatastore.ensureIndexes(); + } + } + } } public static synchronized int getNextId(Class c) { diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 073bf10fd..dbd6ef854 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -8,6 +8,10 @@ import emu.grasscutter.utils.Utils; import java.util.ArrayList; import java.util.List; +import org.bson.Document; + +import com.mongodb.DBObject; + @Entity(value = "accounts", useDiscriminator = false) public class Account { @Id private String id; @@ -17,7 +21,7 @@ public class Account { private String username; private String password; // Unused for now - private int playerId; + @AlsoLoad("playerUid") private int playerId; private String email; private String token; @@ -61,7 +65,7 @@ public class Account { this.token = token; } - public int getPlayerId() { + public int getPlayerUid() { return this.playerId; } @@ -117,13 +121,12 @@ public class Account { public void save() { DatabaseHelper.saveAccount(this); } - -// TODO: Find an implementation for this. Morphia 2.0 breaks this method for some reason? -// @PreLoad -// public void onLoad(DBObject object) { -// // Grant the superuser permissions to accounts created before the permissions update -// if (!object.containsField("permissions")) { -// this.addPermission("*"); -// } -// } + + @PreLoad + public void onLoad(Document document) { + // Grant the superuser permissions to accounts created before the permissions update + if (!document.containsKey("permissions")) { + this.addPermission("*"); + } + } } diff --git a/src/main/java/emu/grasscutter/game/GenshinPlayer.java b/src/main/java/emu/grasscutter/game/GenshinPlayer.java index 535ef06d9..29e5bd4c1 100644 --- a/src/main/java/emu/grasscutter/game/GenshinPlayer.java +++ b/src/main/java/emu/grasscutter/game/GenshinPlayer.java @@ -31,6 +31,7 @@ import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo; import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason; import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; +import emu.grasscutter.net.proto.WorldPlayerLocationInfoOuterClass.WorldPlayerLocationInfo; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketAbilityInvocationsNotify; @@ -49,9 +50,11 @@ import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify; import emu.grasscutter.server.packet.send.PacketPlayerPropNotify; import emu.grasscutter.server.packet.send.PacketPlayerStoreNotify; import emu.grasscutter.server.packet.send.PacketPrivateChatNotify; +import emu.grasscutter.server.packet.send.PacketScenePlayerLocationNotify; import emu.grasscutter.server.packet.send.PacketSetNameCardRsp; import emu.grasscutter.server.packet.send.PacketStoreWeightLimitNotify; import emu.grasscutter.server.packet.send.PacketUnlockNameCardNotify; +import emu.grasscutter.server.packet.send.PacketWorldPlayerLocationNotify; import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify; import emu.grasscutter.utils.Position; @@ -101,6 +104,7 @@ public class GenshinPlayer { @Transient private int enterSceneToken; @Transient private SceneLoadState sceneState; @Transient private boolean hasSentAvatarDataNotify; + @Transient private long nextSendPlayerLocTime = 0; @Transient private final Int2ObjectMap coopRequests; @Transient private final InvokeHandler combatInvokeHandler; @@ -121,6 +125,12 @@ public class GenshinPlayer { } this.properties.put(prop.getId(), 0); } + + this.gachaInfo = new PlayerGachaInfo(); + this.nameCardList = new HashSet<>(); + this.flyCloakList = new HashSet<>(); + this.costumeList = new HashSet<>(); + this.setSceneId(3); this.setRegionId(1); this.sceneState = SceneLoadState.NONE; @@ -140,11 +150,6 @@ public class GenshinPlayer { this.nickname = "Traveler"; this.signature = ""; this.teamManager = new TeamManager(this); - this.gachaInfo = new PlayerGachaInfo(); - this.playerProfile = new PlayerProfile(this); - this.nameCardList = new HashSet<>(); - this.flyCloakList = new HashSet<>(); - this.costumeList = new HashSet<>(); this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1); this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1); this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50); @@ -288,7 +293,7 @@ public class GenshinPlayer { } private float getExpModifier() { - return Grasscutter.getConfig().getGameRates().ADVENTURE_EXP_RATE; + return Grasscutter.getConfig().getGameServerOptions().getGameRates().ADVENTURE_EXP_RATE; } // Affected by exp rate @@ -347,7 +352,6 @@ public class GenshinPlayer { public PlayerProfile getProfile() { if (this.playerProfile == null) { this.playerProfile = new PlayerProfile(this); - this.save(); } return playerProfile; } @@ -654,6 +658,13 @@ public class GenshinPlayer { return social; } + public WorldPlayerLocationInfo getWorldPlayerLocationInfo() { + return WorldPlayerLocationInfo.newBuilder() + .setSceneId(this.getSceneId()) + .setPlayerLoc(this.getPlayerLocationInfo()) + .build(); + } + public PlayerLocationInfo getPlayerLocationInfo() { return PlayerLocationInfo.newBuilder() .setUid(this.getUid()) @@ -679,9 +690,22 @@ public class GenshinPlayer { } // Ping if (this.getWorld() != null) { - this.sendPacket(new PacketWorldPlayerRTTNotify(this.getWorld())); // Player ping + // RTT notify - very important to send this often + this.sendPacket(new PacketWorldPlayerRTTNotify(this.getWorld())); + + // Update player locations if in multiplayer every 5 seconds + long time = System.currentTimeMillis(); + if (this.getWorld().isMultiplayer() && this.getScene() != null && time > nextSendPlayerLocTime) { + this.sendPacket(new PacketWorldPlayerLocationNotify(this.getWorld())); + this.sendPacket(new PacketScenePlayerLocationNotify(this.getScene())); + this.resetSendPlayerLocTime(); + } } } + + public void resetSendPlayerLocTime() { + this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000; + } @PostLoad private void onLoad() { @@ -696,12 +720,8 @@ public class GenshinPlayer { // Make sure these exist if (this.getTeamManager() == null) { this.teamManager = new TeamManager(this); - } if (this.getGachaInfo() == null) { - this.gachaInfo = new PlayerGachaInfo(); - } if (this.nameCardList == null) { - this.nameCardList = new HashSet<>(); - } if (this.costumeList == null) { - this.costumeList = new HashSet<>(); + } if (this.getProfile().getUid() == 0) { + this.getProfile().syncWithCharacter(this); } // Check if player object exists in server diff --git a/src/main/java/emu/grasscutter/game/GenshinScene.java b/src/main/java/emu/grasscutter/game/GenshinScene.java index 668902d87..ea462fcdb 100644 --- a/src/main/java/emu/grasscutter/game/GenshinScene.java +++ b/src/main/java/emu/grasscutter/game/GenshinScene.java @@ -34,7 +34,8 @@ public class GenshinScene { private int time; private ClimateType climate; - + private int weather; + public GenshinScene(World world, SceneData sceneData) { this.world = world; this.sceneData = sceneData; @@ -89,10 +90,18 @@ public class GenshinScene { return climate; } + public int getWeather() { + return weather; + } + public void setClimate(ClimateType climate) { this.climate = climate; } + public void setWeather(int weather) { + this.weather = weather; + } + public boolean isInScene(GenshinEntity entity) { return this.entities.containsKey(entity.getId()); } diff --git a/src/main/java/emu/grasscutter/game/TeamInfo.java b/src/main/java/emu/grasscutter/game/TeamInfo.java index 83220a7e5..efaf5ea1c 100644 --- a/src/main/java/emu/grasscutter/game/TeamInfo.java +++ b/src/main/java/emu/grasscutter/game/TeamInfo.java @@ -15,7 +15,7 @@ public class TeamInfo { public TeamInfo() { this.name = ""; - this.avatars = new ArrayList<>(Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeam); + this.avatars = new ArrayList<>(Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); } public String getName() { @@ -39,7 +39,7 @@ public class TeamInfo { } public boolean addAvatar(GenshinAvatar avatar) { - if (size() >= Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeam || contains(avatar)) { + if (size() >= Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam || contains(avatar)) { return false; } @@ -59,7 +59,7 @@ public class TeamInfo { } public void copyFrom(TeamInfo team) { - copyFrom(team, Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeam); + copyFrom(team, Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); } public void copyFrom(TeamInfo team, int maxTeamSize) { diff --git a/src/main/java/emu/grasscutter/game/TeamManager.java b/src/main/java/emu/grasscutter/game/TeamManager.java index 2ab9639c2..6cd447d49 100644 --- a/src/main/java/emu/grasscutter/game/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/TeamManager.java @@ -166,13 +166,13 @@ public class TeamManager { public int getMaxTeamSize() { if (getPlayer().isInMultiplayer()) { - int max = Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeamMultiplayer; + int max = Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeamMultiplayer; if (getPlayer().getWorld().getHost() == this.getPlayer()) { return Math.max(1, (int) Math.ceil(max / (double) getWorld().getPlayerCount())); } return Math.max(1, (int) Math.floor(max / (double) getWorld().getPlayerCount())); } - return Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeam; + return Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam; } // Methods diff --git a/src/main/java/emu/grasscutter/game/World.java b/src/main/java/emu/grasscutter/game/World.java index 4375d8e4f..ffffb1e53 100644 --- a/src/main/java/emu/grasscutter/game/World.java +++ b/src/main/java/emu/grasscutter/game/World.java @@ -206,14 +206,16 @@ public class World implements Iterable { public void deregisterScene(GenshinScene scene) { this.getScenes().remove(scene.getId()); } - - public boolean forceTransferPlayerToScene(GenshinPlayer player, int sceneId, Position pos) { - // Forces the client to reload the scene map to prevent the player from falling off the map. + + public boolean transferPlayerToScene(GenshinPlayer player, int sceneId, Position pos) { if (GenshinData.getSceneDataMap().get(sceneId) == null) { return false; } + + Integer oldSceneId = null; if (player.getScene() != null) { + oldSceneId = player.getScene().getId(); player.getScene().removePlayer(player); } @@ -222,25 +224,11 @@ public class World implements Iterable { player.getPos().set(pos); // Teleport packet - player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterSelf, EnterReason.TransPoint, sceneId, pos)); - return true; - } - - public boolean transferPlayerToScene(GenshinPlayer player, int sceneId, Position pos) { - if (player.getScene().getId() == sceneId || GenshinData.getSceneDataMap().get(sceneId) == null) { - return false; + if (oldSceneId.equals(sceneId)) { + player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterGoto, EnterReason.TransPoint, sceneId, pos)); + } else { + player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterJump, EnterReason.TransPoint, sceneId, pos)); } - - if (player.getScene() != null) { - player.getScene().removePlayer(player); - } - - GenshinScene scene = this.getSceneById(sceneId); - scene.addPlayer(player); - player.getPos().set(pos); - - // Teleport packet - player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterSelf, EnterReason.TransPoint, sceneId, pos)); return true; } diff --git a/src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java b/src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java index d16208047..b80b080d9 100644 --- a/src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java @@ -1,7 +1,9 @@ package emu.grasscutter.game.avatar; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -13,6 +15,7 @@ import dev.morphia.annotations.Indexed; import dev.morphia.annotations.PostLoad; import dev.morphia.annotations.PrePersist; import dev.morphia.annotations.Transient; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GenshinData; import emu.grasscutter.data.common.FightPropData; import emu.grasscutter.data.custom.OpenConfigEntry; @@ -38,10 +41,13 @@ import emu.grasscutter.game.inventory.EquipType; import emu.grasscutter.game.inventory.GenshinItem; import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.FetterState; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo; +import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData; import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo; +import emu.grasscutter.server.packet.send.PacketAbilityChangeNotify; import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify; import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify; import emu.grasscutter.utils.ProtoHelper; @@ -69,8 +75,10 @@ public class GenshinAvatar { @Transient private final Int2ObjectMap equips; @Transient private final Int2FloatOpenHashMap fightProp; - @Transient private final Set bonusAbilityList; + @Transient private Set extraAbilityEmbryos; + private List fetters; + private Map skillLevelMap; // Talent levels private Map proudSkillBonusMap; // Talent bonus levels (from const) private int skillDepotId; @@ -86,8 +94,9 @@ public class GenshinAvatar { // Morhpia only! this.equips = new Int2ObjectOpenHashMap<>(); this.fightProp = new Int2FloatOpenHashMap(); - this.bonusAbilityList = new HashSet<>(); - this.proudSkillBonusMap = new HashMap<>(); // TODO Move to genshin avatar + this.extraAbilityEmbryos = new HashSet<>(); + this.proudSkillBonusMap = new HashMap<>(); + this.fetters = new ArrayList<>(); // TODO Move to genshin avatar } // On creation @@ -260,8 +269,16 @@ public class GenshinAvatar { return proudSkillBonusMap; } - public Set getBonusAbilityList() { - return bonusAbilityList; + public Set getExtraAbilityEmbryos() { + return extraAbilityEmbryos; + } + + public void setFetterList(List fetterList) { + this.fetters = fetterList; + } + + public List getFetterList() { + return fetters; } public float getCurrentHp() { @@ -347,14 +364,14 @@ public class GenshinAvatar { item.setEquipCharacter(this.getAvatarId()); item.save(); + if (this.getPlayer().hasSentAvatarDataNotify()) { + this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item)); + } + if (shouldRecalc) { this.recalcStats(); } - if (this.getPlayer().hasSentAvatarDataNotify()) { - this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item)); - } - return true; } @@ -371,11 +388,21 @@ public class GenshinAvatar { } public void recalcStats() { + recalcStats(false); + } + + public void recalcStats(boolean forceSendAbilityChange) { // Setup AvatarData data = this.getAvatarData(); AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(data.getAvatarPromoteId(), this.getPromoteLevel()); Int2IntOpenHashMap setMap = new Int2IntOpenHashMap(); - this.getBonusAbilityList().clear(); + + // Extra ability embryos + Set prevExtraAbilityEmbryos = this.getExtraAbilityEmbryos(); + this.extraAbilityEmbryos = new HashSet<>(); + + // Fetters + this.setFetterList(data.getFetters()); // Get hp percent, set to 100% if none float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); @@ -458,7 +485,7 @@ public class GenshinAvatar { } // Add any skill strings from this affix - this.addToAbilityList(affix.getOpenConfig(), true); + this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true); } else { break; } @@ -505,7 +532,7 @@ public class GenshinAvatar { } // Add any skill strings from this affix - this.addToAbilityList(affix.getOpenConfig(), true); + this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true); } } } @@ -538,7 +565,7 @@ public class GenshinAvatar { } // Add any skill strings from this proud skill - this.addToAbilityList(proudSkillData.getOpenConfig(), true); + this.addToExtraAbilityEmbryos(proudSkillData.getOpenConfig(), true); } // Constellations @@ -550,7 +577,7 @@ public class GenshinAvatar { } // Add any skill strings from this constellation - this.addToAbilityList(avatarTalentData.getOpenConfig(), false); + this.addToExtraAbilityEmbryos(avatarTalentData.getOpenConfig(), false); } } @@ -573,11 +600,17 @@ public class GenshinAvatar { // Packet if (getPlayer() != null && getPlayer().hasSentAvatarDataNotify()) { + // Update stats for client getPlayer().sendPacket(new PacketAvatarFightPropNotify(this)); + // Update client abilities + EntityAvatar entity = this.getAsEntity(); + if (entity != null && (!this.getExtraAbilityEmbryos().equals(prevExtraAbilityEmbryos) || forceSendAbilityChange)) { + getPlayer().sendPacket(new PacketAbilityChangeNotify(entity)); + } } } - public void addToAbilityList(String openConfig, boolean forceAdd) { + public void addToExtraAbilityEmbryos(String openConfig, boolean forceAdd) { if (openConfig == null || openConfig.length() == 0) { return; } @@ -586,14 +619,14 @@ public class GenshinAvatar { if (entry == null) { if (forceAdd) { // Add config string to ability skill list anyways - this.getBonusAbilityList().add(openConfig); + this.getExtraAbilityEmbryos().add(openConfig); } return; } if (entry.getAddAbilities() != null) { for (String ability : entry.getAddAbilities()) { - this.getBonusAbilityList().add(ability); + this.getExtraAbilityEmbryos().add(ability); } } } @@ -668,6 +701,20 @@ public class GenshinAvatar { } public AvatarInfo toProto() { + AvatarFetterInfo.Builder avatarFetter = AvatarFetterInfo.newBuilder() + .setExpLevel(10) + .setExpNumber(6325); // Highest Level + + if (this.getFetterList() != null) { + for (int i = 0; i < this.getFetterList().size(); i++) { + avatarFetter.addFetterList( + FetterData.newBuilder() + .setFetterId(this.getFetterList().get(i)) + .setFetterState(FetterState.FINISH.getValue()) + ); + } + } + AvatarInfo.Builder avatarInfo = AvatarInfo.newBuilder() .setAvatarId(this.getAvatarId()) .setGuid(this.getGuid()) @@ -681,7 +728,7 @@ public class GenshinAvatar { .putAllProudSkillExtraLevel(getProudSkillBonusMap()) .setAvatarType(1) .setBornTime(this.getBornTime()) - .setFetterInfo(AvatarFetterInfo.newBuilder().setExpLevel(1)) + .setFetterInfo(avatarFetter) .setWearingFlycloakId(this.getFlyCloak()) .setCostumeId(this.getCostume()); diff --git a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java index 86e8ea458..233da5288 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java @@ -223,8 +223,8 @@ public class EntityAvatar extends GenshinEntity { } } // Add equip abilities - if (this.getAvatar().getBonusAbilityList().size() > 0) { - for (String skill : this.getAvatar().getBonusAbilityList()) { + if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) { + for (String skill : this.getAvatar().getExtraAbilityEmbryos()) { AbilityEmbryo emb = AbilityEmbryo.newBuilder() .setAbilityId(++embryoId) .setAbilityNameHash(Utils.abilityHash(skill)) diff --git a/src/main/java/emu/grasscutter/game/friends/FriendsList.java b/src/main/java/emu/grasscutter/game/friends/FriendsList.java index 582a10efd..45a11fb1c 100644 --- a/src/main/java/emu/grasscutter/game/friends/FriendsList.java +++ b/src/main/java/emu/grasscutter/game/friends/FriendsList.java @@ -220,7 +220,7 @@ public class FriendsList { friendship.setOwner(getPlayer()); // Check if friend is online - GenshinPlayer friend = getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getId()); + GenshinPlayer friend = getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getUid()); if (friend != null) { // Set friend to online mode friendship.setFriendProfile(friend); diff --git a/src/main/java/emu/grasscutter/game/friends/Friendship.java b/src/main/java/emu/grasscutter/game/friends/Friendship.java index 7ac147b6a..28561dc39 100644 --- a/src/main/java/emu/grasscutter/game/friends/Friendship.java +++ b/src/main/java/emu/grasscutter/game/friends/Friendship.java @@ -88,7 +88,7 @@ public class Friendship { public FriendBrief toProto() { FriendBrief proto = FriendBrief.newBuilder() - .setUid(getFriendProfile().getId()) + .setUid(getFriendProfile().getUid()) .setNickname(getFriendProfile().getName()) .setLevel(getFriendProfile().getPlayerLevel()) .setAvatar(HeadImage.newBuilder().setAvatarId(getFriendProfile().getAvatarId())) diff --git a/src/main/java/emu/grasscutter/game/friends/PlayerProfile.java b/src/main/java/emu/grasscutter/game/friends/PlayerProfile.java index 50889ad0c..4af5f570e 100644 --- a/src/main/java/emu/grasscutter/game/friends/PlayerProfile.java +++ b/src/main/java/emu/grasscutter/game/friends/PlayerProfile.java @@ -8,7 +8,7 @@ import emu.grasscutter.utils.Utils; public class PlayerProfile { @Transient private GenshinPlayer player; - private int id; + @AlsoLoad("id") private int uid; private int nameCard; private int avatarId; private String name; @@ -23,12 +23,12 @@ public class PlayerProfile { public PlayerProfile() { } public PlayerProfile(GenshinPlayer player) { - this.id = player.getUid(); + this.uid = player.getUid(); this.syncWithCharacter(player); } - public int getId() { - return id; + public int getUid() { + return uid; } public GenshinPlayer getPlayer() { @@ -88,6 +88,7 @@ public class PlayerProfile { return; } + this.uid = player.getUid(); this.name = player.getNickname(); this.avatarId = player.getHeadImage(); this.signature = player.getSignature(); diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 3e6bcdf7e..ed5492b62 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -92,7 +92,7 @@ public class GachaBanner { } public GachaInfo toProto() { - String record = "http://" + (Grasscutter.getConfig().DispatchServerPublicIp.isEmpty() ? Grasscutter.getConfig().DispatchServerIp : Grasscutter.getConfig().DispatchServerPublicIp) + "/gacha"; + String record = "http://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + "/gacha"; GachaInfo.Builder info = GachaInfo.newBuilder() .setGachaType(this.getGachaType()) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index eb1cba379..079f0dda3 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -218,7 +218,6 @@ public class GachaManager { addStarglitter = 2; // Add 1 const gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null)); - gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(constItemId).setCount(1)); player.getInventory().addItem(constItemId, 1); } else { // Is max const @@ -300,7 +299,7 @@ public class GachaManager { @Subscribe public synchronized void watchBannerJson(GameServerTickEvent tickEvent) { - if(Grasscutter.getConfig().getServerOptions().WatchGacha) { + if(Grasscutter.getConfig().getGameServerOptions().WatchGacha) { try { WatchKey watchKey = watchService.take(); diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index 19bdcc525..8954b1031 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -37,10 +37,10 @@ public class Inventory implements Iterable { this.store = new Long2ObjectOpenHashMap<>(); this.inventoryTypes = new Int2ObjectOpenHashMap<>(); - this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitWeapon)); - this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitRelic)); - this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitMaterial)); - this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitFurniture)); + this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon)); + this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic)); + this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial)); + this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture)); } public GenshinPlayer getPlayer() { diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index 935652d77..532155db1 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -589,7 +589,6 @@ public class InventoryManager { // Update proud skills AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId()); - boolean hasAddedProudSkill = false; if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) { for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) { @@ -599,7 +598,6 @@ public class InventoryManager { if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) { int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1; if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) { - hasAddedProudSkill = true; avatar.getProudSkillList().add(proudSkillId); player.sendPacket(new PacketProudSkillChangeNotify(avatar)); } @@ -607,20 +605,13 @@ public class InventoryManager { } } - // Racalc stats and save avatar - avatar.recalcStats(); - avatar.save(); - - // Resend ability embryos if proud skill has been added - if (hasAddedProudSkill && avatar.getAsEntity() != null) { - player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity())); - } - - // TODO Send entity prop update packet to world - // Packets player.sendPacket(new PacketAvatarPropNotify(avatar)); player.sendPacket(new PacketAvatarPromoteRsp(avatar)); + + // TODO Send entity prop update packet to world + avatar.recalcStats(true); + avatar.save(); } public void upgradeAvatar(GenshinPlayer player, long guid, int itemId, int count) { @@ -827,25 +818,20 @@ public class InventoryManager { // Apply + recalc avatar.getTalentIdList().add(talentData.getId()); avatar.setCoreProudSkillLevel(currentTalentLevel + 1); - avatar.recalcStats(); // Packet player.sendPacket(new PacketAvatarUnlockTalentNotify(avatar, nextTalentId)); player.sendPacket(new PacketUnlockAvatarTalentRsp(avatar, nextTalentId)); - // Proud skill bonus map + // Proud skill bonus map (Extra skills) OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(talentData.getOpenConfig()); if (entry != null && entry.getExtraTalentIndex() > 0) { avatar.recalcProudSkillBonusMap(); player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex())); } - // Resend ability embryos - if (avatar.getAsEntity() != null) { - player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity())); - } - - // Save avatar + // Recalc + save avatar + avatar.recalcStats(true); avatar.save(); } diff --git a/src/main/java/emu/grasscutter/game/props/FetterState.java b/src/main/java/emu/grasscutter/game/props/FetterState.java new file mode 100644 index 000000000..346060e19 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/FetterState.java @@ -0,0 +1,42 @@ +package emu.grasscutter.game.props; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public enum FetterState { + NONE(0), + NOT_OPEN(1), + OPEN(1), + FINISH(3); + + private final int value; + private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); + private static final Map stringMap = new HashMap<>(); + + static { + Stream.of(values()).forEach(e -> { + map.put(e.getValue(), e); + stringMap.put(e.name(), e); + }); + } + + private FetterState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static FetterState getTypeByValue(int value) { + return map.getOrDefault(value, NONE); + } + + public static FetterState getTypeByName(String name) { + return stringMap.getOrDefault(name, NONE); + } +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index f13b9dc2f..7c7915675 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -1,30 +1,14 @@ package emu.grasscutter.server.dispatch; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.InetSocketAddress; -import java.net.URI; -import java.net.URLDecoder; -import java.security.KeyStore; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.protobuf.ByteString; import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; +import emu.grasscutter.Config; import emu.grasscutter.Grasscutter; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; @@ -32,30 +16,34 @@ import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegio import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp; import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; -import emu.grasscutter.server.dispatch.json.ComboTokenReqJson; -import emu.grasscutter.server.dispatch.json.ComboTokenResJson; -import emu.grasscutter.server.dispatch.json.LoginAccountRequestJson; -import emu.grasscutter.server.dispatch.json.LoginResultJson; -import emu.grasscutter.server.dispatch.json.LoginTokenRequestJson; +import emu.grasscutter.server.dispatch.json.*; import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; -import com.sun.net.httpserver.HttpServer; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import java.io.*; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URLDecoder; +import java.security.KeyStore; +import java.util.*; public final class DispatchServer { - private final InetSocketAddress address; - private final Gson gson; - private QueryCurrRegionHttpRsp currRegion; - - public String regionListBase64; - public String regionCurrentBase64; - public static String query_region_list = ""; public static String query_cur_region = ""; + + private final InetSocketAddress address; + private final Gson gson; + private final String defaultServerName = "os_usa"; + public String regionListBase64; + public HashMap regions; + public DispatchServer() { - this.address = new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, Grasscutter.getConfig().DispatchServerPort); + this.regions = new HashMap(); + this.address = new InetSocketAddress(Grasscutter.getConfig().getDispatchOptions().Ip, Grasscutter.getConfig().getDispatchOptions().Port); this.gson = new GsonBuilder().create(); this.loadQueries(); @@ -71,7 +59,13 @@ public final class DispatchServer { } public QueryCurrRegionHttpRsp getCurrRegion() { - return currRegion; + // Needs to be fixed by having the game servers connect to the dispatch server. + if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("HYBRID")) { + return regions.get(defaultServerName).parsedRegionQuery; + } + + Grasscutter.getLogger().warn("[Dispatch] Unsupported run mode for getCurrRegion()"); + return null; } public void loadQueries() { @@ -81,14 +75,14 @@ public final class DispatchServer { if (file.exists()) { query_region_list = new String(FileUtils.read(file)); } else { - Grasscutter.getLogger().warn("query_region_list not found! Using default region list."); + Grasscutter.getLogger().warn("[Dispatch] query_region_list not found! Using default region list."); } file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt"); if (file.exists()) { query_cur_region = new String(FileUtils.read(file)); } else { - Grasscutter.getLogger().warn("query_cur_region not found! Using default current region."); + Grasscutter.getLogger().warn("[Dispatch] query_cur_region not found! Using default current region."); } } @@ -99,53 +93,80 @@ public final class DispatchServer { byte[] decoded2 = Base64.getDecoder().decode(query_cur_region); QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2); - - RegionSimpleInfo server = RegionSimpleInfo.newBuilder() - .setName("os_usa") - .setTitle(Grasscutter.getConfig().GameServerName) - .setType("DEV_PUBLIC") - .setDispatchUrl("https://" + (Grasscutter.getConfig().DispatchServerPublicIp.isEmpty() ? Grasscutter.getConfig().DispatchServerIp : Grasscutter.getConfig().DispatchServerPublicIp) + ":" + getAddress().getPort() + "/query_cur_region") - .build(); - - RegionSimpleInfo serverTest2 = RegionSimpleInfo.newBuilder() - .setName("os_euro") - .setTitle("Grasscutter") - .setType("DEV_PUBLIC") - .setDispatchUrl("https://" + (Grasscutter.getConfig().DispatchServerPublicIp.isEmpty() ? Grasscutter.getConfig().DispatchServerIp : Grasscutter.getConfig().DispatchServerPublicIp) + ":" + getAddress().getPort() + "/query_cur_region") - .build(); - + + List servers = new ArrayList(); + List usedNames = new ArrayList(); // List to check for potential naming conflicts + if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("HYBRID")) { // Automatically add the game server if in hybrid mode + RegionSimpleInfo server = RegionSimpleInfo.newBuilder() + .setName("os_usa") + .setTitle(Grasscutter.getConfig().getGameServerOptions().Name) + .setType("DEV_PUBLIC") + .setDispatchUrl("http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + ":" + (Grasscutter.getConfig().getDispatchOptions().PublicPort != 0 ? Grasscutter.getConfig().getDispatchOptions().PublicPort : Grasscutter.getConfig().getDispatchOptions().Port) + "/query_cur_region_" + defaultServerName) + .build(); + usedNames.add(defaultServerName); + servers.add(server); + + RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() + .setIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getGameServerOptions().Ip : Grasscutter.getConfig().getGameServerOptions().PublicIp)) + .setPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0 ? Grasscutter.getConfig().getGameServerOptions().PublicPort : Grasscutter.getConfig().getGameServerOptions().Port) + .setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .build(); + + QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); + regions.put(defaultServerName, new RegionData(parsedRegionQuery, Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); + + } else { + if(Grasscutter.getConfig().getDispatchOptions().getGameServers().length == 0) { + Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); + System.exit(1); + } + } + + for (Config.DispatchServerOptions.RegionInfo regionInfo : Grasscutter.getConfig().getDispatchOptions().getGameServers()) { + if(usedNames.contains(regionInfo.Name)) { + Grasscutter.getLogger().error("Region name already in use."); + continue; + } + RegionSimpleInfo server = RegionSimpleInfo.newBuilder() + .setName(regionInfo.Name) + .setTitle(regionInfo.Title) + .setType("DEV_PUBLIC") + .setDispatchUrl("http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + ":" + getAddress().getPort() + "/query_cur_region_" + regionInfo.Name) + .build(); + usedNames.add(regionInfo.Name); + servers.add(server); + + RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() + .setIp(regionInfo.Ip) + .setPort(regionInfo.Port) + .setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .build(); + + QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); + regions.put(regionInfo.Name, new RegionData(parsedRegionQuery, Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); + } + QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.newBuilder() - .addServers(server) - .addServers(serverTest2) + .addAllServers(servers) .setClientSecretKey(rl.getClientSecretKey()) .setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted()) .setEnableLoginPc(true) .build(); - - RegionInfo currentRegion = regionQuery.getRegionInfo().toBuilder() - .setIp((Grasscutter.getConfig().GameServerPublicIp.isEmpty() ? Grasscutter.getConfig().GameServerIp : Grasscutter.getConfig().GameServerPublicIp)) - .setPort(Grasscutter.getConfig().GameServerPort) - .setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) - .build(); - - QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(currentRegion).build(); this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray()); - this.regionCurrentBase64 = Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()); - this.currRegion = parsedRegionQuery; } catch (Exception e) { - Grasscutter.getLogger().error("Error while initializing region info!", e); + Grasscutter.getLogger().error("[Dispatch] Error while initializing region info!", e); } } public void start() throws Exception { HttpServer server; - if(Grasscutter.getConfig().UseSSL) { + if (Grasscutter.getConfig().getDispatchOptions().UseSSL) { HttpsServer httpsServer; httpsServer = HttpsServer.create(getAddress(), 0); SSLContext sslContext = SSLContext.getInstance("TLS"); - try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) { - char[] keystorePassword = Grasscutter.getConfig().DispatchServerKeystorePassword.toCharArray(); + try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().getDispatchOptions().KeystorePath)) { + char[] keystorePassword = Grasscutter.getConfig().getDispatchOptions().KeystorePassword.toCharArray(); KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(fis, keystorePassword); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); @@ -156,56 +177,39 @@ public final class DispatchServer { httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); server = httpsServer; } catch (Exception e) { - Grasscutter.getLogger().error("No SSL cert found!"); - return; + Grasscutter.getLogger().warn("[Dispatch] No SSL cert found! Falling back to HTTP server."); + Grasscutter.getConfig().getDispatchOptions().UseSSL = false; + server = HttpServer.create(getAddress(), 0); } } else { server = HttpServer.create(getAddress(), 0); } - - server.createContext("/", t -> { - //Create a response form the request query parameters - String response = "Hello"; - //Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); - t.sendResponseHeaders(200, response.getBytes().length); - //Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - }); + + server.createContext("/", t -> responseHTML(t, "Hello")); // Dispatch server.createContext("/query_region_list", t -> { // Log - Grasscutter.getLogger().info("Client request: query_region_list"); - // Create a response form the request query parameters - String response = regionListBase64; - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - }); - server.createContext("/query_cur_region", t -> { - // Log - Grasscutter.getLogger().info("Client request: query_cur_region"); - // Create a response form the request query parameters - URI uri = t.getRequestURI(); - String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; - if (uri.getQuery() != null && uri.getQuery().length() > 0) { - response = regionCurrentBase64; - } - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", t.getRemoteAddress())); + + responseHTML(t, regionListBase64); }); + + for (String regionName : regions.keySet()) { + server.createContext("/query_cur_region_" + regionName, t -> { + String regionCurrentBase64 = regions.get(regionName).Base64; + // Log + Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region_%s", t.getRemoteAddress(), regionName)); + // Create a response form the request query parameters + URI uri = t.getRequestURI(); + String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; + if (uri.getQuery() != null && uri.getQuery().length() > 0) { + response = regionCurrentBase64; + } + responseHTML(t, response); + }); + } + // Login via account server.createContext("/hk4e_global/mdk/shield/api/login", t -> { // Get post data @@ -213,14 +217,15 @@ public final class DispatchServer { try { String body = Utils.toString(t.getRequestBody()); requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class); - } catch (Exception e) { - - } + } catch (Exception ignored) { } + // Create response json if (requestData == null) { return; } LoginResultJson responseData = new LoginResultJson(); + + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s is trying to log in", t.getRemoteAddress())); // Login Account account = DatabaseHelper.getAccountByName(requestData.account); @@ -228,17 +233,28 @@ public final class DispatchServer { // 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 - if (Grasscutter.getConfig().ServerOptions.AutomaticallyCreateAccounts) { + if (Grasscutter.getConfig().getDispatchOptions().AutomaticallyCreateAccounts) { // This account has been created AUTOMATICALLY. There will be no permissions added. account = DatabaseHelper.createAccountWithId(requestData.account, 0); - - responseData.message = "OK"; - responseData.data.account.uid = account.getId(); - responseData.data.account.token = account.generateSessionKey(); - responseData.data.account.email = account.getEmail(); + + if (account != null) { + responseData.message = "OK"; + responseData.data.account.uid = account.getId(); + responseData.data.account.token = account.generateSessionKey(); + responseData.data.account.email = account.getEmail(); + + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account %s created", t.getRemoteAddress(), responseData.data.account.uid)); + } else { + responseData.retcode = -201; + responseData.message = "Username not found, create failed."; + + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account create failed", t.getRemoteAddress())); + } } else { responseData.retcode = -201; responseData.message = "Username not found."; + + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account no found", t.getRemoteAddress())); } } else { // Account was found, log the player in @@ -246,17 +262,11 @@ public final class DispatchServer { responseData.data.account.uid = account.getId(); responseData.data.account.token = account.generateSessionKey(); responseData.data.account.email = account.getEmail(); + + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s logged in as %s", t.getRemoteAddress(), responseData.data.account.uid)); } - - // Create a response - String response = getGsonFactory().toJson(responseData); - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); + + responseJSON(t, responseData); }); // Login via token server.createContext("/hk4e_global/mdk/shield/api/verify", t -> { @@ -265,14 +275,14 @@ public final class DispatchServer { try { String body = Utils.toString(t.getRequestBody()); requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class); - } catch (Exception e) { - - } + } catch (Exception ignored) { } + // Create response json if (requestData == null) { return; } LoginResultJson responseData = new LoginResultJson(); + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s is trying to log in via token", t.getRemoteAddress())); // Login Account account = DatabaseHelper.getAccountById(requestData.uid); @@ -281,22 +291,18 @@ public final class DispatchServer { if (account == null || !account.getSessionKey().equals(requestData.token)) { responseData.retcode = -111; responseData.message = "Game account cache information error"; + + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in via token", t.getRemoteAddress())); } 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(String.format("[Dispatch] Client %s logged in via token as %s", t.getRemoteAddress(), responseData.data.account.uid)); } - - // Create a response - String response = getGsonFactory().toJson(responseData); - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); + + responseJSON(t, responseData); }); // Exchange for combo token server.createContext("/hk4e_global/combo/granter/login/v2/login", t -> { @@ -305,9 +311,8 @@ public final class DispatchServer { try { String body = Utils.toString(t.getRequestBody()); requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class); - } catch (Exception e) { - - } + } catch (Exception ignored) { } + // Create response json if (requestData == null || requestData.data == null) { return; @@ -322,22 +327,18 @@ public final class DispatchServer { if (account == null || !account.getSessionKey().equals(loginData.token)) { responseData.retcode = -201; responseData.message = "Wrong session key."; + + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to exchange combo token", t.getRemoteAddress())); } else { responseData.message = "OK"; responseData.data.open_id = loginData.uid; responseData.data.combo_id = "157795300"; responseData.data.combo_token = account.generateLoginToken(); + + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s succeed to exchange combo token", t.getRemoteAddress())); } - - // Create a response - String response = getGsonFactory().toJson(responseData); - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); + + responseJSON(t, responseData); }); // Agreement and Protocol server.createContext( // hk4e-sdk-os.hoyoverse.com @@ -405,63 +406,84 @@ public final class DispatchServer { "/perf/config/verify", new DispatchHttpJsonHandler("{\"code\":0}") ); - // Start server - server.start(); - Grasscutter.getLogger().info("Dispatch server started on port " + getAddress().getPort()); // Logging servers - HttpServer overseaLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, 8888), 0); - overseaLogServer.createContext( // overseauspider.yuanshen.com - "/log", + server.createContext( // overseauspider.yuanshen.com + "/log", new DispatchHttpJsonHandler("{\"code\":0}") ); - overseaLogServer.start(); - Grasscutter.getLogger().info("Log server (overseauspider) started on port " + 8888); - - HttpServer uploadLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, Grasscutter.getConfig().UploadLogPort), 0); - uploadLogServer.createContext( // log-upload-os.mihoyo.com - "/crash/dataUpload", + + server.createContext( // log-upload-os.mihoyo.com + "/crash/dataUpload", new DispatchHttpJsonHandler("{\"code\":0}") ); - uploadLogServer.createContext("/gacha", t -> { - //Create a response form the request query parameters - String response = "Gacha"; - //Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); - t.sendResponseHeaders(200, response.getBytes().length); - //Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - }); - uploadLogServer.start(); - Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + Grasscutter.getConfig().UploadLogPort); + server.createContext("/gacha", t -> responseHTML(t, "Gacha")); + + // Start server + server.start(); + Grasscutter.getLogger().info("[Dispatch] Dispatch server started on port " + getAddress().getPort()); + } + + private void responseJSON(HttpExchange t, Object data) throws IOException { + // Create a response + String response = getGsonFactory().toJson(data); + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + + private void responseHTML(HttpExchange t, String response) throws IOException { + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); + t.sendResponseHeaders(200, response.getBytes().length); + //Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); } private Map parseQueryString(String qs) { - Map result = new HashMap<>(); - if (qs == null) - return result; + Map result = new HashMap<>(); + if (qs == null) { + return result; + } - int last = 0, next, l = qs.length(); - while (last < l) { - next = qs.indexOf('&', last); - if (next == -1) - next = l; + int last = 0, next, l = qs.length(); + while (last < l) { + next = qs.indexOf('&', last); + if (next == -1) { + next = l; + } - if (next > last) { - int eqPos = qs.indexOf('=', last); - try { - if (eqPos < 0 || eqPos > next) - result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), ""); - else - result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java - } - } - last = next + 1; - } - return result; + if (next > last) { + int eqPos = qs.indexOf('=', last); + try { + if (eqPos < 0 || eqPos > next) { + result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), ""); + } else { + result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8")); + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java + } + } + last = next + 1; + } + return result; + } + + public static class RegionData { + + QueryCurrRegionHttpRsp parsedRegionQuery; + String Base64; + + public RegionData(QueryCurrRegionHttpRsp prq, String b64) { + this.parsedRegionQuery = prq; + this.Base64 = b64; + } } } diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java index dac26cfa4..b3497f8d4 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java +++ b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java @@ -7,7 +7,7 @@ public class ComboTokenReqJson { public String device; public String sign; - public class LoginTokenData { + public static class LoginTokenData { public String uid; public String token; public boolean guest; diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java index 731d50853..7c49d1278 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java +++ b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java @@ -5,7 +5,7 @@ public class ComboTokenResJson { public int retcode; public LoginData data = new LoginData(); - public class LoginData { + public static class LoginData { public int account_type = 1; public boolean heartbeat; public String combo_id; diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java index 5988752d8..88e142d4f 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java +++ b/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java @@ -5,7 +5,7 @@ public class LoginResultJson { public int retcode; public VerifyData data = new VerifyData(); - public class VerifyData { + public static class VerifyData { public VerifyAccountData account = new VerifyAccountData(); public boolean device_grant_required = false; public String realname_operation = "NONE"; @@ -13,7 +13,7 @@ public class LoginResultJson { public boolean safe_mobile_required = false; } - public class VerifyAccountData { + public static class VerifyAccountData { public String uid; public String name = ""; public String email; diff --git a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java index 118637668..62a57df91 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java +++ b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java @@ -87,7 +87,7 @@ public class GameServerPacketHandler { } // Log unhandled packets - if (Grasscutter.getConfig().LOG_PACKETS) { + if (Grasscutter.getConfig().getGameServerOptions().LOG_PACKETS) { //Grasscutter.getLogger().info("Unhandled packet (" + opcode + "): " + PacketOpcodesUtil.getOpcodeName(opcode)); } } diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index 1a54f31ba..ebd66dc20 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -165,7 +165,7 @@ public class GameSession extends MihoyoKcpChannel { byte[] data = genshinPacket.build(); // Log - if (Grasscutter.getConfig().LOG_PACKETS) { + if (Grasscutter.getConfig().getGameServerOptions().LOG_PACKETS) { logPacket(genshinPacket); } @@ -225,7 +225,7 @@ public class GameSession extends MihoyoKcpChannel { } // Log packet - if (Grasscutter.getConfig().LOG_PACKETS) { + if (Grasscutter.getConfig().getGameServerOptions().LOG_PACKETS) { Grasscutter.getLogger().info("RECV: " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")"); System.out.println(Utils.bytesToHex(payload)); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java index 5b56a39f4..2f7da6884 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java @@ -31,8 +31,11 @@ public class HandlerEnterSceneDoneReq extends PacketHandler { // Locations session.send(new PacketWorldPlayerLocationNotify(session.getPlayer().getWorld())); - session.send(new PacketScenePlayerLocationNotify(session.getPlayer())); + session.send(new PacketScenePlayerLocationNotify(session.getPlayer().getScene())); session.send(new PacketWorldPlayerRTTNotify(session.getPlayer().getWorld())); + + // Reset timer for sending player locations + session.getPlayer().resetSendPlayerLocTime(); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java index dc686f5c1..5034fb01a 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java @@ -35,15 +35,15 @@ public class HandlerGetPlayerTokenReq extends PacketHandler { // Has character boolean doesPlayerExist = false; - if (account.getPlayerId() > 0) { + if (account.getPlayerUid() > 0) { // Set flag for player existing - doesPlayerExist = DatabaseHelper.checkPlayerExists(account.getPlayerId()); + doesPlayerExist = DatabaseHelper.checkPlayerExists(account.getPlayerUid()); } // Set reserve player id if account doesnt exist if (!doesPlayerExist) { - int id = DatabaseHelper.getNextPlayerId(session.getAccount().getPlayerId()); - if (id != session.getAccount().getPlayerId()) { + int id = DatabaseHelper.getNextPlayerId(session.getAccount().getPlayerUid()); + if (id != session.getAccount().getPlayerUid()) { session.getAccount().setPlayerId(id); session.getAccount().save(); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java index 183ef7461..05753aec0 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPlayerLoginReq.java @@ -30,7 +30,7 @@ public class HandlerPlayerLoginReq extends PacketHandler { } // Load character from db - GenshinPlayer player = DatabaseHelper.getPlayerById(session.getAccount().getPlayerId()); + GenshinPlayer player = DatabaseHelper.getPlayerById(session.getAccount().getPlayerUid()); if (player == null) { // Send packets diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneTransToPointReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneTransToPointReq.java index 619a37b1e..f01f5980d 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneTransToPointReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSceneTransToPointReq.java @@ -1,11 +1,15 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.data.GenshinData; +import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.SceneTransToPointReqOuterClass.SceneTransToPointReq; +import emu.grasscutter.net.proto.SceneTransToPointRspOuterClass.SceneTransToPointRsp; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketSceneTransToPointRsp; +import emu.grasscutter.utils.Position; @Opcodes(PacketOpcodes.SceneTransToPointReq) public class HandlerSceneTransToPointReq extends PacketHandler { @@ -13,7 +17,20 @@ public class HandlerSceneTransToPointReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { SceneTransToPointReq req = SceneTransToPointReq.parseFrom(payload); - session.send(new PacketSceneTransToPointRsp(session.getPlayer(), req.getPointId(), req.getSceneId())); + + String code = req.getSceneId() + "_" + req.getPointId(); + ScenePointEntry scenePointEntry = GenshinData.getScenePointEntries().get(code); + + if (scenePointEntry != null) { + float x = scenePointEntry.getPointData().getTranPos().getX(); + float y = scenePointEntry.getPointData().getTranPos().getY(); + float z = scenePointEntry.getPointData().getTranPos().getZ(); + + session.getPlayer().getWorld().transferPlayerToScene(session.getPlayer(), req.getSceneId(), new Position(x, y, z)); + session.send(new PacketSceneTransToPointRsp(session.getPlayer(), req.getPointId(), req.getSceneId())); + } else { + session.send(new PacketSceneTransToPointRsp()); + } } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java index 1a60f677b..d194e1465 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java @@ -3,6 +3,7 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.net.packet.GenshinPacket; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetEntityClientDataNotifyOuterClass.SetEntityClientDataNotify; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; @@ -16,8 +17,11 @@ public class HandlerSetEntityClientDataNotify extends PacketHandler { return; } + // Make sure packet is a valid proto before replaying it to the other players + SetEntityClientDataNotify notif = SetEntityClientDataNotify.parseFrom(payload); + GenshinPacket packet = new GenshinPacket(PacketOpcodes.SetEntityClientDataNotify, true); - packet.setData(payload); + packet.setData(notif); session.getPlayer().getScene().broadcastPacketToOthers(session.getPlayer(), packet); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java index 5cbbd8d04..72fc709d9 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java @@ -43,7 +43,7 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler { try { // Save to db - DatabaseHelper.createPlayer(player, session.getAccount().getPlayerId()); + DatabaseHelper.createPlayer(player, session.getAccount().getPlayerUid()); // Create avatar if (player.getAvatars().getAvatarCount() == 0) { diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityChangeNotify.java index a6e1fb452..d8cdfdef8 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityChangeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAbilityChangeNotify.java @@ -8,8 +8,8 @@ import emu.grasscutter.net.proto.AbilityChangeNotifyOuterClass.AbilityChangeNoti public class PacketAbilityChangeNotify extends GenshinPacket { public PacketAbilityChangeNotify(EntityAvatar entity) { - super(PacketOpcodes.AbilityChangeNotify); - + super(PacketOpcodes.AbilityChangeNotify, true); + AbilityChangeNotify proto = AbilityChangeNotify.newBuilder() .setEntityId(entity.getId()) .setAbilityControlBlock(entity.getAbilityControlBlock()) diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java index 9df89af5f..4d22f9603 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java @@ -23,7 +23,6 @@ public class PacketGetPlayerFriendListRsp extends GenshinPacket { .setWorldLevel(0) .setSignature("") .setLastActiveTime((int) (System.currentTimeMillis() / 1000f)) - .setIsMpModeAvailable(true) .setNameCardId(210001) .setOnlineState(FriendOnlineState.FRIEND_ONLINE) .setParam(1) diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerTokenRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerTokenRsp.java index 2306aa0ae..00f512ea7 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerTokenRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerTokenRsp.java @@ -16,7 +16,7 @@ public class PacketGetPlayerTokenRsp extends GenshinPacket { this.setUseDispatchKey(true); GetPlayerTokenRsp p = GetPlayerTokenRsp.newBuilder() - .setPlayerUid(session.getAccount().getPlayerId()) + .setPlayerUid(session.getAccount().getPlayerUid()) .setAccountToken(session.getAccount().getToken()) .setAccountType(1) .setIsProficientPlayer(doesPlayerExist) // Not sure where this goes diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneNotify.java index e2db99153..d66575273 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerEnterSceneNotify.java @@ -52,7 +52,7 @@ public class PacketPlayerEnterSceneNotify extends GenshinPacket { .setSceneId(newScene) .setPos(newPos.toProto()) .setSceneBeginTime(System.currentTimeMillis()) - .setType(EnterType.EnterSelf) + .setType(type) .setTargetUid(target.getUid()) .setEnterSceneToken(player.getEnterSceneToken()) .setWorldLevel(target.getWorld().getWorldLevel()) diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java index 0d0871ca9..3a796c7ad 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java @@ -1,21 +1,61 @@ package emu.grasscutter.server.packet.send; +import com.google.protobuf.ByteString; import emu.grasscutter.Grasscutter; import emu.grasscutter.net.packet.GenshinPacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.PlayerLoginRspOuterClass.PlayerLoginRsp; +import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass; import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.utils.FileUtils; + +import java.io.File; +import java.net.URL; +import java.util.Base64; public class PacketPlayerLoginRsp extends GenshinPacket { + private static QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp regionCache; + public PacketPlayerLoginRsp(GameSession session) { super(PacketOpcodes.PlayerLoginRsp, 1); this.setUseDispatchKey(true); - - RegionInfo info = Grasscutter.getDispatchServer().getCurrRegion().getRegionInfo(); - + + RegionInfo info; + + if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("GAME_ONLY")) { + if (regionCache == null) { + try { + File file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt"); + String query_cur_region = ""; + if (file.exists()) { + query_cur_region = new String(FileUtils.read(file)); + } else { + Grasscutter.getLogger().warn("query_cur_region not found! Using default current region."); + } + + byte[] decodedCurRegion = Base64.getDecoder().decode(query_cur_region); + QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp.parseFrom(decodedCurRegion); + + RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() + .setIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getGameServerOptions().Ip : Grasscutter.getConfig().getGameServerOptions().PublicIp)) + .setPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0 ? Grasscutter.getConfig().getGameServerOptions().PublicPort : Grasscutter.getConfig().getGameServerOptions().Port) + .setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .build(); + + regionCache = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); + } catch (Exception e) { + Grasscutter.getLogger().error("Error while initializing region cache!", e); + } + } + + info = regionCache.getRegionInfo(); + } else { + info = Grasscutter.getDispatchServer().getCurrRegion().getRegionInfo(); + } + PlayerLoginRsp p = PlayerLoginRsp.newBuilder() .setIsUseAbilityHash(true) // true .setAbilityHashCode(1844674) // 1844674 diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java index c21a15351..a3309a5c1 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java @@ -19,7 +19,7 @@ public class PacketPlayerStoreNotify extends GenshinPacket { PlayerStoreNotify.Builder p = PlayerStoreNotify.newBuilder() .setStoreType(StoreType.StorePack) - .setWeightLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitAll); + .setWeightLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitAll); for (GenshinItem item : player.getInventory()) { Item itemProto = item.toProto(); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java index 8048e4b0d..5af6999d5 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java @@ -1,6 +1,6 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.Config.ServerOptions; +import emu.grasscutter.Config.GameServerOptions; import emu.grasscutter.GenshinConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.game.GenshinPlayer; @@ -14,7 +14,7 @@ public class PacketPullRecentChatRsp extends GenshinPacket { public PacketPullRecentChatRsp(GenshinPlayer player) { super(PacketOpcodes.PullRecentChatRsp); - ServerOptions serverOptions = Grasscutter.getConfig().getServerOptions(); + GameServerOptions serverOptions = Grasscutter.getConfig().getGameServerOptions(); PullRecentChatRsp.Builder proto = PullRecentChatRsp.newBuilder(); if (serverOptions.WelcomeEmotes != null && serverOptions.WelcomeEmotes.length > 0) { @@ -33,7 +33,7 @@ public class PacketPullRecentChatRsp extends GenshinPacket { .setTime((int) (System.currentTimeMillis() / 1000)) .setUid(GenshinConstants.SERVER_CONSOLE_UID) .setToUid(player.getUid()) - .setText(Grasscutter.getConfig().getServerOptions().WelcomeMotd) + .setText(Grasscutter.getConfig().getGameServerOptions().WelcomeMotd) .build(); proto.addChatInfo(welcomeMotd); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java index de26dd064..69c40dba0 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneAreaWeatherNotify.java @@ -12,7 +12,7 @@ public class PacketSceneAreaWeatherNotify extends GenshinPacket { super(PacketOpcodes.SceneAreaWeatherNotify); SceneAreaWeatherNotify proto = SceneAreaWeatherNotify.newBuilder() - .setWeatherAreaId(1) + .setWeatherAreaId(player.getScene().getWeather()) .setClimateType(player.getScene().getClimate().getValue()) .build(); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerLocationNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerLocationNotify.java index f6fa9b8f0..2e9fb479d 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerLocationNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketScenePlayerLocationNotify.java @@ -1,19 +1,20 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.game.GenshinPlayer; +import emu.grasscutter.game.GenshinScene; import emu.grasscutter.net.packet.GenshinPacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.ScenePlayerLocationNotifyOuterClass.ScenePlayerLocationNotify; public class PacketScenePlayerLocationNotify extends GenshinPacket { - public PacketScenePlayerLocationNotify(GenshinPlayer player) { + public PacketScenePlayerLocationNotify(GenshinScene scene) { super(PacketOpcodes.ScenePlayerLocationNotify); ScenePlayerLocationNotify.Builder proto = ScenePlayerLocationNotify.newBuilder() - .setSceneId(player.getSceneId()); + .setSceneId(scene.getId()); - for (GenshinPlayer p : player.getWorld().getPlayers()) { + for (GenshinPlayer p : scene.getPlayers()) { proto.addPlayerLocList(p.getPlayerLocationInfo()); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTransToPointRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTransToPointRsp.java index 965c6aa6b..4795c5e9f 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTransToPointRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneTransToPointRsp.java @@ -13,23 +13,22 @@ public class PacketSceneTransToPointRsp extends GenshinPacket { public PacketSceneTransToPointRsp(GenshinPlayer player, int pointId, int sceneId) { super(PacketOpcodes.SceneTransToPointRsp); - String code = sceneId + "_" + pointId; - ScenePointEntry scenePointEntry = GenshinData.getScenePointEntries().get(code); - - float x = scenePointEntry.getPointData().getTranPos().getX(); - float y = scenePointEntry.getPointData().getTranPos().getY(); - float z = scenePointEntry.getPointData().getTranPos().getZ(); - - player.getPos().set(new Position(x, y, z)); - - player.getWorld().forceTransferPlayerToScene(player, sceneId, player.getPos()); - SceneTransToPointRsp proto = SceneTransToPointRsp.newBuilder() - .setRetcode(0) - .setPointId(pointId) - .setSceneId(sceneId) - .build(); + .setRetcode(0) + .setPointId(pointId) + .setSceneId(sceneId) + .build(); + + this.setData(proto); + } + + public PacketSceneTransToPointRsp() { + super(PacketOpcodes.SceneTransToPointRsp); + SceneTransToPointRsp proto = SceneTransToPointRsp.newBuilder() + .setRetcode(1) // Internal server error + .build(); + this.setData(proto); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneUnlockInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneUnlockInfoNotify.java index 857f9879c..bd1b30685 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketSceneUnlockInfoNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSceneUnlockInfoNotify.java @@ -12,8 +12,8 @@ public class PacketSceneUnlockInfoNotify extends GenshinPacket { SceneUnlockInfoNotify proto = SceneUnlockInfoNotify.newBuilder() .addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(1)) - .addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(3)) - .addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(4)) + .addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(3).addSceneTagIdList(102).addSceneTagIdList(113).addSceneTagIdList(117)) + .addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(4).addSceneTagIdList(106).addSceneTagIdList(109)) .addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(5)) .addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(6)) .addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(7)) diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java index b93130388..04668b00e 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java @@ -13,11 +13,11 @@ public class PacketStoreWeightLimitNotify extends GenshinPacket { StoreWeightLimitNotify p = StoreWeightLimitNotify.newBuilder() .setStoreType(StoreType.StorePack) - .setWeightLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitAll) - .setWeaponCountLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitWeapon) - .setReliquaryCountLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitRelic) - .setMaterialCountLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitMaterial) - .setFurnitureCountLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitFurniture) + .setWeightLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitAll) + .setWeaponCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon) + .setReliquaryCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic) + .setMaterialCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial) + .setFurnitureCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture) .build(); this.setData(p); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerLocationNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerLocationNotify.java index cb546f0e7..1bb09f8db 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerLocationNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerLocationNotify.java @@ -14,7 +14,7 @@ public class PacketWorldPlayerLocationNotify extends GenshinPacket { WorldPlayerLocationNotify.Builder proto = WorldPlayerLocationNotify.newBuilder(); for (GenshinPlayer p : world.getPlayers()) { - proto.addPlayerLocList(p.getPlayerLocationInfo()); + proto.addPlayerLocList(p.getWorldPlayerLocationInfo()); } this.setData(proto); diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index 6f6466773..1afcc1ebe 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -1,8 +1,13 @@ package emu.grasscutter.tools; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; +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; @@ -30,13 +35,13 @@ public final class Tools { ResourceLoader.loadResources(); Map map; - try (FileReader fileReader = new FileReader(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMapEN.json"))) { + try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMapEN.json")), StandardCharsets.UTF_8)) { map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken>() {}.getType()); } List list; String fileName = "./GM Handbook.txt"; - try (FileWriter fileWriter = new FileWriter(fileName); PrintWriter writer = new PrintWriter(fileWriter)) { + try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(fileName), StandardCharsets.UTF_8), false)) { DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); LocalDateTime now = LocalDateTime.now(); diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 251898bd4..c481ffd07 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -158,7 +158,7 @@ public final class Utils { // Check for GenshinData. if(!fileExists(resourcesFolder + "BinOutput") || !fileExists(resourcesFolder + "ExcelBinOutput")) { - logger.info("Place a copy of 'GenshinData' in the resources folder."); + logger.info("Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder."); exit = true; } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 666b53b42..477c1ac5b 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -4,8 +4,19 @@ [%d{HH:mm:ss}] [%highlight(%level)] %msg%n + + logs/latest.log + + logs/log.%d{yyyy-MM-dd}_%d{HH}.log.tar.gz + 24 + + + %d{yyyy-MM-dd'T'HH:mm:ss'Z'} - %m%n + + + \ No newline at end of file diff --git a/start.cmd b/start.cmd new file mode 100644 index 000000000..8c525dfc8 --- /dev/null +++ b/start.cmd @@ -0,0 +1,164 @@ +@rem +@rem Copyright (C) 2002-2022 MlgmXyysd All Rights Reserved. +@rem + +@if "%DEBUG%" == "" echo off +pushd %~dp0 +set CUR_PATH=%~dp0 +title Grasscutter +call :LOG [INFO] Welcome to Grasscutter +call :LOG [INFO] To proper exit this console, use [Ctrl + C] and enter N not Y. +call :LOG [INFO] +call :LOG [INFO] Initializing... + +set CONFIG=start_config + +set JAVA_PATH=DO_NOT_CHECK_PATH +set MITMDUMP_PATH=DO_NOT_CHECK_PATH +set MONGODB_PATH=DO_NOT_CHECK_PATH + +set SERVER_JAR_PATH=%CUR_PATH% +set DATABASE_STORAGE_PATH=%CUR_PATH%resources\Database + +set SERVER_JAR_NAME=grasscutter.jar +set PROXY_SCRIPT_NAME=proxy + +if exist "%CUR_PATH%%CONFIG%.cmd" ( + call "%CUR_PATH%%CONFIG%.cmd" >nul 2>nul +) + +if not "%JAVA_PATH%" == "DO_NOT_CHECK_PATH" ( + if not exist "%JAVA_PATH%java.exe" ( + call :LOG [ERROR] Java not found. + goto :EXIT + ) +) else set JAVA_PATH= +if not exist "%SERVER_PATH%grasscutter.jar" ( + call :LOG [ERROR] Server jar not found. + goto :EXIT +) + +@rem mitmproxy not found, server only +if not "%MITMDUMP_PATH%" == "DO_NOT_CHECK_PATH" ( + if not exist "%MITMDUMP_PATH%mitmdump.exe" ( + call :LOG [WARN] mitmdump not found, server only mode. + goto :SERVER + ) +) else set MITMDUMP_PATH= +@rem proxy script not found, server only +if not exist "%PROXY_SCRIPT_NAME%.py" ( + if not exist "%PROXY_SCRIPT_NAME%.pyc" ( + call :LOG [WARN] Missing proxy script or compiled proxy script, server only mode. + goto :SERVER + ) else set PROXY_SCRIPT_NAME=%PROXY_SCRIPT_NAME%.pyc +) else set PROXY_SCRIPT_NAME=%PROXY_SCRIPT_NAME%.py + +:PROXY +@rem UAC Administrator privileges +>nul 2>&1 reg query "HKU\S-1-5-19" || ( + call :LOG [WARN] Currently running with non Administrator privileges, raising... + echo set UAC = CreateObject^("Shell.Application"^) > "%temp%\UAC.vbs" + echo UAC.ShellExecute "%~f0","%1","","runas",1 >> "%temp%\UAC.vbs" + "%temp%\UAC.vbs" + del /f /q "%temp%\UAC.vbs" >nul 2>nul + exit /b +) + +call :LOG [INFO] Starting proxy daemon... + +set PROXY=true + +@rem Store original proxy settings +for /f "tokens=2*" %%a in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable 2^>nul') do set "ORIG_PROXY_ENABLE=%%b" +for /f "tokens=2*" %%a in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer 2^>nul') do set "ORIG_PROXY_SERVER=%%b" + +@rem TODO: External proxy when ORIG_PROXY_ENABLE == 0x1 +echo set ws = createobject("wscript.shell") > "%temp%\proxy.vbs" +if not "%MITMDUMP_PATH%" == "" ( +echo ws.currentdirectory = "%MITMDUMP_PATH%" >> "%temp%\proxy.vbs" +) +echo ws.run "cmd /c mitmdump.exe -s "^&chr(34)^&"%PROXY_SCRIPT_NAME%"^&chr(34)^&" -k",0 >> "%temp%\proxy.vbs" +"%temp%\proxy.vbs" +del /f /q "%temp%\proxy.vbs" >nul 2>nul + +@rem CA certificate for HTTPS scheme +call :LOG [INFO] Waiting for CA certificate generation... +set CA_CERT_FILE="%USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer" + +set /a TIMEOUT_COUNT=0 + +:CERT_CA_CHECK +if not exist %CA_CERT_FILE% ( + timeout /t 1 >nul 2>nul + set /a TIMEOUT_COUNT+=1 + goto CERT_CA_CHECK +) +:EXTRA_TIMEOUT +if %TIMEOUT_COUNT% LEQ 2 ( + timeout /t 1 >nul 2>nul + set /a TIMEOUT_COUNT+=1 + goto EXTRA_TIMEOUT +) +call :LOG [INFO] Adding CA certificate to store... +certutil -addstore root %CA_CERT_FILE% >nul 2>nul + +call :LOG [INFO] Setting up network proxy... +reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f >nul 2>nul +reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer /d "127.0.0.1:8080" /f >nul 2>nul + +:SERVER +if not "%MONGODB_PATH%" == "DO_NOT_CHECK_PATH" ( + if not exist "%MONGODB_PATH%mongod.exe" ( + call :LOG [WARN] MongoDB daemon not found, server only mode. + goto :GAME + ) +) else set MONGODB_PATH= +call :LOG [INFO] Starting MongoDB daemon... +set DATABASE=true + +mkdir "%DATABASE_STORAGE_PATH%" >nul 2>nul + +echo set ws = createobject("wscript.shell") > "%temp%\db.vbs" +if not "%MONGODB_PATH%" == "" ( +echo ws.currentdirectory = "%MONGODB_PATH%" >> "%temp%\db.vbs" +) +echo ws.run "cmd /c mongod.exe --dbpath "^&chr(34)^&"%DATABASE_STORAGE_PATH%"^&chr(34)^&"",0 >> "%temp%\db.vbs" +"%temp%\db.vbs" +del /f /q "%temp%\db.vbs" >nul 2>nul + +:GAME +call :LOG [INFO] Starting server... +"%JAVA_PATH%java.exe" -jar "%SERVER_PATH%grasscutter.jar" +call :LOG [INFO] Server stopped + +:EXIT +if "%DATABASE%" == "" ( + call :LOG [INFO] MongoDB daemon not started, no need to clean up. +) else ( + call :LOG [INFO] Shutting down MongoDB daemon... + taskkill /t /f /im mongod.exe >nul 2>nul +) +if "%PROXY%" == "" ( + call :LOG [INFO] Proxy daemon not started, no need to clean up. +) else ( + call :LOG [INFO] Restoring network settings... + + reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable /t REG_DWORD /d "%ORIG_PROXY_ENABLE%" /f >nul 2>nul + reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer /d "%ORIG_PROXY_SERVER%" /f >nul 2>nul + + call :LOG [INFO] Shutting down proxy daemon... + taskkill /t /f /im mitmdump.exe >nul 2>nul + + call :LOG [INFO] Removing CA certificate... + for /F "tokens=2" %%s in ('certutil -dump %CA_CERT_FILE% ^| findstr ^"^sha1^"') do ( + set SERIAL=%%s + ) + + certutil -delstore root %SERIAL% >nul 2>nul +) + +call :LOG [INFO] See you again :) +goto :EOF + +:LOG +echo [%time:~0,8%] %* \ No newline at end of file diff --git a/start_config.cmd b/start_config.cmd new file mode 100644 index 000000000..575e4d8df --- /dev/null +++ b/start_config.cmd @@ -0,0 +1,25 @@ +@rem +@rem Copyright (C) 2002-2022 MlgmXyysd All Rights Reserved. +@rem + +@echo off +pushd %~dp0 +set CUR_PATH=%~dp0 + +@rem This will not work if your java or mitmproxy is in a different location, plugin as necessary +@rem this just saves you from changing your PATH + +@rem Executable Path +@rem Note: Fill DO_NOT_CHECK_PATH if you need to run it from PATH +@rem without detecting whether the executable file exists +set JAVA_PATH=C:\Program Files\Java\jdk1.8.0_202\bin\ +set MITMDUMP_PATH=%CUR_PATH% +set MONGODB_PATH=%CUR_PATH% + +@rem Utility Path +set SERVER_JAR_PATH=%CUR_PATH% +set DATABASE_STORAGE_PATH=%CUR_PATH%resources\Database + +@rem Utility Name +set SERVER_JAR_NAME=grasscutter.jar +set PROXY_SCRIPT_NAME=proxy \ No newline at end of file