mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-21 18:58: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