Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
ziqian zhang
Grasscutter
Commits
716380a0
Commit
716380a0
authored
Apr 22, 2022
by
Melledy
Browse files
Merge branch 'development' into java-16
parents
09c8ed34
627d3dda
Changes
69
Show whitespace changes
Inline
Side-by-side
src/main/java/emu/grasscutter/data/def/FetterData.java
0 → 100644
View file @
716380a0
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
()
{
}
}
src/main/java/emu/grasscutter/database/DatabaseHelper.java
View file @
716380a0
...
...
@@ -66,7 +66,7 @@ public final class DatabaseHelper {
}
public
static
void
saveAccount
(
Account
account
)
{
DatabaseManager
.
getDatastore
().
save
(
account
);
DatabaseManager
.
get
Account
Datastore
().
save
(
account
);
}
public
static
Account
getAccountByName
(
String
username
)
{
...
...
src/main/java/emu/grasscutter/database/DatabaseManager.java
View file @
716380a0
package
emu.grasscutter.database
;
import
com.mongodb.MongoClientURI
;
import
com.mongodb.MongoCommandException
;
import
com.mongodb.client.MongoClient
;
import
com.mongodb.client.MongoClients
;
...
...
@@ -18,7 +19,12 @@ import emu.grasscutter.game.friends.Friendship;
import
emu.grasscutter.game.inventory.GenshinItem
;
public
final
class
DatabaseManager
{
private
static
MongoClient
mongoClient
;
private
static
MongoClient
dispatchMongoClient
;
private
static
Datastore
datastore
;
private
static
Datastore
dispatchDatastore
;
private
static
final
Class
<?>[]
mappedClasses
=
new
Class
<?>[]
{
DatabaseCounter
.
class
,
Account
.
class
,
GenshinPlayer
.
class
,
GenshinAvatar
.
class
,
GenshinItem
.
class
,
Friendship
.
class
...
...
@@ -32,6 +38,16 @@ public final class DatabaseManager {
return
getDatastore
().
getDatabase
();
}
// Yes. I very dislike this method. However, this will be good for now.
// TODO: Add dispatch routes for player account management
public
static
Datastore
getAccountDatastore
()
{
if
(
Grasscutter
.
getConfig
().
RunMode
.
equalsIgnoreCase
(
"GAME_ONLY"
))
{
return
dispatchDatastore
;
}
else
{
return
datastore
;
}
}
public
static
void
initialize
()
{
// Initialize
MongoClient
mongoClient
=
MongoClients
.
create
(
Grasscutter
.
getConfig
().
DatabaseUrl
);
...
...
@@ -60,6 +76,28 @@ public final class DatabaseManager {
datastore
.
ensureIndexes
();
}
}
if
(
Grasscutter
.
getConfig
().
RunMode
.
equalsIgnoreCase
(
"GAME_ONLY"
))
{
dispatchMongoClient
=
MongoClients
.
create
(
Grasscutter
.
getConfig
().
getGameServerOptions
().
DispatchServerDatabaseUrl
);
dispatchDatastore
=
Morphia
.
createDatastore
(
dispatchMongoClient
,
Grasscutter
.
getConfig
().
getGameServerOptions
().
DispatchServerDatabaseCollection
);
// Ensure indexes for dispatch server
try
{
dispatchDatastore
.
ensureIndexes
();
}
catch
(
MongoCommandException
e
)
{
Grasscutter
.
getLogger
().
info
(
"Mongo index error: "
,
e
);
// Duplicate index error
if
(
e
.
getCode
()
==
85
)
{
// Drop all indexes and re add them
MongoIterable
<
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
)
{
...
...
src/main/java/emu/grasscutter/game/Account.java
View file @
716380a0
...
...
@@ -8,6 +8,10 @@ import emu.grasscutter.utils.Utils;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.bson.Document
;
import
com.mongodb.DBObject
;
@Entity
(
value
=
"accounts"
,
useDiscriminator
=
false
)
public
class
Account
{
@Id
private
String
id
;
...
...
@@ -17,7 +21,7 @@ public class Account {
private
String
username
;
private
String
password
;
// Unused for now
private
int
playerId
;
@AlsoLoad
(
"playerUid"
)
private
int
playerId
;
private
String
email
;
private
String
token
;
...
...
@@ -61,7 +65,7 @@ public class Account {
this
.
token
=
token
;
}
public
int
getPlayer
I
d
()
{
public
int
getPlayer
Ui
d
()
{
return
this
.
playerId
;
}
...
...
@@ -118,12 +122,11 @@ public class Account {
DatabaseHelper
.
saveAccount
(
this
);
}
// TODO: Find an implementation for this. Morphia 2.0 breaks this method for some reason?
// @PreLoad
// public void onLoad(DBObject object) {
// // Grant the superuser permissions to accounts created before the permissions update
// if (!object.containsField("permissions")) {
// this.addPermission("*");
// }
// }
@PreLoad
public
void
onLoad
(
Document
document
)
{
// Grant the superuser permissions to accounts created before the permissions update
if
(!
document
.
containsKey
(
"permissions"
))
{
this
.
addPermission
(
"*"
);
}
}
}
src/main/java/emu/grasscutter/game/GenshinPlayer.java
View file @
716380a0
...
...
@@ -31,6 +31,7 @@ import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo;
import
emu.grasscutter.net.proto.PlayerApplyEnterMpReasonOuterClass.PlayerApplyEnterMpReason
;
import
emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo
;
import
emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail
;
import
emu.grasscutter.net.proto.WorldPlayerLocationInfoOuterClass.WorldPlayerLocationInfo
;
import
emu.grasscutter.server.game.GameServer
;
import
emu.grasscutter.server.game.GameSession
;
import
emu.grasscutter.server.packet.send.PacketAbilityInvocationsNotify
;
...
...
@@ -49,9 +50,11 @@ import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
import
emu.grasscutter.server.packet.send.PacketPlayerPropNotify
;
import
emu.grasscutter.server.packet.send.PacketPlayerStoreNotify
;
import
emu.grasscutter.server.packet.send.PacketPrivateChatNotify
;
import
emu.grasscutter.server.packet.send.PacketScenePlayerLocationNotify
;
import
emu.grasscutter.server.packet.send.PacketSetNameCardRsp
;
import
emu.grasscutter.server.packet.send.PacketStoreWeightLimitNotify
;
import
emu.grasscutter.server.packet.send.PacketUnlockNameCardNotify
;
import
emu.grasscutter.server.packet.send.PacketWorldPlayerLocationNotify
;
import
emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify
;
import
emu.grasscutter.utils.Position
;
...
...
@@ -101,6 +104,7 @@ public class GenshinPlayer {
@Transient
private
int
enterSceneToken
;
@Transient
private
SceneLoadState
sceneState
;
@Transient
private
boolean
hasSentAvatarDataNotify
;
@Transient
private
long
nextSendPlayerLocTime
=
0
;
@Transient
private
final
Int2ObjectMap
<
CoopRequest
>
coopRequests
;
@Transient
private
final
InvokeHandler
<
CombatInvokeEntry
>
combatInvokeHandler
;
...
...
@@ -121,6 +125,12 @@ public class GenshinPlayer {
}
this
.
properties
.
put
(
prop
.
getId
(),
0
);
}
this
.
gachaInfo
=
new
PlayerGachaInfo
();
this
.
nameCardList
=
new
HashSet
<>();
this
.
flyCloakList
=
new
HashSet
<>();
this
.
costumeList
=
new
HashSet
<>();
this
.
setSceneId
(
3
);
this
.
setRegionId
(
1
);
this
.
sceneState
=
SceneLoadState
.
NONE
;
...
...
@@ -140,11 +150,6 @@ public class GenshinPlayer {
this
.
nickname
=
"Traveler"
;
this
.
signature
=
""
;
this
.
teamManager
=
new
TeamManager
(
this
);
this
.
gachaInfo
=
new
PlayerGachaInfo
();
this
.
playerProfile
=
new
PlayerProfile
(
this
);
this
.
nameCardList
=
new
HashSet
<>();
this
.
flyCloakList
=
new
HashSet
<>();
this
.
costumeList
=
new
HashSet
<>();
this
.
setProperty
(
PlayerProperty
.
PROP_PLAYER_LEVEL
,
1
);
this
.
setProperty
(
PlayerProperty
.
PROP_IS_SPRING_AUTO_USE
,
1
);
this
.
setProperty
(
PlayerProperty
.
PROP_SPRING_AUTO_USE_PERCENT
,
50
);
...
...
@@ -288,7 +293,7 @@ public class GenshinPlayer {
}
private
float
getExpModifier
()
{
return
Grasscutter
.
getConfig
().
getGameRates
().
ADVENTURE_EXP_RATE
;
return
Grasscutter
.
getConfig
().
getGameServerOptions
().
getGameRates
().
ADVENTURE_EXP_RATE
;
}
// Affected by exp rate
...
...
@@ -347,7 +352,6 @@ public class GenshinPlayer {
public
PlayerProfile
getProfile
()
{
if
(
this
.
playerProfile
==
null
)
{
this
.
playerProfile
=
new
PlayerProfile
(
this
);
this
.
save
();
}
return
playerProfile
;
}
...
...
@@ -654,6 +658,13 @@ public class GenshinPlayer {
return
social
;
}
public
WorldPlayerLocationInfo
getWorldPlayerLocationInfo
()
{
return
WorldPlayerLocationInfo
.
newBuilder
()
.
setSceneId
(
this
.
getSceneId
())
.
setPlayerLoc
(
this
.
getPlayerLocationInfo
())
.
build
();
}
public
PlayerLocationInfo
getPlayerLocationInfo
()
{
return
PlayerLocationInfo
.
newBuilder
()
.
setUid
(
this
.
getUid
())
...
...
@@ -679,10 +690,23 @@ public class GenshinPlayer {
}
// Ping
if
(
this
.
getWorld
()
!=
null
)
{
this
.
sendPacket
(
new
PacketWorldPlayerRTTNotify
(
this
.
getWorld
()));
// Player ping
// RTT notify - very important to send this often
this
.
sendPacket
(
new
PacketWorldPlayerRTTNotify
(
this
.
getWorld
()));
// Update player locations if in multiplayer every 5 seconds
long
time
=
System
.
currentTimeMillis
();
if
(
this
.
getWorld
().
isMultiplayer
()
&&
this
.
getScene
()
!=
null
&&
time
>
nextSendPlayerLocTime
)
{
this
.
sendPacket
(
new
PacketWorldPlayerLocationNotify
(
this
.
getWorld
()));
this
.
sendPacket
(
new
PacketScenePlayerLocationNotify
(
this
.
getScene
()));
this
.
resetSendPlayerLocTime
();
}
}
}
public
void
resetSendPlayerLocTime
()
{
this
.
nextSendPlayerLocTime
=
System
.
currentTimeMillis
()
+
5000
;
}
@PostLoad
private
void
onLoad
()
{
this
.
getTeamManager
().
setPlayer
(
this
);
...
...
@@ -696,12 +720,8 @@ public class GenshinPlayer {
// Make sure these exist
if
(
this
.
getTeamManager
()
==
null
)
{
this
.
teamManager
=
new
TeamManager
(
this
);
}
if
(
this
.
getGachaInfo
()
==
null
)
{
this
.
gachaInfo
=
new
PlayerGachaInfo
();
}
if
(
this
.
nameCardList
==
null
)
{
this
.
nameCardList
=
new
HashSet
<>();
}
if
(
this
.
costumeList
==
null
)
{
this
.
costumeList
=
new
HashSet
<>();
}
if
(
this
.
getProfile
().
getUid
()
==
0
)
{
this
.
getProfile
().
syncWithCharacter
(
this
);
}
// Check if player object exists in server
...
...
src/main/java/emu/grasscutter/game/GenshinScene.java
View file @
716380a0
...
...
@@ -34,6 +34,7 @@ public class GenshinScene {
private
int
time
;
private
ClimateType
climate
;
private
int
weather
;
public
GenshinScene
(
World
world
,
SceneData
sceneData
)
{
this
.
world
=
world
;
...
...
@@ -89,10 +90,18 @@ public class GenshinScene {
return
climate
;
}
public
int
getWeather
()
{
return
weather
;
}
public
void
setClimate
(
ClimateType
climate
)
{
this
.
climate
=
climate
;
}
public
void
setWeather
(
int
weather
)
{
this
.
weather
=
weather
;
}
public
boolean
isInScene
(
GenshinEntity
entity
)
{
return
this
.
entities
.
containsKey
(
entity
.
getId
());
}
...
...
src/main/java/emu/grasscutter/game/TeamInfo.java
View file @
716380a0
...
...
@@ -15,7 +15,7 @@ public class TeamInfo {
public
TeamInfo
()
{
this
.
name
=
""
;
this
.
avatars
=
new
ArrayList
<>(
Grasscutter
.
getConfig
().
getServerOptions
().
MaxAvatarsInTeam
);
this
.
avatars
=
new
ArrayList
<>(
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
MaxAvatarsInTeam
);
}
public
String
getName
()
{
...
...
@@ -39,7 +39,7 @@ public class TeamInfo {
}
public
boolean
addAvatar
(
GenshinAvatar
avatar
)
{
if
(
size
()
>=
Grasscutter
.
getConfig
().
getServerOptions
().
MaxAvatarsInTeam
||
contains
(
avatar
))
{
if
(
size
()
>=
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
MaxAvatarsInTeam
||
contains
(
avatar
))
{
return
false
;
}
...
...
@@ -59,7 +59,7 @@ public class TeamInfo {
}
public
void
copyFrom
(
TeamInfo
team
)
{
copyFrom
(
team
,
Grasscutter
.
getConfig
().
getServerOptions
().
MaxAvatarsInTeam
);
copyFrom
(
team
,
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
MaxAvatarsInTeam
);
}
public
void
copyFrom
(
TeamInfo
team
,
int
maxTeamSize
)
{
...
...
src/main/java/emu/grasscutter/game/TeamManager.java
View file @
716380a0
...
...
@@ -166,13 +166,13 @@ public class TeamManager {
public
int
getMaxTeamSize
()
{
if
(
getPlayer
().
isInMultiplayer
())
{
int
max
=
Grasscutter
.
getConfig
().
getServerOptions
().
MaxAvatarsInTeamMultiplayer
;
int
max
=
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
MaxAvatarsInTeamMultiplayer
;
if
(
getPlayer
().
getWorld
().
getHost
()
==
this
.
getPlayer
())
{
return
Math
.
max
(
1
,
(
int
)
Math
.
ceil
(
max
/
(
double
)
getWorld
().
getPlayerCount
()));
}
return
Math
.
max
(
1
,
(
int
)
Math
.
floor
(
max
/
(
double
)
getWorld
().
getPlayerCount
()));
}
return
Grasscutter
.
getConfig
().
getServerOptions
().
MaxAvatarsInTeam
;
return
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
MaxAvatarsInTeam
;
}
// Methods
...
...
src/main/java/emu/grasscutter/game/World.java
View file @
716380a0
...
...
@@ -207,31 +207,15 @@ public class World implements Iterable<GenshinPlayer> {
this
.
getScenes
().
remove
(
scene
.
getId
());
}
public
boolean
forceTransferPlayerToScene
(
GenshinPlayer
player
,
int
sceneId
,
Position
pos
)
{
// Forces the client to reload the scene map to prevent the player from falling off the map.
public
boolean
transferPlayerToScene
(
GenshinPlayer
player
,
int
sceneId
,
Position
pos
)
{
if
(
GenshinData
.
getSceneDataMap
().
get
(
sceneId
)
==
null
)
{
return
false
;
}
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
;
}
public
boolean
transferPlayerToScene
(
GenshinPlayer
player
,
int
sceneId
,
Position
pos
)
{
if
(
player
.
getScene
().
getId
()
==
sceneId
||
GenshinData
.
getSceneDataMap
().
get
(
sceneId
)
==
null
)
{
return
false
;
}
Integer
oldSceneId
=
null
;
if
(
player
.
getScene
()
!=
null
)
{
oldSceneId
=
player
.
getScene
().
getId
();
player
.
getScene
().
removePlayer
(
player
);
}
...
...
@@ -240,7 +224,11 @@ public class World implements Iterable<GenshinPlayer> {
player
.
getPos
().
set
(
pos
);
// Teleport packet
player
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
player
,
EnterType
.
EnterSelf
,
EnterReason
.
TransPoint
,
sceneId
,
pos
));
if
(
oldSceneId
.
equals
(
sceneId
))
{
player
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
player
,
EnterType
.
EnterGoto
,
EnterReason
.
TransPoint
,
sceneId
,
pos
));
}
else
{
player
.
sendPacket
(
new
PacketPlayerEnterSceneNotify
(
player
,
EnterType
.
EnterJump
,
EnterReason
.
TransPoint
,
sceneId
,
pos
));
}
return
true
;
}
...
...
src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java
View file @
716380a0
package
emu.grasscutter.game.avatar
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
...
...
@@ -13,6 +15,7 @@ import dev.morphia.annotations.Indexed;
import
dev.morphia.annotations.PostLoad
;
import
dev.morphia.annotations.PrePersist
;
import
dev.morphia.annotations.Transient
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.data.GenshinData
;
import
emu.grasscutter.data.common.FightPropData
;
import
emu.grasscutter.data.custom.OpenConfigEntry
;
...
...
@@ -38,10 +41,13 @@ import emu.grasscutter.game.inventory.EquipType;
import
emu.grasscutter.game.inventory.GenshinItem
;
import
emu.grasscutter.game.props.ElementType
;
import
emu.grasscutter.game.props.EntityIdType
;
import
emu.grasscutter.game.props.FetterState
;
import
emu.grasscutter.game.props.FightProperty
;
import
emu.grasscutter.game.props.PlayerProperty
;
import
emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo
;
import
emu.grasscutter.net.proto.FetterDataOuterClass.FetterData
;
import
emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo
;
import
emu.grasscutter.server.packet.send.PacketAbilityChangeNotify
;
import
emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify
;
import
emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify
;
import
emu.grasscutter.utils.ProtoHelper
;
...
...
@@ -69,7 +75,9 @@ public class GenshinAvatar {
@Transient
private
final
Int2ObjectMap
<
GenshinItem
>
equips
;
@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
>
proudSkillBonusMap
;
// Talent bonus levels (from const)
...
...
@@ -86,8 +94,9 @@ public class GenshinAvatar {
// Morhpia only!
this
.
equips
=
new
Int2ObjectOpenHashMap
<>();
this
.
fightProp
=
new
Int2FloatOpenHashMap
();
this
.
bonusAbilityList
=
new
HashSet
<>();
this
.
proudSkillBonusMap
=
new
HashMap
<>();
// TODO Move to genshin avatar
this
.
extraAbilityEmbryos
=
new
HashSet
<>();
this
.
proudSkillBonusMap
=
new
HashMap
<>();
this
.
fetters
=
new
ArrayList
<>();
// TODO Move to genshin avatar
}
// On creation
...
...
@@ -260,8 +269,16 @@ public class GenshinAvatar {
return
proudSkillBonusMap
;
}
public
Set
<
String
>
getBonusAbilityList
()
{
return
bonusAbilityList
;
public
Set
<
String
>
getExtraAbilityEmbryos
()
{
return
extraAbilityEmbryos
;
}
public
void
setFetterList
(
List
<
Integer
>
fetterList
)
{
this
.
fetters
=
fetterList
;
}
public
List
<
Integer
>
getFetterList
()
{
return
fetters
;
}
public
float
getCurrentHp
()
{
...
...
@@ -347,14 +364,14 @@ public class GenshinAvatar {
item
.
setEquipCharacter
(
this
.
getAvatarId
());
item
.
save
();
if
(
shouldRecalc
)
{
this
.
recalcStats
();
}
if
(
this
.
getPlayer
().
hasSentAvatarDataNotify
())
{
this
.
getPlayer
().
sendPacket
(
new
PacketAvatarEquipChangeNotify
(
this
,
item
));
}
if
(
shouldRecalc
)
{
this
.
recalcStats
();
}
return
true
;
}
...
...
@@ -371,11 +388,21 @@ public class GenshinAvatar {
}
public
void
recalcStats
()
{
recalcStats
(
false
);
}
public
void
recalcStats
(
boolean
forceSendAbilityChange
)
{
// Setup
AvatarData
data
=
this
.
getAvatarData
();
AvatarPromoteData
promoteData
=
GenshinData
.
getAvatarPromoteData
(
data
.
getAvatarPromoteId
(),
this
.
getPromoteLevel
());
Int2IntOpenHashMap
setMap
=
new
Int2IntOpenHashMap
();
this
.
getBonusAbilityList
().
clear
();
// Extra ability embryos
Set
<
String
>
prevExtraAbilityEmbryos
=
this
.
getExtraAbilityEmbryos
();
this
.
extraAbilityEmbryos
=
new
HashSet
<>();
// Fetters
this
.
setFetterList
(
data
.
getFetters
());
// Get hp percent, set to 100% if none
float
hpPercent
=
this
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_MAX_HP
)
<=
0
?
1
f
:
this
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_CUR_HP
)
/
this
.
getFightProperty
(
FightProperty
.
FIGHT_PROP_MAX_HP
);
...
...
@@ -458,7 +485,7 @@ public class GenshinAvatar {
}
// Add any skill strings from this affix
this
.
addToAbility
List
(
affix
.
getOpenConfig
(),
true
);
this
.
addTo
Extra
Ability
Embryos
(
affix
.
getOpenConfig
(),
true
);
}
else
{
break
;
}
...
...
@@ -505,7 +532,7 @@ public class GenshinAvatar {
}
// Add any skill strings from this affix
this
.
addToAbility
List
(
affix
.
getOpenConfig
(),
true
);
this
.
addTo
Extra
Ability
Embryos
(
affix
.
getOpenConfig
(),
true
);
}
}
}
...
...
@@ -538,7 +565,7 @@ public class GenshinAvatar {
}
// Add any skill strings from this proud skill
this
.
addToAbility
List
(
proudSkillData
.
getOpenConfig
(),
true
);
this
.
addTo
Extra
Ability
Embryos
(
proudSkillData
.
getOpenConfig
(),
true
);
}
// Constellations
...
...
@@ -550,7 +577,7 @@ public class GenshinAvatar {
}
// Add any skill strings from this constellation
this
.
addToAbility
List
(
avatarTalentData
.
getOpenConfig
(),
false
);
this
.
addTo
Extra
Ability
Embryos
(
avatarTalentData
.
getOpenConfig
(),
false
);
}
}
...
...
@@ -573,11 +600,17 @@ public class GenshinAvatar {
// Packet
if
(
getPlayer
()
!=
null
&&
getPlayer
().
hasSentAvatarDataNotify
())
{
// Update stats for client
getPlayer
().
sendPacket
(
new
PacketAvatarFightPropNotify
(
this
));
// Update client abilities
EntityAvatar
entity
=
this
.
getAsEntity
();
if
(
entity
!=
null
&&
(!
this
.
getExtraAbilityEmbryos
().
equals
(
prevExtraAbilityEmbryos
)
||
forceSendAbilityChange
))
{
getPlayer
().
sendPacket
(
new
PacketAbilityChangeNotify
(
entity
));
}
}
}
public
void
addToAbility
List
(
String
openConfig
,
boolean
forceAdd
)
{
public
void
addTo
Extra
Ability
Embryos
(
String
openConfig
,
boolean
forceAdd
)
{
if
(
openConfig
==
null
||
openConfig
.
length
()
==
0
)
{
return
;
}
...
...
@@ -586,14 +619,14 @@ public class GenshinAvatar {
if
(
entry
==
null
)
{
if
(
forceAdd
)
{
// Add config string to ability skill list anyways
this
.
get
Bonus
Ability
List
().
add
(
openConfig
);
this
.
get
Extra
Ability
Embryos
().
add
(
openConfig
);
}
return
;
}
if
(
entry
.
getAddAbilities
()
!=
null
)
{
for
(
String
ability
:
entry
.
getAddAbilities
())
{
this
.
get
Bonus
Ability
List
().
add
(
ability
);
this
.
get
Extra
Ability
Embryos
().
add
(
ability
);
}
}
}
...
...
@@ -668,6 +701,20 @@ public class GenshinAvatar {
}
public
AvatarInfo
toProto
()
{
AvatarFetterInfo
.
Builder
avatarFetter
=
AvatarFetterInfo
.
newBuilder
()
.
setExpLevel
(
10
)
.
setExpNumber
(
6325
);
// Highest Level
if
(
this
.
getFetterList
()
!=
null
)
{
for
(
int
i
=
0
;
i
<
this
.
getFetterList
().
size
();
i
++)
{
avatarFetter
.
addFetterList
(
FetterData
.
newBuilder
()
.
setFetterId
(
this
.
getFetterList
().
get
(
i
))
.
setFetterState
(
FetterState
.
FINISH
.
getValue
())
);
}
}
AvatarInfo
.
Builder
avatarInfo
=
AvatarInfo
.
newBuilder
()
.
setAvatarId
(
this
.
getAvatarId
())
.
setGuid
(
this
.
getGuid
())
...
...
@@ -681,7 +728,7 @@ public class GenshinAvatar {
.
putAllProudSkillExtraLevel
(
getProudSkillBonusMap
())
.
setAvatarType
(
1
)
.
setBornTime
(
this
.
getBornTime
())
.
setFetterInfo
(
A
vatarFetter
Info
.
newBuilder
().
setExpLevel
(
1
)
)
.
setFetterInfo
(
a
vatarFetter
)
.
setWearingFlycloakId
(
this
.
getFlyCloak
())
.
setCostumeId
(
this
.
getCostume
());
...
...
src/main/java/emu/grasscutter/game/entity/EntityAvatar.java
View file @
716380a0
...
...
@@ -223,8 +223,8 @@ public class EntityAvatar extends GenshinEntity {
}
}
// Add equip abilities
if
(
this
.
getAvatar
().
get
Bonus
Ability
List
().
size
()
>
0
)
{
for
(
String
skill
:
this
.
getAvatar
().
get
Bonus
Ability
List
())
{
if
(
this
.
getAvatar
().
get
Extra
Ability
Embryos
().
size
()
>
0
)
{
for
(
String
skill
:
this
.
getAvatar
().
get
Extra
Ability
Embryos
())
{
AbilityEmbryo
emb
=
AbilityEmbryo
.
newBuilder
()
.
setAbilityId
(++
embryoId
)
.
setAbilityNameHash
(
Utils
.
abilityHash
(
skill
))
...
...
src/main/java/emu/grasscutter/game/friends/FriendsList.java
View file @
716380a0
...
...
@@ -220,7 +220,7 @@ public class FriendsList {
friendship
.
setOwner
(
getPlayer
());
// Check if friend is online
GenshinPlayer
friend
=
getPlayer
().
getSession
().
getServer
().
getPlayerByUid
(
friendship
.
getFriendProfile
().
get
I
d
());
GenshinPlayer
friend
=
getPlayer
().
getSession
().
getServer
().
getPlayerByUid
(
friendship
.
getFriendProfile
().
get
Ui
d
());
if
(
friend
!=
null
)
{
// Set friend to online mode
friendship
.
setFriendProfile
(
friend
);
...
...
src/main/java/emu/grasscutter/game/friends/Friendship.java
View file @
716380a0
...
...
@@ -88,7 +88,7 @@ public class Friendship {
public
FriendBrief
toProto
()
{
FriendBrief
proto
=
FriendBrief
.
newBuilder
()
.
setUid
(
getFriendProfile
().
get
I
d
())
.
setUid
(
getFriendProfile
().
get
Ui
d
())
.
setNickname
(
getFriendProfile
().
getName
())
.
setLevel
(
getFriendProfile
().
getPlayerLevel
())
.
setAvatar
(
HeadImage
.
newBuilder
().
setAvatarId
(
getFriendProfile
().
getAvatarId
()))
...
...
src/main/java/emu/grasscutter/game/friends/PlayerProfile.java
View file @
716380a0
...
...
@@ -8,7 +8,7 @@ import emu.grasscutter.utils.Utils;
public
class
PlayerProfile
{
@Transient
private
GenshinPlayer
player
;
private
int
id
;
@AlsoLoad
(
"id"
)
private
int
u
id
;
private
int
nameCard
;
private
int
avatarId
;
private
String
name
;
...
...
@@ -23,12 +23,12 @@ public class PlayerProfile {
public
PlayerProfile
()
{
}
public
PlayerProfile
(
GenshinPlayer
player
)
{
this
.
id
=
player
.
getUid
();
this
.
u
id
=
player
.
getUid
();
this
.
syncWithCharacter
(
player
);
}
public
int
get
I
d
()
{
return
id
;
public
int
get
Ui
d
()
{
return
u
id
;
}
public
GenshinPlayer
getPlayer
()
{
...
...
@@ -88,6 +88,7 @@ public class PlayerProfile {
return
;
}
this
.
uid
=
player
.
getUid
();
this
.
name
=
player
.
getNickname
();
this
.
avatarId
=
player
.
getHeadImage
();
this
.
signature
=
player
.
getSignature
();
...
...
src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
View file @
716380a0
...
...
@@ -92,7 +92,7 @@ public class GachaBanner {
}
public
GachaInfo
toProto
()
{
String
record
=
"http://"
+
(
Grasscutter
.
getConfig
().
Dispatch
Server
PublicIp
.
isEmpty
()
?
Grasscutter
.
getConfig
().
Dispatch
Server
Ip
:
Grasscutter
.
getConfig
().
Dispatch
Server
PublicIp
)
+
"/gacha"
;
String
record
=
"http://"
+
(
Grasscutter
.
getConfig
().
get
Dispatch
Options
().
PublicIp
.
isEmpty
()
?
Grasscutter
.
getConfig
().
get
Dispatch
Options
().
Ip
:
Grasscutter
.
getConfig
().
get
Dispatch
Options
().
PublicIp
)
+
"/gacha"
;
GachaInfo
.
Builder
info
=
GachaInfo
.
newBuilder
()
.
setGachaType
(
this
.
getGachaType
())
...
...
src/main/java/emu/grasscutter/game/gacha/GachaManager.java
View file @
716380a0
...
...
@@ -218,7 +218,6 @@ public class GachaManager {
addStarglitter
=
2
;
// Add 1 const
gachaItem
.
addTransferItems
(
GachaTransferItem
.
newBuilder
().
setItem
(
ItemParam
.
newBuilder
().
setItemId
(
constItemId
).
setCount
(
1
)).
setIsTransferItemNew
(
constItem
==
null
));
gachaItem
.
addTokenItemList
(
ItemParam
.
newBuilder
().
setItemId
(
constItemId
).
setCount
(
1
));
player
.
getInventory
().
addItem
(
constItemId
,
1
);
}
else
{
// Is max const
...
...
@@ -300,7 +299,7 @@ public class GachaManager {
@Subscribe
public
synchronized
void
watchBannerJson
(
GameServerTickEvent
tickEvent
)
{
if
(
Grasscutter
.
getConfig
().
getServerOptions
().
WatchGacha
)
{
if
(
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
WatchGacha
)
{
try
{
WatchKey
watchKey
=
watchService
.
take
();
...
...
src/main/java/emu/grasscutter/game/inventory/Inventory.java
View file @
716380a0
...
...
@@ -37,10 +37,10 @@ public class Inventory implements Iterable<GenshinItem> {
this
.
store
=
new
Long2ObjectOpenHashMap
<>();
this
.
inventoryTypes
=
new
Int2ObjectOpenHashMap
<>();
this
.
createInventoryTab
(
ItemType
.
ITEM_WEAPON
,
new
EquipInventoryTab
(
Grasscutter
.
getConfig
().
getServerOptions
().
InventoryLimitWeapon
));
this
.
createInventoryTab
(
ItemType
.
ITEM_RELIQUARY
,
new
EquipInventoryTab
(
Grasscutter
.
getConfig
().
getServerOptions
().
InventoryLimitRelic
));
this
.
createInventoryTab
(
ItemType
.
ITEM_MATERIAL
,
new
MaterialInventoryTab
(
Grasscutter
.
getConfig
().
getServerOptions
().
InventoryLimitMaterial
));
this
.
createInventoryTab
(
ItemType
.
ITEM_FURNITURE
,
new
MaterialInventoryTab
(
Grasscutter
.
getConfig
().
getServerOptions
().
InventoryLimitFurniture
));
this
.
createInventoryTab
(
ItemType
.
ITEM_WEAPON
,
new
EquipInventoryTab
(
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
InventoryLimitWeapon
));
this
.
createInventoryTab
(
ItemType
.
ITEM_RELIQUARY
,
new
EquipInventoryTab
(
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
InventoryLimitRelic
));
this
.
createInventoryTab
(
ItemType
.
ITEM_MATERIAL
,
new
MaterialInventoryTab
(
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
InventoryLimitMaterial
));
this
.
createInventoryTab
(
ItemType
.
ITEM_FURNITURE
,
new
MaterialInventoryTab
(
Grasscutter
.
getConfig
().
get
Game
ServerOptions
().
InventoryLimitFurniture
));
}
public
GenshinPlayer
getPlayer
()
{
...
...
src/main/java/emu/grasscutter/game/managers/InventoryManager.java
View file @
716380a0
...
...
@@ -589,7 +589,6 @@ public class InventoryManager {
// Update proud skills
AvatarSkillDepotData
skillDepot
=
GenshinData
.
getAvatarSkillDepotDataMap
().
get
(
avatar
.
getSkillDepotId
());
boolean
hasAddedProudSkill
=
false
;
if
(
skillDepot
!=
null
&&
skillDepot
.
getInherentProudSkillOpens
()
!=
null
)
{
for
(
InherentProudSkillOpens
openData
:
skillDepot
.
getInherentProudSkillOpens
())
{
...
...
@@ -599,7 +598,6 @@ public class InventoryManager {
if
(
openData
.
getNeedAvatarPromoteLevel
()
==
avatar
.
getPromoteLevel
())
{
int
proudSkillId
=
(
openData
.
getProudSkillGroupId
()
*
100
)
+
1
;
if
(
GenshinData
.
getProudSkillDataMap
().
containsKey
(
proudSkillId
))
{
hasAddedProudSkill
=
true
;
avatar
.
getProudSkillList
().
add
(
proudSkillId
);
player
.
sendPacket
(
new
PacketProudSkillChangeNotify
(
avatar
));
}
...
...
@@ -607,20 +605,13 @@ public class InventoryManager {
}
}
// Racalc stats and save avatar
avatar
.
recalcStats
();
avatar
.
save
();
// Resend ability embryos if proud skill has been added
if
(
hasAddedProudSkill
&&
avatar
.
getAsEntity
()
!=
null
)
{
player
.
sendPacket
(
new
PacketAbilityChangeNotify
(
avatar
.
getAsEntity
()));
}
// TODO Send entity prop update packet to world
// Packets
player
.
sendPacket
(
new
PacketAvatarPropNotify
(
avatar
));
player
.
sendPacket
(
new
PacketAvatarPromoteRsp
(
avatar
));
// TODO Send entity prop update packet to world
avatar
.
recalcStats
(
true
);
avatar
.
save
();
}
public
void
upgradeAvatar
(
GenshinPlayer
player
,
long
guid
,
int
itemId
,
int
count
)
{
...
...
@@ -827,25 +818,20 @@ public class InventoryManager {
// Apply + recalc
avatar
.
getTalentIdList
().
add
(
talentData
.
getId
());
avatar
.
setCoreProudSkillLevel
(
currentTalentLevel
+
1
);
avatar
.
recalcStats
();
// Packet
player
.
sendPacket
(
new
PacketAvatarUnlockTalentNotify
(
avatar
,
nextTalentId
));
player
.
sendPacket
(
new
PacketUnlockAvatarTalentRsp
(
avatar
,
nextTalentId
));
// Proud skill bonus map
// Proud skill bonus map
(Extra skills)
OpenConfigEntry
entry
=
GenshinData
.
getOpenConfigEntries
().
get
(
talentData
.
getOpenConfig
());
if
(
entry
!=
null
&&
entry
.
getExtraTalentIndex
()
>
0
)
{
avatar
.
recalcProudSkillBonusMap
();
player
.
sendPacket
(
new
PacketProudSkillExtraLevelNotify
(
avatar
,
entry
.
getExtraTalentIndex
()));
}
// Resend ability embryos
if
(
avatar
.
getAsEntity
()
!=
null
)
{
player
.
sendPacket
(
new
PacketAbilityChangeNotify
(
avatar
.
getAsEntity
()));
}
// Save avatar
// Recalc + save avatar
avatar
.
recalcStats
(
true
);
avatar
.
save
();
}
...
...
src/main/java/emu/grasscutter/game/props/FetterState.java
0 → 100644
View file @
716380a0
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
);
}
}
src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
View file @
716380a0
package
emu.grasscutter.server.dispatch
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.io.UnsupportedEncodingException
;
import
java.net.InetSocketAddress
;
import
java.net.URI
;
import
java.net.URLDecoder
;
import
java.security.KeyStore
;
import
java.util.Base64
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.Map
;
import
javax.net.ssl.KeyManagerFactory
;
import
javax.net.ssl.SSLContext
;
import
com.google.gson.Gson
;
import
com.google.gson.GsonBuilder
;
import
com.google.protobuf.ByteString
;
import
com.sun.net.httpserver.HttpExchange
;
import
com.sun.net.httpserver.Http
Handl
er
;
import
com.sun.net.httpserver.Http
Serv
er
;
import
com.sun.net.httpserver.HttpsConfigurator
;
import
com.sun.net.httpserver.HttpsServer
;
import
emu.grasscutter.Config
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.database.DatabaseHelper
;
import
emu.grasscutter.game.Account
;
...
...
@@ -32,30 +16,34 @@ import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegio
import
emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp
;
import
emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo
;
import
emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo
;
import
emu.grasscutter.server.dispatch.json.ComboTokenReqJson
;
import
emu.grasscutter.server.dispatch.json.ComboTokenResJson
;
import
emu.grasscutter.server.dispatch.json.LoginAccountRequestJson
;
import
emu.grasscutter.server.dispatch.json.LoginResultJson
;
import
emu.grasscutter.server.dispatch.json.LoginTokenRequestJson
;
import
emu.grasscutter.server.dispatch.json.*
;
import
emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
com.sun.net.httpserver.HttpServer
;
import
javax.net.ssl.KeyManagerFactory
;
import
javax.net.ssl.SSLContext
;
import
java.io.*
;
import
java.net.InetSocketAddress
;
import
java.net.URI
;
import
java.net.URLDecoder
;
import
java.security.KeyStore
;
import
java.util.*
;
public
final
class
DispatchServer
{
public
static
String
query_region_list
=
""
;
public
static
String
query_cur_region
=
""
;
private
final
InetSocketAddress
address
;
private
final
Gson
gson
;
private
QueryCurrRegionHttpRsp
currRegion
;
private
final
String
defaultServerName
=
"os_usa"
;
public
String
regionListBase64
;
public
String
regionCurrentBase64
;
public
static
String
query_region_list
=
""
;
public
static
String
query_cur_region
=
""
;
public
HashMap
<
String
,
RegionData
>
regions
;
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
.
loadQueries
();
...
...
@@ -71,7 +59,13 @@ public final class DispatchServer {
}
public
QueryCurrRegionHttpRsp
getCurrRegion
()
{
return
currRegion
;
// Needs to be fixed by having the game servers connect to the dispatch server.
if
(
Grasscutter
.
getConfig
().
RunMode
.
equalsIgnoreCase
(
"HYBRID"
))
{
return
regions
.
get
(
defaultServerName
).
parsedRegionQuery
;
}
Grasscutter
.
getLogger
().
warn
(
"[Dispatch] Unsupported run mode for getCurrRegion()"
);
return
null
;
}
public
void
loadQueries
()
{
...
...
@@ -81,14 +75,14 @@ public final class DispatchServer {
if
(
file
.
exists
())
{
query_region_list
=
new
String
(
FileUtils
.
read
(
file
));
}
else
{
Grasscutter
.
getLogger
().
warn
(
"query_region_list not found! Using default region list."
);
Grasscutter
.
getLogger
().
warn
(
"
[Dispatch]
query_region_list not found! Using default region list."
);
}
file
=
new
File
(
Grasscutter
.
getConfig
().
DATA_FOLDER
+
"query_cur_region.txt"
);
if
(
file
.
exists
())
{
query_cur_region
=
new
String
(
FileUtils
.
read
(
file
));
}
else
{
Grasscutter
.
getLogger
().
warn
(
"query_cur_region not found! Using default current region."
);
Grasscutter
.
getLogger
().
warn
(
"
[Dispatch]
query_cur_region not found! Using default current region."
);
}
}
...
...
@@ -100,52 +94,79 @@ public final class DispatchServer {
byte
[]
decoded2
=
Base64
.
getDecoder
().
decode
(
query_cur_region
);
QueryCurrRegionHttpRsp
regionQuery
=
QueryCurrRegionHttpRsp
.
parseFrom
(
decoded2
);
List
<
RegionSimpleInfo
>
servers
=
new
ArrayList
<
RegionSimpleInfo
>();
List
<
String
>
usedNames
=
new
ArrayList
<
String
>();
// List to check for potential naming conflicts
if
(
Grasscutter
.
getConfig
().
RunMode
.
equalsIgnoreCase
(
"HYBRID"
))
{
// Automatically add the game server if in hybrid mode
RegionSimpleInfo
server
=
RegionSimpleInfo
.
newBuilder
()
.
setName
(
"os_usa"
)
.
setTitle
(
Grasscutter
.
getConfig
().
GameServerName
)
.
setTitle
(
Grasscutter
.
getConfig
().
get
GameServer
Options
().
Name
)
.
setType
(
"DEV_PUBLIC"
)
.
setDispatchUrl
(
"https://"
+
(
Grasscutter
.
getConfig
().
DispatchServerPublicIp
.
isEmpty
()
?
Grasscutter
.
getConfig
().
DispatchServerIp
:
Grasscutter
.
getConfig
().
DispatchServerPublicIp
)
+
":"
+
getAddress
().
getPort
()
+
"/query_cur_region"
)
.
setDispatchUrl
(
"http"
+
(
Grasscutter
.
getConfig
().
getDispatchOptions
().
FrontHTTPS
?
"s"
:
""
)
+
"://"
+
(
Grasscutter
.
getConfig
().
getDispatchOptions
().
PublicIp
.
isEmpty
()
?
Grasscutter
.
getConfig
().
getDispatchOptions
().
Ip
:
Grasscutter
.
getConfig
().
getDispatchOptions
().
PublicIp
)
+
":"
+
(
Grasscutter
.
getConfig
().
getDispatchOptions
().
PublicPort
!=
0
?
Grasscutter
.
getConfig
().
getDispatchOptions
().
PublicPort
:
Grasscutter
.
getConfig
().
getDispatchOptions
().
Port
)
+
"/query_cur_region_"
+
defaultServerName
)
.
build
();
usedNames
.
add
(
defaultServerName
);
servers
.
add
(
server
);
RegionInfo
serverRegion
=
regionQuery
.
getRegionInfo
().
toBuilder
()
.
setIp
((
Grasscutter
.
getConfig
().
getGameServerOptions
().
PublicIp
.
isEmpty
()
?
Grasscutter
.
getConfig
().
getGameServerOptions
().
Ip
:
Grasscutter
.
getConfig
().
getGameServerOptions
().
PublicIp
))
.
setPort
(
Grasscutter
.
getConfig
().
getGameServerOptions
().
PublicPort
!=
0
?
Grasscutter
.
getConfig
().
getGameServerOptions
().
PublicPort
:
Grasscutter
.
getConfig
().
getGameServerOptions
().
Port
)
.
setSecretKey
(
ByteString
.
copyFrom
(
FileUtils
.
read
(
Grasscutter
.
getConfig
().
KEY_FOLDER
+
"dispatchSeed.bin"
)))
.
build
();
RegionSimpleInfo
serverTest2
=
RegionSimpleInfo
.
newBuilder
()
.
setName
(
"os_euro"
)
.
setTitle
(
"Grasscutter"
)
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
s://
"
+
(
Grasscutter
.
getConfig
().
Dispatch
Server
PublicIp
.
isEmpty
()
?
Grasscutter
.
getConfig
().
Dispatch
Server
Ip
:
Grasscutter
.
getConfig
().
Dispatch
Server
PublicIp
)
+
":"
+
getAddress
().
getPort
()
+
"/query_cur_region
"
)
.
setDispatchUrl
(
"http"
+
(
Grasscutter
.
getConfig
().
get
Dispatch
Options
().
FrontHTTPS
?
"s"
:
""
)
+
"://"
+
(
Grasscutter
.
getConfig
().
getDispatchOptions
().
PublicIp
.
isEmpty
()
?
Grasscutter
.
getConfig
().
get
Dispatch
Options
().
Ip
:
Grasscutter
.
getConfig
().
get
Dispatch
Options
().
PublicIp
)
+
":"
+
getAddress
().
getPort
()
+
"/query_cur_region
_"
+
regionInfo
.
Name
)
.
build
();
usedNames
.
add
(
regionInfo
.
Name
);
servers
.
add
(
server
);
RegionInfo
serverRegion
=
regionQuery
.
getRegionInfo
().
toBuilder
()
.
setIp
(
regionInfo
.
Ip
)
.
setPort
(
regionInfo
.
Port
)
.
setSecretKey
(
ByteString
.
copyFrom
(
FileUtils
.
read
(
Grasscutter
.
getConfig
().
KEY_FOLDER
+
"dispatchSeed.bin"
)))
.
build
();
QueryCurrRegionHttpRsp
parsedRegionQuery
=
regionQuery
.
toBuilder
().
setRegionInfo
(
serverRegion
).
build
();
regions
.
put
(
regionInfo
.
Name
,
new
RegionData
(
parsedRegionQuery
,
Base64
.
getEncoder
().
encodeToString
(
parsedRegionQuery
.
toByteString
().
toByteArray
())));
}
QueryRegionListHttpRsp
regionList
=
QueryRegionListHttpRsp
.
newBuilder
()
.
addServers
(
server
)
.
addServers
(
serverTest2
)
.
addAllServers
(
servers
)
.
setClientSecretKey
(
rl
.
getClientSecretKey
())
.
setClientCustomConfigEncrypted
(
rl
.
getClientCustomConfigEncrypted
())
.
setEnableLoginPc
(
true
)
.
build
();
RegionInfo
currentRegion
=
regionQuery
.
getRegionInfo
().
toBuilder
()
.
setIp
((
Grasscutter
.
getConfig
().
GameServerPublicIp
.
isEmpty
()
?
Grasscutter
.
getConfig
().
GameServerIp
:
Grasscutter
.
getConfig
().
GameServerPublicIp
))
.
setPort
(
Grasscutter
.
getConfig
().
GameServerPort
)
.
setSecretKey
(
ByteString
.
copyFrom
(
FileUtils
.
read
(
Grasscutter
.
getConfig
().
KEY_FOLDER
+
"dispatchSeed.bin"
)))
.
build
();
QueryCurrRegionHttpRsp
parsedRegionQuery
=
regionQuery
.
toBuilder
().
setRegionInfo
(
currentRegion
).
build
();
this
.
regionListBase64
=
Base64
.
getEncoder
().
encodeToString
(
regionList
.
toByteString
().
toByteArray
());
this
.
regionCurrentBase64
=
Base64
.
getEncoder
().
encodeToString
(
parsedRegionQuery
.
toByteString
().
toByteArray
());
this
.
currRegion
=
parsedRegionQuery
;
}
catch
(
Exception
e
)
{
Grasscutter
.
getLogger
().
error
(
"Error while initializing region info!"
,
e
);
Grasscutter
.
getLogger
().
error
(
"
[Dispatch]
Error while initializing region info!"
,
e
);
}
}
public
void
start
()
throws
Exception
{
HttpServer
server
;
if
(
Grasscutter
.
getConfig
().
UseSSL
)
{
if
(
Grasscutter
.
getConfig
().
getDispatchOptions
().
UseSSL
)
{
HttpsServer
httpsServer
;
httpsServer
=
HttpsServer
.
create
(
getAddress
(),
0
);
SSLContext
sslContext
=
SSLContext
.
getInstance
(
"TLS"
);
try
(
FileInputStream
fis
=
new
FileInputStream
(
Grasscutter
.
getConfig
().
Dispatch
Server
KeystorePath
))
{
char
[]
keystorePassword
=
Grasscutter
.
getConfig
().
Dispatch
Server
KeystorePassword
.
toCharArray
();
try
(
FileInputStream
fis
=
new
FileInputStream
(
Grasscutter
.
getConfig
().
get
Dispatch
Options
().
KeystorePath
))
{
char
[]
keystorePassword
=
Grasscutter
.
getConfig
().
get
Dispatch
Options
().
KeystorePassword
.
toCharArray
();
KeyStore
ks
=
KeyStore
.
getInstance
(
"PKCS12"
);
ks
.
load
(
fis
,
keystorePassword
);
KeyManagerFactory
kmf
=
KeyManagerFactory
.
getInstance
(
"SunX509"
);
...
...
@@ -156,56 +177,39 @@ public final class DispatchServer {
httpsServer
.
setHttpsConfigurator
(
new
HttpsConfigurator
(
sslContext
));
server
=
httpsServer
;
}
catch
(
Exception
e
)
{
Grasscutter
.
getLogger
().
error
(
"No SSL cert found!"
);
return
;
Grasscutter
.
getLogger
().
warn
(
"[Dispatch] No SSL cert found! Falling back to HTTP server."
);
Grasscutter
.
getConfig
().
getDispatchOptions
().
UseSSL
=
false
;
server
=
HttpServer
.
create
(
getAddress
(),
0
);
}
}
else
{
server
=
HttpServer
.
create
(
getAddress
(),
0
);
}
server
.
createContext
(
"/"
,
t
->
{
//Create a response form the request query parameters
String
response
=
"Hello"
;
//Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"text/html; charset=UTF-8"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
//Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
});
server
.
createContext
(
"/"
,
t
->
responseHTML
(
t
,
"Hello"
));
// Dispatch
server
.
createContext
(
"/query_region_list"
,
t
->
{
// Log
Grasscutter
.
getLogger
().
info
(
"Client request: query_region_list"
);
// Create a response form the request query parameters
String
response
=
regionListBase64
;
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"text/html; charset=UTF-8"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s request: query_region_list"
,
t
.
getRemoteAddress
()));
responseHTML
(
t
,
regionListBase64
);
});
server
.
createContext
(
"/query_cur_region"
,
t
->
{
for
(
String
regionName
:
regions
.
keySet
())
{
server
.
createContext
(
"/query_cur_region_"
+
regionName
,
t
->
{
String
regionCurrentBase64
=
regions
.
get
(
regionName
).
Base64
;
// Log
Grasscutter
.
getLogger
().
info
(
"Client request: query_cur_region
"
);
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
;
}
// 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
();
responseHTML
(
t
,
response
);
});
}
// Login via account
server
.
createContext
(
"/hk4e_global/mdk/shield/api/login"
,
t
->
{
// Get post data
...
...
@@ -213,32 +217,44 @@ public final class DispatchServer {
try
{
String
body
=
Utils
.
toString
(
t
.
getRequestBody
());
requestData
=
getGsonFactory
().
fromJson
(
body
,
LoginAccountRequestJson
.
class
);
}
catch
(
Exception
e
)
{
}
catch
(
Exception
ignored
)
{
}
}
// Create response json
if
(
requestData
==
null
)
{
return
;
}
LoginResultJson
responseData
=
new
LoginResultJson
();
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s is trying to log in"
,
t
.
getRemoteAddress
()));
// Login
Account
account
=
DatabaseHelper
.
getAccountByName
(
requestData
.
account
);
// Check if account exists, else create a new one.
if
(
account
==
null
)
{
// Account doesnt exist, so we can either auto create it if the config value is set
if
(
Grasscutter
.
getConfig
().
Server
Options
.
AutomaticallyCreateAccounts
)
{
if
(
Grasscutter
.
getConfig
().
getDispatch
Options
()
.
AutomaticallyCreateAccounts
)
{
// This account has been created AUTOMATICALLY. There will be no permissions added.
account
=
DatabaseHelper
.
createAccountWithId
(
requestData
.
account
,
0
);
if
(
account
!=
null
)
{
responseData
.
message
=
"OK"
;
responseData
.
data
.
account
.
uid
=
account
.
getId
();
responseData
.
data
.
account
.
token
=
account
.
generateSessionKey
();
responseData
.
data
.
account
.
email
=
account
.
getEmail
();
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s failed to log in: Account %s created"
,
t
.
getRemoteAddress
(),
responseData
.
data
.
account
.
uid
));
}
else
{
responseData
.
retcode
=
-
201
;
responseData
.
message
=
"Username not found, create failed."
;
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s failed to log in: Account create failed"
,
t
.
getRemoteAddress
()));
}
}
else
{
responseData
.
retcode
=
-
201
;
responseData
.
message
=
"Username not found."
;
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s failed to log in: Account no found"
,
t
.
getRemoteAddress
()));
}
}
else
{
// Account was found, log the player in
...
...
@@ -246,17 +262,11 @@ public final class DispatchServer {
responseData
.
data
.
account
.
uid
=
account
.
getId
();
responseData
.
data
.
account
.
token
=
account
.
generateSessionKey
();
responseData
.
data
.
account
.
email
=
account
.
getEmail
();
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s logged in as %s"
,
t
.
getRemoteAddress
(),
responseData
.
data
.
account
.
uid
));
}
// Create a response
String
response
=
getGsonFactory
().
toJson
(
responseData
);
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"application/json"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
responseJSON
(
t
,
responseData
);
});
// Login via token
server
.
createContext
(
"/hk4e_global/mdk/shield/api/verify"
,
t
->
{
...
...
@@ -265,14 +275,14 @@ public final class DispatchServer {
try
{
String
body
=
Utils
.
toString
(
t
.
getRequestBody
());
requestData
=
getGsonFactory
().
fromJson
(
body
,
LoginTokenRequestJson
.
class
);
}
catch
(
Exception
e
)
{
}
catch
(
Exception
ignored
)
{
}
}
// Create response json
if
(
requestData
==
null
)
{
return
;
}
LoginResultJson
responseData
=
new
LoginResultJson
();
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s is trying to log in via token"
,
t
.
getRemoteAddress
()));
// Login
Account
account
=
DatabaseHelper
.
getAccountById
(
requestData
.
uid
);
...
...
@@ -281,22 +291,18 @@ public final class DispatchServer {
if
(
account
==
null
||
!
account
.
getSessionKey
().
equals
(
requestData
.
token
))
{
responseData
.
retcode
=
-
111
;
responseData
.
message
=
"Game account cache information error"
;
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s failed to log in via token"
,
t
.
getRemoteAddress
()));
}
else
{
responseData
.
message
=
"OK"
;
responseData
.
data
.
account
.
uid
=
requestData
.
uid
;
responseData
.
data
.
account
.
token
=
requestData
.
token
;
responseData
.
data
.
account
.
email
=
account
.
getEmail
();
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s logged in via token as %s"
,
t
.
getRemoteAddress
(),
responseData
.
data
.
account
.
uid
));
}
// Create a response
String
response
=
getGsonFactory
().
toJson
(
responseData
);
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"application/json"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
responseJSON
(
t
,
responseData
);
});
// Exchange for combo token
server
.
createContext
(
"/hk4e_global/combo/granter/login/v2/login"
,
t
->
{
...
...
@@ -305,9 +311,8 @@ public final class DispatchServer {
try
{
String
body
=
Utils
.
toString
(
t
.
getRequestBody
());
requestData
=
getGsonFactory
().
fromJson
(
body
,
ComboTokenReqJson
.
class
);
}
catch
(
Exception
e
)
{
}
catch
(
Exception
ignored
)
{
}
}
// Create response json
if
(
requestData
==
null
||
requestData
.
data
==
null
)
{
return
;
...
...
@@ -322,22 +327,18 @@ public final class DispatchServer {
if
(
account
==
null
||
!
account
.
getSessionKey
().
equals
(
loginData
.
token
))
{
responseData
.
retcode
=
-
201
;
responseData
.
message
=
"Wrong session key."
;
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s failed to exchange combo token"
,
t
.
getRemoteAddress
()));
}
else
{
responseData
.
message
=
"OK"
;
responseData
.
data
.
open_id
=
loginData
.
uid
;
responseData
.
data
.
combo_id
=
"157795300"
;
responseData
.
data
.
combo_token
=
account
.
generateLoginToken
();
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s succeed to exchange combo token"
,
t
.
getRemoteAddress
()));
}
// Create a response
String
response
=
getGsonFactory
().
toJson
(
responseData
);
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"application/json"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
responseJSON
(
t
,
responseData
);
});
// Agreement and Protocol
server
.
createContext
(
// hk4e-sdk-os.hoyoverse.com
...
...
@@ -405,57 +406,67 @@ public final class DispatchServer {
"/perf/config/verify"
,
new
DispatchHttpJsonHandler
(
"{\"code\":0}"
)
);
// Start server
server
.
start
();
Grasscutter
.
getLogger
().
info
(
"Dispatch server started on port "
+
getAddress
().
getPort
());
// Logging servers
HttpServer
overseaLogServer
=
HttpServer
.
create
(
new
InetSocketAddress
(
Grasscutter
.
getConfig
().
DispatchServerIp
,
8888
),
0
);
overseaLogServer
.
createContext
(
// overseauspider.yuanshen.com
server
.
createContext
(
// overseauspider.yuanshen.com
"/log"
,
new
DispatchHttpJsonHandler
(
"{\"code\":0}"
)
);
overseaLogServer
.
start
();
Grasscutter
.
getLogger
().
info
(
"Log server (overseauspider) started on port "
+
8888
);
HttpServer
uploadLogServer
=
HttpServer
.
create
(
new
InetSocketAddress
(
Grasscutter
.
getConfig
().
DispatchServerIp
,
Grasscutter
.
getConfig
().
UploadLogPort
),
0
);
uploadLogServer
.
createContext
(
// log-upload-os.mihoyo.com
server
.
createContext
(
// log-upload-os.mihoyo.com
"/crash/dataUpload"
,
new
DispatchHttpJsonHandler
(
"{\"code\":0}"
)
);
uploadLogServer
.
createContext
(
"/gacha"
,
t
->
{
//Create a response form the request query parameters
String
response
=
"<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>"
;
//Set the response header status and length
server
.
createContext
(
"/gacha"
,
t
->
responseHTML
(
t
,
"<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>"
));
// Start server
server
.
start
();
Grasscutter
.
getLogger
().
info
(
"[Dispatch] Dispatch server started on port "
+
getAddress
().
getPort
());
}
private
void
responseJSON
(
HttpExchange
t
,
Object
data
)
throws
IOException
{
// Create a response
String
response
=
getGsonFactory
().
toJson
(
data
);
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"application/json"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
private
void
responseHTML
(
HttpExchange
t
,
String
response
)
throws
IOException
{
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"text/html; charset=UTF-8"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
//Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
});
uploadLogServer
.
start
();
Grasscutter
.
getLogger
().
info
(
"Log server (log-upload-os) started on port "
+
Grasscutter
.
getConfig
().
UploadLogPort
);
}
private
Map
<
String
,
String
>
parseQueryString
(
String
qs
)
{
Map
<
String
,
String
>
result
=
new
HashMap
<>();
if
(
qs
==
null
)
if
(
qs
==
null
)
{
return
result
;
}
int
last
=
0
,
next
,
l
=
qs
.
length
();
while
(
last
<
l
)
{
next
=
qs
.
indexOf
(
'&'
,
last
);
if
(
next
==
-
1
)
if
(
next
==
-
1
)
{
next
=
l
;
}
if
(
next
>
last
)
{
int
eqPos
=
qs
.
indexOf
(
'='
,
last
);
try
{
if
(
eqPos
<
0
||
eqPos
>
next
)
if
(
eqPos
<
0
||
eqPos
>
next
)
{
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"
));
}
}
catch
(
UnsupportedEncodingException
e
)
{
throw
new
RuntimeException
(
e
);
// will never happen, utf-8 support is mandatory for java
}
...
...
@@ -464,4 +475,15 @@ public final class DispatchServer {
}
return
result
;
}
public
static
class
RegionData
{
QueryCurrRegionHttpRsp
parsedRegionQuery
;
String
Base64
;
public
RegionData
(
QueryCurrRegionHttpRsp
prq
,
String
b64
)
{
this
.
parsedRegionQuery
=
prq
;
this
.
Base64
=
b64
;
}
}
}
Prev
1
2
3
4
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment