mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-21 14:18:19 +00:00
Initial commit
This commit is contained in:
commit
7925d1cda3
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal 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
201
LICENSE
Normal 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
49
README.md
Normal 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
63
build.gradle
Normal 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
64
data/Banners.json
Normal 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]
|
||||
}
|
||||
]
|
1
data/query_cur_region.txt
Normal file
1
data/query_cur_region.txt
Normal file
File diff suppressed because one or more lines are too long
1
data/query_region_list.txt
Normal file
1
data/query_region_list.txt
Normal 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
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
188
gradlew
vendored
Normal 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
100
gradlew.bat
vendored
Normal 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
BIN
keys/dispatchKey.bin
Normal file
Binary file not shown.
BIN
keys/dispatchSeed.bin
Normal file
BIN
keys/dispatchSeed.bin
Normal file
Binary file not shown.
BIN
keys/secretKey.bin
Normal file
BIN
keys/secretKey.bin
Normal file
Binary file not shown.
1
keys/secretKeyBuffer.bin
Normal file
1
keys/secretKeyBuffer.bin
Normal file
@ -0,0 +1 @@
|
||||
<EFBFBD><EFBFBD>lt1L <09><>ܟ<EFBFBD>.<15>\<5C>pXP<58><50>"ƀ(<28>a<><61><EFBFBD>
|
BIN
keystore.p12
Normal file
BIN
keystore.p12
Normal file
Binary file not shown.
BIN
lib/fastutil-mini-8.5.6.jar
Normal file
BIN
lib/fastutil-mini-8.5.6.jar
Normal file
Binary file not shown.
BIN
lib/kcp-netty.jar
Normal file
BIN
lib/kcp-netty.jar
Normal file
Binary file not shown.
10
settings.gradle
Normal file
10
settings.gradle
Normal 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'
|
45
src/main/java/emu/grasscutter/Config.java
Normal file
45
src/main/java/emu/grasscutter/Config.java
Normal 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";
|
||||
}
|
||||
}
|
37
src/main/java/emu/grasscutter/GenshinConstants.java
Normal file
37
src/main/java/emu/grasscutter/GenshinConstants.java
Normal 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");
|
||||
}
|
127
src/main/java/emu/grasscutter/Grasscutter.java
Normal file
127
src/main/java/emu/grasscutter/Grasscutter.java
Normal 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
|
||||
}
|
||||
}
|
13
src/main/java/emu/grasscutter/commands/Command.java
Normal file
13
src/main/java/emu/grasscutter/commands/Command.java
Normal 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 "";
|
||||
}
|
307
src/main/java/emu/grasscutter/commands/PlayerCommands.java
Normal file
307
src/main/java/emu/grasscutter/commands/PlayerCommands.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
140
src/main/java/emu/grasscutter/commands/ServerCommands.java
Normal file
140
src/main/java/emu/grasscutter/commands/ServerCommands.java
Normal 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;
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
212
src/main/java/emu/grasscutter/data/GenshinData.java
Normal file
212
src/main/java/emu/grasscutter/data/GenshinData.java
Normal 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;
|
||||
}
|
||||
}
|
49
src/main/java/emu/grasscutter/data/GenshinDepot.java
Normal file
49
src/main/java/emu/grasscutter/data/GenshinDepot.java
Normal 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);
|
||||
}
|
||||
}
|
12
src/main/java/emu/grasscutter/data/GenshinResource.java
Normal file
12
src/main/java/emu/grasscutter/data/GenshinResource.java
Normal file
@ -0,0 +1,12 @@
|
||||
package emu.grasscutter.data;
|
||||
|
||||
public abstract class GenshinResource {
|
||||
|
||||
public int getId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void onLoad() {
|
||||
|
||||
}
|
||||
}
|
281
src/main/java/emu/grasscutter/data/ResourceLoader.java
Normal file
281
src/main/java/emu/grasscutter/data/ResourceLoader.java
Normal 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;
|
||||
}
|
||||
}
|
32
src/main/java/emu/grasscutter/data/ResourceType.java
Normal file
32
src/main/java/emu/grasscutter/data/ResourceType.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
17
src/main/java/emu/grasscutter/data/common/CurveInfo.java
Normal file
17
src/main/java/emu/grasscutter/data/common/CurveInfo.java
Normal 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;
|
||||
}
|
||||
}
|
25
src/main/java/emu/grasscutter/data/common/FightPropData.java
Normal file
25
src/main/java/emu/grasscutter/data/common/FightPropData.java
Normal 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);
|
||||
}
|
||||
}
|
14
src/main/java/emu/grasscutter/data/common/ItemParamData.java
Normal file
14
src/main/java/emu/grasscutter/data/common/ItemParamData.java
Normal 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;
|
||||
}
|
||||
}
|
13
src/main/java/emu/grasscutter/data/common/PropGrowCurve.java
Normal file
13
src/main/java/emu/grasscutter/data/common/PropGrowCurve.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
36
src/main/java/emu/grasscutter/data/def/AvatarCurveData.java
Normal file
36
src/main/java/emu/grasscutter/data/def/AvatarCurveData.java
Normal 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()));
|
||||
}
|
||||
}
|
246
src/main/java/emu/grasscutter/data/def/AvatarData.java
Normal file
246
src/main/java/emu/grasscutter/data/def/AvatarData.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
23
src/main/java/emu/grasscutter/data/def/AvatarLevelData.java
Normal file
23
src/main/java/emu/grasscutter/data/def/AvatarLevelData.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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()]);
|
||||
}
|
||||
}
|
84
src/main/java/emu/grasscutter/data/def/AvatarSkillData.java
Normal file
84
src/main/java/emu/grasscutter/data/def/AvatarSkillData.java
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
123
src/main/java/emu/grasscutter/data/def/AvatarSkillDepotData.java
Normal file
123
src/main/java/emu/grasscutter/data/def/AvatarSkillDepotData.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
69
src/main/java/emu/grasscutter/data/def/AvatarTalentData.java
Normal file
69
src/main/java/emu/grasscutter/data/def/AvatarTalentData.java
Normal 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()]);
|
||||
}
|
||||
}
|
59
src/main/java/emu/grasscutter/data/def/EquipAffixData.java
Normal file
59
src/main/java/emu/grasscutter/data/def/EquipAffixData.java
Normal 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()]);
|
||||
}
|
||||
}
|
60
src/main/java/emu/grasscutter/data/def/GadgetData.java
Normal file
60
src/main/java/emu/grasscutter/data/def/GadgetData.java
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
253
src/main/java/emu/grasscutter/data/def/ItemData.java
Normal file
253
src/main/java/emu/grasscutter/data/def/ItemData.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
32
src/main/java/emu/grasscutter/data/def/MonsterCurveData.java
Normal file
32
src/main/java/emu/grasscutter/data/def/MonsterCurveData.java
Normal 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()));
|
||||
}
|
||||
}
|
198
src/main/java/emu/grasscutter/data/def/MonsterData.java
Normal file
198
src/main/java/emu/grasscutter/data/def/MonsterData.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
72
src/main/java/emu/grasscutter/data/def/NpcData.java
Normal file
72
src/main/java/emu/grasscutter/data/def/NpcData.java
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
33
src/main/java/emu/grasscutter/data/def/PlayerLevelData.java
Normal file
33
src/main/java/emu/grasscutter/data/def/PlayerLevelData.java
Normal 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;
|
||||
}
|
||||
}
|
101
src/main/java/emu/grasscutter/data/def/ProudSkillData.java
Normal file
101
src/main/java/emu/grasscutter/data/def/ProudSkillData.java
Normal 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()]);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
39
src/main/java/emu/grasscutter/data/def/ReliquarySetData.java
Normal file
39
src/main/java/emu/grasscutter/data/def/ReliquarySetData.java
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
32
src/main/java/emu/grasscutter/data/def/WeaponCurveData.java
Normal file
32
src/main/java/emu/grasscutter/data/def/WeaponCurveData.java
Normal 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()));
|
||||
}
|
||||
}
|
23
src/main/java/emu/grasscutter/data/def/WeaponLevelData.java
Normal file
23
src/main/java/emu/grasscutter/data/def/WeaponLevelData.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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()]);
|
||||
}
|
||||
}
|
23
src/main/java/emu/grasscutter/database/DatabaseCounter.java
Normal file
23
src/main/java/emu/grasscutter/database/DatabaseCounter.java
Normal 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;
|
||||
}
|
||||
}
|
207
src/main/java/emu/grasscutter/database/DatabaseHelper.java
Normal file
207
src/main/java/emu/grasscutter/database/DatabaseHelper.java
Normal 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();
|
||||
}
|
||||
}
|
95
src/main/java/emu/grasscutter/database/DatabaseManager.java
Normal file
95
src/main/java/emu/grasscutter/database/DatabaseManager.java
Normal 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());
|
||||
}
|
||||
}
|
98
src/main/java/emu/grasscutter/game/Account.java
Normal file
98
src/main/java/emu/grasscutter/game/Account.java
Normal 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);
|
||||
}
|
||||
}
|
29
src/main/java/emu/grasscutter/game/CoopRequest.java
Normal file
29
src/main/java/emu/grasscutter/game/CoopRequest.java
Normal 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();
|
||||
}
|
||||
}
|
759
src/main/java/emu/grasscutter/game/GenshinPlayer.java
Normal file
759
src/main/java/emu/grasscutter/game/GenshinPlayer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
66
src/main/java/emu/grasscutter/game/InvokeHandler.java
Normal file
66
src/main/java/emu/grasscutter/game/InvokeHandler.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
73
src/main/java/emu/grasscutter/game/TeamInfo.java
Normal file
73
src/main/java/emu/grasscutter/game/TeamInfo.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
484
src/main/java/emu/grasscutter/game/TeamManager.java
Normal file
484
src/main/java/emu/grasscutter/game/TeamManager.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
434
src/main/java/emu/grasscutter/game/World.java
Normal file
434
src/main/java/emu/grasscutter/game/World.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
124
src/main/java/emu/grasscutter/game/avatar/AvatarStat.java
Normal file
124
src/main/java/emu/grasscutter/game/avatar/AvatarStat.java
Normal 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);
|
||||
}
|
||||
}
|
174
src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java
Normal file
174
src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java
Normal 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();
|
||||
}
|
||||
}
|
695
src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java
Normal file
695
src/main/java/emu/grasscutter/game/avatar/GenshinAvatar.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
239
src/main/java/emu/grasscutter/game/entity/EntityAvatar.java
Normal file
239
src/main/java/emu/grasscutter/game/entity/EntityAvatar.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
17
src/main/java/emu/grasscutter/game/entity/EntityGadget.java
Normal file
17
src/main/java/emu/grasscutter/game/entity/EntityGadget.java
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
118
src/main/java/emu/grasscutter/game/entity/EntityItem.java
Normal file
118
src/main/java/emu/grasscutter/game/entity/EntityItem.java
Normal 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();
|
||||
}
|
||||
}
|
219
src/main/java/emu/grasscutter/game/entity/EntityMonster.java
Normal file
219
src/main/java/emu/grasscutter/game/entity/EntityMonster.java
Normal 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();
|
||||
}
|
||||
}
|
102
src/main/java/emu/grasscutter/game/entity/GenshinEntity.java
Normal file
102
src/main/java/emu/grasscutter/game/entity/GenshinEntity.java
Normal 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;
|
||||
}
|
||||
}
|
262
src/main/java/emu/grasscutter/game/friends/FriendsList.java
Normal file
262
src/main/java/emu/grasscutter/game/friends/FriendsList.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
108
src/main/java/emu/grasscutter/game/friends/Friendship.java
Normal file
108
src/main/java/emu/grasscutter/game/friends/Friendship.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
150
src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
Normal file
150
src/main/java/emu/grasscutter/game/gacha/GachaBanner.java
Normal 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;
|
||||
}
|
||||
}
|
287
src/main/java/emu/grasscutter/game/gacha/GachaManager.java
Normal file
287
src/main/java/emu/grasscutter/game/gacha/GachaManager.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
45
src/main/java/emu/grasscutter/game/inventory/EquipType.java
Normal file
45
src/main/java/emu/grasscutter/game/inventory/EquipType.java
Normal 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);
|
||||
}
|
||||
}
|
430
src/main/java/emu/grasscutter/game/inventory/GenshinItem.java
Normal file
430
src/main/java/emu/grasscutter/game/inventory/GenshinItem.java
Normal 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();
|
||||
}
|
||||
}
|
353
src/main/java/emu/grasscutter/game/inventory/Inventory.java
Normal file
353
src/main/java/emu/grasscutter/game/inventory/Inventory.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
27
src/main/java/emu/grasscutter/game/inventory/ItemDef.java
Normal file
27
src/main/java/emu/grasscutter/game/inventory/ItemDef.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
45
src/main/java/emu/grasscutter/game/inventory/ItemType.java
Normal file
45
src/main/java/emu/grasscutter/game/inventory/ItemType.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user