mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-16 17:05:20 +01:00
Merge branch 'development' into java-16
This commit is contained in:
24
.github/workflows/build.yml
vendored
Normal file
24
.github/workflows/build.yml
vendored
Normal file
@@ -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
|
||||||
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,6 +47,7 @@ tmp/
|
|||||||
|
|
||||||
# Grasscutter
|
# Grasscutter
|
||||||
resources/*
|
resources/*
|
||||||
|
logs/*
|
||||||
data/AbilityEmbryos.json
|
data/AbilityEmbryos.json
|
||||||
data/OpenConfig.json
|
data/OpenConfig.json
|
||||||
proto/auto/
|
proto/auto/
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -12,6 +12,9 @@ A WIP server reimplementation for *some anime game* 2.3-2.6
|
|||||||
* Friends list
|
* Friends list
|
||||||
* Co-op *partially* work
|
* Co-op *partially* work
|
||||||
# Quick setup guide
|
# Quick setup guide
|
||||||
|
### Note
|
||||||
|
* If you update from an older version, delete `config.json` for regeneration
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
* JDK-8u202 ([mirror link](https://mirrors.huaweicloud.com/java/jdk/8u202-b08/) since Oracle required an account to download old builds)
|
* 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+)
|
* Mongodb (recommended 4.0+)
|
||||||
@@ -26,7 +29,7 @@ A WIP server reimplementation for *some anime game* 2.3-2.6
|
|||||||
### Connecting with the client
|
### Connecting with the client
|
||||||
½. Create an account using *server console command* below
|
½. Create an account using *server console command* below
|
||||||
1. Run a proxy daemon: (choose either one)
|
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).
|
- 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)
|
- [Hosts file](https://github.com/Melledy/Grasscutter/wiki/Running#traffic-route-map)
|
||||||
2. Trust CA certificate:
|
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
|
* or you can use `run.cmd` to start Server & Proxy daemon with one click
|
||||||
|
|
||||||
# Grasscutter commands
|
# 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.
|
`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
|
`spawn [monster id] [level] [amount]`
|
||||||
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]`
|
`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/).*
|
*More commands will be updated in the [wiki](https://github.com/Melledy/Grasscutter/wiki/).*
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"bannerType": "EVENT",
|
"bannerType": "EVENT",
|
||||||
"prefabPath": "GachaShowPanel_A079",
|
"prefabPath": "GachaShowPanel_A079",
|
||||||
"previewPrefabPath": "UI_Tab_GachaShowPanel_A079",
|
"previewPrefabPath": "UI_Tab_GachaShowPanel_A079",
|
||||||
"titlePath": "UI_GACHA_SHOW_PANEL_A079_TITLE",
|
"titlePath": "UI_GACHA_SHOW_PANEL_A048_TITLE",
|
||||||
"costItem": 223,
|
"costItem": 223,
|
||||||
"beginTime": 0,
|
"beginTime": 0,
|
||||||
"endTime": 1924992000,
|
"endTime": 1924992000,
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"bannerType": "WEAPON",
|
"bannerType": "WEAPON",
|
||||||
"prefabPath": "GachaShowPanel_A080",
|
"prefabPath": "GachaShowPanel_A080",
|
||||||
"previewPrefabPath": "UI_Tab_GachaShowPanel_A080",
|
"previewPrefabPath": "UI_Tab_GachaShowPanel_A080",
|
||||||
"titlePath": "UI_GACHA_SHOW_PANEL_A080_TITLE",
|
"titlePath": "UI_GACHA_SHOW_PANEL_A021_TITLE",
|
||||||
"costItem": 223,
|
"costItem": 223,
|
||||||
"beginTime": 0,
|
"beginTime": 0,
|
||||||
"endTime": 1924992000,
|
"endTime": 1924992000,
|
||||||
|
|||||||
13
proxy.py
13
proxy.py
@@ -16,12 +16,14 @@
|
|||||||
# - mitmdump from mitmproxy
|
# - mitmdump from mitmproxy
|
||||||
#
|
#
|
||||||
# @author MlgmXyysd
|
# @author MlgmXyysd
|
||||||
# @version 1.0
|
# @version 1.1
|
||||||
#
|
#
|
||||||
##
|
##
|
||||||
|
|
||||||
from mitmproxy import http
|
from mitmproxy import http
|
||||||
|
from proxy_config import USE_SSL
|
||||||
from proxy_config import REMOTE_HOST
|
from proxy_config import REMOTE_HOST
|
||||||
|
from proxy_config import REMOTE_PORT
|
||||||
|
|
||||||
class MlgmXyysd_Genshin_Impact_Proxy:
|
class MlgmXyysd_Genshin_Impact_Proxy:
|
||||||
|
|
||||||
@@ -55,12 +57,19 @@ class MlgmXyysd_Genshin_Impact_Proxy:
|
|||||||
"minor-api.mihoyo.com",
|
"minor-api.mihoyo.com",
|
||||||
"public-data-api.mihoyo.com",
|
"public-data-api.mihoyo.com",
|
||||||
"uspider.yuanshen.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:
|
def request(self, flow: http.HTTPFlow) -> None:
|
||||||
if flow.request.host in self.LIST_DOMAINS:
|
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.host = REMOTE_HOST
|
||||||
|
flow.request.port = REMOTE_PORT
|
||||||
|
|
||||||
addons = [
|
addons = [
|
||||||
MlgmXyysd_Genshin_Impact_Proxy()
|
MlgmXyysd_Genshin_Impact_Proxy()
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
# This can also be replaced with another IP address.
|
# This can also be replaced with another IP address.
|
||||||
REMOTE_HOST = "localhost"
|
USE_SSL = True
|
||||||
|
REMOTE_HOST = "127.0.0.1"
|
||||||
|
REMOTE_PORT = 443
|
||||||
115
run.cmd
115
run.cmd
@@ -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%] %*
|
|
||||||
@@ -1,19 +1,8 @@
|
|||||||
package emu.grasscutter;
|
package emu.grasscutter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public final class Config {
|
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 DatabaseUrl = "mongodb://localhost:27017";
|
||||||
public String DatabaseCollection = "grasscutter";
|
public String DatabaseCollection = "grasscutter";
|
||||||
@@ -23,26 +12,55 @@ public final class Config {
|
|||||||
public String PACKETS_FOLDER = "./packets/";
|
public String PACKETS_FOLDER = "./packets/";
|
||||||
public String DUMPS_FOLDER = "./dumps/";
|
public String DUMPS_FOLDER = "./dumps/";
|
||||||
public String KEY_FOLDER = "./keys/";
|
public String KEY_FOLDER = "./keys/";
|
||||||
public boolean LOG_PACKETS = false;
|
|
||||||
|
|
||||||
public GameRates Game = new GameRates();
|
public String RunMode = "HYBRID"; // HYBRID, DISPATCH_ONLY, GAME_ONLY
|
||||||
public ServerOptions ServerOptions = new ServerOptions();
|
public GameServerOptions GameServer = new GameServerOptions();
|
||||||
|
public DispatchServerOptions DispatchServer = new DispatchServerOptions();
|
||||||
|
|
||||||
public GameRates getGameRates() {
|
public GameServerOptions getGameServerOptions() {
|
||||||
return Game;
|
return GameServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerOptions getServerOptions() {
|
public DispatchServerOptions getDispatchOptions() { return DispatchServer; }
|
||||||
return ServerOptions;
|
|
||||||
|
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 static class GameRates {
|
public static class GameServerOptions {
|
||||||
public float ADVENTURE_EXP_RATE = 1.0f;
|
public String Name = "Test";
|
||||||
public float MORA_RATE = 1.0f;
|
public String Ip = "0.0.0.0";
|
||||||
public float DOMAIN_DROP_RATE = 1.0f;
|
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 static class ServerOptions {
|
|
||||||
public int InventoryLimitWeapon = 2000;
|
public int InventoryLimitWeapon = 2000;
|
||||||
public int InventoryLimitRelic = 2000;
|
public int InventoryLimitRelic = 2000;
|
||||||
public int InventoryLimitMaterial = 2000;
|
public int InventoryLimitMaterial = 2000;
|
||||||
@@ -54,6 +72,15 @@ public final class Config {
|
|||||||
public boolean WatchGacha = false;
|
public boolean WatchGacha = false;
|
||||||
public int[] WelcomeEmotes = {2007, 1002, 4010};
|
public int[] WelcomeEmotes = {2007, 1002, 4010};
|
||||||
public String WelcomeMotd = "Welcome to Grasscutter emu";
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,11 +73,26 @@ public final class Grasscutter {
|
|||||||
DatabaseManager.initialize();
|
DatabaseManager.initialize();
|
||||||
|
|
||||||
// Start servers.
|
// Start servers.
|
||||||
dispatchServer = new DispatchServer();
|
if(getConfig().RunMode.equalsIgnoreCase("HYBRID")) {
|
||||||
dispatchServer.start();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
gameServer = new GameServer(new InetSocketAddress(getConfig().GameServerIp, getConfig().GameServerPort));
|
|
||||||
gameServer.start();
|
|
||||||
|
|
||||||
// Open console.
|
// Open console.
|
||||||
startConsole();
|
startConsole();
|
||||||
@@ -86,8 +101,10 @@ public final class Grasscutter {
|
|||||||
public static void loadConfig() {
|
public static void loadConfig() {
|
||||||
try (FileReader file = new FileReader(configFile)) {
|
try (FileReader file = new FileReader(configFile)) {
|
||||||
config = gson.fromJson(file, Config.class);
|
config = gson.fromJson(file, Config.class);
|
||||||
|
saveConfig();
|
||||||
} catch (Exception e) {
|
} 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))) {
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
|
||||||
while ((input = br.readLine()) != null) {
|
while ((input = br.readLine()) != null) {
|
||||||
try {
|
try {
|
||||||
|
if(getConfig().RunMode.equalsIgnoreCase("DISPATCH_ONLY")) {
|
||||||
|
getLogger().error("Commands are not supported in dispatch only mode");
|
||||||
|
return;
|
||||||
|
}
|
||||||
CommandMap.getInstance().invoke(null, input);
|
CommandMap.getInstance().invoke(null, input);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Grasscutter.getLogger().error("Command error: " + e.getMessage());
|
Grasscutter.getLogger().error("Command error: ");
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public final class AccountCommand implements CommandHandler {
|
|||||||
CommandHandler.sendMessage(null, "Account already exists.");
|
CommandHandler.sendMessage(null, "Account already exists.");
|
||||||
return;
|
return;
|
||||||
} else {
|
} 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.addPermission("*"); // Grant the player superuser permissions.
|
||||||
account.save(); // Save account to database.
|
account.save(); // Save account to database.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,17 @@ public final class ChangeSceneCommand implements CommandHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
int sceneId = Integer.parseInt(args.get(0));
|
int sceneId = Integer.parseInt(args.get(0));
|
||||||
boolean result = sender.getWorld().transferPlayerToScene(sender, sceneId, sender.getPos());
|
|
||||||
|
|
||||||
|
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);
|
CommandHandler.sendMessage(sender, "Changed to scene " + sceneId);
|
||||||
|
|
||||||
if (!result) {
|
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) {
|
} catch (Exception e) {
|
||||||
CommandHandler.sendMessage(sender, "Usage: changescene <scene id>");
|
CommandHandler.sendMessage(sender, "Usage: changescene <scene id>");
|
||||||
|
|||||||
@@ -95,18 +95,19 @@ public final class GiveCommand implements CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void item(GenshinPlayer player, ItemData itemData, int amount) {
|
private void item(GenshinPlayer player, ItemData itemData, int amount) {
|
||||||
GenshinItem genshinItem = new GenshinItem(itemData);
|
|
||||||
if (itemData.isEquip()) {
|
if (itemData.isEquip()) {
|
||||||
List<GenshinItem> items = new LinkedList<>();
|
List<GenshinItem> items = new LinkedList<>();
|
||||||
for (int i = 0; i < amount; i++) {
|
for (int i = 0; i < amount; i++) {
|
||||||
items.add(genshinItem);
|
items.add(new GenshinItem(itemData));
|
||||||
}
|
}
|
||||||
player.getInventory().addItems(items);
|
player.getInventory().addItems(items);
|
||||||
player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop));
|
player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop));
|
||||||
} else {
|
} else {
|
||||||
|
GenshinItem genshinItem = new GenshinItem(itemData);
|
||||||
genshinItem.setCount(amount);
|
genshinItem.setCount(amount);
|
||||||
player.getInventory().addItem(genshinItem);
|
player.getInventory().addItem(genshinItem);
|
||||||
player.sendPacket(new PacketItemAddHintNotify(genshinItem, ActionReason.SubfieldDrop));
|
player.sendPacket(new PacketItemAddHintNotify(genshinItem, ActionReason.SubfieldDrop));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<String> 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ public final class KickCommand implements CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sender != null) {
|
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()));
|
CommandHandler.sendMessage(sender, String.format("Kicking player [%s:%s]", target, targetPlayer.getAccount().getUsername()));
|
||||||
|
|
||||||
|
|||||||
@@ -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<String> args) {
|
||||||
|
Map<Integer, GenshinPlayer> 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<Integer, GenshinPlayer> entry : playersMap.entrySet()) {
|
||||||
|
playerSet.append(entry.getValue().getNickname());
|
||||||
|
playerSet.append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
String players = playerSet.toString();
|
||||||
|
|
||||||
|
CommandHandler.sendMessage(sender, players.substring(0, players.length() - 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Command(label = "setstats", usage = "setstats|stats <stat> <value>",
|
@Command(label = "setstats", usage = "setstats|stats <stat> <value>",
|
||||||
aliases = {"stats"})
|
description = "Set fight property for your current active character", aliases = {"stats"}, permission = "player.setstats")
|
||||||
public final class SetStatsCommand implements CommandHandler {
|
public final class SetStatsCommand implements CommandHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -20,6 +20,11 @@ public final class SetStatsCommand implements CommandHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.size() < 2){
|
||||||
|
CommandHandler.sendMessage(sender, "Usage: setstats|stats <stat> <value>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String stat = args.get(0);
|
String stat = args.get(0);
|
||||||
switch (stat) {
|
switch (stat) {
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -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 <talentID> <value>",
|
||||||
|
description = "Set talent level for your current active character", permission = "player.settalent")
|
||||||
|
public class TalentCommand implements CommandHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(GenshinPlayer sender, List<String> 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 <talentID> <value>");
|
||||||
|
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 <talentID> <value>");
|
||||||
|
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 <talentID> <value>");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import emu.grasscutter.server.packet.send.PacketSceneAreaWeatherNotify;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Command(label = "weather", usage = "weather <weatherId>",
|
@Command(label = "weather", usage = "weather <weatherId> [climateId]",
|
||||||
description = "Changes the weather.", aliases = {"w"}, permission = "player.weather")
|
description = "Changes the weather.", aliases = {"w"}, permission = "player.weather")
|
||||||
public final class WeatherCommand implements CommandHandler {
|
public final class WeatherCommand implements CommandHandler {
|
||||||
|
|
||||||
@@ -20,20 +20,22 @@ public final class WeatherCommand implements CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.size() < 1) {
|
if (args.size() < 1) {
|
||||||
CommandHandler.sendMessage(sender, "Usage: weather <weatherId>");
|
CommandHandler.sendMessage(sender, "Usage: weather <weatherId> [climateId]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int weatherId = Integer.parseInt(args.get(0));
|
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().setClimate(climate);
|
||||||
sender.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(sender));
|
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) {
|
} catch (NumberFormatException ignored) {
|
||||||
CommandHandler.sendMessage(sender, "Invalid weather ID.");
|
CommandHandler.sendMessage(sender, "Invalid ID.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package emu.grasscutter.data;
|
package emu.grasscutter.data;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
@@ -54,6 +56,10 @@ public class GenshinData {
|
|||||||
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>();
|
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>();
|
||||||
|
|
||||||
private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>();
|
private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>();
|
||||||
|
private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
private static Map<Integer, List<Integer>> fetters = new HashMap<>();
|
||||||
|
|
||||||
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
|
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
|
||||||
Int2ObjectMap<?> map = null;
|
Int2ObjectMap<?> map = null;
|
||||||
@@ -221,4 +227,17 @@ public class GenshinData {
|
|||||||
public static Int2ObjectMap<SceneData> getSceneDataMap() {
|
public static Int2ObjectMap<SceneData> getSceneDataMap() {
|
||||||
return sceneDataMap;
|
return sceneDataMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Map<Integer, List<Integer>> getFetterDataEntries() {
|
||||||
|
if (fetters.isEmpty()) {
|
||||||
|
fetterDataMap.forEach((k, v) -> {
|
||||||
|
if (!fetters.containsKey(v.getAvatarId())) {
|
||||||
|
fetters.put(v.getAvatarId(), new ArrayList<>());
|
||||||
|
}
|
||||||
|
fetters.get(v.getAvatarId()).add(k);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetters;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,13 @@ public class ResourceLoader {
|
|||||||
|
|
||||||
private static void loadScenePoints() {
|
private static void loadScenePoints() {
|
||||||
Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)");
|
Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)");
|
||||||
File folder = new File(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<ScenePointEntry> scenePointList = new ArrayList<>();
|
List<ScenePointEntry> scenePointList = new ArrayList<>();
|
||||||
for (File file : folder.listFiles()) {
|
for (File file : folder.listFiles()) {
|
||||||
ScenePointConfig config = null;
|
ScenePointConfig config = null;
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ public class AvatarData extends GenshinResource {
|
|||||||
private AvatarSkillDepotData skillDepot;
|
private AvatarSkillDepotData skillDepot;
|
||||||
private IntList abilities;
|
private IntList abilities;
|
||||||
|
|
||||||
|
private List<Integer> fetters;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getId(){
|
public int getId(){
|
||||||
return this.Id;
|
return this.Id;
|
||||||
@@ -193,10 +195,17 @@ public class AvatarData extends GenshinResource {
|
|||||||
return abilities;
|
return abilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Integer> getFetters() {
|
||||||
|
return fetters;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoad() {
|
public void onLoad() {
|
||||||
this.skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.SkillDepotId);
|
this.skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.SkillDepotId);
|
||||||
|
|
||||||
|
// Get fetters from GenshinData
|
||||||
|
this.fetters = GenshinData.getFetterDataEntries().get(this.Id);
|
||||||
|
|
||||||
int size = GenshinData.getAvatarCurveDataMap().size();
|
int size = GenshinData.getAvatarCurveDataMap().size();
|
||||||
this.hpGrowthCurve = new float[size];
|
this.hpGrowthCurve = new float[size];
|
||||||
this.attackGrowthCurve = new float[size];
|
this.attackGrowthCurve = new float[size];
|
||||||
|
|||||||
24
src/main/java/emu/grasscutter/data/def/FetterData.java
Normal file
24
src/main/java/emu/grasscutter/data/def/FetterData.java
Normal file
@@ -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() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ public final class DatabaseHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void saveAccount(Account account) {
|
public static void saveAccount(Account account) {
|
||||||
DatabaseManager.getDatastore().save(account);
|
DatabaseManager.getAccountDatastore().save(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Account getAccountByName(String username) {
|
public static Account getAccountByName(String username) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package emu.grasscutter.database;
|
package emu.grasscutter.database;
|
||||||
|
|
||||||
|
import com.mongodb.MongoClientURI;
|
||||||
import com.mongodb.MongoCommandException;
|
import com.mongodb.MongoCommandException;
|
||||||
import com.mongodb.client.MongoClient;
|
import com.mongodb.client.MongoClient;
|
||||||
import com.mongodb.client.MongoClients;
|
import com.mongodb.client.MongoClients;
|
||||||
@@ -18,7 +19,12 @@ import emu.grasscutter.game.friends.Friendship;
|
|||||||
import emu.grasscutter.game.inventory.GenshinItem;
|
import emu.grasscutter.game.inventory.GenshinItem;
|
||||||
|
|
||||||
public final class DatabaseManager {
|
public final class DatabaseManager {
|
||||||
|
|
||||||
|
private static MongoClient mongoClient;
|
||||||
|
private static MongoClient dispatchMongoClient;
|
||||||
|
|
||||||
private static Datastore datastore;
|
private static Datastore datastore;
|
||||||
|
private static Datastore dispatchDatastore;
|
||||||
|
|
||||||
private static final Class<?>[] mappedClasses = new Class<?>[] {
|
private static final Class<?>[] mappedClasses = new Class<?>[] {
|
||||||
DatabaseCounter.class, Account.class, GenshinPlayer.class, GenshinAvatar.class, GenshinItem.class, Friendship.class
|
DatabaseCounter.class, Account.class, GenshinPlayer.class, GenshinAvatar.class, GenshinItem.class, Friendship.class
|
||||||
@@ -32,6 +38,16 @@ public final class DatabaseManager {
|
|||||||
return getDatastore().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() {
|
public static void initialize() {
|
||||||
// Initialize
|
// Initialize
|
||||||
MongoClient mongoClient = MongoClients.create(Grasscutter.getConfig().DatabaseUrl);
|
MongoClient mongoClient = MongoClients.create(Grasscutter.getConfig().DatabaseUrl);
|
||||||
@@ -60,6 +76,28 @@ public final class DatabaseManager {
|
|||||||
datastore.ensureIndexes();
|
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<String> collections = dispatchDatastore.getDatabase().listCollectionNames();
|
||||||
|
for (String name : collections) {
|
||||||
|
dispatchDatastore.getDatabase().getCollection(name).dropIndexes();
|
||||||
|
}
|
||||||
|
// Add back indexes
|
||||||
|
dispatchDatastore.ensureIndexes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized int getNextId(Class<?> c) {
|
public static synchronized int getNextId(Class<?> c) {
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import emu.grasscutter.utils.Utils;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bson.Document;
|
||||||
|
|
||||||
|
import com.mongodb.DBObject;
|
||||||
|
|
||||||
@Entity(value = "accounts", useDiscriminator = false)
|
@Entity(value = "accounts", useDiscriminator = false)
|
||||||
public class Account {
|
public class Account {
|
||||||
@Id private String id;
|
@Id private String id;
|
||||||
@@ -17,7 +21,7 @@ public class Account {
|
|||||||
private String username;
|
private String username;
|
||||||
private String password; // Unused for now
|
private String password; // Unused for now
|
||||||
|
|
||||||
private int playerId;
|
@AlsoLoad("playerUid") private int playerId;
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
private String token;
|
private String token;
|
||||||
@@ -61,7 +65,7 @@ public class Account {
|
|||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPlayerId() {
|
public int getPlayerUid() {
|
||||||
return this.playerId;
|
return this.playerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,12 +122,11 @@ public class Account {
|
|||||||
DatabaseHelper.saveAccount(this);
|
DatabaseHelper.saveAccount(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Find an implementation for this. Morphia 2.0 breaks this method for some reason?
|
@PreLoad
|
||||||
// @PreLoad
|
public void onLoad(Document document) {
|
||||||
// public void onLoad(DBObject object) {
|
// Grant the superuser permissions to accounts created before the permissions update
|
||||||
// // Grant the superuser permissions to accounts created before the permissions update
|
if (!document.containsKey("permissions")) {
|
||||||
// if (!object.containsField("permissions")) {
|
this.addPermission("*");
|
||||||
// this.addPermission("*");
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo;
|
|||||||
import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason;
|
import emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason;
|
||||||
import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo;
|
import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo;
|
||||||
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
|
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.GameServer;
|
||||||
import emu.grasscutter.server.game.GameSession;
|
import emu.grasscutter.server.game.GameSession;
|
||||||
import emu.grasscutter.server.packet.send.PacketAbilityInvocationsNotify;
|
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.PacketPlayerPropNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketPlayerStoreNotify;
|
import emu.grasscutter.server.packet.send.PacketPlayerStoreNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
|
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.PacketSetNameCardRsp;
|
||||||
import emu.grasscutter.server.packet.send.PacketStoreWeightLimitNotify;
|
import emu.grasscutter.server.packet.send.PacketStoreWeightLimitNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketUnlockNameCardNotify;
|
import emu.grasscutter.server.packet.send.PacketUnlockNameCardNotify;
|
||||||
|
import emu.grasscutter.server.packet.send.PacketWorldPlayerLocationNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
|
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
|
||||||
import emu.grasscutter.utils.Position;
|
import emu.grasscutter.utils.Position;
|
||||||
|
|
||||||
@@ -101,6 +104,7 @@ public class GenshinPlayer {
|
|||||||
@Transient private int enterSceneToken;
|
@Transient private int enterSceneToken;
|
||||||
@Transient private SceneLoadState sceneState;
|
@Transient private SceneLoadState sceneState;
|
||||||
@Transient private boolean hasSentAvatarDataNotify;
|
@Transient private boolean hasSentAvatarDataNotify;
|
||||||
|
@Transient private long nextSendPlayerLocTime = 0;
|
||||||
|
|
||||||
@Transient private final Int2ObjectMap<CoopRequest> coopRequests;
|
@Transient private final Int2ObjectMap<CoopRequest> coopRequests;
|
||||||
@Transient private final InvokeHandler<CombatInvokeEntry> combatInvokeHandler;
|
@Transient private final InvokeHandler<CombatInvokeEntry> combatInvokeHandler;
|
||||||
@@ -121,6 +125,12 @@ public class GenshinPlayer {
|
|||||||
}
|
}
|
||||||
this.properties.put(prop.getId(), 0);
|
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.setSceneId(3);
|
||||||
this.setRegionId(1);
|
this.setRegionId(1);
|
||||||
this.sceneState = SceneLoadState.NONE;
|
this.sceneState = SceneLoadState.NONE;
|
||||||
@@ -140,11 +150,6 @@ public class GenshinPlayer {
|
|||||||
this.nickname = "Traveler";
|
this.nickname = "Traveler";
|
||||||
this.signature = "";
|
this.signature = "";
|
||||||
this.teamManager = new TeamManager(this);
|
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_PLAYER_LEVEL, 1);
|
||||||
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
|
this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1);
|
||||||
this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50);
|
this.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 50);
|
||||||
@@ -288,7 +293,7 @@ public class GenshinPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private float getExpModifier() {
|
private float getExpModifier() {
|
||||||
return Grasscutter.getConfig().getGameRates().ADVENTURE_EXP_RATE;
|
return Grasscutter.getConfig().getGameServerOptions().getGameRates().ADVENTURE_EXP_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Affected by exp rate
|
// Affected by exp rate
|
||||||
@@ -347,7 +352,6 @@ public class GenshinPlayer {
|
|||||||
public PlayerProfile getProfile() {
|
public PlayerProfile getProfile() {
|
||||||
if (this.playerProfile == null) {
|
if (this.playerProfile == null) {
|
||||||
this.playerProfile = new PlayerProfile(this);
|
this.playerProfile = new PlayerProfile(this);
|
||||||
this.save();
|
|
||||||
}
|
}
|
||||||
return playerProfile;
|
return playerProfile;
|
||||||
}
|
}
|
||||||
@@ -654,6 +658,13 @@ public class GenshinPlayer {
|
|||||||
return social;
|
return social;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WorldPlayerLocationInfo getWorldPlayerLocationInfo() {
|
||||||
|
return WorldPlayerLocationInfo.newBuilder()
|
||||||
|
.setSceneId(this.getSceneId())
|
||||||
|
.setPlayerLoc(this.getPlayerLocationInfo())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
public PlayerLocationInfo getPlayerLocationInfo() {
|
public PlayerLocationInfo getPlayerLocationInfo() {
|
||||||
return PlayerLocationInfo.newBuilder()
|
return PlayerLocationInfo.newBuilder()
|
||||||
.setUid(this.getUid())
|
.setUid(this.getUid())
|
||||||
@@ -679,10 +690,23 @@ public class GenshinPlayer {
|
|||||||
}
|
}
|
||||||
// Ping
|
// Ping
|
||||||
if (this.getWorld() != null) {
|
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
|
@PostLoad
|
||||||
private void onLoad() {
|
private void onLoad() {
|
||||||
this.getTeamManager().setPlayer(this);
|
this.getTeamManager().setPlayer(this);
|
||||||
@@ -696,12 +720,8 @@ public class GenshinPlayer {
|
|||||||
// Make sure these exist
|
// Make sure these exist
|
||||||
if (this.getTeamManager() == null) {
|
if (this.getTeamManager() == null) {
|
||||||
this.teamManager = new TeamManager(this);
|
this.teamManager = new TeamManager(this);
|
||||||
} if (this.getGachaInfo() == null) {
|
} if (this.getProfile().getUid() == 0) {
|
||||||
this.gachaInfo = new PlayerGachaInfo();
|
this.getProfile().syncWithCharacter(this);
|
||||||
} if (this.nameCardList == null) {
|
|
||||||
this.nameCardList = new HashSet<>();
|
|
||||||
} if (this.costumeList == null) {
|
|
||||||
this.costumeList = new HashSet<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if player object exists in server
|
// Check if player object exists in server
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class GenshinScene {
|
|||||||
|
|
||||||
private int time;
|
private int time;
|
||||||
private ClimateType climate;
|
private ClimateType climate;
|
||||||
|
private int weather;
|
||||||
|
|
||||||
public GenshinScene(World world, SceneData sceneData) {
|
public GenshinScene(World world, SceneData sceneData) {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
@@ -89,10 +90,18 @@ public class GenshinScene {
|
|||||||
return climate;
|
return climate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getWeather() {
|
||||||
|
return weather;
|
||||||
|
}
|
||||||
|
|
||||||
public void setClimate(ClimateType climate) {
|
public void setClimate(ClimateType climate) {
|
||||||
this.climate = climate;
|
this.climate = climate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setWeather(int weather) {
|
||||||
|
this.weather = weather;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isInScene(GenshinEntity entity) {
|
public boolean isInScene(GenshinEntity entity) {
|
||||||
return this.entities.containsKey(entity.getId());
|
return this.entities.containsKey(entity.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class TeamInfo {
|
|||||||
|
|
||||||
public TeamInfo() {
|
public TeamInfo() {
|
||||||
this.name = "";
|
this.name = "";
|
||||||
this.avatars = new ArrayList<>(Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeam);
|
this.avatars = new ArrayList<>(Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -39,7 +39,7 @@ public class TeamInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean addAvatar(GenshinAvatar avatar) {
|
public boolean addAvatar(GenshinAvatar avatar) {
|
||||||
if (size() >= Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeam || contains(avatar)) {
|
if (size() >= Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam || contains(avatar)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ public class TeamInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void copyFrom(TeamInfo team) {
|
public void copyFrom(TeamInfo team) {
|
||||||
copyFrom(team, Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeam);
|
copyFrom(team, Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyFrom(TeamInfo team, int maxTeamSize) {
|
public void copyFrom(TeamInfo team, int maxTeamSize) {
|
||||||
|
|||||||
@@ -166,13 +166,13 @@ public class TeamManager {
|
|||||||
|
|
||||||
public int getMaxTeamSize() {
|
public int getMaxTeamSize() {
|
||||||
if (getPlayer().isInMultiplayer()) {
|
if (getPlayer().isInMultiplayer()) {
|
||||||
int max = Grasscutter.getConfig().getServerOptions().MaxAvatarsInTeamMultiplayer;
|
int max = Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeamMultiplayer;
|
||||||
if (getPlayer().getWorld().getHost() == this.getPlayer()) {
|
if (getPlayer().getWorld().getHost() == this.getPlayer()) {
|
||||||
return Math.max(1, (int) Math.ceil(max / (double) getWorld().getPlayerCount()));
|
return Math.max(1, (int) Math.ceil(max / (double) getWorld().getPlayerCount()));
|
||||||
}
|
}
|
||||||
return Math.max(1, (int) Math.floor(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
|
// Methods
|
||||||
|
|||||||
@@ -207,13 +207,15 @@ public class World implements Iterable<GenshinPlayer> {
|
|||||||
this.getScenes().remove(scene.getId());
|
this.getScenes().remove(scene.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean forceTransferPlayerToScene(GenshinPlayer player, int sceneId, Position pos) {
|
public boolean transferPlayerToScene(GenshinPlayer player, int sceneId, Position pos) {
|
||||||
// Forces the client to reload the scene map to prevent the player from falling off the map.
|
|
||||||
if (GenshinData.getSceneDataMap().get(sceneId) == null) {
|
if (GenshinData.getSceneDataMap().get(sceneId) == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Integer oldSceneId = null;
|
||||||
|
|
||||||
if (player.getScene() != null) {
|
if (player.getScene() != null) {
|
||||||
|
oldSceneId = player.getScene().getId();
|
||||||
player.getScene().removePlayer(player);
|
player.getScene().removePlayer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,25 +224,11 @@ public class World implements Iterable<GenshinPlayer> {
|
|||||||
player.getPos().set(pos);
|
player.getPos().set(pos);
|
||||||
|
|
||||||
// Teleport packet
|
// Teleport packet
|
||||||
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterSelf, EnterReason.TransPoint, sceneId, pos));
|
if (oldSceneId.equals(sceneId)) {
|
||||||
return true;
|
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterGoto, EnterReason.TransPoint, sceneId, pos));
|
||||||
}
|
} else {
|
||||||
|
player.sendPacket(new PacketPlayerEnterSceneNotify(player, EnterType.EnterJump, EnterReason.TransPoint, sceneId, pos));
|
||||||
public boolean transferPlayerToScene(GenshinPlayer player, int sceneId, Position pos) {
|
|
||||||
if (player.getScene().getId() == sceneId || GenshinData.getSceneDataMap().get(sceneId) == null) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package emu.grasscutter.game.avatar;
|
package emu.grasscutter.game.avatar;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ import dev.morphia.annotations.Indexed;
|
|||||||
import dev.morphia.annotations.PostLoad;
|
import dev.morphia.annotations.PostLoad;
|
||||||
import dev.morphia.annotations.PrePersist;
|
import dev.morphia.annotations.PrePersist;
|
||||||
import dev.morphia.annotations.Transient;
|
import dev.morphia.annotations.Transient;
|
||||||
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.data.GenshinData;
|
import emu.grasscutter.data.GenshinData;
|
||||||
import emu.grasscutter.data.common.FightPropData;
|
import emu.grasscutter.data.common.FightPropData;
|
||||||
import emu.grasscutter.data.custom.OpenConfigEntry;
|
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.inventory.GenshinItem;
|
||||||
import emu.grasscutter.game.props.ElementType;
|
import emu.grasscutter.game.props.ElementType;
|
||||||
import emu.grasscutter.game.props.EntityIdType;
|
import emu.grasscutter.game.props.EntityIdType;
|
||||||
|
import emu.grasscutter.game.props.FetterState;
|
||||||
import emu.grasscutter.game.props.FightProperty;
|
import emu.grasscutter.game.props.FightProperty;
|
||||||
import emu.grasscutter.game.props.PlayerProperty;
|
import emu.grasscutter.game.props.PlayerProperty;
|
||||||
import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo;
|
import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo;
|
||||||
|
import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData;
|
||||||
import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo;
|
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.PacketAvatarEquipChangeNotify;
|
||||||
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify;
|
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify;
|
||||||
import emu.grasscutter.utils.ProtoHelper;
|
import emu.grasscutter.utils.ProtoHelper;
|
||||||
@@ -69,7 +75,9 @@ public class GenshinAvatar {
|
|||||||
|
|
||||||
@Transient private final Int2ObjectMap<GenshinItem> equips;
|
@Transient private final Int2ObjectMap<GenshinItem> equips;
|
||||||
@Transient private final Int2FloatOpenHashMap fightProp;
|
@Transient private final Int2FloatOpenHashMap fightProp;
|
||||||
@Transient private final Set<String> bonusAbilityList;
|
@Transient private Set<String> extraAbilityEmbryos;
|
||||||
|
|
||||||
|
private List<Integer> fetters;
|
||||||
|
|
||||||
private Map<Integer, Integer> skillLevelMap; // Talent levels
|
private Map<Integer, Integer> skillLevelMap; // Talent levels
|
||||||
private Map<Integer, Integer> proudSkillBonusMap; // Talent bonus levels (from const)
|
private Map<Integer, Integer> proudSkillBonusMap; // Talent bonus levels (from const)
|
||||||
@@ -86,8 +94,9 @@ public class GenshinAvatar {
|
|||||||
// Morhpia only!
|
// Morhpia only!
|
||||||
this.equips = new Int2ObjectOpenHashMap<>();
|
this.equips = new Int2ObjectOpenHashMap<>();
|
||||||
this.fightProp = new Int2FloatOpenHashMap();
|
this.fightProp = new Int2FloatOpenHashMap();
|
||||||
this.bonusAbilityList = new HashSet<>();
|
this.extraAbilityEmbryos = new HashSet<>();
|
||||||
this.proudSkillBonusMap = new HashMap<>(); // TODO Move to genshin avatar
|
this.proudSkillBonusMap = new HashMap<>();
|
||||||
|
this.fetters = new ArrayList<>(); // TODO Move to genshin avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
// On creation
|
// On creation
|
||||||
@@ -260,8 +269,16 @@ public class GenshinAvatar {
|
|||||||
return proudSkillBonusMap;
|
return proudSkillBonusMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getBonusAbilityList() {
|
public Set<String> getExtraAbilityEmbryos() {
|
||||||
return bonusAbilityList;
|
return extraAbilityEmbryos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFetterList(List<Integer> fetterList) {
|
||||||
|
this.fetters = fetterList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getFetterList() {
|
||||||
|
return fetters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getCurrentHp() {
|
public float getCurrentHp() {
|
||||||
@@ -347,14 +364,14 @@ public class GenshinAvatar {
|
|||||||
item.setEquipCharacter(this.getAvatarId());
|
item.setEquipCharacter(this.getAvatarId());
|
||||||
item.save();
|
item.save();
|
||||||
|
|
||||||
if (shouldRecalc) {
|
|
||||||
this.recalcStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.getPlayer().hasSentAvatarDataNotify()) {
|
if (this.getPlayer().hasSentAvatarDataNotify()) {
|
||||||
this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item));
|
this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldRecalc) {
|
||||||
|
this.recalcStats();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,11 +388,21 @@ public class GenshinAvatar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void recalcStats() {
|
public void recalcStats() {
|
||||||
|
recalcStats(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recalcStats(boolean forceSendAbilityChange) {
|
||||||
// Setup
|
// Setup
|
||||||
AvatarData data = this.getAvatarData();
|
AvatarData data = this.getAvatarData();
|
||||||
AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(data.getAvatarPromoteId(), this.getPromoteLevel());
|
AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(data.getAvatarPromoteId(), this.getPromoteLevel());
|
||||||
Int2IntOpenHashMap setMap = new Int2IntOpenHashMap();
|
Int2IntOpenHashMap setMap = new Int2IntOpenHashMap();
|
||||||
this.getBonusAbilityList().clear();
|
|
||||||
|
// Extra ability embryos
|
||||||
|
Set<String> prevExtraAbilityEmbryos = this.getExtraAbilityEmbryos();
|
||||||
|
this.extraAbilityEmbryos = new HashSet<>();
|
||||||
|
|
||||||
|
// Fetters
|
||||||
|
this.setFetterList(data.getFetters());
|
||||||
|
|
||||||
// Get hp percent, set to 100% if none
|
// 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);
|
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
|
// Add any skill strings from this affix
|
||||||
this.addToAbilityList(affix.getOpenConfig(), true);
|
this.addToExtraAbilityEmbryos(affix.getOpenConfig(), true);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -505,7 +532,7 @@ public class GenshinAvatar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add any skill strings from this affix
|
// 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
|
// Add any skill strings from this proud skill
|
||||||
this.addToAbilityList(proudSkillData.getOpenConfig(), true);
|
this.addToExtraAbilityEmbryos(proudSkillData.getOpenConfig(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constellations
|
// Constellations
|
||||||
@@ -550,7 +577,7 @@ public class GenshinAvatar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add any skill strings from this constellation
|
// 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
|
// Packet
|
||||||
if (getPlayer() != null && getPlayer().hasSentAvatarDataNotify()) {
|
if (getPlayer() != null && getPlayer().hasSentAvatarDataNotify()) {
|
||||||
|
// Update stats for client
|
||||||
getPlayer().sendPacket(new PacketAvatarFightPropNotify(this));
|
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) {
|
if (openConfig == null || openConfig.length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -586,14 +619,14 @@ public class GenshinAvatar {
|
|||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
if (forceAdd) {
|
if (forceAdd) {
|
||||||
// Add config string to ability skill list anyways
|
// Add config string to ability skill list anyways
|
||||||
this.getBonusAbilityList().add(openConfig);
|
this.getExtraAbilityEmbryos().add(openConfig);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.getAddAbilities() != null) {
|
if (entry.getAddAbilities() != null) {
|
||||||
for (String ability : entry.getAddAbilities()) {
|
for (String ability : entry.getAddAbilities()) {
|
||||||
this.getBonusAbilityList().add(ability);
|
this.getExtraAbilityEmbryos().add(ability);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -668,6 +701,20 @@ public class GenshinAvatar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public AvatarInfo toProto() {
|
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()
|
AvatarInfo.Builder avatarInfo = AvatarInfo.newBuilder()
|
||||||
.setAvatarId(this.getAvatarId())
|
.setAvatarId(this.getAvatarId())
|
||||||
.setGuid(this.getGuid())
|
.setGuid(this.getGuid())
|
||||||
@@ -681,7 +728,7 @@ public class GenshinAvatar {
|
|||||||
.putAllProudSkillExtraLevel(getProudSkillBonusMap())
|
.putAllProudSkillExtraLevel(getProudSkillBonusMap())
|
||||||
.setAvatarType(1)
|
.setAvatarType(1)
|
||||||
.setBornTime(this.getBornTime())
|
.setBornTime(this.getBornTime())
|
||||||
.setFetterInfo(AvatarFetterInfo.newBuilder().setExpLevel(1))
|
.setFetterInfo(avatarFetter)
|
||||||
.setWearingFlycloakId(this.getFlyCloak())
|
.setWearingFlycloakId(this.getFlyCloak())
|
||||||
.setCostumeId(this.getCostume());
|
.setCostumeId(this.getCostume());
|
||||||
|
|
||||||
|
|||||||
@@ -223,8 +223,8 @@ public class EntityAvatar extends GenshinEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add equip abilities
|
// Add equip abilities
|
||||||
if (this.getAvatar().getBonusAbilityList().size() > 0) {
|
if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) {
|
||||||
for (String skill : this.getAvatar().getBonusAbilityList()) {
|
for (String skill : this.getAvatar().getExtraAbilityEmbryos()) {
|
||||||
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
|
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
|
||||||
.setAbilityId(++embryoId)
|
.setAbilityId(++embryoId)
|
||||||
.setAbilityNameHash(Utils.abilityHash(skill))
|
.setAbilityNameHash(Utils.abilityHash(skill))
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ public class FriendsList {
|
|||||||
friendship.setOwner(getPlayer());
|
friendship.setOwner(getPlayer());
|
||||||
|
|
||||||
// Check if friend is online
|
// 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) {
|
if (friend != null) {
|
||||||
// Set friend to online mode
|
// Set friend to online mode
|
||||||
friendship.setFriendProfile(friend);
|
friendship.setFriendProfile(friend);
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class Friendship {
|
|||||||
|
|
||||||
public FriendBrief toProto() {
|
public FriendBrief toProto() {
|
||||||
FriendBrief proto = FriendBrief.newBuilder()
|
FriendBrief proto = FriendBrief.newBuilder()
|
||||||
.setUid(getFriendProfile().getId())
|
.setUid(getFriendProfile().getUid())
|
||||||
.setNickname(getFriendProfile().getName())
|
.setNickname(getFriendProfile().getName())
|
||||||
.setLevel(getFriendProfile().getPlayerLevel())
|
.setLevel(getFriendProfile().getPlayerLevel())
|
||||||
.setAvatar(HeadImage.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
|
.setAvatar(HeadImage.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import emu.grasscutter.utils.Utils;
|
|||||||
public class PlayerProfile {
|
public class PlayerProfile {
|
||||||
@Transient private GenshinPlayer player;
|
@Transient private GenshinPlayer player;
|
||||||
|
|
||||||
private int id;
|
@AlsoLoad("id") private int uid;
|
||||||
private int nameCard;
|
private int nameCard;
|
||||||
private int avatarId;
|
private int avatarId;
|
||||||
private String name;
|
private String name;
|
||||||
@@ -23,12 +23,12 @@ public class PlayerProfile {
|
|||||||
public PlayerProfile() { }
|
public PlayerProfile() { }
|
||||||
|
|
||||||
public PlayerProfile(GenshinPlayer player) {
|
public PlayerProfile(GenshinPlayer player) {
|
||||||
this.id = player.getUid();
|
this.uid = player.getUid();
|
||||||
this.syncWithCharacter(player);
|
this.syncWithCharacter(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getUid() {
|
||||||
return id;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenshinPlayer getPlayer() {
|
public GenshinPlayer getPlayer() {
|
||||||
@@ -88,6 +88,7 @@ public class PlayerProfile {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.uid = player.getUid();
|
||||||
this.name = player.getNickname();
|
this.name = player.getNickname();
|
||||||
this.avatarId = player.getHeadImage();
|
this.avatarId = player.getHeadImage();
|
||||||
this.signature = player.getSignature();
|
this.signature = player.getSignature();
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class GachaBanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GachaInfo toProto() {
|
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()
|
GachaInfo.Builder info = GachaInfo.newBuilder()
|
||||||
.setGachaType(this.getGachaType())
|
.setGachaType(this.getGachaType())
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ public class GachaManager {
|
|||||||
addStarglitter = 2;
|
addStarglitter = 2;
|
||||||
// Add 1 const
|
// Add 1 const
|
||||||
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null));
|
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);
|
player.getInventory().addItem(constItemId, 1);
|
||||||
} else {
|
} else {
|
||||||
// Is max const
|
// Is max const
|
||||||
@@ -300,7 +299,7 @@ public class GachaManager {
|
|||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
|
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
|
||||||
if(Grasscutter.getConfig().getServerOptions().WatchGacha) {
|
if(Grasscutter.getConfig().getGameServerOptions().WatchGacha) {
|
||||||
try {
|
try {
|
||||||
WatchKey watchKey = watchService.take();
|
WatchKey watchKey = watchService.take();
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ public class Inventory implements Iterable<GenshinItem> {
|
|||||||
this.store = new Long2ObjectOpenHashMap<>();
|
this.store = new Long2ObjectOpenHashMap<>();
|
||||||
this.inventoryTypes = new Int2ObjectOpenHashMap<>();
|
this.inventoryTypes = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitWeapon));
|
this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon));
|
||||||
this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitRelic));
|
this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic));
|
||||||
this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitMaterial));
|
this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial));
|
||||||
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getServerOptions().InventoryLimitFurniture));
|
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture));
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenshinPlayer getPlayer() {
|
public GenshinPlayer getPlayer() {
|
||||||
|
|||||||
@@ -589,7 +589,6 @@ public class InventoryManager {
|
|||||||
|
|
||||||
// Update proud skills
|
// Update proud skills
|
||||||
AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
|
AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
|
||||||
boolean hasAddedProudSkill = false;
|
|
||||||
|
|
||||||
if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) {
|
if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) {
|
||||||
for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) {
|
for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) {
|
||||||
@@ -599,7 +598,6 @@ public class InventoryManager {
|
|||||||
if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) {
|
if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) {
|
||||||
int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1;
|
int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1;
|
||||||
if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) {
|
if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) {
|
||||||
hasAddedProudSkill = true;
|
|
||||||
avatar.getProudSkillList().add(proudSkillId);
|
avatar.getProudSkillList().add(proudSkillId);
|
||||||
player.sendPacket(new PacketProudSkillChangeNotify(avatar));
|
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
|
// Packets
|
||||||
player.sendPacket(new PacketAvatarPropNotify(avatar));
|
player.sendPacket(new PacketAvatarPropNotify(avatar));
|
||||||
player.sendPacket(new PacketAvatarPromoteRsp(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) {
|
public void upgradeAvatar(GenshinPlayer player, long guid, int itemId, int count) {
|
||||||
@@ -827,25 +818,20 @@ public class InventoryManager {
|
|||||||
// Apply + recalc
|
// Apply + recalc
|
||||||
avatar.getTalentIdList().add(talentData.getId());
|
avatar.getTalentIdList().add(talentData.getId());
|
||||||
avatar.setCoreProudSkillLevel(currentTalentLevel + 1);
|
avatar.setCoreProudSkillLevel(currentTalentLevel + 1);
|
||||||
avatar.recalcStats();
|
|
||||||
|
|
||||||
// Packet
|
// Packet
|
||||||
player.sendPacket(new PacketAvatarUnlockTalentNotify(avatar, nextTalentId));
|
player.sendPacket(new PacketAvatarUnlockTalentNotify(avatar, nextTalentId));
|
||||||
player.sendPacket(new PacketUnlockAvatarTalentRsp(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());
|
OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(talentData.getOpenConfig());
|
||||||
if (entry != null && entry.getExtraTalentIndex() > 0) {
|
if (entry != null && entry.getExtraTalentIndex() > 0) {
|
||||||
avatar.recalcProudSkillBonusMap();
|
avatar.recalcProudSkillBonusMap();
|
||||||
player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex()));
|
player.sendPacket(new PacketProudSkillExtraLevelNotify(avatar, entry.getExtraTalentIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resend ability embryos
|
// Recalc + save avatar
|
||||||
if (avatar.getAsEntity() != null) {
|
avatar.recalcStats(true);
|
||||||
player.sendPacket(new PacketAbilityChangeNotify(avatar.getAsEntity()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save avatar
|
|
||||||
avatar.save();
|
avatar.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
42
src/main/java/emu/grasscutter/game/props/FetterState.java
Normal file
42
src/main/java/emu/grasscutter/game/props/FetterState.java
Normal file
@@ -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<FetterState> map = new Int2ObjectOpenHashMap<>();
|
||||||
|
private static final Map<String, FetterState> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,14 @@
|
|||||||
package emu.grasscutter.server.dispatch;
|
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.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
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.HttpsConfigurator;
|
||||||
import com.sun.net.httpserver.HttpsServer;
|
import com.sun.net.httpserver.HttpsServer;
|
||||||
|
|
||||||
|
import emu.grasscutter.Config;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.database.DatabaseHelper;
|
import emu.grasscutter.database.DatabaseHelper;
|
||||||
import emu.grasscutter.game.Account;
|
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.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp;
|
||||||
import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
|
import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
|
||||||
import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo;
|
import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo;
|
||||||
import emu.grasscutter.server.dispatch.json.ComboTokenReqJson;
|
import emu.grasscutter.server.dispatch.json.*;
|
||||||
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.ComboTokenReqJson.LoginTokenData;
|
import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData;
|
||||||
import emu.grasscutter.utils.FileUtils;
|
import emu.grasscutter.utils.FileUtils;
|
||||||
import emu.grasscutter.utils.Utils;
|
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 {
|
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_region_list = "";
|
||||||
public static String query_cur_region = "";
|
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<String, RegionData> regions;
|
||||||
|
|
||||||
public DispatchServer() {
|
public DispatchServer() {
|
||||||
this.address = new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, Grasscutter.getConfig().DispatchServerPort);
|
this.regions = new HashMap<String, RegionData>();
|
||||||
|
this.address = new InetSocketAddress(Grasscutter.getConfig().getDispatchOptions().Ip, Grasscutter.getConfig().getDispatchOptions().Port);
|
||||||
this.gson = new GsonBuilder().create();
|
this.gson = new GsonBuilder().create();
|
||||||
|
|
||||||
this.loadQueries();
|
this.loadQueries();
|
||||||
@@ -71,7 +59,13 @@ public final class DispatchServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public QueryCurrRegionHttpRsp getCurrRegion() {
|
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() {
|
public void loadQueries() {
|
||||||
@@ -81,14 +75,14 @@ public final class DispatchServer {
|
|||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
query_region_list = new String(FileUtils.read(file));
|
query_region_list = new String(FileUtils.read(file));
|
||||||
} else {
|
} 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");
|
file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt");
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
query_cur_region = new String(FileUtils.read(file));
|
query_cur_region = new String(FileUtils.read(file));
|
||||||
} else {
|
} 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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,52 +94,79 @@ public final class DispatchServer {
|
|||||||
byte[] decoded2 = Base64.getDecoder().decode(query_cur_region);
|
byte[] decoded2 = Base64.getDecoder().decode(query_cur_region);
|
||||||
QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2);
|
QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2);
|
||||||
|
|
||||||
RegionSimpleInfo server = RegionSimpleInfo.newBuilder()
|
List<RegionSimpleInfo> servers = new ArrayList<RegionSimpleInfo>();
|
||||||
.setName("os_usa")
|
List<String> usedNames = new ArrayList<String>(); // List to check for potential naming conflicts
|
||||||
.setTitle(Grasscutter.getConfig().GameServerName)
|
if(Grasscutter.getConfig().RunMode.equalsIgnoreCase("HYBRID")) { // Automatically add the game server if in hybrid mode
|
||||||
.setType("DEV_PUBLIC")
|
RegionSimpleInfo server = RegionSimpleInfo.newBuilder()
|
||||||
.setDispatchUrl("https://" + (Grasscutter.getConfig().DispatchServerPublicIp.isEmpty() ? Grasscutter.getConfig().DispatchServerIp : Grasscutter.getConfig().DispatchServerPublicIp) + ":" + getAddress().getPort() + "/query_cur_region")
|
.setName("os_usa")
|
||||||
.build();
|
.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);
|
||||||
|
|
||||||
RegionSimpleInfo serverTest2 = RegionSimpleInfo.newBuilder()
|
RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder()
|
||||||
.setName("os_euro")
|
.setIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getGameServerOptions().Ip : Grasscutter.getConfig().getGameServerOptions().PublicIp))
|
||||||
.setTitle("Grasscutter")
|
.setPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0 ? Grasscutter.getConfig().getGameServerOptions().PublicPort : Grasscutter.getConfig().getGameServerOptions().Port)
|
||||||
.setType("DEV_PUBLIC")
|
.setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin")))
|
||||||
.setDispatchUrl("https://" + (Grasscutter.getConfig().DispatchServerPublicIp.isEmpty() ? Grasscutter.getConfig().DispatchServerIp : Grasscutter.getConfig().DispatchServerPublicIp) + ":" + getAddress().getPort() + "/query_cur_region")
|
.build();
|
||||||
.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()
|
QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.newBuilder()
|
||||||
.addServers(server)
|
.addAllServers(servers)
|
||||||
.addServers(serverTest2)
|
|
||||||
.setClientSecretKey(rl.getClientSecretKey())
|
.setClientSecretKey(rl.getClientSecretKey())
|
||||||
.setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted())
|
.setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted())
|
||||||
.setEnableLoginPc(true)
|
.setEnableLoginPc(true)
|
||||||
.build();
|
.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.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray());
|
||||||
this.regionCurrentBase64 = Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray());
|
|
||||||
this.currRegion = parsedRegionQuery;
|
|
||||||
} catch (Exception e) {
|
} 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 {
|
public void start() throws Exception {
|
||||||
HttpServer server;
|
HttpServer server;
|
||||||
if(Grasscutter.getConfig().UseSSL) {
|
if (Grasscutter.getConfig().getDispatchOptions().UseSSL) {
|
||||||
HttpsServer httpsServer;
|
HttpsServer httpsServer;
|
||||||
httpsServer = HttpsServer.create(getAddress(), 0);
|
httpsServer = HttpsServer.create(getAddress(), 0);
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) {
|
try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().getDispatchOptions().KeystorePath)) {
|
||||||
char[] keystorePassword = Grasscutter.getConfig().DispatchServerKeystorePassword.toCharArray();
|
char[] keystorePassword = Grasscutter.getConfig().getDispatchOptions().KeystorePassword.toCharArray();
|
||||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||||
ks.load(fis, keystorePassword);
|
ks.load(fis, keystorePassword);
|
||||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||||
@@ -156,56 +177,39 @@ public final class DispatchServer {
|
|||||||
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
|
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
|
||||||
server = httpsServer;
|
server = httpsServer;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Grasscutter.getLogger().error("No SSL cert found!");
|
Grasscutter.getLogger().warn("[Dispatch] No SSL cert found! Falling back to HTTP server.");
|
||||||
return;
|
Grasscutter.getConfig().getDispatchOptions().UseSSL = false;
|
||||||
|
server = HttpServer.create(getAddress(), 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
server = HttpServer.create(getAddress(), 0);
|
server = HttpServer.create(getAddress(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
server.createContext("/", t -> {
|
server.createContext("/", t -> responseHTML(t, "Hello"));
|
||||||
//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();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dispatch
|
// Dispatch
|
||||||
server.createContext("/query_region_list", t -> {
|
server.createContext("/query_region_list", t -> {
|
||||||
// Log
|
// Log
|
||||||
Grasscutter.getLogger().info("Client request: query_region_list");
|
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", t.getRemoteAddress()));
|
||||||
// Create a response form the request query parameters
|
|
||||||
String response = regionListBase64;
|
responseHTML(t, 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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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
|
// Login via account
|
||||||
server.createContext("/hk4e_global/mdk/shield/api/login", t -> {
|
server.createContext("/hk4e_global/mdk/shield/api/login", t -> {
|
||||||
// Get post data
|
// Get post data
|
||||||
@@ -213,32 +217,44 @@ public final class DispatchServer {
|
|||||||
try {
|
try {
|
||||||
String body = Utils.toString(t.getRequestBody());
|
String body = Utils.toString(t.getRequestBody());
|
||||||
requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class);
|
requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) { }
|
||||||
|
|
||||||
}
|
|
||||||
// Create response json
|
// Create response json
|
||||||
if (requestData == null) {
|
if (requestData == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LoginResultJson responseData = new LoginResultJson();
|
LoginResultJson responseData = new LoginResultJson();
|
||||||
|
|
||||||
|
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s is trying to log in", t.getRemoteAddress()));
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
Account account = DatabaseHelper.getAccountByName(requestData.account);
|
Account account = DatabaseHelper.getAccountByName(requestData.account);
|
||||||
|
|
||||||
// Check if account exists, else create a new one.
|
// Check if account exists, else create a new one.
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
// Account doesnt exist, so we can either auto create it if the config value is set
|
// 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.
|
// This account has been created AUTOMATICALLY. There will be no permissions added.
|
||||||
account = DatabaseHelper.createAccountWithId(requestData.account, 0);
|
account = DatabaseHelper.createAccountWithId(requestData.account, 0);
|
||||||
|
|
||||||
responseData.message = "OK";
|
if (account != null) {
|
||||||
responseData.data.account.uid = account.getId();
|
responseData.message = "OK";
|
||||||
responseData.data.account.token = account.generateSessionKey();
|
responseData.data.account.uid = account.getId();
|
||||||
responseData.data.account.email = account.getEmail();
|
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 {
|
} else {
|
||||||
responseData.retcode = -201;
|
responseData.retcode = -201;
|
||||||
responseData.message = "Username not found.";
|
responseData.message = "Username not found.";
|
||||||
|
|
||||||
|
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in: Account no found", t.getRemoteAddress()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Account was found, log the player in
|
// Account was found, log the player in
|
||||||
@@ -246,17 +262,11 @@ public final class DispatchServer {
|
|||||||
responseData.data.account.uid = account.getId();
|
responseData.data.account.uid = account.getId();
|
||||||
responseData.data.account.token = account.generateSessionKey();
|
responseData.data.account.token = account.generateSessionKey();
|
||||||
responseData.data.account.email = account.getEmail();
|
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
|
responseJSON(t, responseData);
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
// Login via token
|
// Login via token
|
||||||
server.createContext("/hk4e_global/mdk/shield/api/verify", t -> {
|
server.createContext("/hk4e_global/mdk/shield/api/verify", t -> {
|
||||||
@@ -265,14 +275,14 @@ public final class DispatchServer {
|
|||||||
try {
|
try {
|
||||||
String body = Utils.toString(t.getRequestBody());
|
String body = Utils.toString(t.getRequestBody());
|
||||||
requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class);
|
requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) { }
|
||||||
|
|
||||||
}
|
|
||||||
// Create response json
|
// Create response json
|
||||||
if (requestData == null) {
|
if (requestData == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LoginResultJson responseData = new LoginResultJson();
|
LoginResultJson responseData = new LoginResultJson();
|
||||||
|
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s is trying to log in via token", t.getRemoteAddress()));
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
Account account = DatabaseHelper.getAccountById(requestData.uid);
|
Account account = DatabaseHelper.getAccountById(requestData.uid);
|
||||||
@@ -281,22 +291,18 @@ public final class DispatchServer {
|
|||||||
if (account == null || !account.getSessionKey().equals(requestData.token)) {
|
if (account == null || !account.getSessionKey().equals(requestData.token)) {
|
||||||
responseData.retcode = -111;
|
responseData.retcode = -111;
|
||||||
responseData.message = "Game account cache information error";
|
responseData.message = "Game account cache information error";
|
||||||
|
|
||||||
|
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to log in via token", t.getRemoteAddress()));
|
||||||
} else {
|
} else {
|
||||||
responseData.message = "OK";
|
responseData.message = "OK";
|
||||||
responseData.data.account.uid = requestData.uid;
|
responseData.data.account.uid = requestData.uid;
|
||||||
responseData.data.account.token = requestData.token;
|
responseData.data.account.token = requestData.token;
|
||||||
responseData.data.account.email = account.getEmail();
|
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
|
responseJSON(t, responseData);
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
// Exchange for combo token
|
// Exchange for combo token
|
||||||
server.createContext("/hk4e_global/combo/granter/login/v2/login", t -> {
|
server.createContext("/hk4e_global/combo/granter/login/v2/login", t -> {
|
||||||
@@ -305,9 +311,8 @@ public final class DispatchServer {
|
|||||||
try {
|
try {
|
||||||
String body = Utils.toString(t.getRequestBody());
|
String body = Utils.toString(t.getRequestBody());
|
||||||
requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class);
|
requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) { }
|
||||||
|
|
||||||
}
|
|
||||||
// Create response json
|
// Create response json
|
||||||
if (requestData == null || requestData.data == null) {
|
if (requestData == null || requestData.data == null) {
|
||||||
return;
|
return;
|
||||||
@@ -322,22 +327,18 @@ public final class DispatchServer {
|
|||||||
if (account == null || !account.getSessionKey().equals(loginData.token)) {
|
if (account == null || !account.getSessionKey().equals(loginData.token)) {
|
||||||
responseData.retcode = -201;
|
responseData.retcode = -201;
|
||||||
responseData.message = "Wrong session key.";
|
responseData.message = "Wrong session key.";
|
||||||
|
|
||||||
|
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s failed to exchange combo token", t.getRemoteAddress()));
|
||||||
} else {
|
} else {
|
||||||
responseData.message = "OK";
|
responseData.message = "OK";
|
||||||
responseData.data.open_id = loginData.uid;
|
responseData.data.open_id = loginData.uid;
|
||||||
responseData.data.combo_id = "157795300";
|
responseData.data.combo_id = "157795300";
|
||||||
responseData.data.combo_token = account.generateLoginToken();
|
responseData.data.combo_token = account.generateLoginToken();
|
||||||
|
|
||||||
|
Grasscutter.getLogger().info(String.format("[Dispatch] Client %s succeed to exchange combo token", t.getRemoteAddress()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a response
|
responseJSON(t, responseData);
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
// Agreement and Protocol
|
// Agreement and Protocol
|
||||||
server.createContext( // hk4e-sdk-os.hoyoverse.com
|
server.createContext( // hk4e-sdk-os.hoyoverse.com
|
||||||
@@ -405,63 +406,84 @@ public final class DispatchServer {
|
|||||||
"/perf/config/verify",
|
"/perf/config/verify",
|
||||||
new DispatchHttpJsonHandler("{\"code\":0}")
|
new DispatchHttpJsonHandler("{\"code\":0}")
|
||||||
);
|
);
|
||||||
// Start server
|
|
||||||
server.start();
|
|
||||||
Grasscutter.getLogger().info("Dispatch server started on port " + getAddress().getPort());
|
|
||||||
|
|
||||||
// Logging servers
|
// Logging servers
|
||||||
HttpServer overseaLogServer = HttpServer.create(new InetSocketAddress(Grasscutter.getConfig().DispatchServerIp, 8888), 0);
|
server.createContext( // overseauspider.yuanshen.com
|
||||||
overseaLogServer.createContext( // overseauspider.yuanshen.com
|
|
||||||
"/log",
|
"/log",
|
||||||
new DispatchHttpJsonHandler("{\"code\":0}")
|
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);
|
server.createContext( // log-upload-os.mihoyo.com
|
||||||
uploadLogServer.createContext( // log-upload-os.mihoyo.com
|
|
||||||
"/crash/dataUpload",
|
"/crash/dataUpload",
|
||||||
new DispatchHttpJsonHandler("{\"code\":0}")
|
new DispatchHttpJsonHandler("{\"code\":0}")
|
||||||
);
|
);
|
||||||
uploadLogServer.createContext("/gacha", t -> {
|
server.createContext("/gacha", t -> responseHTML(t, "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>"));
|
||||||
//Create a response form the request query parameters
|
|
||||||
String response = "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>";
|
// Start server
|
||||||
//Set the response header status and length
|
server.start();
|
||||||
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
|
Grasscutter.getLogger().info("[Dispatch] Dispatch server started on port " + getAddress().getPort());
|
||||||
t.sendResponseHeaders(200, response.getBytes().length);
|
}
|
||||||
//Write the response string
|
|
||||||
OutputStream os = t.getResponseBody();
|
private void responseJSON(HttpExchange t, Object data) throws IOException {
|
||||||
os.write(response.getBytes());
|
// Create a response
|
||||||
os.close();
|
String response = getGsonFactory().toJson(data);
|
||||||
});
|
// Set the response header status and length
|
||||||
uploadLogServer.start();
|
t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json"));
|
||||||
Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + Grasscutter.getConfig().UploadLogPort);
|
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<String, String> parseQueryString(String qs) {
|
private Map<String, String> parseQueryString(String qs) {
|
||||||
Map<String, String> result = new HashMap<>();
|
Map<String, String> result = new HashMap<>();
|
||||||
if (qs == null)
|
if (qs == null) {
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
int last = 0, next, l = qs.length();
|
int last = 0, next, l = qs.length();
|
||||||
while (last < l) {
|
while (last < l) {
|
||||||
next = qs.indexOf('&', last);
|
next = qs.indexOf('&', last);
|
||||||
if (next == -1)
|
if (next == -1) {
|
||||||
next = l;
|
next = l;
|
||||||
|
}
|
||||||
|
|
||||||
if (next > last) {
|
if (next > last) {
|
||||||
int eqPos = qs.indexOf('=', last);
|
int eqPos = qs.indexOf('=', last);
|
||||||
try {
|
try {
|
||||||
if (eqPos < 0 || eqPos > next)
|
if (eqPos < 0 || eqPos > next) {
|
||||||
result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), "");
|
result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), "");
|
||||||
else
|
} else {
|
||||||
result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8"));
|
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
|
} catch (UnsupportedEncodingException e) {
|
||||||
}
|
throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java
|
||||||
}
|
}
|
||||||
last = next + 1;
|
}
|
||||||
}
|
last = next + 1;
|
||||||
return result;
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RegionData {
|
||||||
|
|
||||||
|
QueryCurrRegionHttpRsp parsedRegionQuery;
|
||||||
|
String Base64;
|
||||||
|
|
||||||
|
public RegionData(QueryCurrRegionHttpRsp prq, String b64) {
|
||||||
|
this.parsedRegionQuery = prq;
|
||||||
|
this.Base64 = b64;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public class ComboTokenReqJson {
|
|||||||
public String device;
|
public String device;
|
||||||
public String sign;
|
public String sign;
|
||||||
|
|
||||||
public class LoginTokenData {
|
public static class LoginTokenData {
|
||||||
public String uid;
|
public String uid;
|
||||||
public String token;
|
public String token;
|
||||||
public boolean guest;
|
public boolean guest;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ public class ComboTokenResJson {
|
|||||||
public int retcode;
|
public int retcode;
|
||||||
public LoginData data = new LoginData();
|
public LoginData data = new LoginData();
|
||||||
|
|
||||||
public class LoginData {
|
public static class LoginData {
|
||||||
public int account_type = 1;
|
public int account_type = 1;
|
||||||
public boolean heartbeat;
|
public boolean heartbeat;
|
||||||
public String combo_id;
|
public String combo_id;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ public class LoginResultJson {
|
|||||||
public int retcode;
|
public int retcode;
|
||||||
public VerifyData data = new VerifyData();
|
public VerifyData data = new VerifyData();
|
||||||
|
|
||||||
public class VerifyData {
|
public static class VerifyData {
|
||||||
public VerifyAccountData account = new VerifyAccountData();
|
public VerifyAccountData account = new VerifyAccountData();
|
||||||
public boolean device_grant_required = false;
|
public boolean device_grant_required = false;
|
||||||
public String realname_operation = "NONE";
|
public String realname_operation = "NONE";
|
||||||
@@ -13,7 +13,7 @@ public class LoginResultJson {
|
|||||||
public boolean safe_mobile_required = false;
|
public boolean safe_mobile_required = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VerifyAccountData {
|
public static class VerifyAccountData {
|
||||||
public String uid;
|
public String uid;
|
||||||
public String name = "";
|
public String name = "";
|
||||||
public String email;
|
public String email;
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ public class GameServerPacketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Log unhandled packets
|
// Log unhandled packets
|
||||||
if (Grasscutter.getConfig().LOG_PACKETS) {
|
if (Grasscutter.getConfig().getGameServerOptions().LOG_PACKETS) {
|
||||||
//Grasscutter.getLogger().info("Unhandled packet (" + opcode + "): " + PacketOpcodesUtil.getOpcodeName(opcode));
|
//Grasscutter.getLogger().info("Unhandled packet (" + opcode + "): " + PacketOpcodesUtil.getOpcodeName(opcode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public class GameSession extends MihoyoKcpChannel {
|
|||||||
byte[] data = genshinPacket.build();
|
byte[] data = genshinPacket.build();
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
if (Grasscutter.getConfig().LOG_PACKETS) {
|
if (Grasscutter.getConfig().getGameServerOptions().LOG_PACKETS) {
|
||||||
logPacket(genshinPacket);
|
logPacket(genshinPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ public class GameSession extends MihoyoKcpChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Log packet
|
// Log packet
|
||||||
if (Grasscutter.getConfig().LOG_PACKETS) {
|
if (Grasscutter.getConfig().getGameServerOptions().LOG_PACKETS) {
|
||||||
Grasscutter.getLogger().info("RECV: " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")");
|
Grasscutter.getLogger().info("RECV: " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")");
|
||||||
System.out.println(Utils.bytesToHex(payload));
|
System.out.println(Utils.bytesToHex(payload));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ public class HandlerEnterSceneDoneReq extends PacketHandler {
|
|||||||
|
|
||||||
// Locations
|
// Locations
|
||||||
session.send(new PacketWorldPlayerLocationNotify(session.getPlayer().getWorld()));
|
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()));
|
session.send(new PacketWorldPlayerRTTNotify(session.getPlayer().getWorld()));
|
||||||
|
|
||||||
|
// Reset timer for sending player locations
|
||||||
|
session.getPlayer().resetSendPlayerLocTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,15 +35,15 @@ public class HandlerGetPlayerTokenReq extends PacketHandler {
|
|||||||
|
|
||||||
// Has character
|
// Has character
|
||||||
boolean doesPlayerExist = false;
|
boolean doesPlayerExist = false;
|
||||||
if (account.getPlayerId() > 0) {
|
if (account.getPlayerUid() > 0) {
|
||||||
// Set flag for player existing
|
// Set flag for player existing
|
||||||
doesPlayerExist = DatabaseHelper.checkPlayerExists(account.getPlayerId());
|
doesPlayerExist = DatabaseHelper.checkPlayerExists(account.getPlayerUid());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set reserve player id if account doesnt exist
|
// Set reserve player id if account doesnt exist
|
||||||
if (!doesPlayerExist) {
|
if (!doesPlayerExist) {
|
||||||
int id = DatabaseHelper.getNextPlayerId(session.getAccount().getPlayerId());
|
int id = DatabaseHelper.getNextPlayerId(session.getAccount().getPlayerUid());
|
||||||
if (id != session.getAccount().getPlayerId()) {
|
if (id != session.getAccount().getPlayerUid()) {
|
||||||
session.getAccount().setPlayerId(id);
|
session.getAccount().setPlayerId(id);
|
||||||
session.getAccount().save();
|
session.getAccount().save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class HandlerPlayerLoginReq extends PacketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load character from db
|
// Load character from db
|
||||||
GenshinPlayer player = DatabaseHelper.getPlayerById(session.getAccount().getPlayerId());
|
GenshinPlayer player = DatabaseHelper.getPlayerById(session.getAccount().getPlayerUid());
|
||||||
|
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
// Send packets
|
// Send packets
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package emu.grasscutter.server.packet.recv;
|
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.Opcodes;
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
import emu.grasscutter.net.proto.SceneTransToPointReqOuterClass.SceneTransToPointReq;
|
import emu.grasscutter.net.proto.SceneTransToPointReqOuterClass.SceneTransToPointReq;
|
||||||
|
import emu.grasscutter.net.proto.SceneTransToPointRspOuterClass.SceneTransToPointRsp;
|
||||||
import emu.grasscutter.net.packet.PacketHandler;
|
import emu.grasscutter.net.packet.PacketHandler;
|
||||||
import emu.grasscutter.server.game.GameSession;
|
import emu.grasscutter.server.game.GameSession;
|
||||||
import emu.grasscutter.server.packet.send.PacketSceneTransToPointRsp;
|
import emu.grasscutter.server.packet.send.PacketSceneTransToPointRsp;
|
||||||
|
import emu.grasscutter.utils.Position;
|
||||||
|
|
||||||
@Opcodes(PacketOpcodes.SceneTransToPointReq)
|
@Opcodes(PacketOpcodes.SceneTransToPointReq)
|
||||||
public class HandlerSceneTransToPointReq extends PacketHandler {
|
public class HandlerSceneTransToPointReq extends PacketHandler {
|
||||||
@@ -13,7 +17,20 @@ public class HandlerSceneTransToPointReq extends PacketHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||||
SceneTransToPointReq req = SceneTransToPointReq.parseFrom(payload);
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package emu.grasscutter.server.packet.recv;
|
|||||||
import emu.grasscutter.net.packet.GenshinPacket;
|
import emu.grasscutter.net.packet.GenshinPacket;
|
||||||
import emu.grasscutter.net.packet.Opcodes;
|
import emu.grasscutter.net.packet.Opcodes;
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
|
import emu.grasscutter.net.proto.SetEntityClientDataNotifyOuterClass.SetEntityClientDataNotify;
|
||||||
import emu.grasscutter.net.packet.PacketHandler;
|
import emu.grasscutter.net.packet.PacketHandler;
|
||||||
import emu.grasscutter.server.game.GameSession;
|
import emu.grasscutter.server.game.GameSession;
|
||||||
|
|
||||||
@@ -16,8 +17,11 @@ public class HandlerSetEntityClientDataNotify extends PacketHandler {
|
|||||||
return;
|
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);
|
GenshinPacket packet = new GenshinPacket(PacketOpcodes.SetEntityClientDataNotify, true);
|
||||||
packet.setData(payload);
|
packet.setData(notif);
|
||||||
|
|
||||||
session.getPlayer().getScene().broadcastPacketToOthers(session.getPlayer(), packet);
|
session.getPlayer().getScene().broadcastPacketToOthers(session.getPlayer(), packet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Save to db
|
// Save to db
|
||||||
DatabaseHelper.createPlayer(player, session.getAccount().getPlayerId());
|
DatabaseHelper.createPlayer(player, session.getAccount().getPlayerUid());
|
||||||
|
|
||||||
// Create avatar
|
// Create avatar
|
||||||
if (player.getAvatars().getAvatarCount() == 0) {
|
if (player.getAvatars().getAvatarCount() == 0) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import emu.grasscutter.net.proto.AbilityChangeNotifyOuterClass.AbilityChangeNoti
|
|||||||
public class PacketAbilityChangeNotify extends GenshinPacket {
|
public class PacketAbilityChangeNotify extends GenshinPacket {
|
||||||
|
|
||||||
public PacketAbilityChangeNotify(EntityAvatar entity) {
|
public PacketAbilityChangeNotify(EntityAvatar entity) {
|
||||||
super(PacketOpcodes.AbilityChangeNotify);
|
super(PacketOpcodes.AbilityChangeNotify, true);
|
||||||
|
|
||||||
AbilityChangeNotify proto = AbilityChangeNotify.newBuilder()
|
AbilityChangeNotify proto = AbilityChangeNotify.newBuilder()
|
||||||
.setEntityId(entity.getId())
|
.setEntityId(entity.getId())
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ public class PacketGetPlayerFriendListRsp extends GenshinPacket {
|
|||||||
.setWorldLevel(0)
|
.setWorldLevel(0)
|
||||||
.setSignature("")
|
.setSignature("")
|
||||||
.setLastActiveTime((int) (System.currentTimeMillis() / 1000f))
|
.setLastActiveTime((int) (System.currentTimeMillis() / 1000f))
|
||||||
.setIsMpModeAvailable(true)
|
|
||||||
.setNameCardId(210001)
|
.setNameCardId(210001)
|
||||||
.setOnlineState(FriendOnlineState.FRIEND_ONLINE)
|
.setOnlineState(FriendOnlineState.FRIEND_ONLINE)
|
||||||
.setParam(1)
|
.setParam(1)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class PacketGetPlayerTokenRsp extends GenshinPacket {
|
|||||||
this.setUseDispatchKey(true);
|
this.setUseDispatchKey(true);
|
||||||
|
|
||||||
GetPlayerTokenRsp p = GetPlayerTokenRsp.newBuilder()
|
GetPlayerTokenRsp p = GetPlayerTokenRsp.newBuilder()
|
||||||
.setPlayerUid(session.getAccount().getPlayerId())
|
.setPlayerUid(session.getAccount().getPlayerUid())
|
||||||
.setAccountToken(session.getAccount().getToken())
|
.setAccountToken(session.getAccount().getToken())
|
||||||
.setAccountType(1)
|
.setAccountType(1)
|
||||||
.setIsProficientPlayer(doesPlayerExist) // Not sure where this goes
|
.setIsProficientPlayer(doesPlayerExist) // Not sure where this goes
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class PacketPlayerEnterSceneNotify extends GenshinPacket {
|
|||||||
.setSceneId(newScene)
|
.setSceneId(newScene)
|
||||||
.setPos(newPos.toProto())
|
.setPos(newPos.toProto())
|
||||||
.setSceneBeginTime(System.currentTimeMillis())
|
.setSceneBeginTime(System.currentTimeMillis())
|
||||||
.setType(EnterType.EnterSelf)
|
.setType(type)
|
||||||
.setTargetUid(target.getUid())
|
.setTargetUid(target.getUid())
|
||||||
.setEnterSceneToken(player.getEnterSceneToken())
|
.setEnterSceneToken(player.getEnterSceneToken())
|
||||||
.setWorldLevel(target.getWorld().getWorldLevel())
|
.setWorldLevel(target.getWorld().getWorldLevel())
|
||||||
|
|||||||
@@ -1,20 +1,60 @@
|
|||||||
package emu.grasscutter.server.packet.send;
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.net.packet.GenshinPacket;
|
import emu.grasscutter.net.packet.GenshinPacket;
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
import emu.grasscutter.net.proto.PlayerLoginRspOuterClass.PlayerLoginRsp;
|
import emu.grasscutter.net.proto.PlayerLoginRspOuterClass.PlayerLoginRsp;
|
||||||
|
import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass;
|
||||||
import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
|
import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
|
||||||
import emu.grasscutter.server.game.GameSession;
|
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 {
|
public class PacketPlayerLoginRsp extends GenshinPacket {
|
||||||
|
|
||||||
|
private static QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp regionCache;
|
||||||
|
|
||||||
public PacketPlayerLoginRsp(GameSession session) {
|
public PacketPlayerLoginRsp(GameSession session) {
|
||||||
super(PacketOpcodes.PlayerLoginRsp, 1);
|
super(PacketOpcodes.PlayerLoginRsp, 1);
|
||||||
|
|
||||||
this.setUseDispatchKey(true);
|
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()
|
PlayerLoginRsp p = PlayerLoginRsp.newBuilder()
|
||||||
.setIsUseAbilityHash(true) // true
|
.setIsUseAbilityHash(true) // true
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class PacketPlayerStoreNotify extends GenshinPacket {
|
|||||||
|
|
||||||
PlayerStoreNotify.Builder p = PlayerStoreNotify.newBuilder()
|
PlayerStoreNotify.Builder p = PlayerStoreNotify.newBuilder()
|
||||||
.setStoreType(StoreType.StorePack)
|
.setStoreType(StoreType.StorePack)
|
||||||
.setWeightLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitAll);
|
.setWeightLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitAll);
|
||||||
|
|
||||||
for (GenshinItem item : player.getInventory()) {
|
for (GenshinItem item : player.getInventory()) {
|
||||||
Item itemProto = item.toProto();
|
Item itemProto = item.toProto();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package emu.grasscutter.server.packet.send;
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
import emu.grasscutter.Config.ServerOptions;
|
import emu.grasscutter.Config.GameServerOptions;
|
||||||
import emu.grasscutter.GenshinConstants;
|
import emu.grasscutter.GenshinConstants;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.game.GenshinPlayer;
|
import emu.grasscutter.game.GenshinPlayer;
|
||||||
@@ -14,7 +14,7 @@ public class PacketPullRecentChatRsp extends GenshinPacket {
|
|||||||
public PacketPullRecentChatRsp(GenshinPlayer player) {
|
public PacketPullRecentChatRsp(GenshinPlayer player) {
|
||||||
super(PacketOpcodes.PullRecentChatRsp);
|
super(PacketOpcodes.PullRecentChatRsp);
|
||||||
|
|
||||||
ServerOptions serverOptions = Grasscutter.getConfig().getServerOptions();
|
GameServerOptions serverOptions = Grasscutter.getConfig().getGameServerOptions();
|
||||||
PullRecentChatRsp.Builder proto = PullRecentChatRsp.newBuilder();
|
PullRecentChatRsp.Builder proto = PullRecentChatRsp.newBuilder();
|
||||||
|
|
||||||
if (serverOptions.WelcomeEmotes != null && serverOptions.WelcomeEmotes.length > 0) {
|
if (serverOptions.WelcomeEmotes != null && serverOptions.WelcomeEmotes.length > 0) {
|
||||||
@@ -33,7 +33,7 @@ public class PacketPullRecentChatRsp extends GenshinPacket {
|
|||||||
.setTime((int) (System.currentTimeMillis() / 1000))
|
.setTime((int) (System.currentTimeMillis() / 1000))
|
||||||
.setUid(GenshinConstants.SERVER_CONSOLE_UID)
|
.setUid(GenshinConstants.SERVER_CONSOLE_UID)
|
||||||
.setToUid(player.getUid())
|
.setToUid(player.getUid())
|
||||||
.setText(Grasscutter.getConfig().getServerOptions().WelcomeMotd)
|
.setText(Grasscutter.getConfig().getGameServerOptions().WelcomeMotd)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
proto.addChatInfo(welcomeMotd);
|
proto.addChatInfo(welcomeMotd);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class PacketSceneAreaWeatherNotify extends GenshinPacket {
|
|||||||
super(PacketOpcodes.SceneAreaWeatherNotify);
|
super(PacketOpcodes.SceneAreaWeatherNotify);
|
||||||
|
|
||||||
SceneAreaWeatherNotify proto = SceneAreaWeatherNotify.newBuilder()
|
SceneAreaWeatherNotify proto = SceneAreaWeatherNotify.newBuilder()
|
||||||
.setWeatherAreaId(1)
|
.setWeatherAreaId(player.getScene().getWeather())
|
||||||
.setClimateType(player.getScene().getClimate().getValue())
|
.setClimateType(player.getScene().getClimate().getValue())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
package emu.grasscutter.server.packet.send;
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
import emu.grasscutter.game.GenshinPlayer;
|
import emu.grasscutter.game.GenshinPlayer;
|
||||||
|
import emu.grasscutter.game.GenshinScene;
|
||||||
import emu.grasscutter.net.packet.GenshinPacket;
|
import emu.grasscutter.net.packet.GenshinPacket;
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
import emu.grasscutter.net.proto.ScenePlayerLocationNotifyOuterClass.ScenePlayerLocationNotify;
|
import emu.grasscutter.net.proto.ScenePlayerLocationNotifyOuterClass.ScenePlayerLocationNotify;
|
||||||
|
|
||||||
public class PacketScenePlayerLocationNotify extends GenshinPacket {
|
public class PacketScenePlayerLocationNotify extends GenshinPacket {
|
||||||
|
|
||||||
public PacketScenePlayerLocationNotify(GenshinPlayer player) {
|
public PacketScenePlayerLocationNotify(GenshinScene scene) {
|
||||||
super(PacketOpcodes.ScenePlayerLocationNotify);
|
super(PacketOpcodes.ScenePlayerLocationNotify);
|
||||||
|
|
||||||
ScenePlayerLocationNotify.Builder proto = ScenePlayerLocationNotify.newBuilder()
|
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());
|
proto.addPlayerLocList(p.getPlayerLocationInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,22 +13,21 @@ public class PacketSceneTransToPointRsp extends GenshinPacket {
|
|||||||
public PacketSceneTransToPointRsp(GenshinPlayer player, int pointId, int sceneId) {
|
public PacketSceneTransToPointRsp(GenshinPlayer player, int pointId, int sceneId) {
|
||||||
super(PacketOpcodes.SceneTransToPointRsp);
|
super(PacketOpcodes.SceneTransToPointRsp);
|
||||||
|
|
||||||
String code = sceneId + "_" + pointId;
|
SceneTransToPointRsp proto = SceneTransToPointRsp.newBuilder()
|
||||||
ScenePointEntry scenePointEntry = GenshinData.getScenePointEntries().get(code);
|
.setRetcode(0)
|
||||||
|
.setPointId(pointId)
|
||||||
|
.setSceneId(sceneId)
|
||||||
|
.build();
|
||||||
|
|
||||||
float x = scenePointEntry.getPointData().getTranPos().getX();
|
this.setData(proto);
|
||||||
float y = scenePointEntry.getPointData().getTranPos().getY();
|
}
|
||||||
float z = scenePointEntry.getPointData().getTranPos().getZ();
|
|
||||||
|
|
||||||
player.getPos().set(new Position(x, y, z));
|
public PacketSceneTransToPointRsp() {
|
||||||
|
super(PacketOpcodes.SceneTransToPointRsp);
|
||||||
player.getWorld().forceTransferPlayerToScene(player, sceneId, player.getPos());
|
|
||||||
|
|
||||||
SceneTransToPointRsp proto = SceneTransToPointRsp.newBuilder()
|
SceneTransToPointRsp proto = SceneTransToPointRsp.newBuilder()
|
||||||
.setRetcode(0)
|
.setRetcode(1) // Internal server error
|
||||||
.setPointId(pointId)
|
.build();
|
||||||
.setSceneId(sceneId)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
this.setData(proto);
|
this.setData(proto);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ public class PacketSceneUnlockInfoNotify extends GenshinPacket {
|
|||||||
|
|
||||||
SceneUnlockInfoNotify proto = SceneUnlockInfoNotify.newBuilder()
|
SceneUnlockInfoNotify proto = SceneUnlockInfoNotify.newBuilder()
|
||||||
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(1))
|
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(1))
|
||||||
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(3))
|
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(3).addSceneTagIdList(102).addSceneTagIdList(113).addSceneTagIdList(117))
|
||||||
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(4))
|
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(4).addSceneTagIdList(106).addSceneTagIdList(109))
|
||||||
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(5))
|
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(5))
|
||||||
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(6))
|
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(6))
|
||||||
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(7))
|
.addUnlockInfos(SceneUnlockInfo.newBuilder().setSceneId(7))
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ public class PacketStoreWeightLimitNotify extends GenshinPacket {
|
|||||||
|
|
||||||
StoreWeightLimitNotify p = StoreWeightLimitNotify.newBuilder()
|
StoreWeightLimitNotify p = StoreWeightLimitNotify.newBuilder()
|
||||||
.setStoreType(StoreType.StorePack)
|
.setStoreType(StoreType.StorePack)
|
||||||
.setWeightLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitAll)
|
.setWeightLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitAll)
|
||||||
.setWeaponCountLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitWeapon)
|
.setWeaponCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon)
|
||||||
.setReliquaryCountLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitRelic)
|
.setReliquaryCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic)
|
||||||
.setMaterialCountLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitMaterial)
|
.setMaterialCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial)
|
||||||
.setFurnitureCountLimit(Grasscutter.getConfig().getServerOptions().InventoryLimitFurniture)
|
.setFurnitureCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
this.setData(p);
|
this.setData(p);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class PacketWorldPlayerLocationNotify extends GenshinPacket {
|
|||||||
WorldPlayerLocationNotify.Builder proto = WorldPlayerLocationNotify.newBuilder();
|
WorldPlayerLocationNotify.Builder proto = WorldPlayerLocationNotify.newBuilder();
|
||||||
|
|
||||||
for (GenshinPlayer p : world.getPlayers()) {
|
for (GenshinPlayer p : world.getPlayers()) {
|
||||||
proto.addPlayerLocList(p.getPlayerLocationInfo());
|
proto.addPlayerLocList(p.getWorldPlayerLocationInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setData(proto);
|
this.setData(proto);
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package emu.grasscutter.tools;
|
package emu.grasscutter.tools;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -30,13 +35,13 @@ public final class Tools {
|
|||||||
ResourceLoader.loadResources();
|
ResourceLoader.loadResources();
|
||||||
|
|
||||||
Map<Long, String> map;
|
Map<Long, String> 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<Map<Long, String>>() {}.getType());
|
map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken<Map<Long, String>>() {}.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Integer> list;
|
List<Integer> list;
|
||||||
String fileName = "./GM Handbook.txt";
|
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");
|
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ public final class Utils {
|
|||||||
// Check for GenshinData.
|
// Check for GenshinData.
|
||||||
if(!fileExists(resourcesFolder + "BinOutput") ||
|
if(!fileExists(resourcesFolder + "BinOutput") ||
|
||||||
!fileExists(resourcesFolder + "ExcelBinOutput")) {
|
!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;
|
exit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,19 @@
|
|||||||
<pattern>[%d{HH:mm:ss}] [%highlight(%level)] %msg%n</pattern>
|
<pattern>[%d{HH:mm:ss}] [%highlight(%level)] %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>logs/latest.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>logs/log.%d{yyyy-MM-dd}_%d{HH}.log.tar.gz</fileNamePattern>
|
||||||
|
<maxHistory>24</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd'T'HH:mm:ss'Z'} - %m%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
<logger name="org.reflections" level="OFF"/>
|
<logger name="org.reflections" level="OFF"/>
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="STDOUT" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
</root>
|
</root>
|
||||||
</Configuration>
|
</Configuration>
|
||||||
164
start.cmd
Normal file
164
start.cmd
Normal file
@@ -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%] %*
|
||||||
25
start_config.cmd
Normal file
25
start_config.cmd
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user