Initial commit

This commit is contained in:
Melledy 2022-04-17 05:43:07 -07:00
commit 7925d1cda3
354 changed files with 20869 additions and 0 deletions

54
.gitignore vendored Normal file
View File

@ -0,0 +1,54 @@
# Compiled class file
*.class
#idea
*.idea
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build
# Eclipse
.project
.classpath
.settings
.metadata
.properties
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
.loadpath
.recommenders
# Grasscutter
resources/*
data/AbilityEmbryos.json
data/OpenConfig.json
proto/auto/
proto/protoc.exe
GM Handbook.txt
config.json

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

49
README.md Normal file
View File

@ -0,0 +1,49 @@
# Grasscutter
A WIP server emulator for Genshin Impact 2.3-2.6
# Current features
* Logging in
* Spawning monsters via console
* Combat
* Inventory features (recieving items/characters, upgrading items/characters, etc)
* Co-op does work, but movement is kind of buggy and some player ults do not spawn properly
* Friends list
* Gacha system
# Running the server and client
### Prerequisites
* Java 8 JDK
* Mongodb (recommended 4.0+)
### Starting up the server
1. Compile the server with `./gradlew jar`
2. Create a folder named `resources` in your server directory, you will need to copy `BinData` and `ExcelBinOutput` folders which you can get from a repo like [https://github.com/Dimbreath/GenshinData](https://github.com/Dimbreath/GenshinData) into your resources folder.
3. Run the server with `java -jar grasscutter.jar`. Make sure mongodb is running as well.
### Connecting with the client
1. If you are using the provided keystore, you will need to install and have [Fiddler](https://www.telerik.com/fiddler) running. Make sure fiddler is set to decrypt https traffic.
2. Set your hosts file to redirect at least `api-account-os.hoyoverse.com` and `dispatchosglobal.yuanshen.com` to your dispatch server ip.
### Server console commands
`/account create [username] {playerid}` - Creates an account with the specified username and the in-game uid for that account. The playerid parameter is optional and will be auto generated if not set.
### In-Game commands
There is a dummy user named "Server" in every player's friends list that you can message to use commands. Commands also work in other chat rooms, such as private/team chats.
`!spawn [monster id] [level] [amount]`
`!give [item id] [amount]`
`!drop [item id] [amount]`
`!killall`
`!godmode` - Prevents you from taking damage
`!resetconst` - Resets the constellation level on your current active character, will need to relog after using the command to see any changes.
`!sethp [hp]`
`!clearartifacts` - Deletes all unequipped and unlocked level 0 artifacts, **including yellow rarity ones** from your inventory

63
build.gradle Normal file
View File

@ -0,0 +1,63 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java project to get you started.
* For more details take a look at the Java Quickstart chapter in the Gradle
* User Manual available at https://docs.gradle.org/5.6.3/userguide/tutorial_java_projects.html
*/
plugins {
// Apply the java plugin to add support for Java
id 'java'
// Apply the application plugin to add support for building a CLI application
id 'application'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
jcenter()
}
dependencies {
compile fileTree(dir: 'lib', include: '*.jar')
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32'
compile group: 'ch.qos.logback', name: 'logback-core', version: '1.2.6'
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.6'
compile group: 'io.netty', name: 'netty-all', version: '4.1.69.Final'
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.8'
compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.18.1'
compile group: 'org.reflections', name: 'reflections', version: '0.9.12'
compile group: 'dev.morphia.morphia', name: 'core', version: '1.6.1'
}
application {
// Define the main class for the application
mainClassName = 'emu.grasscutter.Grasscutter'
}
jar {
manifest {
attributes 'Main-Class': 'emu.grasscutter.Grasscutter'
}
jar.baseName = 'grasscutter'
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
from('src/main/java') {
include '*.xml'
}
destinationDir = file(".")
}

64
data/Banners.json Normal file
View File

@ -0,0 +1,64 @@
[
{
"gachaType": 200,
"scheduleId": 893,
"bannerType": "STANDARD",
"prefabPath": "GachaShowPanel_A022",
"previewPrefabPath": "UI_Tab_GachaShowPanel_A022",
"titlePath": "UI_GACHA_SHOW_PANEL_A022_TITLE",
"costItem": 224,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 1000,
"rateUpItems1": [],
"rateUpItems2": []
},
{
"gachaType": 301,
"scheduleId": 903,
"bannerType": "EVENT",
"prefabPath": "GachaShowPanel_A076",
"previewPrefabPath": "UI_Tab_GachaShowPanel_A076",
"titlePath": "UI_GACHA_SHOW_PANEL_A076_TITLE",
"costItem": 223,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 9998,
"maxItemType": 1,
"rateUpItems1": [1066],
"rateUpItems2": [1023, 1043, 1064]
},
{
"gachaType": 400,
"scheduleId": 913,
"bannerType": "EVENT",
"prefabPath": "GachaShowPanel_A077",
"previewPrefabPath": "UI_Tab_GachaShowPanel_A077",
"titlePath": "UI_Tab_GachaShowPanel_A077",
"costItem": 223,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 9998,
"maxItemType": 1,
"rateUpItems1": [1022],
"rateUpItems2": [1023, 1043, 1064]
},
{
"gachaType": 302,
"scheduleId": 923,
"bannerType": "WEAPON",
"prefabPath": "GachaShowPanel_A078",
"previewPrefabPath": "UI_Tab_GachaShowPanel_A078",
"titlePath": "UI_GACHA_SHOW_PANEL_A078_TITLE",
"costItem": 223,
"beginTime": 0,
"endTime": 1924992000,
"sortId": 9997,
"minItemType": 2,
"eventChance": 75,
"softPity": 80,
"hardPity": 80,
"rateUpItems1": [11510, 15503],
"rateUpItems2": [11402, 12403, 13401, 14402, 15405]
}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
ElIKBm9zX3VzYRIHQW1lcmljYRoKREVWX1BVQkxJQyIzaHR0cHM6Ly9vc3VzYWRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uElMKB29zX2V1cm8SBkV1cm9wZRoKREVWX1BVQkxJQyI0aHR0cHM6Ly9vc2V1cm9kaXNwYXRjaC55dWFuc2hlbi5jb20vcXVlcnlfY3VyX3JlZ2lvbhJRCgdvc19hc2lhEgRBc2lhGgpERVZfUFVCTElDIjRodHRwczovL29zYXNpYWRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uElUKBm9zX2NodBIKVFcsIEhLLCBNTxoKREVWX1BVQkxJQyIzaHR0cHM6Ly9vc2NodGRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uKpwQRWMyYhAAAABbrAvbhfIRHfaSCN24qQyVAAgAAMs68ZiMdPfEj41O2wBCYqGiC/WdovvJvaw4t3/m1zIYDrt3/ftK9GKFb7C+2E8FmaHqOnwjJYBg2wI1sXpGmuSxkeWw8Avr36wlNtQjhXNV9zoNKstuZYuheyLlpbPRbYZ3UA6/BzTVsjIhjR1lcqFrigQnpV6MgRR9KqxakCaffK6qIzMlodx4ZPKlqseQhCiyVAvLWQSRqCRcZipzotXsmgLQbpDFtRzhgukXPjfW5dAlzMwswPuu7ZQsf1AKipI34dVQLu6gtXthGgbjn89h/79VR5AokLCPGqIV7/2s+gHfykrjDtyp5rwCcmGQqwV3gHy5LGrHl8Zm12jNd7Qcng51ydqtX4xzet6J2iMF6Dw5nPd/hTyxn+i3Ttk6fop9rbCq3iNgEw3+0cSDal1I1ThYdVnMgPhZgQkZc5/SpTaR+8vfDzRIKbSSrrPSEgLnQvWZOOugXhNdyuiaBc8rJveno7vvktmnhDUF3xWi6osj75j2KghRrdHfDR3Zuh4COrGZDRBSKHft2AvfrxaMT9O8hPzzzYk0U2iicVCDlNP/8wqaT9Vqt1kHmruLxqh377iyp0mxKfNt0+SNRzLyRoyvOar/z3AT6TU9LRoCFrkcJpVsUN+2MVeT52PfMbv5O/Nw9sqsFDlofCJJ/EknY0wDc+tNarYOhDM67/ojn/p6W3ZPBJxb2wcF1TOh9dpAeZdCGJusqhMIj5lpoW8nENTFhkEgMUv2Lh5Z6WpeOAKAu9eDpBMhlRNCccDaNYUgo6TdVDtWxtPrS3NRYqtkvb2I2SEFP0apht954oKdG3ncxyOgHRUkwgtxbCMAngzWo9+VWV3H3OlqeEOv7DdO2o0y95EvlHYb/qtosXPI2jC+6FPa+yl4xmLqcENRTUrU23dsmX3SyBEmZvML4dNeyC53B+mh7DUFtPFJFndxj2tGO9mTSDgy8eCmKG90AiJOMoxaLB2HpnDXN1sTiIcd3WraiE6ZCt4E54hKXvXHPyN52CHkxq1y/TeXHEq4X4MyHyDSRLHmzVs9pnwHM0ZLthKFNyvGfTvjiYokAWtNEuh74syt+m6Wietb6JvgibnnDj6uFKI3BbH4GUT9blsnMgug323bJ6bFvV4iESvz1fNnnUSokWQy5+fWzxPDohULgFzhDCpwov78Bp0E3t6DXSWnrUdNqpLbYKmXO1Hdbn+QH4B90p85UB1V5eSZgxPpUvZbIO4GPScil8K+dkDLdsFa1zypWNmlUN0Ns5H/iuzMuJql2QFYz+SnV1R1T+qywwqCNP9oswcLiAR3XnSacs52vd3PI9+0PZuoF6tVMWlvutsQ34IFZaAwIkdKigZcHumLBt/0KyFASBfN674n8FnHrHOQHU6oCeXkQA9kC8MtkvMb7fOLdzbTsD6SVojzZ64i9mDXxF+iLR9o52OxjIFzwLGRy/ivT/aAnHLZ3AsbnvslDjlQl2ADBFvf7xjmvFu0xlfK58TUpfVEkScFFapWJyKVybB4CRz1wKKz6n/a9581LpCVOWRsJa5p+j0zYcS2PfhmRf3RzwsDHeBjEVlIARbhxNKvmjdZyIidSdMMcsJHDRLE3bvo9kKfag0vRVKmuPLPc9FrACsz3vlkApcVQvzieHWoiP+foEvfj9+7Ti2tLfKdzVkMUmugZiZ46+7PKvIciiiuBPlyld0CCPTtTFHUOMO5dUfrUblX8K3awWiaNQFBS0J3iK08t1bgWfLhsKzsS32fRWugaqecwO9Rji9oHn+UuN8Nz9SgNxodroq9q7y/KHFxbqjCl62g25HN9zUa/s5wnIRwVAiWgTuOe3qGqjwp5m/GR8YVSSK/8mV9EL4AaF8d1uifdVA6wWSH1e/1UB8vcdU83P8ne3u1ho+Y/57WB7KnQaGaiD/108+wiAxNqMb2ex8on01VxdLKV1makXV3gzsvWaRevW8t/K11ZwYfo9g+guWADsA0JO0jWooiaupq1kNWrEheBdSRXBO7Jnb+56cTjPGwLpp7ZOHe/bSCJ4MGzPF3lK66LXhVo+rxvNjhoKVRjhGYxN4T8+AiRo3r+1KwdIGSrtODp3ri3JWAy6Eajp1Ukp9GaCbHSJFnYml84nKew7zLLe//ExQpjd4QAjMTvnbm+Ff6a1jf69QEVo0I33gI7/buwqgjiuvjeL6EYaMolKrKlHZHf/HwWbFbdID8T9aoyZJuCUd6YHaMPRAS6n5nvTwkRLlJ/f6wgyypUGZ22Bb1qGIb9SoPgSgIJkifUoewQW2EexqfoAsHXJVABLy+jp/SC4xzHZOSh42zU1k80HIgrnSOmu6T56F6gqy4Y2cZuZU8LXbO/01u8ifEz8yaXfEFSFdxE0TWl92OLKFtJZr9nNOBQQQr5FDGf6zB1/0CziG/5+PrUDgG3irzho6+7wXkc2CpxlBKOLWdjs3V/Lab6cURz1QZY4HYgUkJtm4U5OKUeO2+murlhC7SrnwyUtGrsD8NbCmI4SRHKPoeLBJQO/m3dRze5Ltr8N9IS7/ukPeOYe1O2agrmhH/JjYfz/l8Gmq8PGY+oavYp8I+2yKvGLD9kCxEgKcTeRh9AW/xPTLGsacrGKQCY+M76DfyLKxCZDiDY9xkBIKchxsMsn7FqZvRMMyJBHbqa3AKQyAN73NCSuFF5f1qDjARU/xqJFhOaKoR64c78oqh1GqOqEFbfNQIRw6WeFCGyW6v6p10uLdR7KXnR7+wub9aG992MpIBk0+gru74yO/WcA0vLdDEQIBwc+M0lmLB53ylsPtde3nliaC5ROHR1IS4LO8Q+3o0BHMr0my0bqFwwCAvZVXOFBHxXyUgrrmUTnZYVSQXNV6+MALBmmRU5yOzhhyHoEdj9YHZeyPpZkYc6DkJWCRYbFfmczNIs133KB9rlfug40w/hHa8pXyRyLaKQUMIUYEvt3Y4AQ==

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

188
gradlew vendored Normal file
View File

@ -0,0 +1,188 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

100
gradlew.bat vendored Normal file
View File

@ -0,0 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
keys/dispatchKey.bin Normal file

Binary file not shown.

BIN
keys/dispatchSeed.bin Normal file

Binary file not shown.

BIN
keys/secretKey.bin Normal file

Binary file not shown.

1
keys/secretKeyBuffer.bin Normal file
View File

@ -0,0 +1 @@
<EFBFBD><EFBFBD>lt1L <09><>ܟ<EFBFBD>.<15>\<5C>pXP<58><50>"ƀ(<28>a<><61><EFBFBD>

BIN
keystore.p12 Normal file

Binary file not shown.

BIN
lib/fastutil-mini-8.5.6.jar Normal file

Binary file not shown.

BIN
lib/kcp-netty.jar Normal file

Binary file not shown.

10
settings.gradle Normal file
View File

@ -0,0 +1,10 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/5.6.3/userguide/multi_project_builds.html
*/
rootProject.name = 'Grasscutter'

View File

@ -0,0 +1,45 @@
package emu.grasscutter;
public class Config {
public String DispatchServerIp = "127.0.0.1";
public int DispatchServerPort = 443;
public String DispatchServerKeystorePath = "./keystore.p12";
public String DispatchServerKeystorePassword = "";
public String GameServerName = "Test";
public String GameServerIp = "127.0.0.1";
public int GameServerPort = 22102;
public String DatabaseUrl = "mongodb://localhost:27017";
public String DatabaseCollection = "grasscutter";
public String RESOURCE_FOLDER = "./resources/";
public String DATA_FOLDER = "./data/";
public String PACKETS_FOLDER = "./packets/";
public String DUMPS_FOLDER = "./dumps/";
public String KEY_FOLDER = "./keys/";
public boolean LOG_PACKETS = false;
public GameRates Game = new GameRates();
public ServerOptions ServerOptions = new ServerOptions();
public GameRates getGameRates() {
return Game;
}
public ServerOptions getServerOptions() {
return ServerOptions;
}
public class GameRates {
public float ADVENTURE_EXP_RATE = 1.0f;
public float MORA_RATE = 1.0f;
public float DOMAIN_DROP_RATE = 1.0f;
}
public class ServerOptions {
public int MaxEntityLimit = 1000; // Max entity limit per world. TODO Unenforced for now
public int[] WelcomeEmotes = {2007, 1002, 4010};
public String WelcomeMotd = "Welcome to Grasscutter emu";
}
}

View File

@ -0,0 +1,37 @@
package emu.grasscutter;
import java.util.Arrays;
import emu.grasscutter.game.props.OpenState;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
public class GenshinConstants {
public static String VERSION = "2.6.0";
public static final int MAX_TEAMS = 4;
public static final int MAX_AVATARS_IN_TEAM = 4;
public static final int LIMIT_WEAPON = 2000;
public static final int LIMIT_RELIC = 2000;
public static final int LIMIT_MATERIAL = 2000;
public static final int LIMIT_FURNITURE = 2000;
public static final int LIMIT_ALL = 30000;
public static final int MAIN_CHARACTER_MALE = 10000005;
public static final int MAIN_CHARACTER_FEMALE = 10000007;
public static final Position START_POSITION = new Position(2747, 194, -1719);
public static final int MAX_FRIENDS = 45;
public static final int MAX_FRIEND_REQUESTS = 50;
public static final int SERVER_CONSOLE_UID = 99; // uid of the fake player used for commands
// Default entity ability hashes
public static final String[] DEFAULT_ABILITY_STRINGS = {
"Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible",
"Avatar_Freeze_Duration_Reducer", "Avatar_Attack_ReviveEnergy", "Avatar_Component_Initializer", "Avatar_FallAnthem_Achievement_Listener"
};
public static final int[] DEFAULT_ABILITY_HASHES = Arrays.stream(DEFAULT_ABILITY_STRINGS).mapToInt(Utils::abilityHash).toArray();
public static final int DEFAULT_ABILITY_NAME = Utils.abilityHash("Default");
}

View File

@ -0,0 +1,127 @@
package emu.grasscutter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.util.Arrays;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ch.qos.logback.classic.Logger;
import emu.grasscutter.commands.ServerCommands;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.server.dispatch.DispatchServer;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.Crypto;
public class Grasscutter {
private static Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class);
private static Config config;
private static Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static File configFile = new File("./config.json");
public static RunMode MODE = RunMode.BOTH;
private static DispatchServer dispatchServer;
private static GameServer gameServer;
public static void main(String[] args) throws Exception {
Grasscutter.loadConfig();
Crypto.loadKeys();
for (String arg : args) {
switch (arg.toLowerCase()) {
case "-auth":
MODE = RunMode.AUTH;
break;
case "-game":
MODE = RunMode.GAME;
break;
case "-handbook":
Tools.createGmHandbook();
return;
}
}
// Startup
Grasscutter.getLogger().info("Grasscutter Emu");
// Load resource files
ResourceLoader.loadAll();
// Database
DatabaseManager.initialize();
// Run servers
dispatchServer = new DispatchServer();
dispatchServer.start();
gameServer = new GameServer(new InetSocketAddress(getConfig().GameServerIp, getConfig().GameServerPort));
gameServer.start();
startConsole();
}
public static Config getConfig() {
return config;
}
public static Logger getLogger() {
return log;
}
public static Gson getGsonFactory() {
return gson;
}
public static DispatchServer getDispatchServer() {
return dispatchServer;
}
public static GameServer getGameServer() {
return gameServer;
}
public static void loadConfig() {
try (FileReader file = new FileReader(configFile)) {
config = gson.fromJson(file, Config.class);
} catch (Exception e) {
Grasscutter.config = new Config();
}
saveConfig();
}
public static void saveConfig() {
try (FileWriter file = new FileWriter(configFile)) {
file.write(gson.toJson(config));
} catch (Exception e) {
Grasscutter.getLogger().error("Config save error");
}
}
public static void startConsole() {
String input;
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
while ((input = br.readLine()) != null) {
ServerCommands.handle(input);
}
} catch (Exception e) {
Grasscutter.getLogger().error("Console error:", e);
}
}
public enum RunMode {
BOTH,
AUTH,
GAME
}
}

View File

@ -0,0 +1,13 @@
package emu.grasscutter.commands;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Command {
public String[] aliases() default "";
public int gmLevel() default 1;
public String helpText() default "";
}

View File

@ -0,0 +1,307 @@
package emu.grasscutter.commands;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GenshinEntity;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.utils.Position;
public class PlayerCommands {
private static HashMap<String, PlayerCommand> list = new HashMap<String, PlayerCommand>();
static {
try {
// Look for classes
for (Class<?> cls : PlayerCommands.class.getDeclaredClasses()) {
// Get non abstract classes
if (!Modifier.isAbstract(cls.getModifiers())) {
Command commandAnnotation = cls.getAnnotation(Command.class);
PlayerCommand command = (PlayerCommand) cls.newInstance();
if (commandAnnotation != null) {
command.setLevel(commandAnnotation.gmLevel());
for (String alias : commandAnnotation.aliases()) {
if (alias.length() == 0) {
continue;
}
String commandName = "!" + alias;
list.put(commandName, command);
commandName = "/" + alias;
list.put(commandName, command);
}
}
String commandName = "!" + cls.getSimpleName().toLowerCase();
list.put(commandName, command);
commandName = "/" + cls.getSimpleName().toLowerCase();
list.put(commandName, command);
}
}
} catch (Exception e) {
}
}
public static void handle(GenshinPlayer player, String msg) {
String[] split = msg.split(" ");
// End if invalid
if (split.length == 0) {
return;
}
//
String first = split[0].toLowerCase();
PlayerCommand c = PlayerCommands.list.get(first);
if (c != null) {
// Level check
if (player.getGmLevel() < c.getLevel()) {
return;
}
// Execute
int len = Math.min(first.length() + 1, msg.length());
c.execute(player, msg.substring(len));
}
}
public static abstract class PlayerCommand {
// GM level required to use this command
private int level;
protected int getLevel() { return this.level; }
protected void setLevel(int minLevel) { this.level = minLevel; }
// Main
public abstract void execute(GenshinPlayer player, String raw);
}
// ================ Commands ================
@Command(aliases = {"g", "item", "additem"}, helpText = "/give [item id] [count] - Gives {count} amount of {item id}")
public static class Give extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
String[] split = raw.split(" ");
int itemId = 0, count = 1;
try {
itemId = Integer.parseInt(split[0]);
} catch (Exception e) {
itemId = 0;
}
try {
count = Math.max(Math.min(Integer.parseInt(split[1]), Integer.MAX_VALUE), 1);
} catch (Exception e) {
count = 1;
}
// Give
ItemData itemData = GenshinData.getItemDataMap().get(itemId);
GenshinItem item;
if (itemData == null) {
player.dropMessage("Error: Item data not found");
return;
}
if (itemData.isEquip()) {
List<GenshinItem> items = new LinkedList<>();
for (int i = 0; i < count; i++) {
item = new GenshinItem(itemData);
items.add(item);
}
player.getInventory().addItems(items);
player.sendPacket(new PacketItemAddHintNotify(items, ActionReason.SubfieldDrop));
} else {
item = new GenshinItem(itemData, count);
player.getInventory().addItem(item);
player.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop));
}
}
}
@Command(aliases = {"d"}, helpText = "/drop [item id] [count] - Drops {count} amount of {item id}")
public static class Drop extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
String[] split = raw.split(" ");
int itemId = 0, count = 1;
try {
itemId = Integer.parseInt(split[0]);
} catch (Exception e) {
itemId = 0;
}
try {
count = Math.max(Math.min(Integer.parseInt(split[1]), Integer.MAX_VALUE), 1);
} catch (Exception e) {
count = 1;
}
// Give
ItemData itemData = GenshinData.getItemDataMap().get(itemId);
if (itemData == null) {
player.dropMessage("Error: Item data not found");
return;
}
if (itemData.isEquip()) {
float range = (5f + (.1f * count));
for (int i = 0; i < count; i++) {
Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2));
EntityItem entity = new EntityItem(player.getWorld(), player, itemData, pos, 1);
player.getWorld().addEntity(entity);
}
} else {
EntityItem entity = new EntityItem(player.getWorld(), player, itemData, player.getPos().clone().addY(3f), count);
player.getWorld().addEntity(entity);
}
}
}
@Command(helpText = "/spawn [monster id] [count] - Creates {count} amount of {item id}")
public static class Spawn extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
String[] split = raw.split(" ");
int monsterId = 0, count = 1, level = 1;
try {
monsterId = Integer.parseInt(split[0]);
} catch (Exception e) {
monsterId = 0;
}
try {
level = Math.max(Math.min(Integer.parseInt(split[1]), 200), 1);
} catch (Exception e) {
level = 1;
}
try {
count = Math.max(Math.min(Integer.parseInt(split[2]), 1000), 1);
} catch (Exception e) {
count = 1;
}
// Give
MonsterData monsterData = GenshinData.getMonsterDataMap().get(monsterId);
if (monsterData == null) {
player.dropMessage("Error: Monster data not found");
return;
}
float range = (5f + (.1f * count));
for (int i = 0; i < count; i++) {
Position pos = player.getPos().clone().addX((float) (Math.random() * range) - (range / 2)).addY(3f).addZ((float) (Math.random() * range) - (range / 2));
EntityMonster entity = new EntityMonster(player.getWorld(), monsterData, pos, level);
player.getWorld().addEntity(entity);
}
}
}
@Command(helpText = "/killall")
public static class KillAll extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
List<GenshinEntity> toRemove = new LinkedList<>();
for (GenshinEntity entity : player.getWorld().getEntities().values()) {
if (entity instanceof EntityMonster) {
toRemove.add(entity);
}
}
toRemove.forEach(e -> player.getWorld().killEntity(e, 0));
}
}
@Command(helpText = "/resetconst - Resets all constellations for the currently active character")
public static class ResetConst extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity();
if (entity == null) {
return;
}
GenshinAvatar avatar = entity.getAvatar();
avatar.getTalentIdList().clear();
avatar.setCoreProudSkillLevel(0);
avatar.recalcStats();
avatar.save();
player.dropMessage("Constellations for " + entity.getAvatar().getAvatarData().getName() + " have been reset. Please relogin to see changes.");
}
}
@Command(helpText = "/godmode - Prevents you from taking damage")
public static class Godmode extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
player.setGodmode(!player.hasGodmode());
player.dropMessage("Godmode is now " + (player.hasGodmode() ? "ON" : "OFF"));
}
}
@Command(helpText = "/sethp [hp]")
public static class Sethp extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
String[] split = raw.split(" ");
int hp = 0;
try {
hp = Math.max(Integer.parseInt(split[0]), 1);
} catch (Exception e) {
hp = 1;
}
EntityAvatar entity = player.getTeamManager().getCurrentAvatarEntity();
if (entity == null) {
return;
}
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, hp);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
}
}
@Command(aliases = {"clearart"}, helpText = "/clearartifacts")
public static class ClearArtifacts extends PlayerCommand {
@Override
public void execute(GenshinPlayer player, String raw) {
List<GenshinItem> toRemove = new LinkedList<>();
for (GenshinItem item : player.getInventory().getItems().values()) {
if (item.getItemType() == ItemType.ITEM_RELIQUARY && item.getLevel() == 1 && item.getExp() == 0 && !item.isLocked() && !item.isEquipped()) {
toRemove.add(item);
}
}
player.getInventory().removeItems(toRemove);
}
}
}

View File

@ -0,0 +1,140 @@
package emu.grasscutter.commands;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils;
public class ServerCommands {
private static HashMap<String, ServerCommand> list = new HashMap<>();
static {
try {
// Look for classes
for (Class<?> cls : ServerCommands.class.getDeclaredClasses()) {
// Get non abstract classes
if (!Modifier.isAbstract(cls.getModifiers())) {
String commandName = cls.getSimpleName().toLowerCase();
list.put(commandName, (ServerCommand) cls.newInstance());
}
}
} catch (Exception e) {
}
}
public static void handle(String msg) {
String[] split = msg.split(" ");
// End if invalid
if (split.length == 0) {
return;
}
//
String first = split[0].toLowerCase();
ServerCommand c = ServerCommands.list.get(first);
if (c != null) {
// Execute
int len = Math.min(first.length() + 1, msg.length());
c.execute(msg.substring(len));
}
}
public static abstract class ServerCommand {
public abstract void execute(String raw);
}
// ================ Commands ================
public static class Reload extends ServerCommand {
@Override
public void execute(String raw) {
Grasscutter.getLogger().info("Reloading config.");
Grasscutter.loadConfig();
Grasscutter.getDispatchServer().loadQueries();
Grasscutter.getLogger().info("Reload complete.");
}
}
public static class Account extends ServerCommand {
@Override
public void execute(String raw) {
String[] split = raw.split(" ");
if (split.length < 2) {
Grasscutter.getLogger().error("Invalid amount of args");
return;
}
String command = split[0].toLowerCase();
String username = split[1];
switch (command) {
case "create":
if (split.length < 2) {
Grasscutter.getLogger().error("Invalid amount of args");
return;
}
int reservedId = 0;
try {
reservedId = Integer.parseInt(split[2]);
} catch (Exception e) {
reservedId = 0;
}
emu.grasscutter.game.Account account = DatabaseHelper.createAccountWithId(username, reservedId);
if (account != null) {
Grasscutter.getLogger().info("Account created" + (reservedId > 0 ? " with an id of " + reservedId : ""));
} else {
Grasscutter.getLogger().error("Account already exists");
}
break;
case "delete":
boolean success = DatabaseHelper.deleteAccount(username);
if (success) {
Grasscutter.getLogger().info("Account deleted");
}
break;
/*
case "setpw":
case "setpass":
case "setpassword":
if (split.length < 3) {
Grasscutter.getLogger().error("Invalid amount of args");
return;
}
account = DatabaseHelper.getAccountByName(username);
if (account == null) {
Grasscutter.getLogger().error("No account found!");
return;
}
token = split[2];
token = PasswordHelper.hashPassword(token);
account.setPassword(token);
DatabaseHelper.saveAccount(account);
Grasscutter.getLogger().info("Password set");
break;
*/
}
}
}
}

View File

@ -0,0 +1,212 @@
package emu.grasscutter.data;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.utils.Utils;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.def.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class GenshinData {
// BinOutputs
private static final Int2ObjectMap<String> abilityHashes = new Int2ObjectOpenHashMap<>();
private static final Map<String, AbilityEmbryoEntry> abilityEmbryos = new HashMap<>();
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
// ExcelConfigs
private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarData> avatarDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarLevelData> avatarLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarSkillDepotData> avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarSkillData> avatarSkillDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarCurveData> avatarCurveDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarPromoteData> avatarPromoteDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ProudSkillData> proudSkillDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ItemData> itemDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryLevelData> reliquaryLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryAffixData> reliquaryAffixDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryMainPropData> reliquaryMainPropDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquarySetData> reliquarySetDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeaponLevelData> weaponLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeaponPromoteData> weaponPromoteDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WeaponCurveData> weaponCurveDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<EquipAffixData> equipAffixDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MonsterData> monsterDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<NpcData> npcDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<GadgetData> gadgetDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MonsterCurveData> monsterCurveDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MonsterDescribeData> monsterDescribeDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarFlycloakData> avatarFlycloakDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>();
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
Int2ObjectMap<?> map = null;
try {
Field field = GenshinData.class.getDeclaredField(Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map");
field.setAccessible(true);
map = (Int2ObjectMap<?>) field.get(null);
field.setAccessible(false);
} catch (Exception e) {
Grasscutter.getLogger().error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e);
}
return map;
}
public static Int2ObjectMap<String> getAbilityHashes() {
return abilityHashes;
}
public static Map<String, AbilityEmbryoEntry> getAbilityEmbryoInfo() {
return abilityEmbryos;
}
public static Map<String, OpenConfigEntry> getOpenConfigEntries() {
return openConfigEntries;
}
public static Int2ObjectMap<AvatarData> getAvatarDataMap() {
return avatarDataMap;
}
public static Int2ObjectMap<ItemData> getItemDataMap() {
return itemDataMap;
}
public static Int2ObjectMap<AvatarSkillDepotData> getAvatarSkillDepotDataMap() {
return avatarSkillDepotDataMap;
}
public static Int2ObjectMap<AvatarSkillData> getAvatarSkillDataMap() {
return avatarSkillDataMap;
}
public static Int2ObjectMap<PlayerLevelData> getPlayerLevelDataMap() {
return playerLevelDataMap;
}
public static Int2ObjectMap<AvatarLevelData> getAvatarLevelDataMap() {
return avatarLevelDataMap;
}
public static Int2ObjectMap<WeaponLevelData> getWeaponLevelDataMap() {
return weaponLevelDataMap;
}
public static Int2ObjectMap<ReliquaryAffixData> getReliquaryAffixDataMap() {
return reliquaryAffixDataMap;
}
public static Int2ObjectMap<ReliquaryMainPropData> getReliquaryMainPropDataMap() {
return reliquaryMainPropDataMap;
}
public static Int2ObjectMap<WeaponPromoteData> getWeaponPromoteDataMap() {
return weaponPromoteDataMap;
}
public static Int2ObjectMap<WeaponCurveData> getWeaponCurveDataMap() {
return weaponCurveDataMap;
}
public static Int2ObjectMap<AvatarCurveData> getAvatarCurveDataMap() {
return avatarCurveDataMap;
}
public static int getRelicExpRequired(int rankLevel, int level) {
ReliquaryLevelData levelData = reliquaryLevelDataMap.get((rankLevel << 8) + level);
return levelData != null ? levelData.getExp() : 0;
}
public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) {
return reliquaryLevelDataMap.get((rankLevel << 8) + level);
}
public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) {
return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel);
}
public static int getWeaponExpRequired(int rankLevel, int level) {
WeaponLevelData levelData = weaponLevelDataMap.get(level);
if (levelData == null) {
return 0;
}
try {
return levelData.getRequiredExps()[rankLevel - 1];
} catch (Exception e) {
return 0;
}
}
public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) {
return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel);
}
public static int getAvatarLevelExpRequired(int level) {
AvatarLevelData levelData = avatarLevelDataMap.get(level);
return levelData != null ? levelData.getExp() : 0;
}
public static Int2ObjectMap<ProudSkillData> getProudSkillDataMap() {
return proudSkillDataMap;
}
public static Int2ObjectMap<MonsterData> getMonsterDataMap() {
return monsterDataMap;
}
public static Int2ObjectMap<NpcData> getNpcDataMap() {
return npcDataMap;
}
public static Int2ObjectMap<GadgetData> getGadgetDataMap() {
return gadgetDataMap;
}
public static Int2ObjectMap<ReliquarySetData> getReliquarySetDataMap() {
return reliquarySetDataMap;
}
public static Int2ObjectMap<EquipAffixData> getEquipAffixDataMap() {
return equipAffixDataMap;
}
public static Int2ObjectMap<MonsterCurveData> getMonsterCurveDataMap() {
return monsterCurveDataMap;
}
public static Int2ObjectMap<MonsterDescribeData> getMonsterDescribeDataMap() {
return monsterDescribeDataMap;
}
public static Int2ObjectMap<AvatarTalentData> getAvatarTalentDataMap() {
return avatarTalentDataMap;
}
public static Int2ObjectMap<AvatarFlycloakData> getAvatarFlycloakDataMap() {
return avatarFlycloakDataMap;
}
public static Int2ObjectMap<AvatarCostumeData> getAvatarCostumeDataMap() {
return avatarCostumeDataMap;
}
public static Int2ObjectMap<AvatarCostumeData> getAvatarCostumeDataItemIdMap() {
return avatarCostumeDataItemIdMap;
}
}

View File

@ -0,0 +1,49 @@
package emu.grasscutter.data;
import java.util.ArrayList;
import java.util.List;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.def.ReliquaryAffixData;
import emu.grasscutter.data.def.ReliquaryMainPropData;
import emu.grasscutter.utils.WeightedList;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class GenshinDepot {
private static Int2ObjectMap<WeightedList<ReliquaryMainPropData>> relicMainPropDepot = new Int2ObjectOpenHashMap<>();
private static Int2ObjectMap<List<ReliquaryAffixData>> relicAffixDepot = new Int2ObjectOpenHashMap<>();
public static void load() {
for (ReliquaryMainPropData data : GenshinData.getReliquaryMainPropDataMap().values()) {
if (data.getWeight() <= 0 || data.getPropDepotId() <= 0) {
continue;
}
WeightedList<ReliquaryMainPropData> list = relicMainPropDepot.computeIfAbsent(data.getPropDepotId(), k -> new WeightedList<>());
list.add(data.getWeight(), data);
}
for (ReliquaryAffixData data : GenshinData.getReliquaryAffixDataMap().values()) {
if (data.getWeight() <= 0 || data.getDepotId() <= 0) {
continue;
}
List<ReliquaryAffixData> list = relicAffixDepot.computeIfAbsent(data.getDepotId(), k -> new ArrayList<>());
list.add(data);
}
// Let the server owner know if theyre missing weights
if (relicMainPropDepot.size() == 0 || relicAffixDepot.size() == 0) {
Grasscutter.getLogger().error("Relic properties are missing weights! Please check your ReliquaryMainPropExcelConfigData or ReliquaryAffixExcelConfigData files in your ExcelBinOutput folder.");
}
}
public static ReliquaryMainPropData getRandomRelicMainProp(int depot) {
WeightedList<ReliquaryMainPropData> depotList = relicMainPropDepot.get(depot);
if (depotList == null) {
return null;
}
return depotList.next();
}
public static List<ReliquaryAffixData> getRandomRelicAffixList(int depot) {
return relicAffixDepot.get(depot);
}
}

View File

@ -0,0 +1,12 @@
package emu.grasscutter.data;
public abstract class GenshinResource {
public int getId() {
return 0;
}
public void onLoad() {
}
}

View File

@ -0,0 +1,281 @@
package emu.grasscutter.data;
import java.io.File;
import java.io.FileReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.reflections.Reflections;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
public class ResourceLoader {
public static List<Class<?>> getResourceDefClasses() {
Reflections reflections = new Reflections(ResourceLoader.class.getPackage().getName());
Set<?> classes = reflections.getSubTypesOf(GenshinResource.class);
List<Class<?>> classList = new ArrayList<>(classes.size());
classes.forEach(o -> {
Class<?> c = (Class<?>) o;
if (c.getAnnotation(ResourceType.class) != null) {
classList.add(c);
}
});
classList.sort((a, b) -> {
return b.getAnnotation(ResourceType.class).loadPriority().value() - a.getAnnotation(ResourceType.class).loadPriority().value();
});
return classList;
}
public static void loadAll() {
// Create resource folder if it doesnt exist
File resFolder = new File(Grasscutter.getConfig().RESOURCE_FOLDER);
if (!resFolder.exists()) {
resFolder.mkdir();
}
// Load ability lists
loadAbilityEmbryos();
loadOpenConfig();
// Load resources
loadResources();
// Process into depots
GenshinDepot.load();
// Custom - TODO move this somewhere else
try {
GenshinData.getAvatarSkillDepotDataMap().get(504).setAbilities(
new AbilityEmbryoEntry(
"",
new String[] {
"Avatar_PlayerBoy_ExtraAttack_Wind",
"Avatar_Player_UziExplode_Mix",
"Avatar_Player_UziExplode",
"Avatar_Player_UziExplode_Strike_01",
"Avatar_Player_UziExplode_Strike_02",
"Avatar_Player_WindBreathe",
"Avatar_Player_WindBreathe_CameraController"
}
));
GenshinData.getAvatarSkillDepotDataMap().get(704).setAbilities(
new AbilityEmbryoEntry(
"",
new String[] {
"Avatar_PlayerGirl_ExtraAttack_Wind",
"Avatar_Player_UziExplode_Mix",
"Avatar_Player_UziExplode",
"Avatar_Player_UziExplode_Strike_01",
"Avatar_Player_UziExplode_Strike_02",
"Avatar_Player_WindBreathe",
"Avatar_Player_WindBreathe_CameraController"
}
));
} catch (Exception e) {
Grasscutter.getLogger().error("Error loading abilities", e);
}
}
public static void loadResources() {
for (Class<?> resourceDefinition : getResourceDefClasses()) {
ResourceType type = resourceDefinition.getAnnotation(ResourceType.class);
if (type == null) {
continue;
}
@SuppressWarnings("rawtypes")
Int2ObjectMap map = GenshinData.getMapByResourceDef(resourceDefinition);
if (map == null) {
continue;
}
try {
loadFromResource(resourceDefinition, type, map);
} catch (Exception e) {
Grasscutter.getLogger().error("Error loading resource file: " + type.name(), e);
}
}
}
@SuppressWarnings("rawtypes")
protected static void loadFromResource(Class<?> c, ResourceType type, Int2ObjectMap map) throws Exception {
for (String name : type.name()) {
loadFromResource(c, name, map);
}
Grasscutter.getLogger().info("Loaded " + map.size() + " " + c.getSimpleName() + "s.");
}
@SuppressWarnings({"rawtypes", "unchecked"})
protected static void loadFromResource(Class<?> c, String fileName, Int2ObjectMap map) throws Exception {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName)) {
List list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, c).getType());
for (Object o : list) {
GenshinResource res = (GenshinResource) o;
res.onLoad();
map.put(res.getId(), res);
}
}
}
private static void loadAbilityEmbryos() {
// Read from cached file if exists
File embryoCache = new File(Grasscutter.getConfig().DATA_FOLDER + "AbilityEmbryos.json");
List<AbilityEmbryoEntry> embryoList = null;
if (embryoCache.exists()) {
// Load from cache
try (FileReader fileReader = new FileReader(embryoCache)) {
embryoList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityEmbryoEntry.class).getType());
} catch (Exception e) {
e.printStackTrace();
}
} else {
// Load from BinOutput
Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)");
embryoList = new LinkedList<>();
File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput\\Avatar\\");
for (File file : folder.listFiles()) {
AvatarConfig config = null;
String avatarName = null;
Matcher matcher = pattern.matcher(file.getName());
if (matcher.find()) {
avatarName = matcher.group(0);
} else {
continue;
}
try (FileReader fileReader = new FileReader(file)) {
config = Grasscutter.getGsonFactory().fromJson(fileReader, AvatarConfig.class);
} catch (Exception e) {
e.printStackTrace();
continue;
}
if (config.abilities == null) {
continue;
}
int s = config.abilities.size();
AbilityEmbryoEntry al = new AbilityEmbryoEntry(avatarName, config.abilities.stream().map(Object::toString).toArray(size -> new String[s]));
embryoList.add(al);
}
}
if (embryoList == null || embryoList.isEmpty()) {
Grasscutter.getLogger().error("No embryos loaded!");
return;
}
for (AbilityEmbryoEntry entry : embryoList) {
GenshinData.getAbilityEmbryoInfo().put(entry.getName(), entry);
}
}
private static void loadOpenConfig() {
// Read from cached file if exists
File openConfigCache = new File(Grasscutter.getConfig().DATA_FOLDER + "OpenConfig.json");
List<OpenConfigEntry> list = null;
if (openConfigCache.exists()) {
try (FileReader fileReader = new FileReader(openConfigCache)) {
list = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, OpenConfigEntry.class).getType());
} catch (Exception e) {
e.printStackTrace();
}
} else {
Map<String, OpenConfigEntry> map = new TreeMap<>();
java.lang.reflect.Type type = new TypeToken<Map<String, OpenConfigData[]>>() {}.getType();
String[] folderNames = {"BinOutput\\Talent\\EquipTalents\\", "BinOutput\\Talent\\AvatarTalents\\"};
for (String name : folderNames) {
File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + name);
for (File file : folder.listFiles()) {
if (!file.getName().endsWith(".json")) {
continue;
}
Map<String, OpenConfigData[]> config = null;
try (FileReader fileReader = new FileReader(file)) {
config = Grasscutter.getGsonFactory().fromJson(fileReader, type);
} catch (Exception e) {
e.printStackTrace();
continue;
}
for (Entry<String, OpenConfigData[]> e : config.entrySet()) {
List<String> abilityList = new ArrayList<>();
int extraTalentIndex = 0;
for (OpenConfigData entry : e.getValue()) {
if (entry.$type.contains("AddAbility")) {
abilityList.add(entry.abilityName);
} else if (entry.talentIndex > 0) {
extraTalentIndex = entry.talentIndex;
}
}
OpenConfigEntry entry = new OpenConfigEntry(e.getKey(), abilityList, extraTalentIndex);
map.put(entry.getName(), entry);
}
}
}
list = new ArrayList<>(map.values());
}
if (list == null || list.isEmpty()) {
Grasscutter.getLogger().error("No openconfig entries loaded!");
return;
}
for (OpenConfigEntry entry : list) {
GenshinData.getOpenConfigEntries().put(entry.getName(), entry);
}
}
// BinOutput configs
private static class AvatarConfig {
public ArrayList<AvatarConfigAbility> abilities;
private static class AvatarConfigAbility {
public String abilityName;
public String toString() {
return abilityName;
}
}
}
private static class OpenConfig {
public OpenConfigData[] data;
}
private static class OpenConfigData {
public String $type;
public String abilityName;
public int talentIndex;
}
}

View File

@ -0,0 +1,32 @@
package emu.grasscutter.data;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceType {
/** Names of the file that this Resource loads from */
String[] name();
/** Load priority - dictates which order to load this resource, with "highest" being loaded first */
LoadPriority loadPriority() default LoadPriority.NORMAL;
public enum LoadPriority {
HIGHEST (4),
HIGH (3),
NORMAL (2),
LOW (1),
LOWEST (0);
private final int value;
LoadPriority(int value) {
this.value = value;
}
public int value() {
return value;
}
}
}

View File

@ -0,0 +1,17 @@
package emu.grasscutter.data.common;
public class CurveInfo {
private String Type;
private String Arith;
private float Value;
public String getType() {
return Type;
}
public String getArith() {
return Arith;
}
public float getValue() {
return Value;
}
}

View File

@ -0,0 +1,25 @@
package emu.grasscutter.data.common;
import emu.grasscutter.game.props.FightProperty;
public class FightPropData {
private String PropType;
private FightProperty prop;
private float Value;
public String getPropType() {
return PropType;
}
public float getValue() {
return Value;
}
public FightProperty getProp() {
return prop;
}
public void onLoad() {
this.prop = FightProperty.getPropByName(PropType);
}
}

View File

@ -0,0 +1,14 @@
package emu.grasscutter.data.common;
public class ItemParamData {
private int Id;
private int Count;
public int getId() {
return Id;
}
public int getCount() {
return Count;
}
}

View File

@ -0,0 +1,13 @@
package emu.grasscutter.data.common;
public class PropGrowCurve {
private String Type;
private String GrowCurve;
public String getType(){
return this.Type;
}
public String getGrowCurve(){
return this.GrowCurve;
}
}

View File

@ -0,0 +1,23 @@
package emu.grasscutter.data.custom;
public class AbilityEmbryoEntry {
private String name;
private String[] abilities;
public AbilityEmbryoEntry() {
}
public AbilityEmbryoEntry(String avatarName, String[] array) {
this.name = avatarName;
this.abilities = array;
}
public String getName() {
return name;
}
public String[] getAbilities() {
return abilities;
}
}

View File

@ -0,0 +1,29 @@
package emu.grasscutter.data.custom;
import java.util.List;
public class OpenConfigEntry {
private String name;
private String[] addAbilities;
private int extraTalentIndex;
public OpenConfigEntry(String name, List<String> abilityList, int extraTalentIndex) {
this.name = name;
this.extraTalentIndex = extraTalentIndex;
if (abilityList.size() > 0) {
this.addAbilities = abilityList.toArray(new String[0]);
}
}
public String getName() {
return name;
}
public String[] getAddAbilities() {
return addAbilities;
}
public int getExtraTalentIndex() {
return extraTalentIndex;
}
}

View File

@ -0,0 +1,30 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarCostumeExcelConfigData.json")
public class AvatarCostumeData extends GenshinResource {
private int CostumeId;
private int ItemId;
private int AvatarId;
@Override
public int getId() {
return this.CostumeId;
}
public int getItemId() {
return this.ItemId;
}
public int getAvatarId() {
return AvatarId;
}
@Override
public void onLoad() {
GenshinData.getAvatarCostumeDataItemIdMap().put(this.getItemId(), this);
}
}

View File

@ -0,0 +1,36 @@
package emu.grasscutter.data.def;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.CurveInfo;
@ResourceType(name = "AvatarCurveExcelConfigData.json")
public class AvatarCurveData extends GenshinResource {
private int Level;
private CurveInfo[] CurveInfos;
private Map<String, Float> curveInfos;
@Override
public int getId() {
return this.Level;
}
public int getLevel() {
return Level;
}
public Map<String, Float> getCurveInfos() {
return curveInfos;
}
@Override
public void onLoad() {
this.curveInfos = new HashMap<>();
Stream.of(this.CurveInfos).forEach(info -> this.curveInfos.put(info.getType(), info.getValue()));
}
}

View File

@ -0,0 +1,246 @@
package emu.grasscutter.data.def;
import java.util.List;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@ResourceType(name = "AvatarExcelConfigData.json", loadPriority = LoadPriority.LOW)
public class AvatarData extends GenshinResource {
private String name;
private String IconName;
private String BodyType;
private String QualityType;
private int ChargeEfficiency;
private int InitialWeapon;
private String WeaponType;
private String ImageName;
private int AvatarPromoteId;
private String CutsceneShow;
private int SkillDepotId;
private int StaminaRecoverSpeed;
private List<String> CandSkillDepotIds;
private long DescTextMapHash;
private String AvatarIdentityType;
private List<Integer> AvatarPromoteRewardLevelList;
private List<Integer> AvatarPromoteRewardIdList;
private int FeatureTagGroupID;
private long NameTextMapHash;
private long GachaImageNameHashSuffix;
private long InfoDescTextMapHash;
private float HpBase;
private float AttackBase;
private float DefenseBase;
private float Critical;
private float CriticalHurt;
private List<PropGrowCurve> PropGrowCurves;
private int Id;
private Int2ObjectMap<String> growthCurveMap;
private float[] hpGrowthCurve;
private float[] attackGrowthCurve;
private float[] defenseGrowthCurve;
private AvatarSkillDepotData skillDepot;
private IntList abilities;
@Override
public int getId(){
return this.Id;
}
public String getName() {
return name;
}
public String getBodyType(){
return this.BodyType;
}
public String getQualityType(){
return this.QualityType;
}
public int getChargeEfficiency(){
return this.ChargeEfficiency;
}
public int getInitialWeapon(){
return this.InitialWeapon;
}
public String getWeaponType(){
return this.WeaponType;
}
public String getImageName(){
return this.ImageName;
}
public int getAvatarPromoteId(){
return this.AvatarPromoteId;
}
public long getGachaImageNameHashSuffix(){
return this.GachaImageNameHashSuffix;
}
public String getCutsceneShow(){
return this.CutsceneShow;
}
public int getSkillDepotId(){
return this.SkillDepotId;
}
public int getStaminaRecoverSpeed(){
return this.StaminaRecoverSpeed;
}
public List<String> getCandSkillDepotIds(){
return this.CandSkillDepotIds;
}
public long getDescTextMapHash(){
return this.DescTextMapHash;
}
public String getAvatarIdentityType(){
return this.AvatarIdentityType;
}
public List<Integer> getAvatarPromoteRewardLevelList(){
return this.AvatarPromoteRewardLevelList;
}
public List<Integer> getAvatarPromoteRewardIdList(){
return this.AvatarPromoteRewardIdList;
}
public int getFeatureTagGroupID(){
return this.FeatureTagGroupID;
}
public long getInfoDescTextMapHash(){
return this.InfoDescTextMapHash;
}
public float getBaseHp(int level){
try {
return this.HpBase * this.hpGrowthCurve[level - 1];
} catch (Exception e) {
return this.HpBase;
}
}
public float getBaseAttack(int level){
try {
return this.AttackBase * this.attackGrowthCurve[level - 1];
} catch (Exception e) {
return this.AttackBase;
}
}
public float getBaseDefense(int level){
try {
return this.DefenseBase * this.defenseGrowthCurve[level - 1];
} catch (Exception e) {
return this.DefenseBase;
}
}
public float getBaseCritical(){
return this.Critical;
}
public float getBaseCriticalHurt(){
return this.CriticalHurt;
}
public float getGrowthCurveById(int level, FightProperty prop) {
String growCurve = this.growthCurveMap.get(prop.getId());
if (growCurve == null) {
return 1f;
}
AvatarCurveData curveData = GenshinData.getAvatarCurveDataMap().get(level);
if (curveData == null) {
return 1f;
}
return curveData.getCurveInfos().getOrDefault(growCurve, 1f);
}
public long getNameTextMapHash(){
return this.NameTextMapHash;
}
public AvatarSkillDepotData getSkillDepot() {
return skillDepot;
}
public IntList getAbilities() {
return abilities;
}
@Override
public void onLoad() {
this.skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.SkillDepotId);
int size = GenshinData.getAvatarCurveDataMap().size();
this.hpGrowthCurve = new float[size];
this.attackGrowthCurve = new float[size];
this.defenseGrowthCurve = new float[size];
for (AvatarCurveData curveData : GenshinData.getAvatarCurveDataMap().values()) {
int level = curveData.getLevel() - 1;
for (PropGrowCurve growCurve : this.PropGrowCurves) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
switch (prop) {
case FIGHT_PROP_BASE_HP:
this.hpGrowthCurve[level] = curveData.getCurveInfos().get(growCurve.getGrowCurve());
break;
case FIGHT_PROP_BASE_ATTACK:
this.attackGrowthCurve[level] = curveData.getCurveInfos().get(growCurve.getGrowCurve());
break;
case FIGHT_PROP_BASE_DEFENSE:
this.defenseGrowthCurve[level] = curveData.getCurveInfos().get(growCurve.getGrowCurve());
break;
default:
break;
}
}
}
/*
for (PropGrowCurve growCurve : this.PropGrowCurves) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
this.growthCurveMap.put(prop.getId(), growCurve.getGrowCurve());
}
*/
// Cache abilities
String[] split = this.IconName.split("_");
if (split.length > 0) {
this.name = split[split.length - 1];
AbilityEmbryoEntry info = GenshinData.getAbilityEmbryoInfo().get(this.name);
if (info != null) {
this.abilities = new IntArrayList(info.getAbilities().length);
for (String ability : info.getAbilities()) {
this.abilities.add(Utils.abilityHash(ability));
}
}
}
}
}

View File

@ -0,0 +1,24 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarFlycloakExcelConfigData.json")
public class AvatarFlycloakData extends GenshinResource {
private int FlycloakId;
private long NameTextMapHash;
@Override
public int getId() {
return this.FlycloakId;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
@Override
public void onLoad() {
}
}

View File

@ -0,0 +1,23 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "AvatarLevelExcelConfigData.json")
public class AvatarLevelData extends GenshinResource {
private int Level;
private int Exp;
@Override
public int getId() {
return this.Level;
}
public int getLevel() {
return Level;
}
public int getExp() {
return Exp;
}
}

View File

@ -0,0 +1,74 @@
package emu.grasscutter.data.def;
import java.util.ArrayList;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.common.ItemParamData;
@ResourceType(name = "AvatarPromoteExcelConfigData.json")
public class AvatarPromoteData extends GenshinResource {
private int AvatarPromoteId;
private int PromoteLevel;
private int ScoinCost;
private ItemParamData[] CostItems;
private int UnlockMaxLevel;
private FightPropData[] AddProps;
private int RequiredPlayerLevel;
@Override
public int getId() {
return (AvatarPromoteId << 8) + PromoteLevel;
}
public int getAvatarPromoteId() {
return AvatarPromoteId;
}
public int getPromoteLevel() {
return PromoteLevel;
}
public ItemParamData[] getCostItems() {
return CostItems;
}
public int getCoinCost() {
return ScoinCost;
}
public FightPropData[] getAddProps() {
return AddProps;
}
public int getUnlockMaxLevel() {
return UnlockMaxLevel;
}
public int getRequiredPlayerLevel() {
return RequiredPlayerLevel;
}
@Override
public void onLoad() {
// Trim item params
ArrayList<ItemParamData> trim = new ArrayList<>(getAddProps().length);
for (ItemParamData itemParam : getCostItems()) {
if (itemParam.getId() == 0) {
continue;
}
trim.add(itemParam);
}
this.CostItems = trim.toArray(new ItemParamData[trim.size()]);
// Trim fight prop data (just in case)
ArrayList<FightPropData> parsed = new ArrayList<>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null && prop.getValue() != 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.AddProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}

View File

@ -0,0 +1,84 @@
package emu.grasscutter.data.def;
import java.util.List;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
@ResourceType(name = "AvatarSkillExcelConfigData.json", loadPriority = LoadPriority.HIGHEST)
public class AvatarSkillData extends GenshinResource {
private int Id;
private float CdTime;
private int CostElemVal;
private int MaxChargeNum;
private int TriggerID;
private boolean IsAttackCameraLock;
private int ProudSkillGroupId;
private String CostElemType;
private List<Float> LockWeightParams;
private long NameTextMapHash;
private String AbilityName;
private String LockShape;
private String GlobalValueKey;
@Override
public int getId(){
return this.Id;
}
public float getCdTime() {
return CdTime;
}
public int getCostElemVal() {
return CostElemVal;
}
public int getMaxChargeNum() {
return MaxChargeNum;
}
public int getTriggerID() {
return TriggerID;
}
public boolean isIsAttackCameraLock() {
return IsAttackCameraLock;
}
public int getProudSkillGroupId() {
return ProudSkillGroupId;
}
public String getCostElemType() {
return CostElemType;
}
public List<Float> getLockWeightParams() {
return LockWeightParams;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
public String getAbilityName() {
return AbilityName;
}
public String getLockShape() {
return LockShape;
}
public String getGlobalValueKey() {
return GlobalValueKey;
}
@Override
public void onLoad() {
}
}

View File

@ -0,0 +1,123 @@
package emu.grasscutter.data.def;
import java.util.List;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@ResourceType(name = "AvatarSkillDepotExcelConfigData.json", loadPriority = LoadPriority.HIGH)
public class AvatarSkillDepotData extends GenshinResource {
private int Id;
private int EnergySkill;
private int AttackModeSkill;
private List<Integer> Skills;
private List<Integer> SubSkills;
private List<String> ExtraAbilities;
private List<Integer> Talents;
private List<InherentProudSkillOpens> InherentProudSkillOpens;
private String TalentStarName;
private String SkillDepotAbilityGroup;
private AvatarSkillData energySkillData;
private ElementType elementType;
private IntList abilities;
@Override
public int getId(){
return this.Id;
}
public int getEnergySkill(){
return this.EnergySkill;
}
public List<Integer> getSkills(){
return this.Skills;
}
public List<Integer> getSubSkills(){
return this.SubSkills;
}
public int getAttackModeSkill(){
return this.AttackModeSkill;
}
public List<String> getExtraAbilities(){
return this.ExtraAbilities;
}
public List<Integer> getTalents(){
return this.Talents;
}
public String getTalentStarName(){
return this.TalentStarName;
}
public List<InherentProudSkillOpens> getInherentProudSkillOpens(){
return this.InherentProudSkillOpens;
}
public String getSkillDepotAbilityGroup(){
return this.SkillDepotAbilityGroup;
}
public AvatarSkillData getEnergySkillData() {
return this.energySkillData;
}
public ElementType getElementType() {
return elementType;
}
public IntList getAbilities() {
return abilities;
}
public void setAbilities(AbilityEmbryoEntry info) {
this.abilities = new IntArrayList(info.getAbilities().length);
for (String ability : info.getAbilities()) {
this.abilities.add(Utils.abilityHash(ability));
}
}
@Override
public void onLoad() {
this.energySkillData = GenshinData.getAvatarSkillDataMap().get(this.EnergySkill);
if (getEnergySkillData() != null) {
this.elementType = ElementType.getTypeByName(getEnergySkillData().getCostElemType());
} else {
this.elementType = ElementType.None;
}
}
public static class InherentProudSkillOpens {
private int ProudSkillGroupId;
private int NeedAvatarPromoteLevel;
public void setProudSkillGroupId(int ProudSkillGroupId){
this.ProudSkillGroupId = ProudSkillGroupId;
}
public int getProudSkillGroupId(){
return this.ProudSkillGroupId;
}
public void setNeedAvatarPromoteLevel(int NeedAvatarPromoteLevel){
this.NeedAvatarPromoteLevel = NeedAvatarPromoteLevel;
}
public int getNeedAvatarPromoteLevel(){
return this.NeedAvatarPromoteLevel;
}
}
}

View File

@ -0,0 +1,69 @@
package emu.grasscutter.data.def;
import java.util.ArrayList;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.FightPropData;
@ResourceType(name = "AvatarTalentExcelConfigData.json", loadPriority = LoadPriority.HIGHEST)
public class AvatarTalentData extends GenshinResource {
private int TalentId;
private int PrevTalent;
private long NameTextMapHash;
private String Icon;
private int MainCostItemId;
private int MainCostItemCount;
private String OpenConfig;
private FightPropData[] AddProps;
private float[] ParamList;
@Override
public int getId(){
return this.TalentId;
}
public int PrevTalent() {
return PrevTalent;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
public String getIcon() {
return Icon;
}
public int getMainCostItemId() {
return MainCostItemId;
}
public int getMainCostItemCount() {
return MainCostItemCount;
}
public String getOpenConfig() {
return OpenConfig;
}
public FightPropData[] getAddProps() {
return AddProps;
}
public float[] getParamList() {
return ParamList;
}
@Override
public void onLoad() {
ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null || prop.getValue() == 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.AddProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}

View File

@ -0,0 +1,59 @@
package emu.grasscutter.data.def;
import java.util.ArrayList;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData;
@ResourceType(name = "EquipAffixExcelConfigData.json")
public class EquipAffixData extends GenshinResource {
private int AffixId;
private int Id;
private int Level;
private long NameTextMapHash;
private String OpenConfig;
private FightPropData[] AddProps;
private float[] ParamList;
@Override
public int getId() {
return AffixId;
}
public int getMainId() {
return Id;
}
public int getLevel() {
return Level;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
public String getOpenConfig() {
return OpenConfig;
}
public FightPropData[] getAddProps() {
return AddProps;
}
public float[] getParamList() {
return ParamList;
}
@Override
public void onLoad() {
ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null || prop.getValue() == 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.AddProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}

View File

@ -0,0 +1,60 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "GadgetExcelConfigData.json")
public class GadgetData extends GenshinResource {
private int Id;
private String Type;
private String JsonName;
private boolean IsInteractive;
private String[] Tags;
private String ItemJsonName;
private String InteeIconName;
private long NameTextMapHash;
private int CampID;
@Override
public int getId() {
return this.Id;
}
public String getType() {
return Type;
}
public String getJsonName() {
return JsonName;
}
public boolean isInteractive() {
return IsInteractive;
}
public String[] getTags() {
return Tags;
}
public String getItemJsonName() {
return ItemJsonName;
}
public String getInteeIconName() {
return InteeIconName;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
public int getCampID() {
return CampID;
}
@Override
public void onLoad() {
}
}

View File

@ -0,0 +1,253 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.FightProperty;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
@ResourceType(name = {"MaterialExcelConfigData.json", "WeaponExcelConfigData.json", "ReliquaryExcelConfigData.json"})
public class ItemData extends GenshinResource {
private int Id;
private int StackLimit = 1;
private int MaxUseCount;
private int RankLevel;
private String EffectName;
private int[] SatiationParams;
private int Rank;
private int Weight;
private int GadgetId;
private int[] DestroyReturnMaterial;
private int[] DestroyReturnMaterialCount;
// Food
private String FoodQuality;
private String UseTarget;
private String[] UseParam;
// String enums
private String ItemType;
private String MaterialType;
private String EquipType;
private String EffectType;
private String DestroyRule;
// Relic
private int MainPropDepotId;
private int AppendPropDepotId;
private int AppendPropNum;
private int SetId;
private int[] AddPropLevels;
private int BaseConvExp;
private int MaxLevel;
// Weapon
private int WeaponPromoteId;
private int WeaponBaseExp;
private int StoryId;
private int AvatarPromoteId;
private int[] AwakenCosts;
private int[] SkillAffix;
private WeaponProperty[] WeaponProp;
// Hash
private String Icon;
private long NameTextMapHash;
// Post load
private transient emu.grasscutter.game.inventory.MaterialType materialType;
private transient emu.grasscutter.game.inventory.ItemType itemType;
private transient emu.grasscutter.game.inventory.EquipType equipType;
private IntSet addPropLevelSet;
@Override
public int getId(){
return this.Id;
}
public String getMaterialTypeString(){
return this.MaterialType;
}
public int getStackLimit(){
return this.StackLimit;
}
public int getMaxUseCount(){
return this.MaxUseCount;
}
public String getUseTarget(){
return this.UseTarget;
}
public String[] getUseParam(){
return this.UseParam;
}
public int getRankLevel(){
return this.RankLevel;
}
public String getFoodQuality(){
return this.FoodQuality;
}
public String getEffectName(){
return this.EffectName;
}
public int[] getSatiationParams(){
return this.SatiationParams;
}
public int[] getDestroyReturnMaterial(){
return this.DestroyReturnMaterial;
}
public int[] getDestroyReturnMaterialCount(){
return this.DestroyReturnMaterialCount;
}
public long getNameTextMapHash(){
return this.NameTextMapHash;
}
public String getIcon(){
return this.Icon;
}
public String getItemTypeString(){
return this.ItemType;
}
public int getRank(){
return this.Rank;
}
public int getGadgetId() {
return GadgetId;
}
public int getBaseConvExp() {
return BaseConvExp;
}
public int getMainPropDepotId() {
return MainPropDepotId;
}
public int getAppendPropDepotId() {
return AppendPropDepotId;
}
public int getAppendPropNum() {
return AppendPropNum;
}
public int getSetId() {
return SetId;
}
public int getWeaponPromoteId() {
return WeaponPromoteId;
}
public int getWeaponBaseExp() {
return WeaponBaseExp;
}
public int[] getAwakenCosts() {
return AwakenCosts;
}
public IntSet getAddPropLevelSet() {
return addPropLevelSet;
}
public int[] getSkillAffix() {
return SkillAffix;
}
public WeaponProperty[] getWeaponProperties() {
return WeaponProp;
}
public int getMaxLevel() {
return MaxLevel;
}
public emu.grasscutter.game.inventory.ItemType getItemType() {
return this.itemType;
}
public emu.grasscutter.game.inventory.MaterialType getMaterialType() {
return this.materialType;
}
public emu.grasscutter.game.inventory.EquipType getEquipType() {
return this.equipType;
}
public boolean canAddRelicProp(int level) {
return this.addPropLevelSet != null & this.addPropLevelSet.contains(level);
}
public boolean isEquip() {
return this.itemType == emu.grasscutter.game.inventory.ItemType.ITEM_RELIQUARY || this.itemType == emu.grasscutter.game.inventory.ItemType.ITEM_WEAPON;
}
@Override
public void onLoad() {
this.itemType = emu.grasscutter.game.inventory.ItemType.getTypeByName(getItemTypeString());
this.materialType = emu.grasscutter.game.inventory.MaterialType.getTypeByName(getMaterialTypeString());
if (this.itemType == emu.grasscutter.game.inventory.ItemType.ITEM_RELIQUARY) {
this.equipType = emu.grasscutter.game.inventory.EquipType.getTypeByName(this.EquipType);
if (this.AddPropLevels != null && this.AddPropLevels.length > 0) {
this.addPropLevelSet = new IntOpenHashSet(this.AddPropLevels);
}
} else if (this.itemType == emu.grasscutter.game.inventory.ItemType.ITEM_WEAPON) {
this.equipType = emu.grasscutter.game.inventory.EquipType.EQUIP_WEAPON;
} else {
this.equipType = emu.grasscutter.game.inventory.EquipType.EQUIP_NONE;
}
if (this.getWeaponProperties() != null) {
for (WeaponProperty weaponProperty : this.getWeaponProperties()) {
weaponProperty.onLoad();
}
}
}
public static class WeaponProperty {
private FightProperty fightProp;
private String PropType;
private float InitValue;
private String Type;
public String getPropType(){
return this.PropType;
}
public float getInitValue(){
return this.InitValue;
}
public String getType(){
return this.Type;
}
public FightProperty getFightProp() {
return fightProp;
}
public void onLoad() {
this.fightProp = FightProperty.getPropByName(PropType);
}
}
}

View File

@ -0,0 +1,32 @@
package emu.grasscutter.data.def;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.CurveInfo;
@ResourceType(name = "MonsterCurveExcelConfigData.json")
public class MonsterCurveData extends GenshinResource {
private int Level;
private CurveInfo[] CurveInfos;
private Map<String, Float> curveInfos;
@Override
public int getId() {
return Level;
}
public float getMultByProp(String fightProp) {
return curveInfos.getOrDefault(fightProp, 1f);
}
@Override
public void onLoad() {
this.curveInfos = new HashMap<>();
Stream.of(this.CurveInfos).forEach(info -> this.curveInfos.put(info.getType(), info.getValue()));
}
}

View File

@ -0,0 +1,198 @@
package emu.grasscutter.data.def;
import java.util.List;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
import emu.grasscutter.data.common.PropGrowCurve;
@ResourceType(name = "MonsterExcelConfigData.json", loadPriority = LoadPriority.LOW)
public class MonsterData extends GenshinResource {
private int Id;
private String MonsterName;
private String Type;
private String ServerScript;
private List<Integer> Affix;
private String Ai;
private int[] Equips;
private List<HpDrops> HpDrops;
private int KillDropId;
private String ExcludeWeathers;
private int FeatureTagGroupID;
private int MpPropID;
private String Skin;
private int DescribeId;
private int CombatBGMLevel;
private int EntityBudgetLevel;
private float HpBase;
private float AttackBase;
private float DefenseBase;
private float FireSubHurt;
private float ElecSubHurt;
private float GrassSubHurt;
private float WaterSubHurt;
private float WindSubHurt;
private float RockSubHurt;
private float IceSubHurt;
private float PhysicalSubHurt;
private List<PropGrowCurve> PropGrowCurves;
private long NameTextMapHash;
private int CampID;
private int weaponId;
private MonsterDescribeData describeData;
@Override
public int getId() {
return this.Id;
}
public String getMonsterName() {
return MonsterName;
}
public String getType() {
return Type;
}
public String getServerScript() {
return ServerScript;
}
public List<Integer> getAffix() {
return Affix;
}
public String getAi() {
return Ai;
}
public int[] getEquips() {
return Equips;
}
public List<HpDrops> getHpDrops() {
return HpDrops;
}
public int getKillDropId() {
return KillDropId;
}
public String getExcludeWeathers() {
return ExcludeWeathers;
}
public int getFeatureTagGroupID() {
return FeatureTagGroupID;
}
public int getMpPropID() {
return MpPropID;
}
public String getSkin() {
return Skin;
}
public int getDescribeId() {
return DescribeId;
}
public int getCombatBGMLevel() {
return CombatBGMLevel;
}
public int getEntityBudgetLevel() {
return EntityBudgetLevel;
}
public float getBaseHp() {
return HpBase;
}
public float getBaseAttack() {
return AttackBase;
}
public float getBaseDefense() {
return DefenseBase;
}
public float getElecSubHurt() {
return ElecSubHurt;
}
public float getGrassSubHurt() {
return GrassSubHurt;
}
public float getWaterSubHurt() {
return WaterSubHurt;
}
public float getWindSubHurt() {
return WindSubHurt;
}
public float getIceSubHurt() {
return IceSubHurt;
}
public float getPhysicalSubHurt() {
return PhysicalSubHurt;
}
public List<PropGrowCurve> getPropGrowCurves() {
return PropGrowCurves;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
public int getCampID() {
return CampID;
}
public MonsterDescribeData getDescribeData() {
return describeData;
}
public int getWeaponId() {
return weaponId;
}
@Override
public void onLoad() {
this.describeData = GenshinData.getMonsterDescribeDataMap().get(this.getDescribeId());
for (int id : this.Equips) {
if (id == 0) {
continue;
}
GadgetData gadget = GenshinData.getGadgetDataMap().get(id);
if (gadget == null) {
continue;
}
if (gadget.getItemJsonName().equals("Default_MonsterWeapon")) {
this.weaponId = id;
}
}
}
public class HpDrops {
private int DropId;
private int HpPercent;
public int getDropId(){
return this.DropId;
}
public int getHpPercent(){
return this.HpPercent;
}
}
}

View File

@ -0,0 +1,40 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.ResourceType.LoadPriority;
@ResourceType(name = "MonsterDescribeExcelConfigData.json", loadPriority = LoadPriority.HIGH)
public class MonsterDescribeData extends GenshinResource {
private int Id;
private long NameTextMapHash;
private int TitleID;
private int SpecialNameLabID;
private String Icon;
@Override
public int getId() {
return Id;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
public int getTitleID() {
return TitleID;
}
public int getSpecialNameLabID() {
return SpecialNameLabID;
}
public String getIcon() {
return Icon;
}
@Override
public void onLoad() {
}
}

View File

@ -0,0 +1,72 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "NpcExcelConfigData.json")
public class NpcData extends GenshinResource {
private int Id;
private String JsonName;
private String Alias;
private String ScriptDataPath;
private String LuaDataPath;
private boolean IsInteractive;
private boolean HasMove;
private String DyePart;
private String BillboardIcon;
private long NameTextMapHash;
private int CampID;
@Override
public int getId() {
return this.Id;
}
public String getJsonName() {
return JsonName;
}
public String getAlias() {
return Alias;
}
public String getScriptDataPath() {
return ScriptDataPath;
}
public String getLuaDataPath() {
return LuaDataPath;
}
public boolean isIsInteractive() {
return IsInteractive;
}
public boolean isHasMove() {
return HasMove;
}
public String getDyePart() {
return DyePart;
}
public String getBillboardIcon() {
return BillboardIcon;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
public int getCampID() {
return CampID;
}
@Override
public void onLoad() {
}
}

View File

@ -0,0 +1,33 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "PlayerLevelExcelConfigData.json")
public class PlayerLevelData extends GenshinResource {
private int Level;
private int Exp;
private int RewardId;
private int UnlockWorldLevel;
@Override
public int getId() {
return this.Level;
}
public int getLevel() {
return Level;
}
public int getExp() {
return Exp;
}
public int getRewardId() {
return RewardId;
}
public int getUnlockWorldLevel() {
return UnlockWorldLevel;
}
}

View File

@ -0,0 +1,101 @@
package emu.grasscutter.data.def;
import java.util.ArrayList;
import java.util.List;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.common.ItemParamData;
@ResourceType(name = "ProudSkillExcelConfigData.json")
public class ProudSkillData extends GenshinResource {
private int ProudSkillId;
private int ProudSkillGroupId;
private int Level;
private int CoinCost;
private int BreakLevel;
private int ProudSkillType;
private String OpenConfig;
private List<ItemParamData> CostItems;
private List<String> FilterConds;
private List<String> LifeEffectParams;
private FightPropData[] AddProps;
private float[] ParamList;
private long[] ParamDescList;
private long NameTextMapHash;
@Override
public int getId() {
return ProudSkillId;
}
public int getProudSkillGroupId() {
return ProudSkillGroupId;
}
public int getLevel() {
return Level;
}
public int getCoinCost() {
return CoinCost;
}
public int getBreakLevel() {
return BreakLevel;
}
public int getProudSkillType() {
return ProudSkillType;
}
public String getOpenConfig() {
return OpenConfig;
}
public List<ItemParamData> getCostItems() {
return CostItems;
}
public List<String> getFilterConds() {
return FilterConds;
}
public List<String> getLifeEffectParams() {
return LifeEffectParams;
}
public FightPropData[] getAddProps() {
return AddProps;
}
public float[] getParamList() {
return ParamList;
}
public long[] getParamDescList() {
return ParamDescList;
}
public long getNameTextMapHash() {
return NameTextMapHash;
}
@Override
public void onLoad() {
if (this.getOpenConfig() != null & this.getOpenConfig().length() > 0) {
this.OpenConfig = "Avatar_" + this.getOpenConfig();
}
// Fight props
ArrayList<FightPropData> parsed = new ArrayList<FightPropData>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null && prop.getValue() != 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.AddProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}

View File

@ -0,0 +1,48 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.FightProperty;
@ResourceType(name = "ReliquaryAffixExcelConfigData.json")
public class ReliquaryAffixData extends GenshinResource {
private int Id;
private int DepotId;
private int GroupId;
private String PropType;
private float PropValue;
private int Weight;
private int UpgradeWeight;
private FightProperty fightProp;
@Override
public int getId() {
return Id;
}
public int getDepotId() {
return DepotId;
}
public int getGroupId() {
return GroupId;
}
public float getPropValue() {
return PropValue;
}
public int getWeight() {
return Weight;
}
public int getUpgradeWeight() {
return UpgradeWeight;
}
public FightProperty getFightProp() {
return fightProp;
}
@Override
public void onLoad() {
this.fightProp = FightProperty.getPropByName(this.PropType);
}
}

View File

@ -0,0 +1,67 @@
package emu.grasscutter.data.def;
import java.util.List;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.FightProperty;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ResourceType(name = "ReliquaryLevelExcelConfigData.json")
public class ReliquaryLevelData extends GenshinResource {
private int id;
private Int2ObjectMap<Float> propMap;
private int Rank;
private int Level;
private int Exp;
private List<RelicLevelProperty> AddProps;
@Override
public int getId() {
return this.id;
}
public int getRank() {
return Rank;
}
public int getLevel() {
return Level;
}
public int getExp() {
return Exp;
}
public float getPropValue(FightProperty prop) {
return getPropValue(prop.getId());
}
public float getPropValue(int id) {
return propMap.get(id);
}
@Override
public void onLoad() {
this.id = (Rank << 8) + this.getLevel();
this.propMap = new Int2ObjectOpenHashMap<>();
for (RelicLevelProperty p : AddProps) {
this.propMap.put(FightProperty.getPropByName(p.getPropType()).getId(), (Float) p.getValue());
}
}
public class RelicLevelProperty {
private String PropType;
private float Value;
public String getPropType() {
return PropType;
}
public float getValue() {
return Value;
}
}
}

View File

@ -0,0 +1,37 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.props.FightProperty;
@ResourceType(name = "ReliquaryMainPropExcelConfigData.json")
public class ReliquaryMainPropData extends GenshinResource {
private int Id;
private int PropDepotId;
private String PropType;
private String AffixName;
private int Weight;
private FightProperty fightProp;
@Override
public int getId() {
return Id;
}
public int getPropDepotId() {
return PropDepotId;
}
public int getWeight() {
return Weight;
}
public FightProperty getFightProp() {
return fightProp;
}
@Override
public void onLoad() {
this.fightProp = FightProperty.getPropByName(this.PropType);
}
}

View File

@ -0,0 +1,39 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "ReliquarySetExcelConfigData.json")
public class ReliquarySetData extends GenshinResource {
private int SetId;
private int[] SetNeedNum;
private int EquipAffixId;
private int DisableFilter;
private int[] ContainsList;
@Override
public int getId() {
return SetId;
}
public int[] getSetNeedNum() {
return SetNeedNum;
}
public int getEquipAffixId() {
return EquipAffixId;
}
public int getDisableFilter() {
return DisableFilter;
}
public int[] getContainsList() {
return ContainsList;
}
@Override
public void onLoad() {
}
}

View File

@ -0,0 +1,32 @@
package emu.grasscutter.data.def;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.CurveInfo;
@ResourceType(name = "WeaponCurveExcelConfigData.json")
public class WeaponCurveData extends GenshinResource {
private int Level;
private CurveInfo[] CurveInfos;
private Map<String, Float> curveInfos;
@Override
public int getId() {
return Level;
}
public float getMultByProp(String fightProp) {
return curveInfos.getOrDefault(fightProp, 1f);
}
@Override
public void onLoad() {
this.curveInfos = new HashMap<>();
Stream.of(this.CurveInfos).forEach(info -> this.curveInfos.put(info.getType(), info.getValue()));
}
}

View File

@ -0,0 +1,23 @@
package emu.grasscutter.data.def;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
@ResourceType(name = "WeaponLevelExcelConfigData.json")
public class WeaponLevelData extends GenshinResource {
private int Level;
private int[] RequiredExps;
@Override
public int getId() {
return this.Level;
}
public int getLevel() {
return Level;
}
public int[] getRequiredExps() {
return RequiredExps;
}
}

View File

@ -0,0 +1,74 @@
package emu.grasscutter.data.def;
import java.util.ArrayList;
import emu.grasscutter.data.GenshinResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.common.ItemParamData;
@ResourceType(name = "WeaponPromoteExcelConfigData.json")
public class WeaponPromoteData extends GenshinResource {
private int WeaponPromoteId;
private int PromoteLevel;
private ItemParamData[] CostItems;
private int CoinCost;
private FightPropData[] AddProps;
private int UnlockMaxLevel;
private int RequiredPlayerLevel;
@Override
public int getId() {
return (WeaponPromoteId << 8) + PromoteLevel;
}
public int getWeaponPromoteId() {
return WeaponPromoteId;
}
public int getPromoteLevel() {
return PromoteLevel;
}
public ItemParamData[] getCostItems() {
return CostItems;
}
public int getCoinCost() {
return CoinCost;
}
public FightPropData[] getAddProps() {
return AddProps;
}
public int getUnlockMaxLevel() {
return UnlockMaxLevel;
}
public int getRequiredPlayerLevel() {
return RequiredPlayerLevel;
}
@Override
public void onLoad() {
// Trim item params
ArrayList<ItemParamData> trim = new ArrayList<>(getAddProps().length);
for (ItemParamData itemParam : getCostItems()) {
if (itemParam.getId() == 0) {
continue;
}
trim.add(itemParam);
}
this.CostItems = trim.toArray(new ItemParamData[trim.size()]);
// Trim fight prop data
ArrayList<FightPropData> parsed = new ArrayList<>(getAddProps().length);
for (FightPropData prop : getAddProps()) {
if (prop.getPropType() != null && prop.getValue() != 0f) {
prop.onLoad();
parsed.add(prop);
}
}
this.AddProps = parsed.toArray(new FightPropData[parsed.size()]);
}
}

View File

@ -0,0 +1,23 @@
package emu.grasscutter.database;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
@Entity(value = "counters", noClassnameStored = true)
public class DatabaseCounter {
@Id
private String id;
private int count;
public DatabaseCounter() {}
public DatabaseCounter(String id) {
this.id = id;
this.count = 10000;
}
public int getNextId() {
int id = ++count;
return id;
}
}

View File

@ -0,0 +1,207 @@
package emu.grasscutter.database;
import java.util.List;
import com.mongodb.WriteResult;
import dev.morphia.query.FindOptions;
import dev.morphia.query.Query;
import dev.morphia.query.internal.MorphiaCursor;
import emu.grasscutter.GenshinConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.friends.Friendship;
import emu.grasscutter.game.inventory.GenshinItem;
public class DatabaseHelper {
protected static FindOptions FIND_ONE = new FindOptions().limit(1);
public static Account createAccount(String username) {
return createAccountWithId(username, 0);
}
public static Account createAccountWithId(String username, int reservedId) {
// Unique names only
Account exists = DatabaseHelper.getAccountByName(username);
if (exists != null) {
return null;
}
// Make sure there are no id collisions
if (reservedId > 0) {
// Cannot make account with the same uid as the server console
if (reservedId == GenshinConstants.SERVER_CONSOLE_UID) {
return null;
}
exists = DatabaseHelper.getAccountByPlayerId(reservedId);
if (exists != null) {
return null;
}
}
// Account
Account account = new Account();
account.setUsername(username);
account.setId(Integer.toString(DatabaseManager.getNextId(account)));
if (reservedId > 0) {
account.setPlayerId(reservedId);
}
DatabaseHelper.saveAccount(account);
return account;
}
@Deprecated
public static Account createAccountWithPassword(String username, String password) {
// Unique names only
Account exists = DatabaseHelper.getAccountByName(username);
if (exists != null) {
return null;
}
// Account
Account account = new Account();
account.setId(Integer.toString(DatabaseManager.getNextId(account)));
account.setUsername(username);
account.setPassword(password);
DatabaseHelper.saveAccount(account);
return account;
}
public static void saveAccount(Account account) {
DatabaseManager.getDatastore().save(account);
}
public static Account getAccountByName(String username) {
MorphiaCursor<Account> cursor = DatabaseManager.getDatastore().createQuery(Account.class).field("username").equalIgnoreCase(username).find(FIND_ONE);
if (!cursor.hasNext()) return null;
return cursor.next();
}
public static Account getAccountByToken(String token) {
if (token == null) return null;
MorphiaCursor<Account> cursor = DatabaseManager.getDatastore().createQuery(Account.class).field("token").equal(token).find(FIND_ONE);
if (!cursor.hasNext()) return null;
return cursor.next();
}
public static Account getAccountById(String uid) {
MorphiaCursor<Account> cursor = DatabaseManager.getDatastore().createQuery(Account.class).field("_id").equal(uid).find(FIND_ONE);
if (!cursor.hasNext()) return null;
return cursor.next();
}
private static Account getAccountByPlayerId(int playerId) {
MorphiaCursor<Account> cursor = DatabaseManager.getDatastore().createQuery(Account.class).field("playerId").equal(playerId).find(FIND_ONE);
if (!cursor.hasNext()) return null;
return cursor.next();
}
public static boolean deleteAccount(String username) {
Query<Account> q = DatabaseManager.getDatastore().createQuery(Account.class).field("username").equalIgnoreCase(username);
return DatabaseManager.getDatastore().findAndDelete(q) != null;
}
public static GenshinPlayer getPlayerById(int id) {
Query<GenshinPlayer> query = DatabaseManager.getDatastore().createQuery(GenshinPlayer.class).field("_id").equal(id);
MorphiaCursor<GenshinPlayer> cursor = query.find(FIND_ONE);
if (!cursor.hasNext()) return null;
return cursor.next();
}
public static boolean checkPlayerExists(int id) {
MorphiaCursor<GenshinPlayer> query = DatabaseManager.getDatastore().createQuery(GenshinPlayer.class).field("_id").equal(id).find(FIND_ONE);
return query.hasNext();
}
public static synchronized GenshinPlayer createPlayer(GenshinPlayer character, int reservedId) {
// Check if reserved id
int id = 0;
if (reservedId > 0 && !checkPlayerExists(reservedId)) {
id = reservedId;
character.setId(id);
} else {
do {
id = DatabaseManager.getNextId(character);
}
while (checkPlayerExists(id));
character.setId(id);
}
// Save to database
DatabaseManager.getDatastore().save(character);
return character;
}
public static synchronized int getNextPlayerId(int reservedId) {
// Check if reserved id
int id = 0;
if (reservedId > 0 && !checkPlayerExists(reservedId)) {
id = reservedId;
} else {
do {
id = DatabaseManager.getNextId(GenshinPlayer.class);
}
while (checkPlayerExists(id));
}
return id;
}
public static void savePlayer(GenshinPlayer character) {
DatabaseManager.getDatastore().save(character);
}
public static void saveAvatar(GenshinAvatar avatar) {
DatabaseManager.getDatastore().save(avatar);
}
public static List<GenshinAvatar> getAvatars(GenshinPlayer player) {
Query<GenshinAvatar> query = DatabaseManager.getDatastore().createQuery(GenshinAvatar.class).filter("ownerId", player.getId());
return query.find().toList();
}
public static void saveItem(GenshinItem item) {
DatabaseManager.getDatastore().save(item);
}
public static boolean deleteItem(GenshinItem item) {
WriteResult result = DatabaseManager.getDatastore().delete(item);
return result.wasAcknowledged();
}
public static List<GenshinItem> getInventoryItems(GenshinPlayer player) {
Query<GenshinItem> query = DatabaseManager.getDatastore().createQuery(GenshinItem.class).filter("ownerId", player.getId());
return query.find().toList();
}
public static List<Friendship> getFriends(GenshinPlayer player) {
Query<Friendship> query = DatabaseManager.getDatastore().createQuery(Friendship.class).filter("ownerId", player.getId());
return query.find().toList();
}
public static List<Friendship> getReverseFriends(GenshinPlayer player) {
Query<Friendship> query = DatabaseManager.getDatastore().createQuery(Friendship.class).filter("friendId", player.getId());
return query.find().toList();
}
public static void saveFriendship(Friendship friendship) {
DatabaseManager.getDatastore().save(friendship);
}
public static void deleteFriendship(Friendship friendship) {
DatabaseManager.getDatastore().delete(friendship);
}
public static Friendship getReverseFriendship(Friendship friendship) {
Query<Friendship> query = DatabaseManager.getDatastore().createQuery(Friendship.class);
query.and(
query.criteria("ownerId").equal(friendship.getFriendId()),
query.criteria("friendId").equal(friendship.getOwnerId())
);
MorphiaCursor<Friendship> reverseFriendship = query.find(FIND_ONE);
if (!reverseFriendship.hasNext()) return null;
return reverseFriendship.next();
}
}

View File

@ -0,0 +1,95 @@
package emu.grasscutter.database;
import java.sql.Connection;
import java.sql.SQLException;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import dev.morphia.Datastore;
import dev.morphia.Morphia;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.friends.Friendship;
import emu.grasscutter.game.inventory.GenshinItem;
public class DatabaseManager {
private static MongoClient mongoClient;
private static Morphia morphia;
private static Datastore datastore;
private static Class<?>[] mappedClasses = new Class<?>[] {
DatabaseCounter.class, Account.class, GenshinPlayer.class, GenshinAvatar.class, GenshinItem.class, Friendship.class
};
public static MongoClient getMongoClient() {
return mongoClient;
}
public static Datastore getDatastore() {
return datastore;
}
public static MongoDatabase getDatabase() {
return getDatastore().getDatabase();
}
public static Connection getConnection() throws SQLException {
return null;
}
public static void initialize() {
// Initialize
mongoClient = new MongoClient(new MongoClientURI(Grasscutter.getConfig().DatabaseUrl));
morphia = new Morphia();
// TODO Update when migrating to Morphia 2.0
morphia.getMapper().getOptions().setStoreEmpties(true);
morphia.getMapper().getOptions().setStoreNulls(false);
morphia.getMapper().getOptions().setDisableEmbeddedIndexes(true);
// Map
morphia.map(mappedClasses);
// Build datastore
datastore = morphia.createDatastore(mongoClient, Grasscutter.getConfig().DatabaseCollection);
// Ensure indexes
try {
datastore.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 = datastore.getDatabase().listCollectionNames();
for (String name : collections) {
datastore.getDatabase().getCollection(name).dropIndexes();
}
// Add back indexes
datastore.ensureIndexes();
}
}
}
public static synchronized int getNextId(Class<?> c) {
DatabaseCounter counter = getDatastore().createQuery(DatabaseCounter.class).field("_id").equal(c.getSimpleName()).find().tryNext();
if (counter == null) {
counter = new DatabaseCounter(c.getSimpleName());
}
try {
return counter.getNextId();
} finally {
getDatastore().save(counter);
}
}
public static synchronized int getNextId(Object o) {
return getNextId(o.getClass());
}
}

View File

@ -0,0 +1,98 @@
package emu.grasscutter.game;
import dev.morphia.annotations.Collation;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils;
import dev.morphia.annotations.IndexOptions;
@Entity(value = "accounts", noClassnameStored = true)
public class Account {
@Id private String id;
@Indexed(options = @IndexOptions(unique = true))
@Collation(locale = "simple", caseLevel = true)
private String username;
private String password; // Unused for now
private int playerId;
private String email;
private String token;
private String sessionKey; // Session token for dispatch server
@Deprecated
public Account() {}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public int getPlayerId() {
return this.playerId;
}
public void setPlayerId(int playerId) {
this.playerId = playerId;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getSessionKey() {
return this.sessionKey;
}
public String generateSessionKey() {
this.sessionKey = Utils.bytesToHex(Crypto.createSessionKey(32));
this.save();
return this.sessionKey;
}
// TODO make unique
public String generateLoginToken() {
this.token = Utils.bytesToHex(Crypto.createSessionKey(32));
this.save();
return this.token;
}
public void save() {
DatabaseHelper.saveAccount(this);
}
}

View File

@ -0,0 +1,29 @@
package emu.grasscutter.game;
public class CoopRequest {
private final GenshinPlayer requester;
private final long requestTime;
private final long expireTime;
public CoopRequest(GenshinPlayer requester) {
this.requester = requester;
this.requestTime = System.currentTimeMillis();
this.expireTime = this.requestTime + 10000;
}
public GenshinPlayer getRequester() {
return requester;
}
public long getRequestTime() {
return requestTime;
}
public long getExpireTime() {
return expireTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > getExpireTime();
}
}

View File

@ -0,0 +1,759 @@
package emu.grasscutter.game;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import dev.morphia.annotations.*;
import emu.grasscutter.GenshinConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.PlayerLevelData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.avatar.AvatarProfileData;
import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.GenshinEntity;
import emu.grasscutter.game.friends.FriendsList;
import emu.grasscutter.game.friends.PlayerProfile;
import emu.grasscutter.game.gacha.PlayerGachaInfo;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.HeadImageOuterClass.HeadImage;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType;
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.server.game.GameServer;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAbilityInvocationsNotify;
import emu.grasscutter.server.packet.send.PacketAvatarAddNotify;
import emu.grasscutter.server.packet.send.PacketAvatarDataNotify;
import emu.grasscutter.server.packet.send.PacketAvatarGainCostumeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarGainFlycloakNotify;
import emu.grasscutter.server.packet.send.PacketCombatInvocationsNotify;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
import emu.grasscutter.server.packet.send.PacketOpenStateUpdateNotify;
import emu.grasscutter.server.packet.send.PacketPlayerApplyEnterMpResultNotify;
import emu.grasscutter.server.packet.send.PacketPlayerDataNotify;
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.PacketSetNameCardRsp;
import emu.grasscutter.server.packet.send.PacketStoreWeightLimitNotify;
import emu.grasscutter.server.packet.send.PacketUnlockNameCardNotify;
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@Entity(value = "players", noClassnameStored = true)
public class GenshinPlayer {
@Id private int id;
@Indexed(options = @IndexOptions(unique = true)) private String accountId;
@Transient private Account account;
private String nickname;
private String signature;
private int headImage;
private int nameCardId = 210001;
private Position pos;
private Position rotation;
private Map<Integer, Integer> properties;
private Set<Integer> nameCardList;
private Set<Integer> flyCloakList;
private Set<Integer> costumeList;
@Transient private long nextGuid = 0;
@Transient private int peerId;
@Transient private World world;
@Transient private GameSession session;
@Transient private AvatarStorage avatars;
@Transient private Inventory inventory;
@Transient private FriendsList friendsList;
private TeamManager teamManager;
private PlayerGachaInfo gachaInfo;
private PlayerProfile playerProfile;
private MpSettingType mpSetting = MpSettingType.MpSettingEnterAfterApply;
private boolean showAvatar;
private ArrayList<AvatarProfileData> shownAvatars;
private int sceneId;
private int regionId;
private int mainCharacterId;
private boolean godmode;
@Transient private boolean paused;
@Transient private int enterSceneToken;
@Transient private SceneLoadState sceneState;
@Transient private boolean hasSentAvatarDataNotify;
@Transient private final Int2ObjectMap<CoopRequest> coopRequests;
@Transient private final InvokeHandler<CombatInvokeEntry> combatInvokeHandler;
@Transient private final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler;
@Deprecated @SuppressWarnings({ "rawtypes", "unchecked" }) // Morphia only!
public GenshinPlayer() {
this.inventory = new Inventory(this);
this.avatars = new AvatarStorage(this);
this.friendsList = new FriendsList(this);
this.pos = new Position();
this.rotation = new Position();
this.properties = new HashMap<>();
for (PlayerProperty prop : PlayerProperty.values()) {
if (prop.getId() < 10000) {
continue;
}
this.properties.put(prop.getId(), 0);
}
this.setSceneId(3);
this.setRegionId(1);
this.sceneState = SceneLoadState.NONE;
this.coopRequests = new Int2ObjectOpenHashMap<>();
this.combatInvokeHandler = new InvokeHandler(PacketCombatInvocationsNotify.class);
this.abilityInvokeHandler = new InvokeHandler(PacketAbilityInvocationsNotify.class);
}
// On player creation
public GenshinPlayer(GameSession session) {
this();
this.account = session.getAccount();
this.accountId = this.getAccount().getId();
this.session = session;
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);
this.setProperty(PlayerProperty.PROP_IS_FLYABLE, 1);
this.setProperty(PlayerProperty.PROP_IS_TRANSFERABLE, 1);
this.setProperty(PlayerProperty.PROP_MAX_STAMINA, 24000);
this.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, 24000);
this.setProperty(PlayerProperty.PROP_PLAYER_RESIN, 160);
this.getFlyCloakList().add(140001);
this.getNameCardList().add(210001);
this.getPos().set(GenshinConstants.START_POSITION);
this.getRotation().set(0, 307, 0);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public long getNextGuid() {
long nextId = ++this.nextGuid;
return ((long) this.getId() << 32) + nextId;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
this.account.setPlayerId(getId());
}
public GameSession getSession() {
return session;
}
public void setSession(GameSession session) {
this.session = session;
}
public boolean isOnline() {
return this.getSession() != null && this.getSession().isActive();
}
public GameServer getServer() {
return this.getSession().getServer();
}
public synchronized World getWorld() {
return this.world;
}
public synchronized void setWorld(World world) {
this.world = world;
}
public int getGmLevel() {
return 1;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickName) {
this.nickname = nickName;
this.updateProfile();
}
public int getHeadImage() {
return headImage;
}
public void setHeadImage(int picture) {
this.headImage = picture;
this.updateProfile();
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
this.updateProfile();
}
public Position getPos() {
return pos;
}
public Position getRotation() {
return rotation;
}
public int getLevel() {
return this.getProperty(PlayerProperty.PROP_PLAYER_LEVEL);
}
public int getExp() {
return this.getProperty(PlayerProperty.PROP_PLAYER_EXP);
}
public int getWorldLevel() {
return this.getProperty(PlayerProperty.PROP_PLAYER_WORLD_LEVEL);
}
public int getPrimogems() {
return this.getProperty(PlayerProperty.PROP_PLAYER_HCOIN);
}
public void setPrimogems(int primogem) {
this.setProperty(PlayerProperty.PROP_PLAYER_HCOIN, primogem);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_HCOIN));
}
public int getMora() {
return this.getProperty(PlayerProperty.PROP_PLAYER_SCOIN);
}
public void setMora(int mora) {
this.setProperty(PlayerProperty.PROP_PLAYER_SCOIN, mora);
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_SCOIN));
}
private int getExpRequired(int level) {
PlayerLevelData levelData = GenshinData.getPlayerLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0;
}
private float getExpModifier() {
return Grasscutter.getConfig().getGameRates().ADVENTURE_EXP_RATE;
}
// Affected by exp rate
public void earnExp(int exp) {
addExpDirectly((int) (exp * getExpModifier()));
}
// Directly give player exp
public void addExpDirectly(int gain) {
boolean hasLeveledUp = false;
int level = getLevel();
int exp = getExp();
int reqExp = getExpRequired(level);
exp += gain;
while (exp >= reqExp && reqExp > 0) {
exp -= reqExp;
level += 1;
reqExp = getExpRequired(level);
hasLeveledUp = true;
}
if (hasLeveledUp) {
// Set level property
this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, level);
// Update social status
this.updateProfile();
// Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_LEVEL));
}
// Set exp
this.setProperty(PlayerProperty.PROP_PLAYER_EXP, exp);
// Update player with packet
this.sendPacket(new PacketPlayerPropNotify(this, PlayerProperty.PROP_PLAYER_EXP));
}
private void updateProfile() {
getProfile().syncWithCharacter(this);
}
public boolean isFirstLoginEnterScene() {
return !this.hasSentAvatarDataNotify;
}
public TeamManager getTeamManager() {
return this.teamManager;
}
public PlayerGachaInfo getGachaInfo() {
return gachaInfo;
}
public PlayerProfile getProfile() {
if (this.playerProfile == null) {
this.playerProfile = new PlayerProfile(this);
this.save();
}
return playerProfile;
}
public Map<Integer, Integer> getProperties() {
return properties;
}
public void setProperty(PlayerProperty prop, int value) {
getProperties().put(prop.getId(), value);
}
public int getProperty(PlayerProperty prop) {
return getProperties().get(prop.getId());
}
public Set<Integer> getFlyCloakList() {
return flyCloakList;
}
public Set<Integer> getCostumeList() {
return costumeList;
}
public Set<Integer> getNameCardList() {
return this.nameCardList;
}
public MpSettingType getMpSetting() {
return mpSetting;
}
public synchronized Int2ObjectMap<CoopRequest> getCoopRequests() {
return coopRequests;
}
public InvokeHandler<CombatInvokeEntry> getCombatInvokeHandler() {
return this.combatInvokeHandler;
}
public InvokeHandler<AbilityInvokeEntry> getAbilityInvokeHandler() {
return this.abilityInvokeHandler;
}
public void setMpSetting(MpSettingType mpSetting) {
this.mpSetting = mpSetting;
}
public AvatarStorage getAvatars() {
return avatars;
}
public Inventory getInventory() {
return inventory;
}
public FriendsList getFriendsList() {
return this.friendsList;
}
public int getEnterSceneToken() {
return enterSceneToken;
}
public void setEnterSceneToken(int enterSceneToken) {
this.enterSceneToken = enterSceneToken;
}
public int getNameCardId() {
return nameCardId;
}
public void setNameCardId(int nameCardId) {
this.nameCardId = nameCardId;
this.updateProfile();
}
public int getMainCharacterId() {
return mainCharacterId;
}
public void setMainCharacterId(int mainCharacterId) {
this.mainCharacterId = mainCharacterId;
}
public int getPeerId() {
return peerId;
}
public void setPeerId(int peerId) {
this.peerId = peerId;
}
public int getClientTime() {
return session.getClientTime();
}
public long getLastPingTime() {
return session.getLastPingTime();
}
public boolean isPaused() {
return paused;
}
public void setPaused(boolean newPauseState) {
boolean oldPauseState = this.paused;
this.paused = newPauseState;
if (newPauseState && !oldPauseState) {
this.onPause();
} else if (oldPauseState && !newPauseState) {
this.onUnpause();
}
}
public SceneLoadState getSceneLoadState() {
return sceneState;
}
public void setSceneLoadState(SceneLoadState sceneState) {
this.sceneState = sceneState;
}
public boolean isInMultiplayer() {
return this.getWorld() != null && this.getWorld().isMultiplayer();
}
public int getSceneId() {
return sceneId;
}
public void setSceneId(int sceneId) {
this.sceneId = sceneId;
}
public int getRegionId() {
return regionId;
}
public void setRegionId(int regionId) {
this.regionId = regionId;
}
public boolean hasGodmode() {
return godmode;
}
public void setGodmode(boolean godmode) {
this.godmode = godmode;
}
public boolean hasSentAvatarDataNotify() {
return hasSentAvatarDataNotify;
}
public void setHasSentAvatarDataNotify(boolean hasSentAvatarDataNotify) {
this.hasSentAvatarDataNotify = hasSentAvatarDataNotify;
}
public void addAvatar(GenshinAvatar avatar) {
boolean result = getAvatars().addAvatar(avatar);
if (result) {
// Add starting weapon
getAvatars().addStartingWeapon(avatar);
// Try adding to team if possible
//List<EntityAvatar> currentTeam = this.getTeamManager().getCurrentTeam();
boolean addedToTeam = false;
/*
if (currentTeam.size() <= GenshinConstants.MAX_AVATARS_IN_TEAM) {
addedToTeam = currentTeam
}
*/
// Done
if (hasSentAvatarDataNotify()) {
// Recalc stats
avatar.recalcStats();
// Packet
sendPacket(new PacketAvatarAddNotify(avatar, addedToTeam));
}
} else {
// Failed adding avatar
}
}
public void addFlycloak(int flycloakId) {
this.getFlyCloakList().add(flycloakId);
this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId));
}
public void addCostume(int costumeId) {
this.getCostumeList().add(costumeId);
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
}
public void addNameCard(int nameCardId) {
this.getNameCardList().add(nameCardId);
this.sendPacket(new PacketUnlockNameCardNotify(nameCardId));
}
public void setNameCard(int nameCardId) {
if (!this.getNameCardList().contains(nameCardId)) {
return;
}
this.setNameCardId(nameCardId);
this.sendPacket(new PacketSetNameCardRsp(nameCardId));
}
public void dropMessage(Object message) {
this.sendPacket(new PacketPrivateChatNotify(GenshinConstants.SERVER_CONSOLE_UID, getId(), message.toString()));
}
public void interactWith(int gadgetEntityId) {
GenshinEntity entity = getWorld().getEntityById(gadgetEntityId);
if (entity == null) {
return;
}
// Delete
entity.getWorld().removeEntity(entity);
// Handle
if (entity instanceof EntityItem) {
// Pick item
EntityItem drop = (EntityItem) entity;
GenshinItem item = new GenshinItem(drop.getItemData(), drop.getCount());
// Add to inventory
boolean success = getInventory().addItem(item);
if (success) {
this.sendPacket(new PacketGadgetInteractRsp(drop, InteractType.InteractPickItem));
this.sendPacket(new PacketItemAddHintNotify(item, ActionReason.SubfieldDrop));
}
}
return;
}
public void onPause() {
}
public void onUnpause() {
}
public void sendPacket(GenshinPacket packet) {
if (this.hasSentAvatarDataNotify) {
this.getSession().send(packet);
}
}
public OnlinePlayerInfo getOnlinePlayerInfo() {
OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder()
.setUid(this.getId())
.setNickname(this.getNickname())
.setPlayerLevel(this.getLevel())
.setMpSettingType(this.getMpSetting())
.setNameCardId(this.getNameCardId())
.setSignature(this.getSignature())
.setAvatar(HeadImage.newBuilder().setAvatarId(this.getHeadImage()));
if (this.getWorld() != null) {
onlineInfo.setCurPlayerNumInWorld(this.getWorld().getPlayers().indexOf(this) + 1);
} else {
onlineInfo.setCurPlayerNumInWorld(1);
}
return onlineInfo.build();
}
public SocialDetail.Builder getSocialDetail() {
SocialDetail.Builder social = SocialDetail.newBuilder()
.setUid(this.getId())
.setAvatar(HeadImage.newBuilder().setAvatarId(this.getHeadImage()))
.setNickname(this.getNickname())
.setSignature(this.getSignature())
.setLevel(this.getLevel())
.setBirthday(Birthday.newBuilder())
.setWorldLevel(this.getWorldLevel())
.setUnk1(1)
.setUnk3(1)
.setNameCardId(this.getNameCardId())
.setFinishAchievementNum(0);
return social;
}
public PlayerLocationInfo getPlayerLocationInfo() {
return PlayerLocationInfo.newBuilder()
.setUid(this.getId())
.setPos(this.getPos().toProto())
.setRot(this.getRotation().toProto())
.build();
}
public synchronized void onTick() {
// Check ping
if (this.getLastPingTime() > System.currentTimeMillis() + 60000) {
this.getSession().close();
return;
}
// Check co-op requests
Iterator<CoopRequest> it = this.getCoopRequests().values().iterator();
while (it.hasNext()) {
CoopRequest req = it.next();
if (req.isExpired()) {
req.getRequester().sendPacket(new PacketPlayerApplyEnterMpResultNotify(this, false, PlayerApplyEnterMpReason.SystemJudge));
it.remove();
}
}
// Ping
if (this.getWorld() != null) {
this.sendPacket(new PacketWorldPlayerRTTNotify(this.getWorld())); // Player ping
}
}
@PostLoad
private void onLoad() {
this.getTeamManager().setPlayer(this);
}
public void save() {
DatabaseHelper.savePlayer(this);
}
public void onLogin() {
// 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<>();
}
// Check if player object exists in server
// TODO - optimize
GenshinPlayer exists = this.getServer().getPlayerById(getId());
if (exists != null) {
exists.getSession().close();
}
// Load from db
this.getAvatars().loadFromDatabase();
this.getInventory().loadFromDatabase();
this.getAvatars().postLoad();
this.getFriendsList().loadFromDatabase();
// Create world
World world = new World(this);
world.addPlayer(this);
// Add to gameserver
if (getSession().isActive()) {
getServer().registerPlayer(this);
getProfile().setPlayer(this); // Set online
}
// Multiplayer setting
this.setProperty(PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE, this.getMpSetting().getNumber());
this.setProperty(PlayerProperty.PROP_IS_MP_MODE_AVAILABLE, 1);
// Packets
session.send(new PacketPlayerDataNotify(this)); // Player data
session.send(new PacketStoreWeightLimitNotify());
session.send(new PacketPlayerStoreNotify(this));
session.send(new PacketAvatarDataNotify(this));
session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world
session.send(new PacketOpenStateUpdateNotify());
// First notify packets sent
this.setHasSentAvatarDataNotify(true);
}
public void onLogout() {
// Leave world
if (this.getWorld() != null) {
this.getWorld().removePlayer(this);
}
// Status stuff
this.getProfile().syncWithCharacter(this);
this.getProfile().setPlayer(null); // Set offline
this.getCoopRequests().clear();
// Save to db
this.save();
this.getTeamManager().saveAvatars();
this.getFriendsList().save();
}
public enum SceneLoadState {
NONE (0), LOADING (1), INIT (2), LOADED (3);
private final int value;
private SceneLoadState(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
}

View File

@ -0,0 +1,66 @@
package emu.grasscutter.game;
import java.util.ArrayList;
import java.util.List;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.proto.ForwardTypeOuterClass.ForwardType;
public class InvokeHandler<T> {
private final List<T> entryListForwardAll;
private final List<T> entryListForwardAllExceptCur;
private final List<T> entryListForwardHost;
private final Class<? extends GenshinPacket> packetClass;
public InvokeHandler(Class<? extends GenshinPacket> packetClass) {
this.entryListForwardAll = new ArrayList<>();
this.entryListForwardAllExceptCur = new ArrayList<>();
this.entryListForwardHost = new ArrayList<>();
this.packetClass = packetClass;
}
public synchronized void addEntry(ForwardType forward, T entry) {
switch (forward) {
case ForwardToAll:
entryListForwardAll.add(entry);
break;
case ForwardToAllExceptCur:
case ForwardToAllExistExceptCur:
entryListForwardAllExceptCur.add(entry);
break;
case ForwardToHost:
entryListForwardHost.add(entry);
break;
default:
break;
}
}
public synchronized void update(GenshinPlayer player) {
if (player.getWorld() == null) {
this.entryListForwardAll.clear();
this.entryListForwardAllExceptCur.clear();
this.entryListForwardHost.clear();
return;
}
try {
if (entryListForwardAll.size() > 0) {
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAll);
player.getWorld().broadcastPacket(packet);
this.entryListForwardAll.clear();
}
if (entryListForwardAllExceptCur.size() > 0) {
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAllExceptCur);
player.getWorld().broadcastPacketToOthers(player, packet);
this.entryListForwardAllExceptCur.clear();
}
if (entryListForwardHost.size() > 0) {
GenshinPacket packet = packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardHost);
player.getWorld().getHost().sendPacket(packet);
this.entryListForwardHost.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,73 @@
package emu.grasscutter.game;
import java.util.ArrayList;
import java.util.List;
import emu.grasscutter.GenshinConstants;
import emu.grasscutter.game.avatar.GenshinAvatar;
public class TeamInfo {
private String name;
private List<Integer> avatars;
public TeamInfo() {
this.name = "";
this.avatars = new ArrayList<>(GenshinConstants.MAX_AVATARS_IN_TEAM);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Integer> getAvatars() {
return avatars;
}
public int size() {
return avatars.size();
}
public boolean contains(GenshinAvatar avatar) {
return getAvatars().contains(avatar.getAvatarId());
}
public boolean addAvatar(GenshinAvatar avatar) {
if (size() >= GenshinConstants.MAX_AVATARS_IN_TEAM || contains(avatar)) {
return false;
}
getAvatars().add(avatar.getAvatarId());
return true;
}
public boolean removeAvatar(int slot) {
if (size() <= 1) {
return false;
}
getAvatars().remove(slot);
return true;
}
public void copyFrom(TeamInfo team) {
copyFrom(team, GenshinConstants.MAX_AVATARS_IN_TEAM);
}
public void copyFrom(TeamInfo team, int maxTeamSize) {
// Clear
this.getAvatars().clear();
// Copy from team
int len = Math.min(team.getAvatars().size(), maxTeamSize);
for (int i = 0; i < len; i++) {
int id = team.getAvatars().get(i);
this.getAvatars().add(id);
}
}
}

View File

@ -0,0 +1,484 @@
package emu.grasscutter.game;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import dev.morphia.annotations.Transient;
import emu.grasscutter.GenshinConstants;
import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.server.packet.send.PacketAvatarDieAnimationEndRsp;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarTeamUpdateNotify;
import emu.grasscutter.server.packet.send.PacketChangeAvatarRsp;
import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp;
import emu.grasscutter.server.packet.send.PacketChangeTeamNameRsp;
import emu.grasscutter.server.packet.send.PacketChooseCurAvatarTeamRsp;
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
import emu.grasscutter.server.packet.send.PacketSceneTeamUpdateNotify;
import emu.grasscutter.server.packet.send.PacketSetUpAvatarTeamRsp;
import emu.grasscutter.server.packet.send.PacketWorldPlayerDieNotify;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
public class TeamManager {
@Transient private GenshinPlayer player;
private Map<Integer, TeamInfo> teams;
private int currentTeamIndex;
private int currentCharacterIndex;
@Transient private TeamInfo mpTeam;
@Transient private int entityId;
@Transient private final List<EntityAvatar> avatars;
@Transient private final List<EntityGadget> gadgets;
@Transient private final IntSet teamResonances;
@Transient private final IntSet teamResonancesConfig;
public TeamManager() {
this.mpTeam = new TeamInfo();
this.avatars = new ArrayList<>();
this.gadgets = new ArrayList<>();
this.teamResonances = new IntOpenHashSet();
this.teamResonancesConfig = new IntOpenHashSet();
}
public TeamManager(GenshinPlayer player) {
this();
this.player = player;
this.teams = new HashMap<>();
this.currentTeamIndex = 1;
for (int i = 1; i <= GenshinConstants.MAX_TEAMS; i++) {
this.teams.put(i, new TeamInfo());
}
}
public GenshinPlayer getPlayer() {
return player;
}
public World getWorld() {
return player.getWorld();
}
public void setPlayer(GenshinPlayer player) {
this.player = player;
}
public Map<Integer, TeamInfo> getTeams() {
return this.teams;
}
public TeamInfo getMpTeam() {
return mpTeam;
}
public void setMpTeam(TeamInfo mpTeam) {
this.mpTeam = mpTeam;
}
public int getCurrentTeamId() {
// Starts from 1
return currentTeamIndex;
}
private void setCurrentTeamId(int currentTeamIndex) {
this.currentTeamIndex = currentTeamIndex;
}
public int getCurrentCharacterIndex() {
return currentCharacterIndex;
}
public void setCurrentCharacterIndex(int currentCharacterIndex) {
this.currentCharacterIndex = currentCharacterIndex;
}
public long getCurrentCharacterGuid() {
return getCurrentAvatarEntity().getAvatar().getGuid();
}
public TeamInfo getCurrentTeamInfo() {
if (this.getPlayer().isInMultiplayer()) {
return this.getMpTeam();
}
return this.getTeams().get(this.currentTeamIndex);
}
public TeamInfo getCurrentSinglePlayerTeamInfo() {
return this.getTeams().get(this.currentTeamIndex);
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public IntSet getTeamResonances() {
return teamResonances;
}
public IntSet getTeamResonancesConfig() {
return teamResonancesConfig;
}
public List<EntityAvatar> getActiveTeam() {
return avatars;
}
public EntityAvatar getCurrentAvatarEntity() {
return getActiveTeam().get(currentCharacterIndex);
}
public boolean isSpawned() {
return getPlayer().getWorld() != null && getPlayer().getWorld().getEntities().containsKey(getCurrentAvatarEntity().getId());
}
public int getMaxTeamSize() {
if (getPlayer().isInMultiplayer()) {
if (getPlayer().getWorld().getHost() == this.getPlayer()) {
return Math.max(1, (int) Math.ceil(GenshinConstants.MAX_AVATARS_IN_TEAM / (double) getWorld().getPlayerCount()));
}
return Math.max(1, (int) Math.floor(GenshinConstants.MAX_AVATARS_IN_TEAM / (double) getWorld().getPlayerCount()));
}
return GenshinConstants.MAX_AVATARS_IN_TEAM;
}
// Methods
public void updateTeamResonances() {
Int2IntOpenHashMap map = new Int2IntOpenHashMap();
this.getTeamResonances().clear();
this.getTeamResonancesConfig().clear();
for (EntityAvatar entity : getActiveTeam()) {
AvatarSkillDepotData skillData = entity.getAvatar().getAvatarData().getSkillDepot();
if (skillData != null) {
map.addTo(skillData.getElementType().getValue(), 1);
}
}
for (Int2IntMap.Entry e : map.int2IntEntrySet()) {
if (e.getIntValue() >= 2) {
ElementType element = ElementType.getTypeByValue(e.getIntKey());
if (element.getTeamResonanceId() != 0) {
this.getTeamResonances().add(element.getTeamResonanceId());
this.getTeamResonancesConfig().add(element.getConfigHash());
}
}
}
// No resonances
if (this.getTeamResonances().size() == 0) {
this.getTeamResonances().add(ElementType.Default.getTeamResonanceId());
this.getTeamResonancesConfig().add(ElementType.Default.getTeamResonanceId());
}
}
private void updateTeamEntities(GenshinPacket responsePacket) {
// Sanity check - Should never happen
if (this.getCurrentTeamInfo().getAvatars().size() <= 0) {
return;
}
// If current team has changed
EntityAvatar currentEntity = this.getCurrentAvatarEntity();
Int2ObjectMap<EntityAvatar> existingAvatars = new Int2ObjectOpenHashMap<>();
int prevSelectedAvatarIndex = -1;
for (EntityAvatar entity : getActiveTeam()) {
existingAvatars.put(entity.getAvatar().getAvatarId(), entity);
}
// Clear active team entity list
this.getActiveTeam().clear();
// Add back entities into team
for (int i = 0; i < this.getCurrentTeamInfo().getAvatars().size(); i++) {
int avatarId = this.getCurrentTeamInfo().getAvatars().get(i);
EntityAvatar entity = null;
if (existingAvatars.containsKey(avatarId)) {
entity = existingAvatars.get(avatarId);
existingAvatars.remove(avatarId);
if (entity == currentEntity) {
prevSelectedAvatarIndex = i;
}
} else {
entity = new EntityAvatar(getPlayer().getWorld(), getPlayer().getAvatars().getAvatarById(avatarId));
}
this.getActiveTeam().add(entity);
}
// Unload removed entities
for (EntityAvatar entity : existingAvatars.values()) {
getPlayer().getWorld().removeEntity(entity);
entity.getAvatar().save();
}
// Set new selected character index
if (prevSelectedAvatarIndex == -1) {
// Previous selected avatar is not in the same spot, we will select the current one in the prev slot
prevSelectedAvatarIndex = Math.min(this.currentCharacterIndex, getCurrentTeamInfo().getAvatars().size() - 1);
}
this.currentCharacterIndex = prevSelectedAvatarIndex;
// Update team resonances
updateTeamResonances();
// Packets
getPlayer().getWorld().broadcastPacket(new PacketSceneTeamUpdateNotify(getPlayer()));
// Run callback
if (responsePacket != null) {
getPlayer().sendPacket(responsePacket);
}
// Check if character changed
if (currentEntity != getCurrentAvatarEntity()) {
// Remove and Add
getWorld().replaceEntity(currentEntity, getCurrentAvatarEntity());
}
}
public synchronized void setupAvatarTeam(int teamId, List<Long> list) {
// Sanity checks
if (list.size() == 0 || list.size() > getMaxTeamSize() || getPlayer().isInMultiplayer()) {
return;
}
// Get team
TeamInfo teamInfo = this.getTeams().get(teamId);
if (teamInfo == null) {
return;
}
// Set team data
LinkedHashSet<GenshinAvatar> newTeam = new LinkedHashSet<>();
for (int i = 0; i < list.size(); i++) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i));
if (avatar == null || newTeam.contains(avatar)) {
// Should never happen
return;
}
newTeam.add(avatar);
}
// Clear current team info and add avatars from our new team
teamInfo.getAvatars().clear();
for (GenshinAvatar avatar : newTeam) {
teamInfo.addAvatar(avatar);
}
// Update packet
getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify(getPlayer()));
// Update entites
if (teamId == this.getCurrentTeamId()) {
this.updateTeamEntities(new PacketSetUpAvatarTeamRsp(getPlayer(), teamId, teamInfo));
} else {
getPlayer().sendPacket(new PacketSetUpAvatarTeamRsp(getPlayer(), teamId, teamInfo));
}
}
public void setupMpTeam(List<Long> list) {
// Sanity checks
if (list.size() == 0 || list.size() > getMaxTeamSize() || !getPlayer().isInMultiplayer()) {
return;
}
TeamInfo teamInfo = this.getMpTeam();
// Set team data
LinkedHashSet<GenshinAvatar> newTeam = new LinkedHashSet<>();
for (int i = 0; i < list.size(); i++) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i));
if (avatar == null || newTeam.contains(avatar)) {
// Should never happen
return;
}
newTeam.add(avatar);
}
// Clear current team info and add avatars from our new team
teamInfo.getAvatars().clear();
for (GenshinAvatar avatar : newTeam) {
teamInfo.addAvatar(avatar);
}
// Packet
this.updateTeamEntities(new PacketChangeMpTeamAvatarRsp(getPlayer(), teamInfo));
}
public synchronized void setCurrentTeam(int teamId) {
//
if (getPlayer().isInMultiplayer()) {
return;
}
// Get team
TeamInfo teamInfo = this.getTeams().get(teamId);
if (teamInfo == null || teamInfo.getAvatars().size() == 0) {
return;
}
// Set
this.setCurrentTeamId(teamId);
this.updateTeamEntities(new PacketChooseCurAvatarTeamRsp(teamId));
}
public synchronized void setTeamName(int teamId, String teamName) {
// Get team
TeamInfo teamInfo = this.getTeams().get(teamId);
if (teamInfo == null) {
return;
}
teamInfo.setName(teamName);
// Packet
getPlayer().sendPacket(new PacketChangeTeamNameRsp(teamId, teamName));
}
public synchronized void changeAvatar(long guid) {
EntityAvatar oldEntity = this.getCurrentAvatarEntity();
if (guid == oldEntity.getAvatar().getGuid()) {
return;
}
EntityAvatar newEntity = null;
int index = -1;
for (int i = 0; i < getActiveTeam().size(); i++) {
if (guid == getActiveTeam().get(i).getAvatar().getGuid()) {
index = i;
newEntity = getActiveTeam().get(i);
}
}
if (index < 0 || newEntity == oldEntity) {
return;
}
// Set index
this.setCurrentCharacterIndex(index);
// Old entity motion state
oldEntity.setMotionState(MotionState.MotionStandby);
// Remove and Add
getWorld().replaceEntity(oldEntity, newEntity);
getPlayer().sendPacket(new PacketChangeAvatarRsp(guid));
}
public void onAvatarDie(long dieGuid) {
EntityAvatar deadAvatar = this.getCurrentAvatarEntity();
if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) {
return;
}
// Replacement avatar
EntityAvatar replacement = null;
int replaceIndex = -1;
for (int i = 0; i < this.getActiveTeam().size(); i++) {
EntityAvatar entity = this.getActiveTeam().get(i);
if (entity.isAlive()) {
replaceIndex = i;
replacement = entity;
break;
}
}
if (replacement == null) {
// No more living team members...
getPlayer().sendPacket(new PacketWorldPlayerDieNotify(deadAvatar.getKilledType(), deadAvatar.getKilledBy()));
} else {
// Set index and spawn replacement member
this.setCurrentCharacterIndex(replaceIndex);
getWorld().addEntity(replacement);
}
// Response packet
getPlayer().sendPacket(new PacketAvatarDieAnimationEndRsp(deadAvatar.getId(), 0));
}
public boolean reviveAvatar(GenshinAvatar avatar) {
for (EntityAvatar entity : getActiveTeam()) {
if (entity.getAvatar() == avatar) {
if (entity.isAlive()) {
return false;
}
entity.setFightProperty(
FightProperty.FIGHT_PROP_CUR_HP,
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .1f
);
getPlayer().sendPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP));
getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
return true;
}
}
return false;
}
public void respawnTeam() {
// Make sure all team members are dead
for (EntityAvatar entity : getActiveTeam()) {
if (entity.isAlive()) {
return;
}
}
// Revive all team members
for (EntityAvatar entity : getActiveTeam()) {
entity.setFightProperty(
FightProperty.FIGHT_PROP_CUR_HP,
entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .4f
);
getPlayer().sendPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP));
getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar()));
}
// Teleport player
getPlayer().sendPacket(new PacketPlayerEnterSceneNotify(getPlayer(), EnterType.EnterSelf, EnterReason.Revival, 3, GenshinConstants.START_POSITION));
// Set player position
player.setSceneId(3);
player.getPos().set(GenshinConstants.START_POSITION);
// Packets
getPlayer().sendPacket(new GenshinPacket(PacketOpcodes.WorldPlayerReviveRsp));
}
public void saveAvatars() {
// Save all avatars from active team
for (EntityAvatar entity : getActiveTeam()) {
entity.getAvatar().save();
}
}
}

View File

@ -0,0 +1,434 @@
package emu.grasscutter.game;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import emu.grasscutter.game.entity.GenshinEntity;
import emu.grasscutter.game.props.ClimateType;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.GenshinPlayer.SceneLoadState;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityClientGadget;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType;
import emu.grasscutter.server.packet.send.PacketDelTeamEntityNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.server.packet.send.PacketSceneEntityDisappearNotify;
import emu.grasscutter.server.packet.send.PacketScenePlayerInfoNotify;
import emu.grasscutter.server.packet.send.PacketSceneTeamUpdateNotify;
import emu.grasscutter.server.packet.send.PacketSyncScenePlayTeamEntityNotify;
import emu.grasscutter.server.packet.send.PacketSyncTeamEntityNotify;
import emu.grasscutter.server.packet.send.PacketWorldPlayerInfoNotify;
import emu.grasscutter.server.packet.send.PacketWorldPlayerRTTNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class World implements Iterable<GenshinPlayer> {
private final GenshinPlayer owner;
private final List<GenshinPlayer> players;
private int levelEntityId;
private int nextEntityId = 0;
private int nextPeerId = 0;
private final Int2ObjectMap<GenshinEntity> entities;
private int worldLevel;
private int sceneId;
private int time;
private ClimateType climate;
private boolean isMultiplayer;
private boolean isDungeon;
public World(GenshinPlayer player) {
this(player, false);
}
public World(GenshinPlayer player, boolean isMultiplayer) {
this.owner = player;
this.players = Collections.synchronizedList(new ArrayList<>());
this.entities = new Int2ObjectOpenHashMap<>();
this.levelEntityId = getNextEntityId(EntityIdType.MPLEVEL);
this.sceneId = player.getSceneId();
this.time = 8 * 60;
this.climate = ClimateType.CLIMATE_SUNNY;
this.worldLevel = player.getWorldLevel();
this.isMultiplayer = isMultiplayer;
}
public GenshinPlayer getHost() {
return owner;
}
public int getLevelEntityId() {
return levelEntityId;
}
public int getHostPeerId() {
if (this.getHost() == null) {
return 0;
}
return this.getHost().getPeerId();
}
public int getNextPeerId() {
return ++this.nextPeerId;
}
public int getSceneId() {
return sceneId;
}
public void setSceneId(int sceneId) {
this.sceneId = sceneId;
}
public int getTime() {
return time;
}
public void changeTime(int time) {
this.time = time % 1440;
}
public int getWorldLevel() {
return worldLevel;
}
public void setWorldLevel(int worldLevel) {
this.worldLevel = worldLevel;
}
public ClimateType getClimate() {
return climate;
}
public void setClimate(ClimateType climate) {
this.climate = climate;
}
public List<GenshinPlayer> getPlayers() {
return players;
}
public int getPlayerCount() {
return getPlayers().size();
}
public Int2ObjectMap<GenshinEntity> getEntities() {
return this.entities;
}
public boolean isInWorld(GenshinEntity entity) {
return this.entities.containsKey(entity.getId());
}
public boolean isMultiplayer() {
return isMultiplayer;
}
public boolean isDungeon() {
return isDungeon;
}
public int getNextEntityId(EntityIdType idType) {
return (idType.getId() << 24) + ++this.nextEntityId;
}
public GenshinEntity getEntityById(int id) {
return this.entities.get(id);
}
public synchronized void addPlayer(GenshinPlayer player) {
// Check if player already in
if (getPlayers().contains(player)) {
return;
}
// Remove player from prev world
if (player.getWorld() != null) {
player.getWorld().removePlayer(player);
}
// Register
player.setWorld(this);
getPlayers().add(player);
player.setPeerId(this.getNextPeerId());
player.getTeamManager().setEntityId(getNextEntityId(EntityIdType.TEAM));
// TODO Update team of all players
this.setupPlayerAvatars(player);
// Info packet for other players
if (this.getPlayers().size() > 1) {
this.updatePlayerInfos(player);
}
}
public synchronized void removePlayer(GenshinPlayer player) {
// Remove team entities
player.sendPacket(
new PacketDelTeamEntityNotify(
player.getSceneId(),
getPlayers().stream().map(p -> p.getTeamManager().getEntityId()).collect(Collectors.toList())
)
);
// Deregister
getPlayers().remove(player);
player.setWorld(null);
this.removePlayerAvatars(player);
// Info packet for other players
if (this.getPlayers().size() > 0) {
this.updatePlayerInfos(player);
}
// Disband world if host leaves
if (getHost() == player) {
List<GenshinPlayer> kicked = new ArrayList<>(this.getPlayers());
for (GenshinPlayer victim : kicked) {
World world = new World(victim);
world.addPlayer(victim);
victim.sendPacket(new PacketPlayerEnterSceneNotify(victim, EnterType.EnterSelf, EnterReason.TeamKick, victim.getWorld().getSceneId(), victim.getPos()));
}
}
}
private void updatePlayerInfos(GenshinPlayer paramPlayer) {
for (GenshinPlayer player : getPlayers()) {
// Dont send packets if player is loading in
if (!player.hasSentAvatarDataNotify() || player.getSceneLoadState().getValue() < SceneLoadState.INIT.getValue() || player == paramPlayer) {
continue;
}
// World player info packets
player.getSession().send(new PacketWorldPlayerInfoNotify(this));
player.getSession().send(new PacketScenePlayerInfoNotify(this));
player.getSession().send(new PacketWorldPlayerRTTNotify(this));
// Team packets
player.getSession().send(new PacketSceneTeamUpdateNotify(player));
player.getSession().send(new PacketSyncTeamEntityNotify(player));
player.getSession().send(new PacketSyncScenePlayTeamEntityNotify(player));
}
}
private void addEntityDirectly(GenshinEntity entity) {
getEntities().put(entity.getId(), entity);
}
public synchronized void addEntity(GenshinEntity entity) {
this.addEntityDirectly(entity);
this.broadcastPacket(new PacketSceneEntityAppearNotify(entity));
}
public synchronized void addEntities(Collection<GenshinEntity> entities) {
for (GenshinEntity entity : entities) {
this.addEntityDirectly(entity);
}
this.broadcastPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VisionBorn));
}
private GenshinEntity removeEntityDirectly(GenshinEntity entity) {
return getEntities().remove(entity.getId());
}
public void removeEntity(GenshinEntity entity) {
this.removeEntity(entity, VisionType.VisionDie);
}
public synchronized void removeEntity(GenshinEntity entity, VisionType visionType) {
GenshinEntity removed = this.removeEntityDirectly(entity);
if (removed != null) {
this.broadcastPacket(new PacketSceneEntityDisappearNotify(removed, visionType));
}
}
public synchronized void replaceEntity(EntityAvatar oldEntity, EntityAvatar newEntity) {
this.removeEntityDirectly(oldEntity);
this.addEntityDirectly(newEntity);
this.broadcastPacket(new PacketSceneEntityDisappearNotify(oldEntity, VisionType.VisionReplace));
this.broadcastPacket(new PacketSceneEntityAppearNotify(newEntity, VisionType.VisionReplace, oldEntity.getId()));
}
private void setupPlayerAvatars(GenshinPlayer player) {
// Copy main team to mp team
if (this.isMultiplayer()) {
player.getTeamManager().getMpTeam().copyFrom(player.getTeamManager().getCurrentSinglePlayerTeamInfo(), player.getTeamManager().getMaxTeamSize());
}
// Clear entities from old team
player.getTeamManager().getActiveTeam().clear();
// Add new entities for player
TeamInfo teamInfo = player.getTeamManager().getCurrentTeamInfo();
for (int avatarId : teamInfo.getAvatars()) {
EntityAvatar entity = new EntityAvatar(this, player.getAvatars().getAvatarById(avatarId));
player.getTeamManager().getActiveTeam().add(entity);
}
}
private void removePlayerAvatars(GenshinPlayer player) {
Iterator<EntityAvatar> it = player.getTeamManager().getActiveTeam().iterator();
while (it.hasNext()) {
this.removeEntity(it.next(), VisionType.VisionRemove);
it.remove();
}
}
public void spawnPlayer(GenshinPlayer player) {
if (isInWorld(player.getTeamManager().getCurrentAvatarEntity())) {
return;
}
if (player.getTeamManager().getCurrentAvatarEntity().getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
player.getTeamManager().getCurrentAvatarEntity().setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f);
}
this.addEntity(player.getTeamManager().getCurrentAvatarEntity());
}
public void showOtherEntities(GenshinPlayer player) {
List<GenshinEntity> entities = new LinkedList<>();
GenshinEntity currentEntity = player.getTeamManager().getCurrentAvatarEntity();
for (GenshinEntity entity : this.getEntities().values()) {
if (entity == currentEntity) {
continue;
}
entities.add(entity);
}
player.sendPacket(new PacketSceneEntityAppearNotify(entities, VisionType.VisionMeet));
}
public void handleAttack(AttackResult result) {
//GenshinEntity attacker = getEntityById(result.getAttackerId());
GenshinEntity target = getEntityById(result.getDefenseId());
if (target == null) {
return;
}
// Godmode check
if (target instanceof EntityAvatar) {
if (((EntityAvatar) target).getPlayer().hasGodmode()) {
return;
}
}
// Lose hp
target.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -result.getDamage());
// Check if dead
boolean isDead = false;
if (target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
target.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
isDead = true;
}
// Packets
this.broadcastPacket(new PacketEntityFightPropUpdateNotify(target, FightProperty.FIGHT_PROP_CUR_HP));
// Check if dead
if (isDead) {
this.killEntity(target, result.getAttackerId());
}
}
public void killEntity(GenshinEntity target, int attackerId) {
// Packet
this.broadcastPacket(new PacketLifeStateChangeNotify(attackerId, target, LifeState.LIFE_DEAD));
this.removeEntity(target);
// Death event
target.onDeath(attackerId);
}
// Gadgets
public void onPlayerCreateGadget(EntityClientGadget gadget) {
// Directly add
this.addEntityDirectly(gadget);
// Add to owner's gadget list TODO
// Optimization
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) {
return;
}
this.broadcastPacketToOthers(gadget.getOwner(), new PacketSceneEntityAppearNotify(gadget));
}
public void onPlayerDestroyGadget(int entityId) {
GenshinEntity entity = getEntities().get(entityId);
if (entity == null || !(entity instanceof EntityClientGadget)) {
return;
}
// Get and remove entity
EntityClientGadget gadget = (EntityClientGadget) entity;
this.removeEntityDirectly(gadget);
// Remove from owner's gadget list TODO
// Optimization
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == gadget.getOwner()) {
return;
}
this.broadcastPacketToOthers(gadget.getOwner(), new PacketSceneEntityDisappearNotify(gadget, VisionType.VisionDie));
}
// Broadcasting
public void broadcastPacket(GenshinPacket packet) {
// Send to all players - might have to check if player has been sent data packets
for (GenshinPlayer player : this.getPlayers()) {
player.getSession().send(packet);
}
}
public void broadcastPacketToOthers(GenshinPlayer excludedPlayer, GenshinPacket packet) {
// Optimization
if (this.getPlayerCount() == 1 && this.getPlayers().get(0) == excludedPlayer) {
return;
}
// Send to all players - might have to check if player has been sent data packets
for (GenshinPlayer player : this.getPlayers()) {
if (player == excludedPlayer) {
continue;
}
// Send
player.getSession().send(packet);
}
}
public void close() {
}
@Override
public Iterator<GenshinPlayer> iterator() {
return getPlayers().iterator();
}
}

View File

@ -0,0 +1,23 @@
package emu.grasscutter.game.avatar;
public class AvatarProfileData {
private int avatarId;
private int level;
public AvatarProfileData(GenshinAvatar avatar) {
this.update(avatar);
}
public int getAvatarId() {
return avatarId;
}
public int getLevel() {
return level;
}
public void update(GenshinAvatar avatar) {
this.avatarId = avatar.getAvatarId();
this.level = avatar.getLevel();
}
}

View File

@ -0,0 +1,124 @@
package emu.grasscutter.game.avatar;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum AvatarStat {
FIGHT_PROP_NONE(0),
FIGHT_PROP_BASE_HP(1),
FIGHT_PROP_HP(2),
FIGHT_PROP_HP_PERCENT(3),
FIGHT_PROP_BASE_ATTACK(4),
FIGHT_PROP_ATTACK(5),
FIGHT_PROP_ATTACK_PERCENT(6),
FIGHT_PROP_BASE_DEFENSE(7),
FIGHT_PROP_DEFENSE(8),
FIGHT_PROP_DEFENSE_PERCENT(9),
FIGHT_PROP_BASE_SPEED(10),
FIGHT_PROP_SPEED_PERCENT(11),
FIGHT_PROP_HP_MP_PERCENT(12),
FIGHT_PROP_ATTACK_MP_PERCENT(13),
FIGHT_PROP_CRITICAL(20),
FIGHT_PROP_ANTI_CRITICAL(21),
FIGHT_PROP_CRITICAL_HURT(22),
FIGHT_PROP_CHARGE_EFFICIENCY(23),
FIGHT_PROP_ADD_HURT(24),
FIGHT_PROP_SUB_HURT(25),
FIGHT_PROP_HEAL_ADD(26),
FIGHT_PROP_HEALED_ADD(27),
FIGHT_PROP_ELEMENT_MASTERY(28),
FIGHT_PROP_PHYSICAL_SUB_HURT(29),
FIGHT_PROP_PHYSICAL_ADD_HURT(30),
FIGHT_PROP_DEFENCE_IGNORE_RATIO(31),
FIGHT_PROP_DEFENCE_IGNORE_DELTA(32),
FIGHT_PROP_FIRE_ADD_HURT(40),
FIGHT_PROP_ELEC_ADD_HURT(41),
FIGHT_PROP_WATER_ADD_HURT(42),
FIGHT_PROP_GRASS_ADD_HURT(43),
FIGHT_PROP_WIND_ADD_HURT(44),
FIGHT_PROP_ROCK_ADD_HURT(45),
FIGHT_PROP_ICE_ADD_HURT(46),
FIGHT_PROP_HIT_HEAD_ADD_HURT(47),
FIGHT_PROP_FIRE_SUB_HURT(50),
FIGHT_PROP_ELEC_SUB_HURT(51),
FIGHT_PROP_WATER_SUB_HURT(52),
FIGHT_PROP_GRASS_SUB_HURT(53),
FIGHT_PROP_WIND_SUB_HURT(54),
FIGHT_PROP_ROCK_SUB_HURT(55),
FIGHT_PROP_ICE_SUB_HURT(56),
FIGHT_PROP_EFFECT_HIT(60),
FIGHT_PROP_EFFECT_RESIST(61),
FIGHT_PROP_FREEZE_RESIST(62),
FIGHT_PROP_TORPOR_RESIST(63),
FIGHT_PROP_DIZZY_RESIST(64),
FIGHT_PROP_FREEZE_SHORTEN(65),
FIGHT_PROP_TORPOR_SHORTEN(66),
FIGHT_PROP_DIZZY_SHORTEN(67),
FIGHT_PROP_MAX_FIRE_ENERGY(70),
FIGHT_PROP_MAX_ELEC_ENERGY(71),
FIGHT_PROP_MAX_WATER_ENERGY(72),
FIGHT_PROP_MAX_GRASS_ENERGY(73),
FIGHT_PROP_MAX_WIND_ENERGY(74),
FIGHT_PROP_MAX_ICE_ENERGY(75),
FIGHT_PROP_MAX_ROCK_ENERGY(76),
FIGHT_PROP_SKILL_CD_MINUS_RATIO(80),
FIGHT_PROP_SHIELD_COST_MINUS_RATIO(81),
FIGHT_PROP_CUR_FIRE_ENERGY(1000),
FIGHT_PROP_CUR_ELEC_ENERGY(1001),
FIGHT_PROP_CUR_WATER_ENERGY(1002),
FIGHT_PROP_CUR_GRASS_ENERGY(1003),
FIGHT_PROP_CUR_WIND_ENERGY(1004),
FIGHT_PROP_CUR_ICE_ENERGY(1005),
FIGHT_PROP_CUR_ROCK_ENERGY(1006),
FIGHT_PROP_CUR_HP(1010),
FIGHT_PROP_MAX_HP(2000),
FIGHT_PROP_CUR_ATTACK(2001),
FIGHT_PROP_CUR_DEFENSE(2002),
FIGHT_PROP_CUR_SPEED(2003),
FIGHT_PROP_NONEXTRA_ATTACK(3000),
FIGHT_PROP_NONEXTRA_DEFENSE(3001),
FIGHT_PROP_NONEXTRA_CRITICAL(3002),
FIGHT_PROP_NONEXTRA_ANTI_CRITICAL(3003),
FIGHT_PROP_NONEXTRA_CRITICAL_HURT(3004),
FIGHT_PROP_NONEXTRA_CHARGE_EFFICIENCY(3005),
FIGHT_PROP_NONEXTRA_ELEMENT_MASTERY(3006),
FIGHT_PROP_NONEXTRA_PHYSICAL_SUB_HURT(3007),
FIGHT_PROP_NONEXTRA_FIRE_ADD_HURT(3008),
FIGHT_PROP_NONEXTRA_ELEC_ADD_HURT(3009),
FIGHT_PROP_NONEXTRA_WATER_ADD_HURT(3010),
FIGHT_PROP_NONEXTRA_GRASS_ADD_HURT(3011),
FIGHT_PROP_NONEXTRA_WIND_ADD_HURT(3012),
FIGHT_PROP_NONEXTRA_ROCK_ADD_HURT(3013),
FIGHT_PROP_NONEXTRA_ICE_ADD_HURT(3014),
FIGHT_PROP_NONEXTRA_FIRE_SUB_HURT(3015),
FIGHT_PROP_NONEXTRA_ELEC_SUB_HURT(3016),
FIGHT_PROP_NONEXTRA_WATER_SUB_HURT(3017),
FIGHT_PROP_NONEXTRA_GRASS_SUB_HURT(3018),
FIGHT_PROP_NONEXTRA_WIND_SUB_HURT(3019),
FIGHT_PROP_NONEXTRA_ROCK_SUB_HURT(3020),
FIGHT_PROP_NONEXTRA_ICE_SUB_HURT(3021),
FIGHT_PROP_NONEXTRA_SKILL_CD_MINUS_RATIO(3022),
FIGHT_PROP_NONEXTRA_SHIELD_COST_MINUS_RATIO(3023),
FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT(3024);
private final int id;
private static final Int2ObjectMap<AvatarStat> map = new Int2ObjectOpenHashMap<>();
static {
Stream.of(values()).forEach(e -> map.put(e.getId(), e));
}
private AvatarStat(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static AvatarStat getStatById(int value) {
return map.getOrDefault(value, FIGHT_PROP_NONE);
}
}

View File

@ -0,0 +1,174 @@
package emu.grasscutter.game.avatar;
import java.util.Iterator;
import java.util.List;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.AvatarData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFlycloakChangeNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
public class AvatarStorage implements Iterable<GenshinAvatar> {
private final GenshinPlayer player;
private final Int2ObjectMap<GenshinAvatar> avatars;
private final Long2ObjectMap<GenshinAvatar> avatarsGuid;
public AvatarStorage(GenshinPlayer player) {
this.player = player;
this.avatars = new Int2ObjectOpenHashMap<>();
this.avatarsGuid = new Long2ObjectOpenHashMap<>();
}
public GenshinPlayer getPlayer() {
return player;
}
public Int2ObjectMap<GenshinAvatar> getAvatars() {
return avatars;
}
public int getAvatarCount() {
return this.avatars.size();
}
public GenshinAvatar getAvatarById(int id) {
return getAvatars().get(id);
}
public GenshinAvatar getAvatarByGuid(long id) {
return avatarsGuid.get(id);
}
public boolean hasAvatar(int id) {
return getAvatars().containsKey(id);
}
public boolean addAvatar(GenshinAvatar avatar) {
if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
return false;
}
// Set owner first
avatar.setOwner(getPlayer());
// Put into maps
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
avatar.save();
return true;
}
public void addStartingWeapon(GenshinAvatar avatar) {
// Make sure avatar owner is this player
if (avatar.getPlayer() != this.getPlayer()) {
return;
}
// Create weapon
GenshinItem weapon = new GenshinItem(avatar.getAvatarData().getInitialWeapon());
if (weapon.getItemData() != null) {
this.getPlayer().getInventory().addItem(weapon);
avatar.equipItem(weapon, true);
}
}
public boolean wearFlycloak(long avatarGuid, int flycloakId) {
GenshinAvatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null || !getPlayer().getFlyCloakList().contains(flycloakId)) {
return false;
}
avatar.setFlyCloak(flycloakId);
avatar.save();
// Update
getPlayer().sendPacket(new PacketAvatarFlycloakChangeNotify(avatar));
return true;
}
public boolean changeCostume(long avatarGuid, int costumeId) {
GenshinAvatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null) {
return false;
}
if (costumeId != 0 && !getPlayer().getCostumeList().contains(costumeId)) {
return false;
}
// TODO make sure avatar can wear costume
avatar.setCostume(costumeId);
avatar.save();
// Update entity
EntityAvatar entity = avatar.getAsEntity();
if (entity == null) {
entity = new EntityAvatar(avatar);
getPlayer().sendPacket(new PacketAvatarChangeCostumeNotify(entity));
} else {
getPlayer().getWorld().broadcastPacket(new PacketAvatarChangeCostumeNotify(entity));
}
// Done
return true;
}
public void loadFromDatabase() {
List<GenshinAvatar> avatars = DatabaseHelper.getAvatars(getPlayer());
for (GenshinAvatar avatar : avatars) {
// Should never happen
if (avatar.getObjectId() == null) {
continue;
}
AvatarData avatarData = GenshinData.getAvatarDataMap().get(avatar.getAvatarId());
if (avatarData == null) {
continue;
}
// Set ownerships
avatar.setAvatarData(avatarData);
avatar.setOwner(getPlayer());
// Force recalc of const boosted skills
avatar.recalcProudSkillBonusMap();
// Add to avatar storage
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
}
}
public void postLoad() {
for (GenshinAvatar avatar : this) {
// Weapon check
if (avatar.getWeapon() == null) {
this.addStartingWeapon(avatar);
}
// Recalc stats
avatar.recalcStats();
}
}
@Override
public Iterator<GenshinAvatar> iterator() {
return getAvatars().values().iterator();
}
}

View File

@ -0,0 +1,695 @@
package emu.grasscutter.game.avatar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.PostLoad;
import dev.morphia.annotations.PrePersist;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.def.AvatarData;
import emu.grasscutter.data.def.AvatarPromoteData;
import emu.grasscutter.data.def.AvatarSkillData;
import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.data.def.AvatarSkillDepotData.InherentProudSkillOpens;
import emu.grasscutter.data.def.AvatarTalentData;
import emu.grasscutter.data.def.EquipAffixData;
import emu.grasscutter.data.def.ReliquaryAffixData;
import emu.grasscutter.data.def.ReliquaryLevelData;
import emu.grasscutter.data.def.ReliquaryMainPropData;
import emu.grasscutter.data.def.ReliquarySetData;
import emu.grasscutter.data.def.WeaponCurveData;
import emu.grasscutter.data.def.WeaponPromoteData;
import emu.grasscutter.data.def.ItemData.WeaponProperty;
import emu.grasscutter.data.def.ProudSkillData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.entity.EntityAvatar;
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.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AvatarFetterInfoOuterClass.AvatarFetterInfo;
import emu.grasscutter.net.proto.AvatarInfoOuterClass.AvatarInfo;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropNotify;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@Entity(value = "avatars", noClassnameStored = true)
public class GenshinAvatar {
@Id private ObjectId id;
@Indexed private int ownerId; // Id of player that this avatar belongs to
@Transient private GenshinPlayer owner;
@Transient private AvatarData data;
@Transient private long guid; // Player unique id
private int avatarId; // Id of avatar
private int level = 1;
private int exp;
private int promoteLevel;
private int satiation; // ?
private int satiationPenalty; // ?
private float currentHp;
@Transient private final Int2ObjectMap<GenshinItem> equips;
@Transient private final Int2FloatOpenHashMap fightProp;
@Transient private final Set<String> bonusAbilityList;
private Map<Integer, Integer> skillLevelMap; // Talent levels
private Map<Integer, Integer> proudSkillBonusMap; // Talent bonus levels (from const)
private int skillDepotId;
private int coreProudSkillLevel; // Constellation level
private Set<Integer> talentIdList; // Constellation id list
private Set<Integer> proudSkillList; // Character passives
private int flyCloak;
private int costume;
private int bornTime;
public GenshinAvatar() {
// Morhpia only!
this.equips = new Int2ObjectOpenHashMap<>();
this.fightProp = new Int2FloatOpenHashMap();
this.bonusAbilityList = new HashSet<>();
this.proudSkillBonusMap = new HashMap<>(); // TODO Move to genshin avatar
}
// On creation
public GenshinAvatar(int avatarId) {
this(GenshinData.getAvatarDataMap().get(avatarId));
}
public GenshinAvatar(AvatarData data) {
this();
this.avatarId = data.getId();
this.data = data;
this.bornTime = (int) (System.currentTimeMillis() / 1000);
this.flyCloak = 140001;
this.skillLevelMap = new HashMap<>();
this.talentIdList = new HashSet<>();
this.proudSkillList = new HashSet<>();
// Combat properties
for (FightProperty prop : FightProperty.values()) {
if (prop.getId() <= 0 || prop.getId() >= 3000) {
continue;
}
this.setFightProperty(prop, 0f);
}
// Skill depot
this.setSkillDepot(getAvatarData().getSkillDepot());
// Set stats
this.recalcStats();
this.currentHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.currentHp);
// Load handler
this.onLoad();
}
public GenshinPlayer getPlayer() {
return this.owner;
}
public ObjectId getObjectId() {
return id;
}
public AvatarData getAvatarData() {
return data;
}
protected void setAvatarData(AvatarData data) {
this.data = data;
}
public int getOwnerId() {
return ownerId;
}
public void setOwner(GenshinPlayer player) {
this.owner = player;
this.ownerId = player.getId();
this.guid = player.getNextGuid();
}
public int getSatiation() {
return satiation;
}
public void setSatiation(int satiation) {
this.satiation = satiation;
}
public int getSatiationPenalty() {
return satiationPenalty;
}
public void setSatiationPenalty(int satiationPenalty) {
this.satiationPenalty = satiationPenalty;
}
public AvatarData getData() {
return data;
}
public long getGuid() {
return guid;
}
public int getAvatarId() {
return avatarId;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getExp() {
return exp;
}
public void setExp(int exp) {
this.exp = exp;
}
public int getPromoteLevel() {
return promoteLevel;
}
public void setPromoteLevel(int promoteLevel) {
this.promoteLevel = promoteLevel;
}
public Int2ObjectMap<GenshinItem> getEquips() {
return equips;
}
public GenshinItem getEquipBySlot(EquipType slot) {
return this.getEquips().get(slot.getValue());
}
private GenshinItem getEquipBySlot(int slotId) {
return this.getEquips().get(slotId);
}
public GenshinItem getWeapon() {
return this.getEquipBySlot(EquipType.EQUIP_WEAPON);
}
public int getSkillDepotId() {
return skillDepotId;
}
public void setSkillDepot(AvatarSkillDepotData skillDepot) {
// Set id
this.skillDepotId = skillDepot.getId();
// Clear, then add skills
getSkillLevelMap().clear();
if (skillDepot.getEnergySkill() > 0) {
getSkillLevelMap().put(skillDepot.getEnergySkill(), 1);
}
for (int skillId : skillDepot.getSkills()) {
if (skillId > 0) {
getSkillLevelMap().put(skillId, 1);
}
}
// Add proud skills
this.getProudSkillList().clear();
for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) {
if (openData.getProudSkillGroupId() == 0) {
continue;
}
if (openData.getNeedAvatarPromoteLevel() <= this.getPromoteLevel()) {
int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1;
if (GenshinData.getProudSkillDataMap().containsKey(proudSkillId)) {
this.getProudSkillList().add(proudSkillId);
}
}
}
}
public Map<Integer, Integer> getSkillLevelMap() {
return skillLevelMap;
}
public Map<Integer, Integer> getProudSkillBonusMap() {
return proudSkillBonusMap;
}
public Set<String> getBonusAbilityList() {
return bonusAbilityList;
}
public float getCurrentHp() {
return currentHp;
}
public void setCurrentHp(float currentHp) {
this.currentHp = currentHp;
}
public Int2FloatOpenHashMap getFightProperties() {
return fightProp;
}
public void setFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), value);
}
private void setFightProperty(int id, float value) {
this.getFightProperties().put(id, value);
}
public void addFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), getFightProperty(prop) + value);
}
public float getFightProperty(FightProperty prop) {
return getFightProperties().getOrDefault(prop.getId(), 0f);
}
public Set<Integer> getTalentIdList() {
return talentIdList;
}
public int getCoreProudSkillLevel() {
return coreProudSkillLevel;
}
public void setCoreProudSkillLevel(int constLevel) {
this.coreProudSkillLevel = constLevel;
}
public Set<Integer> getProudSkillList() {
return proudSkillList;
}
public int getFlyCloak() {
return flyCloak;
}
public void setFlyCloak(int flyCloak) {
this.flyCloak = flyCloak;
}
public int getCostume() {
return costume;
}
public void setCostume(int costume) {
this.costume = costume;
}
public int getBornTime() {
return bornTime;
}
public boolean equipItem(GenshinItem item, boolean shouldRecalc) {
EquipType itemEquipType = item.getItemData().getEquipType();
if (itemEquipType == EquipType.EQUIP_NONE) {
return false;
}
if (getEquips().containsKey(itemEquipType.getValue())) {
unequipItem(itemEquipType);
}
getEquips().put(itemEquipType.getValue(), item);
if (itemEquipType == EquipType.EQUIP_WEAPON && getPlayer().getWorld() != null) {
item.setWeaponEntityId(this.getPlayer().getWorld().getNextEntityId(EntityIdType.WEAPON));
}
item.setEquipCharacter(this.getAvatarId());
item.save();
if (shouldRecalc) {
this.recalcStats();
}
if (this.getPlayer().hasSentAvatarDataNotify()) {
this.getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(this, item));
}
return true;
}
public boolean unequipItem(EquipType slot) {
GenshinItem item = getEquips().remove(slot.getValue());
if (item != null) {
item.setEquipCharacter(0);
item.save();
return true;
}
return false;
}
public void recalcStats() {
// Setup
AvatarData data = this.getAvatarData();
AvatarPromoteData promoteData = GenshinData.getAvatarPromoteData(data.getAvatarPromoteId(), this.getPromoteLevel());
Int2IntOpenHashMap setMap = new Int2IntOpenHashMap();
this.getBonusAbilityList().clear();
// Get hp percent, set to 100% if none
float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
// Clear properties
this.getFightProperties().clear();
// Base stats
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, data.getBaseHp(this.getLevel()));
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, data.getBaseAttack(this.getLevel()));
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, data.getBaseDefense(this.getLevel()));
this.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL, data.getBaseCritical());
this.setFightProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, data.getBaseCriticalHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 1f);
if (promoteData != null) {
for (FightPropData fightPropData : promoteData.getAddProps()) {
this.addFightProperty(fightPropData.getProp(), fightPropData.getValue());
}
}
// Set energy usage
if (data.getSkillDepot() != null && data.getSkillDepot().getEnergySkillData() != null) {
ElementType element = data.getSkillDepot().getElementType();
this.setFightProperty(element.getEnergyProperty(), data.getSkillDepot().getEnergySkillData().getCostElemVal());
this.setFightProperty((element.getEnergyProperty().getId() % 70) + 1000, data.getSkillDepot().getEnergySkillData().getCostElemVal());
}
// Artifacts
for (int slotId = 1; slotId <= 5; slotId++) {
// Get artifact
GenshinItem equip = this.getEquipBySlot(slotId);
if (equip == null) {
continue;
}
// Artifact main stat
ReliquaryMainPropData mainPropData = GenshinData.getReliquaryMainPropDataMap().get(equip.getMainPropId());
if (mainPropData != null) {
ReliquaryLevelData levelData = GenshinData.getRelicLevelData(equip.getItemData().getRankLevel(), equip.getLevel());
if (levelData != null) {
this.addFightProperty(mainPropData.getFightProp(), levelData.getPropValue(mainPropData.getFightProp()));
}
}
// Artifact sub stats
for (int appendPropId : equip.getAppendPropIdList()) {
ReliquaryAffixData affixData = GenshinData.getReliquaryAffixDataMap().get(appendPropId);
if (affixData != null) {
this.addFightProperty(affixData.getFightProp(), affixData.getPropValue());
}
}
// Set bonus
if (equip.getItemData().getSetId() > 0) {
setMap.addTo(equip.getItemData().getSetId(), 1);
}
}
// Set stuff
for (Int2IntOpenHashMap.Entry e : setMap.int2IntEntrySet()) {
ReliquarySetData setData = GenshinData.getReliquarySetDataMap().get(e.getIntKey());
if (setData == null) {
continue;
}
// Calculate how many items are from the set
int amount = e.getIntValue();
// Add affix data from set bonus
for (int setIndex = 0; setIndex < setData.getSetNeedNum().length; setIndex++) {
if (amount >= setData.getSetNeedNum()[setIndex]) {
int affixId = (setData.getEquipAffixId() * 10) + setIndex;
EquipAffixData affix = GenshinData.getEquipAffixDataMap().get(affixId);
if (affix == null) {
continue;
}
// Add properties from this affix to our avatar
for (FightPropData prop : affix.getAddProps()) {
this.addFightProperty(prop.getProp(), prop.getValue());
}
// Add any skill strings from this affix
this.addToAbilityList(affix.getOpenConfig(), true);
} else {
break;
}
}
}
// Weapon
GenshinItem weapon = this.getWeapon();
if (weapon != null) {
// Add stats
WeaponCurveData curveData = GenshinData.getWeaponCurveDataMap().get(weapon.getLevel());
if (curveData != null) {
for (WeaponProperty weaponProperty : weapon.getItemData().getWeaponProperties()) {
this.addFightProperty(weaponProperty.getFightProp(), weaponProperty.getInitValue() * curveData.getMultByProp(weaponProperty.getType()));
}
}
// Weapon promotion stats
WeaponPromoteData wepPromoteData = GenshinData.getWeaponPromoteData(weapon.getItemData().getWeaponPromoteId(), weapon.getPromoteLevel());
if (wepPromoteData != null) {
for (FightPropData prop : wepPromoteData.getAddProps()) {
if (prop.getValue() == 0f || prop.getProp() == null) {
continue;
}
this.addFightProperty(prop.getProp(), prop.getValue());
}
}
// Add weapon skill from affixes
if (weapon.getAffixes() != null && weapon.getAffixes().size() > 0) {
// Weapons usually dont have more than one affix but just in case...
for (int af : weapon.getAffixes()) {
if (af == 0) {
continue;
}
// Calculate affix id
int affixId = (af * 10) + weapon.getRefinement();
EquipAffixData affix = GenshinData.getEquipAffixDataMap().get(affixId);
if (affix == null) {
continue;
}
// Add properties from this affix to our avatar
for (FightPropData prop : affix.getAddProps()) {
this.addFightProperty(prop.getProp(), prop.getValue());
}
// Add any skill strings from this affix
this.addToAbilityList(affix.getOpenConfig(), true);
}
}
}
// Proud skills
for (int proudSkillId : this.getProudSkillList()) {
ProudSkillData proudSkillData = GenshinData.getProudSkillDataMap().get(proudSkillId);
if (proudSkillData == null) {
continue;
}
// Add properties from this proud skill to our avatar
for (FightPropData prop : proudSkillData.getAddProps()) {
this.addFightProperty(prop.getProp(), prop.getValue());
}
// Add any skill strings from this proud skill
this.addToAbilityList(proudSkillData.getOpenConfig(), true);
}
// Constellations
if (this.getTalentIdList().size() > 0) {
for (int talentId : this.getTalentIdList()) {
AvatarTalentData avatarTalentData = GenshinData.getAvatarTalentDataMap().get(talentId);
if (avatarTalentData == null) {
return;
}
// Add any skill strings from this constellation
this.addToAbilityList(avatarTalentData.getOpenConfig(), false);
}
}
// Set % stats
this.setFightProperty(
FightProperty.FIGHT_PROP_MAX_HP,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_HP) * (1f + getFightProperty(FightProperty.FIGHT_PROP_HP_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_HP)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_ATTACK,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK) * (1f + getFightProperty(FightProperty.FIGHT_PROP_ATTACK_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_ATTACK)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_DEFENSE,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE) * (1f + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE)
);
// Set current hp
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
// Packet
if (getPlayer() != null && getPlayer().hasSentAvatarDataNotify()) {
getPlayer().sendPacket(new PacketAvatarFightPropNotify(this));
}
}
public void addToAbilityList(String openConfig, boolean forceAdd) {
if (openConfig == null || openConfig.length() == 0) {
return;
}
OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(openConfig);
if (entry == null) {
if (forceAdd) {
// Add config string to ability skill list anyways
this.getBonusAbilityList().add(openConfig);
}
return;
}
if (entry.getAddAbilities() != null) {
for (String ability : entry.getAddAbilities()) {
this.getBonusAbilityList().add(ability);
}
}
}
public void recalcProudSkillBonusMap() {
// Clear first
this.getProudSkillBonusMap().clear();
// Sanity checks
if (getData() == null || getData().getSkillDepot() == null) {
return;
}
if (this.getTalentIdList().size() > 0) {
for (int talentId : this.getTalentIdList()) {
AvatarTalentData avatarTalentData = GenshinData.getAvatarTalentDataMap().get(talentId);
if (avatarTalentData == null || avatarTalentData.getOpenConfig() == null || avatarTalentData.getOpenConfig().length() == 0) {
continue;
}
// Get open config to find which skill should be boosted
OpenConfigEntry entry = GenshinData.getOpenConfigEntries().get(avatarTalentData.getOpenConfig());
if (entry == null) {
continue;
}
int skillId = 0;
if (entry.getExtraTalentIndex() == 2 && this.getData().getSkillDepot().getSkills().size() >= 2) {
// E skill
skillId = this.getData().getSkillDepot().getSkills().get(1);
} else if (entry.getExtraTalentIndex() == 9) {
// Ult skill
skillId = this.getData().getSkillDepot().getEnergySkill();
}
// Sanity check
if (skillId == 0) {
continue;
}
// Get proud skill group id
AvatarSkillData skillData = GenshinData.getAvatarSkillDataMap().get(skillId);
if (skillData == null) {
continue;
}
// Add to bonus list
this.getProudSkillBonusMap().put(skillData.getProudSkillGroupId(), 3);
}
}
}
public EntityAvatar getAsEntity() {
for (EntityAvatar entity : getPlayer().getTeamManager().getActiveTeam()) {
if (entity.getAvatar() == this) {
return entity;
}
}
return null;
}
public int getEntityId() {
EntityAvatar entity = getAsEntity();
return entity != null ? entity.getId() : 0;
}
public void save() {
DatabaseHelper.saveAvatar(this);
}
public AvatarInfo toProto() {
AvatarInfo.Builder avatarInfo = AvatarInfo.newBuilder()
.setAvatarId(this.getAvatarId())
.setGuid(this.getGuid())
.setLifeState(1)
.addAllTalentIdList(this.getTalentIdList())
.putAllFightPropMap(this.getFightProperties())
.setSkillDepotId(this.getSkillDepotId())
.setCoreProudSkillLevel(this.getCoreProudSkillLevel())
.putAllSkillLevelMap(this.getSkillLevelMap())
.addAllInherentProudSkillList(this.getProudSkillList())
.putAllProudSkillExtraLevel(getProudSkillBonusMap())
.setAvatarType(1)
.setBornTime(this.getBornTime())
.setFetterInfo(AvatarFetterInfo.newBuilder().setExpLevel(1))
.setWearingFlycloakId(this.getFlyCloak())
.setCostumeId(this.getCostume());
for (GenshinItem item : this.getEquips().values()) {
avatarInfo.addEquipGuidList(item.getGuid());
}
avatarInfo.putPropMap(PlayerProperty.PROP_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, this.getLevel()));
avatarInfo.putPropMap(PlayerProperty.PROP_EXP.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp()));
avatarInfo.putPropMap(PlayerProperty.PROP_BREAK_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel()));
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, 0));
avatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_PENALTY_TIME, 0));
return avatarInfo.build();
}
@PostLoad
private void onLoad() {
}
@PrePersist
private void prePersist() {
this.currentHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
}
}

View File

@ -0,0 +1,15 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.server.game.GameServer;
public class DungeonManager {
private final GameServer server;
public DungeonManager(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
}

View File

@ -0,0 +1,239 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.GenshinConstants;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.AvatarData;
import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.World;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlock;
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityAvatar extends GenshinEntity {
private final GenshinAvatar avatar;
private PlayerDieType killedType;
private int killedBy;
public EntityAvatar(World world, GenshinAvatar avatar) {
super(world);
this.avatar = avatar;
this.id = world.getNextEntityId(EntityIdType.AVATAR);
GenshinItem weapon = this.getAvatar().getWeapon();
if (weapon != null) {
weapon.setWeaponEntityId(world.getNextEntityId(EntityIdType.WEAPON));
}
}
public EntityAvatar(GenshinAvatar avatar) {
super(null);
this.avatar = avatar;
}
public GenshinPlayer getPlayer() {
return avatar.getPlayer();
}
@Override
public Position getPosition() {
return getPlayer().getPos();
}
@Override
public Position getRotation() {
return getPlayer().getRotation();
}
public GenshinAvatar getAvatar() {
return avatar;
}
public int getKilledBy() {
return killedBy;
}
public PlayerDieType getKilledType() {
return killedType;
}
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
return getAvatar().getFightProperties();
}
public int getWeaponEntityId() {
if (getAvatar().getWeapon() != null) {
return getAvatar().getWeapon().getWeaponEntityId();
}
return 0;
}
@Override
public void onDeath(int killerId) {
this.killedType = PlayerDieType.PlayerDieKillByMonster;
this.killedBy = killerId;
}
public SceneAvatarInfo getSceneAvatarInfo() {
SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder()
.setPlayerId(this.getPlayer().getId())
.setAvatarId(this.getAvatar().getAvatarId())
.setGuid(this.getAvatar().getGuid())
.setPeerId(this.getPlayer().getPeerId())
.addAllTalentIdList(this.getAvatar().getTalentIdList())
.setCoreProudSkillLevel(this.getAvatar().getCoreProudSkillLevel())
.putAllSkillLevelMap(this.getAvatar().getSkillLevelMap())
.setSkillDepotId(this.getAvatar().getSkillDepotId())
.addAllInherentProudSkillList(this.getAvatar().getProudSkillList())
.putAllProudSkillExtraLevelMap(this.getAvatar().getProudSkillBonusMap())
.addAllTeamResonanceList(this.getAvatar().getPlayer().getTeamManager().getTeamResonances())
.setWearingFlycloakId(this.getAvatar().getFlyCloak())
.setCostumeId(this.getAvatar().getCostume())
.setBornTime(this.getAvatar().getBornTime());
for (GenshinItem item : avatar.getEquips().values()) {
if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) {
avatarInfo.setWeapon(item.createSceneWeaponInfo());
} else {
avatarInfo.addReliquaryList(item.createSceneReliquaryInfo());
}
avatarInfo.addEquipIdList(item.getItemId());
}
return avatarInfo.build();
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.ProtEntityAvatar)
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs())
.setLastMoveReliableSeq(this.getLastMoveReliableSeq())
.setLifeState(this.getLifeState().getValue());
if (this.getWorld() != null) {
entityInfo.setMotionInfo(this.getMotionInfo());
}
for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) {
if (entry.getIntKey() == 0) {
continue;
}
FightPropPair fightProp = FightPropPair.newBuilder().setType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build();
entityInfo.addFightPropList(fightProp);
}
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getAvatar().getLevel()))
.build();
entityInfo.addPropList(pair);
entityInfo.setAvatar(this.getSceneAvatarInfo());
return entityInfo.build();
}
public AbilityControlBlock getAbilityControlBlock() {
AvatarData data = this.getAvatar().getAvatarData();
AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder();
int embryoId = 0;
// Add avatar abilities
if (data.getAbilities() != null) {
for (int id : data.getAbilities()) {
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
// Add default abilities
for (int id : GenshinConstants.DEFAULT_ABILITY_HASHES) {
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
// Add team resonances
for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) {
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
// Add skill depot abilities
AvatarSkillDepotData skillDepot = GenshinData.getAvatarSkillDepotDataMap().get(this.getAvatar().getSkillDepotId());
if (skillDepot != null && skillDepot.getAbilities() != null) {
for (int id : skillDepot.getAbilities()) {
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
// Add equip abilities
if (this.getAvatar().getBonusAbilityList().size() > 0) {
for (String skill : this.getAvatar().getBonusAbilityList()) {
AbilityEmbryo emb = AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(Utils.abilityHash(skill))
.setAbilityOverrideNameHash(GenshinConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
//
return abilityControlBlock.build();
}
}

View File

@ -0,0 +1,147 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.World;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify;
import emu.grasscutter.net.proto.GadgetClientParamOuterClass.GadgetClientParam;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityClientGadget extends EntityGadget {
private final GenshinPlayer owner;
private final Position pos;
private final Position rot;
private int configId;
private int campId;
private int campType;
private int ownerEntityId;
private int targetEntityId;
private boolean asyncLoad;
public EntityClientGadget(World world, GenshinPlayer player, EvtCreateGadgetNotify notify) {
super(world);
this.owner = player;
this.id = notify.getEntityId();
this.pos = new Position(notify.getInitPos());
this.rot = new Position(notify.getInitEulerAngles());
this.configId = notify.getConfigId();
this.campId = notify.getCampId();
this.campType = notify.getCampType();
this.ownerEntityId = notify.getPropOwnerEntityId();
this.targetEntityId = notify.getTargetEntityId();
this.asyncLoad = notify.getIsAsyncLoad();
}
@Override
public int getGadgetId() {
return configId;
}
public GenshinPlayer getOwner() {
return owner;
}
public int getCampId() {
return campId;
}
public int getCampType() {
return campType;
}
public int getOwnerEntityId() {
return ownerEntityId;
}
public int getTargetEntityId() {
return targetEntityId;
}
public boolean isAsyncLoad() {
return this.asyncLoad;
}
@Override
public void onDeath(int killerId) {
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
// TODO Auto-generated method stub
return null;
}
@Override
public Position getPosition() {
// TODO Auto-generated method stub
return this.pos;
}
@Override
public Position getRotation() {
// TODO Auto-generated method stub
return this.rot;
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.ProtEntityGadget)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
GadgetClientParam clientGadget = GadgetClientParam.newBuilder()
.setCampId(this.getCampId())
.setCampType(this.getCampType())
.setOwnerEntityId(this.getOwnerEntityId())
.setTargetEntityId(this.getTargetEntityId())
.setAsyncLoad(this.isAsyncLoad())
.build();
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setOwnerEntityId(this.getOwnerEntityId())
.setIsEnableInteract(true)
.setClientGadget(clientGadget)
.setPropOwnerEntityId(this.getOwnerEntityId())
.setAuthorityPeerId(this.getOwner().getPeerId());
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}

View File

@ -0,0 +1,17 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.World;
public abstract class EntityGadget extends GenshinEntity {
public EntityGadget(World world) {
super(world);
}
public abstract int getGadgetId();
@Override
public void onDeath(int killerId) {
}
}

View File

@ -0,0 +1,118 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.World;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetBornTypeOuterClass.GadgetBornType;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityItem extends EntityGadget {
private final Position pos;
private final Position rot;
private final GenshinItem item;
private final long guid;
public EntityItem(World world, GenshinPlayer player, ItemData itemData, Position pos, int count) {
super(world);
this.id = world.getNextEntityId(EntityIdType.GADGET);
this.pos = new Position(pos);
this.rot = new Position();
this.guid = player.getNextGuid();
this.item = new GenshinItem(itemData, count);
}
@Override
public int getId() {
return this.id;
}
private GenshinItem getItem() {
return this.item;
}
public ItemData getItemData() {
return this.getItem().getItemData();
}
public long getGuid() {
return guid;
}
public int getCount() {
return this.getItem().getCount();
}
@Override
public int getGadgetId() {
return this.getItemData().getGadgetId();
}
@Override
public Position getPosition() {
return this.pos;
}
@Override
public Position getRotation() {
return this.rot;
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
return null;
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.ProtEntityGadget)
.setMotionInfo(MotionInfo.newBuilder().setPos(getPosition().toProto()).setRot(getRotation().toProto()).setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
SceneGadgetInfo.Builder gadgetInfo = SceneGadgetInfo.newBuilder()
.setGadgetId(this.getItemData().getGadgetId())
.setTrifleItem(this.getItem().toProto())
.setBornType(GadgetBornType.GadgetBornInAir)
.setAuthorityPeerId(this.getWorld().getHostPeerId())
.setIsEnableInteract(true);
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}

View File

@ -0,0 +1,219 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.def.MonsterCurveData;
import emu.grasscutter.data.def.MonsterData;
import emu.grasscutter.game.World;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.MonsterBornTypeOuterClass.MonsterBornType;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public class EntityMonster extends GenshinEntity {
private final MonsterData monsterData;
private final Int2FloatOpenHashMap fightProp;
private final Position pos;
private final Position rot;
private final Position bornPos;
private final int level;
private int weaponEntityId;
public EntityMonster(World world, MonsterData monsterData, Position pos, int level) {
super(world);
this.id = world.getNextEntityId(EntityIdType.MONSTER);
this.monsterData = monsterData;
this.fightProp = new Int2FloatOpenHashMap();
this.pos = new Position(pos);
this.rot = new Position();
this.bornPos = getPosition().clone();
this.level = level;
// Monster weapon
if (getMonsterWeaponId() > 0) {
this.weaponEntityId = world.getNextEntityId(EntityIdType.WEAPON);
}
this.recalcStats();
}
@Override
public int getId() {
return this.id;
}
public MonsterData getMonsterData() {
return monsterData;
}
public int getMonsterWeaponId() {
return getMonsterData().getWeaponId();
}
private int getMonsterId() {
return this.getMonsterData().getId();
}
public int getLevel() {
return level;
}
@Override
public Position getPosition() {
return this.pos;
}
@Override
public Position getRotation() {
return this.rot;
}
public Position getBornPos() {
return bornPos;
}
@Override
public Int2FloatOpenHashMap getFightProperties() {
return fightProp;
}
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
@Override
public void onDeath(int killerId) {
}
public void recalcStats() {
// Monster data
MonsterData data = this.getMonsterData();
// Get hp percent, set to 100% if none
float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
// Clear properties
this.getFightProperties().clear();
// Base stats
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, data.getBaseHp());
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, data.getBaseAttack());
this.setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, data.getBaseDefense());
this.setFightProperty(FightProperty.FIGHT_PROP_PHYSICAL_SUB_HURT, data.getPhysicalSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_FIRE_SUB_HURT, .1f);
this.setFightProperty(FightProperty.FIGHT_PROP_ELEC_SUB_HURT, data.getElecSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_WATER_SUB_HURT, data.getWaterSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_GRASS_SUB_HURT, data.getGrassSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_WIND_SUB_HURT, data.getWindSubHurt());
this.setFightProperty(FightProperty.FIGHT_PROP_ROCK_SUB_HURT, .1f);
this.setFightProperty(FightProperty.FIGHT_PROP_ICE_SUB_HURT, data.getIceSubHurt());
// Level curve
MonsterCurveData curve = GenshinData.getMonsterCurveDataMap().get(this.getLevel());
if (curve != null) {
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
this.setFightProperty(prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
}
}
// Set % stats
this.setFightProperty(
FightProperty.FIGHT_PROP_MAX_HP,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_HP) * (1f + getFightProperty(FightProperty.FIGHT_PROP_HP_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_HP)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_ATTACK,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK) * (1f + getFightProperty(FightProperty.FIGHT_PROP_ATTACK_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_ATTACK)
);
this.setFightProperty(
FightProperty.FIGHT_PROP_CUR_DEFENSE,
(getFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE) * (1f + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE)
);
// Set current hp
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority = EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(this.getBornPos().toProto()))
.setBornPos(this.getBornPos().toProto())
.build();
SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.ProtEntityMonster)
.setMotionInfo(this.getMotionInfo())
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(this.getLifeState().getValue());
for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) {
if (entry.getIntKey() == 0) {
continue;
}
FightPropPair fightProp = FightPropPair.newBuilder().setType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build();
entityInfo.addFightPropList(fightProp);
}
PropPair pair = PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel()))
.build();
entityInfo.addPropList(pair);
SceneMonsterInfo.Builder monsterInfo = SceneMonsterInfo.newBuilder()
.setMonsterId(getMonsterId())
.setGroupId(133003095)
.setConfigId(95001)
.addAllAffixList(getMonsterData().getAffix())
.setAuthorityPeerId(getWorld().getHostPeerId())
.setPoseId(0)
.setBlockId(3001)
.setBornType(MonsterBornType.MonsterBornDefault)
.setSpecialNameId(40);
if (getMonsterData().getDescribeData() != null) {
monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleID());
}
if (this.getMonsterWeaponId() > 0) {
SceneWeaponInfo weaponInfo = SceneWeaponInfo.newBuilder()
.setEntityId(this.weaponEntityId)
.setGadgetId(this.getMonsterWeaponId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.build();
monsterInfo.setWeaponList(weaponInfo);
}
entityInfo.setMonster(monsterInfo);
return entityInfo.build();
}
}

View File

@ -0,0 +1,102 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.World;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
public abstract class GenshinEntity {
protected int id;
private final World world;
private MotionState moveState;
private int lastMoveSceneTimeMs;
private int lastMoveReliableSeq;
public GenshinEntity(World world) {
this.world = world;
this.moveState = MotionState.MotionNone;
}
public int getId() {
return this.id;
}
public World getWorld() {
return world;
}
public boolean isAlive() {
return true;
}
public LifeState getLifeState() {
return isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD;
}
public abstract Int2FloatOpenHashMap getFightProperties();
public abstract Position getPosition();
public abstract Position getRotation();
public MotionState getMotionState() {
return moveState;
}
public void setMotionState(MotionState moveState) {
this.moveState = moveState;
}
public int getLastMoveSceneTimeMs() {
return lastMoveSceneTimeMs;
}
public void setLastMoveSceneTimeMs(int lastMoveSceneTimeMs) {
this.lastMoveSceneTimeMs = lastMoveSceneTimeMs;
}
public int getLastMoveReliableSeq() {
return lastMoveReliableSeq;
}
public void setLastMoveReliableSeq(int lastMoveReliableSeq) {
this.lastMoveReliableSeq = lastMoveReliableSeq;
}
public abstract SceneEntityInfo toProto();
public abstract void onDeath(int killerId);
public void setFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), value);
}
private void setFightProperty(int id, float value) {
this.getFightProperties().put(id, value);
}
public void addFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), getFightProperty(prop) + value);
}
public float getFightProperty(FightProperty prop) {
return getFightProperties().getOrDefault(prop.getId(), 0f);
}
protected MotionInfo getMotionInfo() {
MotionInfo proto = MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder())
.setState(this.getMotionState())
.build();
return proto;
}
}

View File

@ -0,0 +1,262 @@
package emu.grasscutter.game.friends;
import java.util.List;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType;
import emu.grasscutter.server.packet.send.PacketAskAddFriendNotify;
import emu.grasscutter.server.packet.send.PacketAskAddFriendRsp;
import emu.grasscutter.server.packet.send.PacketDealAddFriendRsp;
import emu.grasscutter.server.packet.send.PacketDeleteFriendNotify;
import emu.grasscutter.server.packet.send.PacketDeleteFriendRsp;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class FriendsList {
private final GenshinPlayer player;
private final Int2ObjectMap<Friendship> friends;
private final Int2ObjectMap<Friendship> pendingFriends;
private boolean loaded = false;
public FriendsList(GenshinPlayer player) {
this.player = player;
this.friends = new Int2ObjectOpenHashMap<Friendship>();
this.pendingFriends = new Int2ObjectOpenHashMap<Friendship>();
}
public GenshinPlayer getPlayer() {
return player;
}
public boolean hasLoaded() {
return loaded;
}
public synchronized Int2ObjectMap<Friendship> getFriends() {
return friends;
}
public synchronized Int2ObjectMap<Friendship> getPendingFriends() {
return this.pendingFriends;
}
public synchronized boolean isFriendsWith(int uid) {
return this.getFriends().containsKey(uid);
}
private synchronized Friendship getFriendshipById(int id) {
Friendship friendship = this.getFriends().get(id);
if (friendship == null) {
friendship = this.getPendingFriendById(id);
}
return friendship;
}
private synchronized Friendship getFriendById(int id) {
return this.getFriends().get(id);
}
private synchronized Friendship getPendingFriendById(int id) {
return this.getPendingFriends().get(id);
}
public void addFriend(Friendship friendship) {
getFriends().put(friendship.getFriendId(), friendship);
}
public void addPendingFriend(Friendship friendship) {
getPendingFriends().put(friendship.getFriendId(), friendship);
}
public synchronized void handleFriendRequest(int targetUid, DealAddFriendResultType result) {
// Check if player has sent friend request
Friendship myFriendship = this.getPendingFriendById(targetUid);
if (myFriendship == null) {
return;
}
// Make sure asker cant do anything
if (myFriendship.getAskerId() == this.getPlayer().getId()) {
return;
}
GenshinPlayer target = getPlayer().getSession().getServer().forceGetPlayerById(targetUid);
if (target == null) {
return; // Should never happen
}
// Get target's friendship
Friendship theirFriendship = null;
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getId());
} else {
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
}
if (theirFriendship == null) {
// They dont have us on their friends list anymore, rip
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
return;
}
// Handle
if (result == DealAddFriendResultType.DealAddFriendAccept) { // Request accepted
myFriendship.setIsFriend(true);
theirFriendship.setIsFriend(true);
this.getPendingFriends().remove(myFriendship.getOwnerId());
this.addFriend(myFriendship);
if (target.isOnline()) {
target.getFriendsList().getPendingFriends().remove(this.getPlayer().getId());
target.getFriendsList().addFriend(theirFriendship);
}
myFriendship.save();
theirFriendship.save();
} else { // Request declined
// Delete from my pending friends
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
// Delete from target uid
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getId());
}
theirFriendship.delete();
}
// Packet
this.getPlayer().sendPacket(new PacketDealAddFriendRsp(targetUid, result));
}
public synchronized void deleteFriend(int targetUid) {
Friendship myFriendship = this.getFriendById(targetUid);
if (myFriendship == null) {
return;
}
this.getFriends().remove(targetUid);
myFriendship.delete();
Friendship theirFriendship = null;
GenshinPlayer friend = myFriendship.getFriendProfile().getPlayer();
if (friend != null) {
// Friend online
theirFriendship = friend.getFriendsList().getFriendById(this.getPlayer().getId());
if (theirFriendship != null) {
friend.getFriendsList().getFriends().remove(theirFriendship.getFriendId());
theirFriendship.delete();
friend.sendPacket(new PacketDeleteFriendNotify(theirFriendship.getFriendId()));
}
} else {
// Friend offline
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
if (theirFriendship != null) {
theirFriendship.delete();
}
}
// Packet
this.getPlayer().sendPacket(new PacketDeleteFriendRsp(targetUid));
}
public synchronized void sendFriendRequest(int targetUid) {
GenshinPlayer target = getPlayer().getSession().getServer().forceGetPlayerById(targetUid);
if (target == null || target == this.getPlayer()) {
return;
}
// Check if friend already exists
if (this.getPendingFriends().containsKey(targetUid) || this.getFriends().containsKey(targetUid)) {
return;
}
// Create friendships
Friendship myFriendship = new Friendship(getPlayer(), target, getPlayer());
Friendship theirFriendship = new Friendship(target, getPlayer(), getPlayer());
// Add pending lists
this.addPendingFriend(myFriendship);
if (target.isOnline() && target.getFriendsList().hasLoaded()) {
target.getFriendsList().addPendingFriend(theirFriendship);
target.sendPacket(new PacketAskAddFriendNotify(theirFriendship));
}
// Save
myFriendship.save();
theirFriendship.save();
// Packets
this.getPlayer().sendPacket(new PacketAskAddFriendRsp(targetUid));
}
/** Gets total amount of potential friends
* */
public int getFullFriendCount() {
return this.getPendingFriends().size() + this.getFriends().size();
}
public synchronized void loadFromDatabase() {
if (this.hasLoaded()) {
return;
}
// Get friendships from the db
List<Friendship> friendships = DatabaseHelper.getFriends(player);
friendships.forEach(this::loadFriendFromDatabase);
// Set loaded flag
this.loaded = true;
}
private void loadFriendFromDatabase(Friendship friendship) {
// Set friendship owner
friendship.setOwner(getPlayer());
// Check if friend is online
GenshinPlayer friend = getPlayer().getSession().getServer().getPlayerById(friendship.getFriendProfile().getId());
if (friend != null) {
// Set friend to online mode
friendship.setFriendProfile(friend);
// Update our status on friend's client if theyre online
if (friend.getFriendsList().hasLoaded()) {
Friendship theirFriendship = friend.getFriendsList().getFriendshipById(getPlayer().getId());
if (theirFriendship != null) {
// Update friend profile
theirFriendship.setFriendProfile(getPlayer());
} else {
// They dont have us on their friends list anymore, rip
friendship.delete();
return;
}
}
}
// Finally, load to our friends list
if (friendship.isFriend()) {
getFriends().put(friendship.getFriendId(), friendship);
} else {
getPendingFriends().put(friendship.getFriendId(), friendship);
// TODO - Hacky fix to force client to see a notification for a friendship
if (getPendingFriends().size() == 1) {
getPlayer().getSession().send(new PacketAskAddFriendNotify(friendship));
}
}
}
public void save() {
// Update all our friends
List<Friendship> friendships = DatabaseHelper.getReverseFriends(getPlayer());
for (Friendship friend : friendships) {
friend.setFriendProfile(this.getPlayer());
friend.save();
}
}
}

View File

@ -0,0 +1,108 @@
package emu.grasscutter.game.friends;
import org.bson.types.ObjectId;
import dev.morphia.annotations.*;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.net.proto.FriendBriefOuterClass.FriendBrief;
import emu.grasscutter.net.proto.FriendOnlineStateOuterClass.FriendOnlineState;
import emu.grasscutter.net.proto.HeadImageOuterClass.HeadImage;
@Entity(value = "friendships", noClassnameStored = true)
public class Friendship {
@Id private ObjectId id;
@Transient private GenshinPlayer owner;
@Indexed private int ownerId;
@Indexed private int friendId;
private boolean isFriend;
private int askerId;
private PlayerProfile profile;
@Deprecated // Morphia use only
public Friendship() { }
public Friendship(GenshinPlayer owner, GenshinPlayer friend, GenshinPlayer asker) {
this.setOwner(owner);
this.ownerId = owner.getId();
this.friendId = friend.getId();
this.profile = friend.getProfile();
this.askerId = asker.getId();
}
public GenshinPlayer getOwner() {
return owner;
}
public void setOwner(GenshinPlayer owner) {
this.owner = owner;
}
public boolean isFriend() {
return isFriend;
}
public void setIsFriend(boolean b) {
this.isFriend = b;
}
public int getOwnerId() {
return ownerId;
}
public int getFriendId() {
return friendId;
}
public int getAskerId() {
return askerId;
}
public void setAskerId(int askerId) {
this.askerId = askerId;
}
public PlayerProfile getFriendProfile() {
return profile;
}
public void setFriendProfile(GenshinPlayer character) {
if (character == null || this.friendId != character.getId()) return;
this.profile = character.getProfile();
}
public boolean isOnline() {
return getFriendProfile().getPlayer() != null;
}
public void save() {
DatabaseHelper.saveFriendship(this);
}
public void delete() {
DatabaseHelper.deleteFriendship(this);
}
public FriendBrief toProto() {
FriendBrief proto = FriendBrief.newBuilder()
.setUid(getFriendProfile().getId())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setAvatar(HeadImage.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature())
.setOnlineState(getFriendProfile().isOnline() ? FriendOnlineState.FRIEND_ONLINE : FriendOnlineState.FRIEND_DISCONNECT)
.setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin())
.setUnk1(1)
.setUnk2(3)
.build();
return proto;
}
}

View File

@ -0,0 +1,99 @@
package emu.grasscutter.game.friends;
import dev.morphia.annotations.*;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.utils.Utils;
public class PlayerProfile {
@Transient private GenshinPlayer player;
private int id;
private int nameCard;
private int avatarId;
private String name;
private String signature;
private int achievements;
private int playerLevel;
private int worldLevel;
private int lastActiveTime;
@Deprecated // Morphia only
public PlayerProfile() { }
public PlayerProfile(GenshinPlayer player) {
this.id = player.getId();
this.syncWithCharacter(player);
}
public int getId() {
return id;
}
public GenshinPlayer getPlayer() {
return player;
}
public synchronized void setPlayer(GenshinPlayer player) {
this.player = player;
}
public String getName() {
return name;
}
public int getNameCard() {
return nameCard;
}
public int getAvatarId() {
return avatarId;
}
public String getSignature() {
return signature;
}
public int getAchievements() {
return achievements;
}
public int getPlayerLevel() {
return playerLevel;
}
public int getWorldLevel() {
return worldLevel;
}
public int getLastActiveTime() {
return lastActiveTime;
}
public void updateLastActiveTime() {
this.lastActiveTime = Utils.getCurrentSeconds();
}
public int getDaysSinceLogin() {
return (int) Math.floor((Utils.getCurrentSeconds() - getLastActiveTime()) / 86400.0);
}
public boolean isOnline() {
return this.getPlayer() != null;
}
public void syncWithCharacter(GenshinPlayer player) {
if (player == null) {
return;
}
this.name = player.getNickname();
this.avatarId = player.getHeadImage();
this.signature = player.getSignature();
this.nameCard = player.getNameCardId();
this.playerLevel = player.getLevel();
this.worldLevel = player.getWorldLevel();
//this.achievements = 0;
this.updateLastActiveTime();
}
}

View File

@ -0,0 +1,150 @@
package emu.grasscutter.game.gacha;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo;
import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo;
public class GachaBanner {
private int gachaType;
private int scheduleId;
private String prefabPath;
private String previewPrefabPath;
private String titlePath;
private int costItem;
private int beginTime;
private int endTime;
private int sortId;
private int[] rateUpItems1;
private int[] rateUpItems2;
private int minItemType = 1;
private int maxItemType = 2;
private int eventChance = 50; // Chance to win a featured event item
private int softPity = 75;
private int hardPity = 90;
private BannerType bannerType = BannerType.STANDARD;
public int getGachaType() {
return gachaType;
}
public BannerType getBannerType() {
return bannerType;
}
public int getScheduleId() {
return scheduleId;
}
public String getPrefabPath() {
return prefabPath;
}
public String getPreviewPrefabPath() {
return previewPrefabPath;
}
public String getTitlePath() {
return titlePath;
}
public int getCostItem() {
return costItem;
}
public int getBeginTime() {
return beginTime;
}
public int getEndTime() {
return endTime;
}
public int getSortId() {
return sortId;
}
public int[] getRateUpItems1() {
return rateUpItems1;
}
public int[] getRateUpItems2() {
return rateUpItems2;
}
public int getMinItemType() {
return minItemType;
}
public int getMaxItemType() {
return maxItemType;
}
public int getSoftPity() {
return softPity - 1;
}
public int getHardPity() {
return hardPity - 1;
}
public int getEventChance() {
return eventChance;
}
public GachaInfo toProto() {
String record = "http://" + Grasscutter.getConfig().DispatchServerIp + "/gacha";
GachaInfo.Builder info = GachaInfo.newBuilder()
.setGachaType(this.getGachaType())
.setScheduleId(this.getScheduleId())
.setBeginTime(this.getBeginTime())
.setEndTime(this.getEndTime())
.setCostItemId(this.getCostItem())
.setCostItemNum(1)
.setGachaPrefabPath(this.getPrefabPath())
.setGachaPreviewPrefabPath(this.getPreviewPrefabPath())
.setGachaProbUrl(record)
.setGachaProbUrlOversea(record)
.setGachaRecordUrl(record)
.setGachaRecordUrlOversea(record)
.setTenCostItemId(this.getCostItem())
.setTenCostItemNum(10)
.setLeftGachaTimes(Integer.MAX_VALUE)
.setGachaTimesLimit(Integer.MAX_VALUE)
.setGachaSortId(this.getSortId());
if (this.getTitlePath() != null) {
info.setGachaTitlePath(this.getTitlePath());
}
if (this.getRateUpItems1().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1);
for (int id : getRateUpItems1()) {
upInfo.addItemIdList(id);
info.addMainNameId(id);
}
info.addGachaUpInfoList(upInfo);
}
if (this.getRateUpItems2().length > 0) {
GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2);
for (int id : getRateUpItems2()) {
upInfo.addItemIdList(id);
if (info.getSubNameIdCount() == 0) {
info.addSubNameId(id);
}
}
info.addGachaUpInfoList(upInfo);
}
return info.build();
}
public enum BannerType {
STANDARD, EVENT, WEAPON;
}
}

View File

@ -0,0 +1,287 @@
package emu.grasscutter.game.gacha;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.inventory.GenshinItem;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
public class GachaManager {
private final GameServer server;
private final Int2ObjectMap<GachaBanner> gachaBanners;
private GetGachaInfoRsp cachedProto;
private int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041};
private int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502};
private int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064};
private int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405};
private int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304};
private static int starglitterId = 221;
private static int stardustId = 222;
public GachaManager(GameServer server) {
this.server = server;
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
}
public GameServer getServer() {
return server;
}
public Int2ObjectMap<GachaBanner> getGachaBanners() {
return gachaBanners;
}
public int randomRange(int min, int max) {
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
}
public int getRandom(int[] array) {
return array[randomRange(0, array.length - 1)];
}
public synchronized void load() {
try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Banners.json")) {
List<GachaBanner> banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType());
for (GachaBanner banner : banners) {
getGachaBanners().put(banner.getGachaType(), banner);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void doPulls(GenshinPlayer player, int gachaType, int times) {
// Sanity check
if (times != 10 && times != 1) {
return;
}
if (player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Get banner
GachaBanner banner = this.getGachaBanners().get(gachaType);
if (banner == null) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Spend currency
if (banner.getCostItem() > 0) {
GenshinItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(banner.getCostItem());
if (costItem == null || costItem.getCount() < times) {
return;
}
player.getInventory().removeItem(costItem, times);
}
// Roll
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
IntList wonItems = new IntArrayList(times);
for (int i = 0; i < times; i++) {
int random = this.randomRange(1, 10000);
int itemId = 0;
int bonusYellowChance = gachaInfo.getPity5() >= banner.getSoftPity() ? 100 * (gachaInfo.getPity5() - banner.getSoftPity() - 1): 0;
int yellowChance = 60 + (int) Math.floor(100f * (gachaInfo.getPity5() / (banner.getSoftPity() - 1D))) + bonusYellowChance;
int purpleChance = 10000 - (510 + (int) Math.floor(790f * (gachaInfo.getPity4() / 8f)));
if (random <= yellowChance || gachaInfo.getPity5() >= banner.getHardPity()) {
if (banner.getRateUpItems1().length > 0) {
int eventChance = this.randomRange(1, 100);
if (eventChance >= banner.getEventChance() || gachaInfo.getFailedFeaturedItemPulls() >= 1) {
itemId = getRandom(banner.getRateUpItems1());
gachaInfo.setFailedFeaturedItemPulls(0);
} else {
// Lost the 50/50... rip
gachaInfo.addFailedFeaturedItemPulls(1);
}
}
if (itemId == 0) {
int typeChance = this.randomRange(banner.getMinItemType(), banner.getMaxItemType());
if (typeChance == 1) {
itemId = getRandom(this.yellowAvatars);
} else {
itemId = getRandom(this.yellowWeapons);
}
}
// Pity
gachaInfo.addPity4(1);
gachaInfo.setPity5(0);
} else if (random >= purpleChance || gachaInfo.getPity4() >= 9) {
if (banner.getRateUpItems2().length > 0) {
int eventChance = this.randomRange(1, 100);
if (eventChance >= 50) {
itemId = getRandom(banner.getRateUpItems2());
}
}
if (itemId == 0) {
int typeChance = this.randomRange(banner.getMinItemType(), banner.getMaxItemType());
if (typeChance == 1) {
itemId = getRandom(this.purpleAvatars);
} else {
itemId = getRandom(this.purpleWeapons);
}
}
// Pity
gachaInfo.addPity5(1);
gachaInfo.setPity4(0);
} else {
itemId = getRandom(this.blueWeapons);
// Pity
gachaInfo.addPity4(1);
gachaInfo.addPity5(1);
}
// Add winning item
wonItems.add(itemId);
}
// Add to character
List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0;
for (int itemId : wonItems) {
ItemData itemData = GenshinData.getItemDataMap().get(itemId);
if (itemData == null) {
continue;
}
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
int addStardust = 0, addStarglitter = 0;
boolean isTransferItem = false;
// Const check
if (itemData.getMaterialType() == MaterialType.MATERIAL_AVATAR) {
int avatarId = (itemData.getId() % 1000) + 10000000;
GenshinAvatar avatar = player.getAvatars().getAvatarById(avatarId);
if (avatar != null) {
int constLevel = avatar.getCoreProudSkillLevel();
int constItemId = itemData.getId() + 100;
GenshinItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId);
if (constItem != null) {
constLevel += constItem.getCount();
}
if (constLevel < 6) {
// Not max const
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
addStarglitter = 5;
}
if (itemData.getRankLevel() == 5) {
addStarglitter *= 5;
}
isTransferItem = true;
} else {
// New
gachaItem.setIsGachaItemNew(true);
}
} else {
// Is weapon
switch (itemData.getRankLevel()) {
case 5:
addStarglitter = 10;
break;
case 4:
addStarglitter = 2;
break;
case 3:
addStardust = 15;
break;
}
}
// Create item
GenshinItem item = new GenshinItem(itemData);
gachaItem.setGachaItem(item.toItemParam());
player.getInventory().addItem(item);
stardust += addStardust;
starglitter += addStarglitter;
if (addStardust > 0) {
gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
} if (addStarglitter > 0) {
ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
}
gachaItem.addTokenItemList(starglitterParam);
}
list.add(gachaItem.build());
}
// Add stardust/starglitter
if (stardust > 0) {
player.getInventory().addItem(stardustId, stardust);
} if (starglitter > 0) {
player.getInventory().addItem(starglitterId, starglitter);
}
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list));
}
private synchronized GetGachaInfoRsp createProto() {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
for (GachaBanner banner : getGachaBanners().values()) {
proto.addGachaInfoList(banner.toProto());
}
return proto.build();
}
public GetGachaInfoRsp toProto() {
if (this.cachedProto == null) {
this.cachedProto = createProto();
}
return this.cachedProto;
}
}

View File

@ -0,0 +1,43 @@
package emu.grasscutter.game.gacha;
public class PlayerGachaBannerInfo {
private int pity5 = 0;
private int pity4 = 0;
private int failedFeaturedItemPulls = 0;
public int getPity5() {
return pity5;
}
public void setPity5(int pity5) {
this.pity5 = pity5;
}
public void addPity5(int amount) {
this.pity5 += amount;
}
public int getPity4() {
return pity4;
}
public void setPity4(int pity4) {
this.pity4 = pity4;
}
public void addPity4(int amount) {
this.pity4 += amount;
}
public int getFailedFeaturedItemPulls() {
return failedFeaturedItemPulls;
}
public void setFailedFeaturedItemPulls(int failedEventCharacterPulls) {
this.failedFeaturedItemPulls = failedEventCharacterPulls;
}
public void addFailedFeaturedItemPulls(int amount) {
failedFeaturedItemPulls += amount;
}
}

View File

@ -0,0 +1,37 @@
package emu.grasscutter.game.gacha;
public class PlayerGachaInfo {
private PlayerGachaBannerInfo standardBanner;
private PlayerGachaBannerInfo eventCharacterBanner;
private PlayerGachaBannerInfo eventWeaponBanner;
public PlayerGachaInfo() {
this.standardBanner = new PlayerGachaBannerInfo();
this.eventCharacterBanner = new PlayerGachaBannerInfo();
this.eventWeaponBanner = new PlayerGachaBannerInfo();
}
public PlayerGachaBannerInfo getStandardBanner() {
return standardBanner;
}
public PlayerGachaBannerInfo getEventCharacterBanner() {
return eventCharacterBanner;
}
public PlayerGachaBannerInfo getEventWeaponBanner() {
return eventWeaponBanner;
}
public PlayerGachaBannerInfo getBannerInfo(GachaBanner banner) {
switch (banner.getBannerType()) {
case EVENT:
return this.eventCharacterBanner;
case WEAPON:
return this.eventWeaponBanner;
case STANDARD:
default:
return this.standardBanner;
}
}
}

View File

@ -0,0 +1,39 @@
package emu.grasscutter.game.inventory;
import java.util.HashSet;
import java.util.Set;
public class EquipInventoryTab implements InventoryTab {
private final Set<GenshinItem> items;
private final int maxCapacity;
public EquipInventoryTab(int maxCapacity) {
this.items = new HashSet<GenshinItem>();
this.maxCapacity = maxCapacity;
}
@Override
public GenshinItem getItemById(int id) {
return null;
}
@Override
public void onAddItem(GenshinItem item) {
this.items.add(item);
}
@Override
public void onRemoveItem(GenshinItem item) {
this.items.remove(item);
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}

View File

@ -0,0 +1,45 @@
package emu.grasscutter.game.inventory;
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 EquipType {
EQUIP_NONE (0),
EQUIP_BRACER (1),
EQUIP_NECKLACE (2),
EQUIP_SHOES (3),
EQUIP_RING (4),
EQUIP_DRESS (5),
EQUIP_WEAPON (6);
private final int value;
private static final Int2ObjectMap<EquipType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, EquipType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private EquipType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static EquipType getTypeByValue(int value) {
return map.getOrDefault(value, EQUIP_NONE);
}
public static EquipType getTypeByName(String name) {
return stringMap.getOrDefault(name, EQUIP_NONE);
}
}

View File

@ -0,0 +1,430 @@
package emu.grasscutter.game.inventory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.PostLoad;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.GenshinDepot;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.data.def.ReliquaryAffixData;
import emu.grasscutter.data.def.ReliquaryMainPropData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.EquipOuterClass.Equip;
import emu.grasscutter.net.proto.FurnitureOuterClass.Furniture;
import emu.grasscutter.net.proto.ItemHintOuterClass.ItemHint;
import emu.grasscutter.net.proto.ItemOuterClass.Item;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.MaterialOuterClass.Material;
import emu.grasscutter.net.proto.ReliquaryOuterClass.Reliquary;
import emu.grasscutter.net.proto.SceneReliquaryInfoOuterClass.SceneReliquaryInfo;
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
import emu.grasscutter.net.proto.WeaponOuterClass.Weapon;
import emu.grasscutter.utils.WeightedList;
@Entity(value = "items", noClassnameStored = true)
public class GenshinItem {
@Id private ObjectId id;
@Indexed private int ownerId;
private int itemId;
private int count;
@Transient private long guid; // Player unique id
@Transient private ItemData itemData;
// Equips
private int level;
private int exp;
private int totalExp;
private int promoteLevel;
private boolean locked;
// Weapon
private List<Integer> affixes;
private int refinement = 0;
// Relic
private int mainPropId;
private List<Integer> appendPropIdList;
private int equipCharacter;
@Transient private int weaponEntityId;
public GenshinItem() {
// Morphia only
}
public GenshinItem(int itemId) {
this(GenshinData.getItemDataMap().get(itemId));
}
public GenshinItem(int itemId, int count) {
this(GenshinData.getItemDataMap().get(itemId), count);
}
public GenshinItem(ItemData data) {
this(data, 1);
}
public GenshinItem(ItemData data, int count) {
this.itemId = data.getId();
this.itemData = data;
if (data.getItemType() == ItemType.ITEM_VIRTUAL) {
this.count = count;
} else {
this.count = Math.min(count, data.getStackLimit());
}
// Equip data
if (getItemType() == ItemType.ITEM_WEAPON) {
this.level = 1;
this.affixes = new ArrayList<>(2);
if (getItemData().getSkillAffix() != null) {
for (int skillAffix : getItemData().getSkillAffix()) {
if (skillAffix > 0) {
this.affixes.add(skillAffix);
}
}
}
} else if (getItemType() == ItemType.ITEM_RELIQUARY) {
this.level = 1;
this.appendPropIdList = new ArrayList<>();
// Create main property
ReliquaryMainPropData mainPropData = GenshinDepot.getRandomRelicMainProp(getItemData().getMainPropDepotId());
if (mainPropData != null) {
this.mainPropId = mainPropData.getId();
}
// Create extra stats
if (getItemData().getAppendPropNum() > 0) {
for (int i = 0; i < getItemData().getAppendPropNum(); i++) {
this.addAppendProp();
}
}
}
}
public ObjectId getObjectId() {
return id;
}
public int getOwnerId() {
return ownerId;
}
public void setOwner(GenshinPlayer player) {
this.ownerId = player.getId();
this.guid = player.getNextGuid();
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public long getGuid() {
return guid;
}
public ItemType getItemType() {
return this.itemData.getItemType();
}
public ItemData getItemData() {
return itemData;
}
public void setItemData(ItemData materialData) {
this.itemData = materialData;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public int getExp() {
return exp;
}
public void setExp(int exp) {
this.exp = exp;
}
public int getTotalExp() {
return totalExp;
}
public void setTotalExp(int totalExp) {
this.totalExp = totalExp;
}
public int getPromoteLevel() {
return promoteLevel;
}
public void setPromoteLevel(int promoteLevel) {
this.promoteLevel = promoteLevel;
}
public int getEquipSlot() {
return this.getItemData().getEquipType().getValue();
}
public int getEquipCharacter() {
return equipCharacter;
}
public void setEquipCharacter(int equipCharacter) {
this.equipCharacter = equipCharacter;
}
public boolean isEquipped() {
return this.getEquipCharacter() > 0;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public boolean isDestroyable() {
return !this.isLocked() && !this.isEquipped();
}
public int getWeaponEntityId() {
return weaponEntityId;
}
public void setWeaponEntityId(int weaponEntityId) {
this.weaponEntityId = weaponEntityId;
}
public List<Integer> getAffixes() {
return affixes;
}
public int getRefinement() {
return refinement;
}
public void setRefinement(int refinement) {
this.refinement = refinement;
}
public int getMainPropId() {
return mainPropId;
}
public List<Integer> getAppendPropIdList() {
return appendPropIdList;
}
public void addAppendProp() {
if (this.getAppendPropIdList() == null) {
this.appendPropIdList = new ArrayList<>();
}
if (this.getAppendPropIdList().size() < 4) {
addNewAppendProp();
} else {
upgradeRandomAppendProp();
}
}
private void addNewAppendProp() {
List<ReliquaryAffixData> affixList = GenshinDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId());
if (affixList == null) {
return;
}
// Build blacklist - Dont add same stat as main/sub stat
Set<FightProperty> blacklist = new HashSet<>();
ReliquaryMainPropData mainPropData = GenshinData.getReliquaryMainPropDataMap().get(this.getMainPropId());
if (mainPropData != null) {
blacklist.add(mainPropData.getFightProp());
}
int len = Math.min(4, this.getAppendPropIdList().size());
for (int i = 0; i < len; i++) {
ReliquaryAffixData affixData = GenshinData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i));
if (affixData != null) {
blacklist.add(affixData.getFightProp());
}
}
// Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
for (ReliquaryAffixData affix : affixList) {
if (!blacklist.contains(affix.getFightProp())) {
randomList.add(affix.getWeight(), affix);
}
}
if (randomList.size() == 0) {
return;
}
// Add random stat
ReliquaryAffixData affixData = randomList.next();
this.getAppendPropIdList().add(affixData.getId());
}
private void upgradeRandomAppendProp() {
List<ReliquaryAffixData> affixList = GenshinDepot.getRandomRelicAffixList(getItemData().getAppendPropDepotId());
if (affixList == null) {
return;
}
// Build whitelist
Set<FightProperty> whitelist = new HashSet<>();
int len = Math.min(4, this.getAppendPropIdList().size());
for (int i = 0; i < len; i++) {
ReliquaryAffixData affixData = GenshinData.getReliquaryAffixDataMap().get((int) this.getAppendPropIdList().get(i));
if (affixData != null) {
whitelist.add(affixData.getFightProp());
}
}
// Build random list
WeightedList<ReliquaryAffixData> randomList = new WeightedList<>();
for (ReliquaryAffixData affix : affixList) {
if (whitelist.contains(affix.getFightProp())) {
randomList.add(affix.getUpgradeWeight(), affix);
}
}
// Add random stat
ReliquaryAffixData affixData = randomList.next();
this.getAppendPropIdList().add(affixData.getId());
}
@PostLoad
public void onLoad() {
if (this.itemData == null) {
this.itemData = GenshinData.getItemDataMap().get(getItemId());
}
}
public void save() {
if (this.count > 0 && this.ownerId > 0) {
DatabaseHelper.saveItem(this);
} else if (this.getObjectId() != null) {
DatabaseHelper.deleteItem(this);
}
}
public SceneWeaponInfo createSceneWeaponInfo() {
SceneWeaponInfo.Builder weaponInfo = SceneWeaponInfo.newBuilder()
.setEntityId(this.getWeaponEntityId())
.setItemId(this.getItemId())
.setGuid(this.getGuid())
.setLevel(this.getLevel())
.setGadgetId(this.getItemData().getGadgetId())
.setAbilityInfo(AbilitySyncStateInfo.newBuilder().setIsInited(getAffixes().size() > 0));
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
for (int affix : this.getAffixes()) {
weaponInfo.putAffixMap(affix, this.getRefinement());
}
}
return weaponInfo.build();
}
public SceneReliquaryInfo createSceneReliquaryInfo() {
SceneReliquaryInfo relicInfo = SceneReliquaryInfo.newBuilder()
.setItemId(this.getItemId())
.setGuid(this.getGuid())
.setLevel(this.getLevel())
.build();
return relicInfo;
}
public Item toProto() {
Item.Builder proto = Item.newBuilder()
.setGuid(this.getGuid())
.setItemId(this.getItemId());
switch (getItemType()) {
case ITEM_WEAPON:
Weapon.Builder weapon = Weapon.newBuilder()
.setLevel(this.getLevel())
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel());
if (this.getAffixes() != null && this.getAffixes().size() > 0) {
for (int affix : this.getAffixes()) {
weapon.putAffixMap(affix, this.getRefinement());
}
}
proto.setEquip(Equip.newBuilder().setWeapon(weapon).setIsLocked(this.isLocked()).build());
break;
case ITEM_RELIQUARY:
Reliquary relic = Reliquary.newBuilder()
.setLevel(this.getLevel())
.setExp(this.getExp())
.setPromoteLevel(this.getPromoteLevel())
.setMainPropId(this.getMainPropId())
.addAllAppendPropIdList(this.getAppendPropIdList())
.build();
proto.setEquip(Equip.newBuilder().setReliquary(relic).setIsLocked(this.isLocked()).build());
break;
case ITEM_MATERIAL:
Material material = Material.newBuilder()
.setCount(getCount())
.build();
proto.setMaterial(material);
break;
case ITEM_FURNITURE:
Furniture furniture = Furniture.newBuilder()
.setCount(getCount())
.build();
proto.setFurniture(furniture);
break;
default:
break;
}
return proto.build();
}
public ItemHint toItemHintProto() {
return ItemHint.newBuilder().setItemId(getItemId()).setCount(getCount()).setIsNew(false).build();
}
public ItemParam toItemParam() {
return ItemParam.newBuilder().setItemId(this.getItemId()).setCount(this.getCount()).build();
}
}

View File

@ -0,0 +1,353 @@
package emu.grasscutter.game.inventory;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import emu.grasscutter.GenshinConstants;
import emu.grasscutter.data.GenshinData;
import emu.grasscutter.data.def.AvatarCostumeData;
import emu.grasscutter.data.def.AvatarData;
import emu.grasscutter.data.def.AvatarFlycloakData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify;
import emu.grasscutter.server.packet.send.PacketStoreItemDelNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
public class Inventory implements Iterable<GenshinItem> {
private final GenshinPlayer player;
private final Long2ObjectMap<GenshinItem> store;
private final Int2ObjectMap<InventoryTab> inventoryTypes;
public Inventory(GenshinPlayer player) {
this.player = player;
this.store = new Long2ObjectOpenHashMap<>();
this.inventoryTypes = new Int2ObjectOpenHashMap<>();
this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(GenshinConstants.LIMIT_WEAPON));
this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(GenshinConstants.LIMIT_RELIC));
this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(GenshinConstants.LIMIT_MATERIAL));
this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(GenshinConstants.LIMIT_FURNITURE));
}
public GenshinPlayer getPlayer() {
return player;
}
public AvatarStorage getAvatarStorage() {
return this.getPlayer().getAvatars();
}
public Long2ObjectMap<GenshinItem> getItems() {
return store;
}
public Int2ObjectMap<InventoryTab> getInventoryTypes() {
return inventoryTypes;
}
public InventoryTab getInventoryTab(ItemType type) {
return getInventoryTypes().get(type.getValue());
}
public void createInventoryTab(ItemType type, InventoryTab tab) {
this.getInventoryTypes().put(type.getValue(), tab);
}
public GenshinItem getItemByGuid(long id) {
return this.getItems().get(id);
}
public boolean addItem(int itemId) {
return addItem(itemId, 1);
}
public boolean addItem(int itemId, int count) {
ItemData itemData = GenshinData.getItemDataMap().get(itemId);
if (itemData == null) {
return false;
}
GenshinItem item = new GenshinItem(itemData, count);
return addItem(item);
}
public boolean addItem(GenshinItem item) {
GenshinItem result = putItem(item);
if (result != null) {
getPlayer().sendPacket(new PacketStoreItemChangeNotify(result));
return true;
}
return false;
}
public void addItems(Collection<GenshinItem> items) {
List<GenshinItem> changedItems = new LinkedList<>();
for (GenshinItem item : items) {
GenshinItem result = putItem(item);
if (result != null) {
changedItems.add(result);
}
}
getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
}
public void addItemParams(Collection<ItemParam> items) {
List<GenshinItem> changedItems = new LinkedList<>();
for (ItemParam itemParam : items) {
GenshinItem toAdd = new GenshinItem(itemParam.getItemId(), itemParam.getCount());
GenshinItem result = putItem(toAdd);
if (result != null) {
changedItems.add(result);
}
}
getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems));
}
private synchronized GenshinItem putItem(GenshinItem item) {
// Dont add items that dont have a valid item definition.
if (item.getItemData() == null) {
return null;
}
// Add item to inventory store
ItemType type = item.getItemData().getItemType();
InventoryTab tab = getInventoryTab(type);
// Add
if (type == ItemType.ITEM_WEAPON || type == ItemType.ITEM_RELIQUARY) {
if (tab.getSize() >= tab.getMaxCapacity()) {
return null;
}
putItem(item, tab);
} else if (type == ItemType.ITEM_VIRTUAL) {
// Handle
this.addVirtualItem(item.getItemId(), item.getCount());
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR) {
// Get avatar id
int avatarId = (item.getItemId() % 1000) + 10000000;
// Dont let people give themselves extra main characters
if (avatarId == GenshinConstants.MAIN_CHARACTER_MALE || avatarId == GenshinConstants.MAIN_CHARACTER_FEMALE) {
return null;
}
// Add avatar
AvatarData avatarData = GenshinData.getAvatarDataMap().get(avatarId);
if (avatarData != null && !player.getAvatars().hasAvatar(avatarId)) {
this.getPlayer().addAvatar(new GenshinAvatar(avatarData));
}
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_FLYCLOAK) {
AvatarFlycloakData flycloakData = GenshinData.getAvatarFlycloakDataMap().get(item.getItemId());
if (flycloakData != null && !player.getFlyCloakList().contains(item.getItemId())) {
getPlayer().addFlycloak(item.getItemId());
}
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_COSTUME) {
AvatarCostumeData costumeData = GenshinData.getAvatarCostumeDataItemIdMap().get(item.getItemId());
if (costumeData != null && !player.getCostumeList().contains(costumeData.getId())) {
getPlayer().addCostume(costumeData.getId());
}
return null;
} else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_NAMECARD) {
if (!player.getNameCardList().contains(item.getItemId())) {
getPlayer().addNameCard(item.getItemId());
}
return null;
} else if (tab != null) {
GenshinItem existingItem = tab.getItemById(item.getItemId());
if (existingItem == null) {
// Item type didnt exist before, we will add it to main inventory map if there is enough space
if (tab.getSize() >= tab.getMaxCapacity()) {
return null;
}
putItem(item, tab);
} else {
// Add count
existingItem.setCount(Math.min(existingItem.getCount() + item.getCount(), item.getItemData().getStackLimit()));
existingItem.save();
return existingItem;
}
}
// Set ownership and save to db
item.save();
return item;
}
private synchronized void putItem(GenshinItem item, InventoryTab tab) {
// Set owner and guid FIRST!
item.setOwner(getPlayer());
// Put in item store
getItems().put(item.getGuid(), item);
if (tab != null) {
tab.onAddItem(item);
}
}
private void addVirtualItem(int itemId, int count) {
switch (itemId) {
case 102: // Adventure exp
getPlayer().addExpDirectly(count);
break;
case 201: // Primogem
getPlayer().setPrimogems(player.getPrimogems() + count);
break;
case 202: // Mora
getPlayer().setMora(player.getMora() + count);
break;
}
}
public void removeItems(List<GenshinItem> items) {
// TODO Bulk delete
for (GenshinItem item : items) {
this.removeItem(item, item.getCount());
}
}
public boolean removeItem(long guid) {
return removeItem(guid, 1);
}
public synchronized boolean removeItem(long guid, int count) {
GenshinItem item = this.getItemByGuid(guid);
if (item == null) {
return false;
}
return removeItem(item, count);
}
public synchronized boolean removeItem(GenshinItem item) {
return removeItem(item, item.getCount());
}
public synchronized boolean removeItem(GenshinItem item, int count) {
// Sanity check
if (count <= 0 || item == null) {
return false;
}
item.setCount(item.getCount() - count);
if (item.getCount() <= 0) {
// Remove from inventory tab too
InventoryTab tab = null;
if (item.getItemData() != null) {
tab = getInventoryTab(item.getItemData().getItemType());
}
// Remove if less than 0
deleteItem(item, tab);
//
getPlayer().sendPacket(new PacketStoreItemDelNotify(item));
} else {
getPlayer().sendPacket(new PacketStoreItemChangeNotify(item));
}
// Update in db
item.save();
// Returns true on success
return true;
}
private void deleteItem(GenshinItem item, InventoryTab tab) {
getItems().remove(item.getGuid());
if (tab != null) {
tab.onRemoveItem(item);
}
}
public boolean equipItem(long avatarGuid, long equipGuid) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(avatarGuid);
GenshinItem item = this.getItemByGuid(equipGuid);
if (avatar != null && item != null) {
return avatar.equipItem(item, true);
}
return false;
}
public boolean unequipItem(long avatarGuid, int slot) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarByGuid(avatarGuid);
EquipType equipType = EquipType.getTypeByValue(slot);
if (avatar != null && equipType != EquipType.EQUIP_WEAPON) {
if (avatar.unequipItem(equipType)) {
getPlayer().sendPacket(new PacketAvatarEquipChangeNotify(avatar, equipType));
avatar.recalcStats();
return true;
}
}
return false;
}
public void loadFromDatabase() {
List<GenshinItem> items = DatabaseHelper.getInventoryItems(getPlayer());
for (GenshinItem item : items) {
// Should never happen
if (item.getObjectId() == null) {
continue;
}
ItemData itemData = GenshinData.getItemDataMap().get(item.getItemId());
if (itemData == null) {
continue;
}
item.setItemData(itemData);
InventoryTab tab = null;
if (item.getItemData() != null) {
tab = getInventoryTab(item.getItemData().getItemType());
}
putItem(item, tab);
// Equip to a character if possible
if (item.isEquipped()) {
GenshinAvatar avatar = getPlayer().getAvatars().getAvatarById(item.getEquipCharacter());
boolean hasEquipped = false;
if (avatar != null) {
hasEquipped = avatar.equipItem(item, false);
}
if (!hasEquipped) {
item.setEquipCharacter(0);
item.save();
}
}
}
}
@Override
public Iterator<GenshinItem> iterator() {
return this.getItems().values().iterator();
}
}

View File

@ -0,0 +1,13 @@
package emu.grasscutter.game.inventory;
public interface InventoryTab {
public GenshinItem getItemById(int id);
public void onAddItem(GenshinItem item);
public void onRemoveItem(GenshinItem item);
public int getSize();
public int getMaxCapacity();
}

View File

@ -0,0 +1,27 @@
package emu.grasscutter.game.inventory;
public class ItemDef {
private int itemId;
private int count;
public ItemDef(int itemId, int count) {
this.itemId = itemId;
this.count = count;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

View File

@ -0,0 +1,45 @@
package emu.grasscutter.game.inventory;
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 ItemQuality {
QUALITY_NONE(0),
QUALITY_WHITE(1),
QUALITY_GREEN(2),
QUALITY_BLUE(3),
QUALITY_PURPLE(4),
QUALITY_ORANGE(5),
QUALITY_ORANGE_SP(105);
private final int value;
private static final Int2ObjectMap<ItemQuality> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ItemQuality> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private ItemQuality(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static ItemQuality getTypeByValue(int value) {
return map.getOrDefault(value, QUALITY_NONE);
}
public static ItemQuality getTypeByName(String name) {
return stringMap.getOrDefault(name, QUALITY_NONE);
}
}

View File

@ -0,0 +1,45 @@
package emu.grasscutter.game.inventory;
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 ItemType {
ITEM_NONE (0),
ITEM_VIRTUAL (1),
ITEM_MATERIAL (2),
ITEM_RELIQUARY (3),
ITEM_WEAPON (4),
ITEM_DISPLAY (5),
ITEM_FURNITURE (6);
private final int value;
private static final Int2ObjectMap<ItemType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ItemType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private ItemType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static ItemType getTypeByValue(int value) {
return map.getOrDefault(value, ITEM_NONE);
}
public static ItemType getTypeByName(String name) {
return stringMap.getOrDefault(name, ITEM_NONE);
}
}

View File

@ -0,0 +1,39 @@
package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class MaterialInventoryTab implements InventoryTab {
private final Int2ObjectMap<GenshinItem> items;
private final int maxCapacity;
public MaterialInventoryTab(int maxCapacity) {
this.items = new Int2ObjectOpenHashMap<>();
this.maxCapacity = maxCapacity;
}
@Override
public GenshinItem getItemById(int id) {
return this.items.get(id);
}
@Override
public void onAddItem(GenshinItem item) {
this.items.put(item.getItemId(), item);
}
@Override
public void onRemoveItem(GenshinItem item) {
this.items.remove(item.getItemId());
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}

View File

@ -0,0 +1,67 @@
package emu.grasscutter.game.inventory;
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 MaterialType {
MATERIAL_NONE (0),
MATERIAL_FOOD (1),
MATERIAL_QUEST (2),
MATERIAL_EXCHANGE (4),
MATERIAL_CONSUME (5),
MATERIAL_EXP_FRUIT (6),
MATERIAL_AVATAR (7),
MATERIAL_ADSORBATE (8),
MATERIAL_CRICKET (9),
MATERIAL_ELEM_CRYSTAL (10),
MATERIAL_WEAPON_EXP_STONE (11),
MATERIAL_CHEST (12),
MATERIAL_RELIQUARY_MATERIAL (13),
MATERIAL_AVATAR_MATERIAL (14),
MATERIAL_NOTICE_ADD_HP (15),
MATERIAL_SEA_LAMP (16),
MATERIAL_SELECTABLE_CHEST (17),
MATERIAL_FLYCLOAK (18),
MATERIAL_NAMECARD (19),
MATERIAL_TALENT (20),
MATERIAL_WIDGET (21),
MATERIAL_CHEST_BATCH_USE (22),
MATERIAL_FAKE_ABSORBATE (23),
MATERIAL_CONSUME_BATCH_USE (24),
MATERIAL_WOOD (25),
MATERIAL_FURNITURE_FORMULA (27),
MATERIAL_CHANNELLER_SLAB_BUFF (28),
MATERIAL_FURNITURE_SUITE_FORMULA (29),
MATERIAL_COSTUME (30);
private final int value;
private static final Int2ObjectMap<MaterialType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, MaterialType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private MaterialType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static MaterialType getTypeByValue(int value) {
return map.getOrDefault(value, MATERIAL_NONE);
}
public static MaterialType getTypeByName(String name) {
return stringMap.getOrDefault(name, MATERIAL_NONE);
}
}

View File

@ -0,0 +1,15 @@
package emu.grasscutter.game.managers;
import emu.grasscutter.server.game.GameServer;
public class AccountManager {
private final GameServer server;
public AccountManager(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
}

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