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
5d4f2452
Commit
5d4f2452
authored
May 11, 2022
by
Melledy
Browse files
Merge branch 'development' into dev-quests
parents
dfd8fcb2
e5a85f81
Changes
19
Hide whitespace changes
Inline
Side-by-side
.gitignore
View file @
5d4f2452
...
...
@@ -69,6 +69,7 @@ language/
languages/
gacha-mapping.js
data/gacha_mappings.js
BuildConfig.java
# macOS
.DS_Store
build.gradle
View file @
5d4f2452
...
...
@@ -45,6 +45,7 @@ targetCompatibility = JavaVersion.VERSION_17
group
=
'xyz.grasscutters'
version
=
'1.1.1-dev'
sourceCompatibility
=
17
targetCompatibility
=
17
...
...
@@ -97,6 +98,7 @@ application {
mainClassName
=
'emu.grasscutter.Grasscutter'
}
jar
{
manifest
{
attributes
'Main-Class'
:
'emu.grasscutter.Grasscutter'
...
...
@@ -226,6 +228,23 @@ javadoc {
}
}
task
injectGitHash
{
def
gitCommitHash
=
{
try
{
return
'git rev-parse --verify --short HEAD'
.
execute
().
text
.
trim
()
}
catch
(
e
)
{
return
"GIT_NOT_FOUND"
}
}
new
File
(
projectDir
,
"src/main/java/emu/grasscutter/BuildConfig.java"
).
text
=
"""
package emu.grasscutter;
public class BuildConfig {
public static final String VERSION = \"${version}\";
public static final String GIT_HASH = \"${gitCommitHash()}\";
}
"""
}
processResources
{
dependsOn
"generateProto"
}
data/gacha_details.html
0 → 100644
View file @
5d4f2452
<!doctype html>
<html>
<head>
<meta
charset=
"utf-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1, shrink-to-fit=no"
>
<link
rel=
"stylesheet"
href=
"https://fonts.googleapis.com/css?family=Roboto:300,400&display=swap"
>
<link
rel=
"stylesheet"
href=
"https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css"
>
<style>
body
{
background-color
:
#f0f0f0
;
}
p
{
font-weight
:
300
;
}
a
,
a
:hover
{
text-decoration
:
none
!important
;
color
:
#626976
;
}
.content
{
padding
:
3rem
0
;
}
.container
{
color
:
#626976
;
position
:
relative
;
}
h2
{
font-size
:
20px
;
}
h3
{
font-size
:
16px
;
}
</style>
<title>
Banner Details
</title>
<script
type=
"text/javascript"
src=
"/gacha/mappings"
></script>
</head>
<body>
<div
class=
"content"
>
<div
class=
"container"
>
<h2
class=
"mb-5"
>
{{TITLE}}
</h2>
<h3
class=
""
>
{{AVAILABLE_FIVE_STARS}}
</h3>
<hr
/>
<ul
id=
"5-star-list"
>
</ul>
<h3
class=
""
>
{{AVAILABLE_FOUR_STARS}}
</h3>
<hr
/>
<ul
id=
"4-star-list"
>
</ul>
<h3
class=
""
>
{{AVAILABLE_THREE_STARS}}
</h3>
<hr
/>
<ul
id=
"3-star-list"
>
</ul>
</div>
</div>
<footer>
<div
class=
"copyright"
>
<div
class=
"container"
>
<div
class=
"row"
>
<div
class=
"col-md-6"
>
<span>
Template by BecodReyes. All rights reserved.
</span>
</div>
<div
class=
"col-md-6"
>
<ul
style=
"float:right"
>
<li
class=
"list-inline-item"
>
<a
href=
"https://github.com/Grasscutters/Grasscutter"
>
Github
</a>
</li>
<li
class=
"list-inline-item"
>
·
</li>
<li
class=
"list-inline-item"
>
<a
href=
"https://github.com/Grasscutters/Grasscutter/blob/stable/LICENSE"
>
License
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</footer>
<script>
var
fiveStarItems
=
{{
FIVE_STARS
}};
var
fourStarItems
=
{{
FOUR_STARS
}};
var
threeStarItems
=
{{
THREE_STARS
}};
var
lang
=
"
{{LANGUAGE}}
"
;
function
getNameForId
(
itemId
)
{
if
(
mappings
[
lang
]
!=
null
&&
mappings
[
lang
][
itemId
]
!=
null
)
{
return
mappings
[
lang
][
itemId
][
0
];
}
else
if
(
mappings
[
"
en-us
"
]
!=
null
&&
mappings
[
"
en-us
"
][
itemId
]
!=
null
)
{
return
mappings
[
"
en-us
"
][
itemId
][
0
];
}
return
itemId
.
toString
();
}
fiveStarList
=
document
.
getElementById
(
"
5-star-list
"
);
fourStarList
=
document
.
getElementById
(
"
4-star-list
"
);
threeStarList
=
document
.
getElementById
(
"
3-star-list
"
);
fiveStarItems
.
forEach
(
element
=>
{
var
entry
=
document
.
createElement
(
"
li
"
);
entry
.
innerHTML
=
getNameForId
(
element
);
fiveStarList
.
appendChild
(
entry
);
});
fourStarItems
.
forEach
(
element
=>
{
var
entry
=
document
.
createElement
(
"
li
"
);
entry
.
innerHTML
=
getNameForId
(
element
);
fourStarList
.
appendChild
(
entry
);
});
threeStarItems
.
forEach
(
element
=>
{
var
entry
=
document
.
createElement
(
"
li
"
);
entry
.
innerHTML
=
getNameForId
(
element
);
threeStarList
.
appendChild
(
entry
);
});
</script>
</body>
</html>
src/main/java/emu/grasscutter/Grasscutter.java
View file @
5d4f2452
...
...
@@ -29,6 +29,7 @@ import emu.grasscutter.server.dispatch.DispatchServer;
import
emu.grasscutter.server.game.GameServer
;
import
emu.grasscutter.tools.Tools
;
import
emu.grasscutter.utils.Crypto
;
import
emu.grasscutter.BuildConfig
;
import
javax.annotation.Nullable
;
...
...
@@ -82,6 +83,9 @@ public final class Grasscutter {
case
"-gachamap"
->
{
Tools
.
createGachaMapping
(
DATA
(
"gacha_mappings.js"
));
exitEarly
=
true
;
}
case
"-version"
->
{
System
.
out
.
println
(
"Grasscutter version: "
+
BuildConfig
.
VERSION
+
"-"
+
BuildConfig
.
GIT_HASH
);
exitEarly
=
true
;
}
}
}
...
...
@@ -186,6 +190,7 @@ public final class Grasscutter {
}
getLogger
().
info
(
translate
(
"messages.status.done"
));
getLogger
().
info
(
translate
(
"messages.status.version"
,
BuildConfig
.
VERSION
,
BuildConfig
.
GIT_HASH
));
String
input
=
null
;
boolean
isLastInterrupted
=
false
;
while
(
true
)
{
...
...
src/main/java/emu/grasscutter/database/DatabaseHelper.java
View file @
5d4f2452
...
...
@@ -78,25 +78,25 @@ public final class DatabaseHelper {
}
public
static
Account
getAccountByName
(
String
username
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"username"
,
username
)).
first
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"username"
,
username
)).
first
();
}
public
static
Account
getAccountByToken
(
String
token
)
{
if
(
token
==
null
)
return
null
;
return
DatabaseManager
.
getDatastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"token"
,
token
)).
first
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"token"
,
token
)).
first
();
}
public
static
Account
getAccountBySessionKey
(
String
sessionKey
)
{
if
(
sessionKey
==
null
)
return
null
;
return
DatabaseManager
.
getDatastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"sessionKey"
,
sessionKey
)).
first
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"sessionKey"
,
sessionKey
)).
first
();
}
public
static
Account
getAccountById
(
String
uid
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"_id"
,
uid
)).
first
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"_id"
,
uid
)).
first
();
}
public
static
Account
getAccountByPlayerId
(
int
playerId
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"playerId"
,
playerId
)).
first
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"playerId"
,
playerId
)).
first
();
}
public
static
void
deleteAccount
(
Account
target
)
{
...
...
@@ -105,39 +105,39 @@ public final class DatabaseHelper {
// database in an inconsistent state, but unfortunately Mongo only supports that when we have a replica set ...
// Delete Mail.class data
DatabaseManager
.
getDatabase
().
getCollection
(
"mail"
).
deleteMany
(
eq
(
"ownerUid"
,
target
.
getPlayerUid
()));
DatabaseManager
.
get
Game
Database
().
getCollection
(
"mail"
).
deleteMany
(
eq
(
"ownerUid"
,
target
.
getPlayerUid
()));
// Delete Avatar.class data
DatabaseManager
.
getDatabase
().
getCollection
(
"avatars"
).
deleteMany
(
eq
(
"ownerId"
,
target
.
getPlayerUid
()));
DatabaseManager
.
get
Game
Database
().
getCollection
(
"avatars"
).
deleteMany
(
eq
(
"ownerId"
,
target
.
getPlayerUid
()));
// Delete GachaRecord.class data
DatabaseManager
.
getDatabase
().
getCollection
(
"gachas"
).
deleteMany
(
eq
(
"ownerId"
,
target
.
getPlayerUid
()));
DatabaseManager
.
get
Game
Database
().
getCollection
(
"gachas"
).
deleteMany
(
eq
(
"ownerId"
,
target
.
getPlayerUid
()));
// Delete GameItem.class data
DatabaseManager
.
getDatabase
().
getCollection
(
"items"
).
deleteMany
(
eq
(
"ownerId"
,
target
.
getPlayerUid
()));
DatabaseManager
.
get
Game
Database
().
getCollection
(
"items"
).
deleteMany
(
eq
(
"ownerId"
,
target
.
getPlayerUid
()));
// Delete GameMainQuest.class data
DatabaseManager
.
getDatabase
().
getCollection
(
"quests"
).
deleteMany
(
eq
(
"ownerUid"
,
target
.
getPlayerUid
()));
DatabaseManager
.
get
Game
Database
().
getCollection
(
"quests"
).
deleteMany
(
eq
(
"ownerUid"
,
target
.
getPlayerUid
()));
// Delete friendships.
// Here, we need to make sure to not only delete the deleted account's friendships,
// but also all friendship entries for that account's friends.
DatabaseManager
.
getDatabase
().
getCollection
(
"friendships"
).
deleteMany
(
eq
(
"ownerId"
,
target
.
getPlayerUid
()));
DatabaseManager
.
getDatabase
().
getCollection
(
"friendships"
).
deleteMany
(
eq
(
"friendId"
,
target
.
getPlayerUid
()));
DatabaseManager
.
get
Game
Database
().
getCollection
(
"friendships"
).
deleteMany
(
eq
(
"ownerId"
,
target
.
getPlayerUid
()));
DatabaseManager
.
get
Game
Database
().
getCollection
(
"friendships"
).
deleteMany
(
eq
(
"friendId"
,
target
.
getPlayerUid
()));
// Delete the player.
DatabaseManager
.
getDatastore
().
find
(
Player
.
class
).
filter
(
Filters
.
eq
(
"id"
,
target
.
getPlayerUid
())).
delete
();
DatabaseManager
.
get
Game
Datastore
().
find
(
Player
.
class
).
filter
(
Filters
.
eq
(
"id"
,
target
.
getPlayerUid
())).
delete
();
// Finally, delete the account itself.
DatabaseManager
.
getDatastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"id"
,
target
.
getId
())).
delete
();
DatabaseManager
.
get
Game
Datastore
().
find
(
Account
.
class
).
filter
(
Filters
.
eq
(
"id"
,
target
.
getId
())).
delete
();
}
public
static
List
<
Player
>
getAllPlayers
()
{
return
DatabaseManager
.
getDatastore
().
find
(
Player
.
class
).
stream
().
toList
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Player
.
class
).
stream
().
toList
();
}
public
static
Player
getPlayerById
(
int
id
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Player
.
class
).
filter
(
Filters
.
eq
(
"_id"
,
id
)).
first
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Player
.
class
).
filter
(
Filters
.
eq
(
"_id"
,
id
)).
first
();
}
public
static
boolean
checkPlayerExists
(
int
id
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Player
.
class
).
filter
(
Filters
.
eq
(
"_id"
,
id
)).
first
()
!=
null
;
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Player
.
class
).
filter
(
Filters
.
eq
(
"_id"
,
id
)).
first
()
!=
null
;
}
public
static
synchronized
Player
createPlayer
(
Player
character
,
int
reservedId
)
{
...
...
@@ -154,7 +154,7 @@ public final class DatabaseHelper {
character
.
setUid
(
id
);
}
// Save to database
DatabaseManager
.
getDatastore
().
save
(
character
);
DatabaseManager
.
get
Game
Datastore
().
save
(
character
);
return
character
;
}
...
...
@@ -173,48 +173,48 @@ public final class DatabaseHelper {
}
public
static
void
savePlayer
(
Player
character
)
{
DatabaseManager
.
getDatastore
().
save
(
character
);
DatabaseManager
.
get
Game
Datastore
().
save
(
character
);
}
public
static
void
saveAvatar
(
Avatar
avatar
)
{
DatabaseManager
.
getDatastore
().
save
(
avatar
);
DatabaseManager
.
get
Game
Datastore
().
save
(
avatar
);
}
public
static
List
<
Avatar
>
getAvatars
(
Player
player
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Avatar
.
class
).
filter
(
Filters
.
eq
(
"ownerId"
,
player
.
getUid
())).
stream
().
toList
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Avatar
.
class
).
filter
(
Filters
.
eq
(
"ownerId"
,
player
.
getUid
())).
stream
().
toList
();
}
public
static
void
saveItem
(
GameItem
item
)
{
DatabaseManager
.
getDatastore
().
save
(
item
);
DatabaseManager
.
get
Game
Datastore
().
save
(
item
);
}
public
static
boolean
deleteItem
(
GameItem
item
)
{
DeleteResult
result
=
DatabaseManager
.
getDatastore
().
delete
(
item
);
DeleteResult
result
=
DatabaseManager
.
get
Game
Datastore
().
delete
(
item
);
return
result
.
wasAcknowledged
();
}
public
static
List
<
GameItem
>
getInventoryItems
(
Player
player
)
{
return
DatabaseManager
.
getDatastore
().
find
(
GameItem
.
class
).
filter
(
Filters
.
eq
(
"ownerId"
,
player
.
getUid
())).
stream
().
toList
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
GameItem
.
class
).
filter
(
Filters
.
eq
(
"ownerId"
,
player
.
getUid
())).
stream
().
toList
();
}
public
static
List
<
Friendship
>
getFriends
(
Player
player
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Friendship
.
class
).
filter
(
Filters
.
eq
(
"ownerId"
,
player
.
getUid
())).
stream
().
toList
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Friendship
.
class
).
filter
(
Filters
.
eq
(
"ownerId"
,
player
.
getUid
())).
stream
().
toList
();
}
public
static
List
<
Friendship
>
getReverseFriends
(
Player
player
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Friendship
.
class
).
filter
(
Filters
.
eq
(
"friendId"
,
player
.
getUid
())).
stream
().
toList
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Friendship
.
class
).
filter
(
Filters
.
eq
(
"friendId"
,
player
.
getUid
())).
stream
().
toList
();
}
public
static
void
saveFriendship
(
Friendship
friendship
)
{
DatabaseManager
.
getDatastore
().
save
(
friendship
);
DatabaseManager
.
get
Game
Datastore
().
save
(
friendship
);
}
public
static
void
deleteFriendship
(
Friendship
friendship
)
{
DatabaseManager
.
getDatastore
().
delete
(
friendship
);
DatabaseManager
.
get
Game
Datastore
().
delete
(
friendship
);
}
public
static
Friendship
getReverseFriendship
(
Friendship
friendship
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Friendship
.
class
).
filter
(
Filters
.
and
(
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Friendship
.
class
).
filter
(
Filters
.
and
(
Filters
.
eq
(
"ownerId"
,
friendship
.
getFriendId
()),
Filters
.
eq
(
"friendId"
,
friendship
.
getOwnerId
())
)).
first
();
...
...
@@ -225,7 +225,7 @@ public final class DatabaseHelper {
}
public
static
List
<
GachaRecord
>
getGachaRecords
(
int
ownerId
,
int
page
,
int
gachaType
,
int
pageSize
){
return
DatabaseManager
.
getDatastore
().
find
(
GachaRecord
.
class
).
filter
(
return
DatabaseManager
.
get
Game
Datastore
().
find
(
GachaRecord
.
class
).
filter
(
Filters
.
eq
(
"ownerId"
,
ownerId
),
Filters
.
eq
(
"gachaType"
,
gachaType
)
).
iterator
(
new
FindOptions
()
...
...
@@ -240,7 +240,7 @@ public final class DatabaseHelper {
}
public
static
long
getGachaRecordsMaxPage
(
int
ownerId
,
int
page
,
int
gachaType
,
int
pageSize
){
long
count
=
DatabaseManager
.
getDatastore
().
find
(
GachaRecord
.
class
).
filter
(
long
count
=
DatabaseManager
.
get
Game
Datastore
().
find
(
GachaRecord
.
class
).
filter
(
Filters
.
eq
(
"ownerId"
,
ownerId
),
Filters
.
eq
(
"gachaType"
,
gachaType
)
).
count
();
...
...
@@ -248,31 +248,31 @@ public final class DatabaseHelper {
}
public
static
void
saveGachaRecord
(
GachaRecord
gachaRecord
){
DatabaseManager
.
getDatastore
().
save
(
gachaRecord
);
DatabaseManager
.
get
Game
Datastore
().
save
(
gachaRecord
);
}
public
static
List
<
Mail
>
getAllMail
(
Player
player
)
{
return
DatabaseManager
.
getDatastore
().
find
(
Mail
.
class
).
filter
(
Filters
.
eq
(
"ownerUid"
,
player
.
getUid
())).
stream
().
toList
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
Mail
.
class
).
filter
(
Filters
.
eq
(
"ownerUid"
,
player
.
getUid
())).
stream
().
toList
();
}
public
static
void
saveMail
(
Mail
mail
)
{
DatabaseManager
.
getDatastore
().
save
(
mail
);
DatabaseManager
.
get
Game
Datastore
().
save
(
mail
);
}
public
static
boolean
deleteMail
(
Mail
mail
)
{
DeleteResult
result
=
DatabaseManager
.
getDatastore
().
delete
(
mail
);
DeleteResult
result
=
DatabaseManager
.
get
Game
Datastore
().
delete
(
mail
);
return
result
.
wasAcknowledged
();
}
public
static
List
<
GameMainQuest
>
getAllQuests
(
Player
player
)
{
return
DatabaseManager
.
getDatastore
().
find
(
GameMainQuest
.
class
).
filter
(
Filters
.
eq
(
"ownerUid"
,
player
.
getUid
())).
stream
().
toList
();
return
DatabaseManager
.
get
Game
Datastore
().
find
(
GameMainQuest
.
class
).
filter
(
Filters
.
eq
(
"ownerUid"
,
player
.
getUid
())).
stream
().
toList
();
}
public
static
void
saveQuest
(
GameMainQuest
quest
)
{
DatabaseManager
.
getDatastore
().
save
(
quest
);
DatabaseManager
.
get
Game
Datastore
().
save
(
quest
);
}
public
static
boolean
deleteQuest
(
GameMainQuest
quest
)
{
return
DatabaseManager
.
getDatastore
().
delete
(
quest
).
wasAcknowledged
();
return
DatabaseManager
.
get
Game
Datastore
().
delete
(
quest
).
wasAcknowledged
();
}
}
src/main/java/emu/grasscutter/database/DatabaseManager.java
View file @
5d4f2452
...
...
@@ -25,7 +25,7 @@ import emu.grasscutter.game.quest.GameQuest;
import
static
emu
.
grasscutter
.
Configuration
.*;
public
final
class
DatabaseManager
{
private
static
Datastore
d
atastore
;
private
static
Datastore
gameD
atastore
;
private
static
Datastore
dispatchDatastore
;
private
static
final
Class
<?>[]
mappedClasses
=
new
Class
<?>[]
{
...
...
@@ -33,12 +33,12 @@ public final class DatabaseManager {
GachaRecord
.
class
,
Mail
.
class
,
GameMainQuest
.
class
};
public
static
Datastore
getDatastore
()
{
return
d
atastore
;
public
static
Datastore
get
Game
Datastore
()
{
return
gameD
atastore
;
}
public
static
MongoDatabase
getDatabase
()
{
return
getDatastore
().
getDatabase
();
public
static
MongoDatabase
get
Game
Database
()
{
return
get
Game
Datastore
().
getDatabase
();
}
// Yes. I very dislike this method. However, this will be good for now.
...
...
@@ -47,42 +47,42 @@ public final class DatabaseManager {
if
(
SERVER
.
runMode
==
ServerRunMode
.
GAME_ONLY
)
{
return
dispatchDatastore
;
}
else
{
return
d
atastore
;
return
gameD
atastore
;
}
}
public
static
void
initialize
()
{
// Initialize
MongoClient
m
ongoClient
=
MongoClients
.
create
(
DATABASE
.
connectionUri
);
MongoClient
gameM
ongoClient
=
MongoClients
.
create
(
DATABASE
.
game
.
connectionUri
);
// Set mapper options.
MapperOptions
mapperOptions
=
MapperOptions
.
builder
()
.
storeEmpties
(
true
).
storeNulls
(
false
).
build
();
// Create data store.
d
atastore
=
Morphia
.
createDatastore
(
m
ongoClient
,
DATABASE
.
collection
,
mapperOptions
);
gameD
atastore
=
Morphia
.
createDatastore
(
gameM
ongoClient
,
DATABASE
.
game
.
collection
,
mapperOptions
);
// Map classes.
d
atastore
.
getMapper
().
map
(
mappedClasses
);
gameD
atastore
.
getMapper
().
map
(
mappedClasses
);
// Ensure indexes
try
{
d
atastore
.
ensureIndexes
();
gameD
atastore
.
ensureIndexes
();
}
catch
(
MongoCommandException
exception
)
{
Grasscutter
.
getLogger
().
info
(
"Mongo index error: "
,
exception
);
// Duplicate index error
if
(
exception
.
getCode
()
==
85
)
{
// Drop all indexes and re add them
MongoIterable
<
String
>
collections
=
d
atastore
.
getDatabase
().
listCollectionNames
();
MongoIterable
<
String
>
collections
=
gameD
atastore
.
getDatabase
().
listCollectionNames
();
for
(
String
name
:
collections
)
{
d
atastore
.
getDatabase
().
getCollection
(
name
).
dropIndexes
();
gameD
atastore
.
getDatabase
().
getCollection
(
name
).
dropIndexes
();
}
// Add back indexes
d
atastore
.
ensureIndexes
();
gameD
atastore
.
ensureIndexes
();
}
}
if
(
SERVER
.
runMode
==
ServerRunMode
.
GAME_ONLY
)
{
MongoClient
dispatchMongoClient
=
MongoClients
.
create
(
GAME_OPTIONS
.
databaseInfo
.
connectionUri
);
dispatchDatastore
=
Morphia
.
createDatastore
(
dispatchMongoClient
,
GAME_OPTIONS
.
databaseInfo
.
collection
);
MongoClient
dispatchMongoClient
=
MongoClients
.
create
(
DATABASE
.
server
.
connectionUri
);
dispatchDatastore
=
Morphia
.
createDatastore
(
dispatchMongoClient
,
DATABASE
.
server
.
collection
);
// Ensure indexes for dispatch server
try
{
...
...
@@ -104,14 +104,14 @@ public final class DatabaseManager {
}
public
static
synchronized
int
getNextId
(
Class
<?>
c
)
{
DatabaseCounter
counter
=
getDatastore
().
find
(
DatabaseCounter
.
class
).
filter
(
Filters
.
eq
(
"_id"
,
c
.
getSimpleName
())).
first
();
DatabaseCounter
counter
=
get
Game
Datastore
().
find
(
DatabaseCounter
.
class
).
filter
(
Filters
.
eq
(
"_id"
,
c
.
getSimpleName
())).
first
();
if
(
counter
==
null
)
{
counter
=
new
DatabaseCounter
(
c
.
getSimpleName
());
}
try
{
return
counter
.
getNextId
();
}
finally
{
getDatastore
().
save
(
counter
);
get
Game
Datastore
().
save
(
counter
);
}
}
...
...
src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
View file @
5d4f2452
...
...
@@ -102,6 +102,11 @@ public class GachaBanner {
+
lr
(
DISPATCH_INFO
.
accessAddress
,
DISPATCH_INFO
.
bindAddress
)
+
":"
+
lr
(
DISPATCH_INFO
.
accessPort
,
DISPATCH_INFO
.
bindPort
)
+
"/gacha?s="
+
sessionKey
+
"&gachaType="
+
gachaType
;
String
details
=
"http"
+
(
DISPATCH_INFO
.
encryption
.
useInRouting
?
"s"
:
""
)
+
"://"
+
lr
(
DISPATCH_INFO
.
accessAddress
,
DISPATCH_INFO
.
bindAddress
)
+
":"
+
lr
(
DISPATCH_INFO
.
accessPort
,
DISPATCH_INFO
.
bindPort
)
+
"/gacha/details?s="
+
sessionKey
+
"&gachaType="
+
gachaType
;
// Grasscutter.getLogger().info("record = " + record);
GachaInfo
.
Builder
info
=
GachaInfo
.
newBuilder
()
.
setGachaType
(
this
.
getGachaType
())
...
...
@@ -112,8 +117,8 @@ public class GachaBanner {
.
setCostItemNum
(
1
)
.
setGachaPrefabPath
(
this
.
getPrefabPath
())
.
setGachaPreviewPrefabPath
(
this
.
getPreviewPrefabPath
())
.
setGachaProbUrl
(
record
)
.
setGachaProbUrlOversea
(
record
)
.
setGachaProbUrl
(
details
)
.
setGachaProbUrlOversea
(
details
)
.
setGachaRecordUrl
(
record
)
.
setGachaRecordUrlOversea
(
record
)
.
setTenCostItemId
(
this
.
getCostItem
())
...
...
src/main/java/emu/grasscutter/game/gacha/GachaManager.java
View file @
5d4f2452
...
...
@@ -65,7 +65,23 @@ public class GachaManager {
public
Int2ObjectMap
<
GachaBanner
>
getGachaBanners
()
{
return
gachaBanners
;
}
public
int
[]
getYellowAvatars
()
{
return
this
.
yellowAvatars
;
}
public
int
[]
getYellowWeapons
()
{
return
this
.
yellowWeapons
;
}
public
int
[]
getPurpleAvatars
()
{
return
this
.
purpleAvatars
;
}
public
int
[]
getPurpleWeapons
()
{
return
this
.
purpleWeapons
;
}
public
int
[]
getBlueWeapons
()
{
return
this
.
blueWeapons
;
}
public
int
randomRange
(
int
min
,
int
max
)
{
return
ThreadLocalRandom
.
current
().
nextInt
(
max
-
min
+
1
)
+
min
;
}
...
...
src/main/java/emu/grasscutter/plugin/PluginManager.java
View file @
5d4f2452
...
...
@@ -7,6 +7,7 @@ import emu.grasscutter.server.event.HandlerPriority;
import
emu.grasscutter.utils.Utils
;
import
java.io.File
;
import
java.io.FileNotFoundException
;
import
java.io.InputStreamReader
;
import
java.lang.reflect.Method
;
import
java.net.MalformedURLException
;
...
...
@@ -90,6 +91,8 @@ public final class PluginManager {
fileReader
.
close
();
// Close the file reader.
}
catch
(
ClassNotFoundException
ignored
)
{
Grasscutter
.
getLogger
().
warn
(
"Plugin "
+
plugin
.
getName
()
+
" has an invalid main class."
);
}
catch
(
FileNotFoundException
ignored
)
{
Grasscutter
.
getLogger
().
warn
(
"Plugin "
+
plugin
.
getName
()
+
" lacks a valid config file."
);
}
}
catch
(
Exception
exception
)
{
Grasscutter
.
getLogger
().
error
(
"Failed to load plugin: "
+
plugin
.
getName
(),
exception
);
...
...
src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
View file @
5d4f2452
...
...
@@ -16,6 +16,7 @@ import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo;
import
emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo
;
import
emu.grasscutter.server.dispatch.authentication.AuthenticationHandler
;
import
emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler
;
import
emu.grasscutter.server.dispatch.http.GachaDetailsHandler
;
import
emu.grasscutter.server.dispatch.http.GachaRecordHandler
;
import
emu.grasscutter.server.dispatch.json.*
;
import
emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData
;
...
...
@@ -117,7 +118,7 @@ public final class DispatchServer {
.
setTitle
(
DISPATCH_INFO
.
defaultName
)
.
setType
(
"DEV_PUBLIC"
)
.
setDispatchUrl
(
"http"
+
(
DISPATCH_ENCRYPTION
.
use
Encryption
?
"s"
:
""
)
+
"://"
"http"
+
(
DISPATCH_ENCRYPTION
.
use
InRouting
?
"s"
:
""
)
+
"://"
+
lr
(
DISPATCH_INFO
.
accessAddress
,
DISPATCH_INFO
.
bindAddress
)
+
":"
+
lr
(
DISPATCH_INFO
.
accessPort
,
DISPATCH_INFO
.
bindPort
)
+
"/query_cur_region/"
+
defaultServerName
)
...
...
@@ -150,7 +151,7 @@ public final class DispatchServer {
.
setTitle
(
regionInfo
.
Title
)
.
setType
(
"DEV_PUBLIC"
)
.
setDispatchUrl
(
"http"
+
(
DISPATCH_ENCRYPTION
.
use
Encryption
?
"s"
:
""
)
+
"://"
"http"
+
(
DISPATCH_ENCRYPTION
.
use
InRouting
?
"s"
:
""
)
+
"://"
+
lr
(
DISPATCH_INFO
.
accessAddress
,
DISPATCH_INFO
.
bindAddress
)
+
":"
+
lr
(
DISPATCH_INFO
.
accessPort
,
DISPATCH_INFO
.
bindPort
)
+
"/query_cur_region/"
+
regionInfo
.
Name
)
...
...
@@ -455,6 +456,9 @@ public final class DispatchServer {
httpServer
.
raw
().
config
.
addSinglePageRoot
(
"/gacha/mappings"
,
gachaMappingsPath
,
Location
.
EXTERNAL
);
// gacha details
httpServer
.
get
(
"/gacha/details"
,
new
GachaDetailsHandler
());
// static file support for plugins
httpServer
.
raw
().
config
.
precompressStaticFiles
=
false
;
// If this isn't set to false, files such as images may appear corrupted when serving static files
...
...
src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java
View file @
5d4f2452
...
...
@@ -12,5 +12,12 @@ public interface AuthenticationHandler {
void
handleRegister
(
Request
req
,
Response
res
);
void
handleChangePassword
(
Request
req
,
Response
res
);
/**
* Other plugins may need to verify a user's identity using details from handleLogin()
* @param details The user's unique one-time token that needs to be verified
* @return If the verification was successful
*/
boolean
verifyUser
(
String
details
);
LoginResultJson
handleGameLogin
(
Request
req
,
LoginAccountRequestJson
requestData
);
}
src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java
View file @
5d4f2452
...
...
@@ -28,6 +28,12 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler {
res
.
send
(
"Authentication is not available with the default authentication method"
);
}
@Override
public
boolean
verifyUser
(
String
details
)
{
Grasscutter
.
getLogger
().
info
(
translate
(
"dispatch.authentication.default_unable_to_verify"
));
return
false
;
}
@Override
public
LoginResultJson
handleGameLogin
(
Request
req
,
LoginAccountRequestJson
requestData
)
{
LoginResultJson
responseData
=
new
LoginResultJson
();
...
...
src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java
0 → 100644
View file @
5d4f2452
package
emu.grasscutter.server.dispatch.http
;
import
java.io.File
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.LinkedHashSet
;
import
java.util.Set
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.database.DatabaseHelper
;
import
emu.grasscutter.game.Account
;
import
emu.grasscutter.game.gacha.GachaBanner
;
import
emu.grasscutter.game.gacha.GachaManager
;
import
emu.grasscutter.game.gacha.GachaBanner.BannerType
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
express.http.HttpContextHandler
;
import
express.http.Request
;
import
express.http.Response
;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
import
static
emu
.
grasscutter
.
Configuration
.*;
public
final
class
GachaDetailsHandler
implements
HttpContextHandler
{
private
final
String
render_template
;
public
GachaDetailsHandler
()
{
File
template
=
new
File
(
Utils
.
toFilePath
(
DATA
(
"/gacha_details.html"
)));
this
.
render_template
=
template
.
exists
()
?
new
String
(
FileUtils
.
read
(
template
))
:
null
;
}
@Override
public
void
handle
(
Request
req
,
Response
res
)
throws
IOException
{
String
response
=
this
.
render_template
;
// Get player info (for langauge).
String
sessionKey
=
req
.
query
(
"s"
);
Account
account
=
DatabaseHelper
.
getAccountBySessionKey
(
sessionKey
);
Player
player
=
Grasscutter
.
getGameServer
().
getPlayerByUid
(
account
.
getPlayerUid
());
// If the template was not loaded, return an error.
if
(
this
.
render_template
==
null
)
{
res
.
send
(
translate
(
player
,
"gacha.details.template_missing"
));
return
;
}
// Add translated title etc. to the page.
response
=
response
.
replace
(
"{{TITLE}}"
,
translate
(
player
,
"gacha.details.title"
));
response
=
response
.
replace
(
"{{AVAILABLE_FIVE_STARS}}"
,
translate
(
player
,
"gacha.details.available_five_stars"
));
response
=
response
.
replace
(
"{{AVAILABLE_FOUR_STARS}}"
,
translate
(
player
,
"gacha.details.available_four_stars"
));
response
=
response
.
replace
(
"{{AVAILABLE_THREE_STARS}}"
,
translate
(
player
,
"gacha.details.available_three_stars"
));
response
=
response
.
replace
(
"{{LANGUAGE}}"
,
Utils
.
getLanguageCode
(
account
.
getLocale
()));
// Get the banner info for the banner we want.
int
gachaType
=
Integer
.
parseInt
(
req
.
query
(
"gachaType"
));
GachaManager
manager
=
Grasscutter
.
getGameServer
().
getGachaManager
();
GachaBanner
banner
=
manager
.
getGachaBanners
().
get
(
gachaType
);
// Add 5-star items.
Set
<
String
>
fiveStarItems
=
new
LinkedHashSet
<>();
Arrays
.
stream
(
banner
.
getRateUpItems1
()).
forEach
(
i
->
fiveStarItems
.
add
(
Integer
.
toString
(
i
)));
if
(
banner
.
getBannerType
()
==
BannerType
.
STANDARD
||
banner
.
getBannerType
()
==
BannerType
.
EVENT
)
{
Arrays
.
stream
(
manager
.
getYellowAvatars
()).
forEach
(
i
->
fiveStarItems
.
add
(
Integer
.
toString
(
i
)));
}
if
(
banner
.
getBannerType
()
==
BannerType
.
STANDARD
||
banner
.
getBannerType
()
==
BannerType
.
WEAPON
)
{
Arrays
.
stream
(
manager
.
getYellowWeapons
()).
forEach
(
i
->
fiveStarItems
.
add
(
Integer
.
toString
(
i
)));
}
response
=
response
.
replace
(
"{{FIVE_STARS}}"
,
"["
+
String
.
join
(
","
,
fiveStarItems
)
+
"]"
);
// Add 4-star items.
Set
<
String
>
fourStarItems
=
new
LinkedHashSet
<>();
Arrays
.
stream
(
banner
.
getRateUpItems2
()).
forEach
(
i
->
fourStarItems
.
add
(
Integer
.
toString
(
i
)));
Arrays
.
stream
(
manager
.
getPurpleAvatars
()).
forEach
(
i
->
fourStarItems
.
add
(
Integer
.
toString
(
i
)));
Arrays
.
stream
(
manager
.
getPurpleWeapons
()).
forEach
(
i
->
fourStarItems
.
add
(
Integer
.
toString
(
i
)));
response
=
response
.
replace
(
"{{FOUR_STARS}}"
,
"["
+
String
.
join
(
","
,
fourStarItems
)
+
"]"
);
// Add 3-star items.
Set
<
String
>
threeStarItems
=
new
LinkedHashSet
<>();
Arrays
.
stream
(
manager
.
getBlueWeapons
()).
forEach
(
i
->
threeStarItems
.
add
(
Integer
.
toString
(
i
)));
response
=
response
.
replace
(
"{{THREE_STARS}}"
,
"["
+
String
.
join
(
","
,
threeStarItems
)
+
"]"
);
// Done.
res
.
send
(
response
);
}
}
src/main/java/emu/grasscutter/utils/ConfigContainer.java
View file @
5d4f2452
...
...
@@ -2,6 +2,8 @@ package emu.grasscutter.utils;
import
com.google.gson.JsonObject
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.Grasscutter.ServerDebugMode
;
import
emu.grasscutter.Grasscutter.ServerRunMode
;
import
java.io.FileReader
;
import
java.lang.reflect.Field
;
...
...
@@ -15,7 +17,7 @@ import static emu.grasscutter.Grasscutter.config;
*/
public
class
ConfigContainer
{
private
static
int
version
()
{
return
1
;
return
2
;
}
/**
...
...
@@ -69,8 +71,13 @@ public class ConfigContainer {
/* Option containers. */
public
static
class
Database
{
public
String
connectionUri
=
"mongodb://localhost:27017"
;
public
String
collection
=
"grasscutter"
;
public
DataStore
server
=
new
DataStore
();
public
DataStore
game
=
new
DataStore
();
public
static
class
DataStore
{
public
String
connectionUri
=
"mongodb://localhost:27017"
;
public
String
collection
=
"grasscutter"
;
}
}
public
static
class
Structure
{
...
...
@@ -86,8 +93,8 @@ public class ConfigContainer {
}
public
static
class
Server
{
public
Grasscutter
.
ServerDebugMode
debugLevel
=
Grasscutter
.
ServerDebugMode
.
NONE
;
public
Grasscutter
.
ServerRunMode
runMode
=
Grasscutter
.
ServerRunMode
.
HYBRID
;
public
ServerDebugMode
debugLevel
=
ServerDebugMode
.
NONE
;
public
ServerRunMode
runMode
=
ServerRunMode
.
HYBRID
;
public
Dispatch
dispatch
=
new
Dispatch
();
public
Game
game
=
new
Game
();
...
...
@@ -112,7 +119,7 @@ public class ConfigContainer {
public
int
bindPort
=
443
;
/* This is the port used in URLs. */
public
int
accessPort
=
443
;
public
int
accessPort
=
0
;
public
Encryption
encryption
=
new
Encryption
();
public
Policies
policies
=
new
Policies
();
...
...
@@ -128,7 +135,7 @@ public class ConfigContainer {
public
int
bindPort
=
22102
;
/* This is the port used in the default region. */
public
int
accessPort
=
22102
;
public
int
accessPort
=
0
;
public
GameOptions
gameOptions
=
new
GameOptions
();
public
JoinOptions
joinOptions
=
new
JoinOptions
();
...
...
@@ -155,16 +162,14 @@ public class ConfigContainer {
}
public
static
class
GameOptions
{
public
GameOptions
.
InventoryLimits
inventoryLimits
=
new
GameOptions
.
InventoryLimits
();
public
GameOptions
.
AvatarLimits
avatarLimits
=
new
GameOptions
.
AvatarLimits
();
public
InventoryLimits
inventoryLimits
=
new
InventoryLimits
();
public
AvatarLimits
avatarLimits
=
new
AvatarLimits
();
public
int
worldEntityLimit
=
1000
;
// Unenforced. TODO: Implement.
public
boolean
watchGachaConfig
=
false
;
public
boolean
enableShopItems
=
true
;
public
boolean
staminaUsage
=
true
;
public
GameOptions
.
Rates
rates
=
new
GameOptions
.
Rates
();
public
Database
databaseInfo
=
new
Database
();
public
Rates
rates
=
new
Rates
();
public
static
class
InventoryLimits
{
public
int
weapons
=
2000
;
...
...
src/main/java/emu/grasscutter/utils/Language.java
View file @
5d4f2452
...
...
@@ -160,7 +160,9 @@ public final class Language {
JsonObject
object
=
this
.
languageData
;
int
index
=
0
;
String
result
=
"This value does not exist. Please report this to the Discord: "
+
key
;
String
valueNotFoundPattern
=
"This value does not exist. Please report this to the Discord: "
;
String
result
=
valueNotFoundPattern
+
key
;
boolean
isValueFound
=
false
;
while
(
true
)
{
if
(
index
==
keys
.
length
)
break
;
...
...
@@ -171,10 +173,18 @@ public final class Language {
if
(
element
.
isJsonObject
())
object
=
element
.
getAsJsonObject
();
else
{
isValueFound
=
true
;
result
=
element
.
getAsString
();
break
;
}
}
else
break
;
}
if
(!
isValueFound
&&
!
languageCode
.
equals
(
"en-US"
))
{
var
englishValue
=
Grasscutter
.
getLanguage
(
"en-US"
).
get
(
key
);
if
(!
englishValue
.
contains
(
valueNotFoundPattern
))
{
result
+=
"\nhere is english version:\n"
+
englishValue
;
}
}
this
.
cachedTranslations
.
put
(
key
,
result
);
return
result
;
}
...
...
src/main/resources/languages/en-US.json
View file @
5d4f2452
...
...
@@ -16,6 +16,9 @@
"no_keystore_error"
:
"[Dispatch] No SSL cert found! Falling back to HTTP server."
,
"default_password"
:
"[Dispatch] The default keystore password was loaded successfully. Please consider setting the password to 123456 in config.json."
},
"authentication"
:
{
"default_unable_to_verify"
:
"[Authentication] Something called the verifyUser method which is unavailable in the default authentication handler"
},
"no_commands_error"
:
"Commands are not supported in dispatch only mode."
,
"unhandled_request_error"
:
"[Dispatch] Potential unhandled %s request: %s"
,
"account"
:
{
...
...
@@ -45,7 +48,8 @@
"run_mode_error"
:
"Invalid server run mode: %s."
,
"run_mode_help"
:
"Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter..."
,
"create_resources"
:
"Creating resources folder..."
,
"resources_error"
:
"Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder."
"resources_error"
:
"Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder."
,
"version"
:
"Grasscutter version: %s-%s"
}
},
"commands"
:
{
...
...
@@ -356,5 +360,14 @@
"resetshop"
:
{
"description"
:
"reset shop"
}
},
"gacha"
:
{
"details"
:
{
"title"
:
"Banner Details"
,
"available_five_stars"
:
"Available 5-star Items"
,
"available_four_stars"
:
"Available 4-star Items"
,
"available_three_stars"
:
"Available 3-star Items"
,
"template_missing"
:
"data/gacha_details.html is missing."
}
}
}
src/main/resources/languages/pl-PL.json
View file @
5d4f2452
...
...
@@ -45,7 +45,8 @@
"run_mode_error"
:
"Błędny tryb pracy serwera: %s."
,
"run_mode_help"
:
"Tryb pracy serwera musi być ustawiony na 'HYBRID', 'DISPATCH_ONLY', lub 'GAME_ONLY'. Nie można wystartować Grasscutter..."
,
"create_resources"
:
"Tworzenie folderu resources..."
,
"resources_error"
:
"Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources."
"resources_error"
:
"Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources."
,
"version"
:
"Grasscutter versión: %s-%s"
}
},
"commands"
:
{
...
...
@@ -301,5 +302,14 @@
"resetshop"
:
{
"description"
:
"zresetuj sklep"
}
},
"gacha"
:
{
"details"
:
{
"title"
:
"Banner Details"
,
"available_five_stars"
:
"Available 5-star Items"
,
"available_four_stars"
:
"Available 4-star Items"
,
"available_three_stars"
:
"Available 3-star Items"
,
"template_missing"
:
"data/gacha_details.html is missing."
}
}
}
\ No newline at end of file
src/main/resources/languages/zh-CN.json
View file @
5d4f2452
...
...
@@ -20,7 +20,7 @@
"unhandled_request_error"
:
"[Dispatch] 潜在的未处理请求:%s %s"
,
"account"
:
{
"login_attempt"
:
"[Dispatch] 客户端 %s 正在尝试登录"
,
"login_success"
:
"[Dispatch] 客户端 %s 已登录,UID为 %s"
,
"login_success"
:
"[Dispatch] 客户端 %s 已登录,UID
为 %s"
,
"login_token_attempt"
:
"[Dispatch] 客户端 %s 正在尝试使用 token 登录"
,
"login_token_error"
:
"[Dispatch] 客户端 %s 使用 token 登录失败"
,
"login_token_success"
:
"[Dispatch] 客户端 %s 已通过 token 登录,UID 为 %s"
,
...
...
@@ -45,7 +45,8 @@
"run_mode_error"
:
"无效的服务器运行模式:%s。"
,
"run_mode_help"
:
"服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败..."
,
"create_resources"
:
"正在创建 resources 目录..."
,
"resources_error"
:
"请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。"
"resources_error"
:
"请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。"
,
"version"
:
"Grasscutter版本: %s-%s"
}
},
"commands"
:
{
...
...
@@ -55,7 +56,7 @@
"permission_error"
:
"哼哼哼!你没有执行此命令的权限!请联系服务器管理员解决!"
,
"console_execute_error"
:
"此命令只能在服务器控制台执行呐~"
,
"player_execute_error"
:
"此命令只能在游戏内执行哦~"
,
"command_exist_error"
:
"这条命令
……
好像找不到呢?
。
"
,
"command_exist_error"
:
"这条命令
...
好像找不到呢?"
,
"no_description_specified"
:
"没有指定说明"
,
"invalid"
:
{
"amount"
:
"无效的数量。"
,
...
...
@@ -96,20 +97,20 @@
"create"
:
"已创建账号,UID 为 %s。"
,
"delete"
:
"账号已删除。"
,
"no_account"
:
"账号不存在。"
,
"command_usage"
:
"用法:account <create|delete> <用户名> [
uid
]"
,
"description"
:
"创建或删除账号
。
"
"command_usage"
:
"用法:account <create|delete> <用户名> [
UID
]"
,
"description"
:
"创建或删除账号"
},
"broadcast"
:
{
"command_usage"
:
"用法:broadcast <消息>"
,
"message_sent"
:
"公告已发送。"
,
"description"
:
"向所有玩家发送公告
。
"
"description"
:
"向所有玩家发送公告"
},
"changescene"
:
{
"usage"
:
"用法:changescene <场景ID>"
,
"already_in_scene"
:
"你已经在这个场景中了。"
,
"success"
:
"已切换至场景 %s。"
,
"exists_error"
:
"此场景不存在。"
,
"description"
:
"切换指定场景
。
"
"description"
:
"切换指定场景"
},
"clear"
:
{
"command_usage"
:
"用法:clear <all|wp|art|mat>
\n
all: 所有, wp: 武器, art: 圣遗物, mat: 材料"
,
...
...
@@ -120,32 +121,32 @@
"displays"
:
"已清空 %s 的屏幕。"
,
"virtuals"
:
"已清除 %s 的所有货币和经验值。"
,
"everything"
:
"已清除 %s 的所有物品。"
,
"description"
:
"从你的背包中删除所有未装备且已解锁的物品,包括稀有物品
。
"
"description"
:
"从你的背包中删除所有未装备且已解锁的物品,包括稀有物品"
},
"coop"
:
{
"usage"
:
"用法:coop <玩家ID> <目标玩家ID>"
,
"success"
:
"已强制传送 %s 到 %s 的世界"
,
"description"
:
"强制传送指定用户到他人的世界
。
"
"success"
:
"已强制传送 %s 到 %s 的世界
。
"
,
"description"
:
"强制传送指定用户到他人的世界"
},
"enter_dungeon"
:
{
"usage"
:
"用法:enterdungeon <秘境ID>"
,
"changed"
:
"已进入秘境 %s"
,
"changed"
:
"已进入秘境 %s
。
"
,
"not_found_error"
:
"此秘境不存在。"
,
"in_dungeon_error"
:
"你已经在秘境中了。"
,
"description"
:
"进入指定秘境
。
"
"description"
:
"进入指定秘境"
},
"giveAll"
:
{
"usage"
:
"用法:giveall [玩家] [数量]"
,
"started"
:
"正在给予全部物品..."
,
"success"
:
"已给予 %s 全部物品。"
,
"invalid_amount_or_playerId"
:
"无效的数量/玩家ID。"
,
"description"
:
"给予所有物品
。
"
"description"
:
"给予所有物品"
},
"giveArtifact"
:
{
"usage"
:
"用法:giveart|gart [玩家] <圣遗物ID> <主词条ID> [<副词条ID>[,<强化次数>]]... [等级]"
,
"id_error"
:
"无效的圣遗物ID。"
,
"success"
:
"已将 %s 给予 %s。"
,
"description"
:
"给予指定圣遗物
。
"
"description"
:
"给予指定圣遗物"
},
"giveChar"
:
{
"usage"
:
"用法:givechar <玩家> <角色ID|角色名> [数量]"
,
...
...
@@ -153,50 +154,50 @@
"invalid_avatar_id"
:
"无效的角色ID。"
,
"invalid_avatar_level"
:
"无效的角色等级。"
,
"invalid_avatar_or_player_id"
:
"无效的角色ID/玩家ID。"
,
"description"
:
"给予指定角色
。
"
"description"
:
"给予指定角色"
},
"give"
:
{
"usage"
:
"用法:give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]"
,
"refinement_only_applicable_weapons"
:
"只有武器可以设置精炼等级。"
,
"refinement_must_between_1_and_5"
:
"精炼等级必须在 1 到 5 之间。"
,
"given"
:
"已将 %s 个 %s 给予 %s。"
,
"given_with_level_and_refinement"
:
"已将 %s (等级 %s, 精炼 %s) %s 个给予 %s"
,
"given_level"
:
"已将 %s (等级 %s) %s 个给予 %s"
,
"description"
:
"给予指定物品
。
"
"given_with_level_and_refinement"
:
"已将 %s (等级 %s, 精炼 %s) %s 个给予 %s
。
"
,
"given_level"
:
"已将 %s (等级 %s) %s 个给予 %s
。
"
,
"description"
:
"给予指定物品"
},
"godmode"
:
{
"success"
:
"%s 的
无敌
模式已被设置为 %s。"
,
"description"
:
"防止你受到伤害
。
"
"success"
:
"%s 的
上帝
模式已被设置为 %s。"
,
"description"
:
"防止你受到伤害"
},
"heal"
:
{
"success"
:
"已经治疗所有角色。"
,
"description"
:
"治疗当前队伍的角色
。
"
"description"
:
"治疗当前队伍的角色"
},
"kick"
:
{
"player_kick_player"
:
"玩家 [%s:%s] 已将 [%s:%s] 踢出"
,
"server_kick_player"
:
"正在踢出玩家 [%s:%s]"
,
"description"
:
"从服务器内踢出指定玩家
。
"
"player_kick_player"
:
"玩家 [%s:%s] 已将 [%s:%s] 踢出
。
"
,
"server_kick_player"
:
"正在踢出玩家 [%s:%s]
...
"
,
"description"
:
"从服务器内踢出指定玩家"
},
"kill"
:
{
"usage"
:
"用法:killall [玩家UID] [场景ID]"
,
"scene_not_found_in_player_world"
:
"未在玩家世界中找到此场景"
,
"scene_not_found_in_player_world"
:
"未在玩家世界中找到此场景
。
"
,
"kill_monsters_in_scene"
:
"已杀死场景 %s 中的 %s 个怪物。"
,
"description"
:
"杀死所有怪物
。
"
"description"
:
"杀死所有怪物"
},
"killCharacter"
:
{
"usage"
:
"用法:/killcharacter [玩家ID]"
,
"success"
:
"已杀死 %s 当前角色。"
,
"description"
:
"杀死当前角色
。
"
"description"
:
"杀死当前角色"
},
"language"
:
{
"current_language"
:
"当前语言是: %s"
,
"language_changed"
:
"语言切换至: %s"
,
"language_not_found"
:
"目前服务端没有这种语言: %s"
,
"description"
:
"显示或切换当前语言
。
"
"description"
:
"显示或切换当前语言"
},
"list"
:
{
"success"
:
"目前在线人数:%s"
,
"description"
:
"查看所有玩家
。
"
"description"
:
"查看所有玩家"
},
"permission"
:
{
"usage"
:
"用法:permission <add|remove> <用户名> <权限>"
,
...
...
@@ -205,25 +206,25 @@
"remove"
:
"权限已移除。"
,
"not_have_error"
:
"此玩家未拥有权限!"
,
"account_error"
:
"账号不存在。"
,
"description"
:
"添加或移除指定玩家的权限
。
"
"description"
:
"添加或移除指定玩家的权限"
},
"position"
:
{
"success"
:
"坐标:%s, %s, %s
\n
场景ID:%s"
,
"description"
:
"获取所在位置
。
"
"description"
:
"获取所在位置"
},
"reload"
:
{
"reload_start"
:
"正在重载配置文件和数据。"
,
"reload_done"
:
"重载完成。"
,
"description"
:
"重载配置文件和数据
。
"
"description"
:
"重载配置文件和数据"
},
"resetConst"
:
{
"reset_all"
:
"重置所有角色的命座。"
,
"success"
:
"已重置 %s 的命座,重新登录后生效。"
,
"description"
:
"重置当前角色的命之座,执行命令后需重新登录以生效
。
"
"description"
:
"重置当前角色的命之座,执行命令后需重新登录以生效"
},
"resetShopLimit"
:
{
"usage"
:
"用法:/resetshop <玩家ID>"
,
"description"
:
"重置所选玩家的商店刷新时间
。
"
"description"
:
"重置所选玩家的商店刷新时间"
},
"sendMail"
:
{
"usage"
:
"用法:give [玩家] <物品ID|物品名称> [数量]"
,
...
...
@@ -246,19 +247,19 @@
"sender"
:
"<发件人>"
,
"arguments"
:
"<物品ID|物品名称|finish> [数量] [等级]"
,
"error"
:
"错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。"
,
"description"
:
"向指定用户发送邮件。此命令的用法可根据附加的参数而变化
。
"
"description"
:
"向指定用户发送邮件。此命令的用法可根据附加的参数而变化"
},
"sendMessage"
:
{
"usage"
:
"用法:sendmessage <玩家> <消息>"
,
"success"
:
"消息已发送。"
,
"description"
:
"向指定玩家发送消息
。
"
"description"
:
"向指定玩家发送消息"
},
"setFetterLevel"
:
{
"usage"
:
"用法:setfetterlevel <好感度等级>"
,
"range_error"
:
"好感度等级必须在 0 到 10 之间。"
,
"success"
:
"好感度已设置为 %s 级"
,
"success"
:
"好感度已设置为 %s 级
。
"
,
"level_error"
:
"无效的好感度等级。"
,
"description"
:
"设置当前角色的好感度等级
。
"
"description"
:
"设置当前角色的好感度等级"
},
"setStats"
:
{
"usage_console"
:
"用法:setstats|stats @<UID> <属性> <数值>"
,
...
...
@@ -270,27 +271,27 @@
"set_self"
:
"%s 已设为 %s。"
,
"set_for_uid"
:
"将 %s (来自 %s) 设置为 %s。"
,
"set_max_hp"
:
"最大生命值已设为 %s。"
,
"description"
:
"设置当前角色的属性
。
"
"description"
:
"设置当前角色的属性"
},
"setWorldLevel"
:
{
"usage"
:
"用法:setworldlevel <等级>"
,
"value_error"
:
"世界等级必须设置在0-8之间。"
,
"success"
:
"已将世界等级设为 %s。"
,
"invalid_world_level"
:
"无效的世界等级。"
,
"description"
:
"设置世界等级,执行命令后需重新登录以生效
。
"
"description"
:
"设置世界等级,执行命令后需重新登录以生效"
},
"spawn"
:
{
"usage"
:
"用法:spawn <实体ID> [数量] [等级(仅怪物)]"
,
"success"
:
"已生成 %s 个 %s。"
,
"description"
:
"在你附近生成一个生物
。
"
"description"
:
"在你附近生成一个生物"
},
"stop"
:
{
"success"
:
"正在关闭服务器..."
,
"description"
:
"停止服务器
。
"
"description"
:
"停止服务器"
},
"talent"
:
{
"usage_1"
:
"设置天赋等级:/talent set <天赋ID> <数值>"
,
"usage_2"
:
"另一种设置天赋等级的方法:/talent <n (普
攻
) | e (元素战技) | q (元素爆发)> <数值>"
,
"usage_2"
:
"另一种设置天赋等级的方法:/talent <n (普
通攻击
) | e (元素战技) | q (元素爆发)> <数值>"
,
"usage_3"
:
"获取天赋ID:/talent getid"
,
"lower_16"
:
"无效的天赋等级,天赋等级应小于等于15。"
,
"set_id"
:
"将天赋等级设为 %s。"
,
...
...
@@ -303,20 +304,20 @@
"normal_attack_id"
:
"普通攻击的 ID 为 %s。"
,
"e_skill_id"
:
"元素战技ID %s。"
,
"q_skill_id"
:
"元素爆发ID %s。"
,
"description"
:
"设置当前角色的天赋等级
。
"
"description"
:
"设置当前角色的天赋等级"
},
"teleportAll"
:
{
"success"
:
"已将所有玩家传送到你的位置"
,
"success"
:
"已将所有玩家传送到你的位置
。
"
,
"error"
:
"你只能在多人游戏状态下执行此命令。"
,
"description"
:
"将你世界中的所有玩家传送到你所在的位置
。
"
"description"
:
"将你世界中的所有玩家传送到你所在的位置"
},
"teleport"
:
{
"usage_server"
:
"用法:/tp @<玩家ID> <x> <y> <z> [场景ID]"
,
"usage"
:
"用法:/tp [@<玩家ID>] <x> <y> <z> [场景ID]"
,
"specify_player_id"
:
"你必须指定一个玩家ID。"
,
"invalid_position"
:
"无效的位置。"
,
"success"
:
"传送 %s 到坐标 %s,%s,%s,场景为 %s"
,
"description"
:
"改变指定玩家的位置
。
"
"success"
:
"传送 %s 到坐标 %s,%s,%s,场景为 %s
。
"
,
"description"
:
"改变指定玩家的位置"
},
"tower"
:
{
"unlock_done"
:
"深境回廊的所有层已全部解锁。"
...
...
@@ -325,28 +326,37 @@
"usage"
:
"用法:weather <天气ID> [气候ID]"
,
"success"
:
"已更改天气为 %s,气候为 %s。"
,
"invalid_id"
:
"无效的天气ID。"
,
"description"
:
"更改天气
。
"
"description"
:
"更改天气"
},
"drop"
:
{
"command_usage"
:
"用法:drop <物品ID|物品名称> [数量]"
,
"success"
:
"已丢下 %s 个 %s。"
,
"description"
:
"在你附近丢下一个物品
。
"
"description"
:
"在你附近丢下一个物品"
},
"help"
:
{
"usage"
:
"用法:"
,
"aliases"
:
"别名:"
,
"available_commands"
:
"可用命令:"
,
"description"
:
"发送帮助信息或显示指定命令的信息
。
"
"description"
:
"发送帮助信息或显示指定命令的信息"
},
"restart"
:
{
"description"
:
"重新启动服务器
。
"
"description"
:
"重新启动服务器"
},
"unlocktower"
:
{
"success"
:
"解锁完成。"
,
"description"
:
"解锁深境螺旋的所有层
。
"
"description"
:
"解锁深境螺旋的所有层"
},
"resetshop"
:
{
"description"
:
"重置商店刷新时间。"
"description"
:
"重置商店刷新时间"
}
},
"gacha"
:
{
"details"
:
{
"title"
:
"Banner Details"
,
"available_five_stars"
:
"Available 5-star Items"
,
"available_four_stars"
:
"Available 4-star Items"
,
"available_three_stars"
:
"Available 3-star Items"
,
"template_missing"
:
"data/gacha_details.html is missing."
}
}
}
src/main/resources/languages/zh-TW.json
View file @
5d4f2452
...
...
@@ -45,7 +45,8 @@
"run_mode_error"
:
"無效的伺服器運行模式: %s。"
,
"run_mode_help"
:
"伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗..."
,
"create_resources"
:
"正在建立 resources 資料夾..."
,
"resources_error"
:
"請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。"
"resources_error"
:
"請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。"
,
"version"
:
"Grasscutter版本: %s-%s"
}
},
"commands"
:
{
...
...
@@ -301,5 +302,14 @@
"resetshop"
:
{
"description"
:
"重置商店時間"
}
},
"gacha"
:
{
"details"
:
{
"title"
:
"Banner Details"
,
"available_five_stars"
:
"Available 5-star Items"
,
"available_four_stars"
:
"Available 4-star Items"
,
"available_three_stars"
:
"Available 3-star Items"
,
"template_missing"
:
"data/gacha_details.html is missing."
}
}
}
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